A PowerMenu window exists per screen and every one of them lit up on open. Latch the focused monitor in Actions at open time — same shape as launcherScreen — and gate each window's open state on it, so exactly one copy draws and takes keyboard focus. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
118 lines
4.7 KiB
QML
118 lines
4.7 KiB
QML
pragma Singleton
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import Quickshell.Hyprland
|
|
import "root:/services"
|
|
|
|
// Shell-wide UI state and the handful of external commands the shell fires.
|
|
// Also exposes an IPC surface so Hyprland keybinds can drive the same actions
|
|
// (`qs ipc call shell lock`, `qs ipc call shell power`, ...).
|
|
Singleton {
|
|
id: root
|
|
|
|
// ------------------------------------------------------------- UI state
|
|
property bool powerMenuOpen: false
|
|
property bool lockRequested: false
|
|
property bool sidebarOpen: false
|
|
property bool launcherOpen: false
|
|
|
|
// Seed text for the next launcher open. Lets a keybind or a script hand the
|
|
// launcher a query it did not have to type — `qs ipc call shell prompt
|
|
// "https://…"` is an open-with dialog for anything that can run a command.
|
|
property string launcherSeed: ""
|
|
|
|
// A launcher window exists on every screen, but only one of them is being
|
|
// typed at. Latch the focused monitor at open time so the window appears
|
|
// there alone — latched rather than live, because the launcher itself takes
|
|
// keyboard focus and because a window should not hop mid-query.
|
|
property string launcherScreen: ""
|
|
|
|
// Same story for the power menu: a window per screen, one of them wanted.
|
|
property string powerMenuScreen: ""
|
|
|
|
signal osdRequested(string kind)
|
|
|
|
function togglePowerMenu() {
|
|
if (!root.powerMenuOpen)
|
|
root.powerMenuScreen = root.focusedScreenName();
|
|
root.powerMenuOpen = !root.powerMenuOpen;
|
|
}
|
|
function closePowerMenu() { root.powerMenuOpen = false; }
|
|
function lock() { root.powerMenuOpen = false; root.lockRequested = true; }
|
|
|
|
function focusedScreenName(): string {
|
|
return Hyprland.focusedMonitor?.name
|
|
?? Quickshell.screens[0]?.name
|
|
?? "";
|
|
}
|
|
|
|
function toggleLauncher() {
|
|
if (!root.launcherOpen) {
|
|
root.launcherSeed = "";
|
|
root.launcherScreen = root.focusedScreenName();
|
|
}
|
|
root.launcherOpen = !root.launcherOpen;
|
|
}
|
|
function closeLauncher() { root.launcherOpen = false; }
|
|
|
|
// Stand in for `fuzzel --dmenu`. See Apps.beginDmenu for the transport.
|
|
function dmenuLauncher(inPath: string, outPath: string, prompt: string) {
|
|
Apps.beginDmenu(inPath, outPath, prompt);
|
|
root.launcherSeed = "";
|
|
root.launcherScreen = root.focusedScreenName();
|
|
root.launcherOpen = false;
|
|
root.launcherOpen = true;
|
|
}
|
|
|
|
function promptLauncher(seed: string) {
|
|
root.launcherSeed = seed;
|
|
root.launcherScreen = root.focusedScreenName();
|
|
root.launcherOpen = false;
|
|
root.launcherOpen = true;
|
|
}
|
|
|
|
// ------------------------------------------------------------- commands
|
|
// Detached, because nothing here is ever read back. Rotating a fixed set of
|
|
// `Process` slots only moves the collision: `uwsm app` lives as long as the
|
|
// app it started, so the fourth launch reclaims the first slot and kills the
|
|
// app still holding it — and a shell reload kills every one of them at once.
|
|
function run(argv: var) {
|
|
Quickshell.execDetached(argv);
|
|
}
|
|
|
|
function launch(app: string) {
|
|
root.run(["uwsm", "app", "--", app]);
|
|
}
|
|
|
|
// --------------------------------------------------------------- power
|
|
function logout() { root.run(["uwsm", "stop"]); }
|
|
function reboot() { root.run(["systemctl", "reboot"]); }
|
|
function shutdown() { root.run(["systemctl", "poweroff"]); }
|
|
function suspend() { root.lock(); root.run(["systemctl", "suspend"]); }
|
|
function hibernate() { root.lock(); root.run(["systemctl", "hibernate"]); }
|
|
function reloadShell() { Quickshell.reload(true); }
|
|
|
|
// ------------------------------------------------------------- helpers
|
|
function openAudioSettings() { root.launch("pavucontrol"); }
|
|
function openSystemMonitor() { root.run(["alacritty", "-e", "btop"]); }
|
|
function openNetworkSettings() { root.run(["nm-connection-editor"]); }
|
|
function copyText(s: string) { root.run(["wl-copy", "--", s]); }
|
|
|
|
IpcHandler {
|
|
target: "shell"
|
|
|
|
function lock(): void { root.lock(); }
|
|
function power(): void { root.togglePowerMenu(); }
|
|
function launcher(): void { root.toggleLauncher(); }
|
|
function prompt(seed: string): void { root.promptLauncher(seed); }
|
|
function dmenu(inPath: string, outPath: string, prompt: string): void {
|
|
root.dmenuLauncher(inPath, outPath, prompt);
|
|
}
|
|
function reloadConfig(): void { root.reloadShell(); }
|
|
function toggleDnd(): void { Notifs.toggleDnd(); }
|
|
function clearNotifications(): void { Notifs.clearAll(); }
|
|
}
|
|
}
|