diff --git a/home/foot.nix b/home/foot.nix
index fffa5eb..acbc63b 100644
--- a/home/foot.nix
+++ b/home/foot.nix
@@ -33,37 +33,42 @@ in
style = "beam";
blink = "yes";
beam-thickness = 1.5;
- color = "0e0e0e cffc00";
};
mouse = {
hide-when-typing = "yes";
};
- colors = {
+ colors-dark = {
+ # Silicon Volt palette (matches home/hypr theme + mako):
+ # bgPrimary #131313, bgSecondary #0e0e0e, glassBg #1c1b1b,
+ # textPrimary #ffffff, textSecondary #e5e2e1, textMuted #adaaaa,
+ # accents: volt #cffc00, coral #ff7a59, blue #00bfff
alpha = 0.92;
background = "131313";
foreground = "e5e2e1";
- # Silicon Volt palette (anish.govind.cc)
- regular0 = "1c1b1b"; # Glass Black
- regular1 = "ff7a59"; # Coral Red
- regular2 = "cffc00"; # Electric Lime (green)
- regular3 = "e3ff66"; # Volt Light (yellow)
- regular4 = "00bfff"; # Sky Blue
+ # Cursor text and cursor (background) colors
+ cursor = "0e0e0e cffc00";
+
+ regular0 = "0e0e0e"; # bgSecondary
+ regular1 = "ff7a59"; # accentCoral
+ regular2 = "cffc00"; # accentVolt (green)
+ regular3 = "e3ff66"; # accentVoltLight (yellow)
+ regular4 = "00bfff"; # accentBlue
regular5 = "c6c6c7"; # Neutral Gray (magenta)
regular6 = "66d9ff"; # Light Cyan
- regular7 = "e5e2e1"; # Light Gray
+ regular7 = "e5e2e1"; # textSecondary
# Bright Colors
- bright0 = "5c5a59"; # Dim Gray
+ bright0 = "2a2929"; # surfaceVariant
bright1 = "ff9a82"; # Bright Coral
bright2 = "dfff66"; # Bright Lime
bright3 = "f0ff8c"; # Bright Volt
bright4 = "66d9ff"; # Bright Blue
bright5 = "e0e0e1"; # Bright Gray
bright6 = "99e6ff"; # Bright Cyan
- bright7 = "ffffff"; # Pure White
+ bright7 = "ffffff"; # textPrimary
# Selection & URLs - volt lime selection like the site
selection-foreground = "0e0e0e";
diff --git a/home/hypr/hyprland.nix b/home/hypr/hyprland.nix
index 689cd7d..9f68dec 100644
--- a/home/hypr/hyprland.nix
+++ b/home/hypr/hyprland.nix
@@ -207,7 +207,7 @@ in
_args = [
"hyprland.start"
(mkLua ''function()
- hl.exec_cmd("quickshell")
+ hl.exec_cmd("qs")
hl.exec_cmd("hyprpaper")
hl.exec_cmd("mako")
hl.exec_cmd("wl-paste --type text --watch cliphist store")
@@ -225,8 +225,9 @@ in
# Launchers & Window Controls
{ _args = [ (mkLua "mainMod .. \" + Q\"") (mkLua "hl.dsp.exec_cmd(terminal)") ]; }
{ _args = [ (mkLua "mainMod .. \" + C\"") (mkLua "hl.dsp.window.close()") ]; }
- { _args = [ (mkLua "mainMod .. \" + SPACE\"") (mkLua "hl.dsp.exec_cmd(\"quickshell ipc call spotlight toggle || rofi -show combi -show-icons\")") ]; }
- { _args = [ (mkLua "mainMod .. \" + N\"") (mkLua "hl.dsp.exec_cmd(\"quickshell ipc call controlCenter toggle || swaync-client -t -sw\")") ]; }
+ { _args = [ (mkLua "mainMod .. \" + SPACE\"") (mkLua "hl.dsp.exec_cmd(\"rofi -show combi -show-icons\")") ]; }
+ { _args = [ (mkLua "mainMod .. \" + N\"") (mkLua "hl.dsp.exec_cmd(\"swaync-client -t -sw\")") ]; }
+ { _args = [ (mkLua "mainMod .. \" + TAB\"") (mkLua "hl.dsp.exec_cmd(\"qs ipc call overview toggle\")") ]; }
{ _args = [ (mkLua "mainMod .. \" + V\"") (mkLua "hl.dsp.window.float({ action = \"toggle\" })") ]; }
{ _args = [ (mkLua "mainMod .. \" + R\"") (mkLua "hl.dsp.exec_cmd(\"rofi -show combi\")") ]; }
{ _args = [ (mkLua "mainMod .. \" + G\"") (mkLua "hl.dsp.group.toggle()") ]; }
diff --git a/home/hypr/quickshell.nix b/home/hypr/quickshell.nix
index 1330a8d..bb2e7bf 100644
--- a/home/hypr/quickshell.nix
+++ b/home/hypr/quickshell.nix
@@ -8,16 +8,12 @@ in
{
home.packages = with pkgs; [
quickshellPkg
- brightnessctl
- pamixer
- playerctl
- libnotify
- grim
- slurp
- wl-clipboard
+ qt6.qtwayland
];
- # Link Quickshell configuration into ~/.config/quickshell
+ # Quickshell overview module for Hyprland (Super+Tab workspace overview).
+ # Vendored from: https://github.com/Shanu-Kumawat/quickshell-overview
+ # Placed at the quickshell config root so a bare `qs` finds shell.qml.
xdg.configFile."quickshell" = {
source = ./quickshell;
recursive = true;
diff --git a/home/hypr/quickshell/GlobalState.qml b/home/hypr/quickshell/GlobalState.qml
deleted file mode 100644
index 45056f1..0000000
--- a/home/hypr/quickshell/GlobalState.qml
+++ /dev/null
@@ -1,351 +0,0 @@
-pragma Singleton
-import QtQuick
-import Quickshell
-import Quickshell.Hyprland
-import Quickshell.Io
-
-QtObject {
- id: root
-
- // Modal / Overlay states
- property bool spotlightVisible: false
- property bool controlCenterVisible: false
- property bool aboutMacVisible: false
- property bool appleMenuVisible: false
- property string activeDropdownMenu: ""
- property bool volumePopupVisible: false
- property bool wifiPopupVisible: false
- property bool batteryPopupVisible: false
- property bool calendarPopupVisible: false
-
- // Active Window & Compositor info
- property string activeWindowTitle: "Desktop"
- property string activeWindowClass: "Finder"
- property var runningApps: []
-
- // Date & Time
- property string timeString: "9:41 AM"
- property string dateString: "Mon Oct 21"
- property string fullDateString: "Monday, October 21, 2026"
-
- // System Monitoring States
- property int cpuPercent: 0
- property int memPercent: 0
- property string memUsedGb: "0.0"
- property string memTotalGb: "0.0"
-
- property int volume: 50
- property bool volumeMuted: false
- property int brightness: 100
-
- property bool wifiConnected: true
- property string connectionType: "off" // "wifi" | "eth" | "off"
- property string wifiSsid: "Wi-Fi"
- property string ipAddress: ""
-
- property bool batteryPresent: false
- property int batteryPercent: 100
- property bool batteryCharging: false
- property string batteryStatus: "Full"
-
- // Disk Monitoring (root filesystem)
- property int diskPercent: 0
- property string diskUsedGb: "0"
- property string diskTotalGb: "0"
-
- // GPU VRAM Monitoring (NVIDIA)
- property string gpuVramUsed: "0"
- property string gpuVramTotal: "0"
- property int gpuVramPercent: 0
-
- // System HUD state (replaces individual popup states)
- property bool systemHudVisible: false
- property string systemHudActiveSection: "" // "volume", "wifi", "calendar", etc.
-
- // Workspace tracking
- property int activeWorkspace: 1
- property var workspaceList: [1, 2, 3, 4, 5]
- property var workspaceNames: ({1: "main", 2: "browser", 3: "code", 4: "media", 5: "misc"})
-
- // Media Player State (MPRIS)
- property string mediaTitle: ""
- property string mediaArtist: ""
- property bool mediaPlaying: false
-
- // Close all popups and dropdowns
- function closeAllPopups() {
- appleMenuVisible = false;
- activeDropdownMenu = "";
- volumePopupVisible = false;
- wifiPopupVisible = false;
- batteryPopupVisible = false;
- calendarPopupVisible = false;
- systemHudVisible = false;
- }
-
- function toggleSpotlight() {
- closeAllPopups();
- spotlightVisible = !spotlightVisible;
- }
-
- function toggleControlCenter() {
- closeAllPopups();
- controlCenterVisible = !controlCenterVisible;
- }
-
- function toggleAboutMac() {
- closeAllPopups();
- aboutMacVisible = !aboutMacVisible;
- }
-
- function toggleSystemHud() {
- closeAllPopups();
- systemHudVisible = !systemHudVisible;
- }
-
- function switchWorkspace(n) {
- // NOTE: Hyprland's Lua config bridge requires typed dispatcher calls
- // (plain `hyprctl dispatch workspace 2` is mangled into invalid Lua).
- Hyprland.dispatch("hl.dsp.focus({workspace = " + n + "})");
- }
-
- // Hyprland raw event processing
- readonly property Connections hyprConn: Connections {
- target: Hyprland
- function onRawEvent(event) {
- if (!event || !event.name) return;
- const name = event.name;
- const data = event.data !== undefined ? String(event.data) : "";
-
- if (name === "workspace") {
- const wsNum = parseInt(data);
- if (!isNaN(wsNum)) root.activeWorkspace = wsNum;
- } else if (name === "activewindow") {
- const parts = data.split(",");
- const cls = parts[0] ? parts[0].trim() : "";
- const title = parts[1] ? parts[1].trim() : (cls || "");
-
- root.activeWindowClass = cls && cls.length > 0 ? cls : "Desktop";
- root.activeWindowTitle = title && title.length > 0 ? title : root.activeWindowClass;
- } else if (name === "activewindowv2" || name === "openwindow" || name === "closewindow") {
- // Update running clients
- clientsProc.running = true;
- }
- }
- }
-
- // Time Updater Timer
- readonly property Timer clockTimer: Timer {
- interval: 1000
- running: true
- repeat: true
- triggeredOnStart: true
- onTriggered: {
- const now = new Date();
- root.timeString = Qt.formatDateTime(now, "h:mm AP");
- root.dateString = Qt.formatDateTime(now, "ddd MMM d");
- root.fullDateString = Qt.formatDateTime(now, "dddd, MMMM d, yyyy");
- }
- }
-
- // System Stats Poll Timer
- readonly property Timer statsTimer: Timer {
- interval: 3000
- running: true
- repeat: true
- triggeredOnStart: true
- onTriggered: {
- cpuProc.running = true;
- memProc.running = true;
- batProc.running = true;
- wifiProc.running = true;
- audioProc.running = true;
- brightProc.running = true;
- mprisProc.running = true;
- gpuProc.running = true;
- diskProc.running = true;
- }
- }
-
- // Background Process Listeners
- readonly property Process cpuProc: Process {
- command: ["bash", "-c", "read -r c u n s i io ir st g gn < /proc/stat; t1=$((u+n+s+i+io+ir+st+g+gn)); i1=$((i+io)); sleep 0.15; read -r c u n s i io ir st g gn < /proc/stat; t2=$((u+n+s+i+io+ir+st+g+gn)); i2=$((i+io)); td=$((t2-t1)); id=$((i2-i1)); [ $td -gt 0 ] && echo $(( (td-id)*100/td )) || echo 0"]
- stdout: StdioCollector {
- onDataChanged: {
- const val = parseInt(String(data).trim());
- if (!isNaN(val)) root.cpuPercent = Math.max(0, Math.min(100, val));
- }
- }
- }
-
- readonly property Process memProc: Process {
- command: ["bash", "-c", "t=$(grep MemTotal /proc/meminfo | awk '{print $2}'); a=$(grep MemAvailable /proc/meminfo | awk '{print $2}'); if [ -n \"$t\" ] && [ -n \"$a\" ] && [ \"$t\" -gt 0 ]; then u=$((t-a)); p=$((u*100/t)); ug=$(awk \"BEGIN {printf \\\"%.1f\\\", $u/1048576}\"); tg=$(awk \"BEGIN {printf \\\"%.1f\\\", $t/1048576}\"); echo \"$p $ug $tg\"; fi"]
- stdout: StdioCollector {
- onDataChanged: {
- const parts = String(data).trim().split(" ");
- if (parts.length >= 3) {
- root.memPercent = parseInt(parts[0]) || 0;
- root.memUsedGb = parts[1];
- root.memTotalGb = parts[2];
- }
- }
- }
- }
-
- readonly property Process batProc: Process {
- command: ["bash", "-c", "b=$(find /sys/class/power_supply/ -name 'BAT*' 2>/dev/null | head -n 1); if [ -n \"$b\" ]; then cap=$(cat $b/capacity 2>/dev/null || echo 100); st=$(cat $b/status 2>/dev/null || echo Full); echo \"1 $cap $st\"; else echo \"0 100 AC\"; fi"]
- stdout: StdioCollector {
- onDataChanged: {
- const parts = String(data).trim().split(" ");
- if (parts.length >= 3) {
- root.batteryPresent = parts[0] === "1";
- root.batteryPercent = parseInt(parts[1]) || 100;
- root.batteryStatus = parts[2];
- root.batteryCharging = parts[2] === "Charging" || parts[2] === "Full";
- }
- }
- }
- }
-
- readonly property Process wifiProc: Process {
- // Emits: connectionType|ssid|ip ("wifi" | "eth" | "off")
- command: ["bash", "-c", "route=$(ip -4 route get 1.1.1.1 2>/dev/null); ip=$(echo \"$route\" | awk '{for(i=1;i<=NF;i++) if($i==\"src\") print $(i+1)}'); dev=$(echo \"$route\" | awk '{for(i=1;i<=NF;i++) if($i==\"dev\") print $(i+1)}'); type=\"off\"; ssid=\"\"; if [ -n \"$dev\" ]; then if [ -d \"/sys/class/net/$dev/wireless\" ]; then type=\"wifi\"; ssid=$(iwgetid -r 2>/dev/null); if [ -z \"$ssid\" ] && command -v nmcli &>/dev/null; then ssid=$(nmcli -t -f active,ssid dev wifi 2>/dev/null | grep '^yes:' | cut -d':' -f2); fi; else type=\"eth\"; fi; fi; echo \"$type|$ssid|$ip\""]
- stdout: StdioCollector {
- onDataChanged: {
- const parts = String(data).trim().split("|");
- if (parts.length >= 3) {
- root.connectionType = parts[0] !== "" ? parts[0] : "off";
- root.wifiSsid = parts[1];
- root.ipAddress = parts[2];
- root.wifiConnected = parts[2] !== "" && parts[2] !== "Offline";
- }
- }
- }
- }
-
- readonly property Process audioProc: Process {
- command: ["bash", "-c", "if command -v pactl &>/dev/null; then v=$(pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null | grep -Po '[0-9]+(?=%)' | head -n 1); m=$(pactl get-sink-mute @DEFAULT_SINK@ 2>/dev/null | grep -q yes && echo 1 || echo 0); echo \"${v:-50} $m\"; fi"]
- stdout: StdioCollector {
- onDataChanged: {
- const parts = String(data).trim().split(" ");
- if (parts.length >= 2) {
- root.volume = parseInt(parts[0]) || 50;
- root.volumeMuted = parts[1] === "1";
- }
- }
- }
- }
-
- readonly property Process brightProc: Process {
- command: ["bash", "-c", "if command -v brightnessctl &>/dev/null; then b=$(brightnessctl -m 2>/dev/null | cut -d, -f4 | tr -d '%'); echo \"${b:-100}\"; else echo 100; fi"]
- stdout: StdioCollector {
- onDataChanged: {
- const val = parseInt(String(data).trim());
- if (!isNaN(val)) root.brightness = val;
- }
- }
- }
-
- readonly property Process diskProc: Process {
- // Emits: usedGB totalGB percent for the root filesystem
- command: ["bash", "-c", "df -BG / 2>/dev/null | awk 'NR==2 {u=$3; t=$2; gsub(/G/,\"\",u); gsub(/G/,\"\",t); if (t+0 > 0) print u, t, int(u*100/t)}'"]
- stdout: StdioCollector {
- onDataChanged: {
- const parts = String(data).trim().split(" ");
- if (parts.length >= 3) {
- root.diskUsedGb = parts[0];
- root.diskTotalGb = parts[1];
- root.diskPercent = parseInt(parts[2]) || 0;
- }
- }
- }
- }
-
- readonly property Process mprisProc: Process {
- command: ["bash", "-c", "if command -v playerctl &>/dev/null; then t=$(playerctl metadata title 2>/dev/null); a=$(playerctl metadata artist 2>/dev/null); s=$(playerctl status 2>/dev/null); echo \"$s|$t|$a\"; fi"]
- stdout: StdioCollector {
- onDataChanged: {
- const parts = String(data).trim().split("|");
- if (parts.length >= 3) {
- root.mediaPlaying = parts[0] === "Playing";
- root.mediaTitle = parts[1] || "";
- root.mediaArtist = parts[2] || "";
- } else {
- root.mediaPlaying = false;
- root.mediaTitle = "";
- root.mediaArtist = "";
- }
- }
- }
- }
-
- readonly property Process gpuProc: Process {
- command: ["bash", "-c", "nvidia-smi --query-gpu=memory.used,memory.total --format=csv,noheader,nounits 2>/dev/null || echo '0, 0'"]
- stdout: StdioCollector {
- onDataChanged: {
- const parts = String(data).trim().split(",");
- if (parts.length >= 2) {
- const usedMiB = parseInt(parts[0].trim());
- const totalMiB = parseInt(parts[1].trim());
- if (!isNaN(usedMiB) && !isNaN(totalMiB) && totalMiB > 0) {
- root.gpuVramUsed = (usedMiB / 1024).toFixed(1);
- root.gpuVramTotal = (totalMiB / 1024).toFixed(1);
- root.gpuVramPercent = Math.round((usedMiB / totalMiB) * 100);
- }
- }
- }
- }
- }
-
- readonly property Process clientsProc: Process {
- command: ["bash", "-c", "if command -v hyprctl &>/dev/null; then hyprctl -j clients 2>/dev/null; else echo '[]'; fi"]
- stdout: StdioCollector {
- onDataChanged: {
- try {
- const parsed = JSON.parse(String(data).trim());
- if (Array.isArray(parsed)) {
- root.runningApps = parsed;
- }
- } catch(e) {}
- }
- }
- }
-
- // Helper functions to control system (execDetached: fire-and-forget)
- function setVolume(val) {
- root.volume = Math.max(0, Math.min(100, val));
- Quickshell.execDetached(["pactl", "set-sink-volume", "@DEFAULT_SINK@", `${root.volume}%`]);
- }
-
- function toggleVolumeMute() {
- Quickshell.execDetached(["pactl", "set-sink-mute", "@DEFAULT_SINK@", "toggle"]);
- root.volumeMuted = !root.volumeMuted;
- }
-
- function setBrightness(val) {
- root.brightness = Math.max(1, Math.min(100, val));
- Quickshell.execDetached(["brightnessctl", "set", `${root.brightness}%`]);
- }
-
- function mediaPlayPause() {
- Quickshell.execDetached(["playerctl", "play-pause"]);
- mprisProc.running = true;
- }
-
- function mediaNext() {
- Quickshell.execDetached(["playerctl", "next"]);
- mprisProc.running = true;
- }
-
- function mediaPrevious() {
- Quickshell.execDetached(["playerctl", "previous"]);
- mprisProc.running = true;
- }
-
- function launchApp(execCmd) {
- if (!execCmd) return;
- Quickshell.execDetached(["sh", "-c", execCmd]);
- closeAllPopups();
- spotlightVisible = false;
- }
-}
diff --git a/home/hypr/quickshell/README.md b/home/hypr/quickshell/README.md
new file mode 100644
index 0000000..64dd8b6
--- /dev/null
+++ b/home/hypr/quickshell/README.md
@@ -0,0 +1,702 @@
+# Quickshell Overview for Hyprland
+
+
+
+A standalone workspace overview module for Hyprland using Quickshell - shows all workspaces with live window previews, drag-and-drop support, and Super+Tab keybind.
+
+
+
+
+
+
+
+
+
+---
+
+## 📸 Preview
+
+
+
+https://github.com/user-attachments/assets/e8f392d7-d831-4dec-9cd3-fb93d1ccc21c
+
+> *Workspace overview showing live window previews with drag-and-drop support*
+
+---
+
+## ✨ Features
+
+- 🖼️ Visual workspace overview showing all workspaces and windows
+- 🖥️ Multi-monitor support with proper scaling and vertical/rotated monitors [in experimental branch]
+- 📐 Smart row hiding - optionally hide empty workspace rows
+- 🎯 Click windows to focus them
+- 🖱️ Middle-click windows to close them
+- 🔄 Drag and drop windows between workspaces
+- ⌨️ Keyboard navigation (Arrow keys, vim keys, number shortcuts)
+- 🖱️ Auto-close on focus loss / outside click
+- 💡 Hover tooltips showing window information
+- 🎨 Material Design 3 theming
+- ⚡ Smooth animations and transitions
+
+## 📦 Installation
+
+### Arch Linux (AUR)
+
+For Arch Linux users, you can install directly from the AUR:
+
+```bash
+# Using yay
+yay -S quickshell-overview-git
+
+# Using paru
+paru -S quickshell-overview-git
+```
+
+On AUR installs, module files are package-managed under:
+
+```text
+/etc/xdg/quickshell/overview/
+```
+
+Put your custom settings in:
+
+```text
+~/.config/quickshell/overview/config.json
+```
+
+Then add the keybind and auto-start to your Hyprland config (see Setup steps 2-4 below).
+
+### Prerequisites
+
+- **Hyprland** compositor
+- **Quickshell** ([installation guide](https://quickshell.org/docs/v0.1.0/guide/install-setup/))
+- **Qt 6** with modules: QtQuick, QtQuick.Controls
+
+### Setup
+
+1. **Install module files** (choose one):
+ - **Git clone (manual install):**
+ ```bash
+ git clone https://github.com/Shanu-Kumawat/quickshell-overview ~/.config/quickshell/overview
+ ```
+ - **AUR package:** use the command above (`yay -S quickshell-overview-git` or `paru -S ...`)
+
+2. **Add keybind** to your Hyprland config:
+
+ *For Hyprland 0.55+ (`~/.config/hypr/hyprland.lua`):*
+ ```lua
+ hl.bind("SUPER + TAB", hl.dsp.exec_cmd("qs ipc -c overview call overview toggle"))
+ ```
+ *For Hyprland 0.54 and older (`~/.config/hypr/hyprland.conf`):*
+ ```conf
+ bind = Super, TAB, exec, qs ipc -c overview call overview toggle
+ ```
+
+4. **Auto-start** the overview (add to Hyprland config):
+
+ *For Hyprland 0.55+ (`~/.config/hypr/hyprland.lua`):*
+ ```lua
+ hl.on("hyprland.start", function ()
+ hl.exec_cmd("qs -c overview")
+ end)
+ ```
+ *For Hyprland 0.54 and older (`~/.config/hypr/hyprland.conf`):*
+ ```conf
+ exec-once = qs -c overview
+ ```
+
+6. **Reload Hyprland**:
+ ```bash
+ hyprctl reload
+ ```
+
+### Manual Start (if needed)
+
+```bash
+qs -c overview &
+```
+
+### NixOS
+
+For NixOS users, ensure Quickshell has access to required Qt6 modules:
+
+```nix
+# In your configuration.nix or home-manager config
+environment.systemPackages = with pkgs; [
+ quickshell
+ qt6.qtwayland
+];
+```
+
+If you're using home-manager:
+
+```nix
+home.packages = with pkgs; [
+ quickshell
+ qt6.qtwayland
+];
+```
+
+## 🎮 Usage
+
+| Action | Description |
+|--------|-------------|
+| **Super + Tab** | Toggle the overview |
+| **Arrow Keys / h/l** | Navigate left/right within current row* |
+| **Up/Down / j/k** | Navigate between workspace rows |
+| **1-9, 0** | Jump to Nth workspace in current group (0 = 10th) |
+| **Mouse wheel on grid** | Move across all normal workspaces, wrapping from last to first |
+| **Escape / Enter** | Close the overview |
+| **Click outside overview** | Close the overview when `overview.closeOnFocusLoss` is enabled (default) |
+| **Click workspace** | Switch to that workspace |
+| **Click window** | Focus that window |
+| **Middle-click window** | Close that window |
+| **Drag window** | Move window to different workspace |
+
+> *When `hideEmptyRows` is enabled, left/right navigation wraps within the current visible row for better UX
+
+---
+
+## ⚙️ Configuration
+
+> **⚠️ Want to change size, position, workspace count, or toggles?**
+> Create/edit `~/.config/quickshell/overview/config.json`.
+
+`Config.qml` inside the module is now treated as defaults. User overrides are read from:
+
+- `$XDG_CONFIG_HOME/quickshell/overview/config.json`
+- fallback: `~/.config/quickshell/overview/config.json`
+
+> **Note:** After editing `config.json`, manually restart overview for changes to apply:
+> `qs ipc -c overview call overview close && qs -c overview`
+
+### Quick Start
+
+```bash
+mkdir -p ~/.config/quickshell/overview
+cp /etc/xdg/quickshell/overview/config.example.json ~/.config/quickshell/overview/config.json
+```
+
+If you installed from git clone instead of AUR, copy from your repo path:
+
+```bash
+cp ~/.config/quickshell/overview/config.example.json ~/.config/quickshell/overview/config.json
+```
+
+### Workspace Grid
+
+Edit `~/.config/quickshell/overview/config.json`:
+
+```json
+{
+ "overview": {
+ "rows": 2,
+ "columns": 5,
+ "scale": 0.16,
+ "enable": true,
+ "hideEmptyRows": true,
+ "closeOnFocusLoss": true,
+ "useWorkspaceMap": false,
+ "workspaceMap": [0, 10],
+ "orderRightLeft": false,
+ "orderBottomUp": false,
+ "previewsEnabled": true,
+ "previewMode": "live",
+ "includeInactiveMonitorPreviews": true,
+ "previewRecaptureDelayMs": 60,
+ "showSpecialWorkspaces": true,
+ "specialWorkspaces": [],
+ "specialWorkspaceColumns": 5,
+ "emptyWorkspaceWallpaper": "",
+ "specialEmptyWorkspaceWallpaper": "",
+ "effects": {
+ "enableBackdrop": false,
+ "backdropOpacity": 0.28,
+ "panelOpacity": 0.92,
+ "workspaceOpacity": 0.86,
+ "emptyWorkspaceWallpaperOverlayOpacity": 0.18,
+ "windowOverlayOpacity": 0.22,
+ "enableBlur": false,
+ "glassMode": false,
+ "glassTintStrength": 0.35,
+ "glassBorderOpacity": 0.72,
+ "glassShineOpacity": 0.14
+ },
+ "workspaceSpacing": 5,
+ "backgroundPadding": 10,
+ "workspaceNumberBaseSize": 250
+ }
+}
+```
+
+**Common adjustments:**
+- **Too small?** Increase `scale` (try 0.20 or 0.25)
+- **Too big?** Decrease `scale` (try 0.12 or 0.14)
+- **More workspaces?** Change `rows` and `columns` (e.g., 3 rows × 4 columns = 12 workspaces)
+- **Reverse order?** Set `orderRightLeft` and/or `orderBottomUp` to `true`
+- **Prefer the overview to stay open after outside clicks/focus changes?** Set `closeOnFocusLoss` to `false`
+- **Per-monitor workspace groups?** Enable `useWorkspaceMap` and set `workspaceMap` (e.g. `[0,10]`)
+- **Show special workspaces below grid?** Keep `showSpecialWorkspaces: true` and optionally prefill `specialWorkspaces`
+- **Lower memory use?** Set `previewMode` to `event` and `includeInactiveMonitorPreviews` to `false`
+- **Transparency / blur?** Tune `overview.effects.*` (details below)
+
+**Hide empty workspace rows:**
+- Set `hideEmptyRows: true` to automatically hide rows that have no windows
+- Keeps your overview clean by only showing rows with active workspaces
+- The current workspace row is always visible, even if empty
+- Arrow key navigation (left/right) stays within the current row when enabled
+- Great for 2-row setups where you rarely use workspaces 6-10
+
+**Close on focus loss / outside click:**
+- `closeOnFocusLoss` defaults to `true`
+- When enabled, clicking outside the overview closes it, similar to menus, dropdowns, and launchers
+- The overview also closes when its Hyprland focus grab is cleared
+- Set `closeOnFocusLoss: false` if you want the previous behavior where the overview can remain open after focus changes
+
+### Position
+
+Edit `~/.config/quickshell/overview/config.json`:
+
+```json
+{
+ "position": {
+ "topMargin": 100
+ }
+}
+```
+
+Increase `topMargin` to move the overview down. Decrease it to move up.
+
+### Window Preview
+
+```json
+{
+ "windowPreview": {
+ "showIcons": true,
+ "iconToWindowRatio": 0.25,
+ "iconToWindowRatioCompact": 0.45,
+ "xwaylandIndicatorToIconRatio": 0.35,
+ "inactiveMonitorOpacity": 0.4,
+ "cropToFill": false
+ }
+}
+```
+
+- `cropToFill`: crop full-screen windows to fill the workspace preview when `true`; keep the full window in preview with possible horizontal/vertical "padding" bars when `false`
+
+### Performance Tuning
+
+```json
+{
+ "overview": {
+ "previewsEnabled": true,
+ "previewMode": "live",
+ "includeInactiveMonitorPreviews": true,
+ "previewRecaptureDelayMs": 60
+ },
+ "hacks": {
+ "hyprlandEventDebounceMs": 40
+ }
+}
+```
+
+- `overview.previewsEnabled`: turn all window screencopy previews on/off
+- `overview.previewMode`: `live` (best visuals, more RAM) or `event` (lower RAM, refreshes on window events)
+- `overview.includeInactiveMonitorPreviews`: when `false`, only current monitor windows get preview capture
+- `overview.previewRecaptureDelayMs`: delay used for event-mode snapshot refresh (lower = faster updates)
+- `hacks.hyprlandEventDebounceMs`: coalesces Hyprland event refreshes to reduce command churn
+
+### Special Workspaces
+
+```json
+{
+ "overview": {
+ "showSpecialWorkspaces": true,
+ "specialWorkspaces": ["stash", "music", "scratch"],
+ "specialWorkspaceColumns": 5
+ }
+}
+```
+
+- `showSpecialWorkspaces`: renders special workspaces in a strip under the normal grid
+- `specialWorkspaces`: optional preconfigured special workspace names (without the `special:` prefix)
+- `specialWorkspaceColumns`: how many special tiles per row before wrapping
+- `emptyWorkspaceWallpaper`: optional image path used as the background for normal workspace tiles
+- `specialEmptyWorkspaceWallpaper`: optional image path used as the background for special workspace tiles
+
+Interaction behavior:
+- Preconfigured special workspaces appear in the overview even when they are empty
+- The special strip shows active special workspaces plus any names you preconfigure
+- This is useful for fixed workflows like `stash`, `music`, or `scratch`
+- Click a special tile to run `togglespecialworkspace `
+- Click the `+` tile to create and open a new special workspace
+- Drag a window onto a special tile to move it with `movetoworkspacesilent special:`
+- Drag a window onto the `+` tile to auto-create a new special workspace (`stash`, `stash-2`, ...), even when no special workspace is currently open or preconfigured
+- Special windows are visible directly in those tiles
+- Restart Quickshell after changing `config.json`, otherwise the special workspace list will not refresh immediately
+
+Normal workspace scrolling:
+- Scroll on the normal workspace grid to move the active workspace/highlighter across all normal workspaces
+- Scrolling wraps from the last workspace back to `1`, and from `1` back to the last
+
+### Workspace Wallpaper
+
+```json
+{
+ "overview": {
+ "emptyWorkspaceWallpaper": "/home/your-user/Pictures/wallpaper.png",
+ "specialEmptyWorkspaceWallpaper": "/home/your-user/Pictures/special-wallpaper.png",
+ "effects": {
+ "emptyWorkspaceWallpaperOverlayOpacity": 0.12
+ }
+ }
+}
+```
+
+- Normal workspaces can use a wallpaper as their background
+- Special workspaces can use a different wallpaper as their background
+- The wallpaper remains visible behind floating or partially covered windows
+- If no special workspaces exist yet, that special wallpaper is also used for the `+` create tile
+- `emptyWorkspaceWallpaperOverlayOpacity` controls how much tint is applied over that wallpaper
+- Use an absolute path for the image for the most reliable behavior
+- Restart Quickshell after changing `config.json`, otherwise the wallpaper path will not refresh immediately
+
+Demo:
+
+
+
+### Transparency & Blur
+
+```json
+{
+ "overview": {
+ "effects": {
+ "enableBackdrop": false,
+ "backdropOpacity": 0.28,
+ "panelOpacity": 0.92,
+ "workspaceOpacity": 0.86,
+ "emptyWorkspaceWallpaperOverlayOpacity": 0.18,
+ "windowOverlayOpacity": 0.22,
+ "enableBlur": false,
+ "glassMode": false,
+ "glassTintStrength": 0.35,
+ "glassBorderOpacity": 0.72,
+ "glassShineOpacity": 0.14
+ }
+ }
+}
+```
+
+- `enableBackdrop`: show/hide full-screen dim backdrop behind overview
+- `backdropOpacity`: opacity of backdrop dim layer (`0` to `1`)
+- `panelOpacity`: opacity of overview panel container (`0` to `1`)
+- `workspaceOpacity`: opacity of each workspace tile (`0` to `1`)
+- `emptyWorkspaceWallpaperOverlayOpacity`: tint strength over empty-workspace wallpaper (`0` to `1`)
+- `windowOverlayOpacity`: opacity of the color tint over window previews (`0` to `1`)
+- `enableBlur`: switches layer namespace to `quickshell:overview-blur`
+- `glassMode`: enables a glass-like tint + softer transparency preset for panel/workspaces/windows
+- `glassTintStrength`: tint mixing strength for glass mode (`0` to `1`)
+- `glassBorderOpacity`: border alpha used by glass mode (`0` to `1`)
+- `glassShineOpacity`: top highlight strength for glass reflections (`0` to `1`)
+
+Stronger glass preset:
+
+```json
+{
+ "overview": {
+ "effects": {
+ "enableBackdrop": true,
+ "enableBlur": true,
+ "panelOpacity": 0.55,
+ "workspaceOpacity": 0.48,
+ "emptyWorkspaceWallpaperOverlayOpacity": 0.10,
+ "windowOverlayOpacity": 0.08,
+ "glassMode": true,
+ "glassTintStrength": 0.55,
+ "glassBorderOpacity": 0.85,
+ "glassShineOpacity": 0.32
+ }
+ }
+}
+```
+
+For Hyprland blur, add layer rules (example):
+
+```ini
+layerrule = blur true, match:namespace quickshell:overview-blur
+layerrule = ignore_alpha 0.2, match:namespace quickshell:overview-blur
+```
+
+If `enableBlur` is `false`, namespace remains `quickshell:overview`.
+
+Low-memory preset:
+
+```json
+{
+ "overview": {
+ "previewMode": "event",
+ "includeInactiveMonitorPreviews": false
+ },
+ "hacks": {
+ "hyprlandEventDebounceMs": 80
+ }
+}
+```
+
+### Full Example
+
+```json
+{
+ "appearance": {
+ "colorSource": "default",
+ "caelestia": {
+ "autoRefresh": true,
+ "refreshInterval": 2000,
+ "accentProfile": "vibrant"
+ },
+ "rounding": {
+ "unsharpen": 2,
+ "verysmall": 8,
+ "small": 12,
+ "normal": 17,
+ "large": 23,
+ "full": 9999,
+ "screenRounding": 23,
+ "windowRounding": 18
+ },
+ "font": {
+ "family": {
+ "main": "sans-serif",
+ "title": "sans-serif",
+ "expressive": "sans-serif"
+ },
+ "pixelSize": {
+ "smaller": 12,
+ "small": 15,
+ "normal": 16,
+ "larger": 19,
+ "huge": 22
+ }
+ },
+ "animation": {
+ "duration": {
+ "elementMove": 500,
+ "elementMoveEnter": 400,
+ "elementMoveFast": 200
+ }
+ },
+ "sizes": {
+ "elevationMargin": 10
+ }
+ },
+ "overview": {
+ "rows": 2,
+ "columns": 5,
+ "scale": 0.16,
+ "enable": true,
+ "hideEmptyRows": true,
+ "closeOnFocusLoss": true,
+ "useWorkspaceMap": false,
+ "workspaceMap": [0, 10],
+ "orderRightLeft": false,
+ "orderBottomUp": false,
+ "previewsEnabled": true,
+ "previewMode": "live",
+ "includeInactiveMonitorPreviews": true,
+ "previewRecaptureDelayMs": 60,
+ "showSpecialWorkspaces": true,
+ "specialWorkspaces": [],
+ "specialWorkspaceColumns": 5,
+ "emptyWorkspaceWallpaper": "",
+ "specialEmptyWorkspaceWallpaper": "",
+ "effects": {
+ "enableBackdrop": false,
+ "backdropOpacity": 0.28,
+ "panelOpacity": 0.92,
+ "workspaceOpacity": 0.86,
+ "emptyWorkspaceWallpaperOverlayOpacity": 0.18,
+ "windowOverlayOpacity": 0.22,
+ "enableBlur": false,
+ "glassMode": false,
+ "glassTintStrength": 0.35,
+ "glassBorderOpacity": 0.72,
+ "glassShineOpacity": 0.14
+ },
+ "workspaceSpacing": 5,
+ "backgroundPadding": 10,
+ "workspaceNumberBaseSize": 250
+ },
+ "position": {
+ "topMargin": 100
+ },
+ "windowPreview": {
+ "showIcons": true,
+ "iconToWindowRatio": 0.25,
+ "iconToWindowRatioCompact": 0.45,
+ "xwaylandIndicatorToIconRatio": 0.35,
+ "inactiveMonitorOpacity": 0.4,
+ "cropToFill": false
+ },
+ "hacks": {
+ "arbitraryRaceConditionDelay": 150,
+ "hyprlandEventDebounceMs": 40
+ }
+}
+```
+
+### Theme & Colors
+
+Most theme sizing/timing options are now configurable via `config.json`:
+- `appearance.colorSource` (`default`, `matugen`, `caelestia`)
+- `appearance.caelestia.*` (`autoRefresh`, `refreshInterval`, `accentProfile`)
+- `appearance.rounding.*`
+- `appearance.font.*`
+- `appearance.animation.duration.*`
+- `appearance.sizes.elevationMargin`
+
+For full color palette customization, edit `~/.config/quickshell/overview/common/Appearance.qml`.
+
+### Matugen (Dynamic Colors from Wallpaper)
+
+[Matugen](https://github.com/InioX/matugen) lets you generate Material You colors from your wallpaper and apply them to the overview automatically.
+
+**1. Install matugen** - follow [matugen's install guide](https://github.com/InioX/matugen?tab=readme-ov-file#installation)
+
+**2. Copy the template** from this repo to matugen's templates folder:
+```bash
+mkdir -p ~/.config/matugen/templates
+cp ~/.config/quickshell/overview/quickshell-overview.qml ~/.config/matugen/templates/
+```
+
+**3. Add this to `~/.config/matugen/config.toml`** (create the file if it doesn't exist):
+```toml
+[templates.quickshell_overview]
+input_path = "./templates/quickshell-overview.qml"
+output_path = "~/.config/quickshell/overview/common/Appearance.colors.qml"
+```
+
+**4. Enable it** in `~/.config/quickshell/overview/config.json`:
+```json
+{
+ "appearance": {
+ "colorSource": "matugen"
+ }
+}
+```
+
+**5. Run matugen** with your wallpaper to generate colors:
+```bash
+matugen image /path/to/your/wallpaper.jpg
+```
+
+This generates `Appearance.colors.qml` which the overview loads automatically. Re-run step 5 whenever you change your wallpaper.
+
+### Caelestia
+
+If you use Caelestia, set the source to `caelestia`:
+
+```json
+{
+ "appearance": {
+ "colorSource": "caelestia",
+ "caelestia": {
+ "autoRefresh": true,
+ "refreshInterval": 2000,
+ "accentProfile": "vibrant"
+ }
+ }
+}
+```
+
+Overview reads the active palette from `caelestia scheme get` and refreshes it live when `autoRefresh` is enabled, so wallpaper/scheme changes can apply without restarting overview.
+
+---
+
+## 📋 Requirements
+
+- **Hyprland** compositor (tested on latest versions)
+- **Quickshell** (Qt6-based shell framework)
+- **Qt 6** with the following modules:
+ - QtQuick
+ - QtQuick.Controls
+ - QtQuick.Layouts
+ - Quickshell.Wayland
+ - Quickshell.Hyprland
+
+## 🚫 Removed Features (from original illogical-impulse)
+
+The following features were removed to make it standalone:
+
+- App search functionality
+- Emoji picker
+- Clipboard history integration
+- Search widget
+- Integration with the full illogical-impulse shell ecosystem
+
+## 📁 File Structure
+
+```
+~/.config/quickshell/overview/
+├── shell.qml # Main entry point
+├── README.md # This file
+├── config.example.json # User override template
+├── hyprland-config.conf # Configuration reference
+├── common/
+│ ├── Appearance.qml # Theme and styling
+│ ├── Config.qml # Default config + user override loader
+│ ├── functions/
+│ │ └── ColorUtils.qml # Color manipulation utilities
+│ └── widgets/
+│ ├── StyledText.qml # Styled text component
+│ ├── StyledRectangularShadow.qml
+│ ├── StyledToolTip.qml
+│ └── StyledToolTipContent.qml
+├── services/
+│ ├── GlobalStates.qml # Global state management
+│ └── HyprlandData.qml # Hyprland data provider
+└── modules/
+ └── overview/
+ ├── Overview.qml # Main overview component
+ ├── OverviewWidget.qml # Workspace grid widget
+ └── OverviewWindow.qml # Individual window preview
+```
+
+## 🎯 IPC Commands
+
+```bash
+# Toggle overview
+qs ipc -c overview call overview toggle
+
+# Open overview
+qs ipc -c overview call overview open
+
+# Close overview
+qs ipc -c overview call overview close
+```
+
+## 🐛 Known Issues
+
+- Window icons may fallback to generic icon if app class name doesn't match icon theme
+- Potential crashes during rapid window state changes due to Wayland screencopy buffer management
+
+## 💖 Support
+
+If this project helps your setup and you want to support continued maintenance, you can sponsor here:
+
+https://github.com/sponsors/Shanu-Kumawat
+
+## Credits
+
+Extracted from the overview feature in [illogical-impulse](https://github.com/end-4/dots-hyprland) by [end-4](https://github.com/end-4).
+
+Adapted as a standalone component for Hyprland + Quickshell users who want just the overview functionality.
+
+---
+
+
+
+**Note:** Maintenance will be limited due to time constraints, but **PRs and code improvements are welcome!** Feel free to contribute or fork for your own needs.
+
+Made with ❤️ for the Hyprland community
+
+
diff --git a/home/hypr/quickshell/assets/apple.svg b/home/hypr/quickshell/assets/apple.svg
deleted file mode 100644
index 9e8f16b..0000000
--- a/home/hypr/quickshell/assets/apple.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/home/hypr/quickshell/assets/appstore.svg b/home/hypr/quickshell/assets/appstore.svg
deleted file mode 100644
index 2163f9e..0000000
--- a/home/hypr/quickshell/assets/appstore.svg
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/home/hypr/quickshell/assets/battery.svg b/home/hypr/quickshell/assets/battery.svg
deleted file mode 100644
index aac7553..0000000
--- a/home/hypr/quickshell/assets/battery.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/home/hypr/quickshell/assets/bluetooth.svg b/home/hypr/quickshell/assets/bluetooth.svg
deleted file mode 100644
index de77291..0000000
--- a/home/hypr/quickshell/assets/bluetooth.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/home/hypr/quickshell/assets/brightness.svg b/home/hypr/quickshell/assets/brightness.svg
deleted file mode 100644
index 89bf9f7..0000000
--- a/home/hypr/quickshell/assets/brightness.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/home/hypr/quickshell/assets/browser.svg b/home/hypr/quickshell/assets/browser.svg
deleted file mode 100644
index 2b3068c..0000000
--- a/home/hypr/quickshell/assets/browser.svg
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/home/hypr/quickshell/assets/catalina-badge.svg b/home/hypr/quickshell/assets/catalina-badge.svg
deleted file mode 100644
index 0443b9b..0000000
--- a/home/hypr/quickshell/assets/catalina-badge.svg
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/home/hypr/quickshell/assets/code.svg b/home/hypr/quickshell/assets/code.svg
deleted file mode 100644
index e673e1f..0000000
--- a/home/hypr/quickshell/assets/code.svg
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/home/hypr/quickshell/assets/controlcenter.svg b/home/hypr/quickshell/assets/controlcenter.svg
deleted file mode 100644
index fa4fdb2..0000000
--- a/home/hypr/quickshell/assets/controlcenter.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/home/hypr/quickshell/assets/finder.svg b/home/hypr/quickshell/assets/finder.svg
deleted file mode 100644
index 7b85c20..0000000
--- a/home/hypr/quickshell/assets/finder.svg
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/home/hypr/quickshell/assets/folder-downloads.svg b/home/hypr/quickshell/assets/folder-downloads.svg
deleted file mode 100644
index 6009be3..0000000
--- a/home/hypr/quickshell/assets/folder-downloads.svg
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/home/hypr/quickshell/assets/launchpad.svg b/home/hypr/quickshell/assets/launchpad.svg
deleted file mode 100644
index a987dc0..0000000
--- a/home/hypr/quickshell/assets/launchpad.svg
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/home/hypr/quickshell/assets/music.svg b/home/hypr/quickshell/assets/music.svg
deleted file mode 100644
index 55481f0..0000000
--- a/home/hypr/quickshell/assets/music.svg
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/home/hypr/quickshell/assets/preferences.svg b/home/hypr/quickshell/assets/preferences.svg
deleted file mode 100644
index b382a48..0000000
--- a/home/hypr/quickshell/assets/preferences.svg
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/home/hypr/quickshell/assets/spotlight.svg b/home/hypr/quickshell/assets/spotlight.svg
deleted file mode 100644
index fc316b6..0000000
--- a/home/hypr/quickshell/assets/spotlight.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/home/hypr/quickshell/assets/terminal.svg b/home/hypr/quickshell/assets/terminal.svg
deleted file mode 100644
index 934b9e2..0000000
--- a/home/hypr/quickshell/assets/terminal.svg
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/home/hypr/quickshell/assets/trash-empty.svg b/home/hypr/quickshell/assets/trash-empty.svg
deleted file mode 100644
index 01fd97b..0000000
--- a/home/hypr/quickshell/assets/trash-empty.svg
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/home/hypr/quickshell/assets/trash-full.svg b/home/hypr/quickshell/assets/trash-full.svg
deleted file mode 100644
index 179071a..0000000
--- a/home/hypr/quickshell/assets/trash-full.svg
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/home/hypr/quickshell/assets/volume.svg b/home/hypr/quickshell/assets/volume.svg
deleted file mode 100644
index 1c44722..0000000
--- a/home/hypr/quickshell/assets/volume.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/home/hypr/quickshell/assets/wifi.svg b/home/hypr/quickshell/assets/wifi.svg
deleted file mode 100644
index 886b971..0000000
--- a/home/hypr/quickshell/assets/wifi.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/home/hypr/quickshell/common/Appearance.qml b/home/hypr/quickshell/common/Appearance.qml
new file mode 100644
index 0000000..d20510f
--- /dev/null
+++ b/home/hypr/quickshell/common/Appearance.qml
@@ -0,0 +1,359 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import Quickshell
+import Quickshell.Io
+import "functions"
+import "." as Common
+
+Singleton {
+ id: root
+ property string colorSource: Common.Config.options.appearance.colorSource
+ property string caelestiaAccentProfile: Common.Config.options.appearance.caelestia.accentProfile
+ property string lastCaelestiaPayload: ""
+ property QtObject m3colors: {
+ if (colorSource === "matugen" && matugenLoader.item)
+ return matugenLoader.item;
+ if (colorSource === "caelestia" && caelestiaPaletteLoaded)
+ return caelestiaColors;
+ return defaultColors;
+ }
+ property QtObject animation
+ property QtObject animationCurves
+ property QtObject colors
+ property QtObject rounding
+ property QtObject font
+ property QtObject sizes
+ property bool caelestiaPaletteLoaded: false
+
+ Loader {
+ id: matugenLoader
+ active: root.colorSource === "matugen"
+ source: "Appearance.colors.qml"
+ }
+
+ // Silicon Volt palette, ported from the previous shell theme.
+ // Base: charcoal #131313 / #0e0e0e, glass #1c1b1b, sharp corners.
+ // Accents: volt #CFFC00, coral #FF7A59, blue #00BFFF.
+ property QtObject defaultColors: QtObject {
+ property bool darkmode: true
+ property color m3primary: "#CFFC00" // accentVolt
+ property color m3onPrimary: "#131313" // bgPrimary
+ property color m3primaryContainer: "#3D4500"
+ property color m3onPrimaryContainer: "#E3FF66" // accentVoltLight
+ property color m3secondary: "#FF7A59" // accentCoral
+ property color m3onSecondary: "#131313"
+ property color m3secondaryContainer: "#3E2119"
+ property color m3onSecondaryContainer: "#FFB29B"
+ property color m3background: "#131313" // bgPrimary
+ property color m3onBackground: "#FFFFFF" // textPrimary
+ property color m3surface: "#131313"
+ property color m3surfaceContainerLow: "#161616"
+ property color m3surfaceContainer: "#1A1A1A"
+ property color m3surfaceContainerHigh: "#1C1B1B" // glassBg
+ property color m3surfaceContainerHighest: "#242323"
+ property color m3onSurface: "#E5E2E1" // textSecondary
+ property color m3surfaceVariant: "#2A2929"
+ property color m3onSurfaceVariant: "#ADAAAA" // textMuted
+ property color m3inverseSurface: "#E5E2E1"
+ property color m3inverseOnSurface: "#131313"
+ property color m3outline: "#ADAAAA"
+ property color m3outlineVariant: "#2A2929"
+ property color m3shadow: "#000000"
+ }
+
+ property QtObject caelestiaColors: QtObject {
+ property bool darkmode: defaultColors.darkmode
+ property color m3primary: defaultColors.m3primary
+ property color m3onPrimary: defaultColors.m3onPrimary
+ property color m3primaryContainer: defaultColors.m3primaryContainer
+ property color m3onPrimaryContainer: defaultColors.m3onPrimaryContainer
+ property color m3secondary: defaultColors.m3secondary
+ property color m3onSecondary: defaultColors.m3onSecondary
+ property color m3secondaryContainer: defaultColors.m3secondaryContainer
+ property color m3onSecondaryContainer: defaultColors.m3onSecondaryContainer
+ property color m3background: defaultColors.m3background
+ property color m3onBackground: defaultColors.m3onBackground
+ property color m3surface: defaultColors.m3surface
+ property color m3surfaceContainerLow: defaultColors.m3surfaceContainerLow
+ property color m3surfaceContainer: defaultColors.m3surfaceContainer
+ property color m3surfaceContainerHigh: defaultColors.m3surfaceContainerHigh
+ property color m3surfaceContainerHighest: defaultColors.m3surfaceContainerHighest
+ property color m3onSurface: defaultColors.m3onSurface
+ property color m3surfaceVariant: defaultColors.m3surfaceVariant
+ property color m3onSurfaceVariant: defaultColors.m3onSurfaceVariant
+ property color m3inverseSurface: defaultColors.m3inverseSurface
+ property color m3inverseOnSurface: defaultColors.m3inverseOnSurface
+ property color m3outline: defaultColors.m3outline
+ property color m3outlineVariant: defaultColors.m3outlineVariant
+ property color m3shadow: defaultColors.m3shadow
+ }
+
+ function loadCaelestiaPalette() {
+ getCaelestiaScheme.running = true;
+ }
+
+ function relativeLuminance(color) {
+ const c = Qt.color(color);
+ function channel(v) {
+ return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
+ }
+ return (0.2126 * channel(c.r)) + (0.7152 * channel(c.g)) + (0.0722 * channel(c.b));
+ }
+
+ function bestOnColor(backgroundColor) {
+ return relativeLuminance(backgroundColor) > 0.5 ? "#121212" : "#f5f5f5";
+ }
+
+ function firstColor(palette, keys, fallback) {
+ for (const key of keys) {
+ if (palette[key])
+ return palette[key];
+ }
+ return fallback;
+ }
+
+ function applyCaelestiaPalette(palette, mode) {
+ if (caelestiaAccentProfile === "vibrant") {
+ const primary = firstColor(palette, ["blue", "klink", "term12", "primary"], defaultColors.m3primary);
+ const secondary = firstColor(palette, ["mauve", "lavender", "term13", "secondary"], defaultColors.m3secondary);
+ const tertiary = firstColor(palette, ["pink", "rosewater", "term11", "tertiary"], defaultColors.m3secondaryContainer);
+ const primaryContainer = firstColor(palette, ["sapphire", "klinkSelection", "primaryContainer"], defaultColors.m3primaryContainer);
+ const secondaryContainer = firstColor(palette, ["surface2", "secondaryContainer"], defaultColors.m3secondaryContainer);
+
+ caelestiaColors.m3primary = primary;
+ caelestiaColors.m3onPrimary = bestOnColor(primary);
+ caelestiaColors.m3primaryContainer = primaryContainer;
+ caelestiaColors.m3onPrimaryContainer = bestOnColor(primaryContainer);
+ caelestiaColors.m3secondary = secondary;
+ caelestiaColors.m3onSecondary = bestOnColor(secondary);
+ caelestiaColors.m3secondaryContainer = secondaryContainer;
+ caelestiaColors.m3onSecondaryContainer = bestOnColor(secondaryContainer);
+ // Preserve a stronger accent presence across UI mixes.
+ caelestiaColors.m3surfaceVariant = firstColor(palette, ["surface1", "surfaceVariant"], defaultColors.m3surfaceVariant);
+ caelestiaColors.m3outline = firstColor(palette, ["overlay2", "outline"], defaultColors.m3outline);
+ caelestiaColors.m3outlineVariant = firstColor(palette, ["overlay0", "outlineVariant"], defaultColors.m3outlineVariant);
+ if (tertiary)
+ caelestiaColors.m3secondaryContainer = ColorUtils.mix(secondaryContainer, tertiary, 0.7);
+ } else {
+ const map = {
+ "primary": "m3primary",
+ "onPrimary": "m3onPrimary",
+ "primaryContainer": "m3primaryContainer",
+ "onPrimaryContainer": "m3onPrimaryContainer",
+ "secondary": "m3secondary",
+ "onSecondary": "m3onSecondary",
+ "secondaryContainer": "m3secondaryContainer",
+ "onSecondaryContainer": "m3onSecondaryContainer",
+ "surfaceVariant": "m3surfaceVariant",
+ "outline": "m3outline",
+ "outlineVariant": "m3outlineVariant"
+ };
+ for (const key in map) {
+ if (palette[key])
+ caelestiaColors[map[key]] = palette[key];
+ }
+ }
+
+ // Keep foundational tones from Material keys for readability.
+ const baseMap = {
+ "background": "m3background",
+ "onBackground": "m3onBackground",
+ "surface": "m3surface",
+ "surfaceContainerLow": "m3surfaceContainerLow",
+ "surfaceContainer": "m3surfaceContainer",
+ "surfaceContainerHigh": "m3surfaceContainerHigh",
+ "surfaceContainerHighest": "m3surfaceContainerHighest",
+ "onSurface": "m3onSurface",
+ "inverseSurface": "m3inverseSurface",
+ "inverseOnSurface": "m3inverseOnSurface",
+ "shadow": "m3shadow"
+ };
+ for (const key in baseMap) {
+ if (palette[key])
+ caelestiaColors[baseMap[key]] = palette[key];
+ }
+
+ if (palette["onSurfaceVariant"])
+ caelestiaColors.m3onSurfaceVariant = palette["onSurfaceVariant"];
+
+ if (mode === "light")
+ caelestiaColors.darkmode = false;
+ else if (mode === "dark")
+ caelestiaColors.darkmode = true;
+ }
+
+ Process {
+ id: getCaelestiaScheme
+ command: ["sh", "-lc", "caelestia scheme get 2>/dev/null || true"]
+ stdout: StdioCollector {
+ id: caelestiaCollector
+ onStreamFinished: {
+ const text = caelestiaCollector.text;
+ if (!text || !text.trim()) {
+ root.caelestiaPaletteLoaded = false;
+ return;
+ }
+
+ const ansiPattern = /\x1b\[[0-9;]*m/g;
+ const lines = text.split("\n");
+ let mode = "";
+ const palette = ({});
+
+ for (const rawLine of lines) {
+ const line = rawLine.replace(ansiPattern, "").trim();
+
+ if (line.startsWith("Mode:")) {
+ mode = line.split(":")[1]?.trim()?.toLowerCase() ?? "";
+ continue;
+ }
+
+ const match = line.match(/^([A-Za-z0-9_]+):\s*.*?([0-9a-fA-F]{6})$/);
+ if (!match)
+ continue;
+
+ palette[match[1]] = `#${match[2]}`;
+ }
+
+ const normalized = JSON.stringify({ mode: mode, palette: palette, profile: caelestiaAccentProfile });
+ if (normalized === root.lastCaelestiaPayload)
+ return;
+
+ root.lastCaelestiaPayload = normalized;
+ applyCaelestiaPalette(palette, mode);
+ root.caelestiaPaletteLoaded = Object.keys(palette).length > 0;
+ }
+ }
+ }
+
+ Timer {
+ id: caelestiaRefreshTimer
+ interval: Math.max(500, Common.Config.options.appearance.caelestia.refreshInterval)
+ running: root.colorSource === "caelestia" && Common.Config.options.appearance.caelestia.autoRefresh
+ repeat: true
+ triggeredOnStart: false
+ onTriggered: root.loadCaelestiaPalette()
+ }
+
+ onColorSourceChanged: {
+ if (colorSource === "caelestia")
+ loadCaelestiaPalette();
+ }
+
+ onCaelestiaAccentProfileChanged: {
+ if (colorSource === "caelestia") {
+ root.lastCaelestiaPayload = "";
+ loadCaelestiaPalette();
+ }
+ }
+
+ Component.onCompleted: {
+ if (colorSource === "caelestia")
+ loadCaelestiaPalette();
+ }
+
+ colors: QtObject {
+ property color colSubtext: m3colors.m3outline
+ property color colLayer0: m3colors.m3background
+ property color colOnLayer0: m3colors.m3onBackground
+ property color colLayer0Border: ColorUtils.mix(root.m3colors.m3outlineVariant, colLayer0, 0.4)
+ property color colLayer1: m3colors.m3surfaceContainerLow
+ property color colOnLayer1: m3colors.m3onSurfaceVariant
+ property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45)
+ property color colLayer1Hover: ColorUtils.mix(colLayer1, colOnLayer1, 0.92)
+ property color colLayer1Active: ColorUtils.mix(colLayer1, colOnLayer1, 0.85)
+ property color colLayer2: m3colors.m3surfaceContainer
+ property color colOnLayer2: m3colors.m3onSurface
+ property color colLayer2Hover: ColorUtils.mix(colLayer2, colOnLayer2, 0.90)
+ property color colLayer2Active: ColorUtils.mix(colLayer2, colOnLayer2, 0.80)
+ property color colPrimary: m3colors.m3primary
+ property color colOnPrimary: m3colors.m3onPrimary
+ property color colSecondary: m3colors.m3secondary
+ property color colSecondaryContainer: m3colors.m3secondaryContainer
+ property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer
+ property color colTooltip: m3colors.m3inverseSurface
+ property color colOnTooltip: m3colors.m3inverseOnSurface
+ property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7)
+ property color colOutline: m3colors.m3outline
+ }
+
+ rounding: QtObject {
+ property int unsharpen: Common.Config.options.appearance.rounding.unsharpen
+ property int verysmall: Common.Config.options.appearance.rounding.verysmall
+ property int small: Common.Config.options.appearance.rounding.small
+ property int normal: Common.Config.options.appearance.rounding.normal
+ property int large: Common.Config.options.appearance.rounding.large
+ property int full: Common.Config.options.appearance.rounding.full
+ property int screenRounding: Common.Config.options.appearance.rounding.screenRounding
+ property int windowRounding: Common.Config.options.appearance.rounding.windowRounding
+ }
+
+ font: QtObject {
+ property QtObject family: QtObject {
+ property string main: Common.Config.options.appearance.font.family.main
+ property string title: Common.Config.options.appearance.font.family.title
+ property string expressive: Common.Config.options.appearance.font.family.expressive
+ }
+ property QtObject pixelSize: QtObject {
+ property int smaller: Common.Config.options.appearance.font.pixelSize.smaller
+ property int small: Common.Config.options.appearance.font.pixelSize.small
+ property int normal: Common.Config.options.appearance.font.pixelSize.normal
+ property int larger: Common.Config.options.appearance.font.pixelSize.larger
+ property int huge: Common.Config.options.appearance.font.pixelSize.huge
+ }
+ }
+
+ animationCurves: QtObject {
+ readonly property list expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1]
+ readonly property list expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1]
+ readonly property list emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
+ readonly property real expressiveDefaultSpatialDuration: Common.Config.options.appearance.animation.duration.elementMove
+ readonly property real expressiveEffectsDuration: Common.Config.options.appearance.animation.duration.elementMoveFast
+ }
+
+ animation: QtObject {
+ property QtObject elementMove: QtObject {
+ property int duration: animationCurves.expressiveDefaultSpatialDuration
+ property int type: Easing.BezierSpline
+ property list bezierCurve: animationCurves.expressiveDefaultSpatial
+ property Component numberAnimation: Component {
+ NumberAnimation {
+ duration: root.animation.elementMove.duration
+ easing.type: root.animation.elementMove.type
+ easing.bezierCurve: root.animation.elementMove.bezierCurve
+ }
+ }
+ }
+
+ property QtObject elementMoveEnter: QtObject {
+ property int duration: Common.Config.options.appearance.animation.duration.elementMoveEnter
+ property int type: Easing.BezierSpline
+ property list bezierCurve: animationCurves.emphasizedDecel
+ property Component numberAnimation: Component {
+ NumberAnimation {
+ duration: root.animation.elementMoveEnter.duration
+ easing.type: root.animation.elementMoveEnter.type
+ easing.bezierCurve: root.animation.elementMoveEnter.bezierCurve
+ }
+ }
+ }
+
+ property QtObject elementMoveFast: QtObject {
+ property int duration: animationCurves.expressiveEffectsDuration
+ property int type: Easing.BezierSpline
+ property list bezierCurve: animationCurves.expressiveEffects
+ property Component numberAnimation: Component {
+ NumberAnimation {
+ duration: root.animation.elementMoveFast.duration
+ easing.type: root.animation.elementMoveFast.type
+ easing.bezierCurve: root.animation.elementMoveFast.bezierCurve
+ }
+ }
+ }
+ }
+
+ sizes: QtObject {
+ property real elevationMargin: Common.Config.options.appearance.sizes.elevationMargin
+ }
+}
diff --git a/home/hypr/quickshell/common/Config.qml b/home/hypr/quickshell/common/Config.qml
new file mode 100644
index 0000000..ab3b009
--- /dev/null
+++ b/home/hypr/quickshell/common/Config.qml
@@ -0,0 +1,194 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import Quickshell
+import Quickshell.Io
+
+Singleton {
+ id: root
+
+ property var userOptions: ({})
+
+ function read(path, fallback) {
+ const parts = path.split(".");
+ let current = userOptions;
+
+ for (const part of parts) {
+ if (current === null || current === undefined || typeof current !== "object" || !(part in current)) {
+ return fallback;
+ }
+ current = current[part];
+ }
+
+ return current === undefined || current === null ? fallback : current;
+ }
+
+ function readInt(path, fallback) {
+ const value = read(path, fallback);
+ const parsed = Number(value);
+ return Number.isInteger(parsed) ? parsed : fallback;
+ }
+
+ function readReal(path, fallback) {
+ const value = read(path, fallback);
+ const parsed = Number(value);
+ return Number.isFinite(parsed) ? parsed : fallback;
+ }
+
+ function readBool(path, fallback) {
+ const value = read(path, fallback);
+ return typeof value === "boolean" ? value : fallback;
+ }
+
+ function readString(path, fallback) {
+ const value = read(path, fallback);
+ if (typeof value !== "string")
+ return fallback;
+
+ const trimmed = value.trim();
+ return trimmed.length > 0 ? trimmed : fallback;
+ }
+
+ property QtObject options: QtObject {
+ property QtObject appearance: QtObject {
+ property string colorSource: root.readString(
+ "appearance.colorSource",
+ root.readBool("appearance.useMatugenColors", false) ? "matugen" : "default"
+ )
+ property bool useMatugenColors: colorSource === "matugen"
+ property QtObject caelestia: QtObject {
+ property bool autoRefresh: root.readBool("appearance.caelestia.autoRefresh", true)
+ property int refreshInterval: root.readInt("appearance.caelestia.refreshInterval", 2000)
+ property string accentProfile: root.readString("appearance.caelestia.accentProfile", "vibrant")
+ }
+
+ property QtObject rounding: QtObject {
+ property int unsharpen: root.readInt("appearance.rounding.unsharpen", 2)
+ property int verysmall: root.readInt("appearance.rounding.verysmall", 8)
+ property int small: root.readInt("appearance.rounding.small", 12)
+ property int normal: root.readInt("appearance.rounding.normal", 17)
+ property int large: root.readInt("appearance.rounding.large", 23)
+ property int full: root.readInt("appearance.rounding.full", 9999)
+ property int screenRounding: root.readInt("appearance.rounding.screenRounding", large)
+ property int windowRounding: root.readInt("appearance.rounding.windowRounding", 18)
+ }
+
+ property QtObject font: QtObject {
+ property QtObject family: QtObject {
+ property string main: root.readString("appearance.font.family.main", "sans-serif")
+ property string title: root.readString("appearance.font.family.title", "sans-serif")
+ property string expressive: root.readString("appearance.font.family.expressive", "sans-serif")
+ }
+
+ property QtObject pixelSize: QtObject {
+ property int smaller: root.readInt("appearance.font.pixelSize.smaller", 12)
+ property int small: root.readInt("appearance.font.pixelSize.small", 15)
+ property int normal: root.readInt("appearance.font.pixelSize.normal", 16)
+ property int larger: root.readInt("appearance.font.pixelSize.larger", 19)
+ property int huge: root.readInt("appearance.font.pixelSize.huge", 22)
+ }
+ }
+
+ property QtObject animation: QtObject {
+ property QtObject duration: QtObject {
+ property int elementMove: root.readInt("appearance.animation.duration.elementMove", 500)
+ property int elementMoveEnter: root.readInt("appearance.animation.duration.elementMoveEnter", 400)
+ property int elementMoveFast: root.readInt("appearance.animation.duration.elementMoveFast", 200)
+ }
+ }
+
+ property QtObject sizes: QtObject {
+ property real elevationMargin: root.readReal("appearance.sizes.elevationMargin", 10)
+ }
+ }
+
+ property QtObject overview: QtObject {
+ property int rows: root.readInt("overview.rows", 2)
+ property int columns: root.readInt("overview.columns", 5)
+ property real scale: root.readReal("overview.scale", 0.16)
+ property bool enable: root.readBool("overview.enable", true)
+ property bool hideEmptyRows: root.readBool("overview.hideEmptyRows", true)
+ property bool closeOnFocusLoss: root.readBool("overview.closeOnFocusLoss", true)
+ property bool useWorkspaceMap: root.readBool("overview.useWorkspaceMap", false)
+ property var workspaceMap: root.read("overview.workspaceMap", [])
+ property bool orderRightLeft: root.readBool("overview.orderRightLeft", false)
+ property bool orderBottomUp: root.readBool("overview.orderBottomUp", false)
+ property bool previewsEnabled: root.readBool("overview.previewsEnabled", true)
+ property string previewMode: root.readString("overview.previewMode", "live")
+ property bool includeInactiveMonitorPreviews: root.readBool("overview.includeInactiveMonitorPreviews", true)
+ property int previewRecaptureDelayMs: root.readInt("overview.previewRecaptureDelayMs", 60)
+ property bool showSpecialWorkspaces: root.readBool("overview.showSpecialWorkspaces", true)
+ property var specialWorkspaces: root.read("overview.specialWorkspaces", [])
+ property int specialWorkspaceColumns: root.readInt("overview.specialWorkspaceColumns", columns)
+ property string emptyWorkspaceWallpaper: root.readString("overview.emptyWorkspaceWallpaper", "")
+ property string specialEmptyWorkspaceWallpaper: root.readString("overview.specialEmptyWorkspaceWallpaper", "")
+ property real workspaceSpacing: root.readReal("overview.workspaceSpacing", 5)
+ property real backgroundPadding: root.readReal("overview.backgroundPadding", 10)
+ property real workspaceNumberBaseSize: root.readReal("overview.workspaceNumberBaseSize", 250)
+ property QtObject effects: QtObject {
+ property bool enableBackdrop: root.readBool("overview.effects.enableBackdrop", false)
+ property real backdropOpacity: root.readReal("overview.effects.backdropOpacity", 0.28)
+ property real panelOpacity: root.readReal("overview.effects.panelOpacity", 0.92)
+ property real workspaceOpacity: root.readReal("overview.effects.workspaceOpacity", 0.86)
+ property real emptyWorkspaceWallpaperOverlayOpacity: root.readReal("overview.effects.emptyWorkspaceWallpaperOverlayOpacity", 0.18)
+ property real windowOverlayOpacity: root.readReal("overview.effects.windowOverlayOpacity", 0.22)
+ property bool enableBlur: root.readBool("overview.effects.enableBlur", false)
+ property bool glassMode: root.readBool("overview.effects.glassMode", false)
+ property real glassTintStrength: root.readReal("overview.effects.glassTintStrength", 0.35)
+ property real glassBorderOpacity: root.readReal("overview.effects.glassBorderOpacity", 0.72)
+ property real glassShineOpacity: root.readReal("overview.effects.glassShineOpacity", 0.14)
+ }
+ }
+
+ property QtObject position: QtObject {
+ property int topMargin: root.readInt("position.topMargin", 100)
+ }
+
+ property QtObject windowPreview: QtObject {
+ property bool showIcons: root.readBool("windowPreview.showIcons", true)
+ property real iconToWindowRatio: root.readReal("windowPreview.iconToWindowRatio", 0.25)
+ property real iconToWindowRatioCompact: root.readReal("windowPreview.iconToWindowRatioCompact", 0.45)
+ property real xwaylandIndicatorToIconRatio: root.readReal("windowPreview.xwaylandIndicatorToIconRatio", 0.35)
+ property real inactiveMonitorOpacity: root.readReal("windowPreview.inactiveMonitorOpacity", 0.4)
+ property bool cropToFill: root.readBool("windowPreview.cropToFill", false)
+ }
+
+ property QtObject hacks: QtObject {
+ property int arbitraryRaceConditionDelay: root.readInt("hacks.arbitraryRaceConditionDelay", 150)
+ property int hyprlandEventDebounceMs: root.readInt("hacks.hyprlandEventDebounceMs", 40)
+ }
+ }
+
+ Process {
+ id: loadUserConfig
+ command: [
+ "sh",
+ "-lc",
+ "cfg=\"${XDG_CONFIG_HOME:-$HOME/.config}/quickshell/config.json\"; [ -r \"$cfg\" ] && cat \"$cfg\""
+ ]
+ stdout: StdioCollector {
+ id: configCollector
+ onStreamFinished: {
+ const payload = configCollector.text.trim();
+ if (!payload)
+ return;
+
+ try {
+ const parsed = JSON.parse(payload);
+ if (typeof parsed === "object" && parsed !== null) {
+ root.userOptions = parsed;
+ } else {
+ console.warn("overview: config.json must contain a JSON object; ignoring file");
+ }
+ } catch (error) {
+ console.warn("overview: failed to parse user config.json; using defaults", error);
+ }
+ }
+ }
+ }
+
+ Component.onCompleted: {
+ loadUserConfig.running = true;
+ }
+}
diff --git a/home/hypr/quickshell/common/functions/ColorUtils.qml b/home/hypr/quickshell/common/functions/ColorUtils.qml
new file mode 100644
index 0000000..6162df1
--- /dev/null
+++ b/home/hypr/quickshell/common/functions/ColorUtils.qml
@@ -0,0 +1,68 @@
+pragma Singleton
+import Quickshell
+
+Singleton {
+ id: root
+
+ function colorWithHueOf(color1, color2) {
+ var c1 = Qt.color(color1);
+ var c2 = Qt.color(color2);
+ var hue = c2.hsvHue;
+ var sat = c1.hsvSaturation;
+ var val = c1.hsvValue;
+ var alpha = c1.a;
+ return Qt.hsva(hue, sat, val, alpha);
+ }
+
+ function colorWithSaturationOf(color1, color2) {
+ var c1 = Qt.color(color1);
+ var c2 = Qt.color(color2);
+ var hue = c1.hsvHue;
+ var sat = c2.hsvSaturation;
+ var val = c1.hsvValue;
+ var alpha = c1.a;
+ return Qt.hsva(hue, sat, val, alpha);
+ }
+
+ function colorWithLightness(color, lightness) {
+ var c = Qt.color(color);
+ return Qt.hsla(c.hslHue, c.hslSaturation, lightness, c.a);
+ }
+
+ function colorWithLightnessOf(color1, color2) {
+ var c2 = Qt.color(color2);
+ return colorWithLightness(color1, c2.hslLightness);
+ }
+
+ function adaptToAccent(color1, color2) {
+ var c1 = Qt.color(color1);
+ var c2 = Qt.color(color2);
+ var hue = c2.hslHue;
+ var sat = c2.hslSaturation;
+ var light = c1.hslLightness;
+ var alpha = c1.a;
+ return Qt.hsla(hue, sat, light, alpha);
+ }
+
+ function mix(color1, color2, percentage = 0.5) {
+ var c1 = Qt.color(color1);
+ var c2 = Qt.color(color2);
+ return Qt.rgba(
+ percentage * c1.r + (1 - percentage) * c2.r,
+ percentage * c1.g + (1 - percentage) * c2.g,
+ percentage * c1.b + (1 - percentage) * c2.b,
+ percentage * c1.a + (1 - percentage) * c2.a
+ );
+ }
+
+ function transparentize(color, percentage = 1) {
+ var c = Qt.color(color);
+ return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage));
+ }
+
+ function applyAlpha(color, alpha) {
+ var c = Qt.color(color);
+ var a = Math.max(0, Math.min(1, alpha));
+ return Qt.rgba(c.r, c.g, c.b, a);
+ }
+}
diff --git a/home/hypr/quickshell/common/functions/qmldir b/home/hypr/quickshell/common/functions/qmldir
new file mode 100644
index 0000000..4c648e7
--- /dev/null
+++ b/home/hypr/quickshell/common/functions/qmldir
@@ -0,0 +1 @@
+singleton ColorUtils 1.0 ColorUtils.qml
diff --git a/home/hypr/quickshell/common/qmldir b/home/hypr/quickshell/common/qmldir
new file mode 100644
index 0000000..a848518
--- /dev/null
+++ b/home/hypr/quickshell/common/qmldir
@@ -0,0 +1,7 @@
+singleton Appearance 1.0 Appearance.qml
+singleton Config 1.0 Config.qml
+singleton ColorUtils 1.0 functions/ColorUtils.qml
+StyledText 1.0 widgets/StyledText.qml
+StyledRectangularShadow 1.0 widgets/StyledRectangularShadow.qml
+StyledToolTip 1.0 widgets/StyledToolTip.qml
+StyledToolTipContent 1.0 widgets/StyledToolTipContent.qml
diff --git a/home/hypr/quickshell/common/widgets/StyledRectangularShadow.qml b/home/hypr/quickshell/common/widgets/StyledRectangularShadow.qml
new file mode 100644
index 0000000..ccdff1a
--- /dev/null
+++ b/home/hypr/quickshell/common/widgets/StyledRectangularShadow.qml
@@ -0,0 +1,14 @@
+import QtQuick
+import QtQuick.Effects
+import ".."
+
+RectangularShadow {
+ required property var target
+ anchors.fill: target
+ radius: 20
+ blur: 0.9 * Appearance.sizes.elevationMargin
+ offset: Qt.vector2d(0.0, 1.0)
+ spread: 1
+ color: Appearance.colors.colShadow
+ cached: true
+}
diff --git a/home/hypr/quickshell/common/widgets/StyledText.qml b/home/hypr/quickshell/common/widgets/StyledText.qml
new file mode 100644
index 0000000..abfcefa
--- /dev/null
+++ b/home/hypr/quickshell/common/widgets/StyledText.qml
@@ -0,0 +1,16 @@
+import QtQuick
+import ".."
+
+Text {
+ id: root
+ property bool animateChange: false
+
+ renderType: Text.NativeRendering
+ verticalAlignment: Text.AlignVCenter
+ font {
+ hintingPreference: Font.PreferFullHinting
+ family: Appearance?.font.family.main ?? "sans-serif"
+ pixelSize: Appearance?.font.pixelSize.small ?? 15
+ }
+ color: Appearance?.m3colors.m3onBackground ?? "white"
+}
diff --git a/home/hypr/quickshell/common/widgets/StyledToolTip.qml b/home/hypr/quickshell/common/widgets/StyledToolTip.qml
new file mode 100644
index 0000000..4d4678c
--- /dev/null
+++ b/home/hypr/quickshell/common/widgets/StyledToolTip.qml
@@ -0,0 +1,23 @@
+import QtQuick
+import QtQuick.Controls
+import "."
+
+ToolTip {
+ id: root
+ property bool extraVisibleCondition: true
+ property bool alternativeVisibleCondition: false
+ readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
+ verticalPadding: 5
+ horizontalPadding: 10
+ background: null
+
+ visible: internalVisibleCondition
+
+ contentItem: StyledToolTipContent {
+ id: contentItem
+ text: root.text
+ shown: root.internalVisibleCondition
+ horizontalPadding: root.horizontalPadding
+ verticalPadding: root.verticalPadding
+ }
+}
diff --git a/home/hypr/quickshell/common/widgets/StyledToolTipContent.qml b/home/hypr/quickshell/common/widgets/StyledToolTipContent.qml
new file mode 100644
index 0000000..b8c29c1
--- /dev/null
+++ b/home/hypr/quickshell/common/widgets/StyledToolTipContent.qml
@@ -0,0 +1,49 @@
+import QtQuick
+import "."
+import "../"
+
+Item {
+ id: root
+ required property string text
+ property bool shown: false
+ property real horizontalPadding: 10
+ property real verticalPadding: 5
+ implicitWidth: tooltipTextObject.implicitWidth + 2 * root.horizontalPadding
+ implicitHeight: tooltipTextObject.implicitHeight + 2 * root.verticalPadding
+
+ property bool isVisible: backgroundRectangle.implicitHeight > 0
+
+ Rectangle {
+ id: backgroundRectangle
+ anchors {
+ bottom: root.bottom
+ horizontalCenter: root.horizontalCenter
+ }
+ color: Appearance?.colors.colTooltip ?? "#3C4043"
+ radius: Appearance?.rounding.verysmall ?? 7
+ opacity: shown ? 1 : 0
+ implicitWidth: shown ? (tooltipTextObject.implicitWidth + 2 * root.horizontalPadding) : 0
+ implicitHeight: shown ? (tooltipTextObject.implicitHeight + 2 * root.verticalPadding) : 0
+ clip: true
+
+ Behavior on implicitWidth {
+ animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+ Behavior on implicitHeight {
+ animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+ Behavior on opacity {
+ animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+
+ StyledText {
+ id: tooltipTextObject
+ anchors.centerIn: parent
+ text: root.text
+ font.pixelSize: Appearance?.font.pixelSize.smaller ?? 14
+ font.hintingPreference: Font.PreferNoHinting
+ color: Appearance?.colors.colOnTooltip ?? "#FFFFFF"
+ wrapMode: Text.Wrap
+ }
+ }
+}
diff --git a/home/hypr/quickshell/common/widgets/qmldir b/home/hypr/quickshell/common/widgets/qmldir
new file mode 100644
index 0000000..0efe136
--- /dev/null
+++ b/home/hypr/quickshell/common/widgets/qmldir
@@ -0,0 +1,4 @@
+StyledText 1.0 StyledText.qml
+StyledRectangularShadow 1.0 StyledRectangularShadow.qml
+StyledToolTip 1.0 StyledToolTip.qml
+StyledToolTipContent 1.0 StyledToolTipContent.qml
diff --git a/home/hypr/quickshell/components/AboutMac.qml b/home/hypr/quickshell/components/AboutMac.qml
deleted file mode 100644
index 935b9fe..0000000
--- a/home/hypr/quickshell/components/AboutMac.qml
+++ /dev/null
@@ -1,260 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-import QtQuick.Controls
-import Quickshell
-import Quickshell.Wayland
-import "../theme"
-import ".."
-
-PanelWindow {
- id: aboutWindow
- visible: GlobalState.aboutMacVisible
-
- anchors {
- top: true
- left: true
- right: true
- bottom: true
- }
-
- color: Qt.rgba(0, 0, 0, 0.35)
- WlrLayershell.layer: WlrLayer.Overlay
- WlrLayershell.namespace: "quickshell-aboutmac"
- WlrLayershell.keyboardFocus: visible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
-
- // Dismiss when clicking outside modal
- MouseArea {
- anchors.fill: parent
- onClicked: GlobalState.aboutMacVisible = false
- }
-
- // Centered Dialog
- Rectangle {
- id: dialogBox
- anchors.centerIn: parent
- width: 540
- height: 330
- radius: Theme.radiusModal
- color: Theme.windowBg
- border.color: Theme.windowBorder
- border.width: 1
-
- // Prevent clicking inside from dismissing
- MouseArea {
- anchors.fill: parent
- onClicked: {}
- }
-
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: 16
- spacing: 12
-
- // Top Traffic Lights Bar
- RowLayout {
- Layout.fillWidth: true
- spacing: 8
-
- // Red Close Dot
- Rectangle {
- width: 12
- height: 12
- radius: 6
- color: closeMouse.containsMouse ? "#E0443E" : "#FF5F56"
- border.color: "#E0443E"
- border.width: 0.5
-
- Text {
- anchors.centerIn: parent
- visible: closeMouse.containsMouse
- text: "×"
- font.pixelSize: 10
- color: "#4A0002"
- }
-
- MouseArea {
- id: closeMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: GlobalState.aboutMacVisible = false
- }
- }
-
- // Yellow Minimize Dot
- Rectangle {
- width: 12
- height: 12
- radius: 6
- color: "#FFBD2E"
- border.color: "#DEA123"
- border.width: 0.5
- }
-
- // Green Zoom Dot
- Rectangle {
- width: 12
- height: 12
- radius: 6
- color: "#27C93F"
- border.color: "#1AAB29"
- border.width: 0.5
- }
-
- Item { Layout.fillWidth: true }
-
- Text {
- text: "Overview"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.DemiBold
- color: Theme.textSecondary
- }
-
- Item { Layout.fillWidth: true }
- }
-
- // Main Specs Content
- RowLayout {
- Layout.fillWidth: true
- Layout.fillHeight: true
- spacing: 24
-
- // Left: Catalina Island Badge
- Image {
- Layout.alignment: Qt.AlignVCenter
- width: 130
- height: 130
- source: "../assets/catalina-badge.svg"
- sourceSize: Qt.size(130, 130)
- }
-
- // Right: System Details
- ColumnLayout {
- Layout.fillWidth: true
- Layout.alignment: Qt.AlignVCenter
- spacing: 6
-
- Text {
- text: "macOS Catalina"
- font.family: Theme.fontFamily
- font.pixelSize: 22
- font.weight: Font.Bold
- color: "#FFFFFF"
- }
-
- Text {
- text: "Version 10.15.7 (NixOS Edition)"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textSecondary
- }
-
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 1
- color: Qt.rgba(1.0, 1.0, 1.0, 0.12)
- }
-
- // Specs Rows
- SpecRow { label: "Host"; value: "Hades (AMD + NVIDIA RTX)" }
- SpecRow { label: "Processor"; value: "AMD High-Performance Multi-Core" }
- SpecRow { label: "Memory"; value: `${GlobalState.memTotalGb} GB RAM (${GlobalState.memUsedGb} GB in use)` }
- SpecRow { label: "Graphics"; value: "NVIDIA GeForce RTX (Proprietary Driver)" }
- SpecRow { label: "Compositor"; value: "Hyprland 0.55+ (Wayland)" }
- SpecRow { label: "Operating System"; value: "NixOS 26.05 (x86_64-linux)" }
- }
- }
-
- // Bottom Action Buttons
- RowLayout {
- Layout.fillWidth: true
- Layout.alignment: Qt.AlignRight
- spacing: 12
-
- Rectangle {
- implicitWidth: sysRepText.implicitWidth + 20
- height: 24
- radius: Theme.radiusSmall
- color: repMouse.containsMouse ? Qt.rgba(1,1,1,0.2) : Qt.rgba(1,1,1,0.1)
- border.color: Qt.rgba(1,1,1,0.15)
- border.width: 1
-
- Text {
- id: sysRepText
- anchors.centerIn: parent
- text: "System Report..."
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textPrimary
- }
-
- MouseArea {
- id: repMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- GlobalState.aboutMacVisible = false;
- GlobalState.launchApp("foot -e btop");
- }
- }
- }
-
- Rectangle {
- implicitWidth: updText.implicitWidth + 20
- height: 24
- radius: Theme.radiusSmall
- color: updMouse.containsMouse ? Qt.rgba(1,1,1,0.2) : Qt.rgba(1,1,1,0.1)
- border.color: Qt.rgba(1,1,1,0.15)
- border.width: 1
-
- Text {
- id: updText
- anchors.centerIn: parent
- text: "Software Update..."
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textPrimary
- }
-
- MouseArea {
- id: updMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- GlobalState.aboutMacVisible = false;
- GlobalState.launchApp("foot -e zsh -c 'echo \"Checking NixOS system configuration...\"; sudo nixos-rebuild dry-activate --flake /home/anish/NixOS-Configs#hades; exec zsh'");
- }
- }
- }
- }
- }
- }
-
- component SpecRow: RowLayout {
- property string label: ""
- property string value: ""
- Layout.fillWidth: true
- spacing: 10
-
- Text {
- text: label
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.DemiBold
- color: Theme.textSecondary
- Layout.preferredWidth: 105
- }
-
- Text {
- text: value
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textPrimary
- Layout.fillWidth: true
- elide: Text.ElideRight
- }
- }
-}
diff --git a/home/hypr/quickshell/components/AppMenuDropdown.qml b/home/hypr/quickshell/components/AppMenuDropdown.qml
deleted file mode 100644
index ad58b53..0000000
--- a/home/hypr/quickshell/components/AppMenuDropdown.qml
+++ /dev/null
@@ -1,147 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-import QtQuick.Controls
-import "../theme"
-import ".."
-
-Rectangle {
- id: root
- property string menuName: ""
- width: 200
- implicitHeight: menuCol.implicitHeight + 12
- radius: Theme.radiusMedium
- color: Theme.menuDropdownBg
- border.color: Theme.menuDropdownBorder
- border.width: 1
-
- ColumnLayout {
- id: menuCol
- anchors.fill: parent
- anchors.margins: 6
- spacing: 2
-
- // FILE MENU
- Repeater {
- model: root.menuName === "File" ? [
- { text: "New Window", shortcut: "⌘N", cmd: "foot &" },
- { text: "New Incognito Window", shortcut: "⇧⌘N", cmd: "brave --incognito &" },
- { text: "Close Window", shortcut: "⌘W", cmd: "hyprctl dispatch \"hl.dsp.window.close()\"" },
- { text: "---", shortcut: "", cmd: "" },
- { text: "Take Screenshot", shortcut: "⇧⌘5", cmd: "grim -g \"$(slurp)\" ~/screenshot.png" }
- ] : []
- delegate: MenuEntryDelegate {}
- }
-
- // EDIT MENU
- Repeater {
- model: root.menuName === "Edit" ? [
- { text: "Undo", shortcut: "⌘Z", cmd: "" },
- { text: "Redo", shortcut: "⇧⌘Z", cmd: "" },
- { text: "---", shortcut: "", cmd: "" },
- { text: "Cut", shortcut: "⌘X", cmd: "wl-copy" },
- { text: "Copy", shortcut: "⌘C", cmd: "wl-copy" },
- { text: "Paste", shortcut: "⌘V", cmd: "wl-paste" },
- { text: "Select All", shortcut: "⌘A", cmd: "" }
- ] : []
- delegate: MenuEntryDelegate {}
- }
-
- // VIEW MENU
- Repeater {
- model: root.menuName === "View" ? [
- { text: "Toggle Fullscreen", shortcut: "⌃⌘F", cmd: "hyprctl dispatch \"hl.dsp.window.fullscreen({fullscreen = 0})\"" },
- { text: "Toggle Floating", shortcut: "⌘V", cmd: "hyprctl dispatch \"hl.dsp.window.float({action = \"toggle\"})\"" },
- { text: "Toggle Group", shortcut: "⌘G", cmd: "hyprctl dispatch \"hl.dsp.group.toggle()\"" },
- { text: "---", shortcut: "", cmd: "" },
- { text: "Zoom In", shortcut: "⌘+", cmd: "" },
- { text: "Zoom Out", shortcut: "⌘-", cmd: "" }
- ] : []
- delegate: MenuEntryDelegate {}
- }
-
- // WINDOW MENU
- Repeater {
- model: root.menuName === "Window" ? [
- { text: "Minimize Window", shortcut: "⌘M", cmd: "hyprctl dispatch \"hl.dsp.window.move({workspace = \"special:scratch\"})\"" },
- { text: "Tile Left", shortcut: "⌃⌥←", cmd: "hyprctl dispatch \"hl.dsp.window.move({direction = \"left\"})\"" },
- { text: "Tile Right", shortcut: "⌃⌥→", cmd: "hyprctl dispatch \"hl.dsp.window.move({direction = \"right\"})\"" },
- { text: "---", shortcut: "", cmd: "" },
- { text: "Show Scratchpad", shortcut: "⌘↩", cmd: "hyprctl dispatch \"hl.dsp.workspace.toggle_special(\"scratch\")\"" },
- { text: "Show Messaging", shortcut: "⌘M", cmd: "hyprctl dispatch \"hl.dsp.workspace.toggle_special(\"messaging\")\"" }
- ] : []
- delegate: MenuEntryDelegate {}
- }
-
- // HELP MENU
- Repeater {
- model: root.menuName === "Help" ? [
- { text: "Hyprland Documentation", shortcut: "", cmd: "brave https://wiki.hyprland.org &" },
- { text: "NixOS Manual", shortcut: "", cmd: "brave https://nixos.org/manual/nixos/stable/ &" },
- { text: "---", shortcut: "", cmd: "" },
- { text: "About Antigravity", shortcut: "", cmd: "" }
- ] : []
- delegate: MenuEntryDelegate {}
- }
- }
-
- component MenuEntryDelegate: Item {
- Layout.fillWidth: true
- Layout.preferredHeight: modelData.text === "---" ? 7 : 22
-
- Rectangle {
- anchors.fill: parent
- visible: modelData.text === "---"
- color: "transparent"
- Rectangle {
- anchors.centerIn: parent
- width: parent.width
- height: 1
- color: Qt.rgba(1.0, 1.0, 1.0, 0.12)
- }
- }
-
- Rectangle {
- anchors.fill: parent
- visible: modelData.text !== "---"
- radius: Theme.radiusSmall
- color: itemMouse.containsMouse ? Theme.menuHoverBlue : "transparent"
-
- RowLayout {
- anchors.fill: parent
- anchors.leftMargin: 10
- anchors.rightMargin: 10
- spacing: 8
-
- Text {
- text: modelData.text
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeNormal
- color: itemMouse.containsMouse ? Theme.menuTextHover : Theme.textPrimary
- Layout.fillWidth: true
- elide: Text.ElideRight
- }
-
- Text {
- visible: modelData.shortcut !== ""
- text: modelData.shortcut
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: itemMouse.containsMouse ? Theme.menuTextHover : Theme.textTertiary
- }
- }
-
- MouseArea {
- id: itemMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- GlobalState.activeDropdownMenu = "";
- if (modelData.cmd) {
- GlobalState.launchApp(modelData.cmd);
- }
- }
- }
- }
- }
-}
diff --git a/home/hypr/quickshell/components/AppleMenu.qml b/home/hypr/quickshell/components/AppleMenu.qml
deleted file mode 100644
index 8382b27..0000000
--- a/home/hypr/quickshell/components/AppleMenu.qml
+++ /dev/null
@@ -1,158 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-import QtQuick.Controls
-import "../theme"
-import ".."
-
-Rectangle {
- id: root
- width: 220
- implicitHeight: menuColumn.implicitHeight + 12
- radius: Theme.radiusMedium
- color: Theme.menuDropdownBg
- border.color: Theme.menuDropdownBorder
- border.width: 1
-
- // Drop shadow
- layer.enabled: true
-
- ColumnLayout {
- id: menuColumn
- anchors.fill: parent
- anchors.margins: 6
- spacing: 2
-
- // About This Mac
- MenuItem {
- text: "About This Mac"
- onTriggered: {
- GlobalState.appleMenuVisible = false;
- GlobalState.toggleAboutMac();
- }
- }
-
- MenuSeparator {}
-
- MenuItem {
- text: "System Preferences..."
- onTriggered: {
- GlobalState.launchApp("pavucontrol");
- }
- }
-
- MenuItem {
- text: "App Store..."
- onTriggered: {
- GlobalState.launchApp("foot -e zsh -c 'echo NixOS Packages; nix-env -qaP | head -n 50; exec zsh'");
- }
- }
-
- MenuSeparator {}
-
- MenuItem {
- text: "Activity Monitor"
- shortcut: "⌥⌘Esc"
- onTriggered: {
- GlobalState.launchApp("foot -e btop");
- }
- }
-
- MenuSeparator {}
-
- MenuItem {
- text: "Sleep"
- onTriggered: {
- GlobalState.appleMenuVisible = false;
- GlobalState.launchApp("systemctl suspend");
- }
- }
-
- MenuItem {
- text: "Restart..."
- onTriggered: {
- GlobalState.appleMenuVisible = false;
- GlobalState.launchApp("systemctl reboot");
- }
- }
-
- MenuItem {
- text: "Shut Down..."
- onTriggered: {
- GlobalState.appleMenuVisible = false;
- GlobalState.launchApp("systemctl poweroff");
- }
- }
-
- MenuSeparator {}
-
- MenuItem {
- text: "Lock Screen"
- shortcut: "⌃⌘Q"
- onTriggered: {
- GlobalState.appleMenuVisible = false;
- GlobalState.launchApp("hyprlock");
- }
- }
-
- MenuItem {
- text: "Log Out anish..."
- shortcut: "⇧⌘Q"
- onTriggered: {
- GlobalState.appleMenuVisible = false;
- GlobalState.launchApp("loginctl terminate-session");
- }
- }
- }
-
- component MenuItem: Rectangle {
- id: itemRoot
- property string text: ""
- property string shortcut: ""
- signal triggered()
-
- Layout.fillWidth: true
- Layout.preferredHeight: 22
- radius: Theme.radiusSmall
- color: mouseArea.containsMouse ? Theme.menuHoverBlue : "transparent"
-
- RowLayout {
- anchors.fill: parent
- anchors.leftMargin: 10
- anchors.rightMargin: 10
- spacing: 8
-
- Text {
- text: itemRoot.text
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeNormal
- color: mouseArea.containsMouse ? Theme.menuTextHover : Theme.textPrimary
- Layout.fillWidth: true
- elide: Text.ElideRight
- }
-
- Text {
- visible: itemRoot.shortcut !== ""
- text: itemRoot.shortcut
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: mouseArea.containsMouse ? Theme.menuTextHover : Theme.textTertiary
- }
- }
-
- MouseArea {
- id: mouseArea
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: itemRoot.triggered()
- }
- }
-
- component MenuSeparator: Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 1
- Layout.topMargin: 3
- Layout.bottomMargin: 3
- color: Qt.rgba(1.0, 1.0, 1.0, 0.12)
- }
-}
diff --git a/home/hypr/quickshell/components/BatteryPopup.qml b/home/hypr/quickshell/components/BatteryPopup.qml
deleted file mode 100644
index daae340..0000000
--- a/home/hypr/quickshell/components/BatteryPopup.qml
+++ /dev/null
@@ -1,102 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-import QtQuick.Controls
-import "../theme"
-import ".."
-
-Rectangle {
- id: root
- width: 240
- implicitHeight: batCol.implicitHeight + 24
- radius: Theme.radiusMedium
- color: Theme.menuDropdownBg
- border.color: Theme.menuDropdownBorder
- border.width: 1
-
- ColumnLayout {
- id: batCol
- anchors.fill: parent
- anchors.margins: 12
- spacing: 8
-
- RowLayout {
- Layout.fillWidth: true
- Text {
- text: "Battery"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeNormal
- font.weight: Font.DemiBold
- color: Theme.textPrimary
- Layout.fillWidth: true
- }
- Text {
- text: `${GlobalState.batteryPercent}%`
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.DemiBold
- color: GlobalState.batteryPercent <= 20 ? Theme.accentRed : Theme.accentGreen
- }
- }
-
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 1
- color: Qt.rgba(1.0, 1.0, 1.0, 0.12)
- }
-
- ColumnLayout {
- Layout.fillWidth: true
- spacing: 3
-
- Text {
- text: GlobalState.batteryPresent
- ? (GlobalState.batteryCharging ? "Power Source: Power Adapter" : "Power Source: Battery")
- : "Power Source: AC Connected"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textPrimary
- }
-
- Text {
- text: GlobalState.batteryPresent
- ? `Status: ${GlobalState.batteryStatus}`
- : "Desktop Workstation Mode"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textSecondary
- }
- }
-
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 1
- color: Qt.rgba(1.0, 1.0, 1.0, 0.12)
- }
-
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 24
- radius: Theme.radiusSmall
- color: batPrefMouse.containsMouse ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : Qt.rgba(1.0, 1.0, 1.0, 0.08)
-
- Text {
- anchors.centerIn: parent
- text: "Energy Saver Preferences..."
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textPrimary
- }
-
- MouseArea {
- id: batPrefMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- GlobalState.batteryPopupVisible = false;
- GlobalState.launchApp("foot -e btop");
- }
- }
- }
- }
-}
diff --git a/home/hypr/quickshell/components/CalendarPopup.qml b/home/hypr/quickshell/components/CalendarPopup.qml
deleted file mode 100644
index 2ec1588..0000000
--- a/home/hypr/quickshell/components/CalendarPopup.qml
+++ /dev/null
@@ -1,94 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-import QtQuick.Controls
-import "../theme"
-import ".."
-
-Rectangle {
- id: root
- width: 250
- implicitHeight: calCol.implicitHeight + 24
- radius: Theme.radiusMedium
- color: Theme.menuDropdownBg
- border.color: Theme.menuDropdownBorder
- border.width: 1
-
- ColumnLayout {
- id: calCol
- anchors.fill: parent
- anchors.margins: 12
- spacing: 10
-
- Text {
- text: GlobalState.fullDateString
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeNormal
- font.weight: Font.DemiBold
- color: Theme.textPrimary
- Layout.alignment: Qt.AlignHCenter
- }
-
- Text {
- text: GlobalState.timeString
- font.family: Theme.fontFamily
- font.pixelSize: 22
- font.weight: Font.Light
- color: Theme.accentBlue
- Layout.alignment: Qt.AlignHCenter
- }
-
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 1
- color: Qt.rgba(1.0, 1.0, 1.0, 0.12)
- }
-
- // Days grid header
- RowLayout {
- Layout.fillWidth: true
- Repeater {
- model: ["S", "M", "T", "W", "T", "F", "S"]
- delegate: Text {
- text: modelData
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.DemiBold
- color: Theme.textTertiary
- horizontalAlignment: Text.AlignHCenter
- Layout.fillWidth: true
- }
- }
- }
-
- // Quick mini-calendar representation
- GridLayout {
- id: gridDays
- columns: 7
- Layout.fillWidth: true
- rowSpacing: 4
- columnSpacing: 4
-
- readonly property var todayDate: new Date()
- readonly property int currentDay: todayDate.getDate()
-
- Repeater {
- model: 31
- delegate: Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 22
- radius: 11
- color: (index + 1) === gridDays.currentDay ? Theme.accentBlue : "transparent"
-
- Text {
- anchors.centerIn: parent
- text: `${index + 1}`
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- font.weight: (index + 1) === gridDays.currentDay ? Font.Bold : Font.Normal
- color: (index + 1) === gridDays.currentDay ? "#FFFFFF" : Theme.textSecondary
- }
- }
- }
- }
- }
-}
diff --git a/home/hypr/quickshell/components/ControlCenter.qml b/home/hypr/quickshell/components/ControlCenter.qml
deleted file mode 100644
index a204557..0000000
--- a/home/hypr/quickshell/components/ControlCenter.qml
+++ /dev/null
@@ -1,638 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-import QtQuick.Controls
-import Quickshell
-import Quickshell.Wayland
-import "../theme"
-import ".."
-
-PanelWindow {
- id: ccWindow
- visible: GlobalState.controlCenterVisible
-
- anchors {
- top: true
- right: true
- bottom: true
- }
-
- margins {
- top: Theme.menuBarHeight
- }
-
- implicitWidth: 360
- color: "transparent"
- WlrLayershell.layer: WlrLayer.Overlay
- WlrLayershell.namespace: "quickshell-controlcenter"
- WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
-
- property int activeTab: 0 // 0 = Today, 1 = Notifications
-
- // Sidebar Container
- Rectangle {
- id: sidebarPanel
- anchors.fill: parent
- color: Theme.windowBg
- border.color: Theme.windowBorder
- border.width: 1
-
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: 14
- spacing: 12
-
- // =================================================================
- // TAB SWITCHER (Today | Notifications)
- // =================================================================
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 30
- radius: Theme.radiusMedium
- color: Qt.rgba(0.0, 0.0, 0.0, 0.3)
-
- RowLayout {
- anchors.fill: parent
- spacing: 0
-
- // Today Tab Button
- Rectangle {
- Layout.fillWidth: true
- Layout.fillHeight: true
- radius: Theme.radiusMedium
- color: ccWindow.activeTab === 0 ? Qt.rgba(1.0, 1.0, 1.0, 0.18) : "transparent"
-
- Text {
- anchors.centerIn: parent
- text: "Today"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeNormal
- font.weight: ccWindow.activeTab === 0 ? Font.DemiBold : Font.Normal
- color: ccWindow.activeTab === 0 ? "#FFFFFF" : Theme.textTertiary
- }
-
- MouseArea {
- anchors.fill: parent
- cursorShape: Qt.PointingHandCursor
- onClicked: ccWindow.activeTab = 0
- }
- }
-
- // Notifications Tab Button
- Rectangle {
- Layout.fillWidth: true
- Layout.fillHeight: true
- radius: Theme.radiusMedium
- color: ccWindow.activeTab === 1 ? Qt.rgba(1.0, 1.0, 1.0, 0.18) : "transparent"
-
- Text {
- anchors.centerIn: parent
- text: "Notifications"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeNormal
- font.weight: ccWindow.activeTab === 1 ? Font.DemiBold : Font.Normal
- color: ccWindow.activeTab === 1 ? "#FFFFFF" : Theme.textTertiary
- }
-
- MouseArea {
- anchors.fill: parent
- cursorShape: Qt.PointingHandCursor
- onClicked: ccWindow.activeTab = 1
- }
- }
- }
- }
-
- // =================================================================
- // TAB 1: TODAY VIEW
- // =================================================================
- ScrollView {
- visible: ccWindow.activeTab === 0
- Layout.fillWidth: true
- Layout.fillHeight: true
- clip: true
-
- ColumnLayout {
- width: parent.width
- spacing: 12
-
- // 1. Large Date & Time Widget
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 70
- radius: Theme.radiusLarge
- color: Theme.cardBg
- border.color: Theme.cardBorder
- border.width: 1
-
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: 12
- spacing: 2
-
- Text {
- text: GlobalState.fullDateString
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.DemiBold
- color: Theme.accentBlue
- }
-
- Text {
- text: GlobalState.timeString
- font.family: Theme.fontFamily
- font.pixelSize: 26
- font.weight: Font.Light
- color: "#FFFFFF"
- }
- }
- }
-
- // 2. Control Toggles (Wi-Fi, Bluetooth, DND, Night Shift)
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 120
- radius: Theme.radiusLarge
- color: Theme.cardBg
- border.color: Theme.cardBorder
- border.width: 1
-
- GridLayout {
- anchors.fill: parent
- anchors.margins: 10
- columns: 2
- rowSpacing: 8
- columnSpacing: 8
-
- // Wi-Fi Toggle
- ToggleCard {
- iconSource: "../assets/wifi.svg"
- title: "Wi-Fi"
- subtitle: GlobalState.wifiSsid
- isActive: GlobalState.wifiConnected
- onClicked: GlobalState.launchApp("nm-connection-editor || foot -e nmtui")
- }
-
- // Bluetooth Toggle
- ToggleCard {
- iconSource: "../assets/bluetooth.svg"
- title: "Bluetooth"
- subtitle: "Active"
- isActive: true
- onClicked: GlobalState.launchApp("foot -e bluetoothctl")
- }
-
- // Do Not Disturb Toggle
- ToggleCard {
- iconSource: "../assets/preferences.svg"
- title: "Do Not Disturb"
- subtitle: "Off"
- isActive: false
- onClicked: {}
- }
-
- // Sound Mute Toggle
- ToggleCard {
- iconSource: "../assets/volume.svg"
- title: "Sound"
- subtitle: GlobalState.volumeMuted ? "Muted" : `${GlobalState.volume}%`
- isActive: !GlobalState.volumeMuted
- onClicked: GlobalState.toggleVolumeMute()
- }
- }
- }
-
- // 3. Brightness & Volume Sliders
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 110
- radius: Theme.radiusLarge
- color: Theme.cardBg
- border.color: Theme.cardBorder
- border.width: 1
-
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: 12
- spacing: 10
-
- // Display Brightness Slider
- RowLayout {
- Layout.fillWidth: true
- spacing: 8
-
- Image {
- width: 16
- height: 16
- source: "../assets/brightness.svg"
- sourceSize: Qt.size(16, 16)
- }
-
- Item {
- Layout.fillWidth: true
- height: 18
-
- Rectangle {
- anchors.fill: parent
- radius: 9
- color: Qt.rgba(1,1,1,0.18)
-
- Rectangle {
- anchors.left: parent.left
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- width: parent.width * (GlobalState.brightness / 100.0)
- radius: 9
- color: "#FFFFFF"
- }
- }
-
- MouseArea {
- anchors.fill: parent
- cursorShape: Qt.PointingHandCursor
- function update(m) {
- const frac = Math.max(0.01, Math.min(1.0, m.x / width));
- GlobalState.setBrightness(Math.round(frac * 100));
- }
- onPressed: (m) => update(m)
- onPositionChanged: (m) => { if (pressed) update(m); }
- }
- }
- }
-
- // Sound Volume Slider
- RowLayout {
- Layout.fillWidth: true
- spacing: 8
-
- Image {
- width: 16
- height: 16
- source: "../assets/volume.svg"
- sourceSize: Qt.size(16, 16)
- }
-
- Item {
- Layout.fillWidth: true
- height: 18
-
- Rectangle {
- anchors.fill: parent
- radius: 9
- color: Qt.rgba(1,1,1,0.18)
-
- Rectangle {
- anchors.left: parent.left
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- width: parent.width * (GlobalState.volume / 100.0)
- radius: 9
- color: Theme.accentBlue
- }
- }
-
- MouseArea {
- anchors.fill: parent
- cursorShape: Qt.PointingHandCursor
- function update(m) {
- const frac = Math.max(0.0, Math.min(1.0, m.x / width));
- GlobalState.setVolume(Math.round(frac * 100));
- }
- onPressed: (m) => update(m)
- onPositionChanged: (m) => { if (pressed) update(m); }
- }
- }
- }
- }
- }
-
- // 4. System Resources Monitor Widget
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 90
- radius: Theme.radiusLarge
- color: Theme.cardBg
- border.color: Theme.cardBorder
- border.width: 1
-
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: 12
- spacing: 6
-
- Text {
- text: "System Performance"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.DemiBold
- color: Theme.textSecondary
- }
-
- // CPU Bar
- RowLayout {
- Layout.fillWidth: true
- spacing: 8
- Text {
- text: "CPU"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textPrimary
- Layout.preferredWidth: 32
- }
- Rectangle {
- Layout.fillWidth: true
- height: 6
- radius: 3
- color: Qt.rgba(1,1,1,0.15)
- Rectangle {
- anchors.left: parent.left
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- width: parent.width * (GlobalState.cpuPercent / 100.0)
- radius: 3
- color: GlobalState.cpuPercent > 80 ? Theme.accentRed : Theme.accentGreen
- }
- }
- Text {
- text: `${GlobalState.cpuPercent}%`
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textSecondary
- Layout.preferredWidth: 36
- horizontalAlignment: Text.AlignRight
- }
- }
-
- // Memory Bar
- RowLayout {
- Layout.fillWidth: true
- spacing: 8
- Text {
- text: "RAM"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textPrimary
- Layout.preferredWidth: 32
- }
- Rectangle {
- Layout.fillWidth: true
- height: 6
- radius: 3
- color: Qt.rgba(1,1,1,0.15)
- Rectangle {
- anchors.left: parent.left
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- width: parent.width * (GlobalState.memPercent / 100.0)
- radius: 3
- color: Theme.accentBlue
- }
- }
- Text {
- text: `${GlobalState.memUsedGb}G`
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textSecondary
- Layout.preferredWidth: 36
- horizontalAlignment: Text.AlignRight
- }
- }
- }
- }
-
- // 5. Now Playing Media Widget
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 80
- radius: Theme.radiusLarge
- color: Theme.cardBg
- border.color: Theme.cardBorder
- border.width: 1
-
- RowLayout {
- anchors.fill: parent
- anchors.margins: 12
- spacing: 10
-
- Image {
- width: 36
- height: 36
- source: "../assets/music.svg"
- sourceSize: Qt.size(36, 36)
- }
-
- ColumnLayout {
- Layout.fillWidth: true
- spacing: 2
-
- Text {
- text: GlobalState.mediaTitle || "No Media Playing"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeNormal
- font.weight: Font.Medium
- color: "#FFFFFF"
- elide: Text.ElideRight
- Layout.fillWidth: true
- }
-
- Text {
- text: GlobalState.mediaArtist || "Apple Music"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textTertiary
- elide: Text.ElideRight
- Layout.fillWidth: true
- }
- }
-
- // Playback controls
- RowLayout {
- spacing: 4
-
- Rectangle {
- width: 24
- height: 24
- radius: 12
- color: Qt.rgba(1,1,1,0.1)
- Text { anchors.centerIn: parent; text: "⏮"; color: "#FFFFFF"; font.pixelSize: 10 }
- MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: GlobalState.mediaPrevious() }
- }
-
- Rectangle {
- width: 28
- height: 28
- radius: 14
- color: Theme.accentBlue
- Text { anchors.centerIn: parent; text: GlobalState.mediaPlaying ? "⏸" : "▶"; color: "#FFFFFF"; font.pixelSize: 11 }
- MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: GlobalState.mediaPlayPause() }
- }
-
- Rectangle {
- width: 24
- height: 24
- radius: 12
- color: Qt.rgba(1,1,1,0.1)
- Text { anchors.centerIn: parent; text: "⏭"; color: "#FFFFFF"; font.pixelSize: 10 }
- MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: GlobalState.mediaNext() }
- }
- }
- }
- }
- }
- }
-
- // =================================================================
- // TAB 2: NOTIFICATIONS VIEW
- // =================================================================
- ColumnLayout {
- visible: ccWindow.activeTab === 1
- Layout.fillWidth: true
- Layout.fillHeight: true
- spacing: 12
-
- RowLayout {
- Layout.fillWidth: true
- Text {
- text: "Notifications"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeLarge
- font.weight: Font.DemiBold
- color: "#FFFFFF"
- Layout.fillWidth: true
- }
- Rectangle {
- implicitWidth: clearText.implicitWidth + 12
- height: 22
- radius: Theme.radiusSmall
- color: Qt.rgba(1,1,1,0.12)
- Text {
- id: clearText
- anchors.centerIn: parent
- text: "Clear All"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textSecondary
- }
- MouseArea {
- anchors.fill: parent
- cursorShape: Qt.PointingHandCursor
- onClicked: {}
- }
- }
- }
-
- // Placeholder / Active notifications card
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 70
- radius: Theme.radiusLarge
- color: Theme.cardBg
- border.color: Theme.cardBorder
- border.width: 1
-
- RowLayout {
- anchors.fill: parent
- anchors.margins: 12
- spacing: 10
-
- Image {
- width: 28
- height: 28
- source: "../assets/apple.svg"
- sourceSize: Qt.size(28, 28)
- }
-
- ColumnLayout {
- Layout.fillWidth: true
- spacing: 2
-
- Text {
- text: "macOS Catalina on Hyprland"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeNormal
- font.weight: Font.Medium
- color: "#FFFFFF"
- }
-
- Text {
- text: "Quickshell desktop loaded successfully."
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textSecondary
- }
- }
- }
- }
-
- Item {
- Layout.fillHeight: true
- }
- }
- }
- }
-
- // Toggle Card Component
- component ToggleCard: Rectangle {
- id: cardRoot
- property string iconSource: ""
- property string title: ""
- property string subtitle: ""
- property bool isActive: false
- signal clicked()
-
- Layout.fillWidth: true
- Layout.preferredHeight: 46
- radius: Theme.radiusMedium
- color: cardMouse.containsMouse ? Qt.rgba(1.0, 1.0, 1.0, 0.22) : Qt.rgba(1.0, 1.0, 1.0, 0.12)
-
- RowLayout {
- anchors.fill: parent
- anchors.margins: 8
- spacing: 8
-
- Rectangle {
- width: 28
- height: 28
- radius: 14
- color: cardRoot.isActive ? Theme.accentBlue : Qt.rgba(1,1,1,0.2)
-
- Image {
- anchors.centerIn: parent
- width: 14
- height: 14
- source: cardRoot.iconSource
- sourceSize: Qt.size(14, 14)
- }
- }
-
- ColumnLayout {
- Layout.fillWidth: true
- spacing: 1
-
- Text {
- text: cardRoot.title
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Medium
- color: "#FFFFFF"
- elide: Text.ElideRight
- }
-
- Text {
- text: cardRoot.subtitle
- font.family: Theme.fontFamily
- font.pixelSize: 10
- color: Theme.textTertiary
- elide: Text.ElideRight
- }
- }
- }
-
- MouseArea {
- id: cardMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: cardRoot.clicked()
- }
- }
-}
diff --git a/home/hypr/quickshell/components/Dock.qml b/home/hypr/quickshell/components/Dock.qml
deleted file mode 100644
index 2d29eb5..0000000
--- a/home/hypr/quickshell/components/Dock.qml
+++ /dev/null
@@ -1,338 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-import QtQuick.Controls
-import Quickshell
-import Quickshell.Wayland
-import Quickshell.Hyprland
-import "../theme"
-import ".."
-
-PanelWindow {
- id: dockWindow
- property var modelData
- screen: modelData
-
- anchors {
- bottom: true
- }
-
- margins {
- bottom: 6
- }
-
- // Adjust width to fit content
- implicitWidth: dockTray.implicitWidth + 32
- implicitHeight: 90
-
- color: "transparent"
- WlrLayershell.layer: WlrLayer.Top
- WlrLayershell.namespace: "quickshell-dock"
- WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
-
- // Reserve 70px exclusive zone so windows don't overlap the dock
- exclusiveZone: 70
-
- // Tooltip overlay container
- Item {
- id: tooltipContainer
- anchors.fill: parent
- z: 100
- }
-
- // 2.5D Frosted Glass Dock Tray
- Rectangle {
- id: dockTray
- anchors.bottom: parent.bottom
- anchors.horizontalCenter: parent.horizontalCenter
- implicitWidth: dockLayout.implicitWidth + 24
- height: 68
- radius: Theme.radiusDock
- color: Theme.dockBg
- border.color: Theme.dockBorder
- border.width: 1
-
- // Top glossy reflection edge line (Catalina shelf characteristic)
- Rectangle {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.top: parent.top
- anchors.leftMargin: 12
- anchors.rightMargin: 12
- height: 1
- color: Theme.dockHighlight
- opacity: 0.8
- }
-
- // Inner items layout
- RowLayout {
- id: dockLayout
- anchors.centerIn: parent
- spacing: 6
-
- // =================================================================
- // 1. PINNED APPS
- // =================================================================
- DockItem {
- id: finderItem
- name: "Finder"
- iconSource: "../assets/finder.svg"
- execCmd: "foot -e sh -c 'cd ~; lf || yazi || ls -la; exec zsh'"
- appClass: "foot"
- isPinned: true
- }
-
- DockItem {
- name: "Launchpad"
- iconSource: "../assets/launchpad.svg"
- execCmd: ""
- isPinned: true
- onCustomClicked: GlobalState.toggleSpotlight()
- }
-
- DockItem {
- name: "Brave Browser"
- iconSource: "../assets/browser.svg"
- execCmd: "brave"
- appClass: "brave-browser"
- isPinned: true
- }
-
- DockItem {
- name: "Terminal"
- iconSource: "../assets/terminal.svg"
- execCmd: "foot"
- appClass: "foot"
- isPinned: true
- }
-
- DockItem {
- name: "Neovim"
- iconSource: "../assets/code.svg"
- execCmd: "foot -e nvim"
- appClass: "nvim"
- isPinned: true
- }
-
- DockItem {
- name: "System Preferences"
- iconSource: "../assets/preferences.svg"
- execCmd: "pavucontrol"
- appClass: "pavucontrol"
- isPinned: true
- }
-
- DockItem {
- name: "App Store"
- iconSource: "../assets/appstore.svg"
- execCmd: "foot -e zsh -c 'echo NixOS Packages; nix-env -qaP | head -n 50; exec zsh'"
- appClass: ""
- isPinned: true
- }
-
- DockItem {
- name: "Music"
- iconSource: "../assets/music.svg"
- execCmd: ""
- appClass: ""
- isPinned: true
- onCustomClicked: GlobalState.mediaPlayPause()
- }
-
- // =================================================================
- // 2. DYNAMIC RUNNING APPS (Not pinned)
- // =================================================================
- Repeater {
- model: {
- const pinnedClasses = ["foot", "brave-browser", "pavucontrol", "nvim"];
- const list = [];
- const seen = {};
- if (Array.isArray(GlobalState.runningApps)) {
- for (let i = 0; i < GlobalState.runningApps.length; i++) {
- const client = GlobalState.runningApps[i];
- const cls = (client.class || "").toLowerCase();
- if (cls && !pinnedClasses.includes(cls) && !seen[cls]) {
- seen[cls] = true;
- list.push({
- name: client.title || client.class || "App",
- cls: client.class,
- address: client.address
- });
- }
- }
- }
- return list;
- }
- delegate: DockItem {
- required property var modelData
- name: modelData.name
- iconSource: "../assets/code.svg"
- appClass: modelData.cls
- isPinned: false
- onCustomClicked: {
- if (modelData.address) {
- Hyprland.dispatch(`hl.dsp.focus({window = "address:${modelData.address}"})`);
- }
- }
- }
- }
-
- // =================================================================
- // 3. DOCK SEPARATOR DIVIDER
- // =================================================================
- Rectangle {
- Layout.preferredWidth: 1
- Layout.preferredHeight: 44
- Layout.alignment: Qt.AlignVCenter
- Layout.leftMargin: 4
- Layout.rightMargin: 4
- color: Qt.rgba(1.0, 1.0, 1.0, 0.22)
- }
-
- // =================================================================
- // 4. DOWNLOADS FOLDER & TRASH
- // =================================================================
- DockItem {
- name: "Downloads"
- iconSource: "../assets/folder-downloads.svg"
- execCmd: "foot -e sh -c 'cd ~/Downloads; ls -la; exec zsh'"
- isPinned: true
- }
-
- DockItem {
- name: "Trash"
- iconSource: "../assets/trash-empty.svg"
- execCmd: "foot -e sh -c 'cd ~/.local/share/Trash/files 2>/dev/null || cd ~; ls -la; exec zsh'"
- isPinned: true
- }
- }
- }
-
- // Reusable Dock Item Component
- component DockItem: Item {
- id: itemRoot
- property string name: ""
- property string iconSource: ""
- property string execCmd: ""
- property string appClass: ""
- property bool isPinned: true
- signal customClicked()
-
- // Check if app is running
- readonly property bool isRunning: {
- if (!itemRoot.appClass) return false;
- const target = itemRoot.appClass.toLowerCase();
- if (GlobalState.activeWindowClass.toLowerCase().includes(target)) return true;
- if (Array.isArray(GlobalState.runningApps)) {
- for (let i = 0; i < GlobalState.runningApps.length; i++) {
- const c = GlobalState.runningApps[i];
- if ((c.class || "").toLowerCase().includes(target)) return true;
- }
- }
- return false;
- }
-
- implicitWidth: 50
- implicitHeight: 64
- Layout.alignment: Qt.AlignBottom
-
- // Container with scale & bounce transform
- Item {
- id: iconWrapper
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.bottom: parent.bottom
- anchors.bottomMargin: 8
- width: 48
- height: 48
-
- scale: itemArea.containsMouse ? 1.28 : 1.0
- y: itemArea.containsMouse ? -8 : 0
-
- Behavior on scale {
- NumberAnimation { duration: 180; easing.type: Easing.OutCubic }
- }
- Behavior on y {
- NumberAnimation { duration: 180; easing.type: Easing.OutCubic }
- }
-
- // Icon Image
- Image {
- anchors.centerIn: parent
- width: 46
- height: 46
- source: itemRoot.iconSource
- sourceSize: Qt.size(92, 92)
- smooth: true
- mipmap: true
- }
-
- // Click Bounce Sequential Animation
- SequentialAnimation {
- id: bounceAnim
- NumberAnimation { target: iconWrapper; property: "y"; to: -24; duration: 150; easing.type: Easing.OutQuad }
- NumberAnimation { target: iconWrapper; property: "y"; to: -4; duration: 130; easing.type: Easing.InQuad }
- NumberAnimation { target: iconWrapper; property: "y"; to: -14; duration: 100; easing.type: Easing.OutQuad }
- NumberAnimation { target: iconWrapper; property: "y"; to: 0; duration: 100; easing.type: Easing.InQuad }
- }
- }
-
- // Active App Running Indicator Dot (White glowing pill)
- Rectangle {
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.bottom: parent.bottom
- anchors.bottomMargin: 2
- width: 4
- height: 4
- radius: 2
- color: Theme.dockDot
- visible: itemRoot.isRunning
- }
-
- // Hover Tooltip Badge (macOS Catalina Style Black Pill)
- Rectangle {
- id: tooltipPill
- visible: itemArea.containsMouse
- opacity: itemArea.containsMouse ? 1.0 : 0.0
-
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.bottom: parent.top
- anchors.bottomMargin: 6
- implicitWidth: tooltipText.implicitWidth + 16
- height: 24
- radius: 6
- color: Qt.rgba(0.08, 0.08, 0.10, 0.92)
- border.color: Qt.rgba(1.0, 1.0, 1.0, 0.18)
- border.width: 1
-
- Behavior on opacity {
- NumberAnimation { duration: 120 }
- }
-
- Text {
- id: tooltipText
- anchors.centerIn: parent
- text: itemRoot.name
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Medium
- color: "#FFFFFF"
- }
- }
-
- // Mouse interaction
- MouseArea {
- id: itemArea
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
-
- onClicked: {
- bounceAnim.start();
- if (itemRoot.execCmd !== "") {
- GlobalState.launchApp(itemRoot.execCmd);
- } else {
- itemRoot.customClicked();
- }
- }
- }
- }
-}
diff --git a/home/hypr/quickshell/components/MenuBar.qml b/home/hypr/quickshell/components/MenuBar.qml
deleted file mode 100644
index ee1eb8c..0000000
--- a/home/hypr/quickshell/components/MenuBar.qml
+++ /dev/null
@@ -1,507 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-import QtQuick.Controls
-import Quickshell
-import Quickshell.Wayland
-import Quickshell.Hyprland
-import Quickshell.Services.SystemTray
-import "../theme"
-import ".."
-
-PanelWindow {
- id: menuBarWindow
- property var modelData
- screen: modelData
-
- anchors {
- top: true
- left: true
- right: true
- }
-
- // Set implicitHeight to accommodate bar + open popups without clipping
- implicitHeight: (GlobalState.appleMenuVisible || GlobalState.activeDropdownMenu !== "" ||
- GlobalState.volumePopupVisible || GlobalState.wifiPopupVisible ||
- GlobalState.batteryPopupVisible || GlobalState.calendarPopupVisible) ? 420 : Theme.menuBarHeight
-
- color: "transparent"
- WlrLayershell.layer: WlrLayer.Top
- WlrLayershell.namespace: "quickshell-menubar"
- WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
-
- // Set exclusive zone only for the 26px bar
- exclusiveZone: Theme.menuBarHeight
-
- // Background Bar (26px)
- Rectangle {
- id: barRect
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- height: Theme.menuBarHeight
- color: Theme.menuBarBg
- border.color: Theme.menuBarBorder
- border.width: 0
-
- // Bottom hairline border
- Rectangle {
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- height: 1
- color: Theme.menuBarBorder
- }
-
- RowLayout {
- anchors.fill: parent
- anchors.leftMargin: 12
- anchors.rightMargin: 12
- spacing: 0
-
- // =================================================================
- // LEFT SIDE: Apple Menu + App Title + Standard Menus
- // =================================================================
- RowLayout {
- Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
- spacing: 2
-
- // Apple Logo Button
- Rectangle {
- id: appleBtn
- width: 28
- height: 20
- radius: Theme.radiusSmall
- color: (GlobalState.appleMenuVisible || appleMouse.containsMouse)
- ? Qt.rgba(1.0, 1.0, 1.0, 0.18) : "transparent"
-
- Image {
- anchors.centerIn: parent
- width: 14
- height: 14
- source: "../assets/apple.svg"
- sourceSize: Qt.size(14, 14)
- }
-
- MouseArea {
- id: appleMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- const wasOpen = GlobalState.appleMenuVisible;
- GlobalState.closeAllPopups();
- GlobalState.appleMenuVisible = !wasOpen;
- }
- }
- }
-
- // Active App Title (Bold)
- Rectangle {
- id: appTitleBtn
- implicitWidth: appTitleText.implicitWidth + 16
- height: 20
- radius: Theme.radiusSmall
- color: titleMouse.containsMouse ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
-
- Text {
- id: appTitleText
- anchors.centerIn: parent
- text: GlobalState.activeWindowClass || "Finder"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeMenuBar
- font.weight: Font.Bold
- color: Theme.textPrimary
- }
-
- MouseArea {
- id: titleMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- const wasOpen = (GlobalState.activeDropdownMenu === "File");
- GlobalState.closeAllPopups();
- GlobalState.activeDropdownMenu = wasOpen ? "" : "File";
- }
- }
- }
-
- // Standard Menus: File, Edit, View, Window, Help
- Repeater {
- model: ["File", "Edit", "View", "Window", "Help"]
- delegate: Rectangle {
- id: menuBtn
- implicitWidth: menuText.implicitWidth + 14
- height: 20
- radius: Theme.radiusSmall
- color: (GlobalState.activeDropdownMenu === modelData || menuMouse.containsMouse)
- ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
-
- Text {
- id: menuText
- anchors.centerIn: parent
- text: modelData
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeMenuBar
- font.weight: Font.Normal
- color: Theme.textPrimary
- }
-
- MouseArea {
- id: menuMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: {
- // If a menu is already open, hovering switches to this one
- if (GlobalState.activeDropdownMenu !== "") {
- GlobalState.activeDropdownMenu = modelData;
- }
- }
- onClicked: {
- const wasOpen = (GlobalState.activeDropdownMenu === modelData);
- GlobalState.closeAllPopups();
- GlobalState.activeDropdownMenu = wasOpen ? "" : modelData;
- }
- }
- }
- }
- }
-
- // Spacer
- Item {
- Layout.fillWidth: true
- }
-
- // =================================================================
- // RIGHT SIDE: Tray + Media + Volume + Wifi + Battery + Spotlight + Control Center + Clock
- // =================================================================
- RowLayout {
- Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
- spacing: 4
-
- // System Tray Icons
- Repeater {
- model: SystemTray.items
- delegate: Rectangle {
- required property SystemTrayItem modelData
- width: 22
- height: 20
- radius: Theme.radiusSmall
- color: trayMouse.containsMouse ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
-
- Image {
- anchors.centerIn: parent
- width: 15
- height: 15
- source: modelData.icon || ""
- sourceSize: Qt.size(15, 15)
- }
-
- MouseArea {
- id: trayMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: modelData.activate()
- }
- }
- }
-
- // Media Indicator (visible if playing)
- Rectangle {
- visible: GlobalState.mediaPlaying
- implicitWidth: mediaRow.implicitWidth + 12
- height: 20
- radius: Theme.radiusSmall
- color: mediaMouse.containsMouse ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
-
- RowLayout {
- id: mediaRow
- anchors.centerIn: parent
- spacing: 4
- Image {
- width: 13
- height: 13
- source: "../assets/music.svg"
- sourceSize: Qt.size(13, 13)
- }
- Text {
- text: GlobalState.mediaTitle ? `${GlobalState.mediaTitle.substring(0, 18)}...` : "Playing"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textSecondary
- }
- }
-
- MouseArea {
- id: mediaMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: GlobalState.mediaPlayPause()
- }
- }
-
- // Volume Item
- Rectangle {
- id: volBtn
- width: 26
- height: 20
- radius: Theme.radiusSmall
- color: (GlobalState.volumePopupVisible || volMouse.containsMouse)
- ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
-
- Image {
- anchors.centerIn: parent
- width: 14
- height: 14
- source: "../assets/volume.svg"
- sourceSize: Qt.size(14, 14)
- }
-
- MouseArea {
- id: volMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- const wasOpen = GlobalState.volumePopupVisible;
- GlobalState.closeAllPopups();
- GlobalState.volumePopupVisible = !wasOpen;
- }
- }
- }
-
- // Wi-Fi Item
- Rectangle {
- id: wifiBtn
- width: 26
- height: 20
- radius: Theme.radiusSmall
- color: (GlobalState.wifiPopupVisible || wifiMouse.containsMouse)
- ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
-
- Image {
- anchors.centerIn: parent
- width: 14
- height: 14
- source: "../assets/wifi.svg"
- sourceSize: Qt.size(14, 14)
- }
-
- MouseArea {
- id: wifiMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- const wasOpen = GlobalState.wifiPopupVisible;
- GlobalState.closeAllPopups();
- GlobalState.wifiPopupVisible = !wasOpen;
- }
- }
- }
-
- // Battery Item
- Rectangle {
- id: batBtn
- implicitWidth: batRow.implicitWidth + 8
- height: 20
- radius: Theme.radiusSmall
- color: (GlobalState.batteryPopupVisible || batMouse.containsMouse)
- ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
-
- RowLayout {
- id: batRow
- anchors.centerIn: parent
- spacing: 4
-
- Text {
- visible: GlobalState.batteryPresent
- text: `${GlobalState.batteryPercent}%`
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textPrimary
- }
-
- Image {
- width: 18
- height: 12
- source: "../assets/battery.svg"
- sourceSize: Qt.size(18, 12)
- }
- }
-
- MouseArea {
- id: batMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- const wasOpen = GlobalState.batteryPopupVisible;
- GlobalState.closeAllPopups();
- GlobalState.batteryPopupVisible = !wasOpen;
- }
- }
- }
-
- // Spotlight Search Button
- Rectangle {
- id: spotBtn
- width: 26
- height: 20
- radius: Theme.radiusSmall
- color: (GlobalState.spotlightVisible || spotMouse.containsMouse)
- ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
-
- Image {
- anchors.centerIn: parent
- width: 14
- height: 14
- source: "../assets/spotlight.svg"
- sourceSize: Qt.size(14, 14)
- }
-
- MouseArea {
- id: spotMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: GlobalState.toggleSpotlight()
- }
- }
-
- // Control Center / Notification Center Toggle Button
- Rectangle {
- id: ccBtn
- width: 26
- height: 20
- radius: Theme.radiusSmall
- color: (GlobalState.controlCenterVisible || ccMouse.containsMouse)
- ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
-
- Image {
- anchors.centerIn: parent
- width: 14
- height: 14
- source: "../assets/controlcenter.svg"
- sourceSize: Qt.size(14, 14)
- }
-
- MouseArea {
- id: ccMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: GlobalState.toggleControlCenter()
- }
- }
-
- // Date & Time (Clock)
- Rectangle {
- id: clockBtn
- implicitWidth: clockRow.implicitWidth + 10
- height: 20
- radius: Theme.radiusSmall
- color: (GlobalState.calendarPopupVisible || clockMouse.containsMouse)
- ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
-
- RowLayout {
- id: clockRow
- anchors.centerIn: parent
- spacing: 6
-
- Text {
- text: GlobalState.dateString
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeMenuBar
- color: Theme.textSecondary
- }
-
- Text {
- text: GlobalState.timeString
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeMenuBar
- font.weight: Font.Medium
- color: Theme.textPrimary
- }
- }
-
- MouseArea {
- id: clockMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- const wasOpen = GlobalState.calendarPopupVisible;
- GlobalState.closeAllPopups();
- GlobalState.calendarPopupVisible = !wasOpen;
- }
- }
- }
- }
- }
- }
-
- // =========================================================================
- // DROPDOWNS & POPUPS
- // =========================================================================
-
- // 1. Apple Menu Dropdown
- AppleMenu {
- id: appleDropdown
- visible: GlobalState.appleMenuVisible
- x: 8
- y: Theme.menuBarHeight + 2
- }
-
- // 2. Application Menus Dropdown (File, Edit, View, Window, Help)
- AppMenuDropdown {
- id: appMenuDropdown
- visible: GlobalState.activeDropdownMenu !== ""
- menuName: GlobalState.activeDropdownMenu
- x: 60
- y: Theme.menuBarHeight + 2
- }
-
- // 3. Volume Popup
- VolumePopup {
- id: volumeDropdown
- visible: GlobalState.volumePopupVisible
- anchors.top: barRect.bottom
- anchors.topMargin: 2
- anchors.right: parent.right
- anchors.rightMargin: 190
- }
-
- // 4. Wi-Fi Popup
- WifiPopup {
- id: wifiDropdown
- visible: GlobalState.wifiPopupVisible
- anchors.top: barRect.bottom
- anchors.topMargin: 2
- anchors.right: parent.right
- anchors.rightMargin: 160
- }
-
- // 5. Battery Popup
- BatteryPopup {
- id: batteryDropdown
- visible: GlobalState.batteryPopupVisible
- anchors.top: barRect.bottom
- anchors.topMargin: 2
- anchors.right: parent.right
- anchors.rightMargin: 110
- }
-
- // 6. Calendar Popup
- CalendarPopup {
- id: calDropdown
- visible: GlobalState.calendarPopupVisible
- anchors.top: barRect.bottom
- anchors.topMargin: 2
- anchors.right: parent.right
- anchors.rightMargin: 10
- }
-}
diff --git a/home/hypr/quickshell/components/QuickMenu.qml b/home/hypr/quickshell/components/QuickMenu.qml
deleted file mode 100644
index 96fdf61..0000000
--- a/home/hypr/quickshell/components/QuickMenu.qml
+++ /dev/null
@@ -1,190 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-import QtQuick.Controls
-import Quickshell
-import Quickshell.Wayland
-import Quickshell.Io
-import "../theme"
-import ".."
-
-PanelWindow {
- id: quickMenu
-
- WlrLayershell.layer: WlrLayer.Overlay
- WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
-
- anchors {
- top: true
- left: true
- }
-
- margins {
- top: 0
- left: Theme.sidebarWidth
- }
-
- visible: GlobalState.appleMenuVisible
-
- implicitWidth: 220
- implicitHeight: menuContainer.implicitHeight
-
- color: "transparent"
-
- // Close on escape
- Item {
- anchors.fill: parent
- focus: true
- Keys.onEscapePressed: GlobalState.appleMenuVisible = false
- }
-
- Rectangle {
- id: menuContainer
- anchors.fill: parent
- color: Theme.glassBg
- border.color: Theme.glassBorder
- border.width: 1
-
- Column {
- width: parent.width
- padding: 8
-
- component MenuItem: Item {
- id: menuItem
- width: parent.width
- height: 28
-
- property string text: ""
- property string shortcut: ""
- signal triggered()
-
- Rectangle {
- anchors.fill: parent
- color: menuItemMouseArea.containsMouse ? Qt.rgba(Theme.accentVolt.r, Theme.accentVolt.g, Theme.accentVolt.b, 0.1) : "transparent"
- }
-
- Rectangle {
- anchors.left: parent.left
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- width: 2
- color: Theme.accentVolt
- visible: menuItemMouseArea.containsMouse
- }
-
- Text {
- anchors.left: parent.left
- anchors.leftMargin: 12
- anchors.verticalCenter: parent.verticalCenter
- text: menuItem.text
- font.family: Theme.fontSans
- font.pixelSize: 13
- color: menuItemMouseArea.containsMouse ? Theme.textPrimary : Theme.textSecondary
- }
-
- Text {
- anchors.right: parent.right
- anchors.rightMargin: 12
- anchors.verticalCenter: parent.verticalCenter
- text: menuItem.shortcut
- font.family: Theme.fontMono
- font.pixelSize: 11
- color: Theme.textMuted
- visible: text !== ""
- }
-
- MouseArea {
- id: menuItemMouseArea
- anchors.fill: parent
- hoverEnabled: true
- onClicked: {
- menuItem.triggered()
- GlobalState.appleMenuVisible = false
- }
- }
- }
-
- component MenuSeparator: Item {
- width: parent.width
- height: 17
-
- Rectangle {
- anchors.verticalCenter: parent.verticalCenter
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.leftMargin: 12
- anchors.rightMargin: 12
- height: 1
- color: Theme.glassBorder
- }
- }
-
- MenuItem {
- text: "System Info..."
- onTriggered: GlobalState.aboutMacVisible = true
- }
-
- MenuItem {
- text: "System Preferences..."
- onTriggered: {
- let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["pavucontrol"] }', quickMenu)
- p.running = true
- }
- }
-
- MenuItem {
- text: "Activity Monitor"
- shortcut: "btop"
- onTriggered: {
- let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["foot", "-e", "btop"] }', quickMenu)
- p.running = true
- }
- }
-
- MenuSeparator {}
-
- MenuItem {
- text: "Sleep"
- onTriggered: {
- let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["systemctl", "suspend"] }', quickMenu)
- p.running = true
- }
- }
-
- MenuItem {
- text: "Restart..."
- onTriggered: {
- let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["systemctl", "reboot"] }', quickMenu)
- p.running = true
- }
- }
-
- MenuItem {
- text: "Shut Down..."
- onTriggered: {
- let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["systemctl", "poweroff"] }', quickMenu)
- p.running = true
- }
- }
-
- MenuSeparator {}
-
- MenuItem {
- text: "Lock Screen"
- shortcut: "Super+L"
- onTriggered: {
- let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["hyprlock"] }', quickMenu)
- p.running = true
- }
- }
-
- MenuItem {
- text: "Log Out"
- shortcut: "Super+Shift+Q"
- onTriggered: {
- let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["hyprctl", "dispatch", "exit"] }', quickMenu)
- p.running = true
- }
- }
- }
- }
-}
diff --git a/home/hypr/quickshell/components/SidePanel.qml b/home/hypr/quickshell/components/SidePanel.qml
deleted file mode 100644
index 145b0d0..0000000
--- a/home/hypr/quickshell/components/SidePanel.qml
+++ /dev/null
@@ -1,532 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-import Quickshell
-import Quickshell.Hyprland
-import Quickshell.Wayland
-import "../theme"
-import ".."
-
-PanelWindow {
- id: root
- property var screen
-
- WlrLayershell.layer: WlrLayer.Top
- WlrLayershell.namespace: "quickshell-sidepanel"
- WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
-
- anchors {
- top: true
- bottom: true
- left: true
- right: false
- }
-
- exclusiveZone: Theme.sidebarWidth
- implicitWidth: Theme.sidebarWidth
- color: Theme.bgPrimary
-
- // Right edge hairline border
- Rectangle {
- width: 1
- color: Theme.glassBorder
- anchors {
- right: parent.right
- top: parent.top
- bottom: parent.bottom
- }
- }
-
- component Separator: Rectangle {
- width: 36
- height: 1
- color: Theme.glassBorder
- Layout.alignment: Qt.AlignHCenter
- }
-
- component DockIcon: Item {
- id: dockIconRoot
- width: 36
- height: 36
- Layout.alignment: Qt.AlignHCenter
-
- property string appClass: ""
- property string execCmd: ""
- property string iconSource: ""
- property string tooltipText: ""
-
- property bool isRunning: (appClass !== "" && GlobalState.activeWindowClass === appClass) || (GlobalState.runningApps && GlobalState.runningApps.indexOf(appClass) !== -1)
-
- Image {
- id: img
- anchors.centerIn: parent
- width: 24
- height: 24
- source: iconSource ? Qt.resolvedUrl("../assets/" + iconSource) : ""
- sourceSize: Qt.size(24, 24)
- fillMode: Image.PreserveAspectFit
- opacity: mouseArea.containsMouse ? 1.0 : 0.8
-
- scale: mouseArea.containsMouse ? 1.15 : 1.0
- Behavior on scale {
- NumberAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic }
- }
- Behavior on opacity {
- NumberAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic }
- }
- }
-
- Rectangle {
- id: runningDot
- width: 4
- height: 4
- radius: 2
- color: Theme.accentVolt
- anchors.bottom: parent.bottom
- anchors.horizontalCenter: parent.horizontalCenter
- visible: isRunning
- }
-
- MouseArea {
- id: mouseArea
- anchors.fill: parent
- hoverEnabled: true
- onClicked: {
- if (execCmd !== "") {
- GlobalState.launchApp(execCmd)
- }
- }
- }
-
- // Tooltip
- Rectangle {
- id: tooltip
- visible: mouseArea.containsMouse && tooltipText !== ""
- color: Theme.glassBg
- radius: 4
- width: tooltipLabel.implicitWidth + 16
- height: tooltipLabel.implicitHeight + 8
-
- anchors.left: parent.right
- anchors.leftMargin: 24
- anchors.verticalCenter: parent.verticalCenter
-
- Text {
- id: tooltipLabel
- anchors.centerIn: parent
- text: tooltipText
- color: Theme.textPrimary
- font.family: Theme.fontSans
- font.pixelSize: 11
- }
- }
- }
-
- ColumnLayout {
- anchors.fill: parent
- spacing: 8
-
- // Top spacer
- Item { height: 4; Layout.fillWidth: true }
-
- // 1. AG Logo Button
- Rectangle {
- width: 36
- height: 36
- radius: 4
- color: logoMouse.containsMouse ? Qt.rgba(1,1,1,0.05) : "transparent"
- Layout.alignment: Qt.AlignHCenter
-
- Behavior on color {
- ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic }
- }
-
- Text {
- anchors.centerIn: parent
- text: "AG"
- font.family: Theme.fontSerif
- font.italic: true
- font.pixelSize: 16
- color: Theme.accentVolt
- }
-
- MouseArea {
- id: logoMouse
- anchors.fill: parent
- hoverEnabled: true
- onClicked: GlobalState.appleMenuVisible = !GlobalState.appleMenuVisible
- }
- }
-
- Separator {}
-
- // 3. Workspace Indicators
- Column {
- Layout.alignment: Qt.AlignHCenter
- spacing: 2
-
- Repeater {
- model: GlobalState.workspaceList
- delegate: Rectangle {
- width: 52
- height: 36
- color: wsMouse.containsMouse ? Qt.rgba(1,1,1,0.04) : "transparent"
-
- property bool isActive: GlobalState.activeWorkspace === modelData
-
- Behavior on color {
- ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic }
- }
-
- Rectangle {
- width: 2
- height: 20
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
- color: Theme.accentVolt
- visible: isActive
- }
-
- Column {
- anchors.centerIn: parent
- spacing: 2
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: (modelData < 10 ? "0" : "") + modelData
- color: isActive ? Theme.accentVolt : Theme.textMuted
- font.family: Theme.fontMono
- font.pixelSize: 12
-
- Behavior on color {
- ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic }
- }
- }
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: (GlobalState.workspaceNames && GlobalState.workspaceNames[modelData]) ? GlobalState.workspaceNames[modelData] : ""
- color: Theme.textMuted
- font.family: Theme.fontSans
- font.pixelSize: 8
- opacity: 0.7
- }
- }
-
- MouseArea {
- id: wsMouse
- anchors.fill: parent
- hoverEnabled: true
- onClicked: GlobalState.switchWorkspace(modelData)
- }
- }
- }
- }
-
- Separator {}
-
- // 5. Spotlight trigger
- Item {
- width: 36
- height: 36
- Layout.alignment: Qt.AlignHCenter
-
- Text {
- anchors.centerIn: parent
- text: "⌕"
- font.pixelSize: 20
- color: spotlightMouse.containsMouse ? Theme.accentVolt : Theme.textMuted
-
- Behavior on color {
- ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic }
- }
- }
-
- MouseArea {
- id: spotlightMouse
- anchors.fill: parent
- hoverEnabled: true
- onClicked: GlobalState.toggleSpotlight()
- }
- }
-
- Separator {}
-
- // 7. Pinned Apps
- Column {
- Layout.alignment: Qt.AlignHCenter
- spacing: 8
-
- DockIcon {
- appClass: ""
- execCmd: "nautilus"
- iconSource: "finder.svg"
- tooltipText: "Finder"
- }
- DockIcon {
- appClass: "brave-browser"
- execCmd: "brave"
- iconSource: "browser.svg"
- tooltipText: "Brave Browser"
- }
- DockIcon {
- appClass: "foot"
- execCmd: "foot"
- iconSource: "terminal.svg"
- tooltipText: "Terminal"
- }
- DockIcon {
- appClass: "nvim"
- execCmd: "foot -e nvim"
- iconSource: "code.svg"
- tooltipText: "Code"
- }
- DockIcon {
- appClass: "pavucontrol"
- execCmd: "pavucontrol"
- iconSource: "preferences.svg"
- tooltipText: "Settings"
- }
- DockIcon {
- appClass: ""
- execCmd: "spotify"
- iconSource: "music.svg"
- tooltipText: "Music"
- }
- }
-
- Separator {}
-
- // 9. Running Apps (non-pinned)
- Column {
- Layout.alignment: Qt.AlignHCenter
- spacing: 8
-
- Repeater {
- model: {
- var pinned = ["foot", "brave-browser", "pavucontrol", "nvim", "nautilus", "spotify", ""]
- var running = []
- var seen = {}
- if (GlobalState.runningApps) {
- for (var i = 0; i < GlobalState.runningApps.length; i++) {
- // hyprctl clients entries are objects; extract the class string
- var cls = GlobalState.runningApps[i] && GlobalState.runningApps[i].class
- if (cls && cls.length > 0 && pinned.indexOf(cls) === -1 && !seen[cls]) {
- seen[cls] = true
- running.push(cls)
- }
- }
- }
- return running
- }
-
- delegate: Item {
- width: 36
- height: 36
-
- Rectangle {
- anchors.centerIn: parent
- width: 24
- height: 24
- radius: 12
- color: Qt.rgba(1, 1, 1, 0.1)
-
- Text {
- anchors.centerIn: parent
- text: modelData && modelData.length > 0 ? modelData.charAt(0).toUpperCase() : "?"
- color: Theme.textPrimary
- font.family: Theme.fontSans
- font.pixelSize: 12
- }
- }
-
- Rectangle {
- width: 4
- height: 4
- radius: 2
- color: Theme.accentVolt
- anchors.bottom: parent.bottom
- anchors.horizontalCenter: parent.horizontalCenter
- }
-
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- onClicked: {
- // Focus app logic here
- }
- }
- }
- }
- }
-
- // 10. Flexible spacer
- Item { Layout.fillHeight: true }
-
- Separator {}
-
- // 12. Telemetry mini-readouts
- Column {
- Layout.alignment: Qt.AlignHCenter
- spacing: 2
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: "CPU" + (GlobalState.cpuPercent !== undefined ? GlobalState.cpuPercent : "0") + "%"
- font.family: Theme.fontMono
- font.pixelSize: 11
- color: (GlobalState.cpuPercent !== undefined && GlobalState.cpuPercent > 80) ? Theme.accentCoral : Theme.textMuted
- Behavior on color { ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } }
- }
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: "RAM" + (GlobalState.memUsedGb !== undefined ? GlobalState.memUsedGb : "0") + "G"
- font.family: Theme.fontMono
- font.pixelSize: 11
- color: (GlobalState.memPercent !== undefined && GlobalState.memPercent > 85) ? Theme.accentCoral : Theme.textMuted
- Behavior on color { ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } }
- }
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: "VRM" + (GlobalState.gpuVramUsed !== undefined ? GlobalState.gpuVramUsed : "0") + "G"
- font.family: Theme.fontMono
- font.pixelSize: 11
- color: Theme.textMuted
- }
- }
-
- Separator {}
-
- // 14. System tray icons
- Column {
- Layout.alignment: Qt.AlignHCenter
- spacing: 8
-
- // Volume
- Item {
- width: 36
- height: 36
- Image {
- anchors.centerIn: parent
- width: 16
- height: 16
- source: Qt.resolvedUrl("../assets/volume.svg")
- sourceSize: Qt.size(16, 16)
- opacity: volMouse.containsMouse ? 1.0 : 0.7
- Behavior on opacity { NumberAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } }
- }
- MouseArea {
- id: volMouse
- anchors.fill: parent
- hoverEnabled: true
- onClicked: {
- GlobalState.systemHudActiveSection = "volume"
- GlobalState.systemHudVisible = true
- }
- }
- }
-
- // WiFi
- Item {
- width: 36
- height: 36
- Image {
- anchors.centerIn: parent
- width: 16
- height: 16
- source: Qt.resolvedUrl("../assets/wifi.svg")
- sourceSize: Qt.size(16, 16)
- opacity: GlobalState.wifiConnected ? 0.7 : 0.3
- Behavior on opacity { NumberAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } }
- }
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- onClicked: {
- GlobalState.systemHudActiveSection = "network"
- GlobalState.systemHudVisible = true
- }
- }
- }
-
- // Battery
- Item {
- width: 36
- height: 44
- visible: GlobalState.batteryPresent !== undefined ? GlobalState.batteryPresent : false
-
- Column {
- anchors.centerIn: parent
- spacing: 2
-
- Image {
- anchors.horizontalCenter: parent.horizontalCenter
- width: 16
- height: 16
- source: Qt.resolvedUrl("../assets/battery.svg")
- sourceSize: Qt.size(16, 16)
- opacity: (GlobalState.batteryPercent !== undefined && GlobalState.batteryPercent < 20) ? 1.0 : 0.7
- }
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: (GlobalState.batteryPercent !== undefined ? GlobalState.batteryPercent : "100") + "%"
- font.family: Theme.fontMono
- font.pixelSize: 9
- color: (GlobalState.batteryPercent !== undefined && GlobalState.batteryPercent < 20) ? Theme.accentCoral : Theme.textSecondary
- }
- }
-
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- onClicked: {
- GlobalState.systemHudActiveSection = "battery"
- GlobalState.systemHudVisible = true
- }
- }
- }
- }
-
- Separator {}
-
- // 16. Clock
- Item {
- width: 36
- height: 44
- Layout.alignment: Qt.AlignHCenter
-
- Column {
- anchors.centerIn: parent
- spacing: 2
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: GlobalState.timeString !== undefined ? GlobalState.timeString : "00:00"
- font.family: Theme.fontMono
- font.pixelSize: 12
- color: Theme.textPrimary
- }
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: GlobalState.dateString !== undefined ? GlobalState.dateString : "Jan 01"
- font.family: Theme.fontMono
- font.pixelSize: 9
- color: Theme.textMuted
- }
- }
-
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- onClicked: {
- GlobalState.systemHudActiveSection = "calendar"
- GlobalState.systemHudVisible = true
- }
- }
- }
-
- // Bottom spacer
- Item { height: 4; Layout.fillWidth: true }
- }
-}
diff --git a/home/hypr/quickshell/components/Spotlight.qml b/home/hypr/quickshell/components/Spotlight.qml
deleted file mode 100644
index bf8d923..0000000
--- a/home/hypr/quickshell/components/Spotlight.qml
+++ /dev/null
@@ -1,365 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-import QtQuick.Controls
-import Quickshell
-import Quickshell.Wayland
-import Quickshell.Io
-import "../theme"
-import ".."
-
-PanelWindow {
- id: spotWindow
- visible: GlobalState.spotlightVisible
-
- anchors {
- top: true
- left: true
- right: true
- bottom: true
- }
-
- color: Qt.rgba(0, 0, 0, 0.45)
- WlrLayershell.layer: WlrLayer.Overlay
- WlrLayershell.namespace: "quickshell-spotlight"
- WlrLayershell.keyboardFocus: visible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
-
- // Dismiss when clicking outside modal
- MouseArea {
- anchors.fill: parent
- onClicked: GlobalState.spotlightVisible = false
- }
-
- // App Database Loader Process
- readonly property Process appScanProc: Process {
- // Indexes .desktop applications AND executable user scripts so the
- // launcher finds "everything" without depending on an external service.
- command: ["bash", "-c", "dirs=(\"/run/current-system/sw/share/applications\" \"$HOME/.nix-profile/share/applications\" \"$HOME/.local/share/applications\"); bdirs=(\"$HOME/.local/bin\" \"$HOME/bin\" \"$HOME/.cargo/bin\"); echo '['; first=1; for d in \"${dirs[@]}\"; do [ -d \"$d\" ] && for f in \"$d\"/*.desktop; do [ -e \"$f\" ] || continue; grep -q \"^NoDisplay=true\" \"$f\" 2>/dev/null && continue; n=$(grep -m 1 \"^Name=\" \"$f\" 2>/dev/null | cut -d= -f2-); e=$(grep -m 1 \"^Exec=\" \"$f\" 2>/dev/null | cut -d= -f2- | sed -E 's/%[fFuUdDnNickvm]//g'); if [ -n \"$n\" ] && [ -n \"$e\" ]; then [ $first -eq 0 ] && echo ','; first=0; echo \"{\\\"name\\\":\\\"$n\\\",\\\"exec\\\":\\\"$e\\\"}\"; fi; done; done; for d in \"${bdirs[@]}\"; do [ -d \"$d\" ] || continue; for f in \"$d\"/*; do [ -f \"$f\" ] && [ -x \"$f\" ] || continue; n=$(basename \"$f\"); [ $first -eq 0 ] && echo ','; first=0; printf '{\\\"name\\\":\\\"%s\\\",\\\"exec\\\":\\\"%s\\\",\\\"comment\\\":\\\"script\\\"}\\n' \"$n\" \"$f\"; done; done; echo ']'"]
- stdout: StdioCollector {
- onDataChanged: {
- try {
- const parsed = JSON.parse(String(data).trim());
- if (Array.isArray(parsed)) {
- spotWindow.allApps = parsed;
- }
- } catch(e) {}
- }
- }
- }
-
- property var allApps: [
- { name: "Brave Browser", exec: "brave", comment: "Web Browser", icon: "browser" },
- { name: "Foot Terminal", exec: "foot", comment: "Terminal Emulator", icon: "terminal" },
- { name: "Neovim", exec: "foot -e nvim", comment: "Code Editor", icon: "code" },
- { name: "Activity Monitor (Btop)", exec: "foot -e btop", comment: "System Resources", icon: "preferences" },
- { name: "Volume & Sound (Pavucontrol)", exec: "pavucontrol", comment: "Audio Settings", icon: "preferences" },
- { name: "Lock Screen", exec: "hyprlock", comment: "Lock Session", icon: "preferences" },
- { name: "System Reboot", exec: "systemctl reboot", comment: "Reboot NixOS", icon: "preferences" },
- { name: "Shut Down", exec: "systemctl poweroff", comment: "Power Off", icon: "preferences" }
- ]
-
- property int selectedIndex: 0
-
- onVisibleChanged: {
- if (visible) {
- appScanProc.running = true;
- searchInput.text = "";
- selectedIndex = 0;
- searchInput.forceActiveFocus();
- }
- }
-
- // Centered Spotlight Search Modal
- Item {
- id: modalContainer
- anchors.horizontalCenter: parent.horizontalCenter
- y: 140
- width: 640
- height: modalBox.implicitHeight
-
- scale: spotWindow.visible ? 1.0 : 0.96
- opacity: spotWindow.visible ? 1.0 : 0.0
-
- Behavior on scale {
- NumberAnimation { duration: Theme.animSmooth; easing.type: Easing.OutQuint }
- }
- Behavior on opacity {
- NumberAnimation { duration: Theme.animSmooth; easing.type: Easing.OutQuint }
- }
-
- Rectangle {
- id: modalBox
- anchors.fill: parent
- implicitHeight: searchBarRowContainer.height + (resultsListView.count > 0 || mathResultText !== "" ? 340 : 0)
- radius: 0
- color: Theme.bgPrimary
- border.color: Theme.glassBorder
- border.width: 1
-
- Behavior on implicitHeight {
- NumberAnimation { duration: 180; easing.type: Easing.OutQuad }
- }
-
- // Prevent clicking inside from closing modal
- MouseArea {
- anchors.fill: parent
- onClicked: {}
- }
-
- ColumnLayout {
- anchors.fill: parent
- spacing: 0
-
- // Top Search Bar Area
- Item {
- id: searchBarRowContainer
- Layout.fillWidth: true
- Layout.preferredHeight: 64
-
- RowLayout {
- id: searchBarRow
- anchors.fill: parent
- anchors.margins: 14
- spacing: 12
-
- Text {
- text: "⌕"
- font.family: Theme.fontSans
- font.pixelSize: 24
- color: Theme.accentVolt
- Layout.alignment: Qt.AlignVCenter
- }
-
- TextInput {
- id: searchInput
- Layout.fillWidth: true
- Layout.alignment: Qt.AlignVCenter
- font.family: Theme.fontSans
- font.pixelSize: 20
- color: Theme.textPrimary
- focus: true
- clip: true
-
- Text {
- anchors.fill: parent
- visible: searchInput.text === ""
- text: "Spotlight Search"
- font.family: Theme.fontSans
- font.pixelSize: 20
- color: Theme.textMuted
- }
-
- Keys.onEscapePressed: GlobalState.spotlightVisible = false
- Keys.onDownPressed: {
- if (resultsListView.count > 0) {
- spotWindow.selectedIndex = Math.min(resultsListView.count - 1, spotWindow.selectedIndex + 1);
- }
- }
- Keys.onUpPressed: {
- if (resultsListView.count > 0) {
- spotWindow.selectedIndex = Math.max(0, spotWindow.selectedIndex - 1);
- }
- }
- Keys.onReturnPressed: {
- if (filteredModel.count > 0 && spotWindow.selectedIndex < filteredModel.count) {
- const item = filteredModel.get(spotWindow.selectedIndex);
- GlobalState.launchApp(item.exec);
- }
- }
- }
-
- // Clear button
- Rectangle {
- visible: searchInput.text !== ""
- width: 24
- height: 24
- radius: 0
- color: "transparent"
-
- Text {
- anchors.centerIn: parent
- text: "✕"
- font.pixelSize: 16
- color: clearMouseArea.containsMouse ? Theme.textPrimary : Theme.textMuted
- }
-
- MouseArea {
- id: clearMouseArea
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: searchInput.text = ""
- }
- }
- }
-
- Rectangle {
- anchors.bottom: parent.bottom
- anchors.left: parent.left
- anchors.right: parent.right
- height: 1
- color: Theme.glassBorder
- }
- }
-
- // Math Calculation Result Banner (if valid formula)
- Rectangle {
- visible: mathResultText !== ""
- Layout.fillWidth: true
- Layout.preferredHeight: 36
- Layout.margins: 14
- radius: 0
- color: "transparent"
-
- RowLayout {
- anchors.fill: parent
- anchors.margins: 8
- spacing: 8
-
- Text {
- text: "="
- font.family: Theme.fontMono
- font.pixelSize: 16
- font.weight: Font.Bold
- color: Theme.accentVolt
- }
- Text {
- text: mathResultText
- font.family: Theme.fontMono
- font.pixelSize: 16
- color: Theme.accentVolt
- Layout.fillWidth: true
- }
- }
- }
-
- // Search Results List View
- ListView {
- id: resultsListView
- visible: count > 0
- Layout.fillWidth: true
- Layout.preferredHeight: Math.min(270, count * 40)
- Layout.topMargin: 8
- Layout.bottomMargin: 8
- clip: true
- spacing: 0
- model: filteredModel
-
- delegate: Rectangle {
- id: itemDelegate
- required property int index
- required property string name
- required property string exec
- required property string comment
-
- width: resultsListView.width
- height: 40
- radius: 0
- color: spotWindow.selectedIndex === index ? Qt.rgba(0.812, 0.988, 0.0, 0.08) : "transparent"
-
- Rectangle {
- anchors.left: parent.left
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- width: 2
- color: Theme.accentVolt
- visible: spotWindow.selectedIndex === index
- }
-
- RowLayout {
- anchors.fill: parent
- anchors.leftMargin: 14
- anchors.rightMargin: 14
- spacing: 12
-
- ColumnLayout {
- Layout.fillWidth: true
- spacing: 2
-
- Text {
- text: itemDelegate.name
- font.family: Theme.fontSans
- font.pixelSize: 14
- color: Theme.textPrimary
- elide: Text.ElideRight
- Layout.fillWidth: true
- }
-
- Text {
- text: itemDelegate.comment || itemDelegate.exec
- font.family: Theme.fontMono
- font.pixelSize: 11
- color: Theme.textMuted
- elide: Text.ElideRight
- Layout.fillWidth: true
- }
- }
-
- Text {
- text: "↩"
- font.pixelSize: 14
- color: spotWindow.selectedIndex === itemDelegate.index
- ? Theme.textPrimary : "transparent"
- }
- }
-
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onEntered: spotWindow.selectedIndex = itemDelegate.index
- onClicked: GlobalState.launchApp(itemDelegate.exec)
- }
- }
- }
- }
- }
- }
-
- // Math calculation evaluator property
- readonly property string mathResultText: {
- const query = searchInput.text.trim();
- if (/^[0-9+\-*/^().\s%]+$/.test(query) && /[0-9]/.test(query) && /[+\-*/^%]/.test(query)) {
- try {
- // Safe evaluation of arithmetic
- const res = Function(`"use strict"; return (${query.replace(/\^/g, "**")})`)();
- if (typeof res === "number" && !isNaN(res) && isFinite(res)) {
- return `${res}`;
- }
- } catch(e) {}
- }
- return "";
- }
-
- // Filtered ListModel updated on text change
- ListModel {
- id: filteredModel
- }
-
- function updateFilter() {
- filteredModel.clear();
- const query = searchInput.text.toLowerCase().trim();
- let count = 0;
- for (let i = 0; i < allApps.length && count < 12; i++) {
- const app = allApps[i];
- if (!app.name) continue;
- if (query === "" || app.name.toLowerCase().includes(query) || (app.exec && app.exec.toLowerCase().includes(query))) {
- filteredModel.append({
- name: app.name,
- exec: app.exec || "",
- comment: app.comment || ""
- });
- count++;
- }
- }
- }
-
- Connections {
- target: searchInput
- function onTextChanged() {
- spotWindow.selectedIndex = 0;
- updateFilter();
- }
- }
-
- Component.onCompleted: updateFilter()
-}
diff --git a/home/hypr/quickshell/components/SystemHUD.qml b/home/hypr/quickshell/components/SystemHUD.qml
deleted file mode 100644
index e3116c9..0000000
--- a/home/hypr/quickshell/components/SystemHUD.qml
+++ /dev/null
@@ -1,697 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-import QtQuick.Controls
-import Quickshell
-import Quickshell.Wayland
-import Quickshell.Io
-import "../theme"
-import ".."
-
-PanelWindow {
- id: root
-
- anchors {
- top: true
- bottom: true
- left: true
- }
-
- margins {
- left: Theme.sidebarWidth
- top: Theme.topStripHeight
- }
-
- implicitWidth: Theme.hudWidth
-
- WlrLayershell.layer: WlrLayer.Overlay
- WlrLayershell.namespace: "quickshell-systemhud"
- WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
-
- visible: GlobalState.systemHudVisible
-
- color: "transparent"
-
- property real slideOffset: GlobalState.systemHudVisible ? 0 : -Theme.hudWidth
- Behavior on slideOffset {
- NumberAnimation {
- duration: Theme.animSmooth
- easing.type: Easing.OutQuint
- }
- }
-
- Shortcut {
- sequence: "Escape"
- onActivated: GlobalState.systemHudVisible = false
- }
-
- component SectionHeader: Item {
- property string sectionNum: "00"
- property string title: "Section"
- implicitWidth: parent.width
- implicitHeight: 24
-
- RowLayout {
- anchors.fill: parent
- spacing: 8
- Text {
- text: sectionNum + " //"
- font.family: Theme.fontMono
- font.pixelSize: 12
- color: Theme.accentVolt
- }
- Text {
- text: title
- font.family: Theme.fontSerif
- font.italic: true
- font.pixelSize: 16
- color: Theme.textPrimary
- Layout.fillWidth: true
- }
- }
- }
-
- component ToggleCard: Rectangle {
- property string title: ""
- property string icon: ""
- property string glyph: "" // Nerd Font glyph alternative to icon file
- property bool active: false
- signal clicked()
-
- Layout.preferredWidth: 160
- Layout.preferredHeight: 56
- color: Theme.bgPrimary
- border.color: active ? Qt.rgba(Theme.accentVolt.r, Theme.accentVolt.g, Theme.accentVolt.b, 0.3) : Theme.glassBorder
- border.width: 1
- radius: 4
-
- Behavior on border.color { ColorAnimation { duration: Theme.animFast } }
-
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- onClicked: clicked()
- onEntered: if (!active) border.color = Theme.glassBorderHover
- onExited: if (!active) border.color = Theme.glassBorder
- }
-
- RowLayout {
- anchors.fill: parent
- anchors.margins: 12
- spacing: 12
- Text {
- visible: parent.parent.glyph !== ""
- text: parent.parent.glyph
- font.family: "JetBrainsMono Nerd Font"
- font.pixelSize: 20
- color: parent.parent.active ? Theme.accentVolt : Theme.textMuted
- }
- Image {
- source: parent.parent.icon && parent.parent.glyph === "" ? Qt.resolvedUrl("../" + parent.parent.icon) : ""
- Layout.preferredWidth: 24
- Layout.preferredHeight: 24
- sourceSize: Qt.size(24, 24)
- visible: parent.parent.icon !== "" && parent.parent.glyph === ""
- }
- Text {
- text: parent.parent.title
- color: Theme.textPrimary
- font.family: Theme.fontSans
- font.pixelSize: 13
- Layout.fillWidth: true
- elide: Text.ElideRight
- }
- }
- }
-
- component CustomSlider: Item {
- property real value: 0
- signal valueChangedUser(real newVal)
-
- implicitWidth: parent.width
- implicitHeight: 24
-
- Rectangle {
- id: track
- anchors.verticalCenter: parent.verticalCenter
- width: parent.width - 40
- height: 6
- color: Theme.bgSecondary
- radius: 3
-
- Rectangle {
- id: fill
- width: track.width * (parent.parent.value / 100)
- height: track.height
- color: Theme.accentVolt
- radius: 3
- }
-
- Rectangle {
- width: 14
- height: 14
- radius: 7
- color: Theme.textPrimary
- anchors.verticalCenter: parent.verticalCenter
- x: Math.max(0, fill.width - 7)
- }
-
- MouseArea {
- anchors.fill: parent
- anchors.margins: -10
- function updateValue(mouseX) {
- let p = Math.max(0, Math.min(1, mouseX / track.width))
- parent.parent.valueChangedUser(p * 100)
- }
- onPressed: (mouse) => updateValue(mouse.x)
- onPositionChanged: (mouse) => updateValue(mouse.x)
- }
- }
- Text {
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- text: Math.round(parent.value) + "%"
- color: Theme.textPrimary
- font.family: Theme.fontMono
- font.pixelSize: 12
- }
- }
-
- component ProgressBarItem: ColumnLayout {
- property string label: ""
- property real percent: 0
- property string valueText: ""
- property color barColor: Theme.accentVolt
-
- Layout.fillWidth: true
- spacing: 4
-
- RowLayout {
- Layout.fillWidth: true
- Text {
- text: parent.parent.label
- color: Theme.textPrimary
- font.family: Theme.fontMono
- font.pixelSize: 12
- }
- Item { Layout.fillWidth: true }
- Text {
- text: parent.parent.valueText
- color: Theme.textSecondary
- font.family: Theme.fontMono
- font.pixelSize: 12
- }
- }
-
- Rectangle {
- Layout.fillWidth: true
- height: 8
- color: Theme.bgSecondary
- radius: 4
-
- Rectangle {
- width: parent.width * (parent.parent.percent / 100)
- height: parent.height
- color: parent.parent.barColor
- radius: 4
- Behavior on width { NumberAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } }
- Behavior on color { ColorAnimation { duration: Theme.animFast } }
- }
- }
- }
-
- Rectangle {
- id: panelContent
- anchors.fill: parent
- color: Theme.glassBg
- opacity: GlobalState.systemHudVisible ? 1 : 0
-
- transform: Translate { x: slideOffset }
-
- Behavior on opacity {
- NumberAnimation {
- duration: Theme.animSmooth
- easing.type: Easing.OutQuint
- }
- }
-
- Rectangle {
- anchors.right: parent.right
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- width: 1
- color: Theme.glassBorder
- }
-
- Item {
- id: header
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- height: 60
-
- Rectangle {
- anchors.bottom: parent.bottom
- width: parent.width
- height: 1
- color: Theme.glassBorder
- }
-
- RowLayout {
- anchors.fill: parent
- anchors.leftMargin: 20
- anchors.rightMargin: 20
-
- Text {
- text: "System HUD"
- font.family: Theme.fontSerif
- font.italic: true
- font.pixelSize: 19
- color: Theme.textPrimary
- Layout.fillWidth: true
- }
-
- Text {
- text: "✕"
- color: Theme.textMuted
- font.pixelSize: 16
- MouseArea {
- anchors.fill: parent
- anchors.margins: -10
- hoverEnabled: true
- onEntered: parent.color = Theme.textPrimary
- onExited: parent.color = Theme.textMuted
- onClicked: GlobalState.systemHudVisible = false
- }
- }
- }
- }
-
- ScrollView {
- id: scrollView
- anchors.top: header.bottom
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.bottom: parent.bottom
- contentWidth: availableWidth
- clip: true
-
- ColumnLayout {
- id: contentLayout
- width: parent.width - 40
- x: 20
- spacing: 32
-
- Item { Layout.preferredHeight: 16 } // Top padding
-
- // Section 1: Toggles
- ColumnLayout {
- id: sectionToggles
- Layout.fillWidth: true
- spacing: 16
- SectionHeader { sectionNum: "01"; title: "Toggles" }
- GridLayout {
- columns: 2
- columnSpacing: 12
- rowSpacing: 12
- Layout.fillWidth: true
-
- ToggleCard {
- title: GlobalState.wifiSsid !== "" ? GlobalState.wifiSsid : "Wi-Fi"
- icon: "assets/wifi.svg"
- active: GlobalState.wifiConnected
- onClicked: {
- Quickshell.execDetached(["sh", "-c", "nm-connection-editor || foot -e nmtui"])
- }
- }
-
- ToggleCard {
- title: "Bluetooth"
- icon: "assets/bluetooth.svg"
- active: false // Adjust if there's a bluetooth state
- onClicked: {
- let proc = Quickshell.createProcess("sh", ["-c", "foot -e bluetoothctl"])
- proc.start()
- }
- }
-
- ToggleCard {
- property bool dndActive: false
- title: "Do Not Disturb"
- glyph: "\uF186"
- active: dndActive
- onClicked: dndActive = !dndActive
- }
-
- ToggleCard {
- title: GlobalState.volumeMuted ? "Muted" : "Sound"
- glyph: GlobalState.volumeMuted ? "\uF026" : "\uF028"
- active: !GlobalState.volumeMuted
- onClicked: GlobalState.toggleVolumeMute()
- }
- }
- }
-
- // Section 2: Volume
- ColumnLayout {
- id: sectionVolume
- Layout.fillWidth: true
- spacing: 16
- SectionHeader { sectionNum: "02"; title: "Volume" }
-
- CustomSlider {
- Layout.fillWidth: true
- value: GlobalState.volume
- onValueChangedUser: (val) => GlobalState.volume = val // Adjust to actual volume setter if needed
- }
-
- RowLayout {
- Layout.fillWidth: true
- Text {
- text: GlobalState.volumeMuted ? "Unmute" : "Mute"
- color: Theme.textSecondary
- font.family: Theme.fontSans
- font.pixelSize: 13
- MouseArea {
- anchors.fill: parent
- onClicked: GlobalState.toggleVolumeMute()
- }
- }
- Item { Layout.fillWidth: true }
- Text {
- text: "Sound Preferences..."
- color: Theme.textMuted
- font.family: Theme.fontSans
- font.pixelSize: 13
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- onEntered: parent.color = Theme.textPrimary
- onExited: parent.color = Theme.textMuted
- onClicked: {
- let proc = Quickshell.createProcess("pavucontrol", [])
- proc.start()
- }
- }
- }
- }
- }
-
- // Section 3: Brightness
- ColumnLayout {
- id: sectionBrightness
- Layout.fillWidth: true
- spacing: 16
- SectionHeader { sectionNum: "03"; title: "Brightness" }
-
- CustomSlider {
- Layout.fillWidth: true
- value: GlobalState.brightness
- onValueChangedUser: (val) => GlobalState.setBrightness(val)
- }
- }
-
- // Section 4: Performance
- ColumnLayout {
- id: sectionPerformance
- Layout.fillWidth: true
- spacing: 16
- SectionHeader { sectionNum: "04"; title: "Performance" }
-
- ProgressBarItem {
- label: "CPU"
- percent: GlobalState.cpuPercent
- valueText: GlobalState.cpuPercent + "%"
- barColor: GlobalState.cpuPercent > 80 ? Theme.accentCoral : Theme.accentVolt
- }
-
- ProgressBarItem {
- label: "RAM"
- percent: (GlobalState.memUsedGb / Math.max(1, GlobalState.memTotalGb)) * 100
- valueText: GlobalState.memUsedGb + " / " + GlobalState.memTotalGb + " GB"
- }
-
- ProgressBarItem {
- label: "DISK"
- percent: GlobalState.diskPercent
- valueText: GlobalState.diskUsedGb + " / " + GlobalState.diskTotalGb + " GB"
- barColor: GlobalState.diskPercent > 90 ? Theme.accentCoral : Theme.accentVolt
- }
-
- ProgressBarItem {
- label: "VRAM"
- percent: (GlobalState.gpuVramUsed / Math.max(1, GlobalState.gpuVramTotal)) * 100
- valueText: GlobalState.gpuVramUsed + " / " + GlobalState.gpuVramTotal + " GB"
- }
- }
-
- // Section 5: Network
- ColumnLayout {
- id: sectionNetwork
- Layout.fillWidth: true
- spacing: 16
- SectionHeader { sectionNum: "05"; title: "Network" }
-
- RowLayout {
- spacing: 8
- Rectangle {
- width: 8
- height: 8
- radius: 4
- color: GlobalState.wifiConnected ? Theme.accentVolt : Theme.accentCoral
- }
- Text {
- text: {
- if (!GlobalState.wifiConnected) return "Disconnected";
- if (GlobalState.connectionType === "wifi") return "Wi-Fi — " + (GlobalState.wifiSsid !== "" ? GlobalState.wifiSsid : "unknown");
- if (GlobalState.connectionType === "eth") return "Ethernet";
- return "Connected";
- }
- color: Theme.textPrimary
- font.family: Theme.fontSans
- font.pixelSize: 14
- }
- }
-
- Text {
- visible: GlobalState.wifiConnected
- text: "IP: " + (GlobalState.ipAddress !== "" ? GlobalState.ipAddress : "—")
- color: Theme.textSecondary
- font.family: Theme.fontMono
- font.pixelSize: 13
- }
-
- Text {
- text: "Network Preferences..."
- color: Theme.textMuted
- font.family: Theme.fontSans
- font.pixelSize: 13
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- onEntered: parent.color = Theme.textPrimary
- onExited: parent.color = Theme.textMuted
- onClicked: {
- Quickshell.execDetached(["sh", "-c", "nm-connection-editor || foot -e nmtui"])
- }
- }
- }
- }
-
- // Section 6: Battery
- ColumnLayout {
- id: sectionBattery
- Layout.fillWidth: true
- spacing: 16
- visible: GlobalState.batteryPresent
- SectionHeader { sectionNum: "06"; title: "Battery" }
-
- RowLayout {
- spacing: 16
- Text {
- text: GlobalState.batteryPercent + "%"
- font.family: Theme.fontMono
- font.pixelSize: 24
- color: GlobalState.batteryStatus === "Charging" ? Theme.accentVolt :
- (GlobalState.batteryPercent < 20 ? Theme.accentCoral : Theme.textPrimary)
- }
- Text {
- text: GlobalState.batteryStatus
- color: Theme.textSecondary
- font.family: Theme.fontSans
- font.pixelSize: 14
- }
- }
- }
-
- // Section 7: Now Playing
- ColumnLayout {
- id: sectionNowPlaying
- Layout.fillWidth: true
- spacing: 16
- visible: GlobalState.mediaTitle !== ""
- SectionHeader { sectionNum: "07"; title: "Now Playing" }
-
- ColumnLayout {
- spacing: 4
- Text {
- text: GlobalState.mediaTitle
- color: Theme.textPrimary
- font.family: Theme.fontSans
- font.bold: true
- font.pixelSize: 14
- Layout.fillWidth: true
- elide: Text.ElideRight
- }
- Text {
- text: GlobalState.mediaArtist
- color: Theme.textSecondary
- font.family: Theme.fontSans
- font.pixelSize: 12
- Layout.fillWidth: true
- elide: Text.ElideRight
- }
- }
-
- RowLayout {
- Layout.alignment: Qt.AlignHCenter
- spacing: 16
-
- Text {
- text: "⏮"
- font.pixelSize: 20
- color: Theme.textSecondary
- MouseArea {
- anchors.fill: parent
- anchors.margins: -10
- hoverEnabled: true
- onEntered: parent.color = Theme.accentVolt
- onExited: parent.color = Theme.textSecondary
- onClicked: GlobalState.mediaPrevious()
- }
- }
- Text {
- text: GlobalState.mediaPlaying ? "⏸" : "▶"
- font.pixelSize: 24
- color: Theme.textSecondary
- MouseArea {
- anchors.fill: parent
- anchors.margins: -10
- hoverEnabled: true
- onEntered: parent.color = Theme.accentVolt
- onExited: parent.color = Theme.textSecondary
- onClicked: GlobalState.mediaPlayPause()
- }
- }
- Text {
- text: "⏭"
- font.pixelSize: 20
- color: Theme.textSecondary
- MouseArea {
- anchors.fill: parent
- anchors.margins: -10
- hoverEnabled: true
- onEntered: parent.color = Theme.accentVolt
- onExited: parent.color = Theme.textSecondary
- onClicked: GlobalState.mediaNext()
- }
- }
- }
- }
-
- // Section 8: Calendar
- ColumnLayout {
- id: sectionCalendar
- Layout.fillWidth: true
- spacing: 16
- SectionHeader { sectionNum: "08"; title: "Calendar" }
-
- RowLayout {
- spacing: 12
- Text {
- text: GlobalState.timeString
- font.family: Theme.fontMono
- font.pixelSize: 22
- color: Theme.accentVolt
- }
- Text {
- text: GlobalState.fullDateString
- font.family: Theme.fontSans
- font.pixelSize: 13
- color: Theme.textSecondary
- }
- }
-
- GridLayout {
- columns: 7
- columnSpacing: 4
- rowSpacing: 4
- Layout.alignment: Qt.AlignHCenter
-
- Repeater {
- model: ["S", "M", "T", "W", "T", "F", "S"]
- Text {
- text: modelData
- font.family: Theme.fontMono
- font.pixelSize: 11
- color: Theme.textMuted
- horizontalAlignment: Text.AlignHCenter
- Layout.preferredWidth: 32
- }
- }
-
- Repeater {
- model: 31
- Rectangle {
- property bool isToday: (index + 1) === new Date().getDate()
- Layout.preferredWidth: 32
- Layout.preferredHeight: 32
- color: isToday ? Theme.accentVolt : "transparent"
- radius: isToday ? 0 : 4
-
- Text {
- anchors.centerIn: parent
- text: index + 1
- color: parent.isToday ? "#ffffff" : Theme.textSecondary
- font.family: Theme.fontMono
- font.pixelSize: 12
- }
-
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- onEntered: if (!parent.isToday) parent.color = Qt.rgba(1, 1, 1, 0.04)
- onExited: if (!parent.isToday) parent.color = "transparent"
- }
- }
- }
- }
- }
-
- Item { Layout.preferredHeight: 20 } // Bottom padding
- }
- }
- }
-
- // Auto-scroll to active section when changed
- Connections {
- target: GlobalState
- function onSystemHudActiveSectionChanged() {
- if (!GlobalState.systemHudVisible) return;
-
- let targetY = 0;
- if (GlobalState.systemHudActiveSection === "volume") targetY = sectionVolume.y;
- else if (GlobalState.systemHudActiveSection === "brightness") targetY = sectionBrightness.y;
- else if (GlobalState.systemHudActiveSection === "performance") targetY = sectionPerformance.y;
- else if (GlobalState.systemHudActiveSection === "network") targetY = sectionNetwork.y;
- else if (GlobalState.systemHudActiveSection === "battery") targetY = sectionBattery.y;
- else if (GlobalState.systemHudActiveSection === "nowplaying") targetY = sectionNowPlaying.y;
- else if (GlobalState.systemHudActiveSection === "calendar") targetY = sectionCalendar.y;
-
- if (targetY > 0) {
- // Ensure targetY doesn't scroll past max possible scroll
- let maxY = Math.max(0, contentLayout.height - scrollView.height);
- scrollView.ScrollBar.vertical.position = Math.min(targetY, maxY) / contentLayout.height;
- }
- }
- }
-}
diff --git a/home/hypr/quickshell/components/SystemInfo.qml b/home/hypr/quickshell/components/SystemInfo.qml
deleted file mode 100644
index 9d351c1..0000000
--- a/home/hypr/quickshell/components/SystemInfo.qml
+++ /dev/null
@@ -1,257 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-import QtQuick.Controls
-import Quickshell
-import Quickshell.Wayland
-import Quickshell.Io
-import "../theme"
-import ".."
-
-PanelWindow {
- id: systemInfoDialog
-
- WlrLayershell.layer: WlrLayer.Overlay
- WlrLayershell.keyboardFocus: visible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
-
- anchors {
- top: true
- bottom: true
- left: true
- right: true
- }
-
- visible: GlobalState.aboutMacVisible
- color: Qt.rgba(0, 0, 0, 0.35)
-
- // Close on click outside or Escape
- MouseArea {
- anchors.fill: parent
- onClicked: GlobalState.aboutMacVisible = false
- }
- Item {
- anchors.fill: parent
- focus: true
- Keys.onEscapePressed: GlobalState.aboutMacVisible = false
- }
-
- property string cpuName: ""
- property string gpuName: ""
-
- // Process to get CPU info
- Process {
- id: cpuProcess
- command: ["sh", "-c", "grep 'model name' /proc/cpuinfo | head -n1 | cut -d':' -f2 | xargs"]
- running: true
- stdout: StdioCollector {
- onDataChanged: {
- const val = String(data).trim();
- if (val.length > 0) systemInfoDialog.cpuName = val;
- }
- }
- }
-
- // Process to get GPU info
- Process {
- id: gpuProcess
- command: ["sh", "-c", "nvidia-smi --query-gpu=name --format=csv,noheader | head -n1"]
- running: true
- stdout: StdioCollector {
- onDataChanged: {
- const val = String(data).trim();
- if (val.length > 0) systemInfoDialog.gpuName = val;
- }
- }
- }
-
- Rectangle {
- id: dialogBox
- width: 480
- height: 320
- anchors.centerIn: parent
- color: Theme.glassBg
- border.color: Theme.glassBorder
- border.width: 1
- radius: 0
-
- MouseArea {
- anchors.fill: parent
- // Eat clicks so they don't close the dialog
- }
-
- ColumnLayout {
- anchors.fill: parent
- spacing: 0
-
- // Header bar
- Item {
- Layout.fillWidth: true
- Layout.preferredHeight: 40
-
- Rectangle {
- anchors.bottom: parent.bottom
- anchors.left: parent.left
- anchors.right: parent.right
- height: 1
- color: Theme.glassBorder
- }
-
- Rectangle {
- id: closeBtn
- width: 40
- height: 40
- anchors.left: parent.left
- color: closeMouseArea.containsMouse ? Theme.accentCoral : "transparent"
-
- Text {
- anchors.centerIn: parent
- text: "✕"
- color: closeMouseArea.containsMouse ? Theme.textPrimary : Theme.textSecondary
- font.pixelSize: 14
- }
-
- MouseArea {
- id: closeMouseArea
- anchors.fill: parent
- hoverEnabled: true
- onClicked: GlobalState.aboutMacVisible = false
- }
- }
-
- Text {
- anchors.centerIn: parent
- text: "System Info"
- font.family: Theme.fontSerif
- font.italic: true
- font.pixelSize: 15
- color: Theme.textPrimary
- }
- }
-
- // Body
- Item {
- Layout.fillWidth: true
- Layout.fillHeight: true
-
- Column {
- anchors.centerIn: parent
- spacing: 8
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: "Silicon Volt"
- font.family: Theme.fontSerif
- font.italic: true
- font.pixelSize: 24
- color: Theme.accentVolt
- }
-
- Text {
- anchors.horizontalCenter: parent.horizontalCenter
- text: "NixOS // Hyprland"
- font.family: Theme.fontMono
- font.pixelSize: 12
- color: Theme.textMuted
- bottomPadding: 16
- }
-
- Grid {
- anchors.horizontalCenter: parent.horizontalCenter
- columns: 2
- spacing: 12
- rowSpacing: 8
-
- // HOST
- Text { width: 100; horizontalAlignment: Text.AlignRight; text: "HOST"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted }
- Text { text: "hades"; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary }
-
- // CPU
- Text { width: 100; horizontalAlignment: Text.AlignRight; text: "CPU"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted }
- Text { id: cpuText; text: systemInfoDialog.cpuName || "Loading..."; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary }
-
- // MEMORY
- Text { width: 100; horizontalAlignment: Text.AlignRight; text: "MEMORY"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted }
- Text { text: GlobalState.memUsedGb + " / " + GlobalState.memTotalGb + " GB"; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary }
-
- // GPU
- Text { width: 100; horizontalAlignment: Text.AlignRight; text: "GPU"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted }
- Text { id: gpuText; text: systemInfoDialog.gpuName || "Loading..."; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary }
-
- // VRAM
- Text { width: 100; horizontalAlignment: Text.AlignRight; text: "VRAM"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted }
- Text { text: GlobalState.gpuVramUsed + " / " + GlobalState.gpuVramTotal + " GB"; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary }
-
- // WM
- Text { width: 100; horizontalAlignment: Text.AlignRight; text: "WM"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted }
- Text { text: "Hyprland"; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary }
-
- // OS
- Text { width: 100; horizontalAlignment: Text.AlignRight; text: "OS"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted }
- Text { text: "NixOS"; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary }
- }
- }
- }
-
- // Footer bar
- Item {
- Layout.fillWidth: true
- Layout.preferredHeight: 44
-
- Rectangle {
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- height: 1
- color: Theme.glassBorder
- }
-
- component FooterButton: Rectangle {
- id: btn
- property string text: ""
- property alias command: proc.command
-
- width: btnText.width + 24
- height: 28
- anchors.verticalCenter: parent.verticalCenter
- color: btnMouse.containsMouse ? Qt.rgba(Theme.accentVolt.r, Theme.accentVolt.g, Theme.accentVolt.b, 0.1) : "transparent"
- border.color: Theme.glassBorder
- border.width: 1
- radius: 0
-
- Text {
- id: btnText
- anchors.centerIn: parent
- text: btn.text
- font.family: Theme.fontSans
- font.pixelSize: 12
- color: btnMouse.containsMouse ? Theme.accentVolt : Theme.textMuted
- }
-
- MouseArea {
- id: btnMouse
- anchors.fill: parent
- hoverEnabled: true
- onClicked: proc.running = true
- }
-
- Process {
- id: proc
- }
- }
-
- FooterButton {
- anchors.left: parent.left
- anchors.leftMargin: 16
- text: "System Report..."
- command: ["foot", "-e", "btop"]
- }
-
- FooterButton {
- anchors.right: parent.right
- anchors.rightMargin: 16
- text: "Software Update..."
- command: ["foot", "-e", "sudo", "nixos-rebuild", "dry-activate"]
- }
- }
- }
- }
-}
diff --git a/home/hypr/quickshell/components/TopStrip.qml b/home/hypr/quickshell/components/TopStrip.qml
deleted file mode 100644
index 030c43a..0000000
--- a/home/hypr/quickshell/components/TopStrip.qml
+++ /dev/null
@@ -1,177 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-import Quickshell
-import Quickshell.Wayland
-import "../theme"
-import ".."
-
-// Nerd Font glyphs (JetBrainsMono Nerd Font, installed system-wide)
-// \uF1EB wifi \uF796 ethernet \uF538 memory \uF1C0 disk
-// \uF028 volume \uF185 brightness \uF240 battery \uF0E7 charging
-// \uF017 clock \uF2DB cpu
-
-PanelWindow {
- id: root
-
- property var modelData
- screen: modelData
-
- anchors {
- top: true
- left: true
- right: true
- }
-
- margins.left: Theme.sidebarWidth
-
- implicitHeight: Theme.topStripHeight
- color: Qt.rgba(Theme.bgPrimary.r, Theme.bgPrimary.g, Theme.bgPrimary.b, 0.92)
- exclusiveZone: Theme.topStripHeight
-
- WlrLayershell.layer: WlrLayer.Top
- WlrLayershell.namespace: "quickshell-topstrip"
- WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
-
- Rectangle {
- id: bottomBorder
- anchors {
- bottom: parent.bottom
- left: parent.left
- right: parent.right
- }
- height: 1
- color: Theme.glassBorder
- }
-
- // --------------------------------------------------------------------
- // Helpers to compose rich-text status segments
- // --------------------------------------------------------------------
- function pad(num) {
- if (num === undefined || num === null) return "00";
- return num < 10 ? "0" + num : num.toString();
- }
-
- function seg(icon, value, iconColor, valueColor) {
- return "" + icon + " " + value + " ";
- }
-
- function buildStats() {
- const c = "#e5e2e1";
- const dim = "#adaaaa";
- const volt = "#CFFC00";
- const coral = "#FF7A59";
- const sep = " // ";
- const parts = [];
-
- // Network: type icon + SSID (wifi) + IP
- if (GlobalState.wifiConnected) {
- const icon = GlobalState.connectionType === "wifi" ? "\uF1EB" : "\uF796";
- const label = (GlobalState.connectionType === "wifi" && GlobalState.wifiSsid !== "") ? GlobalState.wifiSsid : "eth";
- parts.push(seg(icon, label + " " + (GlobalState.ipAddress !== "" ? GlobalState.ipAddress : "\u2014"), volt, c));
- } else {
- parts.push(seg("\uF1EB", "offline", coral, dim));
- }
-
- // Memory (used GB / total GB)
- parts.push(seg("\uF538", GlobalState.memUsedGb + "G / " + GlobalState.memTotalGb + "G",
- GlobalState.memPercent > 85 ? coral : dim, c));
-
- // Disk (root filesystem percent)
- parts.push(seg("\uF1C0", GlobalState.diskPercent + "%",
- GlobalState.diskPercent > 90 ? coral : dim, c));
-
- // Audio (mute state flips icon + color)
- if (GlobalState.volumeMuted) {
- parts.push(seg("\uF026", "mute", coral, dim));
- } else {
- parts.push(seg("\uF028", GlobalState.volume + "%", dim, c));
- }
-
- // Display brightness
- parts.push(seg("\uF185", GlobalState.brightness + "%", dim, c));
-
- // Battery (only when present, e.g. laptops)
- if (GlobalState.batteryPresent) {
- const icon = GlobalState.batteryCharging ? "\uF0E7" : "\uF240";
- parts.push(seg(icon, GlobalState.batteryPercent + "%",
- GlobalState.batteryPercent < 20 ? coral : dim, c));
- }
-
- return parts.join(sep);
- }
-
- RowLayout {
- anchors.fill: parent
- anchors.leftMargin: 16
- anchors.rightMargin: 16
- spacing: 16
-
- // Left Section: workspace indicator
- Text {
- id: workspaceIndicator
- Layout.alignment: Qt.AlignVCenter
- textFormat: Text.RichText
- font.family: "JetBrainsMono Nerd Font"
- font.pixelSize: 12
-
- text: {
- const name = GlobalState.workspaceNames[GlobalState.activeWorkspace] !== undefined
- ? GlobalState.workspaceNames[GlobalState.activeWorkspace] : "main";
- return "" + pad(GlobalState.activeWorkspace) + " "
- + " // "
- + "" + name + " ";
- }
- }
-
- // Center Section: focused window title
- Text {
- id: windowTitle
- Layout.fillWidth: true
- Layout.alignment: Qt.AlignVCenter
- horizontalAlignment: Text.AlignHCenter
- font.family: Theme.fontSerif
- font.italic: true
- font.pixelSize: Theme.fontSizeLarge
- color: Theme.textSecondary
- elide: Text.ElideRight
- maximumLineCount: 1
-
- text: GlobalState.activeWindowTitle
-
- Behavior on color {
- ColorAnimation { duration: 150 }
- }
- }
-
- // Right Section: live system stats (click to open the System HUD)
- Text {
- id: stats
- Layout.alignment: Qt.AlignVCenter
- textFormat: Text.RichText
- font.family: "JetBrainsMono Nerd Font"
- font.pixelSize: 11
-
- text: buildStats()
-
- MouseArea {
- id: statsMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: GlobalState.toggleSystemHud()
- }
- }
-
- // Clock
- Text {
- id: clock
- Layout.alignment: Qt.AlignVCenter
- textFormat: Text.RichText
- font.family: "JetBrainsMono Nerd Font"
- font.pixelSize: 12
-
- text: "\uF017 "
- + " " + GlobalState.timeString + " "
- }
- }
-}
diff --git a/home/hypr/quickshell/components/VolumePopup.qml b/home/hypr/quickshell/components/VolumePopup.qml
deleted file mode 100644
index fa302aa..0000000
--- a/home/hypr/quickshell/components/VolumePopup.qml
+++ /dev/null
@@ -1,148 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-import QtQuick.Controls
-import "../theme"
-import ".."
-
-Rectangle {
- id: root
- width: 240
- height: 110
- radius: Theme.radiusMedium
- color: Theme.menuDropdownBg
- border.color: Theme.menuDropdownBorder
- border.width: 1
-
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: 12
- spacing: 8
-
- RowLayout {
- Layout.fillWidth: true
- Text {
- text: "Sound"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeNormal
- font.weight: Font.DemiBold
- color: Theme.textPrimary
- Layout.fillWidth: true
- }
- Text {
- text: `${GlobalState.volume}%`
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textSecondary
- }
- }
-
- // Custom Catalina Volume Slider
- Item {
- Layout.fillWidth: true
- Layout.preferredHeight: 24
-
- Rectangle {
- id: track
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.verticalCenter: parent.verticalCenter
- height: 6
- radius: 3
- color: Qt.rgba(1.0, 1.0, 1.0, 0.2)
-
- Rectangle {
- anchors.left: parent.left
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- width: track.width * (GlobalState.volume / 100.0)
- radius: 3
- color: Theme.accentBlue
- }
- }
-
- Rectangle {
- id: thumb
- x: Math.max(0, Math.min(track.width - width, (track.width * (GlobalState.volume / 100.0)) - width / 2))
- anchors.verticalCenter: parent.verticalCenter
- width: 16
- height: 16
- radius: 8
- color: "#FFFFFF"
- border.color: Qt.rgba(0, 0, 0, 0.2)
- border.width: 1
- }
-
- MouseArea {
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- preventStealing: true
-
- function updateFromMouse(mouse) {
- const frac = Math.max(0.0, Math.min(1.0, mouse.x / width));
- GlobalState.setVolume(Math.round(frac * 100));
- }
-
- onPressed: (mouse) => updateFromMouse(mouse)
- onPositionChanged: (mouse) => {
- if (pressed) updateFromMouse(mouse);
- }
- }
- }
-
- // Bottom Controls
- RowLayout {
- Layout.fillWidth: true
- spacing: 8
-
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 24
- radius: Theme.radiusSmall
- color: muteMouse.containsMouse ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : Qt.rgba(1.0, 1.0, 1.0, 0.08)
-
- Text {
- anchors.centerIn: parent
- text: GlobalState.volumeMuted ? "Unmute" : "Mute"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textPrimary
- }
-
- MouseArea {
- id: muteMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: GlobalState.toggleVolumeMute()
- }
- }
-
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 24
- radius: Theme.radiusSmall
- color: prefMouse.containsMouse ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : Qt.rgba(1.0, 1.0, 1.0, 0.08)
-
- Text {
- anchors.centerIn: parent
- text: "Sound Preferences..."
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textPrimary
- }
-
- MouseArea {
- id: prefMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- GlobalState.volumePopupVisible = false;
- GlobalState.launchApp("pavucontrol");
- }
- }
- }
- }
- }
-}
diff --git a/home/hypr/quickshell/components/WifiPopup.qml b/home/hypr/quickshell/components/WifiPopup.qml
deleted file mode 100644
index 657c905..0000000
--- a/home/hypr/quickshell/components/WifiPopup.qml
+++ /dev/null
@@ -1,102 +0,0 @@
-import QtQuick
-import QtQuick.Layouts
-import QtQuick.Controls
-import "../theme"
-import ".."
-
-Rectangle {
- id: root
- width: 240
- implicitHeight: wifiCol.implicitHeight + 24
- radius: Theme.radiusMedium
- color: Theme.menuDropdownBg
- border.color: Theme.menuDropdownBorder
- border.width: 1
-
- ColumnLayout {
- id: wifiCol
- anchors.fill: parent
- anchors.margins: 12
- spacing: 8
-
- RowLayout {
- Layout.fillWidth: true
- Text {
- text: "Wi-Fi"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeNormal
- font.weight: Font.DemiBold
- color: Theme.textPrimary
- Layout.fillWidth: true
- }
- Text {
- text: GlobalState.wifiConnected ? "On" : "Off"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: GlobalState.wifiConnected ? Theme.accentGreen : Theme.textTertiary
- }
- }
-
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 1
- color: Qt.rgba(1.0, 1.0, 1.0, 0.12)
- }
-
- // Active network info
- ColumnLayout {
- Layout.fillWidth: true
- spacing: 3
-
- Text {
- text: GlobalState.wifiConnected ? `Connected to: ${GlobalState.wifiSsid}` : "No Network Connected"
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Medium
- color: Theme.textPrimary
- elide: Text.ElideRight
- Layout.fillWidth: true
- }
-
- Text {
- visible: GlobalState.ipAddress !== "" && GlobalState.ipAddress !== "Offline"
- text: `IP: ${GlobalState.ipAddress}`
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textSecondary
- }
- }
-
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 1
- color: Qt.rgba(1.0, 1.0, 1.0, 0.12)
- }
-
- Rectangle {
- Layout.fillWidth: true
- Layout.preferredHeight: 24
- radius: Theme.radiusSmall
- color: netMouse.containsMouse ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : Qt.rgba(1.0, 1.0, 1.0, 0.08)
-
- Text {
- anchors.centerIn: parent
- text: "Network Preferences..."
- font.family: Theme.fontFamily
- font.pixelSize: Theme.fontSizeSmall
- color: Theme.textPrimary
- }
-
- MouseArea {
- id: netMouse
- anchors.fill: parent
- hoverEnabled: true
- cursorShape: Qt.PointingHandCursor
- onClicked: {
- GlobalState.wifiPopupVisible = false;
- GlobalState.launchApp("nm-connection-editor || foot -e nmtui");
- }
- }
- }
- }
-}
diff --git a/home/hypr/quickshell/config.example.json b/home/hypr/quickshell/config.example.json
new file mode 100644
index 0000000..2f3a4fe
--- /dev/null
+++ b/home/hypr/quickshell/config.example.json
@@ -0,0 +1,96 @@
+{
+ "appearance": {
+ "colorSource": "default",
+ "caelestia": {
+ "autoRefresh": true,
+ "refreshInterval": 2000,
+ "accentProfile": "vibrant"
+ },
+ "rounding": {
+ "unsharpen": 2,
+ "verysmall": 8,
+ "small": 12,
+ "normal": 17,
+ "large": 23,
+ "full": 9999,
+ "screenRounding": 23,
+ "windowRounding": 18
+ },
+ "font": {
+ "family": {
+ "main": "sans-serif",
+ "title": "sans-serif",
+ "expressive": "sans-serif"
+ },
+ "pixelSize": {
+ "smaller": 12,
+ "small": 15,
+ "normal": 16,
+ "larger": 19,
+ "huge": 22
+ }
+ },
+ "animation": {
+ "duration": {
+ "elementMove": 500,
+ "elementMoveEnter": 400,
+ "elementMoveFast": 200
+ }
+ },
+ "sizes": {
+ "elevationMargin": 10
+ }
+ },
+ "overview": {
+ "rows": 2,
+ "columns": 5,
+ "scale": 0.16,
+ "enable": true,
+ "hideEmptyRows": true,
+ "closeOnFocusLoss": true,
+ "useWorkspaceMap": false,
+ "workspaceMap": [0, 10],
+ "orderRightLeft": false,
+ "orderBottomUp": false,
+ "previewsEnabled": true,
+ "previewMode": "live",
+ "includeInactiveMonitorPreviews": true,
+ "previewRecaptureDelayMs": 60,
+ "showSpecialWorkspaces": true,
+ "specialWorkspaces": [],
+ "specialWorkspaceColumns": 5,
+ "emptyWorkspaceWallpaper": "",
+ "specialEmptyWorkspaceWallpaper": "",
+ "effects": {
+ "enableBackdrop": false,
+ "backdropOpacity": 0.28,
+ "panelOpacity": 0.92,
+ "workspaceOpacity": 0.86,
+ "emptyWorkspaceWallpaperOverlayOpacity": 0.18,
+ "windowOverlayOpacity": 0.22,
+ "enableBlur": false,
+ "glassMode": false,
+ "glassTintStrength": 0.35,
+ "glassBorderOpacity": 0.72,
+ "glassShineOpacity": 0.14
+ },
+ "workspaceSpacing": 5,
+ "backgroundPadding": 10,
+ "workspaceNumberBaseSize": 250
+ },
+ "position": {
+ "topMargin": 100
+ },
+ "windowPreview": {
+ "showIcons": true,
+ "iconToWindowRatio": 0.25,
+ "iconToWindowRatioCompact": 0.45,
+ "xwaylandIndicatorToIconRatio": 0.35,
+ "inactiveMonitorOpacity": 0.4,
+ "cropToFill": false
+ },
+ "hacks": {
+ "arbitraryRaceConditionDelay": 150,
+ "hyprlandEventDebounceMs": 40
+ }
+}
diff --git a/home/hypr/quickshell/config.json b/home/hypr/quickshell/config.json
new file mode 100644
index 0000000..ea2ce78
--- /dev/null
+++ b/home/hypr/quickshell/config.json
@@ -0,0 +1,42 @@
+{
+ "appearance": {
+ "colorSource": "default",
+ "rounding": {
+ "unsharpen": 0,
+ "verysmall": 0,
+ "small": 0,
+ "normal": 0,
+ "large": 0,
+ "full": 9999,
+ "screenRounding": 0,
+ "windowRounding": 0
+ },
+ "font": {
+ "family": {
+ "main": "IBM Plex Sans",
+ "title": "Instrument Serif",
+ "expressive": "IBM Plex Sans"
+ },
+ "pixelSize": {
+ "smaller": 11,
+ "small": 13,
+ "normal": 15,
+ "larger": 19,
+ "huge": 24
+ }
+ },
+ "animation": {
+ "duration": {
+ "elementMove": 400,
+ "elementMoveEnter": 400,
+ "elementMoveFast": 200
+ }
+ }
+ },
+ "overview": {
+ "effects": {
+ "enableBackdrop": true,
+ "backdropOpacity": 0.35
+ }
+ }
+}
diff --git a/home/hypr/quickshell/modules/overview/Overview.qml b/home/hypr/quickshell/modules/overview/Overview.qml
new file mode 100644
index 0000000..2fb08bb
--- /dev/null
+++ b/home/hypr/quickshell/modules/overview/Overview.qml
@@ -0,0 +1,252 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Io
+import Quickshell.Wayland
+import Quickshell.Hyprland
+import "../../common"
+import "../../services"
+import "."
+
+Scope {
+ id: overviewScope
+ Variants {
+ id: overviewVariants
+ model: Quickshell.screens
+ PanelWindow {
+ id: root
+ required property var modelData
+ readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen)
+ property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id)
+ property bool blurEnabled: Config.options.overview.effects.enableBlur
+ property bool backdropEnabled: Config.options.overview.effects.enableBackdrop
+ property real backdropOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.backdropOpacity))
+ property bool closeOnFocusLoss: Config.options.overview.closeOnFocusLoss ?? true
+ screen: modelData
+ visible: GlobalStates.overviewOpen
+
+ WlrLayershell.namespace: blurEnabled ? "quickshell:overview-blur" : "quickshell:overview"
+ WlrLayershell.layer: WlrLayer.Overlay
+ WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
+ color: "transparent"
+
+ anchors {
+ top: true
+ bottom: true
+ left: true
+ right: true
+ }
+
+ HyprlandFocusGrab {
+ id: grab
+ windows: [root]
+ property bool canBeActive: root.monitorIsFocused
+ active: false
+ onCleared: () => {
+ // Only the monitor that owns the grab may close the overview
+ if (root.closeOnFocusLoss && !active && canBeActive)
+ GlobalStates.overviewOpen = false;
+ }
+ }
+
+ Connections {
+ target: GlobalStates
+ function onOverviewOpenChanged() {
+ if (GlobalStates.overviewOpen) {
+ delayedGrabTimer.start();
+ }
+ }
+ }
+
+ // Re-evaluate grab ownership when focused monitor changes
+ Connections {
+ target: Hyprland
+ function onFocusedMonitorChanged() {
+ if (!GlobalStates.overviewOpen)
+ return;
+ // Transfer the grab to the newly focused monitor
+ if (root.monitorIsFocused && !grab.active) {
+ grab.active = true;
+ } else if (!root.monitorIsFocused && grab.active) {
+ grab.active = false;
+ }
+ }
+ }
+
+ Timer {
+ id: delayedGrabTimer
+ interval: Config.options.hacks.arbitraryRaceConditionDelay
+ repeat: false
+ onTriggered: {
+ if (!grab.canBeActive)
+ return;
+ grab.active = GlobalStates.overviewOpen;
+ }
+ }
+
+ // Keep the layershell surface full-screen so backdrop/blur are not constrained by content size.
+ implicitWidth: screen.width
+ implicitHeight: screen.height
+
+ Item {
+ id: keyHandler
+ anchors.fill: parent
+ visible: GlobalStates.overviewOpen
+ focus: GlobalStates.overviewOpen
+ z: 0
+
+ Rectangle {
+ id: backdropLayer
+ anchors.fill: parent
+ visible: root.backdropEnabled
+ color: "#000000"
+ opacity: root.backdropOpacity
+ z: 0
+ }
+
+ MouseArea {
+ id: outsideClickCatcher
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
+ enabled: root.closeOnFocusLoss && GlobalStates.overviewOpen
+ z: 0
+ onPressed: mouse => {
+ GlobalStates.overviewOpen = false;
+ mouse.accepted = true;
+ }
+ }
+
+ Keys.onPressed: event => {
+ // close: Escape or Enter
+ if (event.key === Qt.Key_Escape || event.key === Qt.Key_Return) {
+ GlobalStates.overviewOpen = false;
+ event.accepted = true;
+ return;
+ }
+
+ // Helper: compute current group bounds
+ const workspacesPerGroup = Config.options.overview.rows * Config.options.overview.columns;
+ const currentId = Hyprland.focusedMonitor?.activeWorkspace?.id ?? 1;
+ const useWorkspaceMap = Config.options.overview.useWorkspaceMap;
+ const workspaceMap = Config.options.overview.workspaceMap ?? [];
+ const focusedMonitorId = Hyprland.focusedMonitor?.id ?? root.monitor?.id ?? 0;
+ const workspaceOffset = useWorkspaceMap ? Number(workspaceMap[focusedMonitorId] ?? 0) : 0;
+ const currentGroup = Math.floor((currentId - workspaceOffset - 1) / workspacesPerGroup);
+ const minWorkspaceId = currentGroup * workspacesPerGroup + 1 + workspaceOffset;
+ const maxWorkspaceId = minWorkspaceId + workspacesPerGroup - 1;
+
+ const rows = Config.options.overview.rows;
+ const columns = Config.options.overview.columns;
+ const reverseColumns = Config.options.overview.orderRightLeft;
+ const reverseRows = Config.options.overview.orderBottomUp;
+
+ const clampedIndex = Math.max(0, Math.min(workspacesPerGroup - 1, currentId - minWorkspaceId));
+ const currentNormalRow = Math.floor(clampedIndex / columns);
+ const currentNormalColumn = clampedIndex % columns;
+
+ function toVisualRow(normalRow) {
+ return reverseRows ? (rows - normalRow - 1) : normalRow;
+ }
+
+ function toVisualColumn(normalColumn) {
+ return reverseColumns ? (columns - normalColumn - 1) : normalColumn;
+ }
+
+ function toNormalRow(visualRow) {
+ return reverseRows ? (rows - visualRow - 1) : visualRow;
+ }
+
+ function toNormalColumn(visualColumn) {
+ return reverseColumns ? (columns - visualColumn - 1) : visualColumn;
+ }
+
+ let targetVisualRow = toVisualRow(currentNormalRow);
+ let targetVisualColumn = toVisualColumn(currentNormalColumn);
+
+ let targetId = null;
+
+ // Arrow keys and vim-style hjkl
+ if (event.key === Qt.Key_Left || event.key === Qt.Key_H) {
+ targetVisualColumn = (targetVisualColumn - 1 + columns) % columns;
+ } else if (event.key === Qt.Key_Right || event.key === Qt.Key_L) {
+ targetVisualColumn = (targetVisualColumn + 1) % columns;
+ } else if (event.key === Qt.Key_Up || event.key === Qt.Key_K) {
+ targetVisualRow = (targetVisualRow - 1 + rows) % rows;
+ } else if (event.key === Qt.Key_Down || event.key === Qt.Key_J) {
+ targetVisualRow = (targetVisualRow + 1) % rows;
+ }
+
+ // Number keys: jump to workspace within the current group
+ // 1-9 map to positions 1-9, 0 maps to position 10
+ else if (event.key >= Qt.Key_1 && event.key <= Qt.Key_9) {
+ const position = event.key - Qt.Key_0; // 1-9
+ if (position <= workspacesPerGroup) {
+ targetId = minWorkspaceId + position - 1;
+ }
+ } else if (event.key === Qt.Key_0) {
+ // 0 = 10th workspace in the group (if group has 10+ workspaces)
+ if (workspacesPerGroup >= 10) {
+ targetId = minWorkspaceId + 9; // 10th position = offset 9
+ }
+ }
+
+ if (targetId === null && (
+ event.key === Qt.Key_Left || event.key === Qt.Key_H ||
+ event.key === Qt.Key_Right || event.key === Qt.Key_L ||
+ event.key === Qt.Key_Up || event.key === Qt.Key_K ||
+ event.key === Qt.Key_Down || event.key === Qt.Key_J
+ )) {
+ const targetNormalRow = toNormalRow(targetVisualRow);
+ const targetNormalColumn = toNormalColumn(targetVisualColumn);
+ targetId = minWorkspaceId + targetNormalRow * columns + targetNormalColumn;
+ }
+
+ if (targetId !== null) {
+ const clampedTarget = Math.max(minWorkspaceId, Math.min(maxWorkspaceId, targetId));
+ if (Hyprland.usingLua) {
+ Hyprland.dispatch(`hl.dsp.focus({workspace = '${clampedTarget}'})`);
+ } else {
+ Hyprland.dispatch("workspace " + clampedTarget);
+ }
+ event.accepted = true;
+ }
+ }
+ }
+
+ ColumnLayout {
+ id: columnLayout
+ visible: GlobalStates.overviewOpen
+ z: 1
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ top: parent.top
+ topMargin: Config.options.position.topMargin
+ }
+
+ Loader {
+ id: overviewLoader
+ active: Config?.options.overview.enable ?? true
+ sourceComponent: OverviewWidget {
+ panelWindow: root
+ visible: true
+ }
+ }
+ }
+ }
+ }
+
+ IpcHandler {
+ target: "overview"
+
+ function toggle() {
+ GlobalStates.overviewOpen = !GlobalStates.overviewOpen;
+ }
+ function close() {
+ GlobalStates.overviewOpen = false;
+ }
+ function open() {
+ GlobalStates.overviewOpen = true;
+ }
+ }
+}
diff --git a/home/hypr/quickshell/modules/overview/OverviewWidget.qml b/home/hypr/quickshell/modules/overview/OverviewWidget.qml
new file mode 100644
index 0000000..ca82adb
--- /dev/null
+++ b/home/hypr/quickshell/modules/overview/OverviewWidget.qml
@@ -0,0 +1,1214 @@
+import QtQuick
+import QtQuick.Effects
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Wayland
+import Quickshell.Hyprland
+import "../../common"
+import "../../common/functions"
+import "../../common/widgets"
+import "../../services"
+import "."
+
+Item {
+ id: root
+ required property var panelWindow
+ readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)
+ readonly property var toplevels: ToplevelManager.toplevels
+ readonly property int effectiveActiveWorkspaceId: Math.max(1, Math.min(100, monitor?.activeWorkspace?.id ?? 1))
+ readonly property int workspacesShown: Config.options.overview.rows * Config.options.overview.columns
+ readonly property bool useWorkspaceMap: Config.options.overview.useWorkspaceMap
+ readonly property var workspaceMap: Config.options.overview.workspaceMap
+ readonly property int workspaceOffset: useWorkspaceMap ? Number(workspaceMap[root.monitor?.id] ?? 0) : 0
+ readonly property int workspaceGroup: Math.floor((effectiveActiveWorkspaceId - workspaceOffset - 1) / workspacesShown)
+ property bool monitorIsFocused: (Hyprland.focusedMonitor?.name == monitor.name)
+ property var windows: HyprlandData.windowList
+ property var windowByAddress: HyprlandData.windowByAddress
+ property var windowAddresses: HyprlandData.addresses
+ property var workspaceIds: HyprlandData.workspaceIds
+ property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor?.id)
+ property real scale: Config.options.overview.scale
+ property color activeBorderColor: Appearance.colors.colSecondary
+
+ property real workspaceImplicitWidth: Math.round((monitorData?.transform % 2 === 1) ?
+ ((monitor.height / monitor.scale - (monitorData?.reserved?.[0] ?? 0) - (monitorData?.reserved?.[2] ?? 0)) * root.scale) :
+ ((monitor.width / monitor.scale - (monitorData?.reserved?.[0] ?? 0) - (monitorData?.reserved?.[2] ?? 0)) * root.scale))
+ property real workspaceImplicitHeight: Math.round((monitorData?.transform % 2 === 1) ?
+ ((monitor.width / monitor.scale - (monitorData?.reserved?.[1] ?? 0) - (monitorData?.reserved?.[3] ?? 0)) * root.scale) :
+ ((monitor.height / monitor.scale - (monitorData?.reserved?.[1] ?? 0) - (monitorData?.reserved?.[3] ?? 0)) * root.scale))
+
+ property real workspaceNumberMargin: 80
+ property real workspaceNumberSize: Config.options.overview.workspaceNumberBaseSize * monitor.scale
+ property int workspaceZ: 0
+ property int windowZ: 1
+ property int windowDraggingZ: 99999
+ property real workspaceSpacing: Config.options.overview.workspaceSpacing
+ property string emptyWorkspaceWallpaperPath: Config.options.overview.emptyWorkspaceWallpaper
+ property string specialEmptyWorkspaceWallpaperPath: Config.options.overview.specialEmptyWorkspaceWallpaper
+ property bool showSpecialWorkspaces: Config.options.overview.showSpecialWorkspaces
+ property var configuredSpecialWorkspaces: Config.options.overview.specialWorkspaces ?? []
+ property int specialWorkspaceColumns: Math.max(1, Config.options.overview.specialWorkspaceColumns)
+ property real panelOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.panelOpacity))
+ property real workspaceOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.workspaceOpacity))
+ property real emptyWorkspaceWallpaperOverlayOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.emptyWorkspaceWallpaperOverlayOpacity))
+ property bool glassMode: Config.options.overview.effects.glassMode
+ property real glassTintStrength: Math.max(0, Math.min(1, Config.options.overview.effects.glassTintStrength))
+ property real glassBorderOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.glassBorderOpacity))
+ property real glassShineOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.glassShineOpacity))
+ property real effectivePanelOpacity: glassMode ? Math.min(panelOpacity, 0.72) : panelOpacity
+ property real effectiveWorkspaceOpacity: glassMode ? Math.min(workspaceOpacity, 0.62) : workspaceOpacity
+
+ property int draggingFromWorkspace: -1
+ property int draggingTargetWorkspace: -1
+ property string draggingTargetSpecialWorkspace: ""
+ property int previewRecaptureToken: 0
+ property var allWorkspaces: HyprlandData.allWorkspaces
+ property bool previewsEnabled: Config.options.overview.previewsEnabled
+ property string previewModeRaw: Config.options.overview.previewMode
+ property string previewMode: {
+ const mode = `${previewModeRaw ?? "live"}`.trim().toLowerCase();
+ return (mode === "event" || mode === "snapshot") ? "event" : "live";
+ }
+ property bool useEventPreviewRefresh: previewsEnabled && previewMode === "event"
+
+ readonly property var monitorSpecialWorkspaceNames: {
+ const names = [];
+ for (const ws of (allWorkspaces ?? [])) {
+ const name = `${ws?.name ?? ""}`;
+ if (!name.startsWith("special:"))
+ continue;
+ if (`${ws?.monitor ?? ""}` !== `${root.monitor?.name ?? ""}`)
+ continue;
+ names.push(name.slice(8));
+ }
+ return names;
+ }
+
+ readonly property var specialWorkspaceNamesFromWindows: {
+ const names = [];
+ for (const addr in windowByAddress) {
+ const win = windowByAddress[addr];
+ if ((win?.monitor ?? -1) !== (root.monitor?.id ?? -1))
+ continue;
+ const wsName = `${win?.workspace?.name ?? ""}`;
+ if (!wsName.startsWith("special:"))
+ continue;
+ names.push(wsName.slice(8));
+ }
+ return names;
+ }
+
+ readonly property var visibleSpecialWorkspaces: {
+ if (!showSpecialWorkspaces)
+ return [];
+
+ const out = [];
+ const pushUnique = (value) => {
+ const cleaned = `${value ?? ""}`.trim();
+ if (cleaned.length === 0 || out.includes(cleaned))
+ return;
+ out.push(cleaned);
+ };
+
+ for (const configured of configuredSpecialWorkspaces ?? [])
+ pushUnique(configured);
+ for (const name of monitorSpecialWorkspaceNames)
+ pushUnique(name);
+ for (const name of specialWorkspaceNamesFromWindows)
+ pushUnique(name);
+
+ return out;
+ }
+
+ readonly property bool hasSpecialWorkspaceSection: visibleSpecialWorkspaces.length > 0
+ readonly property bool hasEmptyWorkspaceWallpaper: `${emptyWorkspaceWallpaperPath ?? ""}`.trim().length > 0
+ readonly property bool hasSpecialEmptyWorkspaceWallpaper: `${specialEmptyWorkspaceWallpaperPath ?? ""}`.trim().length > 0
+ readonly property string createSpecialWorkspaceTarget: "__create_special_workspace__"
+ readonly property real specialWorkspaceTileHeight: root.workspaceImplicitHeight
+ readonly property real specialStripGap: workspaceSpacing * 1.8
+ readonly property real specialStripPadding: Math.max(8, 12 * root.scale)
+ readonly property real specialStripTitleHeight: Math.max(14, Appearance.font.pixelSize.small * root.scale)
+ readonly property real specialStripTitleGap: Math.max(6, 8 * root.scale)
+ readonly property int totalSpecialTiles: visibleSpecialWorkspaces.length + 1
+ readonly property real specialSectionWidth: workspaceColumnLayout.implicitWidth
+ readonly property real specialGridInnerWidth: Math.max(0, root.specialSectionWidth - root.specialStripPadding * 2)
+ readonly property int effectiveSpecialColumns: Math.max(1, Math.min(root.specialWorkspaceColumns, root.totalSpecialTiles))
+ readonly property int specialWorkspaceRows: Math.ceil(root.totalSpecialTiles / root.effectiveSpecialColumns)
+ readonly property real specialWorkspaceAspectCap: {
+ let maxAspect = Math.max(1, root.workspaceImplicitWidth / Math.max(1, root.workspaceImplicitHeight));
+ for (const name of visibleSpecialWorkspaces) {
+ const geometry = root.specialWorkspaceGeometry(name, root.monitor?.id);
+ const width = geometry?.width;
+ const height = geometry?.height;
+ if (!Number.isFinite(width) || !Number.isFinite(height) || height <= 0)
+ continue;
+ maxAspect = Math.max(maxAspect, width / height);
+ }
+ return maxAspect;
+ }
+ readonly property real specialWorkspaceTileWidth: {
+ const gaps = Math.max(0, root.effectiveSpecialColumns - 1);
+ const rawWidth = (root.specialGridInnerWidth - gaps * workspaceSpacing) / root.effectiveSpecialColumns;
+ const aspectWidth = root.specialWorkspaceTileHeight * root.specialWorkspaceAspectCap;
+ const cappedWidth = Math.min(rawWidth, aspectWidth);
+ return Math.max(80 * root.scale, cappedWidth);
+ }
+ readonly property real specialGridUsedWidth: root.effectiveSpecialColumns * root.specialWorkspaceTileWidth + Math.max(0, root.effectiveSpecialColumns - 1) * workspaceSpacing
+ readonly property real specialGridOffsetX: root.specialStripPadding + Math.max(0, (root.specialGridInnerWidth - root.specialGridUsedWidth) / 2)
+ readonly property real specialStripTop: workspaceColumnLayout.implicitHeight + workspaceSpacing + root.specialStripGap
+ readonly property real specialStripTilesTop: root.specialStripTop + root.specialStripPadding + root.specialStripTitleHeight + root.specialStripTitleGap
+ readonly property real specialGridHeight: root.specialWorkspaceRows * root.specialWorkspaceTileHeight + Math.max(0, root.specialWorkspaceRows - 1) * workspaceSpacing
+ readonly property real specialStripHeight: root.specialStripPadding * 2 + root.specialStripTitleHeight + root.specialStripTitleGap + root.specialGridHeight
+
+ function getWorkspaceRow(workspaceId) {
+ if (!Number.isFinite(workspaceId))
+ return 0;
+ const adjusted = workspaceId - workspaceOffset;
+ const normalRow = Math.floor((adjusted - 1) / Config.options.overview.columns) % Config.options.overview.rows;
+ return Config.options.overview.orderBottomUp ? (Config.options.overview.rows - normalRow - 1) : normalRow;
+ }
+
+ function getWorkspaceColumn(workspaceId) {
+ if (!Number.isFinite(workspaceId))
+ return 0;
+ const adjusted = workspaceId - workspaceOffset;
+ const normalCol = (adjusted - 1) % Config.options.overview.columns;
+ return Config.options.overview.orderRightLeft ? (Config.options.overview.columns - normalCol - 1) : normalCol;
+ }
+
+ function getWorkspaceInCell(rowIndex, colIndex) {
+ const mappedRow = Config.options.overview.orderBottomUp ? (Config.options.overview.rows - rowIndex - 1) : rowIndex;
+ const mappedCol = Config.options.overview.orderRightLeft ? (Config.options.overview.columns - colIndex - 1) : colIndex;
+ return (workspaceGroup * workspacesShown) + (mappedRow * Config.options.overview.columns) + mappedCol + 1 + workspaceOffset;
+ }
+
+ function getVisibleRowPosition(rowIndex) {
+ if (!Number.isFinite(rowIndex) || rowIndex < 0)
+ return 0;
+ if (!Config.options.overview.hideEmptyRows || !(rowsWithContent instanceof Set))
+ return rowIndex;
+
+ let visibleRow = 0;
+ for (let i = 0; i < rowIndex; i += 1) {
+ if (rowsWithContent.has(i))
+ visibleRow += 1;
+ }
+ return visibleRow;
+ }
+
+ function stepWorkspace(delta) {
+ if (!Number.isFinite(delta) || delta === 0)
+ return;
+
+ const currentId = monitor?.activeWorkspace?.id ?? effectiveActiveWorkspaceId;
+ const minWorkspaceId = workspaceOffset + 1;
+ let maxWorkspaceId = minWorkspaceId + workspacesShown - 1;
+ for (const workspaceId of (workspaceIds ?? [])) {
+ if (Number.isFinite(workspaceId) && workspaceId >= minWorkspaceId) {
+ maxWorkspaceId = Math.max(maxWorkspaceId, workspaceId);
+ }
+ }
+ maxWorkspaceId = Math.max(maxWorkspaceId, currentId);
+
+ let targetId = currentId + delta;
+ if (targetId < minWorkspaceId) {
+ targetId = maxWorkspaceId;
+ } else if (targetId > maxWorkspaceId) {
+ targetId = minWorkspaceId;
+ }
+ if (Hyprland.usingLua) {
+ Hyprland.dispatch(`hl.dsp.focus({workspace = '${targetId}'})`);
+ } else {
+ Hyprland.dispatch(`workspace ${targetId}`);
+ }
+ }
+
+ function isSpecialWorkspace(windowData) {
+ const wsName = `${windowData?.workspace?.name ?? ""}`;
+ return wsName.startsWith("special:");
+ }
+
+ function specialWorkspaceName(windowData) {
+ const wsName = `${windowData?.workspace?.name ?? ""}`;
+ return wsName.startsWith("special:") ? wsName.slice(8) : "";
+ }
+
+ function specialWorkspaceIndex(name) {
+ return visibleSpecialWorkspaces.indexOf(`${name ?? ""}`);
+ }
+
+ function specialWorkspaceLabel(name) {
+ const raw = `${name ?? ""}`.trim();
+ if (raw.length === 0)
+ return "Special";
+ return raw.replace(/[-_]+/g, " ");
+ }
+
+ function nextSpecialWorkspaceName() {
+ const taken = new Set();
+ for (const name of visibleSpecialWorkspaces)
+ taken.add(`${name ?? ""}`.trim().toLowerCase());
+
+ const base = "stash";
+ if (!taken.has(base))
+ return base;
+
+ let index = 2;
+ while (taken.has(`${base}-${index}`))
+ index += 1;
+
+ return `${base}-${index}`;
+ }
+
+ function wallpaperSource(path) {
+ const trimmed = `${path ?? ""}`.trim();
+ if (trimmed.length === 0)
+ return "";
+ if (trimmed.startsWith("file:/") || trimmed.startsWith("qrc:/") || trimmed.startsWith("image://") || trimmed.startsWith("http://") || trimmed.startsWith("https://"))
+ return trimmed;
+ if (trimmed.startsWith("/"))
+ return `file://${trimmed}`;
+ return trimmed;
+ }
+
+ function workspaceHasWindows(workspaceId) {
+ if (!Number.isFinite(workspaceId))
+ return false;
+
+ for (const addr in windowByAddress) {
+ const win = windowByAddress[addr];
+ if (root.isSpecialWorkspace(win))
+ continue;
+ if ((win?.workspace?.id ?? -1) === workspaceId)
+ return true;
+ }
+ return false;
+ }
+
+ function specialWindowZ(win) {
+ const pinned = win?.pinned ? 200000 : 0;
+ const floating = win?.floating ? 100000 : 0;
+ const focus = 10000 - (win?.focusHistoryID ?? 9999);
+ return pinned + floating + focus;
+ }
+
+ function specialWorkspaceGeometry(name, monitorId) {
+ const trimmedName = `${name ?? ""}`.trim();
+ const currentMonitorId = monitorId ?? -1;
+ let minX = null;
+ let minY = null;
+ let maxX = null;
+ let maxY = null;
+
+ for (const addr in windowByAddress) {
+ const win = windowByAddress[addr];
+ if ((win?.monitor ?? -1) !== currentMonitorId)
+ continue;
+ if (root.specialWorkspaceName(win) !== trimmedName)
+ continue;
+
+ const atX = win?.at?.[0];
+ const atY = win?.at?.[1];
+ const width = win?.size?.[0];
+ const height = win?.size?.[1];
+ if (!Number.isFinite(atX) || !Number.isFinite(atY))
+ continue;
+ if (!Number.isFinite(width) || !Number.isFinite(height))
+ continue;
+
+ minX = minX === null ? atX : Math.min(minX, atX);
+ minY = minY === null ? atY : Math.min(minY, atY);
+ maxX = maxX === null ? (atX + width) : Math.max(maxX, atX + width);
+ maxY = maxY === null ? (atY + height) : Math.max(maxY, atY + height);
+ }
+
+ return {
+ x: minX,
+ y: minY,
+ width: (minX !== null && maxX !== null) ? Math.max(1, maxX - minX) : null,
+ height: (minY !== null && maxY !== null) ? Math.max(1, maxY - minY) : null
+ };
+ }
+
+ // Calculate which rows have windows or current workspace
+ property var rowsWithContent: {
+ if (!Config.options.overview.hideEmptyRows) return null;
+
+ let rows = new Set();
+ const firstWorkspace = root.workspaceGroup * root.workspacesShown + 1 + workspaceOffset;
+ const lastWorkspace = (root.workspaceGroup + 1) * root.workspacesShown + workspaceOffset;
+
+ // Add row containing current workspace
+ const currentWorkspace = effectiveActiveWorkspaceId;
+ if (currentWorkspace >= firstWorkspace && currentWorkspace <= lastWorkspace) {
+ rows.add(getWorkspaceRow(currentWorkspace));
+ }
+
+ // Add rows with windows
+ for (let addr in windowByAddress) {
+ const win = windowByAddress[addr];
+ const wsId = win?.workspace?.id;
+ if (wsId >= firstWorkspace && wsId <= lastWorkspace) {
+ const rowIndex = getWorkspaceRow(wsId);
+ rows.add(rowIndex);
+ }
+ }
+
+ return rows;
+ }
+
+ implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2
+ implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2
+
+ property Component windowComponent: OverviewWindow {}
+ property list windowWidgets: []
+
+ Connections {
+ target: Hyprland
+ function onRawEvent(event) {
+ if (!GlobalStates.overviewOpen || !root.useEventPreviewRefresh)
+ return;
+
+ const eventName = `${event?.name ?? event?.event ?? event?.type ?? ""}`;
+ if (eventName === "closewindow" || eventName === "openwindow" || eventName === "movewindow") {
+ root.previewRecaptureToken += 1;
+ }
+ }
+ }
+
+ StyledRectangularShadow {
+ target: overviewBackground
+ }
+ Rectangle { // Background
+ id: overviewBackground
+ property real padding: Config.options.overview.backgroundPadding
+ anchors.fill: parent
+ anchors.margins: Appearance.sizes.elevationMargin
+
+ implicitWidth: contentLayout.implicitWidth + padding * 2
+ implicitHeight: contentLayout.implicitHeight + padding * 2
+ radius: Appearance.rounding.screenRounding * root.scale + (Appearance.rounding.screenRounding !== 0 ? padding : 0)
+ clip: true
+ color: ColorUtils.applyAlpha(
+ root.glassMode
+ ? ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colLayer1, 0.78 - root.glassTintStrength * 0.35)
+ : Appearance.colors.colLayer0,
+ root.effectivePanelOpacity
+ )
+ border.width: 1
+ border.color: ColorUtils.applyAlpha(
+ root.glassMode
+ ? ColorUtils.mix(Appearance.colors.colLayer0Border, Appearance.m3colors.m3outline, 0.52)
+ : Appearance.colors.colLayer0Border,
+ root.glassMode ? root.glassBorderOpacity : root.effectivePanelOpacity
+ )
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
+ onPressed: mouse => mouse.accepted = true
+ }
+
+ Rectangle {
+ visible: root.glassMode
+ anchors.fill: parent
+ radius: parent.radius
+ color: "transparent"
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: ColorUtils.applyAlpha("#FFFFFF", root.glassShineOpacity * 0.35) }
+ GradientStop { position: 0.42; color: ColorUtils.applyAlpha("#FFFFFF", 0.0) }
+ GradientStop { position: 1.0; color: ColorUtils.applyAlpha("#000000", root.glassShineOpacity * 0.22) }
+ }
+ }
+
+ Rectangle {
+ visible: root.glassMode
+ anchors.fill: parent
+ anchors.margins: 1
+ radius: Math.max(parent.radius - 1, 0)
+ color: "transparent"
+ border.width: 1
+ border.color: ColorUtils.applyAlpha("#FFFFFF", root.glassBorderOpacity * 0.20)
+ }
+
+ ColumnLayout { // Workspaces
+ id: contentLayout
+
+ z: root.workspaceZ
+ anchors.centerIn: parent
+ spacing: workspaceSpacing
+ ColumnLayout {
+ id: workspaceColumnLayout
+ spacing: workspaceSpacing
+
+ Repeater {
+ model: Config.options.overview.rows
+ delegate: RowLayout {
+ id: row
+ property int rowIndex: index
+ spacing: workspaceSpacing
+ visible: !Config.options.overview.hideEmptyRows ||
+ (root.rowsWithContent && root.rowsWithContent.has(rowIndex))
+ height: visible ? implicitHeight : 0
+
+ Repeater { // Workspace repeater
+ model: Config.options.overview.columns
+ Rectangle { // Workspace
+ id: workspace
+ property int colIndex: index
+ property int workspaceValue: root.getWorkspaceInCell(rowIndex, colIndex)
+ property bool showWallpaper: root.hasEmptyWorkspaceWallpaper
+ property color defaultWorkspaceColor: Appearance.colors.colLayer1
+ property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1)
+ property color hoveredBorderColor: Appearance.colors.colLayer2Hover
+ property bool hoveredWhileDragging: false
+
+ implicitWidth: root.workspaceImplicitWidth
+ implicitHeight: root.workspaceImplicitHeight
+ color: showWallpaper ? "transparent" : ColorUtils.applyAlpha(
+ root.glassMode
+ ? ColorUtils.mix(hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor, Appearance.colors.colLayer0, 0.46)
+ : (hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor),
+ root.effectiveWorkspaceOpacity
+ )
+ radius: Appearance.rounding.screenRounding * root.scale
+ border.width: 2
+ border.color: hoveredWhileDragging
+ ? ColorUtils.applyAlpha(hoveredBorderColor, root.glassMode ? root.glassBorderOpacity : 1)
+ : "transparent"
+
+ Image {
+ id: workspaceWallpaper
+ visible: workspace.showWallpaper
+ anchors.fill: parent
+ source: root.wallpaperSource(root.emptyWorkspaceWallpaperPath)
+ fillMode: Image.PreserveAspectCrop
+ asynchronous: true
+ cache: true
+ smooth: true
+ mipmap: true
+ layer.enabled: workspace.showWallpaper
+ layer.smooth: true
+ layer.effect: MultiEffect {
+ maskEnabled: true
+ maskSource: workspaceWallpaperMask
+ maskThresholdMin: 0.5
+ maskSpreadAtMin: 1.0
+ }
+ }
+
+ Item {
+ id: workspaceWallpaperMask
+ anchors.fill: parent
+ visible: false
+ layer.enabled: true
+ layer.smooth: true
+ Rectangle {
+ anchors.fill: parent
+ radius: workspace.radius
+ }
+ }
+
+ Rectangle {
+ visible: workspace.showWallpaper
+ anchors.fill: parent
+ radius: parent.radius
+ color: ColorUtils.applyAlpha(
+ root.glassMode
+ ? ColorUtils.mix(workspace.hoveredWhileDragging ? workspace.hoveredWorkspaceColor : workspace.defaultWorkspaceColor, Appearance.colors.colLayer0, 0.40)
+ : (workspace.hoveredWhileDragging ? workspace.hoveredWorkspaceColor : workspace.defaultWorkspaceColor),
+ workspace.hoveredWhileDragging
+ ? Math.min(0.28, root.emptyWorkspaceWallpaperOverlayOpacity + 0.08)
+ : root.emptyWorkspaceWallpaperOverlayOpacity
+ )
+ }
+
+ Rectangle {
+ visible: root.glassMode
+ anchors.fill: parent
+ radius: parent.radius
+ color: "transparent"
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: ColorUtils.applyAlpha("#FFFFFF", root.glassShineOpacity * 0.22) }
+ GradientStop { position: 0.46; color: ColorUtils.applyAlpha("#FFFFFF", 0.0) }
+ GradientStop { position: 1.0; color: ColorUtils.applyAlpha("#000000", root.glassShineOpacity * 0.14) }
+ }
+ }
+
+ Rectangle {
+ visible: root.glassMode
+ anchors.fill: parent
+ anchors.margins: 1
+ radius: Math.max(parent.radius - 1, 0)
+ color: "transparent"
+ border.width: 1
+ border.color: ColorUtils.applyAlpha("#FFFFFF", root.glassBorderOpacity * 0.16)
+ }
+
+ StyledText {
+ anchors.centerIn: parent
+ visible: !workspace.showWallpaper
+ text: workspaceValue
+ font {
+ pixelSize: root.workspaceNumberSize * root.scale
+ weight: Font.DemiBold
+ family: Appearance.font.family.expressive
+ }
+ color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, 0.8)
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ MouseArea {
+ id: workspaceArea
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton
+ onClicked: {
+ if (root.draggingTargetWorkspace === -1) {
+ GlobalStates.overviewOpen = false
+ if (Hyprland.usingLua) {
+ Hyprland.dispatch(`hl.dsp.focus({workspace = '${workspaceValue}'})`);
+ } else {
+ Hyprland.dispatch(`workspace ${workspaceValue}`)
+ }
+ }
+ }
+ }
+
+ DropArea {
+ anchors.fill: parent
+ onEntered: {
+ root.draggingTargetWorkspace = workspaceValue
+ root.draggingTargetSpecialWorkspace = ""
+ if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return;
+ hoveredWhileDragging = true
+ }
+ onExited: {
+ hoveredWhileDragging = false
+ if (root.draggingTargetWorkspace == workspaceValue) root.draggingTargetWorkspace = -1
+ }
+ }
+
+ }
+ }
+ }
+ }
+ }
+
+ Item {
+ visible: root.showSpecialWorkspaces
+ implicitWidth: 1
+ implicitHeight: root.specialStripGap
+ }
+
+ Item {
+ id: specialWorkspaceSection
+ visible: root.showSpecialWorkspaces
+ implicitWidth: root.specialSectionWidth
+ implicitHeight: root.specialStripHeight
+
+ Rectangle {
+ anchors.fill: parent
+ radius: Appearance.rounding.normal * root.scale
+ color: ColorUtils.applyAlpha(
+ root.glassMode
+ ? ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colLayer1, 0.70)
+ : Appearance.colors.colLayer1,
+ root.glassMode ? Math.min(0.74, root.effectivePanelOpacity) : root.effectiveWorkspaceOpacity
+ )
+ border.width: 1
+ border.color: ColorUtils.applyAlpha(Appearance.colors.colLayer2Border, root.glassMode ? root.glassBorderOpacity : 0.65)
+
+ Rectangle {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ height: Math.max(18 * root.scale, root.specialStripPadding + root.specialStripTitleHeight * 0.8)
+ radius: parent.radius
+ color: ColorUtils.applyAlpha(Appearance.colors.colSecondary, root.glassMode ? 0.12 : 0.08)
+ }
+
+ StyledText {
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.leftMargin: root.specialStripPadding
+ anchors.topMargin: root.specialStripPadding
+ text: "Special Workspaces"
+ font.family: Appearance.font.family.title
+ font.pixelSize: root.specialStripTitleHeight
+ font.weight: Font.DemiBold
+ color: ColorUtils.applyAlpha(Appearance.colors.colOnLayer1, 0.84)
+ }
+
+ Grid {
+ id: specialWorkspaceGrid
+ x: root.specialGridOffsetX
+ y: root.specialStripPadding + root.specialStripTitleHeight + root.specialStripTitleGap
+ width: root.specialGridUsedWidth
+ columns: root.effectiveSpecialColumns
+ rowSpacing: workspaceSpacing
+ columnSpacing: workspaceSpacing
+
+ Repeater {
+ model: root.visibleSpecialWorkspaces
+ delegate: Rectangle {
+ id: specialWorkspaceTile
+ required property string modelData
+ property string specialName: modelData
+ property var specialGeometry: root.specialWorkspaceGeometry(specialName, root.monitor?.id)
+ property color baseColor: ColorUtils.mix(Appearance.colors.colLayer1, Appearance.colors.colLayer0, 0.52)
+ property bool hasRenderableGeometry: Number.isFinite(specialGeometry?.width)
+ && Number.isFinite(specialGeometry?.height)
+ && specialGeometry.width > 0
+ && specialGeometry.height > 0
+ property bool showWallpaper: root.hasSpecialEmptyWorkspaceWallpaper
+ property real geometryWidth: hasRenderableGeometry ? specialGeometry.width : Math.max(1, root.workspaceImplicitWidth / root.scale)
+ property real geometryHeight: hasRenderableGeometry ? specialGeometry.height : Math.max(1, root.workspaceImplicitHeight / root.scale)
+ property real fitScale: hasRenderableGeometry ? Math.min(width / geometryWidth, height / geometryHeight) : root.scale
+ property real contentWidth: hasRenderableGeometry ? (geometryWidth * fitScale) : width
+ property real contentHeight: hasRenderableGeometry ? (geometryHeight * fitScale) : height
+ property real contentOffsetX: Math.max(0, (width - contentWidth) / 2)
+ property real contentOffsetY: Math.max(0, (height - contentHeight) / 2)
+ implicitWidth: root.specialWorkspaceTileWidth
+ implicitHeight: root.specialWorkspaceTileHeight
+ radius: Appearance.rounding.screenRounding * root.scale
+ clip: true
+ color: showWallpaper ? "transparent" : ColorUtils.applyAlpha(
+ root.glassMode
+ ? ColorUtils.mix(baseColor, Appearance.colors.colLayer0, 0.44)
+ : baseColor,
+ root.effectiveWorkspaceOpacity
+ )
+ border.width: 1
+ border.color: ColorUtils.applyAlpha(Appearance.colors.colLayer2Border, root.glassMode ? root.glassBorderOpacity : 0.75)
+
+ Image {
+ visible: specialWorkspaceTile.showWallpaper
+ anchors.fill: parent
+ source: root.wallpaperSource(root.specialEmptyWorkspaceWallpaperPath)
+ fillMode: Image.PreserveAspectCrop
+ asynchronous: true
+ cache: true
+ smooth: true
+ mipmap: true
+ }
+
+ Rectangle {
+ visible: specialWorkspaceTile.showWallpaper
+ anchors.fill: parent
+ radius: parent.radius
+ color: ColorUtils.applyAlpha(
+ root.glassMode
+ ? ColorUtils.mix(specialWorkspaceTile.baseColor, Appearance.colors.colLayer0, 0.40)
+ : specialWorkspaceTile.baseColor,
+ root.emptyWorkspaceWallpaperOverlayOpacity
+ )
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton
+ onClicked: {
+ if (root.draggingTargetWorkspace === -1 && !root.draggingTargetSpecialWorkspace) {
+ GlobalStates.overviewOpen = false;
+ if (Hyprland.usingLua) {
+ Hyprland.dispatch(`hl.dsp.workspace.toggle_special('${specialWorkspaceTile.specialName}')`);
+ } else {
+ Hyprland.dispatch(`togglespecialworkspace ${specialWorkspaceTile.specialName}`);
+ }
+ }
+ }
+ }
+
+ DropArea {
+ anchors.fill: parent
+ onEntered: {
+ root.draggingTargetWorkspace = -1;
+ root.draggingTargetSpecialWorkspace = specialWorkspaceTile.specialName;
+ }
+ onExited: {
+ if (root.draggingTargetSpecialWorkspace === specialWorkspaceTile.specialName)
+ root.draggingTargetSpecialWorkspace = "";
+ }
+ }
+
+ Item {
+ id: specialWorkspaceContent
+ x: specialWorkspaceTile.contentOffsetX
+ y: specialWorkspaceTile.contentOffsetY
+ width: specialWorkspaceTile.contentWidth
+ height: specialWorkspaceTile.contentHeight
+ clip: true
+
+ Repeater {
+ model: ScriptModel {
+ values: {
+ if (!specialWorkspaceTile.hasRenderableGeometry)
+ return [];
+ return ToplevelManager.toplevels.values.filter((toplevel) => {
+ const address = `0x${toplevel.HyprlandToplevel.address}`;
+ const win = windowByAddress[address];
+ if ((win?.monitor ?? -1) !== (root.monitor?.id ?? -1))
+ return false;
+ return root.specialWorkspaceName(win) === specialWorkspaceTile.specialName;
+ }).sort((a, b) => {
+ const addrA = `0x${a.HyprlandToplevel.address}`;
+ const addrB = `0x${b.HyprlandToplevel.address}`;
+ return addrA.localeCompare(addrB);
+ });
+ }
+ }
+ delegate: OverviewWindow {
+ id: specialWindow
+ required property var modelData
+ required property int index
+ property var address: `0x${modelData.HyprlandToplevel.address}`
+ property int monitorId: windowData?.monitor
+ property var monitor: HyprlandData.monitors.find(m => m.id === monitorId)
+ property Item homeParent: specialWorkspaceContent
+ windowData: windowByAddress[address]
+ toplevel: modelData
+ monitorData: monitor
+ widgetMonitorData: root.monitorData
+ scale: root.scale
+ availableWorkspaceWidth: specialWorkspaceContent.width
+ availableWorkspaceHeight: specialWorkspaceContent.height
+ positionBaseX: Number.isFinite(specialWorkspaceTile.specialGeometry?.x) ? specialWorkspaceTile.specialGeometry.x : ((monitor?.x ?? 0) + (monitor?.reserved?.[0] ?? 0))
+ positionBaseY: Number.isFinite(specialWorkspaceTile.specialGeometry?.y) ? specialWorkspaceTile.specialGeometry.y : ((monitor?.y ?? 0) + (monitor?.reserved?.[1] ?? 0))
+ geometryScaleX: specialWorkspaceTile.fitScale / root.scale
+ geometryScaleY: specialWorkspaceTile.fitScale / root.scale
+ xOffset: 0
+ yOffset: 0
+ widgetMonitorId: root.monitor.id
+ recaptureToken: root.previewRecaptureToken
+ restrictToWorkspace: false
+ animateSize: false
+ z: root.specialWindowZ(windowData)
+
+ function moveToDragLayer() {
+ const mapped = specialWindow.mapToItem(specialWindowDragLayer, 0, 0);
+ specialWindow.suspendPositionAnimation = true;
+ specialWindow.parent = specialWindowDragLayer;
+ specialWindow.x = mapped.x;
+ specialWindow.y = mapped.y;
+ specialWindow.z = root.windowDraggingZ + 1;
+ Qt.callLater(() => specialWindow.suspendPositionAnimation = false);
+ }
+
+ function returnToHomeParent() {
+ specialWindow.suspendPositionAnimation = true;
+ specialWindow.parent = homeParent;
+ specialWindow.z = root.specialWindowZ(windowData);
+ Qt.callLater(() => specialWindow.suspendPositionAnimation = false);
+ }
+
+ MouseArea {
+ id: specialDragArea
+ anchors.fill: parent
+ hoverEnabled: true
+ onEntered: hovered = true
+ onExited: hovered = false
+ acceptedButtons: Qt.LeftButton | Qt.MiddleButton
+ drag.target: parent
+ onPressed: (mouse) => {
+ root.draggingFromWorkspace = -1
+ root.draggingTargetSpecialWorkspace = ""
+ specialWindow.pressed = true
+ specialWindow.dragInProgress = true
+ specialWindow.Drag.source = specialWindow
+ specialWindow.Drag.hotSpot.x = mouse.x
+ specialWindow.Drag.hotSpot.y = mouse.y
+ specialWindow.moveToDragLayer()
+ specialWindow.Drag.active = true
+ }
+ onReleased: {
+ const targetWorkspace = root.draggingTargetWorkspace
+ const targetSpecialWorkspace = root.draggingTargetSpecialWorkspace
+ specialWindow.pressed = false
+ specialWindow.Drag.active = false
+ specialWindow.dragInProgress = false
+ root.draggingFromWorkspace = -1
+ root.draggingTargetWorkspace = -1
+ root.draggingTargetSpecialWorkspace = ""
+ if (targetSpecialWorkspace === root.createSpecialWorkspaceTarget) {
+ const createdName = root.nextSpecialWorkspaceName()
+ //INFO: From special TO NEW special
+ if (Hyprland.usingLua) {
+ Hyprland.dispatch(`hl.dsp.window.move({workspace = 'special:${createdName}', follow = false, window = 'address:${specialWindow.windowData?.address}'})`);
+ } else {
+ Hyprland.dispatch(`movetoworkspacesilent special:${createdName}, address:${specialWindow.windowData?.address}`)
+ }
+ specialWindow.returnToHomeParent()
+ specialWindow.x = specialWindow.initX
+ specialWindow.y = specialWindow.initY
+ }
+ else if (targetSpecialWorkspace && targetSpecialWorkspace !== specialWorkspaceTile.specialName) {
+ // Idk
+ if (Hyprland.usingLua) {
+ Hyprland.dispatch(`hl.dsp.window.move({workspace = 'special:${targetSpecialWorkspace}', follow = false, window = 'address:${specialWindow.windowData?.address}'})`);
+ } else {
+ Hyprland.dispatch(`movetoworkspacesilent special:${targetSpecialWorkspace}, address:${specialWindow.windowData?.address}`)
+ }
+ specialWindow.returnToHomeParent()
+ specialWindow.x = specialWindow.initX
+ specialWindow.y = specialWindow.initY
+ }
+ else if (targetWorkspace !== -1) {
+ //INFO: From Special TO Normal workspace
+ if (Hyprland.usingLua) {
+ Hyprland.dispatch(`hl.dsp.window.move({workspace = '${targetWorkspace}', follow = false, window = 'address:${specialWindow.windowData?.address}'})`);
+ } else {
+ Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace}, address:${specialWindow.windowData?.address}`)
+ }
+ specialWindow.returnToHomeParent()
+ specialWindow.x = specialWindow.initX
+ specialWindow.y = specialWindow.initY
+ }
+ else {
+ specialWindow.returnToHomeParent()
+ specialWindow.x = specialWindow.initX
+ specialWindow.y = specialWindow.initY
+ }
+ }
+ onClicked: (event) => {
+ if (!windowData)
+ return;
+ if (event.button === Qt.LeftButton) {
+ GlobalStates.overviewOpen = false;
+ if (Hyprland.usingLua) {
+ Hyprland.dispatch(`hl.dsp.focus({ window = 'address:${windowData.address}' })`);
+ } else {
+ Hyprland.dispatch(`focuswindow address:${windowData.address}`);
+ }
+ event.accepted = true;
+ } else if (event.button === Qt.MiddleButton) {
+ if (Hyprland.usingLua) {
+ Hyprland.dispatch(`hl.dsp.window.close('address:${windowData.address}')`);
+ } else {
+ Hyprland.dispatch(`closewindow address:${windowData.address}`);
+ }
+ event.accepted = true;
+ }
+ }
+
+ StyledToolTip {
+ extraVisibleCondition: false
+ alternativeVisibleCondition: specialDragArea.containsMouse && !specialWindow.Drag.active
+ text: `${windowData?.title ?? "Unknown"}\n[${windowData?.class ?? "unknown"}] ${windowData?.xwayland ? "[XWayland] " : ""}`
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ Rectangle {
+ id: createSpecialWorkspaceTile
+ property bool showWallpaper: root.hasSpecialEmptyWorkspaceWallpaper
+ implicitWidth: root.specialWorkspaceTileWidth
+ implicitHeight: root.specialWorkspaceTileHeight
+ radius: Appearance.rounding.screenRounding * root.scale
+ color: showWallpaper ? "transparent" : ColorUtils.applyAlpha(
+ root.glassMode
+ ? ColorUtils.mix(Appearance.colors.colSecondaryContainer, Appearance.colors.colLayer1, 0.58)
+ : ColorUtils.mix(Appearance.colors.colLayer2, Appearance.colors.colLayer1, 0.55),
+ root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget ? 0.90 : root.effectiveWorkspaceOpacity
+ )
+ border.width: 1
+ border.color: root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget
+ ? ColorUtils.applyAlpha(root.activeBorderColor, 0.96)
+ : ColorUtils.applyAlpha(Appearance.colors.colSecondary, 0.46)
+
+ Image {
+ visible: createSpecialWorkspaceTile.showWallpaper
+ anchors.fill: parent
+ source: root.wallpaperSource(root.specialEmptyWorkspaceWallpaperPath)
+ fillMode: Image.PreserveAspectCrop
+ asynchronous: true
+ cache: true
+ smooth: true
+ mipmap: true
+ }
+
+ Rectangle {
+ visible: createSpecialWorkspaceTile.showWallpaper
+ anchors.fill: parent
+ radius: parent.radius
+ color: ColorUtils.applyAlpha(
+ root.glassMode
+ ? ColorUtils.mix(Appearance.colors.colSecondaryContainer, Appearance.colors.colLayer0, 0.40)
+ : ColorUtils.mix(Appearance.colors.colLayer2, Appearance.colors.colLayer1, 0.55),
+ root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget
+ ? Math.min(0.28, root.emptyWorkspaceWallpaperOverlayOpacity + 0.08)
+ : root.emptyWorkspaceWallpaperOverlayOpacity
+ )
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ anchors.margins: 1
+ radius: Math.max(parent.radius - 1, 0)
+ color: "transparent"
+ border.width: 1
+ border.color: ColorUtils.applyAlpha("#FFFFFF", root.glassMode ? 0.12 : 0.08)
+ }
+
+ Column {
+ anchors.centerIn: parent
+ spacing: 0
+
+ StyledText {
+ anchors.horizontalCenter: parent.horizontalCenter
+ visible: !createSpecialWorkspaceTile.showWallpaper
+ text: root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget ? "Release" : "+"
+ font.family: Appearance.font.family.expressive
+ font.pixelSize: root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget
+ ? Appearance.font.pixelSize.larger * root.scale
+ : Appearance.font.pixelSize.huge * 1.25 * root.scale
+ font.weight: Font.DemiBold
+ color: ColorUtils.applyAlpha(Appearance.colors.colOnLayer1, 0.92)
+ horizontalAlignment: Text.AlignHCenter
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton
+ onClicked: {
+ const createdName = root.nextSpecialWorkspaceName();
+ GlobalStates.overviewOpen = false;
+ if (Hyprland.usingLua) {
+ Hyprland.dispatch(`hl.dsp.workspace.toggle_special('${createdName}')`);
+ } else {
+ Hyprland.dispatch(`togglespecialworkspace ${createdName}`);
+ }
+ }
+ }
+
+ DropArea {
+ anchors.fill: parent
+ onEntered: {
+ root.draggingTargetWorkspace = -1;
+ root.draggingTargetSpecialWorkspace = root.createSpecialWorkspaceTarget;
+ }
+ onExited: {
+ if (root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget)
+ root.draggingTargetSpecialWorkspace = "";
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Item { // Windows & focused workspace indicator
+ id: windowSpace
+ anchors.centerIn: parent
+ implicitWidth: contentLayout.implicitWidth
+ implicitHeight: contentLayout.implicitHeight
+
+ WheelHandler {
+ target: null
+ acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
+ onWheel: event => {
+ const deltaY = event.angleDelta.y;
+ if (!deltaY)
+ return;
+ root.stepWorkspace(deltaY > 0 ? -1 : 1);
+ event.accepted = true;
+ }
+ }
+
+ Repeater { // Window repeater
+ model: ScriptModel {
+ values: {
+ return ToplevelManager.toplevels.values.filter((toplevel) => {
+ const address = `0x${toplevel.HyprlandToplevel.address}`
+ var win = windowByAddress[address]
+ if (root.isSpecialWorkspace(win))
+ return false;
+ const minWorkspace = root.workspaceGroup * root.workspacesShown + 1 + workspaceOffset;
+ const maxWorkspace = (root.workspaceGroup + 1) * root.workspacesShown + workspaceOffset;
+ const inWorkspaceGroup = (minWorkspace <= win?.workspace?.id && win?.workspace?.id <= maxWorkspace)
+ return inWorkspaceGroup;
+ }).sort((a, b) => {
+ // Proper stacking order based on Hyprland's window properties
+ const addrA = `0x${a.HyprlandToplevel.address}`
+ const addrB = `0x${b.HyprlandToplevel.address}`
+ const winA = windowByAddress[addrA]
+ const winB = windowByAddress[addrB]
+
+ // 1. Pinned windows are always on top
+ if (winA?.pinned !== winB?.pinned) {
+ return winA?.pinned ? 1 : -1
+ }
+
+ // 2. Floating windows above tiled windows
+ if (winA?.floating !== winB?.floating) {
+ return winA?.floating ? 1 : -1
+ }
+
+ // 3. Within same category, sort by focus history
+ // Lower focusHistoryID = more recently focused = higher in stack
+ return (winB?.focusHistoryID ?? 0) - (winA?.focusHistoryID ?? 0)
+ })
+ }
+ }
+ delegate: OverviewWindow {
+ id: window
+ required property var modelData
+ required property int index
+ property int monitorId: windowData?.monitor
+ property var monitor: HyprlandData.monitors.find(m => m.id === monitorId)
+ property var address: `0x${modelData.HyprlandToplevel.address}`
+ windowData: windowByAddress[address]
+ toplevel: modelData
+ monitorData: monitor
+ widgetMonitorData: root.monitorData
+ scale: root.scale
+ availableWorkspaceWidth: root.workspaceImplicitWidth
+ availableWorkspaceHeight: root.workspaceImplicitHeight
+ widgetMonitorId: root.monitor.id
+ recaptureToken: root.previewRecaptureToken
+
+ property bool atInitPosition: (initX == x && initY == y)
+
+ property int workspaceColIndex: root.getWorkspaceColumn(windowData?.workspace.id)
+ property int workspaceRowIndex: root.getWorkspaceRow(windowData?.workspace.id)
+ property int visibleWorkspaceRowIndex: root.getVisibleRowPosition(workspaceRowIndex)
+ xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex
+ yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * visibleWorkspaceRowIndex
+
+ Timer {
+ id: updateWindowPosition
+ interval: Config.options.hacks.arbitraryRaceConditionDelay
+ repeat: false
+ running: false
+ onTriggered: {
+ window.x = Math.round(Math.max((windowData?.at[0] - (monitor?.x ?? 0) - (monitorData?.reserved?.[0] ?? 0)) * root.scale * window.widthRatio, 0) + xOffset)
+ window.y = Math.round(Math.max((windowData?.at[1] - (monitor?.y ?? 0) - (monitorData?.reserved?.[1] ?? 0)) * root.scale * window.heightRatio, 0) + yOffset)
+ }
+ }
+
+ z: atInitPosition ? (root.windowZ + index) : root.windowDraggingZ
+ Drag.hotSpot.x: targetWindowWidth / 2
+ Drag.hotSpot.y: targetWindowHeight / 2
+ MouseArea {
+ id: dragArea
+ anchors.fill: parent
+ hoverEnabled: true
+ onEntered: hovered = true
+ onExited: hovered = false
+ acceptedButtons: Qt.LeftButton | Qt.MiddleButton
+ drag.target: parent
+ onPressed: (mouse) => {
+ root.draggingFromWorkspace = windowData?.workspace.id
+ root.draggingTargetSpecialWorkspace = ""
+ window.pressed = true
+ window.Drag.active = true
+ window.Drag.source = window
+ window.Drag.hotSpot.x = mouse.x
+ window.Drag.hotSpot.y = mouse.y
+ }
+ onReleased: {
+ const targetWorkspace = root.draggingTargetWorkspace
+ const targetSpecialWorkspace = root.draggingTargetSpecialWorkspace
+ window.pressed = false
+ window.Drag.active = false
+ root.draggingFromWorkspace = -1
+ root.draggingTargetWorkspace = -1
+ root.draggingTargetSpecialWorkspace = ""
+ if (targetSpecialWorkspace === root.createSpecialWorkspaceTarget) {
+ const createdName = root.nextSpecialWorkspaceName()
+ //INFO: From normal TO special
+ if (Hyprland.usingLua) {
+ Hyprland.dispatch(`hl.dsp.window.move({workspace = 'special:${createdName}', follow = false, window = 'address:${window.windowData?.address}'})`);
+ } else {
+ Hyprland.dispatch(`movetoworkspacesilent special:${createdName}, address:${window.windowData?.address}`)
+ }
+ updateWindowPosition.restart()
+ }
+ else if (targetSpecialWorkspace && targetSpecialWorkspace !== root.specialWorkspaceName(windowData)) {
+ // Idk
+ if (Hyprland.usingLua) {
+ Hyprland.dispatch(`hl.dsp.window.move({workspace = 'special:${targetSpecialWorkspace}', follow = false, window = 'address:${window.windowData?.address}'})`);
+ } else {
+ Hyprland.dispatch(`movetoworkspacesilent special:${targetSpecialWorkspace}, address:${window.windowData?.address}`)
+ }
+ updateWindowPosition.restart()
+ }
+ else if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) {
+ //INFO: From normal TO normal
+ if (Hyprland.usingLua) {
+ Hyprland.dispatch(`hl.dsp.window.move({workspace = '${targetWorkspace}', follow = false, window = 'address:${window.windowData?.address}'})`);
+ } else {
+ Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace}, address:${window.windowData?.address}`)
+ }
+ updateWindowPosition.restart()
+ }
+ else {
+ window.x = window.initX
+ window.y = window.initY
+ }
+ }
+ onClicked: (event) => {
+ if (!windowData) return;
+
+ if (event.button === Qt.LeftButton) {
+ GlobalStates.overviewOpen = false
+ if (Hyprland.usingLua) {
+ Hyprland.dispatch(`hl.dsp.focus({ window = 'address:${windowData.address}' })`);
+ } else {
+ Hyprland.dispatch(`focuswindow address:${windowData.address}`)
+ }
+ event.accepted = true
+ } else if (event.button === Qt.MiddleButton) {
+ if (Hyprland.usingLua) {
+ Hyprland.dispatch(`hl.dsp.window.close('address:${windowData.address}')`);
+ } else {
+ Hyprland.dispatch(`closewindow address:${windowData.address}`)
+ }
+ event.accepted = true
+ }
+ }
+
+ StyledToolTip {
+ extraVisibleCondition: false
+ alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active
+ text: `${windowData?.title ?? "Unknown"}\n[${windowData?.class ?? "unknown"}] ${windowData?.xwayland ? "[XWayland] " : ""}`
+ }
+ }
+ }
+ }
+
+ Rectangle { // Focused workspace indicator
+ id: focusedWorkspaceIndicator
+ property int activeWorkspaceRowIndex: root.getWorkspaceRow(root.effectiveActiveWorkspaceId)
+ property int visibleActiveWorkspaceRowIndex: root.getVisibleRowPosition(activeWorkspaceRowIndex)
+ property int activeWorkspaceColIndex: root.getWorkspaceColumn(root.effectiveActiveWorkspaceId)
+ x: (root.workspaceImplicitWidth + workspaceSpacing) * activeWorkspaceColIndex
+ y: (root.workspaceImplicitHeight + workspaceSpacing) * visibleActiveWorkspaceRowIndex
+ z: root.windowDraggingZ - 1
+ width: root.workspaceImplicitWidth
+ height: root.workspaceImplicitHeight
+ color: "transparent"
+ radius: Appearance.rounding.screenRounding * root.scale
+ border.width: 2
+ border.color: root.activeBorderColor
+ Behavior on x {
+ animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+ Behavior on y {
+ animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
+ }
+ }
+ }
+
+ Item {
+ id: specialWindowDragLayer
+ anchors.fill: parent
+ z: root.windowDraggingZ + 1
+ }
+ }
+}
diff --git a/home/hypr/quickshell/modules/overview/OverviewWindow.qml b/home/hypr/quickshell/modules/overview/OverviewWindow.qml
new file mode 100644
index 0000000..3b2652a
--- /dev/null
+++ b/home/hypr/quickshell/modules/overview/OverviewWindow.qml
@@ -0,0 +1,264 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Effects
+import Quickshell
+import Quickshell.Wayland
+import "../../common"
+import "../../common/functions"
+import "../../services"
+
+Item { // Window
+ id: root
+ property var toplevel
+ property var windowData
+ property var monitorData
+ property var widgetMonitorData
+ property var scale
+ property var availableWorkspaceWidth
+ property var availableWorkspaceHeight
+ property real positionBaseX: (monitorData?.x ?? 0) + (monitorData?.reserved?.[0] ?? 0)
+ property real positionBaseY: (monitorData?.y ?? 0) + (monitorData?.reserved?.[1] ?? 0)
+ property int recaptureToken: 0
+ property bool restrictToWorkspace: true
+ property real widthRatio: {
+ if (!widgetMonitorData || !monitorData)
+ return 1;
+
+ const widgetWidth = (widgetMonitorData.transform % 2 === 1) ? (widgetMonitorData.height ?? 1) : (widgetMonitorData.width ?? 1);
+ const sourceWidth = (monitorData.transform % 2 === 1) ? (monitorData.height ?? 1) : (monitorData.width ?? 1);
+ const sourceScale = monitorData.scale ?? 1;
+ const widgetScale = widgetMonitorData.scale ?? 1;
+ return (widgetWidth * sourceScale) / (sourceWidth * widgetScale);
+ }
+ property real heightRatio: {
+ if (!widgetMonitorData || !monitorData)
+ return 1;
+
+ const widgetHeight = (widgetMonitorData.transform % 2 === 1) ? (widgetMonitorData.width ?? 1) : (widgetMonitorData.height ?? 1);
+ const sourceHeight = (monitorData.transform % 2 === 1) ? (monitorData.width ?? 1) : (monitorData.height ?? 1);
+ const sourceScale = monitorData.scale ?? 1;
+ const widgetScale = widgetMonitorData.scale ?? 1;
+ return (widgetHeight * sourceScale) / (sourceHeight * widgetScale);
+ }
+ property real initX: Math.max(((windowData?.at[0] ?? 0) - positionBaseX) * root.scale * geometryScaleX, 0) + xOffset
+ property real initY: Math.max(((windowData?.at[1] ?? 0) - positionBaseY) * root.scale * geometryScaleY, 0) + yOffset
+ property real xOffset: 0
+ property real yOffset: 0
+ property int widgetMonitorId: 0
+ property real geometryScaleX: widthRatio
+ property real geometryScaleY: heightRatio
+
+ property var targetWindowWidth: (windowData?.size[0] ?? 100) * scale * geometryScaleX
+ property var targetWindowHeight: (windowData?.size[1] ?? 100) * scale * geometryScaleY
+ property bool hovered: false
+ property bool pressed: false
+
+ property bool showIcons: Config.options.windowPreview.showIcons
+ property var iconToWindowRatio: Config.options.windowPreview.iconToWindowRatio
+ property var xwaylandIndicatorToIconRatio: Config.options.windowPreview.xwaylandIndicatorToIconRatio
+ property var iconToWindowRatioCompact: Config.options.windowPreview.iconToWindowRatioCompact
+ property bool cropToFill: Config.options.windowPreview.cropToFill
+ property bool previewsEnabled: Config.options.overview.previewsEnabled
+ property bool includeInactiveMonitorPreviews: Config.options.overview.includeInactiveMonitorPreviews
+ property int previewRecaptureDelayMs: Config.options.overview.previewRecaptureDelayMs
+ property real windowOverlayOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.windowOverlayOpacity))
+ property bool glassMode: Config.options.overview.effects.glassMode
+ property real glassShineOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.glassShineOpacity))
+ property real effectiveWindowOverlayOpacity: glassMode ? Math.min(windowOverlayOpacity, 0.10) : windowOverlayOpacity
+ property string previewModeRaw: Config.options.overview.previewMode
+ property string previewMode: {
+ const mode = `${previewModeRaw ?? "live"}`.trim().toLowerCase();
+ return (mode === "event" || mode === "snapshot") ? "event" : "live";
+ }
+ property bool livePreviewEnabled: previewsEnabled && previewMode === "live"
+ property bool shouldCapturePreview: {
+ if (!GlobalStates.overviewOpen || !previewsEnabled || !previewCaptureEnabled)
+ return false;
+ if (includeInactiveMonitorPreviews)
+ return true;
+ return (windowData?.monitor ?? -1) === widgetMonitorId;
+ }
+ property var entry: {
+ DesktopEntries.applications.values; // re-run when the entry index updates
+ return DesktopEntries.heuristicLookup(windowData?.class);
+ }
+ property string iconName: {
+ const raw = `${entry?.icon ?? ""}`.trim();
+ const withoutProviderPrefix = raw.replace(/^image:\/\/icon\//, "");
+ const withoutQuery = withoutProviderPrefix.split("?")[0].trim();
+ return withoutQuery.length > 0 ? withoutQuery : "application-x-executable";
+ }
+ property var iconPath: Quickshell.iconPath(iconName, "image-missing")
+ property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth
+
+ property bool indicateXWayland: windowData?.xwayland ?? false
+ property bool previewCaptureEnabled: true
+ property bool initialized: false
+ property bool dragInProgress: false
+ property bool suspendPositionAnimation: false
+ property bool animateSize: true
+
+ x: initX
+ y: initY
+ width: Math.min(targetWindowWidth, availableWorkspaceWidth)
+ height: Math.min(targetWindowHeight, availableWorkspaceHeight)
+ opacity: (windowData?.monitor ?? -1) == widgetMonitorId ? 1 : Config.options.windowPreview.inactiveMonitorOpacity
+ visible: {
+ const thisWsId = windowData?.workspace?.id;
+ const isFullscreen = (windowData?.fullscreen ?? 0) > 0;
+ if (isFullscreen || thisWsId === undefined) return true;
+ return !HyprlandData.windowList.some(w => w.workspace?.id === thisWsId && (w.fullscreen ?? 0) > 0);
+ }
+
+ clip: true
+ Component.onCompleted: Qt.callLater(() => root.initialized = true)
+
+ Behavior on x {
+ enabled: root.initialized && !root.dragInProgress && !root.suspendPositionAnimation
+ animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
+ }
+ Behavior on y {
+ enabled: root.initialized && !root.dragInProgress && !root.suspendPositionAnimation
+ animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
+ }
+ Behavior on width {
+ enabled: root.initialized && root.animateSize && !root.dragInProgress && !root.suspendPositionAnimation
+ animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
+ }
+ Behavior on height {
+ enabled: root.initialized && root.animateSize && !root.dragInProgress && !root.suspendPositionAnimation
+ animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
+ }
+
+ // Opaque background for windows on the active monitor.
+ // The simplest solution for making those windows fully opaque and not interacting with actual
+ // windows behind the overview, e.g., applying blur to them.
+ Rectangle {
+ visible: (root.windowData?.monitor ?? -1) === root.widgetMonitorId
+ anchors.fill: parent
+ radius: Appearance.rounding.windowRounding * root.scale
+ color: root.glassMode
+ ? ColorUtils.mix(Appearance.colors.colLayer2, Appearance.colors.colLayer0, 0.38)
+ : Appearance.colors.colLayer2
+ }
+
+ ScreencopyView {
+ id: windowPreview
+ readonly property real srcAspect: {
+ const w = root.windowData?.size?.[0] ?? 0;
+ const h = root.windowData?.size?.[1] ?? 0;
+ return (w > 0 && h > 0) ? (w / h) : 1;
+ }
+ anchors.centerIn: parent
+ width: root.cropToFill
+ ? Math.max(parent.width, parent.height * srcAspect)
+ : Math.min(parent.width, parent.height * srcAspect)
+ height: root.cropToFill
+ ? Math.max(parent.height, parent.width / srcAspect)
+ : Math.min(parent.height, parent.width / srcAspect)
+ captureSource: shouldCapturePreview ? root.toplevel : null
+ live: livePreviewEnabled
+ layer.enabled: true
+ layer.smooth: true
+ layer.effect: MultiEffect {
+ maskEnabled: true
+ maskSource: previewMask
+ maskThresholdMin: 0.5
+ maskSpreadAtMin: 1.0
+ }
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ radius: Appearance.rounding.windowRounding * root.scale
+ color: pressed ? ColorUtils.applyAlpha(Appearance.colors.colLayer2Active, Math.min(1, root.effectiveWindowOverlayOpacity + 0.30)) :
+ hovered ? ColorUtils.applyAlpha(Appearance.colors.colLayer2Hover, Math.min(1, root.effectiveWindowOverlayOpacity + 0.20)) :
+ ColorUtils.applyAlpha(
+ root.glassMode ? ColorUtils.mix(Appearance.colors.colLayer2, Appearance.colors.colLayer0, 0.38) : Appearance.colors.colLayer2,
+ root.effectiveWindowOverlayOpacity
+ )
+ border.color: root.glassMode
+ ? ColorUtils.applyAlpha(Appearance.m3colors.m3outline, 0.62)
+ : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.7)
+ border.width: 1
+
+ Rectangle {
+ visible: root.glassMode
+ anchors.fill: parent
+ radius: parent.radius
+ color: "transparent"
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: ColorUtils.applyAlpha("#FFFFFF", root.glassShineOpacity * 0.24) }
+ GradientStop { position: 0.5; color: ColorUtils.applyAlpha("#FFFFFF", 0.0) }
+ GradientStop { position: 1.0; color: ColorUtils.applyAlpha("#000000", root.glassShineOpacity * 0.14) }
+ }
+ }
+
+ Rectangle {
+ visible: root.glassMode
+ anchors.fill: parent
+ anchors.margins: 1
+ radius: Math.max(parent.radius - 1, 0)
+ color: "transparent"
+ border.width: 1
+ border.color: ColorUtils.applyAlpha("#FFFFFF", root.glassShineOpacity * 0.32)
+ }
+
+ ColumnLayout {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: parent.left
+ anchors.right: parent.right
+ spacing: Appearance.font.pixelSize.smaller * 0.5
+
+ Image {
+ id: windowIcon
+ visible: root.showIcons
+ property var iconSize: {
+ const renderedSize = Math.min(root.width, root.height);
+ return renderedSize * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / (root.monitorData?.scale ?? 1);
+ }
+ Layout.alignment: Qt.AlignHCenter
+ source: root.iconPath
+ width: iconSize
+ height: iconSize
+ sourceSize: Qt.size(Math.max(1, Math.round(iconSize)), Math.max(1, Math.round(iconSize)))
+ }
+ }
+ }
+
+ Item {
+ id: previewMask
+ width: windowPreview.width
+ height: windowPreview.height
+ anchors.centerIn: parent
+ visible: false
+ layer.enabled: true
+ layer.smooth: true
+ Rectangle {
+ anchors.centerIn: parent
+ width: root.width
+ height: root.height
+ radius: Appearance.rounding.windowRounding * root.scale
+ }
+ }
+
+ function refreshCapture() {
+ if (!GlobalStates.overviewOpen || livePreviewEnabled || !previewsEnabled)
+ return;
+
+ root.previewCaptureEnabled = false;
+ previewResetTimer.restart();
+ }
+
+ Timer {
+ id: previewResetTimer
+ interval: Math.max(1, previewRecaptureDelayMs)
+ repeat: false
+ onTriggered: root.previewCaptureEnabled = true
+ }
+
+ onRecaptureTokenChanged: {
+ if (recaptureToken > 0)
+ root.refreshCapture();
+ }
+}
diff --git a/home/hypr/quickshell/modules/overview/qmldir b/home/hypr/quickshell/modules/overview/qmldir
new file mode 100644
index 0000000..9b15b45
--- /dev/null
+++ b/home/hypr/quickshell/modules/overview/qmldir
@@ -0,0 +1,3 @@
+Overview 1.0 Overview.qml
+OverviewWidget 1.0 OverviewWidget.qml
+OverviewWindow 1.0 OverviewWindow.qml
diff --git a/home/hypr/quickshell/qmldir b/home/hypr/quickshell/qmldir
deleted file mode 100644
index b82af0d..0000000
--- a/home/hypr/quickshell/qmldir
+++ /dev/null
@@ -1 +0,0 @@
-singleton GlobalState 1.0 GlobalState.qml
diff --git a/home/hypr/quickshell/quickshell-overview.qml b/home/hypr/quickshell/quickshell-overview.qml
new file mode 100644
index 0000000..95176b4
--- /dev/null
+++ b/home/hypr/quickshell/quickshell-overview.qml
@@ -0,0 +1,41 @@
+
+import QtQuick
+
+QtObject {
+ id: m3
+
+ property color m3primary: "{{colors.primary.default.hex}}"
+ property color m3onPrimary: "{{colors.on_primary.default.hex}}"
+
+ property color m3primaryContainer: "{{colors.primary_container.default.hex}}"
+ property color m3onPrimaryContainer: "{{colors.on_primary_container.default.hex}}"
+
+ property color m3secondary: "{{colors.secondary.default.hex}}"
+ property color m3onSecondary: "{{colors.on_secondary.default.hex}}"
+
+ property color m3secondaryContainer: "{{colors.secondary_container.default.hex}}"
+ property color m3onSecondaryContainer: "{{colors.on_secondary_container.default.hex}}"
+
+ property color m3background: "{{colors.background.default.hex}}"
+ property color m3onBackground: "{{colors.on_background.default.hex}}"
+
+ property color m3surface: "{{colors.surface.default.hex}}"
+
+ property color m3surfaceContainerLow: "{{colors.surface_container_low.default.hex}}"
+ property color m3surfaceContainer: "{{colors.surface_container.default.hex}}"
+ property color m3surfaceContainerHigh: "{{colors.surface_container_high.default.hex}}"
+ property color m3surfaceContainerHighest: "{{colors.surface_container_highest.default.hex}}"
+
+ property color m3onSurface: "{{colors.on_surface.default.hex}}"
+
+ property color m3surfaceVariant: "{{colors.surface_variant.default.hex}}"
+ property color m3onSurfaceVariant: "{{colors.on_surface_variant.default.hex}}"
+
+ property color m3inverseSurface: "{{colors.inverse_surface.default.hex}}"
+ property color m3inverseOnSurface: "{{colors.inverse_on_surface.default.hex}}"
+
+ property color m3outline: "{{colors.outline.default.hex}}"
+ property color m3outlineVariant: "{{colors.outline_variant.default.hex}}"
+
+ property color m3shadow: "#000000"
+}
diff --git a/home/hypr/quickshell/scripts/get_apps.sh b/home/hypr/quickshell/scripts/get_apps.sh
deleted file mode 100755
index a4b58bc..0000000
--- a/home/hypr/quickshell/scripts/get_apps.sh
+++ /dev/null
@@ -1,66 +0,0 @@
-#!/usr/bin/env bash
-# Scans .desktop files and outputs JSON array of apps
-dirs=(
- "/run/current-system/sw/share/applications"
- "$HOME/.nix-profile/share/applications"
- "$HOME/.local/share/applications"
- "/usr/share/applications"
-)
-
-declare -A seen
-apps="["
-first=true
-
-# Add default curated Mac/system apps first
-curated=(
- '{"name":"Finder","exec":"foot -e sh -c \"cd ~; lf || yazi || ls -la; exec zsh\"","icon":"finder","comment":"File Manager"}'
- '{"name":"Terminal","exec":"foot","icon":"terminal","comment":"Command Line Interface"}'
- '{"name":"Brave Browser","exec":"brave","icon":"browser","comment":"Web Browser"}'
- '{"name":"Neovim","exec":"foot -e nvim","icon":"code","comment":"Text & Code Editor"}'
- '{"name":"System Preferences","exec":"pavucontrol","icon":"preferences","comment":"Audio & System Settings"}'
- '{"name":"Activity Monitor","exec":"foot -e btop","icon":"preferences","comment":"Resource Monitor"}'
- '{"name":"Lock Screen","exec":"hyprlock","icon":"preferences","comment":"Lock current session"}'
- '{"name":"Restart System","exec":"systemctl reboot","icon":"preferences","comment":"Reboot NixOS"}'
- '{"name":"Shut Down","exec":"systemctl poweroff","icon":"preferences","comment":"Power off computer"}'
-)
-
-for app in "${curated[@]}"; do
- if [ "$first" = true ]; then
- apps="$apps$app"
- first=false
- else
- apps="$apps,$app"
- fi
-done
-
-for dir in "${dirs[@]}"; do
- if [ -d "$dir" ]; then
- for f in "$dir"/*.desktop; do
- [ -e "$f" ] || continue
- # Check NoDisplay
- if grep -q "^NoDisplay=true" "$f" 2>/dev/null; then
- continue
- fi
-
- name=$(grep -m 1 "^Name=" "$f" 2>/dev/null | cut -d= -f2- | tr -d '\r\n')
- exec_cmd=$(grep -m 1 "^Exec=" "$f" 2>/dev/null | cut -d= -f2- | sed -E 's/%[fFuUdDnNickvm]//g' | tr -d '\r\n')
- icon=$(grep -m 1 "^Icon=" "$f" 2>/dev/null | cut -d= -f2- | tr -d '\r\n')
- comment=$(grep -m 1 "^Comment=" "$f" 2>/dev/null | cut -d= -f2- | tr -d '\r\n')
-
- if [ -n "$name" ] && [ -n "$exec_cmd" ] && [ -z "${seen[$name]}" ]; then
- seen[$name]=1
- # Escape quotes
- name_esc=$(echo "$name" | sed 's/"/\\"/g')
- exec_esc=$(echo "$exec_cmd" | sed 's/"/\\"/g')
- icon_esc=$(echo "$icon" | sed 's/"/\\"/g')
- comment_esc=$(echo "$comment" | sed 's/"/\\"/g')
-
- entry="{\"name\":\"$name_esc\",\"exec\":\"$exec_esc\",\"icon\":\"$icon_esc\",\"comment\":\"$comment_esc\"}"
- apps="$apps,$entry"
- fi
- done
- fi
-done
-
-apps="$apps]"
-echo "$apps"
diff --git a/home/hypr/quickshell/scripts/get_audio.sh b/home/hypr/quickshell/scripts/get_audio.sh
deleted file mode 100755
index 31b41a3..0000000
--- a/home/hypr/quickshell/scripts/get_audio.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env bash
-# Read audio volume & mute state
-volume=50
-muted=false
-
-if command -v pactl &>/dev/null; then
- vol_str=$(pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null | grep -Po '[0-9]+(?=%)' | head -n 1)
- if [ -n "$vol_str" ]; then
- volume=$vol_str
- fi
- mute_str=$(pactl get-sink-mute @DEFAULT_SINK@ 2>/dev/null)
- if echo "$mute_str" | grep -q "yes"; then
- muted=true
- fi
-elif command -v pamixer &>/dev/null; then
- volume=$(pamixer --get-volume 2>/dev/null || echo 50)
- muted=$(pamixer --get-mute 2>/dev/null || echo false)
-fi
-
-echo "{\"volume\": $volume, \"muted\": $muted}"
diff --git a/home/hypr/quickshell/scripts/get_battery.sh b/home/hypr/quickshell/scripts/get_battery.sh
deleted file mode 100755
index 9cf8815..0000000
--- a/home/hypr/quickshell/scripts/get_battery.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/env bash
-# Read battery state
-bat_path=$(find /sys/class/power_supply/ -name "BAT*" 2>/dev/null | head -n 1)
-
-if [ -n "$bat_path" ] && [ -d "$bat_path" ]; then
- capacity=$(cat "$bat_path/capacity" 2>/dev/null || echo 100)
- status=$(cat "$bat_path/status" 2>/dev/null || echo "Full")
- is_charging=false
- if [ "$status" = "Charging" ] || [ "$status" = "Full" ]; then
- is_charging=true
- fi
- echo "{\"present\": true, \"percent\": $capacity, \"status\": \"$status\", \"charging\": $is_charging}"
-else
- # Desktop without battery
- echo "{\"present\": false, \"percent\": 100, \"status\": \"AC\", \"charging\": true}"
-fi
diff --git a/home/hypr/quickshell/scripts/get_brightness.sh b/home/hypr/quickshell/scripts/get_brightness.sh
deleted file mode 100755
index fe71a9b..0000000
--- a/home/hypr/quickshell/scripts/get_brightness.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/env bash
-# Read brightness
-percent=100
-
-if command -v brightnessctl &>/dev/null; then
- percent=$(brightnessctl -m 2>/dev/null | cut -d, -f4 | tr -d '%' | head -n 1)
- if [ -z "$percent" ]; then
- percent=100
- fi
-fi
-
-echo "{\"brightness\": ${percent:-100}}"
diff --git a/home/hypr/quickshell/scripts/get_cpu.sh b/home/hypr/quickshell/scripts/get_cpu.sh
deleted file mode 100755
index ed744af..0000000
--- a/home/hypr/quickshell/scripts/get_cpu.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env bash
-# Read /proc/stat CPU utilization
-read -r cpu u n s i io ir st g gn < /proc/stat
-total1=$((u + n + s + i + io + ir + st + g + gn))
-idle1=$((i + io))
-sleep 0.2
-read -r cpu u n s i io ir st g gn < /proc/stat
-total2=$((u + n + s + i + io + ir + st + g + gn))
-idle2=$((i + io))
-
-total_diff=$((total2 - total1))
-idle_diff=$((idle2 - idle1))
-
-if [ "$total_diff" -gt 0 ]; then
- cpu_usage=$(( (total_diff - idle_diff) * 100 / total_diff ))
-else
- cpu_usage=0
-fi
-
-echo "$cpu_usage"
diff --git a/home/hypr/quickshell/scripts/get_mem.sh b/home/hypr/quickshell/scripts/get_mem.sh
deleted file mode 100755
index 400a2c1..0000000
--- a/home/hypr/quickshell/scripts/get_mem.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/env bash
-# Read memory usage from /proc/meminfo
-mem_total=$(grep MemTotal /proc/meminfo | awk '{print $2}')
-mem_avail=$(grep MemAvailable /proc/meminfo | awk '{print $2}')
-
-if [ -n "$mem_total" ] && [ -n "$mem_avail" ] && [ "$mem_total" -gt 0 ]; then
- mem_used=$((mem_total - mem_avail))
- mem_percent=$((mem_used * 100 / mem_total))
- used_gb=$(awk "BEGIN {printf \"%.1f\", $mem_used/1048576}")
- total_gb=$(awk "BEGIN {printf \"%.1f\", $mem_total/1048576}")
- echo "{\"percent\": $mem_percent, \"used_gb\": \"$used_gb\", \"total_gb\": \"$total_gb\"}"
-else
- echo "{\"percent\": 0, \"used_gb\": \"0\", \"total_gb\": \"0\"}"
-fi
diff --git a/home/hypr/quickshell/scripts/get_wifi.sh b/home/hypr/quickshell/scripts/get_wifi.sh
deleted file mode 100755
index bbb9b3e..0000000
--- a/home/hypr/quickshell/scripts/get_wifi.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env bash
-# Read WiFi status
-ssid=""
-ip=""
-
-if command -v nmcli &>/dev/null; then
- active_line=$(nmcli -t -f active,ssid,signal dev wifi 2>/dev/null | grep '^yes' | head -n 1)
- if [ -n "$active_line" ]; then
- ssid=$(echo "$active_line" | cut -d':' -f2)
- signal=$(echo "$active_line" | cut -d':' -f3)
- fi
-fi
-
-if [ -z "$ssid" ]; then
- # Fallback to iwgetid or /proc/net/wireless
- if command -v iwgetid &>/dev/null; then
- ssid=$(iwgetid -r 2>/dev/null)
- fi
-fi
-
-# Get default IPv4
-ip=$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{print $7; exit}')
-
-if [ -n "$ssid" ]; then
- echo "{\"connected\": true, \"ssid\": \"$ssid\", \"ip\": \"${ip:-N/A}\"}"
-elif [ -n "$ip" ]; then
- echo "{\"connected\": true, \"ssid\": \"Ethernet\", \"ip\": \"$ip\"}"
-else
- echo "{\"connected\": false, \"ssid\": \"Not Connected\", \"ip\": \"None\"}"
-fi
diff --git a/home/hypr/quickshell/services/GlobalStates.qml b/home/hypr/quickshell/services/GlobalStates.qml
new file mode 100644
index 0000000..7644e38
--- /dev/null
+++ b/home/hypr/quickshell/services/GlobalStates.qml
@@ -0,0 +1,11 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import Quickshell
+
+Singleton {
+ id: root
+ property bool overviewOpen: false
+ property bool superReleaseMightTrigger: true
+}
diff --git a/home/hypr/quickshell/services/HyprlandData.qml b/home/hypr/quickshell/services/HyprlandData.qml
new file mode 100644
index 0000000..6127e98
--- /dev/null
+++ b/home/hypr/quickshell/services/HyprlandData.qml
@@ -0,0 +1,209 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import Quickshell
+import Quickshell.Io
+import Quickshell.Hyprland
+import "../common"
+
+/**
+ * Provides access to some Hyprland data not available in Quickshell.Hyprland.
+ */
+Singleton {
+ id: root
+ property var windowList: []
+ property var addresses: []
+ property var windowByAddress: ({})
+ property var workspaces: []
+ property var allWorkspaces: []
+ property var workspaceIds: []
+ property var workspaceById: ({})
+ property var activeWorkspace: null
+ property var monitors: []
+ property var layers: ({})
+ property bool pendingWindowsUpdate: false
+ property bool pendingMonitorsUpdate: false
+ property bool pendingLayersUpdate: false
+ property bool pendingWorkspacesUpdate: false
+ property bool pendingActiveWorkspaceUpdate: false
+
+ function updateWindowList() {
+ getClients.running = true;
+ }
+
+ function updateLayers() {
+ getLayers.running = true;
+ }
+
+ function updateMonitors() {
+ getMonitors.running = true;
+ }
+
+ function updateWorkspaces() {
+ getWorkspaces.running = true;
+ getActiveWorkspace.running = true;
+ }
+
+ function updateAll() {
+ scheduleUpdates(true, true, true, true, true);
+ }
+
+ function scheduleUpdates(windows, monitors, layers, workspaces, activeWorkspace) {
+ pendingWindowsUpdate = pendingWindowsUpdate || !!windows;
+ pendingMonitorsUpdate = pendingMonitorsUpdate || !!monitors;
+ pendingLayersUpdate = pendingLayersUpdate || !!layers;
+ pendingWorkspacesUpdate = pendingWorkspacesUpdate || !!workspaces;
+ pendingActiveWorkspaceUpdate = pendingActiveWorkspaceUpdate || !!activeWorkspace;
+
+ const debounceMs = Math.max(0, Config.options.hacks.hyprlandEventDebounceMs);
+ if (debounceMs === 0) {
+ flushPendingUpdates();
+ } else {
+ eventDebounceTimer.interval = debounceMs;
+ eventDebounceTimer.restart();
+ }
+ }
+
+ function flushPendingUpdates() {
+ if (pendingWindowsUpdate) {
+ pendingWindowsUpdate = false;
+ updateWindowList();
+ }
+ if (pendingMonitorsUpdate) {
+ pendingMonitorsUpdate = false;
+ updateMonitors();
+ }
+ if (pendingLayersUpdate) {
+ pendingLayersUpdate = false;
+ updateLayers();
+ }
+ if (pendingWorkspacesUpdate) {
+ pendingWorkspacesUpdate = false;
+ getWorkspaces.running = true;
+ }
+ if (pendingActiveWorkspaceUpdate) {
+ pendingActiveWorkspaceUpdate = false;
+ getActiveWorkspace.running = true;
+ }
+ }
+
+ function biggestWindowForWorkspace(workspaceId) {
+ const windowsInThisWorkspace = HyprlandData.windowList.filter(w => w.workspace.id == workspaceId);
+ return windowsInThisWorkspace.reduce((maxWin, win) => {
+ const maxArea = (maxWin?.size?.[0] ?? 0) * (maxWin?.size?.[1] ?? 0);
+ const winArea = (win?.size?.[0] ?? 0) * (win?.size?.[1] ?? 0);
+ return winArea > maxArea ? win : maxWin;
+ }, null);
+ }
+
+ Component.onCompleted: {
+ scheduleUpdates(true, true, true, true, true);
+ flushPendingUpdates();
+ }
+
+ Connections {
+ target: Hyprland
+
+ function onRawEvent(event) {
+ const eventName = `${event?.name ?? event?.event ?? event?.type ?? ""}`;
+ if (["openlayer", "closelayer", "screencast"].includes(eventName))
+ return;
+
+ if (eventName === "openwindow" || eventName === "closewindow" || eventName === "movewindow" || eventName === "movewindowv2" || eventName === "windowtitle") {
+ scheduleUpdates(true, false, false, true, false);
+ return;
+ }
+
+ if (eventName === "workspace" || eventName === "workspacev2" || eventName === "focusedmon" || eventName === "focusedmonv2" || eventName === "activewindow" || eventName === "activewindowv2") {
+ scheduleUpdates(false, false, false, true, true);
+ return;
+ }
+
+ if (eventName.startsWith("monitor") || eventName === "configreloaded") {
+ scheduleUpdates(true, true, false, true, true);
+ return;
+ }
+
+ scheduleUpdates(true, true, true, true, true);
+ }
+ }
+
+ Timer {
+ id: eventDebounceTimer
+ interval: Math.max(0, Config.options.hacks.hyprlandEventDebounceMs)
+ repeat: false
+ onTriggered: root.flushPendingUpdates()
+ }
+
+ Process {
+ id: getClients
+ command: ["hyprctl", "clients", "-j"]
+ stdout: StdioCollector {
+ id: clientsCollector
+ onStreamFinished: {
+ root.windowList = JSON.parse(clientsCollector.text);
+ let tempWinByAddress = {};
+ for (var i = 0; i < root.windowList.length; ++i) {
+ var win = root.windowList[i];
+ tempWinByAddress[win.address] = win;
+ }
+ root.windowByAddress = tempWinByAddress;
+ root.addresses = root.windowList.map(win => win.address);
+ }
+ }
+ }
+
+ Process {
+ id: getMonitors
+ command: ["hyprctl", "monitors", "-j"]
+ stdout: StdioCollector {
+ id: monitorsCollector
+ onStreamFinished: {
+ root.monitors = JSON.parse(monitorsCollector.text);
+ }
+ }
+ }
+
+ Process {
+ id: getLayers
+ command: ["hyprctl", "layers", "-j"]
+ stdout: StdioCollector {
+ id: layersCollector
+ onStreamFinished: {
+ root.layers = JSON.parse(layersCollector.text);
+ }
+ }
+ }
+
+ Process {
+ id: getWorkspaces
+ command: ["hyprctl", "workspaces", "-j"]
+ stdout: StdioCollector {
+ id: workspacesCollector
+ onStreamFinished: {
+ const rawWorkspaces = JSON.parse(workspacesCollector.text);
+ root.allWorkspaces = rawWorkspaces;
+ root.workspaces = rawWorkspaces.filter(ws => ws.id >= 1 && ws.id <= 100);
+ let tempWorkspaceById = {};
+ for (var i = 0; i < root.workspaces.length; ++i) {
+ var ws = root.workspaces[i];
+ tempWorkspaceById[ws.id] = ws;
+ }
+ root.workspaceById = tempWorkspaceById;
+ root.workspaceIds = root.workspaces.map(ws => ws.id);
+ }
+ }
+ }
+
+ Process {
+ id: getActiveWorkspace
+ command: ["hyprctl", "activeworkspace", "-j"]
+ stdout: StdioCollector {
+ id: activeWorkspaceCollector
+ onStreamFinished: {
+ root.activeWorkspace = JSON.parse(activeWorkspaceCollector.text);
+ }
+ }
+ }
+}
diff --git a/home/hypr/quickshell/services/qmldir b/home/hypr/quickshell/services/qmldir
new file mode 100644
index 0000000..b900864
--- /dev/null
+++ b/home/hypr/quickshell/services/qmldir
@@ -0,0 +1,2 @@
+singleton HyprlandData 1.0 HyprlandData.qml
+singleton GlobalStates 1.0 GlobalStates.qml
diff --git a/home/hypr/quickshell/shell.qml b/home/hypr/quickshell/shell.qml
index e2b5c93..7fc9e0e 100644
--- a/home/hypr/quickshell/shell.qml
+++ b/home/hypr/quickshell/shell.qml
@@ -1,84 +1,25 @@
+//@ pragma UseQApplication
+//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic
+//@ pragma Env QT_QPA_PLATFORMTHEME=
+
+import "./modules/overview/"
+import "./services/"
+import "./common/"
+import "./common/functions/"
+import "./common/widgets/"
+
import QtQuick
import Quickshell
-import Quickshell.Io
-import "theme"
-import "components"
-import "."
+import Quickshell.Hyprland
-Scope {
- id: root
+ShellRoot {
+ Connections {
+ target: Quickshell
- // =========================================================================
- // IPC HANDLERS (Allows controlling shell via `qs ipc call `)
- // =========================================================================
- IpcHandler {
- target: "spotlight"
- function toggle() {
- GlobalState.toggleSpotlight();
- }
- function open() {
- GlobalState.spotlightVisible = true;
- }
- function close() {
- GlobalState.spotlightVisible = false;
+ function onReloadCompleted() {
+ Quickshell.inhibitReloadPopup();
}
}
- IpcHandler {
- target: "systemHud"
- function toggle() {
- GlobalState.toggleSystemHud();
- }
- function open() {
- GlobalState.systemHudVisible = true;
- }
- function close() {
- GlobalState.systemHudVisible = false;
- }
- }
-
- IpcHandler {
- target: "systemInfo"
- function toggle() {
- GlobalState.toggleAboutMac();
- }
- }
-
- // =========================================================================
- // LEFT SIDEBAR (Primary monitor only)
- // =========================================================================
- SidePanel {
- screen: Quickshell.screens[0]
- }
-
- // =========================================================================
- // TOP STATUS STRIP (All monitors)
- // =========================================================================
- Variants {
- model: Quickshell.screens
- TopStrip {}
- }
-
- // =========================================================================
- // GLOBAL MODAL OVERLAYS
- // =========================================================================
- // Spotlight Search Overlay
- Spotlight {
- id: spotlightOverlay
- }
-
- // System HUD Flyout Panel
- SystemHUD {
- id: systemHudPanel
- }
-
- // Quick Menu (Power/Session Menu)
- QuickMenu {
- id: quickMenuOverlay
- }
-
- // System Info Dialog
- SystemInfo {
- id: systemInfoDialog
- }
+ Overview {}
}
diff --git a/home/hypr/quickshell/theme/Theme.qml b/home/hypr/quickshell/theme/Theme.qml
deleted file mode 100644
index 7d02f90..0000000
--- a/home/hypr/quickshell/theme/Theme.qml
+++ /dev/null
@@ -1,46 +0,0 @@
-pragma Singleton
-import QtQuick
-
-QtObject {
- // Colors
- readonly property color bgPrimary: "#131313"
- readonly property color bgSecondary: "#0e0e0e"
- readonly property color glassBg: "#1c1b1b"
- readonly property color glassBorder: Qt.rgba(1.0, 1.0, 1.0, 0.08)
- readonly property color glassBorderHover: Qt.rgba(1.0, 1.0, 1.0, 0.18)
- readonly property color accentVolt: "#CFFC00"
- readonly property color accentVoltGlow: Qt.rgba(0.812, 0.988, 0.0, 0.25)
- readonly property color accentVoltLight: "#e3ff66"
- readonly property color accentBlue: "#00BFFF"
- readonly property color accentCoral: "#FF7A59"
- readonly property color textPrimary: "#FFFFFF"
- readonly property color textSecondary: "#e5e2e1"
- readonly property color textMuted: "#ADAAAA"
- readonly property color textDisabled: Qt.rgba(1.0, 1.0, 1.0, 0.25)
-
- // Typography
- readonly property string fontSerif: "Instrument Serif, Georgia, serif"
- readonly property string fontSans: "IBM Plex Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif"
- readonly property string fontMono: "Nanum Gothic Coding, JetBrains Mono, monospace"
-
- readonly property int fontSizeSmall: 11
- readonly property int fontSizeNormal: 13
- readonly property int fontSizeLarge: 15
- readonly property int fontSizeTitle: 19
- readonly property int fontSizeDisplay: 24
- readonly property int fontSizeWorkspace: 12
-
- // Geometry
- readonly property int radiusNone: 0
- readonly property int sidebarWidth: 52
- readonly property int topStripHeight: 28
- readonly property int hudWidth: 360
- readonly property int iconSize: 36
- readonly property int iconSizeSmall: 20
- readonly property int workspaceIndicatorWidth: 2
-
- // Animation Durations
- readonly property int animSmooth: 400
- readonly property int animFast: 200
- readonly property int animBounce: 600
-}
diff --git a/home/hypr/quickshell/theme/qmldir b/home/hypr/quickshell/theme/qmldir
deleted file mode 100644
index d57fb86..0000000
--- a/home/hypr/quickshell/theme/qmldir
+++ /dev/null
@@ -1 +0,0 @@
-singleton Theme 1.0 Theme.qml
diff --git a/home/starship/starship.toml b/home/starship/starship.toml
index 2c52cc2..16beae3 100644
--- a/home/starship/starship.toml
+++ b/home/starship/starship.toml
@@ -7,48 +7,58 @@ format = "$os$directory$git_branch$git_status$nix_shell$rust$c$python$cmd_durati
[os]
disabled = false
style = "bold #e5e2e1"
-symbols = { NixOS = " " }
+symbols = { NixOS = " " }
[directory]
truncation_length = 3
truncate_to_repo = true
style = "bold #CFFC00"
-format = "[$path]($style) "
+format = "[ $path]($style) "
[git_branch]
-symbol = " "
+symbol = " "
style = "bold #00BFFF"
format = "on [$symbol$branch]($style) "
[git_status]
style = "bold #FF7A59"
format = "([$all_status$ahead_behind]($style) )"
+conflicted = " "
+ahead = " "
+behind = " "
+diverged = " "
+untracked = " "
+stashed = " "
+modified = " "
+staged = " [](bold #CFFC00)"
+renamed = " "
+deleted = " "
[nix_shell]
-symbol = " "
+symbol = " "
style = "bold #e3ff66"
format = "via [$symbol$state]($style) "
[rust]
-symbol = " "
+symbol = " "
style = "bold #e3ff66"
format = "[$symbol($version )]($style)"
[c]
-symbol = " "
+symbol = " "
style = "bold #00BFFF"
format = "[$symbol($version )]($style)"
[python]
-symbol = " "
+symbol = " "
style = "bold #CFFC00"
format = "[$symbol($version )]($style)"
[cmd_duration]
min_time = 2000
style = "bold #FF7A59"
-format = "took [$duration]($style) "
+format = "[ $duration]($style) "
[character]
-success_symbol = "[➜](bold #CFFC00) "
-error_symbol = "[➜](bold #FF7A59) "
+success_symbol = "[❯ ](bold #CFFC00) "
+error_symbol = "[❯ ](bold #FF7A59) "
diff --git a/hosts/hades/default.nix b/hosts/hades/default.nix
index e2e318c..5207bd4 100644
--- a/hosts/hades/default.nix
+++ b/hosts/hades/default.nix
@@ -28,7 +28,7 @@
output = "desc:Samsung Electric Company Odyssey Ark H1AK500000";
mode = "3840x2160@164.99Hz";
position = "auto";
- scale = 1.25;
+ scale = 1;
}
{
# Anything else plugged into hades: sensible automatic fallback
diff --git a/modules/apps/terminal.nix b/modules/apps/terminal.nix
index 64f812c..253f24c 100644
--- a/modules/apps/terminal.nix
+++ b/modules/apps/terminal.nix
@@ -1,7 +1,7 @@
{ pkgs, ... }:
{
- # Foot terminal is configured and managed via Home Manager (home/foot.nix)
+ # Terminal is managed elsewhere; system-level CLI helpers live here.
environment.systemPackages = with pkgs; [
zoxide
fzf