Build the Panama Hyprland desktop

This commit is contained in:
Gabriel Brown
2026-08-17 10:32:55 -04:00
parent 67033f2a31
commit 5248883e4b
190 changed files with 18554 additions and 34 deletions
@@ -0,0 +1,252 @@
pragma Singleton
// A single focus session bound to a Hyprland workspace. The deadline and the
// few user decisions are persisted; the once-per-second display value is not,
// so an active session does not turn into a constant disk writer.
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
import QtQuick
import qs.config
Singleton {
id: root
readonly property alias active: state.active
readonly property alias paused: state.paused
readonly property alias capsuleVisible: state.capsuleVisible
readonly property alias workspaceId: state.workspaceId
readonly property alias workspaceLabel: state.workspaceLabel
readonly property alias monitorName: state.monitorName
// Kept in memory only. The persisted deadline is enough to reconstruct it
// after a reload, and paused sessions persist their fixed remainder.
property double nowMs: Date.now()
readonly property int remainingSeconds: {
if (!state.active)
return 0;
if (state.paused)
return Math.max(0, state.pausedRemainingSeconds);
return Math.max(0, Math.ceil((state.deadlineMs - root.nowMs) / 1000));
}
readonly property string remainingText: {
const total = root.remainingSeconds;
const hours = Math.floor(total / 3600);
const minutes = Math.floor((total % 3600) / 60);
const seconds = total % 60;
const pad = value => String(value).padStart(2, "0");
return hours > 0 ? `${hours}:${pad(minutes)}:${pad(seconds)}` : `${minutes}:${pad(seconds)}`;
}
readonly property string statusText: state.paused ? "Paused" : "Do Not Disturb · Caffeine";
FileView {
id: stateFile
path: Quickshell.stateDir + "/focus-session.json"
blockLoading: true
printErrors: false
atomicWrites: true
onLoaded: restoreTimer.restart()
JsonAdapter {
id: state
property bool active: false
property bool paused: false
property bool capsuleVisible: false
property int workspaceId: 0
property string workspaceLabel: ""
property string monitorName: ""
property double deadlineMs: 0
property int pausedRemainingSeconds: 0
property bool previousDnd: false
property bool previousCaffeine: false
}
}
Timer {
interval: 1000
repeat: true
running: state.active && !state.paused
triggeredOnStart: true
onTriggered: {
root.nowMs = Date.now();
if (state.deadlineMs > 0 && root.nowMs >= state.deadlineMs)
root.end(true);
}
}
// Defer restoration until the other service singletons have completed
// construction. An active focus session owns these states across reloads.
Timer {
id: restoreTimer
interval: 0
onTriggered: root.restore()
}
Component.onCompleted: {
// `blockLoading` usually makes this true immediately. Keep the loaded
// signal above as the authoritative path for slower storage.
if (stateFile.loaded)
restoreTimer.restart();
}
function start(minutes: int): void {
const duration = Math.max(1, minutes);
const workspace = root.currentPositiveWorkspace();
// Replacing an active session first releases exactly the state it owns.
if (state.active)
root.end(false);
state.previousDnd = Notifs.doNotDisturb;
state.previousCaffeine = Caffeine.enabled;
Notifs.doNotDisturb = true;
Caffeine.enabled = true;
state.workspaceId = workspace ? workspace.id : 1;
state.workspaceLabel = root.displayWorkspaceName(workspace);
state.monitorName = Hyprland.focusedMonitor?.name ?? "";
state.pausedRemainingSeconds = duration * 60;
state.deadlineMs = Date.now() + duration * 60 * 1000;
state.paused = false;
state.capsuleVisible = true;
state.active = true;
root.nowMs = Date.now();
stateFile.writeAdapter();
}
function startDefault(): void {
root.start(Settings.focusDurationMinutes);
}
// The discoverable "start or show" behavior used by quick settings and
// Super+Shift+F. A second invocation never destroys work accidentally.
function reveal(): void {
if (!state.active) {
root.startDefault();
return;
}
state.capsuleVisible = true;
stateFile.writeAdapter();
}
function dismiss(): void {
state.capsuleVisible = false;
stateFile.writeAdapter();
}
function pauseOrResume(): void {
if (!state.active)
return;
root.nowMs = Date.now();
if (state.paused) {
state.deadlineMs = root.nowMs + state.pausedRemainingSeconds * 1000;
state.paused = false;
} else {
state.pausedRemainingSeconds = root.remainingSeconds;
state.paused = true;
}
stateFile.writeAdapter();
}
function activateWorkspace(): void {
if (!state.active || state.workspaceId <= 0)
return;
const all = Hyprland.workspaces?.values ?? [];
for (const workspace of all) {
if (workspace && workspace.id === state.workspaceId) {
workspace.activate();
return;
}
}
// Dynamic workspaces can disappear while the session is running.
// Focusing the stored positive id recreates the destination cleanly.
Hyprland.dispatch(`hl.dsp.focus({ workspace = ${state.workspaceId} })`);
}
function end(completed: bool): void {
if (!state.active)
return;
const previousDnd = state.previousDnd;
const previousCaffeine = state.previousCaffeine;
const finishedWorkspaceId = state.workspaceId;
const finishedWorkspace = state.workspaceLabel;
state.active = false;
state.paused = false;
state.capsuleVisible = false;
state.workspaceId = 0;
state.workspaceLabel = "";
state.monitorName = "";
state.deadlineMs = 0;
state.pausedRemainingSeconds = 0;
stateFile.writeAdapter();
Notifs.doNotDisturb = previousDnd;
Caffeine.enabled = previousCaffeine;
if (completed) {
StatusEvents.publish({
key: "focus-complete",
glyph: "\u{F051F}",
title: "Focus complete",
detail: finishedWorkspace ? `${finishedWorkspace} session finished` : "Your focus session finished",
tone: "ok",
priority: StatusEvents.importantPriority,
actionId: "open-workspace",
actionData: String(finishedWorkspaceId)
});
Quickshell.execDetached([
"notify-send",
"-a", "Panama",
"-i", "appointment-soon-symbolic",
"Focus complete",
finishedWorkspace ? `${finishedWorkspace} session finished` : "Your focus session finished"
]);
}
}
function restore(): void {
root.nowMs = Date.now();
if (!state.active)
return;
if (!state.paused && state.deadlineMs <= root.nowMs) {
root.end(true);
return;
}
// Caffeine and the in-process notification server restart on a shell
// reload. The persisted session therefore has to reclaim both states.
Notifs.doNotDisturb = true;
Caffeine.enabled = true;
}
function currentPositiveWorkspace(): var {
const focused = Hyprland.focusedWorkspace;
if (focused && focused.id > 0)
return focused;
const all = Hyprland.workspaces?.values ?? [];
for (const workspace of all) {
if (workspace && workspace.id > 0)
return workspace;
}
return null;
}
function displayWorkspaceName(workspace: var): string {
if (!workspace)
return "Workspace 1";
const name = workspace.name || String(workspace.id);
return /^\d+$/.test(name) ? `Workspace ${name}` : name;
}
}