aboutsummaryrefslogtreecommitdiffstats
path: root/window-switcher/Windows.qml
blob: 040f4d4f3dbcea8c7a85a1ce669a5ef73f2538e8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

pragma Singleton

import Quickshell
import Quickshell.Hyprland
import QtQuick

// Everything that touches Hyprland lives here, so the filter, the sort and
// the dispatch syntax are in one place rather than spread across the UI.
Singleton {
    id: root

    // Hyprland.toplevels reads 0 until this is called.
    function refresh() { Hyprland.refreshToplevels(); }

    // Windows on a special: workspace are the scratchpad, which is reached by
    // its own bind and is not something to tab to. Windows on other
    // workspaces are kept whatever their visibility: jumping to another
    // desktop is the point.
    readonly property var windows: {
        const out = [];
        for (const t of Hyprland.toplevels.values) {
            const ws = t.workspace;
            if (!ws || String(ws.name).startsWith("special:")) continue;
            out.push(t);
        }
        // Lowest focusHistoryID is the most recently used. HyprlandToplevel
        // does not expose it as a property, only inside the raw hyprctl
        // object, where a missing one sorts last rather than as 0.
        out.sort((a, b) => root.focusOrder(a) - root.focusOrder(b));
        return out;
    }

    function focusOrder(toplevel) {
        const o = toplevel.lastIpcObject;
        const n = o ? Number(o.focusHistoryID) : NaN;
        return isNaN(n) ? Number.MAX_SAFE_INTEGER : n;
    }

    // Dispatch arguments are evaluated as Lua on Hyprland 0.56.2, so the
    // documented-looking `focuswindow address:0x...` is a syntax error that
    // fails silently. This form is the one that works.
    //
    // HyprlandToplevel.address has no 0x prefix, while the dispatch needs one.
    function addressOf(toplevel) {
        const a = String(toplevel.address);
        return a.startsWith("0x") ? a : "0x" + a;
    }

    function focus(toplevel) {
        Hyprland.dispatch(`hl.dsp.focus({ window = "address:${addressOf(toplevel)}" })`);
    }

    function closeWindow(toplevel) {
        Hyprland.dispatch(`hl.dsp.window.close({ window = "address:${addressOf(toplevel)}" })`);
    }
}