Build the Panama Hyprland desktop
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
//@ pragma IconTheme Adwaita
|
||||
//@ pragma DropExpensiveFonts
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Panama :: Quickshell
|
||||
//
|
||||
// The desktop shell — top bar, dock, overview, quick settings, notifications,
|
||||
// screenshot/record UI and power menu. Replaces GNOME Shell plus the Forge,
|
||||
// Dash-to-Dock, Openbar, Vitals, AppIndicator, Caffeine and clipboard
|
||||
// extensions it was carrying.
|
||||
//
|
||||
// This file is wiring only. It owns two things and nothing else:
|
||||
// 1. Which windows exist, and on which screens.
|
||||
// 2. The IPC surface that hypr/keybinds.lua calls into.
|
||||
//
|
||||
// All shared state lives in services/ShellState.qml, so no module needs a
|
||||
// reference to any other module.
|
||||
//
|
||||
// Layer namespaces set by the modules below are matched by layer rules in
|
||||
// hypr/rules.lua (blur, dim, no-screen-share). Keep the two in sync.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
import qs.config
|
||||
import qs.services
|
||||
import qs.modules.bar
|
||||
import qs.modules.dock
|
||||
import qs.modules.overview
|
||||
import qs.modules.quicksettings
|
||||
import qs.modules.notifications
|
||||
import qs.modules.capture
|
||||
import qs.modules.powermenu
|
||||
import qs.modules.clipboard
|
||||
import qs.modules.datemenu
|
||||
import qs.modules.focus
|
||||
import qs.modules.signals
|
||||
import qs.modules.settings
|
||||
|
||||
// Clipboard and datemenu ship explicit qmldir manifests because they were
|
||||
// added while this daily-driver shell was already running; that also keeps
|
||||
// future hot reloads deterministic instead of relying on module synthesis.
|
||||
|
||||
ShellRoot {
|
||||
id: root
|
||||
|
||||
// ── Per-screen surfaces ─────────────────────────────────────────────────
|
||||
// Variants rebuilds these when monitors are added or removed, so plugging
|
||||
// in a second display Just Works without a reload.
|
||||
//
|
||||
// Do NOT redeclare `required property var modelData` on the delegate here:
|
||||
// Bar and Dock already declare a plain `modelData` and bind `screen` to it.
|
||||
// Redeclaring it shadows theirs, `screen` never resolves, and the windows
|
||||
// are constructed but silently never map — with no error logged.
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
Bar {
|
||||
idleInhibited: Caffeine.enabled
|
||||
onRequestQuickSettings: ShellState.toggle("quicksettings")
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
Dock {}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
SignalGlass {}
|
||||
}
|
||||
|
||||
// ── Single-instance overlays ────────────────────────────────────────────
|
||||
// These are always constructed but only *visible* when ShellState says so.
|
||||
// They're cheap while hidden, and keeping them alive means opening the
|
||||
// overview or quick settings is instant rather than paying a load.
|
||||
Overview {}
|
||||
// QuickSettings is the window; QuickSettingsPanel is its content.
|
||||
QuickSettings {}
|
||||
DateMenu {}
|
||||
ClipboardPanel {}
|
||||
CaptureOverlay {}
|
||||
IntelligenceResult {}
|
||||
ActivityPanel {}
|
||||
PowerMenu {}
|
||||
SettingsWindow {}
|
||||
|
||||
// Toasts are their own always-on layer; they must be able to appear
|
||||
// without any overlay being open.
|
||||
Toasts {}
|
||||
|
||||
// ── IPC ─────────────────────────────────────────────────────────────────
|
||||
// Called from hypr/keybinds.lua as `qs ipc call <target> <function>`.
|
||||
//
|
||||
// Every parameter AND the return type must be annotated, or Quickshell
|
||||
// silently declines to register the function — it will not warn you.
|
||||
|
||||
IpcHandler {
|
||||
target: "overview"
|
||||
function toggle(): void { ShellState.toggle("overview"); }
|
||||
function open(): void { ShellState.openOverview(0); }
|
||||
function search(query: string): void { ShellState.searchOverview(query); }
|
||||
function close(): void { ShellState.close(); }
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "focus"
|
||||
|
||||
function start(): void { FocusSession.startDefault(); }
|
||||
function reveal(): void { FocusSession.reveal(); }
|
||||
function dismiss(): void { FocusSession.dismiss(); }
|
||||
function pause(): void { FocusSession.pauseOrResume(); }
|
||||
function workspace(): void { FocusSession.activateWorkspace(); }
|
||||
function overview(): void {
|
||||
ShellState.openOverview(FocusSession.workspaceId);
|
||||
FocusSession.dismiss();
|
||||
}
|
||||
function end(): void { FocusSession.end(false); }
|
||||
function status(): string {
|
||||
return JSON.stringify({
|
||||
active: FocusSession.active,
|
||||
paused: FocusSession.paused,
|
||||
capsuleVisible: FocusSession.capsuleVisible,
|
||||
workspaceId: FocusSession.workspaceId,
|
||||
remaining: FocusSession.remainingText,
|
||||
caffeine: Caffeine.enabled,
|
||||
doNotDisturb: Notifs.doNotDisturb
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// A small diagnostics surface doubles as a deterministic contract harness.
|
||||
// Real producers call StatusEvents.publish() directly; fixtures never run
|
||||
// unless explicitly requested over IPC by the test suite.
|
||||
IpcHandler {
|
||||
target: "status-events"
|
||||
|
||||
function screenshot(path: string): void {
|
||||
StatusEvents.publish({
|
||||
key: "capture-screenshot",
|
||||
glyph: "\u{F0100}",
|
||||
title: "Screenshot captured",
|
||||
detail: path.split("/").pop(),
|
||||
tone: "ok",
|
||||
priority: StatusEvents.importantPriority,
|
||||
actionId: "open-path",
|
||||
actionData: path
|
||||
});
|
||||
}
|
||||
|
||||
function fixture(name: string): void {
|
||||
if (name === "ambient") {
|
||||
StatusEvents.publish({
|
||||
key: "device-output",
|
||||
glyph: "\u{F07E7}",
|
||||
title: "USB Audio",
|
||||
detail: "Audio output connected",
|
||||
priority: StatusEvents.ambientPriority
|
||||
});
|
||||
} else if (name === "ambient-replacement") {
|
||||
StatusEvents.publish({
|
||||
key: "device-output",
|
||||
glyph: "\u{F07E7}",
|
||||
title: "Studio Display",
|
||||
detail: "Audio output connected",
|
||||
priority: StatusEvents.ambientPriority
|
||||
});
|
||||
} else if (name === "critical") {
|
||||
StatusEvents.publish({
|
||||
key: "privacy-microphone",
|
||||
glyph: "\u{F036C}",
|
||||
title: "Microphone in use",
|
||||
detail: "An application is capturing audio",
|
||||
tone: "danger",
|
||||
priority: StatusEvents.criticalPriority,
|
||||
actionId: "open-activity"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function dismiss(): void { StatusEvents.dismiss(); }
|
||||
function reset(): void { StatusEvents.reset(); }
|
||||
function status(): string {
|
||||
const event = StatusEvents.activeEvent;
|
||||
return JSON.stringify({
|
||||
active: StatusEvents.active,
|
||||
activeKey: event?.key ?? "",
|
||||
title: event?.title ?? "",
|
||||
priority: event?.priority ?? 0,
|
||||
queueLength: StatusEvents.queueLength
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "activity"
|
||||
|
||||
function fixture(name: string): void { PrivacyState.applyFixture(name); }
|
||||
function clear(): void { PrivacyState.clearFixture(); }
|
||||
function open(): void { ShellState.open("activity"); }
|
||||
function close(): void { ShellState.close(); }
|
||||
function toggle(): void { ShellState.toggle("activity"); }
|
||||
function stopRecording(): void { Capture.stopRecording(); }
|
||||
function status(): string {
|
||||
return JSON.stringify({
|
||||
microphoneActive: PrivacyState.microphoneActive,
|
||||
cameraActive: PrivacyState.cameraActive,
|
||||
screenSharingActive: PrivacyState.screenSharingActive,
|
||||
recordingActive: PrivacyState.recordingActive,
|
||||
activeKinds: PrivacyState.activeKinds
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Referencing the transition monitor here ensures the singleton is alive
|
||||
// even before any quick-settings device list has been opened.
|
||||
Connections { target: DeviceEvents }
|
||||
|
||||
IpcHandler {
|
||||
target: "quicksettings"
|
||||
function toggle(): void { ShellState.toggle("quicksettings"); }
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "settings"
|
||||
function open(): void { ShellState.openSettings(DesktopPreferences.lastPage || "home"); }
|
||||
function toggle(): void { ShellState.toggleSettings(); }
|
||||
function close(): void { ShellState.closeSettings(); }
|
||||
function page(name: string): void { ShellState.openSettings(name); }
|
||||
function status(): string {
|
||||
return JSON.stringify({ open: ShellState.settingsOpen, page: ShellState.settingsPage });
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "settings-system"
|
||||
function refresh(): void { SystemSettings.refresh(); }
|
||||
function status(): string {
|
||||
return JSON.stringify({
|
||||
monitor: SystemSettings.monitorName,
|
||||
width: SystemSettings.monitorWidth,
|
||||
height: SystemSettings.monitorHeight,
|
||||
autoHdr: SystemSettings.autoHdr,
|
||||
vrrPolicy: SystemSettings.vrrPolicy,
|
||||
directScanoutPolicy: SystemSettings.directScanoutPolicy,
|
||||
busy: SystemSettings.busy,
|
||||
lastError: SystemSettings.lastError
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "notifications"
|
||||
function toggle(): void { ShellState.toggle("notifications"); }
|
||||
function clear(): void { Notifs.dismissAll(); }
|
||||
function dnd(): bool {
|
||||
Notifs.doNotDisturb = !Notifs.doNotDisturb;
|
||||
return Notifs.doNotDisturb;
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "clipboard"
|
||||
function toggle(): void { ShellState.toggle("clipboard"); }
|
||||
function open(): void { ShellState.open("clipboard"); }
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "powermenu"
|
||||
function toggle(): void { ShellState.toggle("powermenu"); }
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "capture"
|
||||
// Pressing Print while recording stops the recording instead of
|
||||
// opening the picker again — otherwise there's no way to stop it
|
||||
// without the indicator.
|
||||
function open(): void {
|
||||
if (Capture.recording)
|
||||
Capture.stopRecording();
|
||||
else
|
||||
Capture.open();
|
||||
}
|
||||
function screenNow(): void { Capture.screenNow(); }
|
||||
function windowNow(): void { Capture.windowNow(); }
|
||||
function stop(): void { Capture.stopRecording(); }
|
||||
function close(): void { Capture.close(); }
|
||||
function status(): string {
|
||||
return JSON.stringify({
|
||||
open: ShellState.captureOpen,
|
||||
mode: Capture.mode,
|
||||
recordMode: Capture.recordMode,
|
||||
intelligenceMode: Capture.intelligenceMode
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "screen-intelligence"
|
||||
function open(): void { Capture.openIntelligence(); }
|
||||
function close(): void { ScreenIntelligence.close(); }
|
||||
function analyzeFile(path: string): void { ScreenIntelligence.analyzeFile(path); }
|
||||
function copy(): void { ScreenIntelligence.copyText(); }
|
||||
function status(): string {
|
||||
return JSON.stringify({
|
||||
phase: ScreenIntelligence.phase,
|
||||
ocrReady: ScreenIntelligence.ocrReady,
|
||||
codeReady: ScreenIntelligence.codeReady,
|
||||
englishReady: ScreenIntelligence.englishReady,
|
||||
text: ScreenIntelligence.text,
|
||||
codeType: ScreenIntelligence.codeType,
|
||||
codeValue: ScreenIntelligence.codeValue,
|
||||
error: ScreenIntelligence.error
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ── Diagnostics ─────────────────────────────────────────────────────────
|
||||
// Quickshell pops a reload notice on every hot reload, which is noise when
|
||||
// you're editing. Failures still surface in the log.
|
||||
Connections {
|
||||
target: Quickshell
|
||||
function onReloadCompleted(): void { Quickshell.inhibitReloadPopup(); }
|
||||
function onReloadFailed(error: string): void {
|
||||
console.warn("Panama shell reload failed:", error);
|
||||
Quickshell.inhibitReloadPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user