From e09304b0b60677503985feb86460aa7fa842cebb Mon Sep 17 00:00:00 2001 From: Anish Govind Date: Mon, 31 Aug 2026 20:17:31 -0700 Subject: [PATCH] updated pi, quickshell, and ollama --- flake.lock | 193 ++++- flake.nix | 14 +- home/default.nix | 20 +- home/hypr/quickshell/GlobalState.qml | 67 +- home/hypr/quickshell/components/QuickMenu.qml | 190 +++++ home/hypr/quickshell/components/SidePanel.qml | 531 ++++++++++++++ home/hypr/quickshell/components/Spotlight.qml | 405 ++++++----- home/hypr/quickshell/components/SystemHUD.qml | 685 ++++++++++++++++++ .../hypr/quickshell/components/SystemInfo.qml | 257 +++++++ home/hypr/quickshell/components/TopStrip.qml | 101 +++ home/hypr/quickshell/shell.qml | 43 +- home/hypr/quickshell/theme/Theme.qml | 71 +- home/pi.nix | 7 + modules/apps/dev.nix | 1 - modules/apps/local_ai.nix | 1 - modules/core/nix.nix | 19 +- 16 files changed, 2290 insertions(+), 315 deletions(-) create mode 100644 home/hypr/quickshell/components/QuickMenu.qml create mode 100644 home/hypr/quickshell/components/SidePanel.qml create mode 100644 home/hypr/quickshell/components/SystemHUD.qml create mode 100644 home/hypr/quickshell/components/SystemInfo.qml create mode 100644 home/hypr/quickshell/components/TopStrip.qml create mode 100644 home/pi.nix diff --git a/flake.lock b/flake.lock index 66e69d7..e33f184 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ ] }, "locked": { - "lastModified": 1786829626, - "narHash": "sha256-Yg4SMZkoln6bEwAUPDhqCU7XWC1REPtHaerrhKf9eew=", + "lastModified": 1787879739, + "narHash": "sha256-F8R8x75eBMGox1Z9pmLhwEH2bjlZDeUGDIkOtzAOVq0=", "owner": "Hy4ri", "repo": "antigravity-flake", - "rev": "bf9c75d727c829794599434cbb29db774dd9ce77", + "rev": "d8581e462a2dde4469e056b736b352978ec7db98", "type": "github" }, "original": { @@ -20,17 +20,45 @@ "type": "github" } }, + "bun2nix": { + "inputs": { + "flake-parts": "flake-parts_2", + "nixpkgs": [ + "pi", + "nixpkgs" + ], + "systems": [ + "pi", + "systems" + ], + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1778446047, + "narHash": "sha256-oQvcadh2BCkrog+SGrG6YffKJrveYpjj3TdQJWaKhaM=", + "owner": "nix-community", + "repo": "bun2nix", + "rev": "f2bc12af1a6369648aac41041ceeaa0b866599c6", + "type": "github" + }, + "original": { + "owner": "nix-community", + "ref": "2.1.0", + "repo": "bun2nix", + "type": "github" + } + }, "comfy-ui": { "inputs": { "flake-parts": "flake-parts", "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1786714237, - "narHash": "sha256-XnXKBpaidykfCegAqBGI7/F61IFtMSKUibQct/dP2Zw=", + "lastModified": 1787851822, + "narHash": "sha256-xLLT67dZFyhNTmPTtpQvaruFHrv0ENB5B082C1NvDmA=", "owner": "utensils", "repo": "comfyui-nix", - "rev": "4501c5fe8dd93530d856866ba8bc4b585b508d41", + "rev": "9c0c693afe7e355b69fdc73dc0b28a628426a814", "type": "github" }, "original": { @@ -77,6 +105,28 @@ "type": "github" } }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": [ + "pi", + "bun2nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1777988971, + "narHash": "sha256-qIoWPDs+0/8JecyYgE3gpKQxW/4bLW/gp45vow9ioCQ=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "0678d8986be1661af6bb555f3489f2fdfc31f6ff", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "herdr": { "inputs": { "nixpkgs": [ @@ -85,11 +135,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1787017031, - "narHash": "sha256-FeF4eZhxRl4jBbAkMaEzDCmMcaitV6GZapjaeeNZy7A=", + "lastModified": 1788211360, + "narHash": "sha256-8wrp7NLYPRaZ9hL7QiqSDQFRme/1UrVwoEDs278aOUA=", "owner": "herdrdev", "repo": "herdr", - "rev": "2d24950ad9a02096921bc764e2e3dcd8900c3366", + "rev": "5158adab10b6dcfea9370782043392f80fa0643c", "type": "github" }, "original": { @@ -105,11 +155,11 @@ ] }, "locked": { - "lastModified": 1785119570, - "narHash": "sha256-Rgs2xKnGLFWQscxUaXX07oyZeuMDOHEbqDOsgliLFGM=", + "lastModified": 1787377438, + "narHash": "sha256-Sxu1NLTD/Ern6hFGLlZmtKCSct3YQXZI/lls8RE1XeM=", "owner": "nix-community", "repo": "home-manager", - "rev": "d4fd24667c8cbef124bb70a20380cab75ec8474d", + "rev": "65258d5c65a250189fde2e35f490d15e064c4c62", "type": "github" }, "original": { @@ -119,16 +169,31 @@ "type": "github" } }, + "jail-nix": { + "locked": { + "lastModified": 1776230864, + "narHash": "sha256-YsEjjdOsGEzTeD+iT7ONh071BqWAOQWpzYVei3okAXE=", + "owner": "~alexdavid", + "repo": "jail.nix", + "rev": "404e7da9da5ab9aa643666682b2ba1312fa5fbe8", + "type": "sourcehut" + }, + "original": { + "owner": "~alexdavid", + "repo": "jail.nix", + "type": "sourcehut" + } + }, "nixos-hardware": { "inputs": { "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1786717298, - "narHash": "sha256-asdPlqtM0AweQ5YNLad6eY4tSYW55YNr1uyayf7mOMQ=", + "lastModified": 1788044418, + "narHash": "sha256-cmOd3iGoE140M2GidgQMQbyxHZ4dT7C0QZq2+CrXQyA=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "2dda192987ab6815e44df0130f03d6c907b79134", + "rev": "dc3f0cfde2050172abf6c3cdb684f735c15a57c5", "type": "github" }, "original": { @@ -171,11 +236,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1786862985, - "narHash": "sha256-FBJRXmbGXiSUDvYEbfLYRkckayyZ6SK1UEqhCrIZ2Cs=", + "lastModified": 1788179007, + "narHash": "sha256-hn1oU2rue2SYK8dAr8+WNZWtbsz1S2W5mnHlSEuh3bo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e5bdc4a41d4c072fe1e3787eaa0320a384741d44", + "rev": "34ab99075ac4f7e40cf037eef32cb1c360bb85e9", "type": "github" }, "original": { @@ -200,11 +265,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1786711500, - "narHash": "sha256-QvnceIGTBeDvDd9oCn+GvdsnkquliuwbVgpiRH68qaQ=", + "lastModified": 1787962033, + "narHash": "sha256-u6z9VTZA4Kf3RkHQo9sQI7NI4Ei/uiU9vrMqOiwWP1Y=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "02e08985a27c65ffd33d434eeb2e660a2e4dc84d", + "rev": "c5c4a43b0e8056328ec4529f735cabdb8f1942bb", "type": "github" }, "original": { @@ -214,24 +279,41 @@ "type": "github" } }, - "quickshell": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, + "nixpkgs_4": { "locked": { - "lastModified": 1786864924, - "narHash": "sha256-9nqIJWuSlAwy4Vs98N9ffoVeU2dmsWcAJ7/DyIDihqM=", - "ref": "refs/heads/master", - "rev": "532a0606cafb05851a203e65be22c9a057447ee2", - "revCount": 844, - "type": "git", - "url": "https://git.outfoxxed.me/outfoxxed/quickshell" + "lastModified": 1773646010, + "narHash": "sha256-iYrs97hS7p5u4lQzuNWzuALGIOdkPXvjz7bviiBjUu8=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "5b2c2d84341b2afb5647081c1386a80d7a8d8605", + "type": "github" }, "original": { - "type": "git", - "url": "https://git.outfoxxed.me/outfoxxed/quickshell" + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "pi": { + "inputs": { + "bun2nix": "bun2nix", + "jail-nix": "jail-nix", + "nixpkgs": "nixpkgs_4", + "systems": "systems" + }, + "locked": { + "lastModified": 1788173678, + "narHash": "sha256-u0YZmQ8Z5luYELFHA6GAQ68HT2Dh5kU3sMFLQ68+2P0=", + "owner": "lukasl-dev", + "repo": "pi.nix", + "rev": "2a11906417b5f2bd9e8f9130da31dfbdfeffecda", + "type": "github" + }, + "original": { + "owner": "lukasl-dev", + "repo": "pi.nix", + "type": "github" } }, "root": { @@ -244,7 +326,7 @@ "nixos-hardware": "nixos-hardware", "nixpkgs": "nixpkgs_3", "nixpkgs-unstable": "nixpkgs-unstable", - "quickshell": "quickshell" + "pi": "pi" } }, "rust-overlay": { @@ -267,6 +349,43 @@ "repo": "rust-overlay", "type": "github" } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "pi", + "bun2nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1775636079, + "narHash": "sha256-pc20NRoMdiar8oPQceQT47UUZMBTiMdUuWrYu2obUP0=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "790751ff7fd3801feeaf96d7dc416a8d581265ba", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 080d4f7..7a70c74 100644 --- a/flake.nix +++ b/flake.nix @@ -25,15 +25,15 @@ 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"; }; + + pi = { + url = "github:lukasl-dev/pi.nix"; + }; }; outputs = @@ -48,6 +48,12 @@ inputs.home-manager.nixosModules.home-manager { nixpkgs.overlays = [ + (final: prev: { + unstable = import inputs.nixpkgs-unstable { + inherit (final) system; + config.allowUnfree = true; + }; + }) inputs.antigravity.overlays.default inputs.comfy-ui.overlays.default ]; diff --git a/home/default.nix b/home/default.nix index 02f44ca..746fee1 100644 --- a/home/default.nix +++ b/home/default.nix @@ -1,15 +1,17 @@ { pkgs, ... }: { - imports = [ - ./hypr - ./foot.nix - ./brave.nix - ./zsh.nix - ./nvim - ]; + imports = [ + ./hypr + ./foot.nix + ./brave.nix + ./zsh.nix + ./nvim - xdg.portal.config.common.default = "*"; + ./pi.nix + ]; - home.stateVersion = "26.05"; + xdg.portal.config.common.default = "*"; + + home.stateVersion = "26.05"; } diff --git a/home/hypr/quickshell/GlobalState.qml b/home/hypr/quickshell/GlobalState.qml index d89822e..a436898 100644 --- a/home/hypr/quickshell/GlobalState.qml +++ b/home/hypr/quickshell/GlobalState.qml @@ -47,6 +47,20 @@ QtObject { property bool batteryCharging: false property string batteryStatus: "Full" + // GPU VRAM Monitoring (NVIDIA) + property string gpuVramUsed: "0" + property string gpuVramTotal: "0" + property int gpuVramPercent: 0 + + // System HUD state (replaces individual popup states) + property bool systemHudVisible: false + property string systemHudActiveSection: "" // "volume", "wifi", "calendar", etc. + + // Workspace tracking + property int activeWorkspace: 1 + property var workspaceList: [1, 2, 3, 4, 5] + property var workspaceNames: ({1: "main", 2: "browser", 3: "code", 4: "media", 5: "misc"}) + // Media Player State (MPRIS) property string mediaTitle: "" property string mediaArtist: "" @@ -60,6 +74,7 @@ QtObject { wifiPopupVisible = false; batteryPopupVisible = false; calendarPopupVisible = false; + systemHudVisible = false; } function toggleSpotlight() { @@ -77,12 +92,25 @@ QtObject { aboutMacVisible = !aboutMacVisible; } + function toggleSystemHud() { + closeAllPopups(); + systemHudVisible = !systemHudVisible; + } + + function switchWorkspace(n) { + Hyprland.dispatch("workspace " + n); + } + // Hyprland raw event processing readonly property Connections hyprConn: Connections { target: Hyprland - function onRawEvent(event) { + function onRawEvent(event) { console.log(JSON.stringify(event), event.name, event.event, event.data); if (!event) return; - if (event.startsWith("activewindow>>")) { + if (event.startsWith("workspace>>")) { + const data = event.substring(11); + const wsNum = parseInt(data); + if (!isNaN(wsNum)) root.activeWorkspace = wsNum; + } else 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() : ""); @@ -127,6 +155,7 @@ QtObject { audioProc.running = true; brightProc.running = true; mprisProc.running = true; + gpuProc.running = true; } } @@ -135,7 +164,7 @@ QtObject { 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()); + const val = parseInt(String(data).trim()); if (!isNaN(val)) root.cpuPercent = Math.max(0, Math.min(100, val)); } } @@ -145,7 +174,7 @@ QtObject { 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(" "); + const parts = String(data).trim().split(" "); if (parts.length >= 3) { root.memPercent = parseInt(parts[0]) || 0; root.memUsedGb = parts[1]; @@ -159,7 +188,7 @@ QtObject { 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(" "); + const parts = String(data).trim().split(" "); if (parts.length >= 3) { root.batteryPresent = parts[0] === "1"; root.batteryPercent = parseInt(parts[1]) || 100; @@ -174,7 +203,7 @@ QtObject { 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 str = String(data).trim(); const parts = str.split(" "); if (parts.length >= 2) { root.wifiSsid = parts[0]; @@ -189,7 +218,7 @@ QtObject { 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(" "); + const parts = String(data).trim().split(" "); if (parts.length >= 2) { root.volume = parseInt(parts[0]) || 50; root.volumeMuted = parts[1] === "1"; @@ -202,7 +231,7 @@ QtObject { 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()); + const val = parseInt(String(data).trim()); if (!isNaN(val)) root.brightness = val; } } @@ -212,7 +241,7 @@ QtObject { 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("|"); + const parts = String(data).trim().split("|"); if (parts.length >= 3) { root.mediaPlaying = parts[0] === "Playing"; root.mediaTitle = parts[1] || ""; @@ -226,12 +255,30 @@ QtObject { } } + readonly property Process gpuProc: Process { + command: ["bash", "-c", "nvidia-smi --query-gpu=memory.used,memory.total --format=csv,noheader,nounits 2>/dev/null || echo '0, 0'"] + stdout: StdioCollector { + onDataChanged: { + const parts = String(data).trim().split(","); + if (parts.length >= 2) { + const usedMiB = parseInt(parts[0].trim()); + const totalMiB = parseInt(parts[1].trim()); + if (!isNaN(usedMiB) && !isNaN(totalMiB) && totalMiB > 0) { + root.gpuVramUsed = (usedMiB / 1024).toFixed(1); + root.gpuVramTotal = (totalMiB / 1024).toFixed(1); + root.gpuVramPercent = Math.round((usedMiB / totalMiB) * 100); + } + } + } + } + } + readonly property Process clientsProc: Process { command: ["bash", "-c", "if command -v hyprctl &>/dev/null; then hyprctl -j clients 2>/dev/null; else echo '[]'; fi"] stdout: StdioCollector { onDataChanged: { try { - const parsed = JSON.parse(data.trim()); + const parsed = JSON.parse(String(data).trim()); if (Array.isArray(parsed)) { root.runningApps = parsed; } diff --git a/home/hypr/quickshell/components/QuickMenu.qml b/home/hypr/quickshell/components/QuickMenu.qml new file mode 100644 index 0000000..96fdf61 --- /dev/null +++ b/home/hypr/quickshell/components/QuickMenu.qml @@ -0,0 +1,190 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import Quickshell.Wayland +import Quickshell.Io +import "../theme" +import ".." + +PanelWindow { + id: quickMenu + + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + + anchors { + top: true + left: true + } + + margins { + top: 0 + left: Theme.sidebarWidth + } + + visible: GlobalState.appleMenuVisible + + implicitWidth: 220 + implicitHeight: menuContainer.implicitHeight + + color: "transparent" + + // Close on escape + Item { + anchors.fill: parent + focus: true + Keys.onEscapePressed: GlobalState.appleMenuVisible = false + } + + Rectangle { + id: menuContainer + anchors.fill: parent + color: Theme.glassBg + border.color: Theme.glassBorder + border.width: 1 + + Column { + width: parent.width + padding: 8 + + component MenuItem: Item { + id: menuItem + width: parent.width + height: 28 + + property string text: "" + property string shortcut: "" + signal triggered() + + Rectangle { + anchors.fill: parent + color: menuItemMouseArea.containsMouse ? Qt.rgba(Theme.accentVolt.r, Theme.accentVolt.g, Theme.accentVolt.b, 0.1) : "transparent" + } + + Rectangle { + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + width: 2 + color: Theme.accentVolt + visible: menuItemMouseArea.containsMouse + } + + Text { + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.verticalCenter: parent.verticalCenter + text: menuItem.text + font.family: Theme.fontSans + font.pixelSize: 13 + color: menuItemMouseArea.containsMouse ? Theme.textPrimary : Theme.textSecondary + } + + Text { + anchors.right: parent.right + anchors.rightMargin: 12 + anchors.verticalCenter: parent.verticalCenter + text: menuItem.shortcut + font.family: Theme.fontMono + font.pixelSize: 11 + color: Theme.textMuted + visible: text !== "" + } + + MouseArea { + id: menuItemMouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + menuItem.triggered() + GlobalState.appleMenuVisible = false + } + } + } + + component MenuSeparator: Item { + width: parent.width + height: 17 + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 12 + anchors.rightMargin: 12 + height: 1 + color: Theme.glassBorder + } + } + + MenuItem { + text: "System Info..." + onTriggered: GlobalState.aboutMacVisible = true + } + + MenuItem { + text: "System Preferences..." + onTriggered: { + let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["pavucontrol"] }', quickMenu) + p.running = true + } + } + + MenuItem { + text: "Activity Monitor" + shortcut: "btop" + onTriggered: { + let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["foot", "-e", "btop"] }', quickMenu) + p.running = true + } + } + + MenuSeparator {} + + MenuItem { + text: "Sleep" + onTriggered: { + let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["systemctl", "suspend"] }', quickMenu) + p.running = true + } + } + + MenuItem { + text: "Restart..." + onTriggered: { + let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["systemctl", "reboot"] }', quickMenu) + p.running = true + } + } + + MenuItem { + text: "Shut Down..." + onTriggered: { + let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["systemctl", "poweroff"] }', quickMenu) + p.running = true + } + } + + MenuSeparator {} + + MenuItem { + text: "Lock Screen" + shortcut: "Super+L" + onTriggered: { + let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["hyprlock"] }', quickMenu) + p.running = true + } + } + + MenuItem { + text: "Log Out" + shortcut: "Super+Shift+Q" + onTriggered: { + let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["hyprctl", "dispatch", "exit"] }', quickMenu) + p.running = true + } + } + } + } +} diff --git a/home/hypr/quickshell/components/SidePanel.qml b/home/hypr/quickshell/components/SidePanel.qml new file mode 100644 index 0000000..c00d69e --- /dev/null +++ b/home/hypr/quickshell/components/SidePanel.qml @@ -0,0 +1,531 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Hyprland +import Quickshell.Wayland +import "../theme" +import ".." + +PanelWindow { + id: root + property var screen + + WlrLayershell.layer: WlrLayer.Top + WlrLayershell.namespace: "quickshell-sidepanel" + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + + anchors { + top: true + bottom: true + left: true + right: false + } + + exclusiveZone: Theme.sidebarWidth + implicitWidth: Theme.sidebarWidth + color: Theme.bgPrimary + + // Right edge hairline border + Rectangle { + width: 1 + color: Theme.glassBorder + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom + } + } + + component Separator: Rectangle { + width: 36 + height: 1 + color: Theme.glassBorder + Layout.alignment: Qt.AlignHCenter + } + + component DockIcon: Item { + id: dockIconRoot + width: 36 + height: 36 + Layout.alignment: Qt.AlignHCenter + + property string appClass: "" + property string execCmd: "" + property string iconSource: "" + property string tooltipText: "" + + property bool isRunning: (appClass !== "" && GlobalState.activeWindowClass === appClass) || (GlobalState.runningApps && GlobalState.runningApps.indexOf(appClass) !== -1) + + Image { + id: img + anchors.centerIn: parent + width: 24 + height: 24 + source: iconSource ? Qt.resolvedUrl("../assets/" + iconSource) : "" + sourceSize: Qt.size(24, 24) + fillMode: Image.PreserveAspectFit + opacity: mouseArea.containsMouse ? 1.0 : 0.8 + + scale: mouseArea.containsMouse ? 1.15 : 1.0 + Behavior on scale { + NumberAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } + } + Behavior on opacity { + NumberAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } + } + } + + Rectangle { + id: runningDot + width: 4 + height: 4 + radius: 2 + color: Theme.accentVolt + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + visible: isRunning + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + if (execCmd !== "") { + GlobalState.launchApp(execCmd) + } + } + } + + // Tooltip + Rectangle { + id: tooltip + visible: mouseArea.containsMouse && tooltipText !== "" + color: Theme.glassBg + radius: 4 + width: tooltipLabel.implicitWidth + 16 + height: tooltipLabel.implicitHeight + 8 + + anchors.left: parent.right + anchors.leftMargin: 24 + anchors.verticalCenter: parent.verticalCenter + + Text { + id: tooltipLabel + anchors.centerIn: parent + text: tooltipText + color: Theme.textPrimary + font.family: Theme.fontSans + font.pixelSize: 11 + } + } + } + + ColumnLayout { + anchors.fill: parent + spacing: 8 + + // Top spacer + Item { height: 4; Layout.fillWidth: true } + + // 1. AG Logo Button + Rectangle { + width: 36 + height: 36 + radius: 4 + color: logoMouse.containsMouse ? Qt.rgba(1,1,1,0.05) : "transparent" + Layout.alignment: Qt.AlignHCenter + + Behavior on color { + ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } + } + + Text { + anchors.centerIn: parent + text: "AG" + font.family: Theme.fontSerif + font.italic: true + font.pixelSize: 16 + color: Theme.accentVolt + } + + MouseArea { + id: logoMouse + anchors.fill: parent + hoverEnabled: true + onClicked: GlobalState.appleMenuVisible = !GlobalState.appleMenuVisible + } + } + + Separator {} + + // 3. Workspace Indicators + Column { + Layout.alignment: Qt.AlignHCenter + spacing: 2 + + Repeater { + model: GlobalState.workspaceList + delegate: Rectangle { + width: 52 + height: 36 + color: wsMouse.containsMouse ? Qt.rgba(1,1,1,0.04) : "transparent" + + property bool isActive: GlobalState.activeWorkspace === modelData + + Behavior on color { + ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } + } + + Rectangle { + width: 2 + height: 20 + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + color: Theme.accentVolt + visible: isActive + } + + Column { + anchors.centerIn: parent + spacing: 2 + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: (modelData < 10 ? "0" : "") + modelData + color: isActive ? Theme.accentVolt : Theme.textMuted + font.family: Theme.fontMono + font.pixelSize: 12 + + Behavior on color { + ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } + } + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: (GlobalState.workspaceNames && GlobalState.workspaceNames[modelData]) ? GlobalState.workspaceNames[modelData] : "" + color: Theme.textMuted + font.family: Theme.fontSans + font.pixelSize: 8 + opacity: 0.7 + } + } + + MouseArea { + id: wsMouse + anchors.fill: parent + hoverEnabled: true + onClicked: GlobalState.switchWorkspace(modelData) + } + } + } + } + + Separator {} + + // 5. Spotlight trigger + Item { + width: 36 + height: 36 + Layout.alignment: Qt.AlignHCenter + + Text { + anchors.centerIn: parent + text: "⌕" + font.pixelSize: 20 + color: spotlightMouse.containsMouse ? Theme.accentVolt : Theme.textMuted + + Behavior on color { + ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } + } + } + + MouseArea { + id: spotlightMouse + anchors.fill: parent + hoverEnabled: true + onClicked: GlobalState.toggleSpotlight() + } + } + + Separator {} + + // 7. Pinned Apps + Column { + Layout.alignment: Qt.AlignHCenter + spacing: 8 + + DockIcon { + appClass: "" + execCmd: "nautilus" + iconSource: "finder.svg" + tooltipText: "Finder" + } + DockIcon { + appClass: "brave-browser" + execCmd: "brave" + iconSource: "browser.svg" + tooltipText: "Brave Browser" + } + DockIcon { + appClass: "foot" + execCmd: "foot" + iconSource: "terminal.svg" + tooltipText: "Terminal" + } + DockIcon { + appClass: "nvim" + execCmd: "foot -e nvim" + iconSource: "code.svg" + tooltipText: "Code" + } + DockIcon { + appClass: "pavucontrol" + execCmd: "pavucontrol" + iconSource: "preferences.svg" + tooltipText: "Settings" + } + DockIcon { + appClass: "" + execCmd: "spotify" + iconSource: "music.svg" + tooltipText: "Music" + } + } + + Separator {} + + // 9. Running Apps (non-pinned) + Column { + Layout.alignment: Qt.AlignHCenter + spacing: 8 + + Repeater { + model: { + var pinned = ["foot", "brave-browser", "pavucontrol", "nvim", "nautilus", "spotify", ""] + var running = [] + var seen = {} + if (GlobalState.runningApps) { + for (var i = 0; i < GlobalState.runningApps.length; i++) { + var cls = GlobalState.runningApps[i] + if (cls && pinned.indexOf(cls) === -1 && !seen[cls]) { + seen[cls] = true + running.push(cls) + } + } + } + return running + } + + delegate: Item { + width: 36 + height: 36 + + Rectangle { + anchors.centerIn: parent + width: 24 + height: 24 + radius: 12 + color: Qt.rgba(1, 1, 1, 0.1) + + Text { + anchors.centerIn: parent + text: modelData.charAt(0).toUpperCase() + color: Theme.textPrimary + font.family: Theme.fontSans + font.pixelSize: 12 + } + } + + Rectangle { + width: 4 + height: 4 + radius: 2 + color: Theme.accentVolt + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onClicked: { + // Focus app logic here + } + } + } + } + } + + // 10. Flexible spacer + Item { Layout.fillHeight: true } + + Separator {} + + // 12. Telemetry mini-readouts + Column { + Layout.alignment: Qt.AlignHCenter + spacing: 2 + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: "CPU" + (GlobalState.cpuPercent !== undefined ? GlobalState.cpuPercent : "0") + "%" + font.family: Theme.fontMono + font.pixelSize: 11 + color: (GlobalState.cpuPercent !== undefined && GlobalState.cpuPercent > 80) ? Theme.accentCoral : Theme.textMuted + Behavior on color { ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } } + } + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: "RAM" + (GlobalState.memUsedGb !== undefined ? GlobalState.memUsedGb : "0") + "G" + font.family: Theme.fontMono + font.pixelSize: 11 + color: (GlobalState.memPercent !== undefined && GlobalState.memPercent > 85) ? Theme.accentCoral : Theme.textMuted + Behavior on color { ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } } + } + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: "VRM" + (GlobalState.gpuVramUsed !== undefined ? GlobalState.gpuVramUsed : "0") + "G" + font.family: Theme.fontMono + font.pixelSize: 11 + color: Theme.textMuted + } + } + + Separator {} + + // 14. System tray icons + Column { + Layout.alignment: Qt.AlignHCenter + spacing: 8 + + // Volume + Item { + width: 36 + height: 36 + Image { + anchors.centerIn: parent + width: 16 + height: 16 + source: Qt.resolvedUrl("../assets/volume.svg") + sourceSize: Qt.size(16, 16) + opacity: volMouse.containsMouse ? 1.0 : 0.7 + Behavior on opacity { NumberAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } } + } + MouseArea { + id: volMouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + GlobalState.systemHudActiveSection = "volume" + GlobalState.systemHudVisible = true + } + } + } + + // WiFi + Item { + width: 36 + height: 36 + Image { + anchors.centerIn: parent + width: 16 + height: 16 + source: Qt.resolvedUrl("../assets/wifi.svg") + sourceSize: Qt.size(16, 16) + opacity: GlobalState.wifiConnected ? 0.7 : 0.3 + Behavior on opacity { NumberAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } } + } + MouseArea { + anchors.fill: parent + hoverEnabled: true + onClicked: { + GlobalState.systemHudActiveSection = "network" + GlobalState.systemHudVisible = true + } + } + } + + // Battery + Item { + width: 36 + height: 44 + visible: GlobalState.batteryPresent !== undefined ? GlobalState.batteryPresent : false + + Column { + anchors.centerIn: parent + spacing: 2 + + Image { + anchors.horizontalCenter: parent.horizontalCenter + width: 16 + height: 16 + source: Qt.resolvedUrl("../assets/battery.svg") + sourceSize: Qt.size(16, 16) + opacity: (GlobalState.batteryPercent !== undefined && GlobalState.batteryPercent < 20) ? 1.0 : 0.7 + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: (GlobalState.batteryPercent !== undefined ? GlobalState.batteryPercent : "100") + "%" + font.family: Theme.fontMono + font.pixelSize: 9 + color: (GlobalState.batteryPercent !== undefined && GlobalState.batteryPercent < 20) ? Theme.accentCoral : Theme.textSecondary + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onClicked: { + GlobalState.systemHudActiveSection = "battery" + GlobalState.systemHudVisible = true + } + } + } + } + + Separator {} + + // 16. Clock + Item { + width: 36 + height: 44 + Layout.alignment: Qt.AlignHCenter + + Column { + anchors.centerIn: parent + spacing: 2 + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: GlobalState.timeString !== undefined ? GlobalState.timeString : "00:00" + font.family: Theme.fontMono + font.pixelSize: 12 + color: Theme.textPrimary + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: GlobalState.dateString !== undefined ? GlobalState.dateString : "Jan 01" + font.family: Theme.fontMono + font.pixelSize: 9 + color: Theme.textMuted + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onClicked: { + GlobalState.systemHudActiveSection = "calendar" + GlobalState.systemHudVisible = true + } + } + } + + // Bottom spacer + Item { height: 4; Layout.fillWidth: true } + } +} diff --git a/home/hypr/quickshell/components/Spotlight.qml b/home/hypr/quickshell/components/Spotlight.qml index 39c914b..b818c35 100644 --- a/home/hypr/quickshell/components/Spotlight.qml +++ b/home/hypr/quickshell/components/Spotlight.qml @@ -18,7 +18,7 @@ PanelWindow { bottom: true } - color: Qt.rgba(0, 0, 0, 0.35) + color: Qt.rgba(0, 0, 0, 0.45) WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.namespace: "quickshell-spotlight" WlrLayershell.keyboardFocus: visible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None @@ -35,7 +35,7 @@ PanelWindow { stdout: StdioCollector { onDataChanged: { try { - const parsed = JSON.parse(data.trim()); + const parsed = JSON.parse(String(data).trim()); if (Array.isArray(parsed)) { spotWindow.allApps = parsed; } @@ -67,223 +67,246 @@ PanelWindow { } // Centered Spotlight Search Modal - Rectangle { - id: modalBox + Item { + id: modalContainer 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 + height: modalBox.implicitHeight + + scale: spotWindow.visible ? 1.0 : 0.96 + opacity: spotWindow.visible ? 1.0 : 0.0 - Behavior on implicitHeight { - NumberAnimation { duration: 180; easing.type: Easing.OutQuad } + Behavior on scale { + NumberAnimation { duration: Theme.animSmooth; easing.type: Easing.OutQuint } + } + Behavior on opacity { + NumberAnimation { duration: Theme.animSmooth; easing.type: Easing.OutQuint } } - // Prevent clicking inside from closing modal - MouseArea { + Rectangle { + id: modalBox anchors.fill: parent - onClicked: {} - } + implicitHeight: searchBarRowContainer.height + (resultsListView.count > 0 || mathResultText !== "" ? 340 : 0) + radius: 0 + color: Theme.bgPrimary + border.color: Theme.glassBorder + border.width: 1 - ColumnLayout { - anchors.fill: parent - anchors.margins: 14 - spacing: 12 + Behavior on implicitHeight { + NumberAnimation { duration: 180; easing.type: Easing.OutQuad } + } - // Top Search Bar - RowLayout { - id: searchBarRow - Layout.fillWidth: true - Layout.preferredHeight: 44 - spacing: 12 + // Prevent clicking inside from closing modal + MouseArea { + anchors.fill: parent + onClicked: {} + } - Image { - width: 24 - height: 24 - source: "../assets/spotlight.svg" - sourceSize: Qt.size(24, 24) - } + ColumnLayout { + anchors.fill: parent + spacing: 0 - TextInput { - id: searchInput + // Top Search Bar Area + Item { + id: searchBarRowContainer Layout.fillWidth: true - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeSpotlight - font.weight: Font.Light - color: Theme.textPrimary - focus: true - clip: true + Layout.preferredHeight: 64 - Text { + RowLayout { + id: searchBarRow anchors.fill: parent - visible: searchInput.text === "" - text: "Spotlight Search" - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeSpotlight - font.weight: Font.Light - color: Theme.textTertiary + anchors.margins: 14 + spacing: 12 + + Text { + text: "⌕" + font.family: Theme.fontSans + font.pixelSize: 24 + color: Theme.accentVolt + Layout.alignment: Qt.AlignVCenter + } + + TextInput { + id: searchInput + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + font.family: Theme.fontSans + font.pixelSize: 20 + color: Theme.textPrimary + focus: true + clip: true + + Text { + anchors.fill: parent + visible: searchInput.text === "" + text: "Spotlight Search" + font.family: Theme.fontSans + font.pixelSize: 20 + color: Theme.textMuted + } + + Keys.onEscapePressed: GlobalState.spotlightVisible = false + Keys.onDownPressed: { + if (resultsListView.count > 0) { + spotWindow.selectedIndex = Math.min(resultsListView.count - 1, spotWindow.selectedIndex + 1); + } + } + Keys.onUpPressed: { + if (resultsListView.count > 0) { + spotWindow.selectedIndex = Math.max(0, spotWindow.selectedIndex - 1); + } + } + Keys.onReturnPressed: { + if (filteredModel.count > 0 && spotWindow.selectedIndex < filteredModel.count) { + const item = filteredModel.get(spotWindow.selectedIndex); + GlobalState.launchApp(item.exec); + } + } + } + + // Clear button + Rectangle { + visible: searchInput.text !== "" + width: 24 + height: 24 + radius: 0 + color: "transparent" + + Text { + anchors.centerIn: parent + text: "✕" + font.pixelSize: 16 + color: clearMouseArea.containsMouse ? Theme.textPrimary : Theme.textMuted + } + + MouseArea { + id: clearMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: searchInput.text = "" + } + } } - 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); - } + Rectangle { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: Theme.glassBorder } } - // Clear button + // Math Calculation Result Banner (if valid formula) 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" + visible: mathResultText !== "" + Layout.fillWidth: true + Layout.preferredHeight: 36 + Layout.margins: 14 + radius: 0 + color: "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 - } - } + anchors.margins: 8 + spacing: 8 Text { - text: "↩" - font.pixelSize: 14 - color: spotWindow.selectedIndex === itemDelegate.index - ? "#FFFFFF" : "transparent" + text: "=" + font.family: Theme.fontMono + font.pixelSize: 16 + font.weight: Font.Bold + color: Theme.accentVolt + } + Text { + text: mathResultText + font.family: Theme.fontMono + font.pixelSize: 16 + color: Theme.accentVolt + Layout.fillWidth: true } } + } - MouseArea { - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onEntered: spotWindow.selectedIndex = itemDelegate.index - onClicked: GlobalState.launchApp(itemDelegate.exec) + // Search Results List View + ListView { + id: resultsListView + visible: count > 0 + Layout.fillWidth: true + Layout.preferredHeight: Math.min(270, count * 40) + Layout.topMargin: 8 + Layout.bottomMargin: 8 + clip: true + spacing: 0 + model: filteredModel + + delegate: Rectangle { + id: itemDelegate + required property int index + required property string name + required property string exec + required property string comment + + width: resultsListView.width + height: 40 + radius: 0 + color: spotWindow.selectedIndex === index ? Qt.rgba(0.812, 0.988, 0.0, 0.08) : "transparent" + + Rectangle { + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + width: 2 + color: Theme.accentVolt + visible: spotWindow.selectedIndex === index + } + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 14 + anchors.rightMargin: 14 + spacing: 12 + + ColumnLayout { + Layout.fillWidth: true + spacing: 2 + + Text { + text: itemDelegate.name + font.family: Theme.fontSans + font.pixelSize: 14 + color: Theme.textPrimary + elide: Text.ElideRight + Layout.fillWidth: true + } + + Text { + text: itemDelegate.comment || itemDelegate.exec + font.family: Theme.fontMono + font.pixelSize: 11 + color: Theme.textMuted + elide: Text.ElideRight + Layout.fillWidth: true + } + } + + Text { + text: "↩" + font.pixelSize: 14 + color: spotWindow.selectedIndex === itemDelegate.index + ? Theme.textPrimary : "transparent" + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onEntered: spotWindow.selectedIndex = itemDelegate.index + onClicked: GlobalState.launchApp(itemDelegate.exec) + } } } } diff --git a/home/hypr/quickshell/components/SystemHUD.qml b/home/hypr/quickshell/components/SystemHUD.qml new file mode 100644 index 0000000..c42bc00 --- /dev/null +++ b/home/hypr/quickshell/components/SystemHUD.qml @@ -0,0 +1,685 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import Quickshell.Wayland +import Quickshell.Io +import "../theme" +import ".." + +PanelWindow { + id: root + + anchors { + top: true + bottom: true + left: true + } + + margins { + left: Theme.sidebarWidth + top: Theme.topStripHeight + } + + implicitWidth: Theme.hudWidth + + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.namespace: "quickshell-systemhud" + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + + visible: GlobalState.systemHudVisible + + color: "transparent" + + property real slideOffset: GlobalState.systemHudVisible ? 0 : -Theme.hudWidth + Behavior on slideOffset { + NumberAnimation { + duration: Theme.animSmooth + easing.type: Easing.OutQuint + } + } + + Shortcut { + sequence: "Escape" + onActivated: GlobalState.systemHudVisible = false + } + + component SectionHeader: Item { + property string sectionNum: "00" + property string title: "Section" + implicitWidth: parent.width + implicitHeight: 24 + + RowLayout { + anchors.fill: parent + spacing: 8 + Text { + text: sectionNum + " //" + font.family: Theme.fontMono + font.pixelSize: 12 + color: Theme.accentVolt + } + Text { + text: title + font.family: Theme.fontSerif + font.italic: true + font.pixelSize: 16 + color: Theme.textPrimary + Layout.fillWidth: true + } + } + } + + component ToggleCard: Rectangle { + property string title: "" + property string icon: "" + property bool active: false + signal clicked() + + Layout.preferredWidth: 160 + Layout.preferredHeight: 56 + color: Theme.bgPrimary + border.color: active ? Qt.rgba(Theme.accentVolt.r, Theme.accentVolt.g, Theme.accentVolt.b, 0.3) : Theme.glassBorder + border.width: 1 + radius: 4 + + Behavior on border.color { ColorAnimation { duration: Theme.animFast } } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onClicked: clicked() + onEntered: if (!active) border.color = Theme.glassBorderHover + onExited: if (!active) border.color = Theme.glassBorder + } + + RowLayout { + anchors.fill: parent + anchors.margins: 12 + spacing: 12 + Image { + source: icon ? Qt.resolvedUrl("../" + icon) : "" + Layout.preferredWidth: 24 + Layout.preferredHeight: 24 + sourceSize: Qt.size(24, 24) + visible: parent.icon !== "" + } + Text { + text: title + color: Theme.textPrimary + font.family: Theme.fontSans + font.pixelSize: 13 + Layout.fillWidth: true + elide: Text.ElideRight + } + } + } + + component CustomSlider: Item { + property real value: 0 + signal valueChangedUser(real newVal) + + implicitWidth: parent.width + implicitHeight: 24 + + Rectangle { + id: track + anchors.verticalCenter: parent.verticalCenter + width: parent.width - 40 + height: 6 + color: Theme.bgSecondary + radius: 3 + + Rectangle { + id: fill + width: track.width * (parent.parent.value / 100) + height: track.height + color: Theme.accentVolt + radius: 3 + } + + Rectangle { + width: 14 + height: 14 + radius: 7 + color: Theme.textPrimary + anchors.verticalCenter: parent.verticalCenter + x: Math.max(0, fill.width - 7) + } + + MouseArea { + anchors.fill: parent + anchors.margins: -10 + function updateValue(mouseX) { + let p = Math.max(0, Math.min(1, mouseX / track.width)) + parent.parent.valueChangedUser(p * 100) + } + onPressed: (mouse) => updateValue(mouse.x) + onPositionChanged: (mouse) => updateValue(mouse.x) + } + } + Text { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + text: Math.round(parent.value) + "%" + color: Theme.textPrimary + font.family: Theme.fontMono + font.pixelSize: 12 + } + } + + component ProgressBarItem: ColumnLayout { + property string label: "" + property real percent: 0 + property string valueText: "" + property color barColor: Theme.accentVolt + + Layout.fillWidth: true + spacing: 4 + + RowLayout { + Layout.fillWidth: true + Text { + text: parent.parent.label + color: Theme.textPrimary + font.family: Theme.fontMono + font.pixelSize: 12 + } + Item { Layout.fillWidth: true } + Text { + text: parent.parent.valueText + color: Theme.textSecondary + font.family: Theme.fontMono + font.pixelSize: 12 + } + } + + Rectangle { + Layout.fillWidth: true + height: 8 + color: Theme.bgSecondary + radius: 4 + + Rectangle { + width: parent.width * (parent.parent.percent / 100) + height: parent.height + color: parent.parent.barColor + radius: 4 + Behavior on width { NumberAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } } + Behavior on color { ColorAnimation { duration: Theme.animFast } } + } + } + } + + Rectangle { + id: panelContent + anchors.fill: parent + color: Theme.glassBg + opacity: GlobalState.systemHudVisible ? 1 : 0 + + transform: Translate { x: slideOffset } + + Behavior on opacity { + NumberAnimation { + duration: Theme.animSmooth + easing.type: Easing.OutQuint + } + } + + Rectangle { + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + width: 1 + color: Theme.glassBorder + } + + Item { + id: header + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + height: 60 + + Rectangle { + anchors.bottom: parent.bottom + width: parent.width + height: 1 + color: Theme.glassBorder + } + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 20 + anchors.rightMargin: 20 + + Text { + text: "System HUD" + font.family: Theme.fontSerif + font.italic: true + font.pixelSize: 19 + color: Theme.textPrimary + Layout.fillWidth: true + } + + Text { + text: "✕" + color: Theme.textMuted + font.pixelSize: 16 + MouseArea { + anchors.fill: parent + anchors.margins: -10 + hoverEnabled: true + onEntered: parent.color = Theme.textPrimary + onExited: parent.color = Theme.textMuted + onClicked: GlobalState.systemHudVisible = false + } + } + } + } + + ScrollView { + id: scrollView + anchors.top: header.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + contentWidth: availableWidth + clip: true + + ColumnLayout { + id: contentLayout + width: parent.width - 40 + x: 20 + spacing: 32 + + Item { Layout.preferredHeight: 16 } // Top padding + + // Section 1: Toggles + ColumnLayout { + id: sectionToggles + Layout.fillWidth: true + spacing: 16 + SectionHeader { sectionNum: "01"; title: "Toggles" } + GridLayout { + columns: 2 + columnSpacing: 12 + rowSpacing: 12 + Layout.fillWidth: true + + ToggleCard { + title: GlobalState.wifiSsid !== "" ? GlobalState.wifiSsid : "Wi-Fi" + icon: "assets/wifi.svg" + active: GlobalState.wifiConnected + onClicked: { + let proc = Quickshell.createProcess("sh", ["-c", "nm-connection-editor || foot -e nmtui"]) + proc.start() + } + } + + ToggleCard { + title: "Bluetooth" + icon: "assets/bluetooth.svg" + active: false // Adjust if there's a bluetooth state + onClicked: { + let proc = Quickshell.createProcess("sh", ["-c", "foot -e bluetoothctl"]) + proc.start() + } + } + + ToggleCard { + property bool dndActive: false + title: "Do Not Disturb" + icon: "" // Optional DND icon + active: dndActive + onClicked: dndActive = !dndActive + } + + ToggleCard { + title: GlobalState.volumeMuted ? "Muted" : "Sound" + icon: "" // Optional sound icon + active: !GlobalState.volumeMuted + onClicked: GlobalState.toggleVolumeMute() + } + } + } + + // Section 2: Volume + ColumnLayout { + id: sectionVolume + Layout.fillWidth: true + spacing: 16 + SectionHeader { sectionNum: "02"; title: "Volume" } + + CustomSlider { + Layout.fillWidth: true + value: GlobalState.volume + onValueChangedUser: (val) => GlobalState.volume = val // Adjust to actual volume setter if needed + } + + RowLayout { + Layout.fillWidth: true + Text { + text: GlobalState.volumeMuted ? "Unmute" : "Mute" + color: Theme.textSecondary + font.family: Theme.fontSans + font.pixelSize: 13 + MouseArea { + anchors.fill: parent + onClicked: GlobalState.toggleVolumeMute() + } + } + Item { Layout.fillWidth: true } + Text { + text: "Sound Preferences..." + color: Theme.textMuted + font.family: Theme.fontSans + font.pixelSize: 13 + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: parent.color = Theme.textPrimary + onExited: parent.color = Theme.textMuted + onClicked: { + let proc = Quickshell.createProcess("pavucontrol", []) + proc.start() + } + } + } + } + } + + // Section 3: Brightness + ColumnLayout { + id: sectionBrightness + Layout.fillWidth: true + spacing: 16 + SectionHeader { sectionNum: "03"; title: "Brightness" } + + CustomSlider { + Layout.fillWidth: true + value: GlobalState.brightness + onValueChangedUser: (val) => GlobalState.setBrightness(val) + } + } + + // Section 4: Performance + ColumnLayout { + id: sectionPerformance + Layout.fillWidth: true + spacing: 16 + SectionHeader { sectionNum: "04"; title: "Performance" } + + ProgressBarItem { + label: "CPU" + percent: GlobalState.cpuPercent + valueText: GlobalState.cpuPercent + "%" + barColor: GlobalState.cpuPercent > 80 ? Theme.accentCoral : Theme.accentVolt + } + + ProgressBarItem { + label: "RAM" + percent: (GlobalState.memUsedGb / Math.max(1, GlobalState.memTotalGb)) * 100 + valueText: GlobalState.memUsedGb + " / " + GlobalState.memTotalGb + " GB" + } + + ProgressBarItem { + label: "VRAM" + percent: (GlobalState.gpuVramUsed / Math.max(1, GlobalState.gpuVramTotal)) * 100 + valueText: GlobalState.gpuVramUsed + " / " + GlobalState.gpuVramTotal + " GB" + } + } + + // Section 5: Network + ColumnLayout { + id: sectionNetwork + Layout.fillWidth: true + spacing: 16 + SectionHeader { sectionNum: "05"; title: "Network" } + + RowLayout { + spacing: 8 + Rectangle { + width: 8 + height: 8 + radius: 4 + color: GlobalState.wifiConnected ? Theme.accentVolt : Theme.accentCoral + } + Text { + text: GlobalState.wifiConnected ? "Connected" : "Disconnected" + color: Theme.textPrimary + font.family: Theme.fontSans + font.pixelSize: 14 + } + } + + Text { + text: "SSID: " + (GlobalState.wifiSsid !== "" ? GlobalState.wifiSsid : "None") + color: Theme.textSecondary + font.family: Theme.fontSans + font.pixelSize: 13 + } + + Text { + text: "IP: " + GlobalState.ipAddress + color: Theme.textSecondary + font.family: Theme.fontMono + font.pixelSize: 13 + } + + Text { + text: "Network Preferences..." + color: Theme.textMuted + font.family: Theme.fontSans + font.pixelSize: 13 + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: parent.color = Theme.textPrimary + onExited: parent.color = Theme.textMuted + onClicked: { + let proc = Quickshell.createProcess("sh", ["-c", "nm-connection-editor || foot -e nmtui"]) + proc.start() + } + } + } + } + + // Section 6: Battery + ColumnLayout { + id: sectionBattery + Layout.fillWidth: true + spacing: 16 + visible: GlobalState.batteryPresent + SectionHeader { sectionNum: "06"; title: "Battery" } + + RowLayout { + spacing: 16 + Text { + text: GlobalState.batteryPercent + "%" + font.family: Theme.fontMono + font.pixelSize: 24 + color: GlobalState.batteryStatus === "Charging" ? Theme.accentVolt : + (GlobalState.batteryPercent < 20 ? Theme.accentCoral : Theme.textPrimary) + } + Text { + text: GlobalState.batteryStatus + color: Theme.textSecondary + font.family: Theme.fontSans + font.pixelSize: 14 + } + } + } + + // Section 7: Now Playing + ColumnLayout { + id: sectionNowPlaying + Layout.fillWidth: true + spacing: 16 + visible: GlobalState.mediaTitle !== "" + SectionHeader { sectionNum: "07"; title: "Now Playing" } + + ColumnLayout { + spacing: 4 + Text { + text: GlobalState.mediaTitle + color: Theme.textPrimary + font.family: Theme.fontSans + font.bold: true + font.pixelSize: 14 + Layout.fillWidth: true + elide: Text.ElideRight + } + Text { + text: GlobalState.mediaArtist + color: Theme.textSecondary + font.family: Theme.fontSans + font.pixelSize: 12 + Layout.fillWidth: true + elide: Text.ElideRight + } + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 16 + + Text { + text: "⏮" + font.pixelSize: 20 + color: Theme.textSecondary + MouseArea { + anchors.fill: parent + anchors.margins: -10 + hoverEnabled: true + onEntered: parent.color = Theme.accentVolt + onExited: parent.color = Theme.textSecondary + onClicked: GlobalState.mediaPrevious() + } + } + Text { + text: GlobalState.mediaStatus === "Playing" ? "⏸" : "▶" + font.pixelSize: 24 + color: Theme.textSecondary + MouseArea { + anchors.fill: parent + anchors.margins: -10 + hoverEnabled: true + onEntered: parent.color = Theme.accentVolt + onExited: parent.color = Theme.textSecondary + onClicked: GlobalState.mediaPlayPause() + } + } + Text { + text: "⏭" + font.pixelSize: 20 + color: Theme.textSecondary + MouseArea { + anchors.fill: parent + anchors.margins: -10 + hoverEnabled: true + onEntered: parent.color = Theme.accentVolt + onExited: parent.color = Theme.textSecondary + onClicked: GlobalState.mediaNext() + } + } + } + } + + // Section 8: Calendar + ColumnLayout { + id: sectionCalendar + Layout.fillWidth: true + spacing: 16 + SectionHeader { sectionNum: "08"; title: "Calendar" } + + RowLayout { + spacing: 12 + Text { + text: GlobalState.timeString + font.family: Theme.fontMono + font.pixelSize: 22 + color: Theme.accentVolt + } + Text { + text: GlobalState.fullDateString + font.family: Theme.fontSans + font.pixelSize: 13 + color: Theme.textSecondary + } + } + + GridLayout { + columns: 7 + columnSpacing: 4 + rowSpacing: 4 + Layout.alignment: Qt.AlignHCenter + + Repeater { + model: ["S", "M", "T", "W", "T", "F", "S"] + Text { + text: modelData + font.family: Theme.fontMono + font.pixelSize: 11 + color: Theme.textMuted + horizontalAlignment: Text.AlignHCenter + Layout.preferredWidth: 32 + } + } + + Repeater { + model: 31 + Rectangle { + property bool isToday: (index + 1) === new Date().getDate() + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + color: isToday ? Theme.accentVolt : "transparent" + radius: isToday ? 0 : 4 + + Text { + anchors.centerIn: parent + text: index + 1 + color: parent.isToday ? "#ffffff" : Theme.textSecondary + font.family: Theme.fontMono + font.pixelSize: 12 + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: if (!parent.isToday) parent.color = Qt.rgba(1, 1, 1, 0.04) + onExited: if (!parent.isToday) parent.color = "transparent" + } + } + } + } + } + + Item { Layout.preferredHeight: 20 } // Bottom padding + } + } + } + + // Auto-scroll to active section when changed + Connections { + target: GlobalState + function onSystemHudActiveSectionChanged() { + if (!GlobalState.systemHudVisible) return; + + let targetY = 0; + if (GlobalState.systemHudActiveSection === "volume") targetY = sectionVolume.y; + else if (GlobalState.systemHudActiveSection === "brightness") targetY = sectionBrightness.y; + else if (GlobalState.systemHudActiveSection === "performance") targetY = sectionPerformance.y; + else if (GlobalState.systemHudActiveSection === "network") targetY = sectionNetwork.y; + else if (GlobalState.systemHudActiveSection === "battery") targetY = sectionBattery.y; + else if (GlobalState.systemHudActiveSection === "nowplaying") targetY = sectionNowPlaying.y; + else if (GlobalState.systemHudActiveSection === "calendar") targetY = sectionCalendar.y; + + if (targetY > 0) { + // Ensure targetY doesn't scroll past max possible scroll + let maxY = Math.max(0, contentLayout.height - scrollView.height); + scrollView.ScrollBar.vertical.position = Math.min(targetY, maxY) / contentLayout.height; + } + } + } +} diff --git a/home/hypr/quickshell/components/SystemInfo.qml b/home/hypr/quickshell/components/SystemInfo.qml new file mode 100644 index 0000000..9d351c1 --- /dev/null +++ b/home/hypr/quickshell/components/SystemInfo.qml @@ -0,0 +1,257 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import Quickshell.Wayland +import Quickshell.Io +import "../theme" +import ".." + +PanelWindow { + id: systemInfoDialog + + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: visible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None + + anchors { + top: true + bottom: true + left: true + right: true + } + + visible: GlobalState.aboutMacVisible + color: Qt.rgba(0, 0, 0, 0.35) + + // Close on click outside or Escape + MouseArea { + anchors.fill: parent + onClicked: GlobalState.aboutMacVisible = false + } + Item { + anchors.fill: parent + focus: true + Keys.onEscapePressed: GlobalState.aboutMacVisible = false + } + + property string cpuName: "" + property string gpuName: "" + + // Process to get CPU info + Process { + id: cpuProcess + command: ["sh", "-c", "grep 'model name' /proc/cpuinfo | head -n1 | cut -d':' -f2 | xargs"] + running: true + stdout: StdioCollector { + onDataChanged: { + const val = String(data).trim(); + if (val.length > 0) systemInfoDialog.cpuName = val; + } + } + } + + // Process to get GPU info + Process { + id: gpuProcess + command: ["sh", "-c", "nvidia-smi --query-gpu=name --format=csv,noheader | head -n1"] + running: true + stdout: StdioCollector { + onDataChanged: { + const val = String(data).trim(); + if (val.length > 0) systemInfoDialog.gpuName = val; + } + } + } + + Rectangle { + id: dialogBox + width: 480 + height: 320 + anchors.centerIn: parent + color: Theme.glassBg + border.color: Theme.glassBorder + border.width: 1 + radius: 0 + + MouseArea { + anchors.fill: parent + // Eat clicks so they don't close the dialog + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + // Header bar + Item { + Layout.fillWidth: true + Layout.preferredHeight: 40 + + Rectangle { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: Theme.glassBorder + } + + Rectangle { + id: closeBtn + width: 40 + height: 40 + anchors.left: parent.left + color: closeMouseArea.containsMouse ? Theme.accentCoral : "transparent" + + Text { + anchors.centerIn: parent + text: "✕" + color: closeMouseArea.containsMouse ? Theme.textPrimary : Theme.textSecondary + font.pixelSize: 14 + } + + MouseArea { + id: closeMouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: GlobalState.aboutMacVisible = false + } + } + + Text { + anchors.centerIn: parent + text: "System Info" + font.family: Theme.fontSerif + font.italic: true + font.pixelSize: 15 + color: Theme.textPrimary + } + } + + // Body + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + Column { + anchors.centerIn: parent + spacing: 8 + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: "Silicon Volt" + font.family: Theme.fontSerif + font.italic: true + font.pixelSize: 24 + color: Theme.accentVolt + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: "NixOS // Hyprland" + font.family: Theme.fontMono + font.pixelSize: 12 + color: Theme.textMuted + bottomPadding: 16 + } + + Grid { + anchors.horizontalCenter: parent.horizontalCenter + columns: 2 + spacing: 12 + rowSpacing: 8 + + // HOST + Text { width: 100; horizontalAlignment: Text.AlignRight; text: "HOST"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted } + Text { text: "hades"; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary } + + // CPU + Text { width: 100; horizontalAlignment: Text.AlignRight; text: "CPU"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted } + Text { id: cpuText; text: systemInfoDialog.cpuName || "Loading..."; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary } + + // MEMORY + Text { width: 100; horizontalAlignment: Text.AlignRight; text: "MEMORY"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted } + Text { text: GlobalState.memUsedGb + " / " + GlobalState.memTotalGb + " GB"; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary } + + // GPU + Text { width: 100; horizontalAlignment: Text.AlignRight; text: "GPU"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted } + Text { id: gpuText; text: systemInfoDialog.gpuName || "Loading..."; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary } + + // VRAM + Text { width: 100; horizontalAlignment: Text.AlignRight; text: "VRAM"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted } + Text { text: GlobalState.gpuVramUsed + " / " + GlobalState.gpuVramTotal + " GB"; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary } + + // WM + Text { width: 100; horizontalAlignment: Text.AlignRight; text: "WM"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted } + Text { text: "Hyprland"; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary } + + // OS + Text { width: 100; horizontalAlignment: Text.AlignRight; text: "OS"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted } + Text { text: "NixOS"; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary } + } + } + } + + // Footer bar + Item { + Layout.fillWidth: true + Layout.preferredHeight: 44 + + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: Theme.glassBorder + } + + component FooterButton: Rectangle { + id: btn + property string text: "" + property alias command: proc.command + + width: btnText.width + 24 + height: 28 + anchors.verticalCenter: parent.verticalCenter + color: btnMouse.containsMouse ? Qt.rgba(Theme.accentVolt.r, Theme.accentVolt.g, Theme.accentVolt.b, 0.1) : "transparent" + border.color: Theme.glassBorder + border.width: 1 + radius: 0 + + Text { + id: btnText + anchors.centerIn: parent + text: btn.text + font.family: Theme.fontSans + font.pixelSize: 12 + color: btnMouse.containsMouse ? Theme.accentVolt : Theme.textMuted + } + + MouseArea { + id: btnMouse + anchors.fill: parent + hoverEnabled: true + onClicked: proc.running = true + } + + Process { + id: proc + } + } + + FooterButton { + anchors.left: parent.left + anchors.leftMargin: 16 + text: "System Report..." + command: ["foot", "-e", "btop"] + } + + FooterButton { + anchors.right: parent.right + anchors.rightMargin: 16 + text: "Software Update..." + command: ["foot", "-e", "sudo", "nixos-rebuild", "dry-activate"] + } + } + } + } +} diff --git a/home/hypr/quickshell/components/TopStrip.qml b/home/hypr/quickshell/components/TopStrip.qml new file mode 100644 index 0000000..1e9924b --- /dev/null +++ b/home/hypr/quickshell/components/TopStrip.qml @@ -0,0 +1,101 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import "../theme" +import ".." + +PanelWindow { + id: root + + property var modelData + screen: modelData + + anchors { + top: true + left: true + right: true + } + + margins.left: Theme.sidebarWidth + + implicitHeight: Theme.topStripHeight + color: Qt.rgba(Theme.bgPrimary.r, Theme.bgPrimary.g, Theme.bgPrimary.b, 0.92) + exclusiveZone: Theme.topStripHeight + + WlrLayershell.layer: WlrLayer.Top + WlrLayershell.namespace: "quickshell-topstrip" + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + + Rectangle { + id: bottomBorder + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + } + height: 1 + color: Theme.glassBorder + } + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 16 + anchors.rightMargin: 16 + spacing: 16 + + // Left Section + Text { + id: workspaceIndicator + Layout.alignment: Qt.AlignVCenter + font.family: Theme.fontMono + font.pixelSize: 12 + color: Theme.textMuted + + function pad(num) { + if (num === undefined || num === null) return "00"; + return num < 10 ? "0" + num : num.toString(); + } + + text: pad(GlobalState.activeWorkspace) + " // " + (GlobalState.workspaceNames[GlobalState.activeWorkspace] !== undefined ? GlobalState.workspaceNames[GlobalState.activeWorkspace] : "main") + + Behavior on color { + ColorAnimation { duration: 150 } + } + } + + // Center Section + Text { + id: windowTitle + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + horizontalAlignment: Text.AlignHCenter + font.family: Theme.fontSerif + font.italic: true + font.pixelSize: Theme.fontSizeLarge + color: Theme.textSecondary + elide: Text.ElideRight + maximumLineCount: 1 + + text: GlobalState.activeWindowTitle + + Behavior on color { + ColorAnimation { duration: 150 } + } + } + + // Right Section + Text { + id: clock + Layout.alignment: Qt.AlignVCenter + font.family: Theme.fontMono + font.pixelSize: 12 + color: Theme.textPrimary + text: GlobalState.timeString + + Behavior on color { + ColorAnimation { duration: 150 } + } + } + } +} diff --git a/home/hypr/quickshell/shell.qml b/home/hypr/quickshell/shell.qml index 7464589..e2b5c93 100644 --- a/home/hypr/quickshell/shell.qml +++ b/home/hypr/quickshell/shell.qml @@ -25,55 +25,60 @@ Scope { } IpcHandler { - target: "controlCenter" + target: "systemHud" function toggle() { - GlobalState.toggleControlCenter(); + GlobalState.toggleSystemHud(); } function open() { - GlobalState.controlCenterVisible = true; + GlobalState.systemHudVisible = true; } function close() { - GlobalState.controlCenterVisible = false; + GlobalState.systemHudVisible = false; } } IpcHandler { - target: "aboutMac" + target: "systemInfo" function toggle() { GlobalState.toggleAboutMac(); } } // ========================================================================= - // MULTI-MONITOR BARS & DOCKS + // LEFT SIDEBAR (Primary monitor only) // ========================================================================= - // 1. macOS Catalina Top Menu Bar (Spawned per monitor) - Variants { - model: Quickshell.screens - MenuBar {} + SidePanel { + screen: Quickshell.screens[0] } - // 2. macOS Catalina Floating Dock (Spawned per monitor) + // ========================================================================= + // TOP STATUS STRIP (All monitors) + // ========================================================================= Variants { model: Quickshell.screens - Dock {} + TopStrip {} } // ========================================================================= // GLOBAL MODAL OVERLAYS // ========================================================================= - // 3. Spotlight Search Overlay + // Spotlight Search Overlay Spotlight { id: spotlightOverlay } - // 4. Control Center & Notification Center Sidebar - ControlCenter { - id: controlCenterSidebar + // System HUD Flyout Panel + SystemHUD { + id: systemHudPanel } - // 5. About This Mac Dialog - AboutMac { - id: aboutMacDialog + // Quick Menu (Power/Session Menu) + QuickMenu { + id: quickMenuOverlay + } + + // System Info Dialog + SystemInfo { + id: systemInfoDialog } } diff --git a/home/hypr/quickshell/theme/Theme.qml b/home/hypr/quickshell/theme/Theme.qml index 7c3d85a..7d02f90 100644 --- a/home/hypr/quickshell/theme/Theme.qml +++ b/home/hypr/quickshell/theme/Theme.qml @@ -2,56 +2,45 @@ 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" - + // Colors + readonly property color bgPrimary: "#131313" + readonly property color bgSecondary: "#0e0e0e" + readonly property color glassBg: "#1c1b1b" + readonly property color glassBorder: Qt.rgba(1.0, 1.0, 1.0, 0.08) + readonly property color glassBorderHover: Qt.rgba(1.0, 1.0, 1.0, 0.18) + readonly property color accentVolt: "#CFFC00" + readonly property color accentVoltGlow: Qt.rgba(0.812, 0.988, 0.0, 0.25) + readonly property color accentVoltLight: "#e3ff66" + readonly property color accentBlue: "#00BFFF" + readonly property color accentCoral: "#FF7A59" readonly property color textPrimary: "#FFFFFF" - readonly property color textSecondary: 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 textSecondary: "#e5e2e1" + readonly property color textMuted: "#ADAAAA" readonly property color textDisabled: Qt.rgba(1.0, 1.0, 1.0, 0.25) // Typography - readonly property string fontFamily: "SF Pro Display, SF Pro Text, Helvetica Neue, Cantarell, Liberation Sans, sans-serif" - readonly property int fontSizeMenuBar: 13 + readonly property string fontSerif: "Instrument Serif, Georgia, serif" + readonly property string fontSans: "IBM Plex Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif" + readonly property string fontMono: "Nanum Gothic Coding, JetBrains Mono, monospace" + readonly property int fontSizeSmall: 11 readonly property int fontSizeNormal: 13 readonly property int fontSizeLarge: 15 readonly property int fontSizeTitle: 19 - readonly property int fontSizeSpotlight: 20 + readonly property int fontSizeDisplay: 24 + readonly property int fontSizeWorkspace: 12 - // 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 + // Geometry + readonly property int radiusNone: 0 + readonly property int sidebarWidth: 52 + readonly property int topStripHeight: 28 + readonly property int hudWidth: 360 + readonly property int iconSize: 36 + readonly property int iconSizeSmall: 20 + readonly property int workspaceIndicatorWidth: 2 // Animation Durations - readonly property int animFast: 120 - readonly property int animNormal: 220 - readonly property int animSmooth: 320 + readonly property int animSmooth: 400 + readonly property int animFast: 200 + readonly property int animBounce: 600 } diff --git a/home/pi.nix b/home/pi.nix new file mode 100644 index 0000000..80e5fbd --- /dev/null +++ b/home/pi.nix @@ -0,0 +1,7 @@ +{ inputs, config, ... }: +{ + imports = [ inputs.pi.homeModules.default ]; + programs.pi.coding-agent = { + enable = true; + }; +} diff --git a/modules/apps/dev.nix b/modules/apps/dev.nix index 7401c34..4e791ec 100644 --- a/modules/apps/dev.nix +++ b/modules/apps/dev.nix @@ -5,7 +5,6 @@ # Core Editors & AI Agents neovim antigravity-cli - pi-coding-agent # Languages & Toolchains uv diff --git a/modules/apps/local_ai.nix b/modules/apps/local_ai.nix index d466498..fe1710a 100644 --- a/modules/apps/local_ai.nix +++ b/modules/apps/local_ai.nix @@ -32,7 +32,6 @@ }; environment.systemPackages = with pkgs; [ - comfy-ui-cuda unstable.ollama-cuda ]; } diff --git a/modules/core/nix.nix b/modules/core/nix.nix index b41d0d2..7beb8f8 100644 --- a/modules/core/nix.nix +++ b/modules/core/nix.nix @@ -8,8 +8,23 @@ "flakes" ]; auto-optimise-store = true; - substituters = [ "https://cache.nixos-cuda.org" ]; - trusted-public-keys = [ "cache.nixos-cuda.org:74DUi4Ye579gUqzH4ziL9IyiJBlDpMRn9MBN8oNan9M=" ]; + extra-substituters = [ + "https://cache.nixos-cuda.org" + "https://pi.cachix.org" + "https://comfyui.cachix.org" + "https://nix-community.cachix.org" + ]; + extra-trusted-public-keys = [ + "cache.nixos-cuda.org:74DUi4Ye579gUqzH4ziL9IyiJBlDpMRn9MBN8oNan9M=" + "pi.cachix.org-1:lGeoGJaZ5ZDabuRzkcD5EBTNnDM4HJ1vqeOxlWk1Flk=" + "comfyui.cachix.org-1:33mf9VzoIjzVbp0zwj+fT51HG0y31ZTK3nzYZAX0rec=" + "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" + ]; + trusted-users = [ + "root" + "@wheel" + "anish" + ]; }; gc = {