Files
appsandbox/tools/linux/agent/gnome/appsandbox-pointer@appsandbox/extension.js
T

122 lines
4.4 KiB
JavaScript

import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import Mtk from 'gi://Mtk';
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
export default class AppSandboxPointer extends Extension {
enable() {
this._uid = new Gio.Credentials().get_unix_user();
const path = GLib.build_filenamev([
GLib.get_user_runtime_dir(), 'appsandbox-pointer.sock',
]);
const file = Gio.File.new_for_path(path);
let info;
try {
info = file.query_info('unix::uid,unix::mode',
Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);
} catch (error) {
if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
throw error;
}
if (info) {
if (info.get_attribute_uint32('unix::uid') !== this._uid ||
(info.get_attribute_uint32('unix::mode') & 0o170000) !== 0o140000)
throw new Error('Invalid AppSandbox pointer socket');
file.delete(null);
}
this._service = new Gio.SocketService({active: false});
try {
this._service.add_address(Gio.UnixSocketAddress.new(path),
Gio.SocketType.STREAM, Gio.SocketProtocol.DEFAULT, null);
this._socketFile = file;
file.set_attribute_uint32('unix::mode', 0o600,
Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);
this._incoming = this._service.connect('incoming',
(_service, connection) => this._accept(connection));
this._service.start();
} catch (error) {
this.disable();
throw error;
}
}
disable() {
if (this._service) {
if (this._incoming)
this._service.disconnect(this._incoming);
this._service.stop();
this._service.close();
this._service = null;
this._incoming = 0;
}
if (this._socketFile) {
try {
this._socketFile.delete(null);
} catch (_) {
}
this._socketFile = null;
}
}
_getPosition() {
const unavailable = [-2147483648, -2147483648, 0, 0];
try {
const views = global.stage.peek_stage_views();
if (views.length !== 1)
return unavailable;
const view = views[0];
if (view.get_transform() !== Mtk.MonitorTransform.NORMAL)
return unavailable;
const framebuffer = view.get_onscreen();
if (!framebuffer)
return unavailable;
const width = framebuffer.get_width();
const height = framebuffer.get_height();
const scale = view.get_scale();
const layout = view.layout;
const [point] = global.backend.get_cursor_tracker().get_pointer();
if (!(width > 0 && height > 0 && Number.isFinite(scale) && scale > 0) ||
!Number.isFinite(point.x) || !Number.isFinite(point.y))
return unavailable;
const x = Math.trunc((point.x - layout.x) * scale);
const y = Math.trunc((point.y - layout.y) * scale);
return [Math.max(0, Math.min(width - 1, x)),
Math.max(0, Math.min(height - 1, y)), width, height];
} catch (_) {
return unavailable;
}
}
_accept(connection) {
try {
if (!this._service)
return true;
const socket = connection.get_socket();
const uid = socket.get_credentials().get_unix_user();
if (uid !== 0 && uid !== this._uid)
return true;
const position = this._getPosition();
const reply = new Uint8Array(16);
const data = new DataView(reply.buffer);
for (let i = 0; i < position.length; i++)
data.setInt32(i * 4, position[i], true);
socket.set_blocking(false);
let offset = 0;
while (offset < reply.length) {
const written = socket.send(reply.subarray(offset), null);
if (written <= 0)
break;
offset += written;
}
} catch (_) {
} finally {
try {
connection.close(null);
} catch (_) {
}
}
return true;
}
}