2023-11-21 01:29:46 -05:00
|
|
|
import App from 'resource:///com/github/Aylur/ags/app.js';
|
2023-11-13 13:19:14 -05:00
|
|
|
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
|
2023-10-31 08:32:40 -04:00
|
|
|
import Service from 'resource:///com/github/Aylur/ags/service.js';
|
2023-12-24 00:26:44 -05:00
|
|
|
|
2023-11-13 13:19:14 -05:00
|
|
|
import { subprocess } from 'resource:///com/github/Aylur/ags/utils.js';
|
2023-12-24 00:26:44 -05:00
|
|
|
|
|
|
|
const { GUdev } = imports.gi;
|
2023-10-24 14:21:33 -04:00
|
|
|
|
|
|
|
const UDEV_POINTERS = [
|
|
|
|
'ID_INPUT_MOUSE',
|
|
|
|
'ID_INPUT_POINTINGSTICK',
|
|
|
|
'ID_INPUT_TOUCHPAD',
|
|
|
|
'ID_INPUT_TOUCHSCREEN',
|
|
|
|
'ID_INPUT_TABLET',
|
|
|
|
];
|
2023-10-24 17:26:38 -04:00
|
|
|
const ON_RELEASE_TRIGGERS = [
|
2023-10-24 14:21:33 -04:00
|
|
|
'released',
|
|
|
|
'TOUCH_UP',
|
|
|
|
'HOLD_END',
|
|
|
|
];
|
2023-10-24 17:26:38 -04:00
|
|
|
const ON_CLICK_TRIGGERS = [
|
|
|
|
'pressed',
|
|
|
|
'TOUCH_DOWN',
|
|
|
|
];
|
2023-10-16 17:06:19 -04:00
|
|
|
|
|
|
|
|
|
|
|
class Pointers extends Service {
|
2023-10-20 23:11:21 -04:00
|
|
|
static {
|
|
|
|
Service.register(this, {
|
|
|
|
'proc-started': ['boolean'],
|
|
|
|
'proc-destroyed': ['boolean'],
|
|
|
|
'device-fetched': ['boolean'],
|
|
|
|
'new-line': ['string'],
|
2023-10-24 17:26:38 -04:00
|
|
|
'released': ['string'],
|
|
|
|
'clicked': ['string'],
|
2023-10-20 23:11:21 -04:00
|
|
|
});
|
|
|
|
}
|
2023-10-16 17:06:19 -04:00
|
|
|
|
2023-12-24 00:26:44 -05:00
|
|
|
/** @type typeof imports.gi.Gio.Subprocess */
|
2023-11-21 01:29:46 -05:00
|
|
|
#process;
|
|
|
|
#lastLine = '';
|
2023-12-24 00:26:44 -05:00
|
|
|
/** @type Array<string> */
|
2023-11-21 01:29:46 -05:00
|
|
|
#pointers = [];
|
|
|
|
#udevClient = GUdev.Client.new(['input']);
|
2023-10-16 17:06:19 -04:00
|
|
|
|
2023-11-21 01:29:46 -05:00
|
|
|
get process() {
|
|
|
|
return this.#process;
|
|
|
|
}
|
|
|
|
|
|
|
|
get lastLine() {
|
|
|
|
return this.#lastLine;
|
|
|
|
}
|
|
|
|
|
|
|
|
get pointers() {
|
|
|
|
return this.#pointers;
|
|
|
|
}
|
2023-10-16 17:06:19 -04:00
|
|
|
|
2023-10-20 23:11:21 -04:00
|
|
|
constructor() {
|
|
|
|
super();
|
2023-11-21 01:29:46 -05:00
|
|
|
this.#initUdevConnection();
|
|
|
|
this.#initAppConnection();
|
2023-10-20 23:11:21 -04:00
|
|
|
}
|
2023-10-16 17:06:19 -04:00
|
|
|
|
2023-10-24 14:21:33 -04:00
|
|
|
// FIXME: logitech mouse screws everything up on disconnect
|
2023-11-21 01:29:46 -05:00
|
|
|
#initUdevConnection() {
|
|
|
|
this.#getDevices();
|
2023-12-24 00:26:44 -05:00
|
|
|
this.#udevClient.connect('uevent',
|
|
|
|
/**
|
|
|
|
* @param {typeof imports.gi.GUdev.Client} _
|
|
|
|
* @param {string} action
|
|
|
|
*/
|
|
|
|
(_, action) => {
|
|
|
|
if (action === 'add' || action === 'remove') {
|
|
|
|
this.#getDevices();
|
|
|
|
if (this.#process) {
|
|
|
|
this.killProc();
|
|
|
|
this.startProc();
|
|
|
|
}
|
2023-10-24 17:26:38 -04:00
|
|
|
}
|
2023-12-24 00:26:44 -05:00
|
|
|
});
|
2023-10-24 17:26:38 -04:00
|
|
|
}
|
|
|
|
|
2023-11-21 01:29:46 -05:00
|
|
|
#getDevices() {
|
|
|
|
this.#pointers = [];
|
2023-12-24 00:26:44 -05:00
|
|
|
this.#udevClient.query_by_subsystem('input').forEach(
|
|
|
|
/** @param {typeof imports.gi.GUdev.Device} dev */
|
|
|
|
(dev) => {
|
|
|
|
const isPointer = UDEV_POINTERS.some(
|
|
|
|
(p) => dev.has_property(p),
|
|
|
|
);
|
|
|
|
|
|
|
|
if (isPointer) {
|
|
|
|
const hasEventFile = dev.has_property('DEVNAME') &&
|
2023-11-21 01:29:46 -05:00
|
|
|
dev.get_property('DEVNAME')
|
|
|
|
.includes('event');
|
|
|
|
|
2023-12-24 00:26:44 -05:00
|
|
|
if (hasEventFile) {
|
|
|
|
this.#pointers.push(dev.get_property('DEVNAME'));
|
|
|
|
}
|
2023-11-21 01:29:46 -05:00
|
|
|
}
|
2023-12-24 00:26:44 -05:00
|
|
|
},
|
|
|
|
);
|
2023-10-16 17:06:19 -04:00
|
|
|
|
2023-10-24 14:21:33 -04:00
|
|
|
this.emit('device-fetched', true);
|
|
|
|
}
|
2023-10-16 17:06:19 -04:00
|
|
|
|
2023-10-20 23:11:21 -04:00
|
|
|
startProc() {
|
2023-11-21 01:29:46 -05:00
|
|
|
if (this.#process) {
|
2023-10-20 23:11:21 -04:00
|
|
|
return;
|
2023-11-21 01:29:46 -05:00
|
|
|
}
|
2023-10-16 18:43:12 -04:00
|
|
|
|
2023-10-20 23:11:21 -04:00
|
|
|
const args = [];
|
2023-11-21 01:29:46 -05:00
|
|
|
|
|
|
|
this.#pointers.forEach((dev) => {
|
2023-10-24 14:21:33 -04:00
|
|
|
args.push('--device');
|
|
|
|
args.push(dev);
|
2023-10-20 23:11:21 -04:00
|
|
|
});
|
2023-10-16 17:06:19 -04:00
|
|
|
|
2023-11-21 01:29:46 -05:00
|
|
|
this.#process = subprocess(
|
2023-10-20 23:11:21 -04:00
|
|
|
['libinput', 'debug-events', ...args],
|
2023-11-21 01:29:46 -05:00
|
|
|
(output) => {
|
|
|
|
if (output.includes('cancelled')) {
|
2023-10-24 17:26:38 -04:00
|
|
|
return;
|
2023-11-21 01:29:46 -05:00
|
|
|
}
|
2023-10-24 17:26:38 -04:00
|
|
|
|
2023-11-21 01:29:46 -05:00
|
|
|
if (ON_RELEASE_TRIGGERS.some((p) => output.includes(p))) {
|
|
|
|
this.#lastLine = output;
|
|
|
|
Pointers.detectClickedOutside('released');
|
2023-10-24 17:26:38 -04:00
|
|
|
this.emit('released', output);
|
|
|
|
this.emit('new-line', output);
|
|
|
|
}
|
|
|
|
|
2023-11-21 01:29:46 -05:00
|
|
|
if (ON_CLICK_TRIGGERS.some((p) => output.includes(p))) {
|
|
|
|
this.#lastLine = output;
|
|
|
|
Pointers.detectClickedOutside('clicked');
|
2023-10-24 17:26:38 -04:00
|
|
|
this.emit('clicked', output);
|
|
|
|
this.emit('new-line', output);
|
2023-10-20 23:11:21 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
this.emit('proc-started', true);
|
|
|
|
}
|
2023-10-16 17:06:19 -04:00
|
|
|
|
2023-10-20 23:11:21 -04:00
|
|
|
killProc() {
|
2023-11-21 01:29:46 -05:00
|
|
|
if (this.#process) {
|
|
|
|
this.#process.force_exit();
|
|
|
|
this.#process = null;
|
2023-10-20 23:11:21 -04:00
|
|
|
this.emit('proc-destroyed', true);
|
2023-10-16 17:06:19 -04:00
|
|
|
}
|
|
|
|
}
|
2023-10-24 17:26:38 -04:00
|
|
|
|
2023-11-21 01:29:46 -05:00
|
|
|
#initAppConnection() {
|
2023-10-24 17:26:38 -04:00
|
|
|
App.connect('window-toggled', () => {
|
2023-11-21 01:29:46 -05:00
|
|
|
const anyVisibleAndClosable = Array.from(App.windows).some((w) => {
|
2023-12-24 00:26:44 -05:00
|
|
|
// @ts-expect-error
|
2023-12-18 23:20:32 -05:00
|
|
|
const closable = w[1].attribute?.close_on_unfocus &&
|
2023-12-24 00:26:44 -05:00
|
|
|
// @ts-expect-error
|
2023-12-18 23:20:32 -05:00
|
|
|
!(w[1].attribute?.close_on_unfocus === 'none' ||
|
2023-12-24 00:26:44 -05:00
|
|
|
// @ts-expect-error
|
|
|
|
w[1].attribute?.close_on_unfocus === 'stay');
|
2023-10-24 17:26:38 -04:00
|
|
|
|
|
|
|
return w[1].visible && closable;
|
|
|
|
});
|
|
|
|
|
2023-11-21 01:29:46 -05:00
|
|
|
if (anyVisibleAndClosable) {
|
2023-10-24 17:26:38 -04:00
|
|
|
this.startProc();
|
2023-11-21 01:29:46 -05:00
|
|
|
}
|
2023-10-24 17:26:38 -04:00
|
|
|
|
2023-11-21 01:29:46 -05:00
|
|
|
else {
|
2023-10-24 17:26:38 -04:00
|
|
|
this.killProc();
|
2023-11-21 01:29:46 -05:00
|
|
|
}
|
2023-10-24 17:26:38 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-12-24 00:26:44 -05:00
|
|
|
/** @param {string} clickStage */
|
2023-11-21 01:29:46 -05:00
|
|
|
static detectClickedOutside(clickStage) {
|
|
|
|
const toClose = Array.from(App.windows).some((w) => {
|
2023-12-24 00:26:44 -05:00
|
|
|
// @ts-expect-error
|
2023-12-18 23:20:32 -05:00
|
|
|
const closable = (w[1].attribute?.close_on_unfocus &&
|
2023-12-24 00:26:44 -05:00
|
|
|
// @ts-expect-error
|
2023-12-18 23:20:32 -05:00
|
|
|
w[1].attribute?.close_on_unfocus === clickStage);
|
2023-10-24 17:26:38 -04:00
|
|
|
|
|
|
|
return w[1].visible && closable;
|
|
|
|
});
|
2023-11-21 01:29:46 -05:00
|
|
|
|
|
|
|
if (!toClose) {
|
2023-10-24 17:26:38 -04:00
|
|
|
return;
|
2023-11-21 01:29:46 -05:00
|
|
|
}
|
2023-10-24 17:26:38 -04:00
|
|
|
|
2023-12-24 00:26:44 -05:00
|
|
|
Hyprland.sendMessage('j/layers').then((response) => {
|
|
|
|
// /** @type import('types/service/hyprland').Layer */
|
|
|
|
const layers = JSON.parse(response);
|
2023-10-24 17:26:38 -04:00
|
|
|
|
2023-12-24 00:26:44 -05:00
|
|
|
Hyprland.sendMessage('j/cursorpos').then((res) => {
|
|
|
|
const pos = JSON.parse(res);
|
2023-10-24 17:26:38 -04:00
|
|
|
|
2023-11-21 01:29:46 -05:00
|
|
|
Object.values(layers).forEach((key) => {
|
2023-12-24 00:26:44 -05:00
|
|
|
const bar = key.levels['3'].find(
|
|
|
|
/** @param {{ namespace: string }} n */
|
|
|
|
(n) => n.namespace === 'bar',
|
|
|
|
);
|
|
|
|
|
|
|
|
const widgets = key.levels['3'].filter(
|
|
|
|
/** @param {{ namespace: string }} n */
|
|
|
|
(n) => {
|
|
|
|
const window = App.getWindow(n.namespace);
|
|
|
|
|
|
|
|
// @ts-expect-error
|
|
|
|
return window?.attribute?.close_on_unfocus &&
|
|
|
|
// @ts-expect-error
|
|
|
|
window?.attribute
|
|
|
|
?.close_on_unfocus === clickStage;
|
|
|
|
},
|
|
|
|
);
|
2023-10-24 17:26:38 -04:00
|
|
|
|
2023-11-27 15:39:55 -05:00
|
|
|
if (pos.x > bar?.x && pos.x < bar?.x + bar?.w &&
|
|
|
|
pos.y > bar?.y && pos.y < bar?.y + bar?.h) {
|
2023-10-24 17:26:38 -04:00
|
|
|
|
2023-11-21 01:29:46 -05:00
|
|
|
// Don't handle clicks when on bar
|
2023-10-24 17:26:38 -04:00
|
|
|
// TODO: make this configurable
|
|
|
|
}
|
|
|
|
else {
|
2023-12-24 00:26:44 -05:00
|
|
|
widgets.forEach(
|
|
|
|
/** @param {{
|
|
|
|
* namespace: string
|
|
|
|
* x: number
|
|
|
|
* y: number
|
|
|
|
* h: number
|
|
|
|
* w: number
|
|
|
|
* }} w
|
|
|
|
*/
|
|
|
|
(w) => {
|
|
|
|
if (!(pos.x > w.x && pos.x < w.x + w.w &&
|
2023-11-21 01:29:46 -05:00
|
|
|
pos.y > w.y && pos.y < w.y + w.h)) {
|
2023-12-24 00:26:44 -05:00
|
|
|
App.closeWindow(w.namespace);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
2023-10-24 17:26:38 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}).catch(print);
|
|
|
|
}).catch(print);
|
|
|
|
}
|
2023-10-16 17:06:19 -04:00
|
|
|
}
|
|
|
|
|
2023-11-21 01:29:46 -05:00
|
|
|
export default new Pointers();
|