diff --git a/flake.lock b/flake.lock
index 00ffcb9..66e69d7 100644
--- a/flake.lock
+++ b/flake.lock
@@ -77,6 +77,27 @@
"type": "github"
}
},
+ "herdr": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ],
+ "rust-overlay": "rust-overlay"
+ },
+ "locked": {
+ "lastModified": 1787017031,
+ "narHash": "sha256-FeF4eZhxRl4jBbAkMaEzDCmMcaitV6GZapjaeeNZy7A=",
+ "owner": "herdrdev",
+ "repo": "herdr",
+ "rev": "2d24950ad9a02096921bc764e2e3dcd8900c3366",
+ "type": "github"
+ },
+ "original": {
+ "owner": "herdrdev",
+ "repo": "herdr",
+ "type": "github"
+ }
+ },
"home-manager": {
"inputs": {
"nixpkgs": [
@@ -193,15 +214,58 @@
"type": "github"
}
},
+ "quickshell": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1786864924,
+ "narHash": "sha256-9nqIJWuSlAwy4Vs98N9ffoVeU2dmsWcAJ7/DyIDihqM=",
+ "ref": "refs/heads/master",
+ "rev": "532a0606cafb05851a203e65be22c9a057447ee2",
+ "revCount": 844,
+ "type": "git",
+ "url": "https://git.outfoxxed.me/outfoxxed/quickshell"
+ },
+ "original": {
+ "type": "git",
+ "url": "https://git.outfoxxed.me/outfoxxed/quickshell"
+ }
+ },
"root": {
"inputs": {
"antigravity": "antigravity",
"comfy-ui": "comfy-ui",
"disko": "disko",
+ "herdr": "herdr",
"home-manager": "home-manager",
"nixos-hardware": "nixos-hardware",
"nixpkgs": "nixpkgs_3",
- "nixpkgs-unstable": "nixpkgs-unstable"
+ "nixpkgs-unstable": "nixpkgs-unstable",
+ "quickshell": "quickshell"
+ }
+ },
+ "rust-overlay": {
+ "inputs": {
+ "nixpkgs": [
+ "herdr",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1783577481,
+ "narHash": "sha256-EWMxOWrJ031f8f/lrcEmhTRs4npw1/5N3Hcjin846c8=",
+ "owner": "oxalica",
+ "repo": "rust-overlay",
+ "rev": "4cdea398dc491b082dccdce0fad1ed0b75fa3394",
+ "type": "github"
+ },
+ "original": {
+ "owner": "oxalica",
+ "repo": "rust-overlay",
+ "type": "github"
}
}
},
diff --git a/flake.nix b/flake.nix
index 01fe4ce..080d4f7 100644
--- a/flake.nix
+++ b/flake.nix
@@ -24,6 +24,16 @@
comfy-ui = {
url = "github:utensils/comfyui-nix";
};
+
+ quickshell = {
+ url = "git+https://git.outfoxxed.me/outfoxxed/quickshell";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+
+ herdr = {
+ url = "github:herdrdev/herdr";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
};
outputs =
diff --git a/home/foot.nix b/home/foot.nix
index c6c6a46..8669de2 100644
--- a/home/foot.nix
+++ b/home/foot.nix
@@ -1,11 +1,73 @@
-{ pkgs, ... }:
+{ pkgs, inputs, ... }:
+let
+ herdrPkg = if (inputs ? herdr && inputs.herdr ? packages && inputs.herdr.packages ? ${pkgs.stdenv.hostPlatform.system})
+ then inputs.herdr.packages.${pkgs.stdenv.hostPlatform.system}.default
+ else (pkgs.herdr or null);
+
+ herdrPicker = pkgs.writeShellScriptBin "herdr-picker" (builtins.readFile ./scripts/herdr-picker.sh);
+in
{
+ home.packages = (if herdrPkg != null then [ herdrPkg ] else []) ++ [
+ herdrPicker
+ ];
+
programs.foot = {
enable = true;
settings = {
main = {
- font = "JetBrainsMono Nerd Font:size=16";
+ font = "JetBrainsMono Nerd Font:size=15";
+ font-bold = "JetBrainsMono Nerd Font:size=15:weight=bold";
+ font-italic = "JetBrainsMono Nerd Font:size=15:slant=italic";
+ pad = "16x16 center";
+ shell = "${herdrPicker}/bin/herdr-picker";
+ selection-target = "clipboard";
+ };
+
+ scrollback = {
+ lines = 10000;
+ multiplier = 3.0;
+ };
+
+ cursor = {
+ style = "beam";
+ blink = "yes";
+ beam-thickness = 1.5;
+ };
+
+ mouse = {
+ hide-when-typing = "yes";
+ };
+
+ colors = {
+ alpha = 0.90;
+ background = "18181a";
+ foreground = "f2f2f7";
+
+ # macOS Catalina Apple System Dark Palette
+ regular0 = "1c1c1e"; # Black / Slate
+ regular1 = "ff453a"; # Apple Red
+ regular2 = "30d158"; # Apple Green
+ regular3 = "ffd60a"; # Apple Yellow
+ regular4 = "0a84ff"; # Apple Blue
+ regular5 = "bf5af2"; # Apple Purple
+ regular6 = "64d2ff"; # Apple Cyan
+ regular7 = "f2f2f7"; # Apple Light Gray
+
+ # Bright Colors
+ bright0 = "636366"; # Gray
+ bright1 = "ff6961"; # Bright Red
+ bright2 = "34c759"; # Bright Green
+ bright3 = "ffe066"; # Bright Yellow
+ bright4 = "409cff"; # Bright Blue
+ bright5 = "c774f4"; # Bright Purple
+ bright6 = "70d7ff"; # Bright Cyan
+ bright7 = "ffffff"; # Pure White
+
+ # Selection & URLs
+ selection-foreground = "ffffff";
+ selection-background = "0058d0"; # Apple selection blue
+ urls = "0a84ff";
};
};
};
diff --git a/home/hypr/default.nix b/home/hypr/default.nix
index c8e6321..e0475d8 100644
--- a/home/hypr/default.nix
+++ b/home/hypr/default.nix
@@ -5,7 +5,7 @@
./hyprland.nix
./hyprlock.nix
./hypridle.nix
- ./waybar.nix
+ ./quickshell.nix
];
home.packages = with pkgs; [
@@ -17,11 +17,8 @@
brightnessctl
# Desktop Environment / Hyprland ecosystem
- waybar
hyprpaper
- swaynotificationcenter
rofi
- wlogout
hyprpolkitagent
];
}
diff --git a/home/hypr/hyprland.nix b/home/hypr/hyprland.nix
index 616b2fe..6cb5102 100644
--- a/home/hypr/hyprland.nix
+++ b/home/hypr/hyprland.nix
@@ -178,9 +178,8 @@ in
_args = [
"hyprland.start"
(mkLua ''function()
- hl.exec_cmd("waybar")
+ hl.exec_cmd("quickshell")
hl.exec_cmd("hyprpaper")
- hl.exec_cmd("swaync")
hl.exec_cmd("${pkgs.hyprpolkitagent}/libexec/hyprpolkitagent")
end'')
];
@@ -194,9 +193,10 @@ 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 .. \" + N\"") (mkLua "hl.dsp.exec_cmd(\"swaync-client -t -sw\")") ]; }
+ { _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 .. \" + V\"") (mkLua "hl.dsp.window.float({ action = \"toggle\" })") ]; }
- { _args = [ (mkLua "mainMod .. \" + R\"") (mkLua "hl.dsp.exec_cmd(\"rofi -show combi -show-icons\")") ]; }
+ { _args = [ (mkLua "mainMod .. \" + R\"") (mkLua "hl.dsp.exec_cmd(\"quickshell ipc call spotlight toggle || rofi -show combi -show-icons\")") ]; }
{ _args = [ (mkLua "mainMod .. \" + G\"") (mkLua "hl.dsp.group.toggle()") ]; }
{ _args = [ (mkLua "mainMod .. \" + F\"") (mkLua "hl.dsp.window.fullscreen({ fullscreen = 0 })") ]; }
@@ -221,7 +221,7 @@ in
{ _args = [ (mkLua "mainMod .. \" + mouse_up\"") (mkLua "hl.dsp.focus({ workspace = \"e-1\" })") ]; }
# Lock shortcut
- { _args = [ (mkLua "mainMod .. \" + S\"") (mkLua "hl.dsp.exec_cmd(\"wlogout\")") ]; }
+ { _args = [ (mkLua "mainMod .. \" + S\"") (mkLua "hl.dsp.exec_cmd(\"hyprlock\")") ]; }
# Quick access applications
{ _args = [ (mkLua "mainMod .. \" + b\"") (mkLua "hl.dsp.exec_cmd(browser)") ]; }
diff --git a/home/hypr/quickshell.nix b/home/hypr/quickshell.nix
new file mode 100644
index 0000000..1330a8d
--- /dev/null
+++ b/home/hypr/quickshell.nix
@@ -0,0 +1,25 @@
+{ pkgs, inputs, ... }:
+
+let
+ quickshellPkg = if (inputs ? quickshell && inputs.quickshell ? packages && inputs.quickshell.packages ? ${pkgs.stdenv.hostPlatform.system})
+ then inputs.quickshell.packages.${pkgs.stdenv.hostPlatform.system}.default
+ else (pkgs.quickshell or pkgs.qt6.qtbase);
+in
+{
+ home.packages = with pkgs; [
+ quickshellPkg
+ brightnessctl
+ pamixer
+ playerctl
+ libnotify
+ grim
+ slurp
+ wl-clipboard
+ ];
+
+ # Link Quickshell configuration into ~/.config/quickshell
+ xdg.configFile."quickshell" = {
+ source = ./quickshell;
+ recursive = true;
+ };
+}
diff --git a/home/hypr/quickshell/GlobalState.qml b/home/hypr/quickshell/GlobalState.qml
new file mode 100644
index 0000000..d89822e
--- /dev/null
+++ b/home/hypr/quickshell/GlobalState.qml
@@ -0,0 +1,280 @@
+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 wifiSsid: "Wi-Fi"
+ property string ipAddress: ""
+
+ property bool batteryPresent: false
+ property int batteryPercent: 100
+ property bool batteryCharging: false
+ property string batteryStatus: "Full"
+
+ // 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;
+ }
+
+ function toggleSpotlight() {
+ closeAllPopups();
+ spotlightVisible = !spotlightVisible;
+ }
+
+ function toggleControlCenter() {
+ closeAllPopups();
+ controlCenterVisible = !controlCenterVisible;
+ }
+
+ function toggleAboutMac() {
+ closeAllPopups();
+ aboutMacVisible = !aboutMacVisible;
+ }
+
+ // Hyprland raw event processing
+ readonly property Connections hyprConn: Connections {
+ target: Hyprland
+ function onRawEvent(event) {
+ if (!event) return;
+ if (event.startsWith("activewindow>>")) {
+ const data = event.substring(14);
+ const parts = data.split(",");
+ const title = parts[1] ? parts[1].trim() : (parts[0] ? parts[0].trim() : "");
+ const cls = parts[0] ? parts[0].trim() : "Finder";
+
+ root.activeWindowClass = cls && cls.length > 0 ? cls : "Finder";
+ root.activeWindowTitle = title && title.length > 0 ? title : root.activeWindowClass;
+ } else if (event.startsWith("activewindowv2>>")) {
+ // Update running clients
+ clientsProc.running = true;
+ } else if (event.startsWith("openwindow>>") || event.startsWith("closewindow>>")) {
+ 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;
+ }
+ }
+
+ // 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(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 = 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 = 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 {
+ command: ["bash", "-c", "ssid=\"\"; if command -v nmcli &>/dev/null; then ssid=$(nmcli -t -f active,ssid dev wifi 2>/dev/null | grep '^yes:' | cut -d':' -f2); fi; [ -z \"$ssid\" ] && [ -f /proc/net/wireless ] && ssid=$(iwgetid -r 2>/dev/null); ip=$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{print $7; exit}'); echo \"${ssid:-Ethernet} ${ip:-Offline}\""]
+ stdout: StdioCollector {
+ onDataChanged: {
+ const str = data.trim();
+ const parts = str.split(" ");
+ if (parts.length >= 2) {
+ root.wifiSsid = parts[0];
+ root.ipAddress = parts[1];
+ root.wifiConnected = parts[1] !== "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 = 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(data.trim());
+ if (!isNaN(val)) root.brightness = val;
+ }
+ }
+ }
+
+ 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 = 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 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(data.trim());
+ if (Array.isArray(parsed)) {
+ root.runningApps = parsed;
+ }
+ } catch(e) {}
+ }
+ }
+ }
+
+ // Helper functions to control system
+ function setVolume(val) {
+ root.volume = Math.max(0, Math.min(100, val));
+ Process.exec(["pactl", "set-sink-volume", "@DEFAULT_SINK@", `${root.volume}%`]);
+ }
+
+ function toggleVolumeMute() {
+ Process.exec(["pactl", "set-sink-mute", "@DEFAULT_SINK@", "toggle"]);
+ root.volumeMuted = !root.volumeMuted;
+ }
+
+ function setBrightness(val) {
+ root.brightness = Math.max(1, Math.min(100, val));
+ Process.exec(["brightnessctl", "set", `${root.brightness}%`]);
+ }
+
+ function mediaPlayPause() {
+ Process.exec(["playerctl", "play-pause"]);
+ mprisProc.running = true;
+ }
+
+ function mediaNext() {
+ Process.exec(["playerctl", "next"]);
+ mprisProc.running = true;
+ }
+
+ function mediaPrevious() {
+ Process.exec(["playerctl", "previous"]);
+ mprisProc.running = true;
+ }
+
+ function launchApp(execCmd) {
+ if (!execCmd) return;
+ Process.exec(["bash", "-c", `${execCmd} &`]);
+ closeAllPopups();
+ spotlightVisible = false;
+ }
+}
diff --git a/home/hypr/quickshell/assets/apple.svg b/home/hypr/quickshell/assets/apple.svg
new file mode 100644
index 0000000..9e8f16b
--- /dev/null
+++ b/home/hypr/quickshell/assets/apple.svg
@@ -0,0 +1,3 @@
+
diff --git a/home/hypr/quickshell/assets/appstore.svg b/home/hypr/quickshell/assets/appstore.svg
new file mode 100644
index 0000000..2163f9e
--- /dev/null
+++ b/home/hypr/quickshell/assets/appstore.svg
@@ -0,0 +1,20 @@
+
diff --git a/home/hypr/quickshell/assets/battery.svg b/home/hypr/quickshell/assets/battery.svg
new file mode 100644
index 0000000..aac7553
--- /dev/null
+++ b/home/hypr/quickshell/assets/battery.svg
@@ -0,0 +1,8 @@
+
diff --git a/home/hypr/quickshell/assets/bluetooth.svg b/home/hypr/quickshell/assets/bluetooth.svg
new file mode 100644
index 0000000..de77291
--- /dev/null
+++ b/home/hypr/quickshell/assets/bluetooth.svg
@@ -0,0 +1,3 @@
+
diff --git a/home/hypr/quickshell/assets/brightness.svg b/home/hypr/quickshell/assets/brightness.svg
new file mode 100644
index 0000000..89bf9f7
--- /dev/null
+++ b/home/hypr/quickshell/assets/brightness.svg
@@ -0,0 +1,4 @@
+
diff --git a/home/hypr/quickshell/assets/browser.svg b/home/hypr/quickshell/assets/browser.svg
new file mode 100644
index 0000000..2b3068c
--- /dev/null
+++ b/home/hypr/quickshell/assets/browser.svg
@@ -0,0 +1,26 @@
+
diff --git a/home/hypr/quickshell/assets/catalina-badge.svg b/home/hypr/quickshell/assets/catalina-badge.svg
new file mode 100644
index 0000000..0443b9b
--- /dev/null
+++ b/home/hypr/quickshell/assets/catalina-badge.svg
@@ -0,0 +1,40 @@
+
diff --git a/home/hypr/quickshell/assets/code.svg b/home/hypr/quickshell/assets/code.svg
new file mode 100644
index 0000000..e673e1f
--- /dev/null
+++ b/home/hypr/quickshell/assets/code.svg
@@ -0,0 +1,23 @@
+
diff --git a/home/hypr/quickshell/assets/controlcenter.svg b/home/hypr/quickshell/assets/controlcenter.svg
new file mode 100644
index 0000000..fa4fdb2
--- /dev/null
+++ b/home/hypr/quickshell/assets/controlcenter.svg
@@ -0,0 +1,8 @@
+
diff --git a/home/hypr/quickshell/assets/finder.svg b/home/hypr/quickshell/assets/finder.svg
new file mode 100644
index 0000000..7b85c20
--- /dev/null
+++ b/home/hypr/quickshell/assets/finder.svg
@@ -0,0 +1,37 @@
+
diff --git a/home/hypr/quickshell/assets/folder-downloads.svg b/home/hypr/quickshell/assets/folder-downloads.svg
new file mode 100644
index 0000000..6009be3
--- /dev/null
+++ b/home/hypr/quickshell/assets/folder-downloads.svg
@@ -0,0 +1,25 @@
+
diff --git a/home/hypr/quickshell/assets/launchpad.svg b/home/hypr/quickshell/assets/launchpad.svg
new file mode 100644
index 0000000..a987dc0
--- /dev/null
+++ b/home/hypr/quickshell/assets/launchpad.svg
@@ -0,0 +1,35 @@
+
diff --git a/home/hypr/quickshell/assets/music.svg b/home/hypr/quickshell/assets/music.svg
new file mode 100644
index 0000000..55481f0
--- /dev/null
+++ b/home/hypr/quickshell/assets/music.svg
@@ -0,0 +1,21 @@
+
diff --git a/home/hypr/quickshell/assets/preferences.svg b/home/hypr/quickshell/assets/preferences.svg
new file mode 100644
index 0000000..b382a48
--- /dev/null
+++ b/home/hypr/quickshell/assets/preferences.svg
@@ -0,0 +1,44 @@
+
diff --git a/home/hypr/quickshell/assets/spotlight.svg b/home/hypr/quickshell/assets/spotlight.svg
new file mode 100644
index 0000000..fc316b6
--- /dev/null
+++ b/home/hypr/quickshell/assets/spotlight.svg
@@ -0,0 +1,3 @@
+
diff --git a/home/hypr/quickshell/assets/terminal.svg b/home/hypr/quickshell/assets/terminal.svg
new file mode 100644
index 0000000..934b9e2
--- /dev/null
+++ b/home/hypr/quickshell/assets/terminal.svg
@@ -0,0 +1,21 @@
+
diff --git a/home/hypr/quickshell/assets/trash-empty.svg b/home/hypr/quickshell/assets/trash-empty.svg
new file mode 100644
index 0000000..01fd97b
--- /dev/null
+++ b/home/hypr/quickshell/assets/trash-empty.svg
@@ -0,0 +1,23 @@
+
diff --git a/home/hypr/quickshell/assets/trash-full.svg b/home/hypr/quickshell/assets/trash-full.svg
new file mode 100644
index 0000000..179071a
--- /dev/null
+++ b/home/hypr/quickshell/assets/trash-full.svg
@@ -0,0 +1,24 @@
+
diff --git a/home/hypr/quickshell/assets/volume.svg b/home/hypr/quickshell/assets/volume.svg
new file mode 100644
index 0000000..1c44722
--- /dev/null
+++ b/home/hypr/quickshell/assets/volume.svg
@@ -0,0 +1,3 @@
+
diff --git a/home/hypr/quickshell/assets/wifi.svg b/home/hypr/quickshell/assets/wifi.svg
new file mode 100644
index 0000000..886b971
--- /dev/null
+++ b/home/hypr/quickshell/assets/wifi.svg
@@ -0,0 +1,3 @@
+
diff --git a/home/hypr/quickshell/components/AboutMac.qml b/home/hypr/quickshell/components/AboutMac.qml
new file mode 100644
index 0000000..935b9fe
--- /dev/null
+++ b/home/hypr/quickshell/components/AboutMac.qml
@@ -0,0 +1,260 @@
+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
new file mode 100644
index 0000000..ee24792
--- /dev/null
+++ b/home/hypr/quickshell/components/AppMenuDropdown.qml
@@ -0,0 +1,147 @@
+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 killactive" },
+ { 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 fullscreen 0" },
+ { text: "Toggle Floating", shortcut: "⌘V", cmd: "hyprctl dispatch togglefloating" },
+ { text: "Toggle Group", shortcut: "⌘G", cmd: "hyprctl dispatch togglegroup" },
+ { 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 movetoworkspacesilent special:scratch" },
+ { text: "Tile Left", shortcut: "⌃⌥←", cmd: "hyprctl dispatch movewindow l" },
+ { text: "Tile Right", shortcut: "⌃⌥→", cmd: "hyprctl dispatch movewindow r" },
+ { text: "---", shortcut: "", cmd: "" },
+ { text: "Show Scratchpad", shortcut: "⌘↩", cmd: "hyprctl dispatch togglespecialworkspace scratch" },
+ { text: "Show Messaging", shortcut: "⌘M", cmd: "hyprctl dispatch togglespecialworkspace 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
new file mode 100644
index 0000000..5b62652
--- /dev/null
+++ b/home/hypr/quickshell/components/AppleMenu.qml
@@ -0,0 +1,158 @@
+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("hyprctl dispatch exit");
+ }
+ }
+ }
+
+ 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
new file mode 100644
index 0000000..daae340
--- /dev/null
+++ b/home/hypr/quickshell/components/BatteryPopup.qml
@@ -0,0 +1,102 @@
+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
new file mode 100644
index 0000000..2ec1588
--- /dev/null
+++ b/home/hypr/quickshell/components/CalendarPopup.qml
@@ -0,0 +1,94 @@
+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
new file mode 100644
index 0000000..a204557
--- /dev/null
+++ b/home/hypr/quickshell/components/ControlCenter.qml
@@ -0,0 +1,638 @@
+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
new file mode 100644
index 0000000..50d3343
--- /dev/null
+++ b/home/hypr/quickshell/components/Dock.qml
@@ -0,0 +1,338 @@
+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(`focuswindow 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
new file mode 100644
index 0000000..ee1eb8c
--- /dev/null
+++ b/home/hypr/quickshell/components/MenuBar.qml
@@ -0,0 +1,507 @@
+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/Spotlight.qml b/home/hypr/quickshell/components/Spotlight.qml
new file mode 100644
index 0000000..39c914b
--- /dev/null
+++ b/home/hypr/quickshell/components/Spotlight.qml
@@ -0,0 +1,340 @@
+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.35)
+ 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 {
+ command: ["bash", "-c", "dirs=(\"/run/current-system/sw/share/applications\" \"$HOME/.nix-profile/share/applications\" \"$HOME/.local/share/applications\"); 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; echo ']'"]
+ stdout: StdioCollector {
+ onDataChanged: {
+ try {
+ const parsed = JSON.parse(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
+ Rectangle {
+ id: modalBox
+ anchors.horizontalCenter: parent.horizontalCenter
+ y: 140
+ width: 640
+ implicitHeight: searchBarRow.height + (resultsListView.count > 0 || mathResultText !== "" ? 340 : 0)
+ radius: Theme.radiusModal
+ color: Theme.windowBg
+ border.color: Theme.windowBorder
+ 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
+ anchors.margins: 14
+ spacing: 12
+
+ // Top Search Bar
+ RowLayout {
+ id: searchBarRow
+ Layout.fillWidth: true
+ Layout.preferredHeight: 44
+ spacing: 12
+
+ Image {
+ width: 24
+ height: 24
+ source: "../assets/spotlight.svg"
+ sourceSize: Qt.size(24, 24)
+ }
+
+ TextInput {
+ id: searchInput
+ Layout.fillWidth: true
+ font.family: Theme.fontFamily
+ font.pixelSize: Theme.fontSizeSpotlight
+ font.weight: Font.Light
+ color: Theme.textPrimary
+ focus: true
+ clip: true
+
+ Text {
+ anchors.fill: parent
+ visible: searchInput.text === ""
+ text: "Spotlight Search"
+ font.family: Theme.fontFamily
+ font.pixelSize: Theme.fontSizeSpotlight
+ font.weight: Font.Light
+ color: Theme.textTertiary
+ }
+
+ 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: 20
+ height: 20
+ radius: 10
+ color: Qt.rgba(1.0, 1.0, 1.0, 0.2)
+
+ Text {
+ anchors.centerIn: parent
+ text: "✕"
+ font.pixelSize: 11
+ color: Theme.textPrimary
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ onClicked: searchInput.text = ""
+ }
+ }
+ }
+
+ // Math Calculation Result Banner (if valid formula)
+ Rectangle {
+ visible: mathResultText !== ""
+ Layout.fillWidth: true
+ Layout.preferredHeight: 36
+ radius: Theme.radiusSmall
+ color: Qt.rgba(0.04, 0.52, 1.0, 0.2)
+ border.color: Theme.accentBlue
+ border.width: 1
+
+ RowLayout {
+ anchors.fill: parent
+ anchors.margins: 8
+ spacing: 8
+
+ Text {
+ text: "="
+ font.family: Theme.fontFamily
+ font.pixelSize: 16
+ font.weight: Font.Bold
+ color: Theme.accentBlue
+ }
+ Text {
+ text: mathResultText
+ font.family: Theme.fontFamily
+ font.pixelSize: 16
+ font.weight: Font.DemiBold
+ color: "#FFFFFF"
+ Layout.fillWidth: true
+ }
+ }
+ }
+
+ // Divider Line
+ Rectangle {
+ visible: resultsListView.count > 0
+ Layout.fillWidth: true
+ Layout.preferredHeight: 1
+ color: Qt.rgba(1.0, 1.0, 1.0, 0.12)
+ }
+
+ // Search Results List View
+ ListView {
+ id: resultsListView
+ visible: count > 0
+ Layout.fillWidth: true
+ Layout.preferredHeight: Math.min(270, count * 42)
+ clip: true
+ spacing: 2
+ 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: Theme.radiusSmall
+ color: spotWindow.selectedIndex === index ? Theme.menuHoverBlue : "transparent"
+
+ RowLayout {
+ anchors.fill: parent
+ anchors.leftMargin: 10
+ anchors.rightMargin: 10
+ spacing: 10
+
+ Image {
+ width: 22
+ height: 22
+ source: "../assets/terminal.svg"
+ sourceSize: Qt.size(22, 22)
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: 1
+
+ Text {
+ text: itemDelegate.name
+ font.family: Theme.fontFamily
+ font.pixelSize: Theme.fontSizeNormal
+ font.weight: Font.Medium
+ color: "#FFFFFF"
+ elide: Text.ElideRight
+ Layout.fillWidth: true
+ }
+
+ Text {
+ text: itemDelegate.comment || itemDelegate.exec
+ font.family: Theme.fontFamily
+ font.pixelSize: Theme.fontSizeSmall
+ color: spotWindow.selectedIndex === itemDelegate.index
+ ? Qt.rgba(1,1,1,0.85) : Theme.textTertiary
+ elide: Text.ElideRight
+ Layout.fillWidth: true
+ }
+ }
+
+ Text {
+ text: "↩"
+ font.pixelSize: 14
+ color: spotWindow.selectedIndex === itemDelegate.index
+ ? "#FFFFFF" : "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/VolumePopup.qml b/home/hypr/quickshell/components/VolumePopup.qml
new file mode 100644
index 0000000..fa302aa
--- /dev/null
+++ b/home/hypr/quickshell/components/VolumePopup.qml
@@ -0,0 +1,148 @@
+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
new file mode 100644
index 0000000..657c905
--- /dev/null
+++ b/home/hypr/quickshell/components/WifiPopup.qml
@@ -0,0 +1,102 @@
+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/qmldir b/home/hypr/quickshell/qmldir
new file mode 100644
index 0000000..b82af0d
--- /dev/null
+++ b/home/hypr/quickshell/qmldir
@@ -0,0 +1 @@
+singleton GlobalState 1.0 GlobalState.qml
diff --git a/home/hypr/quickshell/scripts/get_apps.sh b/home/hypr/quickshell/scripts/get_apps.sh
new file mode 100755
index 0000000..a4b58bc
--- /dev/null
+++ b/home/hypr/quickshell/scripts/get_apps.sh
@@ -0,0 +1,66 @@
+#!/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
new file mode 100755
index 0000000..31b41a3
--- /dev/null
+++ b/home/hypr/quickshell/scripts/get_audio.sh
@@ -0,0 +1,20 @@
+#!/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
new file mode 100755
index 0000000..9cf8815
--- /dev/null
+++ b/home/hypr/quickshell/scripts/get_battery.sh
@@ -0,0 +1,16 @@
+#!/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
new file mode 100755
index 0000000..fe71a9b
--- /dev/null
+++ b/home/hypr/quickshell/scripts/get_brightness.sh
@@ -0,0 +1,12 @@
+#!/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
new file mode 100755
index 0000000..ed744af
--- /dev/null
+++ b/home/hypr/quickshell/scripts/get_cpu.sh
@@ -0,0 +1,20 @@
+#!/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
new file mode 100755
index 0000000..400a2c1
--- /dev/null
+++ b/home/hypr/quickshell/scripts/get_mem.sh
@@ -0,0 +1,14 @@
+#!/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
new file mode 100755
index 0000000..bbb9b3e
--- /dev/null
+++ b/home/hypr/quickshell/scripts/get_wifi.sh
@@ -0,0 +1,30 @@
+#!/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/shell.qml b/home/hypr/quickshell/shell.qml
new file mode 100644
index 0000000..7464589
--- /dev/null
+++ b/home/hypr/quickshell/shell.qml
@@ -0,0 +1,79 @@
+import QtQuick
+import Quickshell
+import Quickshell.Io
+import "theme"
+import "components"
+import "."
+
+Scope {
+ id: root
+
+ // =========================================================================
+ // 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;
+ }
+ }
+
+ IpcHandler {
+ target: "controlCenter"
+ function toggle() {
+ GlobalState.toggleControlCenter();
+ }
+ function open() {
+ GlobalState.controlCenterVisible = true;
+ }
+ function close() {
+ GlobalState.controlCenterVisible = false;
+ }
+ }
+
+ IpcHandler {
+ target: "aboutMac"
+ function toggle() {
+ GlobalState.toggleAboutMac();
+ }
+ }
+
+ // =========================================================================
+ // MULTI-MONITOR BARS & DOCKS
+ // =========================================================================
+ // 1. macOS Catalina Top Menu Bar (Spawned per monitor)
+ Variants {
+ model: Quickshell.screens
+ MenuBar {}
+ }
+
+ // 2. macOS Catalina Floating Dock (Spawned per monitor)
+ Variants {
+ model: Quickshell.screens
+ Dock {}
+ }
+
+ // =========================================================================
+ // GLOBAL MODAL OVERLAYS
+ // =========================================================================
+ // 3. Spotlight Search Overlay
+ Spotlight {
+ id: spotlightOverlay
+ }
+
+ // 4. Control Center & Notification Center Sidebar
+ ControlCenter {
+ id: controlCenterSidebar
+ }
+
+ // 5. About This Mac Dialog
+ AboutMac {
+ id: aboutMacDialog
+ }
+}
diff --git a/home/hypr/quickshell/theme/Theme.qml b/home/hypr/quickshell/theme/Theme.qml
new file mode 100644
index 0000000..7c3d85a
--- /dev/null
+++ b/home/hypr/quickshell/theme/Theme.qml
@@ -0,0 +1,57 @@
+pragma Singleton
+import QtQuick
+
+QtObject {
+ // macOS Catalina Color Palette
+ readonly property color menuBarBg: Qt.rgba(0.12, 0.12, 0.14, 0.88)
+ readonly property color menuBarBorder: Qt.rgba(1.0, 1.0, 1.0, 0.12)
+
+ readonly property color dockBg: Qt.rgba(0.15, 0.16, 0.18, 0.72)
+ readonly property color dockBorder: Qt.rgba(1.0, 1.0, 1.0, 0.22)
+ readonly property color dockHighlight: Qt.rgba(1.0, 1.0, 1.0, 0.35)
+ readonly property color dockShadow: Qt.rgba(0.0, 0.0, 0.0, 0.45)
+ readonly property color dockDot: Qt.rgba(1.0, 1.0, 1.0, 0.95)
+
+ readonly property color windowBg: Qt.rgba(0.14, 0.14, 0.16, 0.94)
+ readonly property color windowBorder: Qt.rgba(1.0, 1.0, 1.0, 0.15)
+ readonly property color cardBg: Qt.rgba(0.22, 0.22, 0.26, 0.65)
+ readonly property color cardBorder: Qt.rgba(1.0, 1.0, 1.0, 0.10)
+
+ readonly property color menuDropdownBg: Qt.rgba(0.16, 0.16, 0.18, 0.96)
+ readonly property color menuDropdownBorder: Qt.rgba(1.0, 1.0, 1.0, 0.18)
+ readonly property color menuHoverBlue: "#0062E3"
+ readonly property color menuTextHover: "#FFFFFF"
+
+ readonly property color accentBlue: "#0A84FF"
+ readonly property color accentGreen: "#30D158"
+ readonly property color accentRed: "#FF453A"
+ readonly property color accentYellow: "#FFD60A"
+ readonly property color accentOrange: "#FF9F0A"
+
+ readonly property color textPrimary: "#FFFFFF"
+ readonly property color textSecondary: Qt.rgba(1.0, 1.0, 1.0, 0.72)
+ readonly property color textTertiary: Qt.rgba(1.0, 1.0, 1.0, 0.45)
+ readonly property color textDisabled: Qt.rgba(1.0, 1.0, 1.0, 0.25)
+
+ // Typography
+ readonly property string fontFamily: "SF Pro Display, SF Pro Text, Helvetica Neue, Cantarell, Liberation Sans, sans-serif"
+ readonly property int fontSizeMenuBar: 13
+ readonly property int fontSizeSmall: 11
+ readonly property int fontSizeNormal: 13
+ readonly property int fontSizeLarge: 15
+ readonly property int fontSizeTitle: 19
+ readonly property int fontSizeSpotlight: 20
+
+ // Geometry & Radii
+ readonly property int menuBarHeight: 26
+ readonly property int radiusSmall: 4
+ readonly property int radiusMedium: 8
+ readonly property int radiusLarge: 14
+ readonly property int radiusDock: 18
+ readonly property int radiusModal: 16
+
+ // Animation Durations
+ readonly property int animFast: 120
+ readonly property int animNormal: 220
+ readonly property int animSmooth: 320
+}
diff --git a/home/hypr/quickshell/theme/qmldir b/home/hypr/quickshell/theme/qmldir
new file mode 100644
index 0000000..d57fb86
--- /dev/null
+++ b/home/hypr/quickshell/theme/qmldir
@@ -0,0 +1 @@
+singleton Theme 1.0 Theme.qml
diff --git a/home/hypr/waybar.nix b/home/hypr/waybar.nix
deleted file mode 100644
index c3a8e20..0000000
--- a/home/hypr/waybar.nix
+++ /dev/null
@@ -1,230 +0,0 @@
-{ pkgs, ... }:
-
-{
- programs.waybar = {
- enable = true;
- settings = {
- mainBar = {
- layer = "top";
- modules-left = [ "custom/symbol" "cpu" "memory" "keyboard-state" "tray" ];
- modules-center = [ "hyprland/workspaces" ];
- modules-right = [ "idle_inhibitor" "backlight" "pulseaudio" "group/networks" "battery" "clock" ];
-
- pulseaudio = {
- tooltip = false;
- scroll-step = 5;
- format = "{icon} {volume}%";
- format-muted = " {volume}%";
- on-click-right = "pactl set-sink-mute @DEFAULT_SINK@ toggle";
- on-click = "pavucontrol";
- format-icons = {
- default = [
- ""
- ""
- ""
- ];
- };
- };
-
- idle_inhibitor = {
- format = "{icon}";
- format-icons = {
- activated = "";
- deactivated = "";
- };
- };
-
- "custom/network-symbol" = {
- format = " ";
- tooltip = false;
- on-click = "nm-connection-editor";
- };
-
- "group/networks" = {
- orientation = "inherit";
- drawer = {
- children-class = "drawer-network";
- };
- modules = [
- "custom/network-symbol"
- "network"
- ];
- };
-
- network = {
- tooltip = false;
- format-wifi = "{ipaddr} ({essid})";
- format-ethernet = "{ipaddr}";
- };
-
- keyboard-state = {
- numlock = true;
- capslock = true;
- format = {
- numlock = "N {icon}";
- capslock = "C {icon}";
- };
- format-icons = {
- locked = " ";
- unlocked = " ";
- };
- };
-
- backlight = {
- tooltip = false;
- format = " {}%";
- interval = 1;
- on-scroll-up = "brightnessctl set +5%";
- on-scroll-down = "brightnessctl set 5%-";
- };
-
- battery = {
- states = {
- good = 95;
- warning = 30;
- critical = 20;
- };
- format = "{icon} {capacity}%";
- format-charging = " {capacity}%";
- format-plugged = " {capacity}%";
- format-alt = "{time} {icon}";
- format-icons = [
- ""
- ""
- ""
- ""
- ""
- ];
- };
-
- tray = {
- icon-size = 18;
- spacing = 10;
- };
-
- clock = {
- format = "{:%I:%M %p | %m/%d/%Y}";
- };
-
- cpu = {
- interval = 15;
- format = " {}%";
- max-length = 10;
- };
-
- memory = {
- interval = 30;
- format = " {}%";
- max-length = 10;
- };
-
- "custom/symbol" = {
- format = " ";
- tooltip = false;
- };
-
- "group/workspaces" = {
- orientation = "inherit";
- drawer = {
- children-class = "drawer-workspace";
- };
- modules = [
- "custom/symbol"
- "hyprland/workspaces"
- ];
- };
-
- "hyprland/workspaces" = {
- disable-scroll = true;
- active-only = true;
- format = "{name} [ {windows} ]";
- window-rewrite-default = "";
- format-window-separator = " ";
- window-rewrite = {
- "class" = "";
- "class" = "";
- "class" = "";
- "class" = "";
- "class" = "";
- "class" = "";
- "class" = "";
- "class" = "";
- };
- };
- };
- };
-
- style = ''
- * {
- font-family: "SF Pro Display", "Font Awesome 6 Free", "JetBrainsMono Nerd Font";
- font-size: 16px;
- min-height: 10px;
- color: #c0caf5;
- background: transparent;
- }
-
- window#waybar {
- background: rgba(255, 255, 255, 0.2);
- border-radius: 5px;
- }
-
- #keyboard-state,
- #pulseaudio,
- #backlight,
- #clock,
- #memory,
- #cpu,
- #battery,
- #custom-symbol,
- #custom-network-symbol,
- #privacy,
- #tray,
- #idle_inhibitor,
- #workspaces button,
- #workspaces button.active {
- margin: 5px;
- border-radius: 3px;
- transition: none;
- border-bottom: 3px solid #c0caf5;
- }
-
- #keyboard-state:hover,
- #pulseaudio:hover,
- #backlight:hover,
- #clock:hover,
- #memory:hover,
- #cpu:hover,
- #custom-symbol:hover,
- #custom-network-symbol:hover,
- #privacy:hover,
- #tray:hover,
- #idle_inhibitor:hover,
- #workspaces button:hover {
- border-bottom: 3px solid #0dd2e7;
- }
-
- #battery.charging,
- #battery.plugged {
- color: #c3e88d;
- border-bottom: 3px solid #c3e88d;
- }
-
- #battery.critical:not(.charging) {
- color: #ff757f;
- border-bottom: 3px solid #ff757f;
- }
-
- #workspaces button.active,
- #workspaces button.focus {
- outline: none;
- color: #0dd2e7;
- text-shadow: none;
- }
- '';
- };
-
- home.packages = with pkgs; [
- pavucontrol
- networkmanagerapplet
- ];
-}
diff --git a/home/scripts/herdr-picker.sh b/home/scripts/herdr-picker.sh
new file mode 100755
index 0000000..5b2c736
--- /dev/null
+++ b/home/scripts/herdr-picker.sh
@@ -0,0 +1,88 @@
+#!/usr/bin/env bash
+
+# Prevent nested execution if already inside Herdr, tmux, zellij, or running non-interactively
+if [ -n "$HERDR" ] || [ -n "$HERDR_SESSION" ] || [ -n "$TMUX" ] || [ -n "$ZELLIJ" ] || [ ! -t 0 ]; then
+ exec zsh "$@"
+fi
+
+# Function to start raw shell
+start_plain_shell() {
+ exec zsh "$@"
+}
+
+# If herdr binary is not found, launch zsh directly without producing noisy stdout
+if ! command -v herdr &>/dev/null; then
+ start_plain_shell "$@"
+fi
+
+# Fetch active herdr sessions
+active_sessions=()
+raw_output=$(herdr session list 2>/dev/null)
+if [ $? -eq 0 ] && [ -n "$raw_output" ]; then
+ while IFS= read -r line; do
+ # Extract first word as session name
+ sname=$(echo "$line" | awk '{print $1}' | tr -d '*:')
+ if [ -n "$sname" ] && [ "$sname" != "NAME" ] && [ "$sname" != "Sessions:" ] && [ "$sname" != "No" ]; then
+ active_sessions+=("$sname")
+ fi
+ done <<< "$raw_output"
+fi
+
+# Prepare menu options
+menu_items=()
+menu_items+=("✨ [Create New Session]")
+menu_items+=("⚡ [Default Session]")
+
+for s in "${active_sessions[@]}"; do
+ if [ "$s" != "default" ]; then
+ menu_items+=("📦 $s")
+ fi
+done
+
+menu_items+=("🐚 [Skip - Plain Shell]")
+
+# Interactive session selector using fzf
+if command -v fzf &>/dev/null; then
+ selected=$(printf '%s\n' "${menu_items[@]}" | fzf \
+ --height=45% \
+ --layout=reverse \
+ --border=rounded \
+ --prompt=" Herdr Session > " \
+ --header="Select Herdr session to attach / create | Esc for plain shell" \
+ --header-first)
+else
+ echo "=========================================="
+ echo " HERDR SESSION PICKER "
+ echo "=========================================="
+ PS3="Choose an option: "
+ select selected in "${menu_items[@]}"; do
+ [ -n "$selected" ] && break
+ done
+fi
+
+case "$selected" in
+ "✨ [Create New Session]")
+ echo ""
+ read -r -p "Enter session name (press Enter for auto-generated name): " s_name
+ if [ -z "$s_name" ]; then
+ s_name="workspace-$(date +%m%d-%H%M)"
+ fi
+ export HERDR_SESSION="$s_name"
+ exec herdr session attach "$s_name" 2>/dev/null || exec herdr session new "$s_name" 2>/dev/null || exec herdr "$s_name"
+ ;;
+ "⚡ [Default Session]")
+ export HERDR_SESSION="default"
+ exec herdr
+ ;;
+ "🐚 [Skip - Plain Shell]"|"")
+ start_plain_shell "$@"
+ ;;
+ 📦\ *)
+ s_name="${selected#📦 }"
+ export HERDR_SESSION="$s_name"
+ exec herdr session attach "$s_name" 2>/dev/null || exec herdr "$s_name"
+ ;;
+ *)
+ start_plain_shell "$@"
+ ;;
+esac
diff --git a/home/starship/starship.toml b/home/starship/starship.toml
new file mode 100644
index 0000000..4af853c
--- /dev/null
+++ b/home/starship/starship.toml
@@ -0,0 +1,54 @@
+# macOS Catalina Dark Aesthetic Starship Theme (Single-Line)
+add_newline = false
+scan_timeout = 10
+
+format = "$os$directory$git_branch$git_status$nix_shell$rust$c$python$cmd_duration$character"
+
+[os]
+disabled = false
+style = "bold #f2f2f7"
+symbols = { NixOS = " " }
+
+[directory]
+truncation_length = 3
+truncate_to_repo = true
+style = "bold #0a84ff"
+format = "[$path]($style) "
+
+[git_branch]
+symbol = " "
+style = "bold #bf5af2"
+format = "on [$symbol$branch]($style) "
+
+[git_status]
+style = "bold #ff453a"
+format = "([$all_status$ahead_behind]($style) )"
+
+[nix_shell]
+symbol = " "
+style = "bold #64d2ff"
+format = "via [$symbol$state]($style) "
+
+[rust]
+symbol = " "
+style = "bold #ff9f0a"
+format = "[$symbol($version )]($style)"
+
+[c]
+symbol = " "
+style = "bold #5e5ce6"
+format = "[$symbol($version )]($style)"
+
+[python]
+symbol = " "
+style = "bold #ffd60a"
+format = "[$symbol($version )]($style)"
+
+[cmd_duration]
+min_time = 2000
+style = "bold #ffd60a"
+format = "took [$duration]($style) "
+
+[character]
+success_symbol = "[➜](bold #30d158) "
+error_symbol = "[➜](bold #ff453a) "
diff --git a/home/zsh.nix b/home/zsh.nix
index a493f1b..32e1d61 100644
--- a/home/zsh.nix
+++ b/home/zsh.nix
@@ -37,6 +37,12 @@
];
};
+ programs.starship = {
+ enable = true;
+ enableZshIntegration = true;
+ settings = builtins.fromTOML (builtins.readFile ./starship/starship.toml);
+ };
+
programs.zsh = {
enable = true;
enableCompletion = true;
@@ -65,11 +71,6 @@
};
plugins = [
- {
- name = "powerlevel10k";
- src = pkgs.zsh-powerlevel10k;
- file = "share/zsh-powerlevel10k/powerlevel10k.zsh-theme";
- }
{
name = "fzf-tab";
src = pkgs.zsh-fzf-tab;
@@ -113,141 +114,16 @@
"rebuild-test" = "rebuild test";
"rebuild-boot" = "rebuild boot";
"rebuild-build" = "rebuild build";
+
+ # Herdr multiplexer aliases
+ hs = "herdr-picker";
+ ha = "herdr session attach";
+ hl = "herdr session list";
+ hn = "herdr session new";
+ hk = "herdr session stop";
};
- initContent = lib.mkMerge [
- (lib.mkBefore ''
- # Enable Powerlevel10k instant prompt. Should stay close to the top of ~/.zshrc.
- if [[ -r "''${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-''${(%):-%n}.zsh" ]]; then
- source "''${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-''${(%):-%n}.zsh"
- fi
- '')
- (lib.mkAfter ''
- # To customize prompt, run `p10k configure` or edit ~/.p10k.zsh.
- [[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
-
- # Path configuration
- export PATH="$HOME/.local/bin:$HOME/bin:$HOME/.cargo/bin:$PNPM_HOME:$PATH"
-
- if [ -d "$HOME/Tools/xilinx/Vivado/2023.1/bin" ]; then
- export PATH="$HOME/Tools/xilinx/Vivado/2023.1/bin:$PATH"
- fi
-
- # Miniconda / conda profile hook if installed
- if [ -f /opt/miniconda3/etc/profile.d/conda.sh ]; then
- source /opt/miniconda3/etc/profile.d/conda.sh
- fi
-
- # Environment modules support if available
- if (( ''${+functions[module]} )) || [ -f /etc/modules/init/zsh ]; then
- [ -f /etc/modules/init/zsh ] && source /etc/modules/init/zsh
- module use --append "$HOME/.config/modulefiles" 2>/dev/null || true
- fi
-
- # Auto-tmux configuration
- use_auto_tmux () {
- ! ([ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ] || $(pgrep -x "i3" > /dev/null || pgrep -x "sway" > /dev/null || pgrep -x "Hyprland" > /dev/null))
- }
-
- if use_auto_tmux; then
- export ZSH_TMUX_AUTOSTART=true
- export ZSH_TMUX_AUTOCONNECT=false
- export ZSH_TMUX_AUTOQUIT=true
- else
- export ZSH_TMUX_AUTOSTART=false
- export ZSH_TMUX_AUTOCONNECT=false
- export ZSH_TMUX_AUTOQUIT=false
- fi
-
- if [[ -n "$TMUX" ]]; then
- tmux-window-name() {
- if [ -n "$TMUX_PLUGIN_MANAGER_PATH" ] && [ -f "$TMUX_PLUGIN_MANAGER_PATH/tmux-window-name/scripts/rename_session_windows.py" ]; then
- ("$TMUX_PLUGIN_MANAGER_PATH/tmux-window-name/scripts/rename_session_windows.py" &)
- fi
- }
- autoload -Uz add-zsh-hook
- add-zsh-hook chpwd tmux-window-name
- fi
-
- # Custom helper functions
- rebuild () {
- local action="''${1:-switch}"
- shift 2>/dev/null || true
-
- local config_dir="''${FLAKE_CONFIG:-$HOME/NixOS-Configs}"
- local host="hades"
- local target="$config_dir#$host"
-
- case "$action" in
- switch|test|boot)
- echo "==> Running: sudo nixos-rebuild $action --flake $target $*"
- sudo nixos-rebuild "$action" --flake "$target" "$@"
- ;;
- build|dry-build|dry-run|dry-activate)
- local act="$action"
- [[ "$action" == "dry-run" ]] && act="dry-build"
- echo "==> Running: nixos-rebuild $act --flake $target $*"
- nixos-rebuild "$act" --flake "$target" "$@"
- ;;
- help|-h|--help)
- echo "Usage: rebuild [switch|test|boot|build|dry-build] [extra nixos-rebuild options]"
- echo ""
- echo "Commands:"
- echo " switch Build, activate, and make default boot entry (default)"
- echo " test Build and activate without creating a boot entry"
- echo " boot Build and make default boot entry without activating"
- echo " build Build system to ./result without activating or sudo"
- echo " dry-build Show what would be built/fetched"
- ;;
- *)
- echo "==> Running: sudo nixos-rebuild $action --flake $target $*"
- sudo nixos-rebuild "$action" --flake "$target" "$@"
- ;;
- esac
- }
-
- perlmutter_pass () {
- echo -n 'bw master password: '
- read -s MASTER_PASS
- echo
- echo -n 'Copying passkey...'
- echo "$(echo "$MASTER_PASS" | bw get password iris.nersc.gov 2>/dev/null)$(echo "$MASTER_PASS" | bw get totp iris.nersc.gov 2>/dev/null)" | wl-copy
- echo 'Done.'
- }
-
- compiler_explorer () {
- docker run -d -p 10240:10240 --name compiler-explorer madduci/docker-compiler-explorer
- }
-
- airplay_server () {
- uxplay -p 10000 -nh -n "$(hostname)" -restrict -allow 88:66:5A:76:32:F5 -allow C4:AC:AA:76:B7:80
- }
-
- zathura_md () {
- cat "$1" | pandoc -f markdown -t pdf | zathura -
- }
-
- fooocus () {
- if [ -f "$HOME/Tools/Fooocus/entry_with_update.py" ]; then
- "$HOME/.conda/envs/fooocus/bin/python" "$HOME/Tools/Fooocus/entry_with_update.py"
- else
- echo "Fooocus not found at $HOME/Tools/Fooocus"
- fi
- }
-
- # Per-host / system local overrides fallback
- if [ -f "$HOME/.config/zsh/$(hostname)/env.sh" ]; then
- source "$HOME/.config/zsh/$(hostname)/env.sh"
- elif [ -f "$HOME/.zshrc.local" ]; then
- source "$HOME/.zshrc.local"
- fi
-
- # Fetch / Banner on interactive terminal start
- if [[ -o interactive ]] && [ -z "$TMUX" ] && command -v pfetch >/dev/null 2>&1; then
- pfetch
- fi
- '')
- ];
+ initContent = builtins.readFile ./zsh/init.zsh;
};
home.packages = with pkgs; [
diff --git a/home/zsh/init.zsh b/home/zsh/init.zsh
new file mode 100644
index 0000000..810c312
--- /dev/null
+++ b/home/zsh/init.zsh
@@ -0,0 +1,120 @@
+# Path configuration
+export PATH="$HOME/.local/bin:$HOME/bin:$HOME/.cargo/bin:$PNPM_HOME:$PATH"
+
+if [ -d "$HOME/Tools/xilinx/Vivado/2023.1/bin" ]; then
+ export PATH="$HOME/Tools/xilinx/Vivado/2023.1/bin:$PATH"
+fi
+
+# Miniconda / conda profile hook if installed
+if [ -f /opt/miniconda3/etc/profile.d/conda.sh ]; then
+ source /opt/miniconda3/etc/profile.d/conda.sh
+fi
+
+# Environment modules support if available
+if (( ${+functions[module]} )) || [ -f /etc/modules/init/zsh ]; then
+ [ -f /etc/modules/init/zsh ] && source /etc/modules/init/zsh
+ module use --append "$HOME/.config/modulefiles" 2>/dev/null || true
+fi
+
+# Auto-tmux configuration
+use_auto_tmux () {
+ ! ([ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ] || $(pgrep -x "i3" > /dev/null || pgrep -x "sway" > /dev/null || pgrep -x "Hyprland" > /dev/null))
+}
+
+if use_auto_tmux; then
+ export ZSH_TMUX_AUTOSTART=true
+ export ZSH_TMUX_AUTOCONNECT=false
+ export ZSH_TMUX_AUTOQUIT=true
+else
+ export ZSH_TMUX_AUTOSTART=false
+ export ZSH_TMUX_AUTOCONNECT=false
+ export ZSH_TMUX_AUTOQUIT=false
+fi
+
+if [[ -n "$TMUX" ]]; then
+ tmux-window-name() {
+ if [ -n "$TMUX_PLUGIN_MANAGER_PATH" ] && [ -f "$TMUX_PLUGIN_MANAGER_PATH/tmux-window-name/scripts/rename_session_windows.py" ]; then
+ ("$TMUX_PLUGIN_MANAGER_PATH/tmux-window-name/scripts/rename_session_windows.py" &)
+ fi
+ }
+ autoload -Uz add-zsh-hook
+ add-zsh-hook chpwd tmux-window-name
+fi
+
+# Custom helper functions
+rebuild () {
+ local action="${1:-switch}"
+ shift 2>/dev/null || true
+
+ local config_dir="${FLAKE_CONFIG:-$HOME/NixOS-Configs}"
+ local host="hades"
+ local target="$config_dir#$host"
+
+ case "$action" in
+ switch|test|boot)
+ echo "==> Running: sudo nixos-rebuild $action --flake $target $*"
+ sudo nixos-rebuild "$action" --flake "$target" "$@"
+ ;;
+ build|dry-build|dry-run|dry-activate)
+ local act="$action"
+ [[ "$action" == "dry-run" ]] && act="dry-build"
+ echo "==> Running: nixos-rebuild $act --flake $target $*"
+ nixos-rebuild "$act" --flake "$target" "$@"
+ ;;
+ help|-h|--help)
+ echo "Usage: rebuild [switch|test|boot|build|dry-build] [extra nixos-rebuild options]"
+ echo ""
+ echo "Commands:"
+ echo " switch Build, activate, and make default boot entry (default)"
+ echo " test Build and activate without creating a boot entry"
+ echo " boot Build and make default boot entry without activating"
+ echo " build Build system to ./result without activating or sudo"
+ echo " dry-build Show what would be built/fetched"
+ ;;
+ *)
+ echo "==> Running: sudo nixos-rebuild $action --flake $target $*"
+ sudo nixos-rebuild "$action" --flake "$target" "$@"
+ ;;
+ esac
+}
+
+perlmutter_pass () {
+ echo -n 'bw master password: '
+ read -s MASTER_PASS
+ echo
+ echo -n 'Copying passkey...'
+ echo "$(echo "$MASTER_PASS" | bw get password iris.nersc.gov 2>/dev/null)$(echo "$MASTER_PASS" | bw get totp iris.nersc.gov 2>/dev/null)" | wl-copy
+ echo 'Done.'
+}
+
+compiler_explorer () {
+ docker run -d -p 10240:10240 --name compiler-explorer madduci/docker-compiler-explorer
+}
+
+airplay_server () {
+ uxplay -p 10000 -nh -n "$(hostname)" -restrict -allow 88:66:5A:76:32:F5 -allow C4:AC:AA:76:B7:80
+}
+
+zathura_md () {
+ cat "$1" | pandoc -f markdown -t pdf | zathura -
+}
+
+fooocus () {
+ if [ -f "$HOME/Tools/Fooocus/entry_with_update.py" ]; then
+ "$HOME/.conda/envs/fooocus/bin/python" "$HOME/Tools/Fooocus/entry_with_update.py"
+ else
+ echo "Fooocus not found at $HOME/Tools/Fooocus"
+ fi
+}
+
+# Per-host / system local overrides fallback
+if [ -f "$HOME/.config/zsh/$(hostname)/env.sh" ]; then
+ source "$HOME/.config/zsh/$(hostname)/env.sh"
+elif [ -f "$HOME/.zshrc.local" ]; then
+ source "$HOME/.zshrc.local"
+fi
+
+# Fetch / Banner on interactive terminal start
+if [[ -o interactive ]] && [ -z "$TMUX" ] && command -v pfetch >/dev/null 2>&1; then
+ pfetch
+fi
diff --git a/modules/apps/dev.nix b/modules/apps/dev.nix
index 18dc9e9..7401c34 100644
--- a/modules/apps/dev.nix
+++ b/modules/apps/dev.nix
@@ -27,7 +27,7 @@
lsof
valgrind
heaptrack
- linuxPackages.perf
+ perf
flamegraph
# Decompilers, Disassemblers & Binary Analysis