diff options
Diffstat (limited to 'modules/desktop/eww/niri-status.lua')
| -rw-r--r-- | modules/desktop/eww/niri-status.lua | 254 |
1 files changed, 0 insertions, 254 deletions
diff --git a/modules/desktop/eww/niri-status.lua b/modules/desktop/eww/niri-status.lua deleted file mode 100644 index 771a12c..0000000 --- a/modules/desktop/eww/niri-status.lua +++ /dev/null @@ -1,254 +0,0 @@ -#!/usr/bin/env luajit --- niri-status: emit {workspaces: [...], active_windows: [...]} as one JSON --- object per line, driven by a single niri event-stream subscription. --- --- This replaces the old activate-windows.lua + workspaces.lua pair. Those --- ran as two separate long-lived processes, each independently subscribed --- to niri's event-stream and, on every relevant event, independently --- re-fetched both `niri msg -j windows` and `niri msg -j workspaces` via a --- shelled-out io.popen. Combined that was up to 8 process spawns per single --- event across both scripts. Here there's one event-stream subscription, --- and one shared windows/workspaces fetch per event, significantly reducing --- the total number of spawned processes and resting children. - -local cjson = require("cjson") - -local Gio, GioUnix, Gtk, Gdk, icon_theme = nil, nil, nil, nil, nil -local DesktopAppInfo = nil -do - local lgi_ok, lgi = pcall(require, "lgi") - if lgi_ok then - local gio_ok, gio = pcall(function() return lgi.Gio end) - if gio_ok then Gio = gio end - - local giounix_ok, giounix = pcall(function() return lgi.GioUnix end) - if giounix_ok then GioUnix = giounix end - - local gtk_ok, gtk = pcall(function() return lgi.Gtk end) - if gtk_ok then Gtk = gtk end - - local gdk_ok, gdk = pcall(function() return lgi.Gdk end) - if gdk_ok then Gdk = gdk end - - DesktopAppInfo = (GioUnix and GioUnix.DesktopAppInfo) or (Gio and Gio.DesktopAppInfo) - - if Gtk then - local get_ok, theme = pcall(Gtk.IconTheme.get_default) - if get_ok and theme then - icon_theme = theme - elseif Gdk then - local disp_ok, display = pcall(Gdk.Display.get_default) - if disp_ok and display then - local get_ok2, theme2 = pcall(Gtk.IconTheme.get_for_display, display) - if get_ok2 and theme2 then - icon_theme = theme2 - end - end - end - end - end -end - -local icon_cache = {} - -local function g_list_to_table(gl) - if gl == nil then return {} end - if type(gl) == "table" then return gl end - local t = {} - local cur = gl - while cur do - t[#t + 1] = cur.data - cur = cur.next - end - return t -end - -local function resolve_icon_path(app_id) - if not app_id or app_id == "" then return "" end - if icon_cache[app_id] then return icon_cache[app_id] end - - local icon_path = "" - if DesktopAppInfo and Gtk and icon_theme then - local function try_desktop_app_info(desktop_id) - local ok, info = pcall(DesktopAppInfo.new, desktop_id) - if ok and info then return info end - return nil - end - - local app_info = try_desktop_app_info(app_id .. ".desktop") - or try_desktop_app_info(string.lower(app_id) .. ".desktop") - - if not app_info then - local lc = string.lower(app_id) - for _, info in ipairs(g_list_to_table(Gio.AppInfo.get_all())) do - if info:is_a(DesktopAppInfo) then - local wm = info:get_startup_wm_class() - if wm and string.lower(wm) == lc then - app_info = info - break - end - end - end - end - - local gicon = app_info and app_info:get_icon() - if gicon then - local ok2, lookup = pcall(icon_theme.lookup_by_gicon, icon_theme, gicon, 24, Gtk.IconLookupFlags.FORCE_SIZE) - if ok2 and lookup then - icon_path = lookup:get_filename() or "" - end - end - if icon_path == "" then - local ok2, lookup = pcall(icon_theme.lookup_icon, icon_theme, app_id, 24, Gtk.IconLookupFlags.FORCE_SIZE) - if ok2 and lookup then - icon_path = lookup:get_filename() or "" - end - end - end - - icon_cache[app_id] = icon_path - return icon_path -end - --- Safely run niri msg without crashing due to luaposix version changes -local function niri_json(command) - local f = io.popen("niri msg -j " .. command .. " 2>/dev/null") - if not f then return nil end - local out = f:read("*a") - f:close() - local ok, decoded = pcall(cjson.decode, out) - if ok then return decoded end - return nil -end - -local function window_sort_key(w) - local layout = w.layout or {} - local pos = layout.pos_in_scrolling_layout - if pos then return { 0, pos[1], pos[2] } end - return { 1, w.id, 0 } -end - -local function lt(a, b) - for k = 1, math.max(#a, #b) do - local x, y = a[k], b[k] - if y == nil then return false end - if x == nil then return true end - if x ~= y then return x < y end - end - return false -end - --- Ensures cjson turns empty tables into `[]` instead of `{}` -local empty_array = cjson.empty_array or setmetatable({}, { __jsontype = "array" }) - -local function build_state() - local workspaces_raw = niri_json("workspaces") or {} - local windows_raw = niri_json("windows") or {} - - local windows_by_id = {} - for _, w in ipairs(windows_raw) do - windows_by_id[w.id] = w - end - - local active_id - for _, ws in ipairs(workspaces_raw) do - if ws.is_active then - active_id = ws.id - break - end - end - - local on_active = {} - for _, w in ipairs(windows_raw) do - if w.workspace_id == active_id then - on_active[#on_active + 1] = w - end - end - table.sort(on_active, function(a, b) - return lt(window_sort_key(a), window_sort_key(b)) - end) - - local active_windows_result = {} - for _, w in ipairs(on_active) do - active_windows_result[#active_windows_result + 1] = { - id = w.id, - title = w.title or "", - app_id = w.app_id or "", - is_focused = w.is_focused or false, - icon = resolve_icon_path(w.app_id or ""), - } - end - -- Enforce JSON Array even if empty - if #active_windows_result == 0 then active_windows_result = empty_array end - - local ws_list = {} - for _, ws in ipairs(workspaces_raw) do - ws_list[#ws_list + 1] = ws - end - table.sort(ws_list, function(a, b) return a.idx < b.idx end) - - local workspaces_result = {} - for _, ws in ipairs(ws_list) do - local active_win = windows_by_id[ws.active_window_id] - local app_id = (active_win and active_win.app_id) or "" - - local ws_name = (type(ws.name) == "string" and ws.name ~= "userdata: NULL" and ws.name) or ("Workspace " .. tostring(ws.idx)) - - local ws_windows = {} - for _, w in ipairs(windows_raw) do - if w.workspace_id == ws.id then - ws_windows[#ws_windows + 1] = w - end - end - table.sort(ws_windows, function(a, b) - return lt(window_sort_key(a), window_sort_key(b)) - end) - - local lines = { ws_name } - for idx, w in ipairs(ws_windows) do - lines[#lines + 1] = string.format("%d. %s: %s", idx, (w.app_id and w.app_id ~= "" and w.app_id) or "unknown", (w.title and w.title ~= "" and w.title) or "(untitled)") - end - - workspaces_result[#workspaces_result + 1] = { - idx = ws.idx, - id = ws.id, - is_active = ws.is_active or false, - icon = resolve_icon_path(app_id), - tooltip = #ws_windows > 0 and table.concat(lines, "\n") or ws_name, - } - end - -- Enforce JSON Array even if empty - if #workspaces_result == 0 then workspaces_result = empty_array end - - -- Output as a ROOT JSON OBJECT - return { - workspaces = workspaces_result, - active_windows = active_windows_result, - } -end - -local function emit_state() - local ok, result = pcall(build_state) - if not ok then - io.stderr:write(tostring(result) .. "\n") - return - end - print(cjson.encode(result)) - io.stdout:flush() -end - -local RELEVANT_EVENTS = { - WindowOpenedOrChanged = true, WindowClosed = true, WindowFocusChanged = true, - WorkspaceActivated = true, WorkspacesChanged = true, WindowsChanged = true, - WorkspaceUrgencyChanged = true, WorkspaceActiveWindowChanged = true -} - -local proc = io.popen("niri msg -j event-stream") -emit_state() -for line in proc:lines() do - local ok, obj = pcall(cjson.decode, line) - if ok and obj then - local key = next(obj) - if RELEVANT_EVENTS[key] then emit_state() end - end -end |
