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