import Service from 'resource:///com/github/Aylur/ags/service.js'; import { execAsync, subprocess } from 'resource:///com/github/Aylur/ags/utils.js'; import GUdev from 'gi://GUdev'; const ROTATION_MAPPING = { 'normal': 0, 'right-up': 3, 'bottom-up': 2, 'left-up': 1, }; const SCREEN = 'desc:BOE 0x0964'; class Tablet extends Service { static { Service.register(this, { 'device-fetched': ['boolean'], 'autorotate-started': ['boolean'], 'autorotate-destroyed': ['boolean'], 'inputs-blocked': ['boolean'], 'inputs-unblocked': ['boolean'], 'laptop-mode': ['boolean'], 'tablet-mode': ['boolean'], 'mode-toggled': ['boolean'], }); } tabletMode = false; autorotate = undefined; blockedInputs = undefined; udevClient = GUdev.Client.new(['input']); get tabletMode() { return this.tabletMode; } get autorotate() { return this.autorotate; } constructor() { super(); this.initUdevConnection(); } blockInputs() { if (this.blockedInputs) return; this.blockedInputs = subprocess(['libinput', 'debug-events', '--grab', '--device', '/dev/input/by-path/platform-i8042-serio-0-event-kbd', '--device', '/dev/input/by-path/platform-i8042-serio-1-event-mouse', '--device', '/dev/input/by-path/platform-AMDI0010:02-event-mouse', '--device', '/dev/input/by-path/platform-thinkpad_acpi-event', '--device', '/dev/video-bus', '--device', '/dev/touchpad', ], () => {}, err => logError(err)); this.emit('inputs-blocked', true); } unblockInputs() { if (this.blockedInputs) { this.blockedInputs.force_exit(); this.blockedInputs = undefined; this.emit('inputs-unblocked', true); } } setTabletMode() { execAsync(['gsettings', 'set', 'org.gnome.desktop.a11y.applications', 'screen-keyboard-enabled', 'true']).catch(print); execAsync(['brightnessctl', '-d', 'tpacpi::kbd_backlight', 's', '0']) .catch(print); this.startAutorotate(); this.blockInputs(); this.tabletMode = true; this.emit('tablet-mode', true); this.emit('mode-toggled', true); } setLaptopMode() { execAsync(['gsettings', 'set', 'org.gnome.desktop.a11y.applications', 'screen-keyboard-enabled', 'false']).catch(print); execAsync(['brightnessctl', '-d', 'tpacpi::kbd_backlight', 's', '2']) .catch(print); this.killAutorotate(); this.unblockInputs(); this.tabletMode = false; this.emit('laptop-mode', true); this.emit('mode-toggled', true); } toggleMode() { if (this.tabletMode) this.setLaptopMode(); else this.setTabletMode(); this.emit('mode-toggled', true); } initUdevConnection() { this.getDevices(); this.udevClient.connect('uevent', (_, action) => { if (action === 'add' || action === 'remove') this.getDevices(); }); } getDevices() { this.devices = []; execAsync(['hyprctl', 'devices', '-j']).then(out => { const devices = JSON.parse(out); devices['touch'].forEach(dev => { this.devices.push(dev.name); }); devices['tablets'].forEach(dev => { this.devices.push(dev.name); }); }).catch(print); this.emit('device-fetched', true); } startAutorotate() { if (this.autorotate) return; this.autorotate = subprocess( ['monitor-sensor'], output => { if (output.includes('orientation changed')) { const orientation = ROTATION_MAPPING[output.split(' ').at(-1)]; execAsync(['hyprctl', 'keyword', 'monitor', `${SCREEN},transform,${orientation}`]).catch(print); this.devices.forEach(dev => { execAsync(['hyprctl', 'keyword', `device:"${dev}":transform`, String(orientation)]).catch(print); }); } }, err => logError(err), ); this.emit('autorotate-started', true); } killAutorotate() { if (this.autorotate) { this.autorotate.force_exit(); this.autorotate = undefined; this.emit('autorotate-destroyed', true); } } } const tabletService = new Tablet(); export default tabletService;