diff --git a/src/service/hyprland.ts b/src/service/hyprland.ts index b9cbfe6..cd77eef 100644 --- a/src/service/hyprland.ts +++ b/src/service/hyprland.ts @@ -1,7 +1,6 @@ import GLib from 'gi://GLib'; -import Gio from 'gi://Gio'; import Service from './service.js'; -import { error, execAsync, warning } from '../utils.js'; +import { error, execAsync, subprocess } from '../utils.js'; const HIS = GLib.getenv('HYPRLAND_INSTANCE_SIGNATURE'); @@ -44,13 +43,6 @@ type Active = { workspace: ActiveWorkspace } -type HyprlandState = { - active: Active - monitors: Map - workspaces: Map - clients: Map -} - class HyprlandService extends Service { static { Service.register(this, { @@ -59,55 +51,59 @@ class HyprlandService extends Service { }); } - _state!: HyprlandState; + _active: Active; + _monitors: Map; + _workspaces: Map; + _clients: Map; constructor() { if (!HIS) error('Hyprland is not running'); super(); - this._state = { - active: { - client: { - address: '', - title: '', - class: '', - }, - monitor: '', - workspace: { - id: 0, - name: '', - }, + this._active = { + client: { + address: '', + title: '', + class: '', + }, + monitor: '', + workspace: { + id: 0, + name: '', }, - monitors: new Map(), - workspaces: new Map(), - clients: new Map(), }; - + this._monitors = new Map(); + this._workspaces = new Map(); + this._clients = new Map(); this._sync(); - this._startSocat(); + + const socat = `socat -U - UNIX-CONNECT:/tmp/hypr/${HIS}/.socket2.sock`; + subprocess(['bash', '-c', socat], line => { + this._onEvent(line); + }); } async _sync() { try { const monitors = await execAsync('hyprctl -j monitors'); - this._state.monitors = new Map(); + this._monitors = new Map(); (JSON.parse(monitors as string) as Monitor[]).forEach(monitor => { - this._state.monitors.set(monitor.name, monitor); + this._monitors.set(monitor.name, monitor); if (monitor.focused) { - this._state.active.monitor = monitor.name; - this._state.active.workspace = monitor.activeWorkspace; + this._active.monitor = monitor.name; + this._active.workspace = monitor.activeWorkspace; } }); const workspaces = await execAsync('hyprctl -j workspaces'); - this._state.workspaces = new Map(); + this._workspaces = new Map(); (JSON.parse(workspaces as string) as Workspace[]).forEach(ws => { - this._state.workspaces.set(ws.id, ws); + this._workspaces.set(ws.id, ws); }); const clients = await execAsync('hyprctl -j clients'); - this._state.clients = new Map(); + this._clients = new Map(); (JSON.parse(clients as string) as Client[]).forEach(c => { const { address, @@ -119,7 +115,7 @@ class HyprlandService extends Service { floating, } = c; - this._state.clients.set(address.substring(2), { + this._clients.set(address.substring(2), { address, pid, workspace, @@ -145,16 +141,16 @@ class HyprlandService extends Service { switch (e) { case 'activewindow': - this._state.active.client.class = argv[0]; - this._state.active.client.title = argv[1]; + this._active.client.class = argv[0]; + this._active.client.title = argv[1]; break; case 'activewindowv2': - this._state.active.client.address = argv[0]; + this._active.client.address = argv[0]; break; case 'closewindow': - this._state.active.client = { + this._active.client = { class: '', title: '', address: '', @@ -172,7 +168,7 @@ class HyprlandService extends Service { break; } case 'changefloating': { - const client = this._state.clients.get(argv[0]); + const client = this._clients.get(argv[0]); if (client) client.floating = argv[1] === '1'; break; @@ -184,50 +180,6 @@ class HyprlandService extends Service { this.emit('changed'); } - - _startSocat() { - try { - const socat = ` - socat -U - UNIX-CONNECT:/tmp/hypr/${HIS}/.socket2.sock | while read lines - do - echo $lines - done`; - - const proc = Gio.Subprocess.new( - ['bash', '-c', socat], - Gio.SubprocessFlags.STDOUT_PIPE, - ); - - const pipe = proc.get_stdout_pipe(); - if (!pipe) { - warning('socat error'); - return; - } - - const stdout = new Gio.DataInputStream({ - base_stream: pipe, - close_base_stream: true, - }); - - this._readSocat(stdout); - } catch (e) { - logError(e as Error); - } - } - - _readSocat(stdout: Gio.DataInputStream) { - stdout.read_line_async(GLib.PRIORITY_LOW, null, (stdout, res) => { - try { - const line = stdout?.read_line_finish_utf8(res)[0]; - if (line) { - this._onEvent(line); - this._readSocat(stdout); - } - } catch (e) { - logError(e as Error); - } - }); - } } export default class Hyprland { @@ -239,10 +191,10 @@ export default class Hyprland { return Hyprland._instance; } - static get active() { return Hyprland.instance._state.active; } - static get monitors() { return Hyprland.instance._state.monitors; } - static get workspaces() { return Hyprland.instance._state.workspaces; } - static get clients() { return Hyprland.instance._state.clients; } + static get active() { return Hyprland.instance._active; } + static get monitors() { return Hyprland.instance._monitors; } + static get workspaces() { return Hyprland.instance._workspaces; } + static get clients() { return Hyprland.instance._clients; } static HyprctlGet(cmd: string): unknown | object { const [success, out, err] = diff --git a/src/utils.ts b/src/utils.ts index 5a957d2..d288b27 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -222,11 +222,8 @@ export function isRunning(dbusName: string) { } export function execAsync(cmd: string | string[]) { - if (typeof cmd === 'string') - cmd = cmd.split(' '); - const proc = Gio.Subprocess.new( - cmd, + typeof cmd === 'string' ? cmd.split(' ') : cmd, Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE, ); @@ -258,3 +255,44 @@ export function exec(cmd: string) { return decoder.decode(out).trim(); } + +export function subprocess( + cmd: string | string[], + callback: (out: string) => void, + onError = logError, +) { + try { + const read = (stdout: Gio.DataInputStream) => { + stdout.read_line_async(GLib.PRIORITY_LOW, null, (stdout, res) => { + try { + const output = stdout?.read_line_finish_utf8(res)[0]; + if (output) { + callback(output); + read(stdout); + } + } catch (e) { + return onError(e as Error); + } + }); + }; + + const proc = Gio.Subprocess.new( + typeof cmd === 'string' ? cmd.split(' ') : cmd, + Gio.SubprocessFlags.STDOUT_PIPE | + Gio.SubprocessFlags.STDERR_PIPE, + ); + + const pipe = proc.get_stdout_pipe(); + if (!pipe) + return onError(new Error(`subprocess ${cmd} stdout pipe is null`)); + + const stdout = new Gio.DataInputStream({ + base_stream: pipe, + close_base_stream: true, + }); + + read(stdout); + } catch (e) { + return onError(e as Error); + } +}