quickshell: replace macOS-style shell with standalone overview module

Swap the illogical-impulse-derived components for the upstream
quickshell-overview standalone module, update Hyprland keybinds
(Super+Tab overview, rofi launcher), refresh foot colors and
starship theme, and adjust related host/module wiring.
This commit is contained in:
2026-09-18 23:25:42 -07:00
parent 47c0c9c719
commit 367e21ef95
75 changed files with 3634 additions and 5773 deletions

View File

@@ -33,37 +33,42 @@ in
style = "beam";
blink = "yes";
beam-thickness = 1.5;
color = "0e0e0e cffc00";
};
mouse = {
hide-when-typing = "yes";
};
colors = {
colors-dark = {
# Silicon Volt palette (matches home/hypr theme + mako):
# bgPrimary #131313, bgSecondary #0e0e0e, glassBg #1c1b1b,
# textPrimary #ffffff, textSecondary #e5e2e1, textMuted #adaaaa,
# accents: volt #cffc00, coral #ff7a59, blue #00bfff
alpha = 0.92;
background = "131313";
foreground = "e5e2e1";
# Silicon Volt palette (anish.govind.cc)
regular0 = "1c1b1b"; # Glass Black
regular1 = "ff7a59"; # Coral Red
regular2 = "cffc00"; # Electric Lime (green)
regular3 = "e3ff66"; # Volt Light (yellow)
regular4 = "00bfff"; # Sky Blue
# Cursor text and cursor (background) colors
cursor = "0e0e0e cffc00";
regular0 = "0e0e0e"; # bgSecondary
regular1 = "ff7a59"; # accentCoral
regular2 = "cffc00"; # accentVolt (green)
regular3 = "e3ff66"; # accentVoltLight (yellow)
regular4 = "00bfff"; # accentBlue
regular5 = "c6c6c7"; # Neutral Gray (magenta)
regular6 = "66d9ff"; # Light Cyan
regular7 = "e5e2e1"; # Light Gray
regular7 = "e5e2e1"; # textSecondary
# Bright Colors
bright0 = "5c5a59"; # Dim Gray
bright0 = "2a2929"; # surfaceVariant
bright1 = "ff9a82"; # Bright Coral
bright2 = "dfff66"; # Bright Lime
bright3 = "f0ff8c"; # Bright Volt
bright4 = "66d9ff"; # Bright Blue
bright5 = "e0e0e1"; # Bright Gray
bright6 = "99e6ff"; # Bright Cyan
bright7 = "ffffff"; # Pure White
bright7 = "ffffff"; # textPrimary
# Selection & URLs - volt lime selection like the site
selection-foreground = "0e0e0e";

View File

@@ -207,7 +207,7 @@ in
_args = [
"hyprland.start"
(mkLua ''function()
hl.exec_cmd("quickshell")
hl.exec_cmd("qs")
hl.exec_cmd("hyprpaper")
hl.exec_cmd("mako")
hl.exec_cmd("wl-paste --type text --watch cliphist store")
@@ -225,8 +225,9 @@ in
# Launchers & Window Controls
{ _args = [ (mkLua "mainMod .. \" + Q\"") (mkLua "hl.dsp.exec_cmd(terminal)") ]; }
{ _args = [ (mkLua "mainMod .. \" + C\"") (mkLua "hl.dsp.window.close()") ]; }
{ _args = [ (mkLua "mainMod .. \" + SPACE\"") (mkLua "hl.dsp.exec_cmd(\"quickshell ipc call spotlight toggle || rofi -show combi -show-icons\")") ]; }
{ _args = [ (mkLua "mainMod .. \" + N\"") (mkLua "hl.dsp.exec_cmd(\"quickshell ipc call controlCenter toggle || swaync-client -t -sw\")") ]; }
{ _args = [ (mkLua "mainMod .. \" + SPACE\"") (mkLua "hl.dsp.exec_cmd(\"rofi -show combi -show-icons\")") ]; }
{ _args = [ (mkLua "mainMod .. \" + N\"") (mkLua "hl.dsp.exec_cmd(\"swaync-client -t -sw\")") ]; }
{ _args = [ (mkLua "mainMod .. \" + TAB\"") (mkLua "hl.dsp.exec_cmd(\"qs ipc call overview toggle\")") ]; }
{ _args = [ (mkLua "mainMod .. \" + V\"") (mkLua "hl.dsp.window.float({ action = \"toggle\" })") ]; }
{ _args = [ (mkLua "mainMod .. \" + R\"") (mkLua "hl.dsp.exec_cmd(\"rofi -show combi\")") ]; }
{ _args = [ (mkLua "mainMod .. \" + G\"") (mkLua "hl.dsp.group.toggle()") ]; }

View File

@@ -8,16 +8,12 @@ in
{
home.packages = with pkgs; [
quickshellPkg
brightnessctl
pamixer
playerctl
libnotify
grim
slurp
wl-clipboard
qt6.qtwayland
];
# Link Quickshell configuration into ~/.config/quickshell
# Quickshell overview module for Hyprland (Super+Tab workspace overview).
# Vendored from: https://github.com/Shanu-Kumawat/quickshell-overview
# Placed at the quickshell config root so a bare `qs` finds shell.qml.
xdg.configFile."quickshell" = {
source = ./quickshell;
recursive = true;

View File

@@ -1,351 +0,0 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
QtObject {
id: root
// Modal / Overlay states
property bool spotlightVisible: false
property bool controlCenterVisible: false
property bool aboutMacVisible: false
property bool appleMenuVisible: false
property string activeDropdownMenu: ""
property bool volumePopupVisible: false
property bool wifiPopupVisible: false
property bool batteryPopupVisible: false
property bool calendarPopupVisible: false
// Active Window & Compositor info
property string activeWindowTitle: "Desktop"
property string activeWindowClass: "Finder"
property var runningApps: []
// Date & Time
property string timeString: "9:41 AM"
property string dateString: "Mon Oct 21"
property string fullDateString: "Monday, October 21, 2026"
// System Monitoring States
property int cpuPercent: 0
property int memPercent: 0
property string memUsedGb: "0.0"
property string memTotalGb: "0.0"
property int volume: 50
property bool volumeMuted: false
property int brightness: 100
property bool wifiConnected: true
property string connectionType: "off" // "wifi" | "eth" | "off"
property string wifiSsid: "Wi-Fi"
property string ipAddress: ""
property bool batteryPresent: false
property int batteryPercent: 100
property bool batteryCharging: false
property string batteryStatus: "Full"
// Disk Monitoring (root filesystem)
property int diskPercent: 0
property string diskUsedGb: "0"
property string diskTotalGb: "0"
// GPU VRAM Monitoring (NVIDIA)
property string gpuVramUsed: "0"
property string gpuVramTotal: "0"
property int gpuVramPercent: 0
// System HUD state (replaces individual popup states)
property bool systemHudVisible: false
property string systemHudActiveSection: "" // "volume", "wifi", "calendar", etc.
// Workspace tracking
property int activeWorkspace: 1
property var workspaceList: [1, 2, 3, 4, 5]
property var workspaceNames: ({1: "main", 2: "browser", 3: "code", 4: "media", 5: "misc"})
// Media Player State (MPRIS)
property string mediaTitle: ""
property string mediaArtist: ""
property bool mediaPlaying: false
// Close all popups and dropdowns
function closeAllPopups() {
appleMenuVisible = false;
activeDropdownMenu = "";
volumePopupVisible = false;
wifiPopupVisible = false;
batteryPopupVisible = false;
calendarPopupVisible = false;
systemHudVisible = false;
}
function toggleSpotlight() {
closeAllPopups();
spotlightVisible = !spotlightVisible;
}
function toggleControlCenter() {
closeAllPopups();
controlCenterVisible = !controlCenterVisible;
}
function toggleAboutMac() {
closeAllPopups();
aboutMacVisible = !aboutMacVisible;
}
function toggleSystemHud() {
closeAllPopups();
systemHudVisible = !systemHudVisible;
}
function switchWorkspace(n) {
// NOTE: Hyprland's Lua config bridge requires typed dispatcher calls
// (plain `hyprctl dispatch workspace 2` is mangled into invalid Lua).
Hyprland.dispatch("hl.dsp.focus({workspace = " + n + "})");
}
// Hyprland raw event processing
readonly property Connections hyprConn: Connections {
target: Hyprland
function onRawEvent(event) {
if (!event || !event.name) return;
const name = event.name;
const data = event.data !== undefined ? String(event.data) : "";
if (name === "workspace") {
const wsNum = parseInt(data);
if (!isNaN(wsNum)) root.activeWorkspace = wsNum;
} else if (name === "activewindow") {
const parts = data.split(",");
const cls = parts[0] ? parts[0].trim() : "";
const title = parts[1] ? parts[1].trim() : (cls || "");
root.activeWindowClass = cls && cls.length > 0 ? cls : "Desktop";
root.activeWindowTitle = title && title.length > 0 ? title : root.activeWindowClass;
} else if (name === "activewindowv2" || name === "openwindow" || name === "closewindow") {
// Update running clients
clientsProc.running = true;
}
}
}
// Time Updater Timer
readonly property Timer clockTimer: Timer {
interval: 1000
running: true
repeat: true
triggeredOnStart: true
onTriggered: {
const now = new Date();
root.timeString = Qt.formatDateTime(now, "h:mm AP");
root.dateString = Qt.formatDateTime(now, "ddd MMM d");
root.fullDateString = Qt.formatDateTime(now, "dddd, MMMM d, yyyy");
}
}
// System Stats Poll Timer
readonly property Timer statsTimer: Timer {
interval: 3000
running: true
repeat: true
triggeredOnStart: true
onTriggered: {
cpuProc.running = true;
memProc.running = true;
batProc.running = true;
wifiProc.running = true;
audioProc.running = true;
brightProc.running = true;
mprisProc.running = true;
gpuProc.running = true;
diskProc.running = true;
}
}
// Background Process Listeners
readonly property Process cpuProc: Process {
command: ["bash", "-c", "read -r c u n s i io ir st g gn < /proc/stat; t1=$((u+n+s+i+io+ir+st+g+gn)); i1=$((i+io)); sleep 0.15; read -r c u n s i io ir st g gn < /proc/stat; t2=$((u+n+s+i+io+ir+st+g+gn)); i2=$((i+io)); td=$((t2-t1)); id=$((i2-i1)); [ $td -gt 0 ] && echo $(( (td-id)*100/td )) || echo 0"]
stdout: StdioCollector {
onDataChanged: {
const val = parseInt(String(data).trim());
if (!isNaN(val)) root.cpuPercent = Math.max(0, Math.min(100, val));
}
}
}
readonly property Process memProc: Process {
command: ["bash", "-c", "t=$(grep MemTotal /proc/meminfo | awk '{print $2}'); a=$(grep MemAvailable /proc/meminfo | awk '{print $2}'); if [ -n \"$t\" ] && [ -n \"$a\" ] && [ \"$t\" -gt 0 ]; then u=$((t-a)); p=$((u*100/t)); ug=$(awk \"BEGIN {printf \\\"%.1f\\\", $u/1048576}\"); tg=$(awk \"BEGIN {printf \\\"%.1f\\\", $t/1048576}\"); echo \"$p $ug $tg\"; fi"]
stdout: StdioCollector {
onDataChanged: {
const parts = String(data).trim().split(" ");
if (parts.length >= 3) {
root.memPercent = parseInt(parts[0]) || 0;
root.memUsedGb = parts[1];
root.memTotalGb = parts[2];
}
}
}
}
readonly property Process batProc: Process {
command: ["bash", "-c", "b=$(find /sys/class/power_supply/ -name 'BAT*' 2>/dev/null | head -n 1); if [ -n \"$b\" ]; then cap=$(cat $b/capacity 2>/dev/null || echo 100); st=$(cat $b/status 2>/dev/null || echo Full); echo \"1 $cap $st\"; else echo \"0 100 AC\"; fi"]
stdout: StdioCollector {
onDataChanged: {
const parts = String(data).trim().split(" ");
if (parts.length >= 3) {
root.batteryPresent = parts[0] === "1";
root.batteryPercent = parseInt(parts[1]) || 100;
root.batteryStatus = parts[2];
root.batteryCharging = parts[2] === "Charging" || parts[2] === "Full";
}
}
}
}
readonly property Process wifiProc: Process {
// Emits: connectionType|ssid|ip ("wifi" | "eth" | "off")
command: ["bash", "-c", "route=$(ip -4 route get 1.1.1.1 2>/dev/null); ip=$(echo \"$route\" | awk '{for(i=1;i<=NF;i++) if($i==\"src\") print $(i+1)}'); dev=$(echo \"$route\" | awk '{for(i=1;i<=NF;i++) if($i==\"dev\") print $(i+1)}'); type=\"off\"; ssid=\"\"; if [ -n \"$dev\" ]; then if [ -d \"/sys/class/net/$dev/wireless\" ]; then type=\"wifi\"; ssid=$(iwgetid -r 2>/dev/null); if [ -z \"$ssid\" ] && command -v nmcli &>/dev/null; then ssid=$(nmcli -t -f active,ssid dev wifi 2>/dev/null | grep '^yes:' | cut -d':' -f2); fi; else type=\"eth\"; fi; fi; echo \"$type|$ssid|$ip\""]
stdout: StdioCollector {
onDataChanged: {
const parts = String(data).trim().split("|");
if (parts.length >= 3) {
root.connectionType = parts[0] !== "" ? parts[0] : "off";
root.wifiSsid = parts[1];
root.ipAddress = parts[2];
root.wifiConnected = parts[2] !== "" && parts[2] !== "Offline";
}
}
}
}
readonly property Process audioProc: Process {
command: ["bash", "-c", "if command -v pactl &>/dev/null; then v=$(pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null | grep -Po '[0-9]+(?=%)' | head -n 1); m=$(pactl get-sink-mute @DEFAULT_SINK@ 2>/dev/null | grep -q yes && echo 1 || echo 0); echo \"${v:-50} $m\"; fi"]
stdout: StdioCollector {
onDataChanged: {
const parts = String(data).trim().split(" ");
if (parts.length >= 2) {
root.volume = parseInt(parts[0]) || 50;
root.volumeMuted = parts[1] === "1";
}
}
}
}
readonly property Process brightProc: Process {
command: ["bash", "-c", "if command -v brightnessctl &>/dev/null; then b=$(brightnessctl -m 2>/dev/null | cut -d, -f4 | tr -d '%'); echo \"${b:-100}\"; else echo 100; fi"]
stdout: StdioCollector {
onDataChanged: {
const val = parseInt(String(data).trim());
if (!isNaN(val)) root.brightness = val;
}
}
}
readonly property Process diskProc: Process {
// Emits: usedGB totalGB percent for the root filesystem
command: ["bash", "-c", "df -BG / 2>/dev/null | awk 'NR==2 {u=$3; t=$2; gsub(/G/,\"\",u); gsub(/G/,\"\",t); if (t+0 > 0) print u, t, int(u*100/t)}'"]
stdout: StdioCollector {
onDataChanged: {
const parts = String(data).trim().split(" ");
if (parts.length >= 3) {
root.diskUsedGb = parts[0];
root.diskTotalGb = parts[1];
root.diskPercent = parseInt(parts[2]) || 0;
}
}
}
}
readonly property Process mprisProc: Process {
command: ["bash", "-c", "if command -v playerctl &>/dev/null; then t=$(playerctl metadata title 2>/dev/null); a=$(playerctl metadata artist 2>/dev/null); s=$(playerctl status 2>/dev/null); echo \"$s|$t|$a\"; fi"]
stdout: StdioCollector {
onDataChanged: {
const parts = String(data).trim().split("|");
if (parts.length >= 3) {
root.mediaPlaying = parts[0] === "Playing";
root.mediaTitle = parts[1] || "";
root.mediaArtist = parts[2] || "";
} else {
root.mediaPlaying = false;
root.mediaTitle = "";
root.mediaArtist = "";
}
}
}
}
readonly property Process gpuProc: Process {
command: ["bash", "-c", "nvidia-smi --query-gpu=memory.used,memory.total --format=csv,noheader,nounits 2>/dev/null || echo '0, 0'"]
stdout: StdioCollector {
onDataChanged: {
const parts = String(data).trim().split(",");
if (parts.length >= 2) {
const usedMiB = parseInt(parts[0].trim());
const totalMiB = parseInt(parts[1].trim());
if (!isNaN(usedMiB) && !isNaN(totalMiB) && totalMiB > 0) {
root.gpuVramUsed = (usedMiB / 1024).toFixed(1);
root.gpuVramTotal = (totalMiB / 1024).toFixed(1);
root.gpuVramPercent = Math.round((usedMiB / totalMiB) * 100);
}
}
}
}
}
readonly property Process clientsProc: Process {
command: ["bash", "-c", "if command -v hyprctl &>/dev/null; then hyprctl -j clients 2>/dev/null; else echo '[]'; fi"]
stdout: StdioCollector {
onDataChanged: {
try {
const parsed = JSON.parse(String(data).trim());
if (Array.isArray(parsed)) {
root.runningApps = parsed;
}
} catch(e) {}
}
}
}
// Helper functions to control system (execDetached: fire-and-forget)
function setVolume(val) {
root.volume = Math.max(0, Math.min(100, val));
Quickshell.execDetached(["pactl", "set-sink-volume", "@DEFAULT_SINK@", `${root.volume}%`]);
}
function toggleVolumeMute() {
Quickshell.execDetached(["pactl", "set-sink-mute", "@DEFAULT_SINK@", "toggle"]);
root.volumeMuted = !root.volumeMuted;
}
function setBrightness(val) {
root.brightness = Math.max(1, Math.min(100, val));
Quickshell.execDetached(["brightnessctl", "set", `${root.brightness}%`]);
}
function mediaPlayPause() {
Quickshell.execDetached(["playerctl", "play-pause"]);
mprisProc.running = true;
}
function mediaNext() {
Quickshell.execDetached(["playerctl", "next"]);
mprisProc.running = true;
}
function mediaPrevious() {
Quickshell.execDetached(["playerctl", "previous"]);
mprisProc.running = true;
}
function launchApp(execCmd) {
if (!execCmd) return;
Quickshell.execDetached(["sh", "-c", execCmd]);
closeAllPopups();
spotlightVisible = false;
}
}

View File

@@ -0,0 +1,702 @@
# Quickshell Overview for Hyprland
<div align="center">
A standalone workspace overview module for Hyprland using Quickshell - shows all workspaces with live window previews, drag-and-drop support, and Super+Tab keybind.
![Quickshell](https://img.shields.io/badge/Quickshell-0.2.0-blue?style=flat-square)
![Hyprland](https://img.shields.io/badge/Hyprland-Compatible-purple?style=flat-square)
![Qt6](https://img.shields.io/badge/Qt-6-green?style=flat-square)
![License](https://img.shields.io/badge/License-GPL-orange?style=flat-square)
</div>
---
## 📸 Preview
![Overview Screenshot](assets/image.png)
https://github.com/user-attachments/assets/e8f392d7-d831-4dec-9cd3-fb93d1ccc21c
> *Workspace overview showing live window previews with drag-and-drop support*
---
## ✨ Features
- 🖼️ Visual workspace overview showing all workspaces and windows
- 🖥️ Multi-monitor support with proper scaling and vertical/rotated monitors [in experimental branch]
- 📐 Smart row hiding - optionally hide empty workspace rows
- 🎯 Click windows to focus them
- 🖱️ Middle-click windows to close them
- 🔄 Drag and drop windows between workspaces
- ⌨️ Keyboard navigation (Arrow keys, vim keys, number shortcuts)
- 🖱️ Auto-close on focus loss / outside click
- 💡 Hover tooltips showing window information
- 🎨 Material Design 3 theming
- ⚡ Smooth animations and transitions
## 📦 Installation
### Arch Linux (AUR)
For Arch Linux users, you can install directly from the AUR:
```bash
# Using yay
yay -S quickshell-overview-git
# Using paru
paru -S quickshell-overview-git
```
On AUR installs, module files are package-managed under:
```text
/etc/xdg/quickshell/overview/
```
Put your custom settings in:
```text
~/.config/quickshell/overview/config.json
```
Then add the keybind and auto-start to your Hyprland config (see Setup steps 2-4 below).
### Prerequisites
- **Hyprland** compositor
- **Quickshell** ([installation guide](https://quickshell.org/docs/v0.1.0/guide/install-setup/))
- **Qt 6** with modules: QtQuick, QtQuick.Controls
### Setup
1. **Install module files** (choose one):
- **Git clone (manual install):**
```bash
git clone https://github.com/Shanu-Kumawat/quickshell-overview ~/.config/quickshell/overview
```
- **AUR package:** use the command above (`yay -S quickshell-overview-git` or `paru -S ...`)
2. **Add keybind** to your Hyprland config:
*For Hyprland 0.55+ (`~/.config/hypr/hyprland.lua`):*
```lua
hl.bind("SUPER + TAB", hl.dsp.exec_cmd("qs ipc -c overview call overview toggle"))
```
*For Hyprland 0.54 and older (`~/.config/hypr/hyprland.conf`):*
```conf
bind = Super, TAB, exec, qs ipc -c overview call overview toggle
```
4. **Auto-start** the overview (add to Hyprland config):
*For Hyprland 0.55+ (`~/.config/hypr/hyprland.lua`):*
```lua
hl.on("hyprland.start", function ()
hl.exec_cmd("qs -c overview")
end)
```
*For Hyprland 0.54 and older (`~/.config/hypr/hyprland.conf`):*
```conf
exec-once = qs -c overview
```
6. **Reload Hyprland**:
```bash
hyprctl reload
```
### Manual Start (if needed)
```bash
qs -c overview &
```
### NixOS
For NixOS users, ensure Quickshell has access to required Qt6 modules:
```nix
# In your configuration.nix or home-manager config
environment.systemPackages = with pkgs; [
quickshell
qt6.qtwayland
];
```
If you're using home-manager:
```nix
home.packages = with pkgs; [
quickshell
qt6.qtwayland
];
```
## 🎮 Usage
| Action | Description |
|--------|-------------|
| **Super + Tab** | Toggle the overview |
| **Arrow Keys / h/l** | Navigate left/right within current row* |
| **Up/Down / j/k** | Navigate between workspace rows |
| **1-9, 0** | Jump to Nth workspace in current group (0 = 10th) |
| **Mouse wheel on grid** | Move across all normal workspaces, wrapping from last to first |
| **Escape / Enter** | Close the overview |
| **Click outside overview** | Close the overview when `overview.closeOnFocusLoss` is enabled (default) |
| **Click workspace** | Switch to that workspace |
| **Click window** | Focus that window |
| **Middle-click window** | Close that window |
| **Drag window** | Move window to different workspace |
> *When `hideEmptyRows` is enabled, left/right navigation wraps within the current visible row for better UX
---
## ⚙️ Configuration
> **⚠️ Want to change size, position, workspace count, or toggles?**
> Create/edit `~/.config/quickshell/overview/config.json`.
`Config.qml` inside the module is now treated as defaults. User overrides are read from:
- `$XDG_CONFIG_HOME/quickshell/overview/config.json`
- fallback: `~/.config/quickshell/overview/config.json`
> **Note:** After editing `config.json`, manually restart overview for changes to apply:
> `qs ipc -c overview call overview close && qs -c overview`
### Quick Start
```bash
mkdir -p ~/.config/quickshell/overview
cp /etc/xdg/quickshell/overview/config.example.json ~/.config/quickshell/overview/config.json
```
If you installed from git clone instead of AUR, copy from your repo path:
```bash
cp ~/.config/quickshell/overview/config.example.json ~/.config/quickshell/overview/config.json
```
### Workspace Grid
Edit `~/.config/quickshell/overview/config.json`:
```json
{
"overview": {
"rows": 2,
"columns": 5,
"scale": 0.16,
"enable": true,
"hideEmptyRows": true,
"closeOnFocusLoss": true,
"useWorkspaceMap": false,
"workspaceMap": [0, 10],
"orderRightLeft": false,
"orderBottomUp": false,
"previewsEnabled": true,
"previewMode": "live",
"includeInactiveMonitorPreviews": true,
"previewRecaptureDelayMs": 60,
"showSpecialWorkspaces": true,
"specialWorkspaces": [],
"specialWorkspaceColumns": 5,
"emptyWorkspaceWallpaper": "",
"specialEmptyWorkspaceWallpaper": "",
"effects": {
"enableBackdrop": false,
"backdropOpacity": 0.28,
"panelOpacity": 0.92,
"workspaceOpacity": 0.86,
"emptyWorkspaceWallpaperOverlayOpacity": 0.18,
"windowOverlayOpacity": 0.22,
"enableBlur": false,
"glassMode": false,
"glassTintStrength": 0.35,
"glassBorderOpacity": 0.72,
"glassShineOpacity": 0.14
},
"workspaceSpacing": 5,
"backgroundPadding": 10,
"workspaceNumberBaseSize": 250
}
}
```
**Common adjustments:**
- **Too small?** Increase `scale` (try 0.20 or 0.25)
- **Too big?** Decrease `scale` (try 0.12 or 0.14)
- **More workspaces?** Change `rows` and `columns` (e.g., 3 rows × 4 columns = 12 workspaces)
- **Reverse order?** Set `orderRightLeft` and/or `orderBottomUp` to `true`
- **Prefer the overview to stay open after outside clicks/focus changes?** Set `closeOnFocusLoss` to `false`
- **Per-monitor workspace groups?** Enable `useWorkspaceMap` and set `workspaceMap` (e.g. `[0,10]`)
- **Show special workspaces below grid?** Keep `showSpecialWorkspaces: true` and optionally prefill `specialWorkspaces`
- **Lower memory use?** Set `previewMode` to `event` and `includeInactiveMonitorPreviews` to `false`
- **Transparency / blur?** Tune `overview.effects.*` (details below)
**Hide empty workspace rows:**
- Set `hideEmptyRows: true` to automatically hide rows that have no windows
- Keeps your overview clean by only showing rows with active workspaces
- The current workspace row is always visible, even if empty
- Arrow key navigation (left/right) stays within the current row when enabled
- Great for 2-row setups where you rarely use workspaces 6-10
**Close on focus loss / outside click:**
- `closeOnFocusLoss` defaults to `true`
- When enabled, clicking outside the overview closes it, similar to menus, dropdowns, and launchers
- The overview also closes when its Hyprland focus grab is cleared
- Set `closeOnFocusLoss: false` if you want the previous behavior where the overview can remain open after focus changes
### Position
Edit `~/.config/quickshell/overview/config.json`:
```json
{
"position": {
"topMargin": 100
}
}
```
Increase `topMargin` to move the overview down. Decrease it to move up.
### Window Preview
```json
{
"windowPreview": {
"showIcons": true,
"iconToWindowRatio": 0.25,
"iconToWindowRatioCompact": 0.45,
"xwaylandIndicatorToIconRatio": 0.35,
"inactiveMonitorOpacity": 0.4,
"cropToFill": false
}
}
```
- `cropToFill`: crop full-screen windows to fill the workspace preview when `true`; keep the full window in preview with possible horizontal/vertical "padding" bars when `false`
### Performance Tuning
```json
{
"overview": {
"previewsEnabled": true,
"previewMode": "live",
"includeInactiveMonitorPreviews": true,
"previewRecaptureDelayMs": 60
},
"hacks": {
"hyprlandEventDebounceMs": 40
}
}
```
- `overview.previewsEnabled`: turn all window screencopy previews on/off
- `overview.previewMode`: `live` (best visuals, more RAM) or `event` (lower RAM, refreshes on window events)
- `overview.includeInactiveMonitorPreviews`: when `false`, only current monitor windows get preview capture
- `overview.previewRecaptureDelayMs`: delay used for event-mode snapshot refresh (lower = faster updates)
- `hacks.hyprlandEventDebounceMs`: coalesces Hyprland event refreshes to reduce command churn
### Special Workspaces
```json
{
"overview": {
"showSpecialWorkspaces": true,
"specialWorkspaces": ["stash", "music", "scratch"],
"specialWorkspaceColumns": 5
}
}
```
- `showSpecialWorkspaces`: renders special workspaces in a strip under the normal grid
- `specialWorkspaces`: optional preconfigured special workspace names (without the `special:` prefix)
- `specialWorkspaceColumns`: how many special tiles per row before wrapping
- `emptyWorkspaceWallpaper`: optional image path used as the background for normal workspace tiles
- `specialEmptyWorkspaceWallpaper`: optional image path used as the background for special workspace tiles
Interaction behavior:
- Preconfigured special workspaces appear in the overview even when they are empty
- The special strip shows active special workspaces plus any names you preconfigure
- This is useful for fixed workflows like `stash`, `music`, or `scratch`
- Click a special tile to run `togglespecialworkspace <name>`
- Click the `+` tile to create and open a new special workspace
- Drag a window onto a special tile to move it with `movetoworkspacesilent special:<name>`
- Drag a window onto the `+` tile to auto-create a new special workspace (`stash`, `stash-2`, ...), even when no special workspace is currently open or preconfigured
- Special windows are visible directly in those tiles
- Restart Quickshell after changing `config.json`, otherwise the special workspace list will not refresh immediately
Normal workspace scrolling:
- Scroll on the normal workspace grid to move the active workspace/highlighter across all normal workspaces
- Scrolling wraps from the last workspace back to `1`, and from `1` back to the last
### Workspace Wallpaper
```json
{
"overview": {
"emptyWorkspaceWallpaper": "/home/your-user/Pictures/wallpaper.png",
"specialEmptyWorkspaceWallpaper": "/home/your-user/Pictures/special-wallpaper.png",
"effects": {
"emptyWorkspaceWallpaperOverlayOpacity": 0.12
}
}
}
```
- Normal workspaces can use a wallpaper as their background
- Special workspaces can use a different wallpaper as their background
- The wallpaper remains visible behind floating or partially covered windows
- If no special workspaces exist yet, that special wallpaper is also used for the `+` create tile
- `emptyWorkspaceWallpaperOverlayOpacity` controls how much tint is applied over that wallpaper
- Use an absolute path for the image for the most reliable behavior
- Restart Quickshell after changing `config.json`, otherwise the wallpaper path will not refresh immediately
Demo:
![Workspace wallpaper demo](assets/Workspace_Wallpaper.png)
### Transparency & Blur
```json
{
"overview": {
"effects": {
"enableBackdrop": false,
"backdropOpacity": 0.28,
"panelOpacity": 0.92,
"workspaceOpacity": 0.86,
"emptyWorkspaceWallpaperOverlayOpacity": 0.18,
"windowOverlayOpacity": 0.22,
"enableBlur": false,
"glassMode": false,
"glassTintStrength": 0.35,
"glassBorderOpacity": 0.72,
"glassShineOpacity": 0.14
}
}
}
```
- `enableBackdrop`: show/hide full-screen dim backdrop behind overview
- `backdropOpacity`: opacity of backdrop dim layer (`0` to `1`)
- `panelOpacity`: opacity of overview panel container (`0` to `1`)
- `workspaceOpacity`: opacity of each workspace tile (`0` to `1`)
- `emptyWorkspaceWallpaperOverlayOpacity`: tint strength over empty-workspace wallpaper (`0` to `1`)
- `windowOverlayOpacity`: opacity of the color tint over window previews (`0` to `1`)
- `enableBlur`: switches layer namespace to `quickshell:overview-blur`
- `glassMode`: enables a glass-like tint + softer transparency preset for panel/workspaces/windows
- `glassTintStrength`: tint mixing strength for glass mode (`0` to `1`)
- `glassBorderOpacity`: border alpha used by glass mode (`0` to `1`)
- `glassShineOpacity`: top highlight strength for glass reflections (`0` to `1`)
Stronger glass preset:
```json
{
"overview": {
"effects": {
"enableBackdrop": true,
"enableBlur": true,
"panelOpacity": 0.55,
"workspaceOpacity": 0.48,
"emptyWorkspaceWallpaperOverlayOpacity": 0.10,
"windowOverlayOpacity": 0.08,
"glassMode": true,
"glassTintStrength": 0.55,
"glassBorderOpacity": 0.85,
"glassShineOpacity": 0.32
}
}
}
```
For Hyprland blur, add layer rules (example):
```ini
layerrule = blur true, match:namespace quickshell:overview-blur
layerrule = ignore_alpha 0.2, match:namespace quickshell:overview-blur
```
If `enableBlur` is `false`, namespace remains `quickshell:overview`.
Low-memory preset:
```json
{
"overview": {
"previewMode": "event",
"includeInactiveMonitorPreviews": false
},
"hacks": {
"hyprlandEventDebounceMs": 80
}
}
```
### Full Example
```json
{
"appearance": {
"colorSource": "default",
"caelestia": {
"autoRefresh": true,
"refreshInterval": 2000,
"accentProfile": "vibrant"
},
"rounding": {
"unsharpen": 2,
"verysmall": 8,
"small": 12,
"normal": 17,
"large": 23,
"full": 9999,
"screenRounding": 23,
"windowRounding": 18
},
"font": {
"family": {
"main": "sans-serif",
"title": "sans-serif",
"expressive": "sans-serif"
},
"pixelSize": {
"smaller": 12,
"small": 15,
"normal": 16,
"larger": 19,
"huge": 22
}
},
"animation": {
"duration": {
"elementMove": 500,
"elementMoveEnter": 400,
"elementMoveFast": 200
}
},
"sizes": {
"elevationMargin": 10
}
},
"overview": {
"rows": 2,
"columns": 5,
"scale": 0.16,
"enable": true,
"hideEmptyRows": true,
"closeOnFocusLoss": true,
"useWorkspaceMap": false,
"workspaceMap": [0, 10],
"orderRightLeft": false,
"orderBottomUp": false,
"previewsEnabled": true,
"previewMode": "live",
"includeInactiveMonitorPreviews": true,
"previewRecaptureDelayMs": 60,
"showSpecialWorkspaces": true,
"specialWorkspaces": [],
"specialWorkspaceColumns": 5,
"emptyWorkspaceWallpaper": "",
"specialEmptyWorkspaceWallpaper": "",
"effects": {
"enableBackdrop": false,
"backdropOpacity": 0.28,
"panelOpacity": 0.92,
"workspaceOpacity": 0.86,
"emptyWorkspaceWallpaperOverlayOpacity": 0.18,
"windowOverlayOpacity": 0.22,
"enableBlur": false,
"glassMode": false,
"glassTintStrength": 0.35,
"glassBorderOpacity": 0.72,
"glassShineOpacity": 0.14
},
"workspaceSpacing": 5,
"backgroundPadding": 10,
"workspaceNumberBaseSize": 250
},
"position": {
"topMargin": 100
},
"windowPreview": {
"showIcons": true,
"iconToWindowRatio": 0.25,
"iconToWindowRatioCompact": 0.45,
"xwaylandIndicatorToIconRatio": 0.35,
"inactiveMonitorOpacity": 0.4,
"cropToFill": false
},
"hacks": {
"arbitraryRaceConditionDelay": 150,
"hyprlandEventDebounceMs": 40
}
}
```
### Theme & Colors
Most theme sizing/timing options are now configurable via `config.json`:
- `appearance.colorSource` (`default`, `matugen`, `caelestia`)
- `appearance.caelestia.*` (`autoRefresh`, `refreshInterval`, `accentProfile`)
- `appearance.rounding.*`
- `appearance.font.*`
- `appearance.animation.duration.*`
- `appearance.sizes.elevationMargin`
For full color palette customization, edit `~/.config/quickshell/overview/common/Appearance.qml`.
### Matugen (Dynamic Colors from Wallpaper)
[Matugen](https://github.com/InioX/matugen) lets you generate Material You colors from your wallpaper and apply them to the overview automatically.
**1. Install matugen** - follow [matugen's install guide](https://github.com/InioX/matugen?tab=readme-ov-file#installation)
**2. Copy the template** from this repo to matugen's templates folder:
```bash
mkdir -p ~/.config/matugen/templates
cp ~/.config/quickshell/overview/quickshell-overview.qml ~/.config/matugen/templates/
```
**3. Add this to `~/.config/matugen/config.toml`** (create the file if it doesn't exist):
```toml
[templates.quickshell_overview]
input_path = "./templates/quickshell-overview.qml"
output_path = "~/.config/quickshell/overview/common/Appearance.colors.qml"
```
**4. Enable it** in `~/.config/quickshell/overview/config.json`:
```json
{
"appearance": {
"colorSource": "matugen"
}
}
```
**5. Run matugen** with your wallpaper to generate colors:
```bash
matugen image /path/to/your/wallpaper.jpg
```
This generates `Appearance.colors.qml` which the overview loads automatically. Re-run step 5 whenever you change your wallpaper.
### Caelestia
If you use Caelestia, set the source to `caelestia`:
```json
{
"appearance": {
"colorSource": "caelestia",
"caelestia": {
"autoRefresh": true,
"refreshInterval": 2000,
"accentProfile": "vibrant"
}
}
}
```
Overview reads the active palette from `caelestia scheme get` and refreshes it live when `autoRefresh` is enabled, so wallpaper/scheme changes can apply without restarting overview.
---
## 📋 Requirements
- **Hyprland** compositor (tested on latest versions)
- **Quickshell** (Qt6-based shell framework)
- **Qt 6** with the following modules:
- QtQuick
- QtQuick.Controls
- QtQuick.Layouts
- Quickshell.Wayland
- Quickshell.Hyprland
## 🚫 Removed Features (from original illogical-impulse)
The following features were removed to make it standalone:
- App search functionality
- Emoji picker
- Clipboard history integration
- Search widget
- Integration with the full illogical-impulse shell ecosystem
## 📁 File Structure
```
~/.config/quickshell/overview/
├── shell.qml # Main entry point
├── README.md # This file
├── config.example.json # User override template
├── hyprland-config.conf # Configuration reference
├── common/
│ ├── Appearance.qml # Theme and styling
│ ├── Config.qml # Default config + user override loader
│ ├── functions/
│ │ └── ColorUtils.qml # Color manipulation utilities
│ └── widgets/
│ ├── StyledText.qml # Styled text component
│ ├── StyledRectangularShadow.qml
│ ├── StyledToolTip.qml
│ └── StyledToolTipContent.qml
├── services/
│ ├── GlobalStates.qml # Global state management
│ └── HyprlandData.qml # Hyprland data provider
└── modules/
└── overview/
├── Overview.qml # Main overview component
├── OverviewWidget.qml # Workspace grid widget
└── OverviewWindow.qml # Individual window preview
```
## 🎯 IPC Commands
```bash
# Toggle overview
qs ipc -c overview call overview toggle
# Open overview
qs ipc -c overview call overview open
# Close overview
qs ipc -c overview call overview close
```
## 🐛 Known Issues
- Window icons may fallback to generic icon if app class name doesn't match icon theme
- Potential crashes during rapid window state changes due to Wayland screencopy buffer management
## 💖 Support
If this project helps your setup and you want to support continued maintenance, you can sponsor here:
https://github.com/sponsors/Shanu-Kumawat
## Credits
Extracted from the overview feature in [illogical-impulse](https://github.com/end-4/dots-hyprland) by [end-4](https://github.com/end-4).
Adapted as a standalone component for Hyprland + Quickshell users who want just the overview functionality.
---
<div align="center">
**Note:** Maintenance will be limited due to time constraints, but **PRs and code improvements are welcome!** Feel free to contribute or fork for your own needs.
Made with ❤️ for the Hyprland community
</div>

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 170 170" width="100%" height="100%">
<path fill="#ffffff" d="M150.37 130.25c-2.45 5.66-5.35 10.87-8.71 15.66-4.58 6.53-8.33 11.05-11.22 13.56-4.48 4.12-9.28 6.23-14.42 6.35-3.69 0-8.14-1.05-13.32-3.18-5.19-2.12-9.97-3.17-14.34-3.17-4.58 0-9.49 1.05-14.75 3.17-5.26 2.13-9.5 3.24-12.74 3.35-4.35.13-9.16-1.9-14.42-6.08-3.7-3.04-7.69-7.75-11.96-14.12-6.53-9.78-11.75-21.2-15.66-34.25-3.9-13.06-5.86-25.2-5.86-36.42 0-14.35 3.69-26.4 11.08-36.17 7.39-9.78 16.74-14.77 28.05-14.99 4.35 0 9.49 1.25 15.42 3.75 5.94 2.5 9.78 3.75 11.53 3.75 1.52 0 5.58-1.29 12.18-3.87 6.6-2.58 11.89-3.75 15.88-3.52 11.52.44 20.98 4.7 28.38 12.79-10.43 6.31-15.54 15.11-15.33 26.41.22 8.91 3.69 16.52 10.42 22.82 6.74 6.31 14.78 9.9 24.13 10.77-2.17 6.74-4.89 13.7-8.15 20.87zM119.22 31.02c0-7.18 2.61-13.8 7.83-19.89 5.22-6.09 11.63-9.9 19.24-11.43.22 1.3.33 2.5.33 3.59 0 7.18-2.72 13.92-8.15 20.22-5.43 6.31-12.06 10.01-19.25 11.1-.33-1.09-.5-2.29-.5-3.59z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1003 B

View File

@@ -1,20 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="100%" height="100%">
<defs>
<linearGradient id="appStoreBg" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#2DA9FF"/>
<stop offset="100%" stop-color="#0062E3"/>
</linearGradient>
<filter id="shadowAppStore" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#000000" flood-opacity="0.35"/>
</filter>
</defs>
<rect x="10" y="10" width="108" height="108" rx="24" fill="url(#appStoreBg)" filter="url(#shadowAppStore)" stroke="#4BB4FF" stroke-width="1.5"/>
<!-- Apple App Store stylized 'A' using bars -->
<!-- Left Leg (Pencil/Ruler) -->
<line x1="38" y1="94" x2="64" y2="34" stroke="#FFFFFF" stroke-width="9" stroke-linecap="round"/>
<!-- Right Leg (Brush) -->
<line x1="90" y1="94" x2="64" y2="34" stroke="#FFFFFF" stroke-width="9" stroke-linecap="round"/>
<!-- Crossbar -->
<line x1="30" y1="74" x2="98" y2="74" stroke="#FFFFFF" stroke-width="9" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,8 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 16" width="100%" height="100%">
<!-- Outer shell -->
<rect x="1" y="1" width="22" height="14" rx="3.5" fill="none" stroke="#FFFFFF" stroke-width="1.8"/>
<!-- Anode terminal -->
<path d="M25 5.5 C25.8 5.5 26.5 6.2 26.5 7 V9 C26.5 9.8 25.8 10.5 25 10.5 Z" fill="#FFFFFF"/>
<!-- Fill bar inside -->
<rect x="3.5" y="3.5" width="15" height="9" rx="2" fill="#FFFFFF"/>
</svg>

Before

Width:  |  Height:  |  Size: 439 B

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="100%" height="100%">
<path fill="none" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M7 7l10 10-5 5V2l5 5L7 17"/>
</svg>

Before

Width:  |  Height:  |  Size: 228 B

View File

@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="100%" height="100%">
<circle cx="12" cy="12" r="5" fill="#FFFFFF"/>
<path stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" d="M12 1v2 M12 21v2 M4.22 4.22l1.42 1.42 M18.36 18.36l1.42 1.42 M1 12h2 M21 12h2 M4.22 19.78l1.42-1.42 M18.36 5.64l1.42-1.42"/>
</svg>

Before

Width:  |  Height:  |  Size: 337 B

View File

@@ -1,26 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="100%" height="100%">
<defs>
<linearGradient id="safBg" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#EBF4FB"/>
<stop offset="100%" stop-color="#C2D8EB"/>
</linearGradient>
<linearGradient id="safCompass" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#00C7FF"/>
<stop offset="100%" stop-color="#0062E3"/>
</linearGradient>
<filter id="shadowSaf" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#000000" flood-opacity="0.35"/>
</filter>
</defs>
<rect x="10" y="10" width="108" height="108" rx="24" fill="url(#safBg)" filter="url(#shadowSaf)" stroke="#B0C4DE" stroke-width="1.5"/>
<circle cx="64" cy="64" r="44" fill="url(#safCompass)" stroke="#FFFFFF" stroke-width="2"/>
<!-- Dial ticks -->
<circle cx="64" cy="64" r="38" fill="none" stroke="#FFFFFF" stroke-width="1" stroke-dasharray="2 6" opacity="0.6"/>
<!-- Compass Needle Red/North -->
<polygon points="64,28 72,64 64,61 56,64" fill="#FF3B30"/>
<!-- Compass Needle White/South -->
<polygon points="64,100 72,64 64,67 56,64" fill="#FFFFFF"/>
<circle cx="64" cy="64" r="4" fill="#FFFFFF" stroke="#D0D0D0" stroke-width="1"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,40 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 160" width="100%" height="100%">
<defs>
<clipPath id="circleClip">
<circle cx="80" cy="80" r="72"/>
</clipPath>
<linearGradient id="skyGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#1A385C"/>
<stop offset="50%" stop-color="#C27A59"/>
<stop offset="100%" stop-color="#FFD6A5"/>
</linearGradient>
<linearGradient id="seaGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#1E4566"/>
<stop offset="100%" stop-color="#0E2338"/>
</linearGradient>
<linearGradient id="islandGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#544943"/>
<stop offset="100%" stop-color="#241E1C"/>
</linearGradient>
<filter id="badgeShadow">
<feDropShadow dx="0" dy="6" stdDeviation="6" flood-color="#000000" flood-opacity="0.4"/>
</filter>
</defs>
<circle cx="80" cy="80" r="74" fill="none" stroke="#FFFFFF" stroke-width="3" filter="url(#badgeShadow)"/>
<g clip-path="url(#circleClip)">
<!-- Sky -->
<rect x="0" y="0" width="160" height="160" fill="url(#skyGrad)"/>
<!-- Sun glow -->
<circle cx="95" cy="80" r="28" fill="#FFF2DE" opacity="0.6"/>
<!-- Distant Mountains -->
<polygon points="10,105 50,75 90,105" fill="#755E57" opacity="0.7"/>
<polygon points="70,105 115,70 155,105" fill="#5E4843" opacity="0.8"/>
<!-- Santa Catalina Island Cliffs -->
<path d="M0 110 Q40 85 85 102 Q125 75 160 115 L160 160 L0 160 Z" fill="url(#islandGrad)"/>
<!-- Ocean / Waves -->
<path d="M0 120 C30 115 60 125 100 118 C130 112 150 122 160 120 L160 160 L0 160 Z" fill="url(#seaGrad)"/>
<path d="M0 135 C40 130 80 140 120 132 C140 128 155 134 160 135 L160 160 L0 160 Z" fill="#132B45" opacity="0.6"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,23 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="100%" height="100%">
<defs>
<linearGradient id="codeBg" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#1E293B"/>
<stop offset="100%" stop-color="#0F172A"/>
</linearGradient>
<linearGradient id="codeBrackets" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#38BDF8"/>
<stop offset="100%" stop-color="#818CF8"/>
</linearGradient>
<filter id="shadowCode" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#000000" flood-opacity="0.35"/>
</filter>
</defs>
<rect x="10" y="10" width="108" height="108" rx="24" fill="url(#codeBg)" filter="url(#shadowCode)" stroke="#334155" stroke-width="1.5"/>
<!-- Left Bracket < -->
<path d="M46 42 L26 64 L46 86" fill="none" stroke="url(#codeBrackets)" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<!-- Right Bracket > -->
<path d="M82 42 L102 64 L82 86" fill="none" stroke="url(#codeBrackets)" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<!-- Slash / -->
<line x1="70" y1="36" x2="58" y2="92" stroke="#F43F5E" stroke-width="7" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,8 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="100%" height="100%">
<!-- Top switch -->
<rect x="3" y="5" width="18" height="6" rx="3" fill="none" stroke="#FFFFFF" stroke-width="1.8"/>
<circle cx="16" cy="8" r="2.2" fill="#FFFFFF"/>
<!-- Bottom switch -->
<rect x="3" y="13" width="18" height="6" rx="3" fill="none" stroke="#FFFFFF" stroke-width="1.8"/>
<circle cx="8" cy="16" r="2.2" fill="#FFFFFF"/>
</svg>

Before

Width:  |  Height:  |  Size: 441 B

View File

@@ -1,37 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="100%" height="100%">
<defs>
<linearGradient id="finderBg" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#E8E8E8"/>
<stop offset="100%" stop-color="#B0B0B0"/>
</linearGradient>
<linearGradient id="faceBlue" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#47A3F3"/>
<stop offset="100%" stop-color="#1D72DB"/>
</linearGradient>
<linearGradient id="faceLightBlue" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#9AD7F8"/>
<stop offset="100%" stop-color="#56AEF7"/>
</linearGradient>
<filter id="shadow" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#000000" flood-opacity="0.35"/>
</filter>
</defs>
<rect x="10" y="10" width="108" height="108" rx="24" fill="url(#finderBg)" filter="url(#shadow)" stroke="#A0A0A0" stroke-width="1.5"/>
<!-- Right half of face (Darker Blue) -->
<path d="M64 12 H94 C107.25 12 116 20.75 116 34 V94 C116 107.25 107.25 116 94 116 H64 Z" fill="url(#faceBlue)"/>
<!-- Left half of face (Lighter Blue) -->
<path d="M34 12 H64 V116 H34 C20.75 116 12 107.25 12 94 V34 C12 20.75 20.75 12 34 12 Z" fill="url(#faceLightBlue)"/>
<!-- Eyes -->
<ellipse cx="40" cy="52" rx="5" ry="9" fill="#1C2D42"/>
<ellipse cx="88" cy="52" rx="5" ry="9" fill="#1C2D42"/>
<!-- Nose (macOS Finder split curve) -->
<path d="M64 34 C64 34 65 62 61 68 C58 72 52 74 48 75" fill="none" stroke="#1C2D42" stroke-width="4.5" stroke-linecap="round"/>
<!-- Smile -->
<path d="M30 80 C36 102 92 102 98 80" fill="none" stroke="#1C2D42" stroke-width="5" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,25 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="100%" height="100%">
<defs>
<linearGradient id="folderBack" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#4CA1F5"/>
<stop offset="100%" stop-color="#1B6DC7"/>
</linearGradient>
<linearGradient id="folderFront" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#80C8FC"/>
<stop offset="100%" stop-color="#3894EE"/>
</linearGradient>
<filter id="shadowFolder" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#000000" flood-opacity="0.35"/>
</filter>
</defs>
<!-- Folder Back Tab -->
<path d="M16 28 C16 24 20 20 24 20 H50 L60 30 H104 C108 30 112 34 112 38 V96 C112 100 108 104 104 104 H24 C20 104 16 100 16 96 Z" fill="url(#folderBack)" filter="url(#shadowFolder)"/>
<!-- Folder Front Pocket -->
<path d="M12 44 C12 40 16 36 20 36 H108 C112 36 116 40 116 44 V98 C116 104 110 108 104 108 H24 C18 108 12 104 12 98 Z" fill="url(#folderFront)"/>
<!-- Download Downward Arrow Badge -->
<circle cx="64" cy="74" r="18" fill="#FFFFFF" opacity="0.9"/>
<path d="M64 62 V82 M54 74 L64 84 L74 74" fill="none" stroke="#1B6DC7" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,35 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="100%" height="100%">
<defs>
<linearGradient id="lpBg" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#485563"/>
<stop offset="100%" stop-color="#29323c"/>
</linearGradient>
<linearGradient id="rocketGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FFFFFF"/>
<stop offset="100%" stop-color="#D0D4DC"/>
</linearGradient>
<linearGradient id="fireGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#FFCC00"/>
<stop offset="100%" stop-color="#FF3B30"/>
</linearGradient>
<filter id="shadowLp" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#000000" flood-opacity="0.35"/>
</filter>
</defs>
<rect x="10" y="10" width="108" height="108" rx="24" fill="url(#lpBg)" filter="url(#shadowLp)" stroke="#5A6675" stroke-width="1.5"/>
<!-- Rocket Body -->
<path d="M88 28 C74 34 52 50 44 68 C40 76 40 84 42 88 C46 90 54 90 62 86 C80 78 96 56 102 42 C104 36 102 30 96 28 C93 27 90 27 88 28 Z" fill="url(#rocketGrad)"/>
<!-- Rocket Window -->
<circle cx="74" cy="54" r="7" fill="#0A84FF" stroke="#FFFFFF" stroke-width="2"/>
<!-- Rocket Wing Left -->
<path d="M48 76 C40 76 30 84 26 92 C34 94 44 90 48 84 Z" fill="#E02438"/>
<!-- Rocket Wing Right -->
<path d="M76 48 C76 40 84 30 92 26 C94 34 90 44 84 48 Z" fill="#E02438"/>
<!-- Fire Exhaust -->
<path d="M42 88 C36 94 32 104 34 108 C38 106 48 102 54 96 Z" fill="url(#fireGrad)"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,21 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="100%" height="100%">
<defs>
<linearGradient id="musicBg" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#FF385C"/>
<stop offset="100%" stop-color="#E00034"/>
</linearGradient>
<filter id="shadowMusic" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#000000" flood-opacity="0.35"/>
</filter>
</defs>
<rect x="10" y="10" width="108" height="108" rx="24" fill="url(#musicBg)" filter="url(#shadowMusic)" stroke="#FF6B8B" stroke-width="1.5"/>
<!-- Musical Eighth Notes -->
<!-- Stems and Beam -->
<path d="M48 76 V38 C48 34 50 32 54 32 H82 C86 32 88 34 88 38 V70" fill="none" stroke="#FFFFFF" stroke-width="6" stroke-linecap="round"/>
<!-- Beams -->
<polygon points="48,32 88,32 88,44 48,44" fill="#FFFFFF"/>
<!-- Note Heads -->
<ellipse cx="40" cy="80" rx="12" ry="8" transform="rotate(-20 40 80)" fill="#FFFFFF"/>
<ellipse cx="80" cy="74" rx="12" ry="8" transform="rotate(-20 80 74)" fill="#FFFFFF"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,44 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="100%" height="100%">
<defs>
<linearGradient id="prefBg" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#D8DBE2"/>
<stop offset="100%" stop-color="#8F96A3"/>
</linearGradient>
<linearGradient id="gearBig" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#7C8494"/>
<stop offset="100%" stop-color="#4A5260"/>
</linearGradient>
<linearGradient id="gearSmall" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#9CA3AF"/>
<stop offset="100%" stop-color="#5B6270"/>
</linearGradient>
<filter id="shadowPref" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#000000" flood-opacity="0.35"/>
</filter>
</defs>
<rect x="10" y="10" width="108" height="108" rx="24" fill="url(#prefBg)" filter="url(#shadowPref)" stroke="#B0B6C2" stroke-width="1.5"/>
<!-- Big Gear -->
<g transform="translate(50, 50)">
<circle cx="0" cy="0" r="28" fill="url(#gearBig)"/>
<!-- Teeth -->
<path d="M-6 -34 H6 V-26 H-6 Z M-6 26 H6 V34 H-6 Z M-34 -6 V6 H-26 V-6 Z M26 -6 V6 H34 V-6 Z" fill="url(#gearBig)"/>
<path d="M-6 -34 H6 V-26 H-6 Z" transform="rotate(45)" fill="url(#gearBig)"/>
<path d="M-6 -34 H6 V-26 H-6 Z" transform="rotate(135)" fill="url(#gearBig)"/>
<path d="M-6 -34 H6 V-26 H-6 Z" transform="rotate(225)" fill="url(#gearBig)"/>
<path d="M-6 -34 H6 V-26 H-6 Z" transform="rotate(315)" fill="url(#gearBig)"/>
<circle cx="0" cy="0" r="12" fill="url(#prefBg)" stroke="#3A404D" stroke-width="1.5"/>
</g>
<!-- Small Gear -->
<g transform="translate(86, 86)">
<circle cx="0" cy="0" r="18" fill="url(#gearSmall)"/>
<!-- Teeth -->
<path d="M-4 -22 H4 V-16 H-4 Z M-4 16 H4 V22 H-4 Z M-22 -4 V4 H-16 V-4 Z M16 -4 V4 H22 V-4 Z" fill="url(#gearSmall)"/>
<path d="M-4 -22 H4 V-16 H-4 Z" transform="rotate(45)" fill="url(#gearSmall)"/>
<path d="M-4 -22 H4 V-16 H-4 Z" transform="rotate(135)" fill="url(#gearSmall)"/>
<path d="M-4 -22 H4 V-16 H-4 Z" transform="rotate(225)" fill="url(#gearSmall)"/>
<path d="M-4 -22 H4 V-16 H-4 Z" transform="rotate(315)" fill="url(#gearSmall)"/>
<circle cx="0" cy="0" r="8" fill="url(#prefBg)" stroke="#3A404D" stroke-width="1.5"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="100%" height="100%">
<path fill="none" stroke="#FFFFFF" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" d="M10.5 18a7.5 7.5 0 1 1 0-15 7.5 7.5 0 0 1 0 15z M16 16l5 5"/>
</svg>

Before

Width:  |  Height:  |  Size: 263 B

View File

@@ -1,21 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="100%" height="100%">
<defs>
<linearGradient id="termBg" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#2D2D30"/>
<stop offset="100%" stop-color="#141416"/>
</linearGradient>
<filter id="shadowTerm" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#000000" flood-opacity="0.35"/>
</filter>
</defs>
<rect x="10" y="10" width="108" height="108" rx="24" fill="url(#termBg)" filter="url(#shadowTerm)" stroke="#4A4A50" stroke-width="1.5"/>
<rect x="18" y="24" width="92" height="80" rx="10" fill="#0C0C0E" stroke="#2A2A2E" stroke-width="1"/>
<!-- macOS Terminal header dots -->
<circle cx="28" cy="32" r="3" fill="#FF5F56"/>
<circle cx="36" cy="32" r="3" fill="#FFBD2E"/>
<circle cx="44" cy="32" r="3" fill="#27C93F"/>
<!-- Terminal Prompt >_ -->
<path d="M28 54 L44 64 L28 74" fill="none" stroke="#30D158" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
<line x1="50" y1="74" x2="68" y2="74" stroke="#FFFFFF" stroke-width="4" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,23 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="100%" height="100%">
<defs>
<linearGradient id="trashCan" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#E2E8F0"/>
<stop offset="100%" stop-color="#94A3B8"/>
</linearGradient>
<filter id="shadowTrash" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#000000" flood-opacity="0.35"/>
</filter>
</defs>
<!-- Lid -->
<ellipse cx="64" cy="30" rx="36" ry="8" fill="#CBD5E1" stroke="#94A3B8" stroke-width="2"/>
<rect x="56" y="18" width="16" height="8" rx="4" fill="#94A3B8"/>
<!-- Can Body -->
<path d="M30 30 L40 106 C41 112 48 116 64 116 C80 116 87 112 88 106 L98 30 Z" fill="url(#trashCan)" stroke="#64748B" stroke-width="2"/>
<!-- Ribs / Vertical Mesh Lines -->
<line x1="48" y1="36" x2="52" y2="108" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
<line x1="64" y1="36" x2="64" y2="110" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
<line x1="80" y1="36" x2="76" y2="108" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,24 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="100%" height="100%">
<defs>
<linearGradient id="trashCanFull" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#E2E8F0"/>
<stop offset="100%" stop-color="#94A3B8"/>
</linearGradient>
<filter id="shadowTrashFull" x="-10%" y="-10%" width="120%" height="120%">
<feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#000000" flood-opacity="0.35"/>
</filter>
</defs>
<!-- Crumpled paper overflowing -->
<path d="M42 28 C38 18 52 14 60 20 C68 12 82 16 80 26 C88 24 92 34 84 38 C74 42 54 42 42 28 Z" fill="#F8FAFC" stroke="#CBD5E1" stroke-width="1.5"/>
<path d="M48 24 L56 16 L64 24 Z" fill="#E2E8F0"/>
<path d="M68 20 L76 14 L82 22 Z" fill="#E2E8F0"/>
<!-- Can Body -->
<path d="M30 36 L40 106 C41 112 48 116 64 116 C80 116 87 112 88 106 L98 36 Z" fill="url(#trashCanFull)" stroke="#64748B" stroke-width="2"/>
<!-- Ribs / Vertical Mesh Lines -->
<line x1="48" y1="42" x2="52" y2="108" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
<line x1="64" y1="42" x2="64" y2="110" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
<line x1="80" y1="42" x2="76" y2="108" stroke="#FFFFFF" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="100%" height="100%">
<path fill="#FFFFFF" d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
</svg>

Before

Width:  |  Height:  |  Size: 312 B

View File

@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="100%" height="100%">
<path fill="#FFFFFF" d="M12 18.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z M4.93 12.93a10 10 0 0 1 14.14 0 1 1 0 0 0 1.42-1.42 12 12 0 0 0-16.98 0 1 1 0 0 0 1.42 1.42z M8.46 16.46a5 5 0 0 1 7.08 0 1 1 0 0 0 1.41-1.41 7 7 0 0 0-9.9 0 1 1 0 0 0 1.41 1.41z"/>
</svg>

Before

Width:  |  Height:  |  Size: 349 B

View File

@@ -0,0 +1,359 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import "functions"
import "." as Common
Singleton {
id: root
property string colorSource: Common.Config.options.appearance.colorSource
property string caelestiaAccentProfile: Common.Config.options.appearance.caelestia.accentProfile
property string lastCaelestiaPayload: ""
property QtObject m3colors: {
if (colorSource === "matugen" && matugenLoader.item)
return matugenLoader.item;
if (colorSource === "caelestia" && caelestiaPaletteLoaded)
return caelestiaColors;
return defaultColors;
}
property QtObject animation
property QtObject animationCurves
property QtObject colors
property QtObject rounding
property QtObject font
property QtObject sizes
property bool caelestiaPaletteLoaded: false
Loader {
id: matugenLoader
active: root.colorSource === "matugen"
source: "Appearance.colors.qml"
}
// Silicon Volt palette, ported from the previous shell theme.
// Base: charcoal #131313 / #0e0e0e, glass #1c1b1b, sharp corners.
// Accents: volt #CFFC00, coral #FF7A59, blue #00BFFF.
property QtObject defaultColors: QtObject {
property bool darkmode: true
property color m3primary: "#CFFC00" // accentVolt
property color m3onPrimary: "#131313" // bgPrimary
property color m3primaryContainer: "#3D4500"
property color m3onPrimaryContainer: "#E3FF66" // accentVoltLight
property color m3secondary: "#FF7A59" // accentCoral
property color m3onSecondary: "#131313"
property color m3secondaryContainer: "#3E2119"
property color m3onSecondaryContainer: "#FFB29B"
property color m3background: "#131313" // bgPrimary
property color m3onBackground: "#FFFFFF" // textPrimary
property color m3surface: "#131313"
property color m3surfaceContainerLow: "#161616"
property color m3surfaceContainer: "#1A1A1A"
property color m3surfaceContainerHigh: "#1C1B1B" // glassBg
property color m3surfaceContainerHighest: "#242323"
property color m3onSurface: "#E5E2E1" // textSecondary
property color m3surfaceVariant: "#2A2929"
property color m3onSurfaceVariant: "#ADAAAA" // textMuted
property color m3inverseSurface: "#E5E2E1"
property color m3inverseOnSurface: "#131313"
property color m3outline: "#ADAAAA"
property color m3outlineVariant: "#2A2929"
property color m3shadow: "#000000"
}
property QtObject caelestiaColors: QtObject {
property bool darkmode: defaultColors.darkmode
property color m3primary: defaultColors.m3primary
property color m3onPrimary: defaultColors.m3onPrimary
property color m3primaryContainer: defaultColors.m3primaryContainer
property color m3onPrimaryContainer: defaultColors.m3onPrimaryContainer
property color m3secondary: defaultColors.m3secondary
property color m3onSecondary: defaultColors.m3onSecondary
property color m3secondaryContainer: defaultColors.m3secondaryContainer
property color m3onSecondaryContainer: defaultColors.m3onSecondaryContainer
property color m3background: defaultColors.m3background
property color m3onBackground: defaultColors.m3onBackground
property color m3surface: defaultColors.m3surface
property color m3surfaceContainerLow: defaultColors.m3surfaceContainerLow
property color m3surfaceContainer: defaultColors.m3surfaceContainer
property color m3surfaceContainerHigh: defaultColors.m3surfaceContainerHigh
property color m3surfaceContainerHighest: defaultColors.m3surfaceContainerHighest
property color m3onSurface: defaultColors.m3onSurface
property color m3surfaceVariant: defaultColors.m3surfaceVariant
property color m3onSurfaceVariant: defaultColors.m3onSurfaceVariant
property color m3inverseSurface: defaultColors.m3inverseSurface
property color m3inverseOnSurface: defaultColors.m3inverseOnSurface
property color m3outline: defaultColors.m3outline
property color m3outlineVariant: defaultColors.m3outlineVariant
property color m3shadow: defaultColors.m3shadow
}
function loadCaelestiaPalette() {
getCaelestiaScheme.running = true;
}
function relativeLuminance(color) {
const c = Qt.color(color);
function channel(v) {
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
}
return (0.2126 * channel(c.r)) + (0.7152 * channel(c.g)) + (0.0722 * channel(c.b));
}
function bestOnColor(backgroundColor) {
return relativeLuminance(backgroundColor) > 0.5 ? "#121212" : "#f5f5f5";
}
function firstColor(palette, keys, fallback) {
for (const key of keys) {
if (palette[key])
return palette[key];
}
return fallback;
}
function applyCaelestiaPalette(palette, mode) {
if (caelestiaAccentProfile === "vibrant") {
const primary = firstColor(palette, ["blue", "klink", "term12", "primary"], defaultColors.m3primary);
const secondary = firstColor(palette, ["mauve", "lavender", "term13", "secondary"], defaultColors.m3secondary);
const tertiary = firstColor(palette, ["pink", "rosewater", "term11", "tertiary"], defaultColors.m3secondaryContainer);
const primaryContainer = firstColor(palette, ["sapphire", "klinkSelection", "primaryContainer"], defaultColors.m3primaryContainer);
const secondaryContainer = firstColor(palette, ["surface2", "secondaryContainer"], defaultColors.m3secondaryContainer);
caelestiaColors.m3primary = primary;
caelestiaColors.m3onPrimary = bestOnColor(primary);
caelestiaColors.m3primaryContainer = primaryContainer;
caelestiaColors.m3onPrimaryContainer = bestOnColor(primaryContainer);
caelestiaColors.m3secondary = secondary;
caelestiaColors.m3onSecondary = bestOnColor(secondary);
caelestiaColors.m3secondaryContainer = secondaryContainer;
caelestiaColors.m3onSecondaryContainer = bestOnColor(secondaryContainer);
// Preserve a stronger accent presence across UI mixes.
caelestiaColors.m3surfaceVariant = firstColor(palette, ["surface1", "surfaceVariant"], defaultColors.m3surfaceVariant);
caelestiaColors.m3outline = firstColor(palette, ["overlay2", "outline"], defaultColors.m3outline);
caelestiaColors.m3outlineVariant = firstColor(palette, ["overlay0", "outlineVariant"], defaultColors.m3outlineVariant);
if (tertiary)
caelestiaColors.m3secondaryContainer = ColorUtils.mix(secondaryContainer, tertiary, 0.7);
} else {
const map = {
"primary": "m3primary",
"onPrimary": "m3onPrimary",
"primaryContainer": "m3primaryContainer",
"onPrimaryContainer": "m3onPrimaryContainer",
"secondary": "m3secondary",
"onSecondary": "m3onSecondary",
"secondaryContainer": "m3secondaryContainer",
"onSecondaryContainer": "m3onSecondaryContainer",
"surfaceVariant": "m3surfaceVariant",
"outline": "m3outline",
"outlineVariant": "m3outlineVariant"
};
for (const key in map) {
if (palette[key])
caelestiaColors[map[key]] = palette[key];
}
}
// Keep foundational tones from Material keys for readability.
const baseMap = {
"background": "m3background",
"onBackground": "m3onBackground",
"surface": "m3surface",
"surfaceContainerLow": "m3surfaceContainerLow",
"surfaceContainer": "m3surfaceContainer",
"surfaceContainerHigh": "m3surfaceContainerHigh",
"surfaceContainerHighest": "m3surfaceContainerHighest",
"onSurface": "m3onSurface",
"inverseSurface": "m3inverseSurface",
"inverseOnSurface": "m3inverseOnSurface",
"shadow": "m3shadow"
};
for (const key in baseMap) {
if (palette[key])
caelestiaColors[baseMap[key]] = palette[key];
}
if (palette["onSurfaceVariant"])
caelestiaColors.m3onSurfaceVariant = palette["onSurfaceVariant"];
if (mode === "light")
caelestiaColors.darkmode = false;
else if (mode === "dark")
caelestiaColors.darkmode = true;
}
Process {
id: getCaelestiaScheme
command: ["sh", "-lc", "caelestia scheme get 2>/dev/null || true"]
stdout: StdioCollector {
id: caelestiaCollector
onStreamFinished: {
const text = caelestiaCollector.text;
if (!text || !text.trim()) {
root.caelestiaPaletteLoaded = false;
return;
}
const ansiPattern = /\x1b\[[0-9;]*m/g;
const lines = text.split("\n");
let mode = "";
const palette = ({});
for (const rawLine of lines) {
const line = rawLine.replace(ansiPattern, "").trim();
if (line.startsWith("Mode:")) {
mode = line.split(":")[1]?.trim()?.toLowerCase() ?? "";
continue;
}
const match = line.match(/^([A-Za-z0-9_]+):\s*.*?([0-9a-fA-F]{6})$/);
if (!match)
continue;
palette[match[1]] = `#${match[2]}`;
}
const normalized = JSON.stringify({ mode: mode, palette: palette, profile: caelestiaAccentProfile });
if (normalized === root.lastCaelestiaPayload)
return;
root.lastCaelestiaPayload = normalized;
applyCaelestiaPalette(palette, mode);
root.caelestiaPaletteLoaded = Object.keys(palette).length > 0;
}
}
}
Timer {
id: caelestiaRefreshTimer
interval: Math.max(500, Common.Config.options.appearance.caelestia.refreshInterval)
running: root.colorSource === "caelestia" && Common.Config.options.appearance.caelestia.autoRefresh
repeat: true
triggeredOnStart: false
onTriggered: root.loadCaelestiaPalette()
}
onColorSourceChanged: {
if (colorSource === "caelestia")
loadCaelestiaPalette();
}
onCaelestiaAccentProfileChanged: {
if (colorSource === "caelestia") {
root.lastCaelestiaPayload = "";
loadCaelestiaPalette();
}
}
Component.onCompleted: {
if (colorSource === "caelestia")
loadCaelestiaPalette();
}
colors: QtObject {
property color colSubtext: m3colors.m3outline
property color colLayer0: m3colors.m3background
property color colOnLayer0: m3colors.m3onBackground
property color colLayer0Border: ColorUtils.mix(root.m3colors.m3outlineVariant, colLayer0, 0.4)
property color colLayer1: m3colors.m3surfaceContainerLow
property color colOnLayer1: m3colors.m3onSurfaceVariant
property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45)
property color colLayer1Hover: ColorUtils.mix(colLayer1, colOnLayer1, 0.92)
property color colLayer1Active: ColorUtils.mix(colLayer1, colOnLayer1, 0.85)
property color colLayer2: m3colors.m3surfaceContainer
property color colOnLayer2: m3colors.m3onSurface
property color colLayer2Hover: ColorUtils.mix(colLayer2, colOnLayer2, 0.90)
property color colLayer2Active: ColorUtils.mix(colLayer2, colOnLayer2, 0.80)
property color colPrimary: m3colors.m3primary
property color colOnPrimary: m3colors.m3onPrimary
property color colSecondary: m3colors.m3secondary
property color colSecondaryContainer: m3colors.m3secondaryContainer
property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer
property color colTooltip: m3colors.m3inverseSurface
property color colOnTooltip: m3colors.m3inverseOnSurface
property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7)
property color colOutline: m3colors.m3outline
}
rounding: QtObject {
property int unsharpen: Common.Config.options.appearance.rounding.unsharpen
property int verysmall: Common.Config.options.appearance.rounding.verysmall
property int small: Common.Config.options.appearance.rounding.small
property int normal: Common.Config.options.appearance.rounding.normal
property int large: Common.Config.options.appearance.rounding.large
property int full: Common.Config.options.appearance.rounding.full
property int screenRounding: Common.Config.options.appearance.rounding.screenRounding
property int windowRounding: Common.Config.options.appearance.rounding.windowRounding
}
font: QtObject {
property QtObject family: QtObject {
property string main: Common.Config.options.appearance.font.family.main
property string title: Common.Config.options.appearance.font.family.title
property string expressive: Common.Config.options.appearance.font.family.expressive
}
property QtObject pixelSize: QtObject {
property int smaller: Common.Config.options.appearance.font.pixelSize.smaller
property int small: Common.Config.options.appearance.font.pixelSize.small
property int normal: Common.Config.options.appearance.font.pixelSize.normal
property int larger: Common.Config.options.appearance.font.pixelSize.larger
property int huge: Common.Config.options.appearance.font.pixelSize.huge
}
}
animationCurves: QtObject {
readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1]
readonly property list<real> expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1]
readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
readonly property real expressiveDefaultSpatialDuration: Common.Config.options.appearance.animation.duration.elementMove
readonly property real expressiveEffectsDuration: Common.Config.options.appearance.animation.duration.elementMoveFast
}
animation: QtObject {
property QtObject elementMove: QtObject {
property int duration: animationCurves.expressiveDefaultSpatialDuration
property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.expressiveDefaultSpatial
property Component numberAnimation: Component {
NumberAnimation {
duration: root.animation.elementMove.duration
easing.type: root.animation.elementMove.type
easing.bezierCurve: root.animation.elementMove.bezierCurve
}
}
}
property QtObject elementMoveEnter: QtObject {
property int duration: Common.Config.options.appearance.animation.duration.elementMoveEnter
property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.emphasizedDecel
property Component numberAnimation: Component {
NumberAnimation {
duration: root.animation.elementMoveEnter.duration
easing.type: root.animation.elementMoveEnter.type
easing.bezierCurve: root.animation.elementMoveEnter.bezierCurve
}
}
}
property QtObject elementMoveFast: QtObject {
property int duration: animationCurves.expressiveEffectsDuration
property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.expressiveEffects
property Component numberAnimation: Component {
NumberAnimation {
duration: root.animation.elementMoveFast.duration
easing.type: root.animation.elementMoveFast.type
easing.bezierCurve: root.animation.elementMoveFast.bezierCurve
}
}
}
}
sizes: QtObject {
property real elevationMargin: Common.Config.options.appearance.sizes.elevationMargin
}
}

View File

@@ -0,0 +1,194 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
property var userOptions: ({})
function read(path, fallback) {
const parts = path.split(".");
let current = userOptions;
for (const part of parts) {
if (current === null || current === undefined || typeof current !== "object" || !(part in current)) {
return fallback;
}
current = current[part];
}
return current === undefined || current === null ? fallback : current;
}
function readInt(path, fallback) {
const value = read(path, fallback);
const parsed = Number(value);
return Number.isInteger(parsed) ? parsed : fallback;
}
function readReal(path, fallback) {
const value = read(path, fallback);
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : fallback;
}
function readBool(path, fallback) {
const value = read(path, fallback);
return typeof value === "boolean" ? value : fallback;
}
function readString(path, fallback) {
const value = read(path, fallback);
if (typeof value !== "string")
return fallback;
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : fallback;
}
property QtObject options: QtObject {
property QtObject appearance: QtObject {
property string colorSource: root.readString(
"appearance.colorSource",
root.readBool("appearance.useMatugenColors", false) ? "matugen" : "default"
)
property bool useMatugenColors: colorSource === "matugen"
property QtObject caelestia: QtObject {
property bool autoRefresh: root.readBool("appearance.caelestia.autoRefresh", true)
property int refreshInterval: root.readInt("appearance.caelestia.refreshInterval", 2000)
property string accentProfile: root.readString("appearance.caelestia.accentProfile", "vibrant")
}
property QtObject rounding: QtObject {
property int unsharpen: root.readInt("appearance.rounding.unsharpen", 2)
property int verysmall: root.readInt("appearance.rounding.verysmall", 8)
property int small: root.readInt("appearance.rounding.small", 12)
property int normal: root.readInt("appearance.rounding.normal", 17)
property int large: root.readInt("appearance.rounding.large", 23)
property int full: root.readInt("appearance.rounding.full", 9999)
property int screenRounding: root.readInt("appearance.rounding.screenRounding", large)
property int windowRounding: root.readInt("appearance.rounding.windowRounding", 18)
}
property QtObject font: QtObject {
property QtObject family: QtObject {
property string main: root.readString("appearance.font.family.main", "sans-serif")
property string title: root.readString("appearance.font.family.title", "sans-serif")
property string expressive: root.readString("appearance.font.family.expressive", "sans-serif")
}
property QtObject pixelSize: QtObject {
property int smaller: root.readInt("appearance.font.pixelSize.smaller", 12)
property int small: root.readInt("appearance.font.pixelSize.small", 15)
property int normal: root.readInt("appearance.font.pixelSize.normal", 16)
property int larger: root.readInt("appearance.font.pixelSize.larger", 19)
property int huge: root.readInt("appearance.font.pixelSize.huge", 22)
}
}
property QtObject animation: QtObject {
property QtObject duration: QtObject {
property int elementMove: root.readInt("appearance.animation.duration.elementMove", 500)
property int elementMoveEnter: root.readInt("appearance.animation.duration.elementMoveEnter", 400)
property int elementMoveFast: root.readInt("appearance.animation.duration.elementMoveFast", 200)
}
}
property QtObject sizes: QtObject {
property real elevationMargin: root.readReal("appearance.sizes.elevationMargin", 10)
}
}
property QtObject overview: QtObject {
property int rows: root.readInt("overview.rows", 2)
property int columns: root.readInt("overview.columns", 5)
property real scale: root.readReal("overview.scale", 0.16)
property bool enable: root.readBool("overview.enable", true)
property bool hideEmptyRows: root.readBool("overview.hideEmptyRows", true)
property bool closeOnFocusLoss: root.readBool("overview.closeOnFocusLoss", true)
property bool useWorkspaceMap: root.readBool("overview.useWorkspaceMap", false)
property var workspaceMap: root.read("overview.workspaceMap", [])
property bool orderRightLeft: root.readBool("overview.orderRightLeft", false)
property bool orderBottomUp: root.readBool("overview.orderBottomUp", false)
property bool previewsEnabled: root.readBool("overview.previewsEnabled", true)
property string previewMode: root.readString("overview.previewMode", "live")
property bool includeInactiveMonitorPreviews: root.readBool("overview.includeInactiveMonitorPreviews", true)
property int previewRecaptureDelayMs: root.readInt("overview.previewRecaptureDelayMs", 60)
property bool showSpecialWorkspaces: root.readBool("overview.showSpecialWorkspaces", true)
property var specialWorkspaces: root.read("overview.specialWorkspaces", [])
property int specialWorkspaceColumns: root.readInt("overview.specialWorkspaceColumns", columns)
property string emptyWorkspaceWallpaper: root.readString("overview.emptyWorkspaceWallpaper", "")
property string specialEmptyWorkspaceWallpaper: root.readString("overview.specialEmptyWorkspaceWallpaper", "")
property real workspaceSpacing: root.readReal("overview.workspaceSpacing", 5)
property real backgroundPadding: root.readReal("overview.backgroundPadding", 10)
property real workspaceNumberBaseSize: root.readReal("overview.workspaceNumberBaseSize", 250)
property QtObject effects: QtObject {
property bool enableBackdrop: root.readBool("overview.effects.enableBackdrop", false)
property real backdropOpacity: root.readReal("overview.effects.backdropOpacity", 0.28)
property real panelOpacity: root.readReal("overview.effects.panelOpacity", 0.92)
property real workspaceOpacity: root.readReal("overview.effects.workspaceOpacity", 0.86)
property real emptyWorkspaceWallpaperOverlayOpacity: root.readReal("overview.effects.emptyWorkspaceWallpaperOverlayOpacity", 0.18)
property real windowOverlayOpacity: root.readReal("overview.effects.windowOverlayOpacity", 0.22)
property bool enableBlur: root.readBool("overview.effects.enableBlur", false)
property bool glassMode: root.readBool("overview.effects.glassMode", false)
property real glassTintStrength: root.readReal("overview.effects.glassTintStrength", 0.35)
property real glassBorderOpacity: root.readReal("overview.effects.glassBorderOpacity", 0.72)
property real glassShineOpacity: root.readReal("overview.effects.glassShineOpacity", 0.14)
}
}
property QtObject position: QtObject {
property int topMargin: root.readInt("position.topMargin", 100)
}
property QtObject windowPreview: QtObject {
property bool showIcons: root.readBool("windowPreview.showIcons", true)
property real iconToWindowRatio: root.readReal("windowPreview.iconToWindowRatio", 0.25)
property real iconToWindowRatioCompact: root.readReal("windowPreview.iconToWindowRatioCompact", 0.45)
property real xwaylandIndicatorToIconRatio: root.readReal("windowPreview.xwaylandIndicatorToIconRatio", 0.35)
property real inactiveMonitorOpacity: root.readReal("windowPreview.inactiveMonitorOpacity", 0.4)
property bool cropToFill: root.readBool("windowPreview.cropToFill", false)
}
property QtObject hacks: QtObject {
property int arbitraryRaceConditionDelay: root.readInt("hacks.arbitraryRaceConditionDelay", 150)
property int hyprlandEventDebounceMs: root.readInt("hacks.hyprlandEventDebounceMs", 40)
}
}
Process {
id: loadUserConfig
command: [
"sh",
"-lc",
"cfg=\"${XDG_CONFIG_HOME:-$HOME/.config}/quickshell/config.json\"; [ -r \"$cfg\" ] && cat \"$cfg\""
]
stdout: StdioCollector {
id: configCollector
onStreamFinished: {
const payload = configCollector.text.trim();
if (!payload)
return;
try {
const parsed = JSON.parse(payload);
if (typeof parsed === "object" && parsed !== null) {
root.userOptions = parsed;
} else {
console.warn("overview: config.json must contain a JSON object; ignoring file");
}
} catch (error) {
console.warn("overview: failed to parse user config.json; using defaults", error);
}
}
}
}
Component.onCompleted: {
loadUserConfig.running = true;
}
}

View File

@@ -0,0 +1,68 @@
pragma Singleton
import Quickshell
Singleton {
id: root
function colorWithHueOf(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
var hue = c2.hsvHue;
var sat = c1.hsvSaturation;
var val = c1.hsvValue;
var alpha = c1.a;
return Qt.hsva(hue, sat, val, alpha);
}
function colorWithSaturationOf(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
var hue = c1.hsvHue;
var sat = c2.hsvSaturation;
var val = c1.hsvValue;
var alpha = c1.a;
return Qt.hsva(hue, sat, val, alpha);
}
function colorWithLightness(color, lightness) {
var c = Qt.color(color);
return Qt.hsla(c.hslHue, c.hslSaturation, lightness, c.a);
}
function colorWithLightnessOf(color1, color2) {
var c2 = Qt.color(color2);
return colorWithLightness(color1, c2.hslLightness);
}
function adaptToAccent(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
var hue = c2.hslHue;
var sat = c2.hslSaturation;
var light = c1.hslLightness;
var alpha = c1.a;
return Qt.hsla(hue, sat, light, alpha);
}
function mix(color1, color2, percentage = 0.5) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
return Qt.rgba(
percentage * c1.r + (1 - percentage) * c2.r,
percentage * c1.g + (1 - percentage) * c2.g,
percentage * c1.b + (1 - percentage) * c2.b,
percentage * c1.a + (1 - percentage) * c2.a
);
}
function transparentize(color, percentage = 1) {
var c = Qt.color(color);
return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage));
}
function applyAlpha(color, alpha) {
var c = Qt.color(color);
var a = Math.max(0, Math.min(1, alpha));
return Qt.rgba(c.r, c.g, c.b, a);
}
}

View File

@@ -0,0 +1 @@
singleton ColorUtils 1.0 ColorUtils.qml

View File

@@ -0,0 +1,7 @@
singleton Appearance 1.0 Appearance.qml
singleton Config 1.0 Config.qml
singleton ColorUtils 1.0 functions/ColorUtils.qml
StyledText 1.0 widgets/StyledText.qml
StyledRectangularShadow 1.0 widgets/StyledRectangularShadow.qml
StyledToolTip 1.0 widgets/StyledToolTip.qml
StyledToolTipContent 1.0 widgets/StyledToolTipContent.qml

View File

@@ -0,0 +1,14 @@
import QtQuick
import QtQuick.Effects
import ".."
RectangularShadow {
required property var target
anchors.fill: target
radius: 20
blur: 0.9 * Appearance.sizes.elevationMargin
offset: Qt.vector2d(0.0, 1.0)
spread: 1
color: Appearance.colors.colShadow
cached: true
}

View File

@@ -0,0 +1,16 @@
import QtQuick
import ".."
Text {
id: root
property bool animateChange: false
renderType: Text.NativeRendering
verticalAlignment: Text.AlignVCenter
font {
hintingPreference: Font.PreferFullHinting
family: Appearance?.font.family.main ?? "sans-serif"
pixelSize: Appearance?.font.pixelSize.small ?? 15
}
color: Appearance?.m3colors.m3onBackground ?? "white"
}

View File

@@ -0,0 +1,23 @@
import QtQuick
import QtQuick.Controls
import "."
ToolTip {
id: root
property bool extraVisibleCondition: true
property bool alternativeVisibleCondition: false
readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition
verticalPadding: 5
horizontalPadding: 10
background: null
visible: internalVisibleCondition
contentItem: StyledToolTipContent {
id: contentItem
text: root.text
shown: root.internalVisibleCondition
horizontalPadding: root.horizontalPadding
verticalPadding: root.verticalPadding
}
}

View File

@@ -0,0 +1,49 @@
import QtQuick
import "."
import "../"
Item {
id: root
required property string text
property bool shown: false
property real horizontalPadding: 10
property real verticalPadding: 5
implicitWidth: tooltipTextObject.implicitWidth + 2 * root.horizontalPadding
implicitHeight: tooltipTextObject.implicitHeight + 2 * root.verticalPadding
property bool isVisible: backgroundRectangle.implicitHeight > 0
Rectangle {
id: backgroundRectangle
anchors {
bottom: root.bottom
horizontalCenter: root.horizontalCenter
}
color: Appearance?.colors.colTooltip ?? "#3C4043"
radius: Appearance?.rounding.verysmall ?? 7
opacity: shown ? 1 : 0
implicitWidth: shown ? (tooltipTextObject.implicitWidth + 2 * root.horizontalPadding) : 0
implicitHeight: shown ? (tooltipTextObject.implicitHeight + 2 * root.verticalPadding) : 0
clip: true
Behavior on implicitWidth {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on implicitHeight {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on opacity {
animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)
}
StyledText {
id: tooltipTextObject
anchors.centerIn: parent
text: root.text
font.pixelSize: Appearance?.font.pixelSize.smaller ?? 14
font.hintingPreference: Font.PreferNoHinting
color: Appearance?.colors.colOnTooltip ?? "#FFFFFF"
wrapMode: Text.Wrap
}
}
}

View File

@@ -0,0 +1,4 @@
StyledText 1.0 StyledText.qml
StyledRectangularShadow 1.0 StyledRectangularShadow.qml
StyledToolTip 1.0 StyledToolTip.qml
StyledToolTipContent 1.0 StyledToolTipContent.qml

View File

@@ -1,260 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import "../theme"
import ".."
PanelWindow {
id: aboutWindow
visible: GlobalState.aboutMacVisible
anchors {
top: true
left: true
right: true
bottom: true
}
color: Qt.rgba(0, 0, 0, 0.35)
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "quickshell-aboutmac"
WlrLayershell.keyboardFocus: visible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
// Dismiss when clicking outside modal
MouseArea {
anchors.fill: parent
onClicked: GlobalState.aboutMacVisible = false
}
// Centered Dialog
Rectangle {
id: dialogBox
anchors.centerIn: parent
width: 540
height: 330
radius: Theme.radiusModal
color: Theme.windowBg
border.color: Theme.windowBorder
border.width: 1
// Prevent clicking inside from dismissing
MouseArea {
anchors.fill: parent
onClicked: {}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 16
spacing: 12
// Top Traffic Lights Bar
RowLayout {
Layout.fillWidth: true
spacing: 8
// Red Close Dot
Rectangle {
width: 12
height: 12
radius: 6
color: closeMouse.containsMouse ? "#E0443E" : "#FF5F56"
border.color: "#E0443E"
border.width: 0.5
Text {
anchors.centerIn: parent
visible: closeMouse.containsMouse
text: "×"
font.pixelSize: 10
color: "#4A0002"
}
MouseArea {
id: closeMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: GlobalState.aboutMacVisible = false
}
}
// Yellow Minimize Dot
Rectangle {
width: 12
height: 12
radius: 6
color: "#FFBD2E"
border.color: "#DEA123"
border.width: 0.5
}
// Green Zoom Dot
Rectangle {
width: 12
height: 12
radius: 6
color: "#27C93F"
border.color: "#1AAB29"
border.width: 0.5
}
Item { Layout.fillWidth: true }
Text {
text: "Overview"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.DemiBold
color: Theme.textSecondary
}
Item { Layout.fillWidth: true }
}
// Main Specs Content
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 24
// Left: Catalina Island Badge
Image {
Layout.alignment: Qt.AlignVCenter
width: 130
height: 130
source: "../assets/catalina-badge.svg"
sourceSize: Qt.size(130, 130)
}
// Right: System Details
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: 6
Text {
text: "macOS Catalina"
font.family: Theme.fontFamily
font.pixelSize: 22
font.weight: Font.Bold
color: "#FFFFFF"
}
Text {
text: "Version 10.15.7 (NixOS Edition)"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textSecondary
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Qt.rgba(1.0, 1.0, 1.0, 0.12)
}
// Specs Rows
SpecRow { label: "Host"; value: "Hades (AMD + NVIDIA RTX)" }
SpecRow { label: "Processor"; value: "AMD High-Performance Multi-Core" }
SpecRow { label: "Memory"; value: `${GlobalState.memTotalGb} GB RAM (${GlobalState.memUsedGb} GB in use)` }
SpecRow { label: "Graphics"; value: "NVIDIA GeForce RTX (Proprietary Driver)" }
SpecRow { label: "Compositor"; value: "Hyprland 0.55+ (Wayland)" }
SpecRow { label: "Operating System"; value: "NixOS 26.05 (x86_64-linux)" }
}
}
// Bottom Action Buttons
RowLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignRight
spacing: 12
Rectangle {
implicitWidth: sysRepText.implicitWidth + 20
height: 24
radius: Theme.radiusSmall
color: repMouse.containsMouse ? Qt.rgba(1,1,1,0.2) : Qt.rgba(1,1,1,0.1)
border.color: Qt.rgba(1,1,1,0.15)
border.width: 1
Text {
id: sysRepText
anchors.centerIn: parent
text: "System Report..."
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textPrimary
}
MouseArea {
id: repMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
GlobalState.aboutMacVisible = false;
GlobalState.launchApp("foot -e btop");
}
}
}
Rectangle {
implicitWidth: updText.implicitWidth + 20
height: 24
radius: Theme.radiusSmall
color: updMouse.containsMouse ? Qt.rgba(1,1,1,0.2) : Qt.rgba(1,1,1,0.1)
border.color: Qt.rgba(1,1,1,0.15)
border.width: 1
Text {
id: updText
anchors.centerIn: parent
text: "Software Update..."
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textPrimary
}
MouseArea {
id: updMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
GlobalState.aboutMacVisible = false;
GlobalState.launchApp("foot -e zsh -c 'echo \"Checking NixOS system configuration...\"; sudo nixos-rebuild dry-activate --flake /home/anish/NixOS-Configs#hades; exec zsh'");
}
}
}
}
}
}
component SpecRow: RowLayout {
property string label: ""
property string value: ""
Layout.fillWidth: true
spacing: 10
Text {
text: label
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.DemiBold
color: Theme.textSecondary
Layout.preferredWidth: 105
}
Text {
text: value
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textPrimary
Layout.fillWidth: true
elide: Text.ElideRight
}
}
}

View File

@@ -1,147 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import "../theme"
import ".."
Rectangle {
id: root
property string menuName: ""
width: 200
implicitHeight: menuCol.implicitHeight + 12
radius: Theme.radiusMedium
color: Theme.menuDropdownBg
border.color: Theme.menuDropdownBorder
border.width: 1
ColumnLayout {
id: menuCol
anchors.fill: parent
anchors.margins: 6
spacing: 2
// FILE MENU
Repeater {
model: root.menuName === "File" ? [
{ text: "New Window", shortcut: "⌘N", cmd: "foot &" },
{ text: "New Incognito Window", shortcut: "⇧⌘N", cmd: "brave --incognito &" },
{ text: "Close Window", shortcut: "⌘W", cmd: "hyprctl dispatch \"hl.dsp.window.close()\"" },
{ text: "---", shortcut: "", cmd: "" },
{ text: "Take Screenshot", shortcut: "⇧⌘5", cmd: "grim -g \"$(slurp)\" ~/screenshot.png" }
] : []
delegate: MenuEntryDelegate {}
}
// EDIT MENU
Repeater {
model: root.menuName === "Edit" ? [
{ text: "Undo", shortcut: "⌘Z", cmd: "" },
{ text: "Redo", shortcut: "⇧⌘Z", cmd: "" },
{ text: "---", shortcut: "", cmd: "" },
{ text: "Cut", shortcut: "⌘X", cmd: "wl-copy" },
{ text: "Copy", shortcut: "⌘C", cmd: "wl-copy" },
{ text: "Paste", shortcut: "⌘V", cmd: "wl-paste" },
{ text: "Select All", shortcut: "⌘A", cmd: "" }
] : []
delegate: MenuEntryDelegate {}
}
// VIEW MENU
Repeater {
model: root.menuName === "View" ? [
{ text: "Toggle Fullscreen", shortcut: "⌃⌘F", cmd: "hyprctl dispatch \"hl.dsp.window.fullscreen({fullscreen = 0})\"" },
{ text: "Toggle Floating", shortcut: "⌘V", cmd: "hyprctl dispatch \"hl.dsp.window.float({action = \"toggle\"})\"" },
{ text: "Toggle Group", shortcut: "⌘G", cmd: "hyprctl dispatch \"hl.dsp.group.toggle()\"" },
{ text: "---", shortcut: "", cmd: "" },
{ text: "Zoom In", shortcut: "⌘+", cmd: "" },
{ text: "Zoom Out", shortcut: "⌘-", cmd: "" }
] : []
delegate: MenuEntryDelegate {}
}
// WINDOW MENU
Repeater {
model: root.menuName === "Window" ? [
{ text: "Minimize Window", shortcut: "⌘M", cmd: "hyprctl dispatch \"hl.dsp.window.move({workspace = \"special:scratch\"})\"" },
{ text: "Tile Left", shortcut: "⌃⌥←", cmd: "hyprctl dispatch \"hl.dsp.window.move({direction = \"left\"})\"" },
{ text: "Tile Right", shortcut: "⌃⌥→", cmd: "hyprctl dispatch \"hl.dsp.window.move({direction = \"right\"})\"" },
{ text: "---", shortcut: "", cmd: "" },
{ text: "Show Scratchpad", shortcut: "⌘↩", cmd: "hyprctl dispatch \"hl.dsp.workspace.toggle_special(\"scratch\")\"" },
{ text: "Show Messaging", shortcut: "⌘M", cmd: "hyprctl dispatch \"hl.dsp.workspace.toggle_special(\"messaging\")\"" }
] : []
delegate: MenuEntryDelegate {}
}
// HELP MENU
Repeater {
model: root.menuName === "Help" ? [
{ text: "Hyprland Documentation", shortcut: "", cmd: "brave https://wiki.hyprland.org &" },
{ text: "NixOS Manual", shortcut: "", cmd: "brave https://nixos.org/manual/nixos/stable/ &" },
{ text: "---", shortcut: "", cmd: "" },
{ text: "About Antigravity", shortcut: "", cmd: "" }
] : []
delegate: MenuEntryDelegate {}
}
}
component MenuEntryDelegate: Item {
Layout.fillWidth: true
Layout.preferredHeight: modelData.text === "---" ? 7 : 22
Rectangle {
anchors.fill: parent
visible: modelData.text === "---"
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(1.0, 1.0, 1.0, 0.12)
}
}
Rectangle {
anchors.fill: parent
visible: modelData.text !== "---"
radius: Theme.radiusSmall
color: itemMouse.containsMouse ? Theme.menuHoverBlue : "transparent"
RowLayout {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 8
Text {
text: modelData.text
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeNormal
color: itemMouse.containsMouse ? Theme.menuTextHover : Theme.textPrimary
Layout.fillWidth: true
elide: Text.ElideRight
}
Text {
visible: modelData.shortcut !== ""
text: modelData.shortcut
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: itemMouse.containsMouse ? Theme.menuTextHover : Theme.textTertiary
}
}
MouseArea {
id: itemMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
GlobalState.activeDropdownMenu = "";
if (modelData.cmd) {
GlobalState.launchApp(modelData.cmd);
}
}
}
}
}
}

View File

@@ -1,158 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import "../theme"
import ".."
Rectangle {
id: root
width: 220
implicitHeight: menuColumn.implicitHeight + 12
radius: Theme.radiusMedium
color: Theme.menuDropdownBg
border.color: Theme.menuDropdownBorder
border.width: 1
// Drop shadow
layer.enabled: true
ColumnLayout {
id: menuColumn
anchors.fill: parent
anchors.margins: 6
spacing: 2
// About This Mac
MenuItem {
text: "About This Mac"
onTriggered: {
GlobalState.appleMenuVisible = false;
GlobalState.toggleAboutMac();
}
}
MenuSeparator {}
MenuItem {
text: "System Preferences..."
onTriggered: {
GlobalState.launchApp("pavucontrol");
}
}
MenuItem {
text: "App Store..."
onTriggered: {
GlobalState.launchApp("foot -e zsh -c 'echo NixOS Packages; nix-env -qaP | head -n 50; exec zsh'");
}
}
MenuSeparator {}
MenuItem {
text: "Activity Monitor"
shortcut: "⌥⌘Esc"
onTriggered: {
GlobalState.launchApp("foot -e btop");
}
}
MenuSeparator {}
MenuItem {
text: "Sleep"
onTriggered: {
GlobalState.appleMenuVisible = false;
GlobalState.launchApp("systemctl suspend");
}
}
MenuItem {
text: "Restart..."
onTriggered: {
GlobalState.appleMenuVisible = false;
GlobalState.launchApp("systemctl reboot");
}
}
MenuItem {
text: "Shut Down..."
onTriggered: {
GlobalState.appleMenuVisible = false;
GlobalState.launchApp("systemctl poweroff");
}
}
MenuSeparator {}
MenuItem {
text: "Lock Screen"
shortcut: "⌃⌘Q"
onTriggered: {
GlobalState.appleMenuVisible = false;
GlobalState.launchApp("hyprlock");
}
}
MenuItem {
text: "Log Out anish..."
shortcut: "⇧⌘Q"
onTriggered: {
GlobalState.appleMenuVisible = false;
GlobalState.launchApp("loginctl terminate-session");
}
}
}
component MenuItem: Rectangle {
id: itemRoot
property string text: ""
property string shortcut: ""
signal triggered()
Layout.fillWidth: true
Layout.preferredHeight: 22
radius: Theme.radiusSmall
color: mouseArea.containsMouse ? Theme.menuHoverBlue : "transparent"
RowLayout {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 8
Text {
text: itemRoot.text
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeNormal
color: mouseArea.containsMouse ? Theme.menuTextHover : Theme.textPrimary
Layout.fillWidth: true
elide: Text.ElideRight
}
Text {
visible: itemRoot.shortcut !== ""
text: itemRoot.shortcut
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: mouseArea.containsMouse ? Theme.menuTextHover : Theme.textTertiary
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: itemRoot.triggered()
}
}
component MenuSeparator: Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
Layout.topMargin: 3
Layout.bottomMargin: 3
color: Qt.rgba(1.0, 1.0, 1.0, 0.12)
}
}

View File

@@ -1,102 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import "../theme"
import ".."
Rectangle {
id: root
width: 240
implicitHeight: batCol.implicitHeight + 24
radius: Theme.radiusMedium
color: Theme.menuDropdownBg
border.color: Theme.menuDropdownBorder
border.width: 1
ColumnLayout {
id: batCol
anchors.fill: parent
anchors.margins: 12
spacing: 8
RowLayout {
Layout.fillWidth: true
Text {
text: "Battery"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeNormal
font.weight: Font.DemiBold
color: Theme.textPrimary
Layout.fillWidth: true
}
Text {
text: `${GlobalState.batteryPercent}%`
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.DemiBold
color: GlobalState.batteryPercent <= 20 ? Theme.accentRed : Theme.accentGreen
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Qt.rgba(1.0, 1.0, 1.0, 0.12)
}
ColumnLayout {
Layout.fillWidth: true
spacing: 3
Text {
text: GlobalState.batteryPresent
? (GlobalState.batteryCharging ? "Power Source: Power Adapter" : "Power Source: Battery")
: "Power Source: AC Connected"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textPrimary
}
Text {
text: GlobalState.batteryPresent
? `Status: ${GlobalState.batteryStatus}`
: "Desktop Workstation Mode"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textSecondary
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Qt.rgba(1.0, 1.0, 1.0, 0.12)
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 24
radius: Theme.radiusSmall
color: batPrefMouse.containsMouse ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : Qt.rgba(1.0, 1.0, 1.0, 0.08)
Text {
anchors.centerIn: parent
text: "Energy Saver Preferences..."
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textPrimary
}
MouseArea {
id: batPrefMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
GlobalState.batteryPopupVisible = false;
GlobalState.launchApp("foot -e btop");
}
}
}
}
}

View File

@@ -1,94 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import "../theme"
import ".."
Rectangle {
id: root
width: 250
implicitHeight: calCol.implicitHeight + 24
radius: Theme.radiusMedium
color: Theme.menuDropdownBg
border.color: Theme.menuDropdownBorder
border.width: 1
ColumnLayout {
id: calCol
anchors.fill: parent
anchors.margins: 12
spacing: 10
Text {
text: GlobalState.fullDateString
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeNormal
font.weight: Font.DemiBold
color: Theme.textPrimary
Layout.alignment: Qt.AlignHCenter
}
Text {
text: GlobalState.timeString
font.family: Theme.fontFamily
font.pixelSize: 22
font.weight: Font.Light
color: Theme.accentBlue
Layout.alignment: Qt.AlignHCenter
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Qt.rgba(1.0, 1.0, 1.0, 0.12)
}
// Days grid header
RowLayout {
Layout.fillWidth: true
Repeater {
model: ["S", "M", "T", "W", "T", "F", "S"]
delegate: Text {
text: modelData
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.DemiBold
color: Theme.textTertiary
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
}
}
}
// Quick mini-calendar representation
GridLayout {
id: gridDays
columns: 7
Layout.fillWidth: true
rowSpacing: 4
columnSpacing: 4
readonly property var todayDate: new Date()
readonly property int currentDay: todayDate.getDate()
Repeater {
model: 31
delegate: Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 22
radius: 11
color: (index + 1) === gridDays.currentDay ? Theme.accentBlue : "transparent"
Text {
anchors.centerIn: parent
text: `${index + 1}`
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: (index + 1) === gridDays.currentDay ? Font.Bold : Font.Normal
color: (index + 1) === gridDays.currentDay ? "#FFFFFF" : Theme.textSecondary
}
}
}
}
}
}

View File

@@ -1,638 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import "../theme"
import ".."
PanelWindow {
id: ccWindow
visible: GlobalState.controlCenterVisible
anchors {
top: true
right: true
bottom: true
}
margins {
top: Theme.menuBarHeight
}
implicitWidth: 360
color: "transparent"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "quickshell-controlcenter"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
property int activeTab: 0 // 0 = Today, 1 = Notifications
// Sidebar Container
Rectangle {
id: sidebarPanel
anchors.fill: parent
color: Theme.windowBg
border.color: Theme.windowBorder
border.width: 1
ColumnLayout {
anchors.fill: parent
anchors.margins: 14
spacing: 12
// =================================================================
// TAB SWITCHER (Today | Notifications)
// =================================================================
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 30
radius: Theme.radiusMedium
color: Qt.rgba(0.0, 0.0, 0.0, 0.3)
RowLayout {
anchors.fill: parent
spacing: 0
// Today Tab Button
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
radius: Theme.radiusMedium
color: ccWindow.activeTab === 0 ? Qt.rgba(1.0, 1.0, 1.0, 0.18) : "transparent"
Text {
anchors.centerIn: parent
text: "Today"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeNormal
font.weight: ccWindow.activeTab === 0 ? Font.DemiBold : Font.Normal
color: ccWindow.activeTab === 0 ? "#FFFFFF" : Theme.textTertiary
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: ccWindow.activeTab = 0
}
}
// Notifications Tab Button
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
radius: Theme.radiusMedium
color: ccWindow.activeTab === 1 ? Qt.rgba(1.0, 1.0, 1.0, 0.18) : "transparent"
Text {
anchors.centerIn: parent
text: "Notifications"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeNormal
font.weight: ccWindow.activeTab === 1 ? Font.DemiBold : Font.Normal
color: ccWindow.activeTab === 1 ? "#FFFFFF" : Theme.textTertiary
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: ccWindow.activeTab = 1
}
}
}
}
// =================================================================
// TAB 1: TODAY VIEW
// =================================================================
ScrollView {
visible: ccWindow.activeTab === 0
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
ColumnLayout {
width: parent.width
spacing: 12
// 1. Large Date & Time Widget
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 70
radius: Theme.radiusLarge
color: Theme.cardBg
border.color: Theme.cardBorder
border.width: 1
ColumnLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 2
Text {
text: GlobalState.fullDateString
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.DemiBold
color: Theme.accentBlue
}
Text {
text: GlobalState.timeString
font.family: Theme.fontFamily
font.pixelSize: 26
font.weight: Font.Light
color: "#FFFFFF"
}
}
}
// 2. Control Toggles (Wi-Fi, Bluetooth, DND, Night Shift)
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 120
radius: Theme.radiusLarge
color: Theme.cardBg
border.color: Theme.cardBorder
border.width: 1
GridLayout {
anchors.fill: parent
anchors.margins: 10
columns: 2
rowSpacing: 8
columnSpacing: 8
// Wi-Fi Toggle
ToggleCard {
iconSource: "../assets/wifi.svg"
title: "Wi-Fi"
subtitle: GlobalState.wifiSsid
isActive: GlobalState.wifiConnected
onClicked: GlobalState.launchApp("nm-connection-editor || foot -e nmtui")
}
// Bluetooth Toggle
ToggleCard {
iconSource: "../assets/bluetooth.svg"
title: "Bluetooth"
subtitle: "Active"
isActive: true
onClicked: GlobalState.launchApp("foot -e bluetoothctl")
}
// Do Not Disturb Toggle
ToggleCard {
iconSource: "../assets/preferences.svg"
title: "Do Not Disturb"
subtitle: "Off"
isActive: false
onClicked: {}
}
// Sound Mute Toggle
ToggleCard {
iconSource: "../assets/volume.svg"
title: "Sound"
subtitle: GlobalState.volumeMuted ? "Muted" : `${GlobalState.volume}%`
isActive: !GlobalState.volumeMuted
onClicked: GlobalState.toggleVolumeMute()
}
}
}
// 3. Brightness & Volume Sliders
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 110
radius: Theme.radiusLarge
color: Theme.cardBg
border.color: Theme.cardBorder
border.width: 1
ColumnLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 10
// Display Brightness Slider
RowLayout {
Layout.fillWidth: true
spacing: 8
Image {
width: 16
height: 16
source: "../assets/brightness.svg"
sourceSize: Qt.size(16, 16)
}
Item {
Layout.fillWidth: true
height: 18
Rectangle {
anchors.fill: parent
radius: 9
color: Qt.rgba(1,1,1,0.18)
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: parent.width * (GlobalState.brightness / 100.0)
radius: 9
color: "#FFFFFF"
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
function update(m) {
const frac = Math.max(0.01, Math.min(1.0, m.x / width));
GlobalState.setBrightness(Math.round(frac * 100));
}
onPressed: (m) => update(m)
onPositionChanged: (m) => { if (pressed) update(m); }
}
}
}
// Sound Volume Slider
RowLayout {
Layout.fillWidth: true
spacing: 8
Image {
width: 16
height: 16
source: "../assets/volume.svg"
sourceSize: Qt.size(16, 16)
}
Item {
Layout.fillWidth: true
height: 18
Rectangle {
anchors.fill: parent
radius: 9
color: Qt.rgba(1,1,1,0.18)
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: parent.width * (GlobalState.volume / 100.0)
radius: 9
color: Theme.accentBlue
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
function update(m) {
const frac = Math.max(0.0, Math.min(1.0, m.x / width));
GlobalState.setVolume(Math.round(frac * 100));
}
onPressed: (m) => update(m)
onPositionChanged: (m) => { if (pressed) update(m); }
}
}
}
}
}
// 4. System Resources Monitor Widget
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 90
radius: Theme.radiusLarge
color: Theme.cardBg
border.color: Theme.cardBorder
border.width: 1
ColumnLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 6
Text {
text: "System Performance"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.DemiBold
color: Theme.textSecondary
}
// CPU Bar
RowLayout {
Layout.fillWidth: true
spacing: 8
Text {
text: "CPU"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textPrimary
Layout.preferredWidth: 32
}
Rectangle {
Layout.fillWidth: true
height: 6
radius: 3
color: Qt.rgba(1,1,1,0.15)
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: parent.width * (GlobalState.cpuPercent / 100.0)
radius: 3
color: GlobalState.cpuPercent > 80 ? Theme.accentRed : Theme.accentGreen
}
}
Text {
text: `${GlobalState.cpuPercent}%`
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textSecondary
Layout.preferredWidth: 36
horizontalAlignment: Text.AlignRight
}
}
// Memory Bar
RowLayout {
Layout.fillWidth: true
spacing: 8
Text {
text: "RAM"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textPrimary
Layout.preferredWidth: 32
}
Rectangle {
Layout.fillWidth: true
height: 6
radius: 3
color: Qt.rgba(1,1,1,0.15)
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: parent.width * (GlobalState.memPercent / 100.0)
radius: 3
color: Theme.accentBlue
}
}
Text {
text: `${GlobalState.memUsedGb}G`
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textSecondary
Layout.preferredWidth: 36
horizontalAlignment: Text.AlignRight
}
}
}
}
// 5. Now Playing Media Widget
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 80
radius: Theme.radiusLarge
color: Theme.cardBg
border.color: Theme.cardBorder
border.width: 1
RowLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 10
Image {
width: 36
height: 36
source: "../assets/music.svg"
sourceSize: Qt.size(36, 36)
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
Text {
text: GlobalState.mediaTitle || "No Media Playing"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeNormal
font.weight: Font.Medium
color: "#FFFFFF"
elide: Text.ElideRight
Layout.fillWidth: true
}
Text {
text: GlobalState.mediaArtist || "Apple Music"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textTertiary
elide: Text.ElideRight
Layout.fillWidth: true
}
}
// Playback controls
RowLayout {
spacing: 4
Rectangle {
width: 24
height: 24
radius: 12
color: Qt.rgba(1,1,1,0.1)
Text { anchors.centerIn: parent; text: "⏮"; color: "#FFFFFF"; font.pixelSize: 10 }
MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: GlobalState.mediaPrevious() }
}
Rectangle {
width: 28
height: 28
radius: 14
color: Theme.accentBlue
Text { anchors.centerIn: parent; text: GlobalState.mediaPlaying ? "⏸" : "▶"; color: "#FFFFFF"; font.pixelSize: 11 }
MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: GlobalState.mediaPlayPause() }
}
Rectangle {
width: 24
height: 24
radius: 12
color: Qt.rgba(1,1,1,0.1)
Text { anchors.centerIn: parent; text: "⏭"; color: "#FFFFFF"; font.pixelSize: 10 }
MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: GlobalState.mediaNext() }
}
}
}
}
}
}
// =================================================================
// TAB 2: NOTIFICATIONS VIEW
// =================================================================
ColumnLayout {
visible: ccWindow.activeTab === 1
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 12
RowLayout {
Layout.fillWidth: true
Text {
text: "Notifications"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.DemiBold
color: "#FFFFFF"
Layout.fillWidth: true
}
Rectangle {
implicitWidth: clearText.implicitWidth + 12
height: 22
radius: Theme.radiusSmall
color: Qt.rgba(1,1,1,0.12)
Text {
id: clearText
anchors.centerIn: parent
text: "Clear All"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textSecondary
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {}
}
}
}
// Placeholder / Active notifications card
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 70
radius: Theme.radiusLarge
color: Theme.cardBg
border.color: Theme.cardBorder
border.width: 1
RowLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 10
Image {
width: 28
height: 28
source: "../assets/apple.svg"
sourceSize: Qt.size(28, 28)
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
Text {
text: "macOS Catalina on Hyprland"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeNormal
font.weight: Font.Medium
color: "#FFFFFF"
}
Text {
text: "Quickshell desktop loaded successfully."
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textSecondary
}
}
}
}
Item {
Layout.fillHeight: true
}
}
}
}
// Toggle Card Component
component ToggleCard: Rectangle {
id: cardRoot
property string iconSource: ""
property string title: ""
property string subtitle: ""
property bool isActive: false
signal clicked()
Layout.fillWidth: true
Layout.preferredHeight: 46
radius: Theme.radiusMedium
color: cardMouse.containsMouse ? Qt.rgba(1.0, 1.0, 1.0, 0.22) : Qt.rgba(1.0, 1.0, 1.0, 0.12)
RowLayout {
anchors.fill: parent
anchors.margins: 8
spacing: 8
Rectangle {
width: 28
height: 28
radius: 14
color: cardRoot.isActive ? Theme.accentBlue : Qt.rgba(1,1,1,0.2)
Image {
anchors.centerIn: parent
width: 14
height: 14
source: cardRoot.iconSource
sourceSize: Qt.size(14, 14)
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 1
Text {
text: cardRoot.title
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: "#FFFFFF"
elide: Text.ElideRight
}
Text {
text: cardRoot.subtitle
font.family: Theme.fontFamily
font.pixelSize: 10
color: Theme.textTertiary
elide: Text.ElideRight
}
}
}
MouseArea {
id: cardMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: cardRoot.clicked()
}
}
}

View File

@@ -1,338 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import "../theme"
import ".."
PanelWindow {
id: dockWindow
property var modelData
screen: modelData
anchors {
bottom: true
}
margins {
bottom: 6
}
// Adjust width to fit content
implicitWidth: dockTray.implicitWidth + 32
implicitHeight: 90
color: "transparent"
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "quickshell-dock"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
// Reserve 70px exclusive zone so windows don't overlap the dock
exclusiveZone: 70
// Tooltip overlay container
Item {
id: tooltipContainer
anchors.fill: parent
z: 100
}
// 2.5D Frosted Glass Dock Tray
Rectangle {
id: dockTray
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
implicitWidth: dockLayout.implicitWidth + 24
height: 68
radius: Theme.radiusDock
color: Theme.dockBg
border.color: Theme.dockBorder
border.width: 1
// Top glossy reflection edge line (Catalina shelf characteristic)
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.leftMargin: 12
anchors.rightMargin: 12
height: 1
color: Theme.dockHighlight
opacity: 0.8
}
// Inner items layout
RowLayout {
id: dockLayout
anchors.centerIn: parent
spacing: 6
// =================================================================
// 1. PINNED APPS
// =================================================================
DockItem {
id: finderItem
name: "Finder"
iconSource: "../assets/finder.svg"
execCmd: "foot -e sh -c 'cd ~; lf || yazi || ls -la; exec zsh'"
appClass: "foot"
isPinned: true
}
DockItem {
name: "Launchpad"
iconSource: "../assets/launchpad.svg"
execCmd: ""
isPinned: true
onCustomClicked: GlobalState.toggleSpotlight()
}
DockItem {
name: "Brave Browser"
iconSource: "../assets/browser.svg"
execCmd: "brave"
appClass: "brave-browser"
isPinned: true
}
DockItem {
name: "Terminal"
iconSource: "../assets/terminal.svg"
execCmd: "foot"
appClass: "foot"
isPinned: true
}
DockItem {
name: "Neovim"
iconSource: "../assets/code.svg"
execCmd: "foot -e nvim"
appClass: "nvim"
isPinned: true
}
DockItem {
name: "System Preferences"
iconSource: "../assets/preferences.svg"
execCmd: "pavucontrol"
appClass: "pavucontrol"
isPinned: true
}
DockItem {
name: "App Store"
iconSource: "../assets/appstore.svg"
execCmd: "foot -e zsh -c 'echo NixOS Packages; nix-env -qaP | head -n 50; exec zsh'"
appClass: ""
isPinned: true
}
DockItem {
name: "Music"
iconSource: "../assets/music.svg"
execCmd: ""
appClass: ""
isPinned: true
onCustomClicked: GlobalState.mediaPlayPause()
}
// =================================================================
// 2. DYNAMIC RUNNING APPS (Not pinned)
// =================================================================
Repeater {
model: {
const pinnedClasses = ["foot", "brave-browser", "pavucontrol", "nvim"];
const list = [];
const seen = {};
if (Array.isArray(GlobalState.runningApps)) {
for (let i = 0; i < GlobalState.runningApps.length; i++) {
const client = GlobalState.runningApps[i];
const cls = (client.class || "").toLowerCase();
if (cls && !pinnedClasses.includes(cls) && !seen[cls]) {
seen[cls] = true;
list.push({
name: client.title || client.class || "App",
cls: client.class,
address: client.address
});
}
}
}
return list;
}
delegate: DockItem {
required property var modelData
name: modelData.name
iconSource: "../assets/code.svg"
appClass: modelData.cls
isPinned: false
onCustomClicked: {
if (modelData.address) {
Hyprland.dispatch(`hl.dsp.focus({window = "address:${modelData.address}"})`);
}
}
}
}
// =================================================================
// 3. DOCK SEPARATOR DIVIDER
// =================================================================
Rectangle {
Layout.preferredWidth: 1
Layout.preferredHeight: 44
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: 4
Layout.rightMargin: 4
color: Qt.rgba(1.0, 1.0, 1.0, 0.22)
}
// =================================================================
// 4. DOWNLOADS FOLDER & TRASH
// =================================================================
DockItem {
name: "Downloads"
iconSource: "../assets/folder-downloads.svg"
execCmd: "foot -e sh -c 'cd ~/Downloads; ls -la; exec zsh'"
isPinned: true
}
DockItem {
name: "Trash"
iconSource: "../assets/trash-empty.svg"
execCmd: "foot -e sh -c 'cd ~/.local/share/Trash/files 2>/dev/null || cd ~; ls -la; exec zsh'"
isPinned: true
}
}
}
// Reusable Dock Item Component
component DockItem: Item {
id: itemRoot
property string name: ""
property string iconSource: ""
property string execCmd: ""
property string appClass: ""
property bool isPinned: true
signal customClicked()
// Check if app is running
readonly property bool isRunning: {
if (!itemRoot.appClass) return false;
const target = itemRoot.appClass.toLowerCase();
if (GlobalState.activeWindowClass.toLowerCase().includes(target)) return true;
if (Array.isArray(GlobalState.runningApps)) {
for (let i = 0; i < GlobalState.runningApps.length; i++) {
const c = GlobalState.runningApps[i];
if ((c.class || "").toLowerCase().includes(target)) return true;
}
}
return false;
}
implicitWidth: 50
implicitHeight: 64
Layout.alignment: Qt.AlignBottom
// Container with scale & bounce transform
Item {
id: iconWrapper
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
width: 48
height: 48
scale: itemArea.containsMouse ? 1.28 : 1.0
y: itemArea.containsMouse ? -8 : 0
Behavior on scale {
NumberAnimation { duration: 180; easing.type: Easing.OutCubic }
}
Behavior on y {
NumberAnimation { duration: 180; easing.type: Easing.OutCubic }
}
// Icon Image
Image {
anchors.centerIn: parent
width: 46
height: 46
source: itemRoot.iconSource
sourceSize: Qt.size(92, 92)
smooth: true
mipmap: true
}
// Click Bounce Sequential Animation
SequentialAnimation {
id: bounceAnim
NumberAnimation { target: iconWrapper; property: "y"; to: -24; duration: 150; easing.type: Easing.OutQuad }
NumberAnimation { target: iconWrapper; property: "y"; to: -4; duration: 130; easing.type: Easing.InQuad }
NumberAnimation { target: iconWrapper; property: "y"; to: -14; duration: 100; easing.type: Easing.OutQuad }
NumberAnimation { target: iconWrapper; property: "y"; to: 0; duration: 100; easing.type: Easing.InQuad }
}
}
// Active App Running Indicator Dot (White glowing pill)
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 2
width: 4
height: 4
radius: 2
color: Theme.dockDot
visible: itemRoot.isRunning
}
// Hover Tooltip Badge (macOS Catalina Style Black Pill)
Rectangle {
id: tooltipPill
visible: itemArea.containsMouse
opacity: itemArea.containsMouse ? 1.0 : 0.0
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.top
anchors.bottomMargin: 6
implicitWidth: tooltipText.implicitWidth + 16
height: 24
radius: 6
color: Qt.rgba(0.08, 0.08, 0.10, 0.92)
border.color: Qt.rgba(1.0, 1.0, 1.0, 0.18)
border.width: 1
Behavior on opacity {
NumberAnimation { duration: 120 }
}
Text {
id: tooltipText
anchors.centerIn: parent
text: itemRoot.name
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: "#FFFFFF"
}
}
// Mouse interaction
MouseArea {
id: itemArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
bounceAnim.start();
if (itemRoot.execCmd !== "") {
GlobalState.launchApp(itemRoot.execCmd);
} else {
itemRoot.customClicked();
}
}
}
}
}

View File

@@ -1,507 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import Quickshell.Services.SystemTray
import "../theme"
import ".."
PanelWindow {
id: menuBarWindow
property var modelData
screen: modelData
anchors {
top: true
left: true
right: true
}
// Set implicitHeight to accommodate bar + open popups without clipping
implicitHeight: (GlobalState.appleMenuVisible || GlobalState.activeDropdownMenu !== "" ||
GlobalState.volumePopupVisible || GlobalState.wifiPopupVisible ||
GlobalState.batteryPopupVisible || GlobalState.calendarPopupVisible) ? 420 : Theme.menuBarHeight
color: "transparent"
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "quickshell-menubar"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
// Set exclusive zone only for the 26px bar
exclusiveZone: Theme.menuBarHeight
// Background Bar (26px)
Rectangle {
id: barRect
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: Theme.menuBarHeight
color: Theme.menuBarBg
border.color: Theme.menuBarBorder
border.width: 0
// Bottom hairline border
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 1
color: Theme.menuBarBorder
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: 12
anchors.rightMargin: 12
spacing: 0
// =================================================================
// LEFT SIDE: Apple Menu + App Title + Standard Menus
// =================================================================
RowLayout {
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
spacing: 2
// Apple Logo Button
Rectangle {
id: appleBtn
width: 28
height: 20
radius: Theme.radiusSmall
color: (GlobalState.appleMenuVisible || appleMouse.containsMouse)
? Qt.rgba(1.0, 1.0, 1.0, 0.18) : "transparent"
Image {
anchors.centerIn: parent
width: 14
height: 14
source: "../assets/apple.svg"
sourceSize: Qt.size(14, 14)
}
MouseArea {
id: appleMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
const wasOpen = GlobalState.appleMenuVisible;
GlobalState.closeAllPopups();
GlobalState.appleMenuVisible = !wasOpen;
}
}
}
// Active App Title (Bold)
Rectangle {
id: appTitleBtn
implicitWidth: appTitleText.implicitWidth + 16
height: 20
radius: Theme.radiusSmall
color: titleMouse.containsMouse ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
Text {
id: appTitleText
anchors.centerIn: parent
text: GlobalState.activeWindowClass || "Finder"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeMenuBar
font.weight: Font.Bold
color: Theme.textPrimary
}
MouseArea {
id: titleMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
const wasOpen = (GlobalState.activeDropdownMenu === "File");
GlobalState.closeAllPopups();
GlobalState.activeDropdownMenu = wasOpen ? "" : "File";
}
}
}
// Standard Menus: File, Edit, View, Window, Help
Repeater {
model: ["File", "Edit", "View", "Window", "Help"]
delegate: Rectangle {
id: menuBtn
implicitWidth: menuText.implicitWidth + 14
height: 20
radius: Theme.radiusSmall
color: (GlobalState.activeDropdownMenu === modelData || menuMouse.containsMouse)
? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
Text {
id: menuText
anchors.centerIn: parent
text: modelData
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeMenuBar
font.weight: Font.Normal
color: Theme.textPrimary
}
MouseArea {
id: menuMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: {
// If a menu is already open, hovering switches to this one
if (GlobalState.activeDropdownMenu !== "") {
GlobalState.activeDropdownMenu = modelData;
}
}
onClicked: {
const wasOpen = (GlobalState.activeDropdownMenu === modelData);
GlobalState.closeAllPopups();
GlobalState.activeDropdownMenu = wasOpen ? "" : modelData;
}
}
}
}
}
// Spacer
Item {
Layout.fillWidth: true
}
// =================================================================
// RIGHT SIDE: Tray + Media + Volume + Wifi + Battery + Spotlight + Control Center + Clock
// =================================================================
RowLayout {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
spacing: 4
// System Tray Icons
Repeater {
model: SystemTray.items
delegate: Rectangle {
required property SystemTrayItem modelData
width: 22
height: 20
radius: Theme.radiusSmall
color: trayMouse.containsMouse ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
Image {
anchors.centerIn: parent
width: 15
height: 15
source: modelData.icon || ""
sourceSize: Qt.size(15, 15)
}
MouseArea {
id: trayMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: modelData.activate()
}
}
}
// Media Indicator (visible if playing)
Rectangle {
visible: GlobalState.mediaPlaying
implicitWidth: mediaRow.implicitWidth + 12
height: 20
radius: Theme.radiusSmall
color: mediaMouse.containsMouse ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
RowLayout {
id: mediaRow
anchors.centerIn: parent
spacing: 4
Image {
width: 13
height: 13
source: "../assets/music.svg"
sourceSize: Qt.size(13, 13)
}
Text {
text: GlobalState.mediaTitle ? `${GlobalState.mediaTitle.substring(0, 18)}...` : "Playing"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textSecondary
}
}
MouseArea {
id: mediaMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: GlobalState.mediaPlayPause()
}
}
// Volume Item
Rectangle {
id: volBtn
width: 26
height: 20
radius: Theme.radiusSmall
color: (GlobalState.volumePopupVisible || volMouse.containsMouse)
? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
Image {
anchors.centerIn: parent
width: 14
height: 14
source: "../assets/volume.svg"
sourceSize: Qt.size(14, 14)
}
MouseArea {
id: volMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
const wasOpen = GlobalState.volumePopupVisible;
GlobalState.closeAllPopups();
GlobalState.volumePopupVisible = !wasOpen;
}
}
}
// Wi-Fi Item
Rectangle {
id: wifiBtn
width: 26
height: 20
radius: Theme.radiusSmall
color: (GlobalState.wifiPopupVisible || wifiMouse.containsMouse)
? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
Image {
anchors.centerIn: parent
width: 14
height: 14
source: "../assets/wifi.svg"
sourceSize: Qt.size(14, 14)
}
MouseArea {
id: wifiMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
const wasOpen = GlobalState.wifiPopupVisible;
GlobalState.closeAllPopups();
GlobalState.wifiPopupVisible = !wasOpen;
}
}
}
// Battery Item
Rectangle {
id: batBtn
implicitWidth: batRow.implicitWidth + 8
height: 20
radius: Theme.radiusSmall
color: (GlobalState.batteryPopupVisible || batMouse.containsMouse)
? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
RowLayout {
id: batRow
anchors.centerIn: parent
spacing: 4
Text {
visible: GlobalState.batteryPresent
text: `${GlobalState.batteryPercent}%`
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textPrimary
}
Image {
width: 18
height: 12
source: "../assets/battery.svg"
sourceSize: Qt.size(18, 12)
}
}
MouseArea {
id: batMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
const wasOpen = GlobalState.batteryPopupVisible;
GlobalState.closeAllPopups();
GlobalState.batteryPopupVisible = !wasOpen;
}
}
}
// Spotlight Search Button
Rectangle {
id: spotBtn
width: 26
height: 20
radius: Theme.radiusSmall
color: (GlobalState.spotlightVisible || spotMouse.containsMouse)
? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
Image {
anchors.centerIn: parent
width: 14
height: 14
source: "../assets/spotlight.svg"
sourceSize: Qt.size(14, 14)
}
MouseArea {
id: spotMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: GlobalState.toggleSpotlight()
}
}
// Control Center / Notification Center Toggle Button
Rectangle {
id: ccBtn
width: 26
height: 20
radius: Theme.radiusSmall
color: (GlobalState.controlCenterVisible || ccMouse.containsMouse)
? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
Image {
anchors.centerIn: parent
width: 14
height: 14
source: "../assets/controlcenter.svg"
sourceSize: Qt.size(14, 14)
}
MouseArea {
id: ccMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: GlobalState.toggleControlCenter()
}
}
// Date & Time (Clock)
Rectangle {
id: clockBtn
implicitWidth: clockRow.implicitWidth + 10
height: 20
radius: Theme.radiusSmall
color: (GlobalState.calendarPopupVisible || clockMouse.containsMouse)
? Qt.rgba(1.0, 1.0, 1.0, 0.15) : "transparent"
RowLayout {
id: clockRow
anchors.centerIn: parent
spacing: 6
Text {
text: GlobalState.dateString
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeMenuBar
color: Theme.textSecondary
}
Text {
text: GlobalState.timeString
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeMenuBar
font.weight: Font.Medium
color: Theme.textPrimary
}
}
MouseArea {
id: clockMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
const wasOpen = GlobalState.calendarPopupVisible;
GlobalState.closeAllPopups();
GlobalState.calendarPopupVisible = !wasOpen;
}
}
}
}
}
}
// =========================================================================
// DROPDOWNS & POPUPS
// =========================================================================
// 1. Apple Menu Dropdown
AppleMenu {
id: appleDropdown
visible: GlobalState.appleMenuVisible
x: 8
y: Theme.menuBarHeight + 2
}
// 2. Application Menus Dropdown (File, Edit, View, Window, Help)
AppMenuDropdown {
id: appMenuDropdown
visible: GlobalState.activeDropdownMenu !== ""
menuName: GlobalState.activeDropdownMenu
x: 60
y: Theme.menuBarHeight + 2
}
// 3. Volume Popup
VolumePopup {
id: volumeDropdown
visible: GlobalState.volumePopupVisible
anchors.top: barRect.bottom
anchors.topMargin: 2
anchors.right: parent.right
anchors.rightMargin: 190
}
// 4. Wi-Fi Popup
WifiPopup {
id: wifiDropdown
visible: GlobalState.wifiPopupVisible
anchors.top: barRect.bottom
anchors.topMargin: 2
anchors.right: parent.right
anchors.rightMargin: 160
}
// 5. Battery Popup
BatteryPopup {
id: batteryDropdown
visible: GlobalState.batteryPopupVisible
anchors.top: barRect.bottom
anchors.topMargin: 2
anchors.right: parent.right
anchors.rightMargin: 110
}
// 6. Calendar Popup
CalendarPopup {
id: calDropdown
visible: GlobalState.calendarPopupVisible
anchors.top: barRect.bottom
anchors.topMargin: 2
anchors.right: parent.right
anchors.rightMargin: 10
}
}

View File

@@ -1,190 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import Quickshell.Io
import "../theme"
import ".."
PanelWindow {
id: quickMenu
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
anchors {
top: true
left: true
}
margins {
top: 0
left: Theme.sidebarWidth
}
visible: GlobalState.appleMenuVisible
implicitWidth: 220
implicitHeight: menuContainer.implicitHeight
color: "transparent"
// Close on escape
Item {
anchors.fill: parent
focus: true
Keys.onEscapePressed: GlobalState.appleMenuVisible = false
}
Rectangle {
id: menuContainer
anchors.fill: parent
color: Theme.glassBg
border.color: Theme.glassBorder
border.width: 1
Column {
width: parent.width
padding: 8
component MenuItem: Item {
id: menuItem
width: parent.width
height: 28
property string text: ""
property string shortcut: ""
signal triggered()
Rectangle {
anchors.fill: parent
color: menuItemMouseArea.containsMouse ? Qt.rgba(Theme.accentVolt.r, Theme.accentVolt.g, Theme.accentVolt.b, 0.1) : "transparent"
}
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: 2
color: Theme.accentVolt
visible: menuItemMouseArea.containsMouse
}
Text {
anchors.left: parent.left
anchors.leftMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: menuItem.text
font.family: Theme.fontSans
font.pixelSize: 13
color: menuItemMouseArea.containsMouse ? Theme.textPrimary : Theme.textSecondary
}
Text {
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: menuItem.shortcut
font.family: Theme.fontMono
font.pixelSize: 11
color: Theme.textMuted
visible: text !== ""
}
MouseArea {
id: menuItemMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
menuItem.triggered()
GlobalState.appleMenuVisible = false
}
}
}
component MenuSeparator: Item {
width: parent.width
height: 17
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 12
anchors.rightMargin: 12
height: 1
color: Theme.glassBorder
}
}
MenuItem {
text: "System Info..."
onTriggered: GlobalState.aboutMacVisible = true
}
MenuItem {
text: "System Preferences..."
onTriggered: {
let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["pavucontrol"] }', quickMenu)
p.running = true
}
}
MenuItem {
text: "Activity Monitor"
shortcut: "btop"
onTriggered: {
let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["foot", "-e", "btop"] }', quickMenu)
p.running = true
}
}
MenuSeparator {}
MenuItem {
text: "Sleep"
onTriggered: {
let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["systemctl", "suspend"] }', quickMenu)
p.running = true
}
}
MenuItem {
text: "Restart..."
onTriggered: {
let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["systemctl", "reboot"] }', quickMenu)
p.running = true
}
}
MenuItem {
text: "Shut Down..."
onTriggered: {
let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["systemctl", "poweroff"] }', quickMenu)
p.running = true
}
}
MenuSeparator {}
MenuItem {
text: "Lock Screen"
shortcut: "Super+L"
onTriggered: {
let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["hyprlock"] }', quickMenu)
p.running = true
}
}
MenuItem {
text: "Log Out"
shortcut: "Super+Shift+Q"
onTriggered: {
let p = Qt.createQmlObject('import Quickshell.Io; Process { command: ["hyprctl", "dispatch", "exit"] }', quickMenu)
p.running = true
}
}
}
}
}

View File

@@ -1,532 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland
import "../theme"
import ".."
PanelWindow {
id: root
property var screen
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "quickshell-sidepanel"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
anchors {
top: true
bottom: true
left: true
right: false
}
exclusiveZone: Theme.sidebarWidth
implicitWidth: Theme.sidebarWidth
color: Theme.bgPrimary
// Right edge hairline border
Rectangle {
width: 1
color: Theme.glassBorder
anchors {
right: parent.right
top: parent.top
bottom: parent.bottom
}
}
component Separator: Rectangle {
width: 36
height: 1
color: Theme.glassBorder
Layout.alignment: Qt.AlignHCenter
}
component DockIcon: Item {
id: dockIconRoot
width: 36
height: 36
Layout.alignment: Qt.AlignHCenter
property string appClass: ""
property string execCmd: ""
property string iconSource: ""
property string tooltipText: ""
property bool isRunning: (appClass !== "" && GlobalState.activeWindowClass === appClass) || (GlobalState.runningApps && GlobalState.runningApps.indexOf(appClass) !== -1)
Image {
id: img
anchors.centerIn: parent
width: 24
height: 24
source: iconSource ? Qt.resolvedUrl("../assets/" + iconSource) : ""
sourceSize: Qt.size(24, 24)
fillMode: Image.PreserveAspectFit
opacity: mouseArea.containsMouse ? 1.0 : 0.8
scale: mouseArea.containsMouse ? 1.15 : 1.0
Behavior on scale {
NumberAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic }
}
Behavior on opacity {
NumberAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic }
}
}
Rectangle {
id: runningDot
width: 4
height: 4
radius: 2
color: Theme.accentVolt
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
visible: isRunning
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
if (execCmd !== "") {
GlobalState.launchApp(execCmd)
}
}
}
// Tooltip
Rectangle {
id: tooltip
visible: mouseArea.containsMouse && tooltipText !== ""
color: Theme.glassBg
radius: 4
width: tooltipLabel.implicitWidth + 16
height: tooltipLabel.implicitHeight + 8
anchors.left: parent.right
anchors.leftMargin: 24
anchors.verticalCenter: parent.verticalCenter
Text {
id: tooltipLabel
anchors.centerIn: parent
text: tooltipText
color: Theme.textPrimary
font.family: Theme.fontSans
font.pixelSize: 11
}
}
}
ColumnLayout {
anchors.fill: parent
spacing: 8
// Top spacer
Item { height: 4; Layout.fillWidth: true }
// 1. AG Logo Button
Rectangle {
width: 36
height: 36
radius: 4
color: logoMouse.containsMouse ? Qt.rgba(1,1,1,0.05) : "transparent"
Layout.alignment: Qt.AlignHCenter
Behavior on color {
ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic }
}
Text {
anchors.centerIn: parent
text: "AG"
font.family: Theme.fontSerif
font.italic: true
font.pixelSize: 16
color: Theme.accentVolt
}
MouseArea {
id: logoMouse
anchors.fill: parent
hoverEnabled: true
onClicked: GlobalState.appleMenuVisible = !GlobalState.appleMenuVisible
}
}
Separator {}
// 3. Workspace Indicators
Column {
Layout.alignment: Qt.AlignHCenter
spacing: 2
Repeater {
model: GlobalState.workspaceList
delegate: Rectangle {
width: 52
height: 36
color: wsMouse.containsMouse ? Qt.rgba(1,1,1,0.04) : "transparent"
property bool isActive: GlobalState.activeWorkspace === modelData
Behavior on color {
ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic }
}
Rectangle {
width: 2
height: 20
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
color: Theme.accentVolt
visible: isActive
}
Column {
anchors.centerIn: parent
spacing: 2
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: (modelData < 10 ? "0" : "") + modelData
color: isActive ? Theme.accentVolt : Theme.textMuted
font.family: Theme.fontMono
font.pixelSize: 12
Behavior on color {
ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic }
}
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: (GlobalState.workspaceNames && GlobalState.workspaceNames[modelData]) ? GlobalState.workspaceNames[modelData] : ""
color: Theme.textMuted
font.family: Theme.fontSans
font.pixelSize: 8
opacity: 0.7
}
}
MouseArea {
id: wsMouse
anchors.fill: parent
hoverEnabled: true
onClicked: GlobalState.switchWorkspace(modelData)
}
}
}
}
Separator {}
// 5. Spotlight trigger
Item {
width: 36
height: 36
Layout.alignment: Qt.AlignHCenter
Text {
anchors.centerIn: parent
text: "⌕"
font.pixelSize: 20
color: spotlightMouse.containsMouse ? Theme.accentVolt : Theme.textMuted
Behavior on color {
ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic }
}
}
MouseArea {
id: spotlightMouse
anchors.fill: parent
hoverEnabled: true
onClicked: GlobalState.toggleSpotlight()
}
}
Separator {}
// 7. Pinned Apps
Column {
Layout.alignment: Qt.AlignHCenter
spacing: 8
DockIcon {
appClass: ""
execCmd: "nautilus"
iconSource: "finder.svg"
tooltipText: "Finder"
}
DockIcon {
appClass: "brave-browser"
execCmd: "brave"
iconSource: "browser.svg"
tooltipText: "Brave Browser"
}
DockIcon {
appClass: "foot"
execCmd: "foot"
iconSource: "terminal.svg"
tooltipText: "Terminal"
}
DockIcon {
appClass: "nvim"
execCmd: "foot -e nvim"
iconSource: "code.svg"
tooltipText: "Code"
}
DockIcon {
appClass: "pavucontrol"
execCmd: "pavucontrol"
iconSource: "preferences.svg"
tooltipText: "Settings"
}
DockIcon {
appClass: ""
execCmd: "spotify"
iconSource: "music.svg"
tooltipText: "Music"
}
}
Separator {}
// 9. Running Apps (non-pinned)
Column {
Layout.alignment: Qt.AlignHCenter
spacing: 8
Repeater {
model: {
var pinned = ["foot", "brave-browser", "pavucontrol", "nvim", "nautilus", "spotify", ""]
var running = []
var seen = {}
if (GlobalState.runningApps) {
for (var i = 0; i < GlobalState.runningApps.length; i++) {
// hyprctl clients entries are objects; extract the class string
var cls = GlobalState.runningApps[i] && GlobalState.runningApps[i].class
if (cls && cls.length > 0 && pinned.indexOf(cls) === -1 && !seen[cls]) {
seen[cls] = true
running.push(cls)
}
}
}
return running
}
delegate: Item {
width: 36
height: 36
Rectangle {
anchors.centerIn: parent
width: 24
height: 24
radius: 12
color: Qt.rgba(1, 1, 1, 0.1)
Text {
anchors.centerIn: parent
text: modelData && modelData.length > 0 ? modelData.charAt(0).toUpperCase() : "?"
color: Theme.textPrimary
font.family: Theme.fontSans
font.pixelSize: 12
}
}
Rectangle {
width: 4
height: 4
radius: 2
color: Theme.accentVolt
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
// Focus app logic here
}
}
}
}
}
// 10. Flexible spacer
Item { Layout.fillHeight: true }
Separator {}
// 12. Telemetry mini-readouts
Column {
Layout.alignment: Qt.AlignHCenter
spacing: 2
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "CPU" + (GlobalState.cpuPercent !== undefined ? GlobalState.cpuPercent : "0") + "%"
font.family: Theme.fontMono
font.pixelSize: 11
color: (GlobalState.cpuPercent !== undefined && GlobalState.cpuPercent > 80) ? Theme.accentCoral : Theme.textMuted
Behavior on color { ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } }
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "RAM" + (GlobalState.memUsedGb !== undefined ? GlobalState.memUsedGb : "0") + "G"
font.family: Theme.fontMono
font.pixelSize: 11
color: (GlobalState.memPercent !== undefined && GlobalState.memPercent > 85) ? Theme.accentCoral : Theme.textMuted
Behavior on color { ColorAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } }
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "VRM" + (GlobalState.gpuVramUsed !== undefined ? GlobalState.gpuVramUsed : "0") + "G"
font.family: Theme.fontMono
font.pixelSize: 11
color: Theme.textMuted
}
}
Separator {}
// 14. System tray icons
Column {
Layout.alignment: Qt.AlignHCenter
spacing: 8
// Volume
Item {
width: 36
height: 36
Image {
anchors.centerIn: parent
width: 16
height: 16
source: Qt.resolvedUrl("../assets/volume.svg")
sourceSize: Qt.size(16, 16)
opacity: volMouse.containsMouse ? 1.0 : 0.7
Behavior on opacity { NumberAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } }
}
MouseArea {
id: volMouse
anchors.fill: parent
hoverEnabled: true
onClicked: {
GlobalState.systemHudActiveSection = "volume"
GlobalState.systemHudVisible = true
}
}
}
// WiFi
Item {
width: 36
height: 36
Image {
anchors.centerIn: parent
width: 16
height: 16
source: Qt.resolvedUrl("../assets/wifi.svg")
sourceSize: Qt.size(16, 16)
opacity: GlobalState.wifiConnected ? 0.7 : 0.3
Behavior on opacity { NumberAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } }
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
GlobalState.systemHudActiveSection = "network"
GlobalState.systemHudVisible = true
}
}
}
// Battery
Item {
width: 36
height: 44
visible: GlobalState.batteryPresent !== undefined ? GlobalState.batteryPresent : false
Column {
anchors.centerIn: parent
spacing: 2
Image {
anchors.horizontalCenter: parent.horizontalCenter
width: 16
height: 16
source: Qt.resolvedUrl("../assets/battery.svg")
sourceSize: Qt.size(16, 16)
opacity: (GlobalState.batteryPercent !== undefined && GlobalState.batteryPercent < 20) ? 1.0 : 0.7
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: (GlobalState.batteryPercent !== undefined ? GlobalState.batteryPercent : "100") + "%"
font.family: Theme.fontMono
font.pixelSize: 9
color: (GlobalState.batteryPercent !== undefined && GlobalState.batteryPercent < 20) ? Theme.accentCoral : Theme.textSecondary
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
GlobalState.systemHudActiveSection = "battery"
GlobalState.systemHudVisible = true
}
}
}
}
Separator {}
// 16. Clock
Item {
width: 36
height: 44
Layout.alignment: Qt.AlignHCenter
Column {
anchors.centerIn: parent
spacing: 2
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: GlobalState.timeString !== undefined ? GlobalState.timeString : "00:00"
font.family: Theme.fontMono
font.pixelSize: 12
color: Theme.textPrimary
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: GlobalState.dateString !== undefined ? GlobalState.dateString : "Jan 01"
font.family: Theme.fontMono
font.pixelSize: 9
color: Theme.textMuted
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: {
GlobalState.systemHudActiveSection = "calendar"
GlobalState.systemHudVisible = true
}
}
}
// Bottom spacer
Item { height: 4; Layout.fillWidth: true }
}
}

View File

@@ -1,365 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import Quickshell.Io
import "../theme"
import ".."
PanelWindow {
id: spotWindow
visible: GlobalState.spotlightVisible
anchors {
top: true
left: true
right: true
bottom: true
}
color: Qt.rgba(0, 0, 0, 0.45)
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "quickshell-spotlight"
WlrLayershell.keyboardFocus: visible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
// Dismiss when clicking outside modal
MouseArea {
anchors.fill: parent
onClicked: GlobalState.spotlightVisible = false
}
// App Database Loader Process
readonly property Process appScanProc: Process {
// Indexes .desktop applications AND executable user scripts so the
// launcher finds "everything" without depending on an external service.
command: ["bash", "-c", "dirs=(\"/run/current-system/sw/share/applications\" \"$HOME/.nix-profile/share/applications\" \"$HOME/.local/share/applications\"); bdirs=(\"$HOME/.local/bin\" \"$HOME/bin\" \"$HOME/.cargo/bin\"); echo '['; first=1; for d in \"${dirs[@]}\"; do [ -d \"$d\" ] && for f in \"$d\"/*.desktop; do [ -e \"$f\" ] || continue; grep -q \"^NoDisplay=true\" \"$f\" 2>/dev/null && continue; n=$(grep -m 1 \"^Name=\" \"$f\" 2>/dev/null | cut -d= -f2-); e=$(grep -m 1 \"^Exec=\" \"$f\" 2>/dev/null | cut -d= -f2- | sed -E 's/%[fFuUdDnNickvm]//g'); if [ -n \"$n\" ] && [ -n \"$e\" ]; then [ $first -eq 0 ] && echo ','; first=0; echo \"{\\\"name\\\":\\\"$n\\\",\\\"exec\\\":\\\"$e\\\"}\"; fi; done; done; for d in \"${bdirs[@]}\"; do [ -d \"$d\" ] || continue; for f in \"$d\"/*; do [ -f \"$f\" ] && [ -x \"$f\" ] || continue; n=$(basename \"$f\"); [ $first -eq 0 ] && echo ','; first=0; printf '{\\\"name\\\":\\\"%s\\\",\\\"exec\\\":\\\"%s\\\",\\\"comment\\\":\\\"script\\\"}\\n' \"$n\" \"$f\"; done; done; echo ']'"]
stdout: StdioCollector {
onDataChanged: {
try {
const parsed = JSON.parse(String(data).trim());
if (Array.isArray(parsed)) {
spotWindow.allApps = parsed;
}
} catch(e) {}
}
}
}
property var allApps: [
{ name: "Brave Browser", exec: "brave", comment: "Web Browser", icon: "browser" },
{ name: "Foot Terminal", exec: "foot", comment: "Terminal Emulator", icon: "terminal" },
{ name: "Neovim", exec: "foot -e nvim", comment: "Code Editor", icon: "code" },
{ name: "Activity Monitor (Btop)", exec: "foot -e btop", comment: "System Resources", icon: "preferences" },
{ name: "Volume & Sound (Pavucontrol)", exec: "pavucontrol", comment: "Audio Settings", icon: "preferences" },
{ name: "Lock Screen", exec: "hyprlock", comment: "Lock Session", icon: "preferences" },
{ name: "System Reboot", exec: "systemctl reboot", comment: "Reboot NixOS", icon: "preferences" },
{ name: "Shut Down", exec: "systemctl poweroff", comment: "Power Off", icon: "preferences" }
]
property int selectedIndex: 0
onVisibleChanged: {
if (visible) {
appScanProc.running = true;
searchInput.text = "";
selectedIndex = 0;
searchInput.forceActiveFocus();
}
}
// Centered Spotlight Search Modal
Item {
id: modalContainer
anchors.horizontalCenter: parent.horizontalCenter
y: 140
width: 640
height: modalBox.implicitHeight
scale: spotWindow.visible ? 1.0 : 0.96
opacity: spotWindow.visible ? 1.0 : 0.0
Behavior on scale {
NumberAnimation { duration: Theme.animSmooth; easing.type: Easing.OutQuint }
}
Behavior on opacity {
NumberAnimation { duration: Theme.animSmooth; easing.type: Easing.OutQuint }
}
Rectangle {
id: modalBox
anchors.fill: parent
implicitHeight: searchBarRowContainer.height + (resultsListView.count > 0 || mathResultText !== "" ? 340 : 0)
radius: 0
color: Theme.bgPrimary
border.color: Theme.glassBorder
border.width: 1
Behavior on implicitHeight {
NumberAnimation { duration: 180; easing.type: Easing.OutQuad }
}
// Prevent clicking inside from closing modal
MouseArea {
anchors.fill: parent
onClicked: {}
}
ColumnLayout {
anchors.fill: parent
spacing: 0
// Top Search Bar Area
Item {
id: searchBarRowContainer
Layout.fillWidth: true
Layout.preferredHeight: 64
RowLayout {
id: searchBarRow
anchors.fill: parent
anchors.margins: 14
spacing: 12
Text {
text: "⌕"
font.family: Theme.fontSans
font.pixelSize: 24
color: Theme.accentVolt
Layout.alignment: Qt.AlignVCenter
}
TextInput {
id: searchInput
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
font.family: Theme.fontSans
font.pixelSize: 20
color: Theme.textPrimary
focus: true
clip: true
Text {
anchors.fill: parent
visible: searchInput.text === ""
text: "Spotlight Search"
font.family: Theme.fontSans
font.pixelSize: 20
color: Theme.textMuted
}
Keys.onEscapePressed: GlobalState.spotlightVisible = false
Keys.onDownPressed: {
if (resultsListView.count > 0) {
spotWindow.selectedIndex = Math.min(resultsListView.count - 1, spotWindow.selectedIndex + 1);
}
}
Keys.onUpPressed: {
if (resultsListView.count > 0) {
spotWindow.selectedIndex = Math.max(0, spotWindow.selectedIndex - 1);
}
}
Keys.onReturnPressed: {
if (filteredModel.count > 0 && spotWindow.selectedIndex < filteredModel.count) {
const item = filteredModel.get(spotWindow.selectedIndex);
GlobalState.launchApp(item.exec);
}
}
}
// Clear button
Rectangle {
visible: searchInput.text !== ""
width: 24
height: 24
radius: 0
color: "transparent"
Text {
anchors.centerIn: parent
text: "✕"
font.pixelSize: 16
color: clearMouseArea.containsMouse ? Theme.textPrimary : Theme.textMuted
}
MouseArea {
id: clearMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: searchInput.text = ""
}
}
}
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: Theme.glassBorder
}
}
// Math Calculation Result Banner (if valid formula)
Rectangle {
visible: mathResultText !== ""
Layout.fillWidth: true
Layout.preferredHeight: 36
Layout.margins: 14
radius: 0
color: "transparent"
RowLayout {
anchors.fill: parent
anchors.margins: 8
spacing: 8
Text {
text: "="
font.family: Theme.fontMono
font.pixelSize: 16
font.weight: Font.Bold
color: Theme.accentVolt
}
Text {
text: mathResultText
font.family: Theme.fontMono
font.pixelSize: 16
color: Theme.accentVolt
Layout.fillWidth: true
}
}
}
// Search Results List View
ListView {
id: resultsListView
visible: count > 0
Layout.fillWidth: true
Layout.preferredHeight: Math.min(270, count * 40)
Layout.topMargin: 8
Layout.bottomMargin: 8
clip: true
spacing: 0
model: filteredModel
delegate: Rectangle {
id: itemDelegate
required property int index
required property string name
required property string exec
required property string comment
width: resultsListView.width
height: 40
radius: 0
color: spotWindow.selectedIndex === index ? Qt.rgba(0.812, 0.988, 0.0, 0.08) : "transparent"
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: 2
color: Theme.accentVolt
visible: spotWindow.selectedIndex === index
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: 14
anchors.rightMargin: 14
spacing: 12
ColumnLayout {
Layout.fillWidth: true
spacing: 2
Text {
text: itemDelegate.name
font.family: Theme.fontSans
font.pixelSize: 14
color: Theme.textPrimary
elide: Text.ElideRight
Layout.fillWidth: true
}
Text {
text: itemDelegate.comment || itemDelegate.exec
font.family: Theme.fontMono
font.pixelSize: 11
color: Theme.textMuted
elide: Text.ElideRight
Layout.fillWidth: true
}
}
Text {
text: "↩"
font.pixelSize: 14
color: spotWindow.selectedIndex === itemDelegate.index
? Theme.textPrimary : "transparent"
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: spotWindow.selectedIndex = itemDelegate.index
onClicked: GlobalState.launchApp(itemDelegate.exec)
}
}
}
}
}
}
// Math calculation evaluator property
readonly property string mathResultText: {
const query = searchInput.text.trim();
if (/^[0-9+\-*/^().\s%]+$/.test(query) && /[0-9]/.test(query) && /[+\-*/^%]/.test(query)) {
try {
// Safe evaluation of arithmetic
const res = Function(`"use strict"; return (${query.replace(/\^/g, "**")})`)();
if (typeof res === "number" && !isNaN(res) && isFinite(res)) {
return `${res}`;
}
} catch(e) {}
}
return "";
}
// Filtered ListModel updated on text change
ListModel {
id: filteredModel
}
function updateFilter() {
filteredModel.clear();
const query = searchInput.text.toLowerCase().trim();
let count = 0;
for (let i = 0; i < allApps.length && count < 12; i++) {
const app = allApps[i];
if (!app.name) continue;
if (query === "" || app.name.toLowerCase().includes(query) || (app.exec && app.exec.toLowerCase().includes(query))) {
filteredModel.append({
name: app.name,
exec: app.exec || "",
comment: app.comment || ""
});
count++;
}
}
}
Connections {
target: searchInput
function onTextChanged() {
spotWindow.selectedIndex = 0;
updateFilter();
}
}
Component.onCompleted: updateFilter()
}

View File

@@ -1,697 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import Quickshell.Io
import "../theme"
import ".."
PanelWindow {
id: root
anchors {
top: true
bottom: true
left: true
}
margins {
left: Theme.sidebarWidth
top: Theme.topStripHeight
}
implicitWidth: Theme.hudWidth
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "quickshell-systemhud"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
visible: GlobalState.systemHudVisible
color: "transparent"
property real slideOffset: GlobalState.systemHudVisible ? 0 : -Theme.hudWidth
Behavior on slideOffset {
NumberAnimation {
duration: Theme.animSmooth
easing.type: Easing.OutQuint
}
}
Shortcut {
sequence: "Escape"
onActivated: GlobalState.systemHudVisible = false
}
component SectionHeader: Item {
property string sectionNum: "00"
property string title: "Section"
implicitWidth: parent.width
implicitHeight: 24
RowLayout {
anchors.fill: parent
spacing: 8
Text {
text: sectionNum + " //"
font.family: Theme.fontMono
font.pixelSize: 12
color: Theme.accentVolt
}
Text {
text: title
font.family: Theme.fontSerif
font.italic: true
font.pixelSize: 16
color: Theme.textPrimary
Layout.fillWidth: true
}
}
}
component ToggleCard: Rectangle {
property string title: ""
property string icon: ""
property string glyph: "" // Nerd Font glyph alternative to icon file
property bool active: false
signal clicked()
Layout.preferredWidth: 160
Layout.preferredHeight: 56
color: Theme.bgPrimary
border.color: active ? Qt.rgba(Theme.accentVolt.r, Theme.accentVolt.g, Theme.accentVolt.b, 0.3) : Theme.glassBorder
border.width: 1
radius: 4
Behavior on border.color { ColorAnimation { duration: Theme.animFast } }
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: clicked()
onEntered: if (!active) border.color = Theme.glassBorderHover
onExited: if (!active) border.color = Theme.glassBorder
}
RowLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 12
Text {
visible: parent.parent.glyph !== ""
text: parent.parent.glyph
font.family: "JetBrainsMono Nerd Font"
font.pixelSize: 20
color: parent.parent.active ? Theme.accentVolt : Theme.textMuted
}
Image {
source: parent.parent.icon && parent.parent.glyph === "" ? Qt.resolvedUrl("../" + parent.parent.icon) : ""
Layout.preferredWidth: 24
Layout.preferredHeight: 24
sourceSize: Qt.size(24, 24)
visible: parent.parent.icon !== "" && parent.parent.glyph === ""
}
Text {
text: parent.parent.title
color: Theme.textPrimary
font.family: Theme.fontSans
font.pixelSize: 13
Layout.fillWidth: true
elide: Text.ElideRight
}
}
}
component CustomSlider: Item {
property real value: 0
signal valueChangedUser(real newVal)
implicitWidth: parent.width
implicitHeight: 24
Rectangle {
id: track
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 40
height: 6
color: Theme.bgSecondary
radius: 3
Rectangle {
id: fill
width: track.width * (parent.parent.value / 100)
height: track.height
color: Theme.accentVolt
radius: 3
}
Rectangle {
width: 14
height: 14
radius: 7
color: Theme.textPrimary
anchors.verticalCenter: parent.verticalCenter
x: Math.max(0, fill.width - 7)
}
MouseArea {
anchors.fill: parent
anchors.margins: -10
function updateValue(mouseX) {
let p = Math.max(0, Math.min(1, mouseX / track.width))
parent.parent.valueChangedUser(p * 100)
}
onPressed: (mouse) => updateValue(mouse.x)
onPositionChanged: (mouse) => updateValue(mouse.x)
}
}
Text {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: Math.round(parent.value) + "%"
color: Theme.textPrimary
font.family: Theme.fontMono
font.pixelSize: 12
}
}
component ProgressBarItem: ColumnLayout {
property string label: ""
property real percent: 0
property string valueText: ""
property color barColor: Theme.accentVolt
Layout.fillWidth: true
spacing: 4
RowLayout {
Layout.fillWidth: true
Text {
text: parent.parent.label
color: Theme.textPrimary
font.family: Theme.fontMono
font.pixelSize: 12
}
Item { Layout.fillWidth: true }
Text {
text: parent.parent.valueText
color: Theme.textSecondary
font.family: Theme.fontMono
font.pixelSize: 12
}
}
Rectangle {
Layout.fillWidth: true
height: 8
color: Theme.bgSecondary
radius: 4
Rectangle {
width: parent.width * (parent.parent.percent / 100)
height: parent.height
color: parent.parent.barColor
radius: 4
Behavior on width { NumberAnimation { duration: Theme.animFast; easing.type: Easing.OutCubic } }
Behavior on color { ColorAnimation { duration: Theme.animFast } }
}
}
}
Rectangle {
id: panelContent
anchors.fill: parent
color: Theme.glassBg
opacity: GlobalState.systemHudVisible ? 1 : 0
transform: Translate { x: slideOffset }
Behavior on opacity {
NumberAnimation {
duration: Theme.animSmooth
easing.type: Easing.OutQuint
}
}
Rectangle {
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
width: 1
color: Theme.glassBorder
}
Item {
id: header
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: 60
Rectangle {
anchors.bottom: parent.bottom
width: parent.width
height: 1
color: Theme.glassBorder
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: 20
anchors.rightMargin: 20
Text {
text: "System HUD"
font.family: Theme.fontSerif
font.italic: true
font.pixelSize: 19
color: Theme.textPrimary
Layout.fillWidth: true
}
Text {
text: "✕"
color: Theme.textMuted
font.pixelSize: 16
MouseArea {
anchors.fill: parent
anchors.margins: -10
hoverEnabled: true
onEntered: parent.color = Theme.textPrimary
onExited: parent.color = Theme.textMuted
onClicked: GlobalState.systemHudVisible = false
}
}
}
}
ScrollView {
id: scrollView
anchors.top: header.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
contentWidth: availableWidth
clip: true
ColumnLayout {
id: contentLayout
width: parent.width - 40
x: 20
spacing: 32
Item { Layout.preferredHeight: 16 } // Top padding
// Section 1: Toggles
ColumnLayout {
id: sectionToggles
Layout.fillWidth: true
spacing: 16
SectionHeader { sectionNum: "01"; title: "Toggles" }
GridLayout {
columns: 2
columnSpacing: 12
rowSpacing: 12
Layout.fillWidth: true
ToggleCard {
title: GlobalState.wifiSsid !== "" ? GlobalState.wifiSsid : "Wi-Fi"
icon: "assets/wifi.svg"
active: GlobalState.wifiConnected
onClicked: {
Quickshell.execDetached(["sh", "-c", "nm-connection-editor || foot -e nmtui"])
}
}
ToggleCard {
title: "Bluetooth"
icon: "assets/bluetooth.svg"
active: false // Adjust if there's a bluetooth state
onClicked: {
let proc = Quickshell.createProcess("sh", ["-c", "foot -e bluetoothctl"])
proc.start()
}
}
ToggleCard {
property bool dndActive: false
title: "Do Not Disturb"
glyph: "\uF186"
active: dndActive
onClicked: dndActive = !dndActive
}
ToggleCard {
title: GlobalState.volumeMuted ? "Muted" : "Sound"
glyph: GlobalState.volumeMuted ? "\uF026" : "\uF028"
active: !GlobalState.volumeMuted
onClicked: GlobalState.toggleVolumeMute()
}
}
}
// Section 2: Volume
ColumnLayout {
id: sectionVolume
Layout.fillWidth: true
spacing: 16
SectionHeader { sectionNum: "02"; title: "Volume" }
CustomSlider {
Layout.fillWidth: true
value: GlobalState.volume
onValueChangedUser: (val) => GlobalState.volume = val // Adjust to actual volume setter if needed
}
RowLayout {
Layout.fillWidth: true
Text {
text: GlobalState.volumeMuted ? "Unmute" : "Mute"
color: Theme.textSecondary
font.family: Theme.fontSans
font.pixelSize: 13
MouseArea {
anchors.fill: parent
onClicked: GlobalState.toggleVolumeMute()
}
}
Item { Layout.fillWidth: true }
Text {
text: "Sound Preferences..."
color: Theme.textMuted
font.family: Theme.fontSans
font.pixelSize: 13
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: parent.color = Theme.textPrimary
onExited: parent.color = Theme.textMuted
onClicked: {
let proc = Quickshell.createProcess("pavucontrol", [])
proc.start()
}
}
}
}
}
// Section 3: Brightness
ColumnLayout {
id: sectionBrightness
Layout.fillWidth: true
spacing: 16
SectionHeader { sectionNum: "03"; title: "Brightness" }
CustomSlider {
Layout.fillWidth: true
value: GlobalState.brightness
onValueChangedUser: (val) => GlobalState.setBrightness(val)
}
}
// Section 4: Performance
ColumnLayout {
id: sectionPerformance
Layout.fillWidth: true
spacing: 16
SectionHeader { sectionNum: "04"; title: "Performance" }
ProgressBarItem {
label: "CPU"
percent: GlobalState.cpuPercent
valueText: GlobalState.cpuPercent + "%"
barColor: GlobalState.cpuPercent > 80 ? Theme.accentCoral : Theme.accentVolt
}
ProgressBarItem {
label: "RAM"
percent: (GlobalState.memUsedGb / Math.max(1, GlobalState.memTotalGb)) * 100
valueText: GlobalState.memUsedGb + " / " + GlobalState.memTotalGb + " GB"
}
ProgressBarItem {
label: "DISK"
percent: GlobalState.diskPercent
valueText: GlobalState.diskUsedGb + " / " + GlobalState.diskTotalGb + " GB"
barColor: GlobalState.diskPercent > 90 ? Theme.accentCoral : Theme.accentVolt
}
ProgressBarItem {
label: "VRAM"
percent: (GlobalState.gpuVramUsed / Math.max(1, GlobalState.gpuVramTotal)) * 100
valueText: GlobalState.gpuVramUsed + " / " + GlobalState.gpuVramTotal + " GB"
}
}
// Section 5: Network
ColumnLayout {
id: sectionNetwork
Layout.fillWidth: true
spacing: 16
SectionHeader { sectionNum: "05"; title: "Network" }
RowLayout {
spacing: 8
Rectangle {
width: 8
height: 8
radius: 4
color: GlobalState.wifiConnected ? Theme.accentVolt : Theme.accentCoral
}
Text {
text: {
if (!GlobalState.wifiConnected) return "Disconnected";
if (GlobalState.connectionType === "wifi") return "Wi-Fi — " + (GlobalState.wifiSsid !== "" ? GlobalState.wifiSsid : "unknown");
if (GlobalState.connectionType === "eth") return "Ethernet";
return "Connected";
}
color: Theme.textPrimary
font.family: Theme.fontSans
font.pixelSize: 14
}
}
Text {
visible: GlobalState.wifiConnected
text: "IP: " + (GlobalState.ipAddress !== "" ? GlobalState.ipAddress : "—")
color: Theme.textSecondary
font.family: Theme.fontMono
font.pixelSize: 13
}
Text {
text: "Network Preferences..."
color: Theme.textMuted
font.family: Theme.fontSans
font.pixelSize: 13
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: parent.color = Theme.textPrimary
onExited: parent.color = Theme.textMuted
onClicked: {
Quickshell.execDetached(["sh", "-c", "nm-connection-editor || foot -e nmtui"])
}
}
}
}
// Section 6: Battery
ColumnLayout {
id: sectionBattery
Layout.fillWidth: true
spacing: 16
visible: GlobalState.batteryPresent
SectionHeader { sectionNum: "06"; title: "Battery" }
RowLayout {
spacing: 16
Text {
text: GlobalState.batteryPercent + "%"
font.family: Theme.fontMono
font.pixelSize: 24
color: GlobalState.batteryStatus === "Charging" ? Theme.accentVolt :
(GlobalState.batteryPercent < 20 ? Theme.accentCoral : Theme.textPrimary)
}
Text {
text: GlobalState.batteryStatus
color: Theme.textSecondary
font.family: Theme.fontSans
font.pixelSize: 14
}
}
}
// Section 7: Now Playing
ColumnLayout {
id: sectionNowPlaying
Layout.fillWidth: true
spacing: 16
visible: GlobalState.mediaTitle !== ""
SectionHeader { sectionNum: "07"; title: "Now Playing" }
ColumnLayout {
spacing: 4
Text {
text: GlobalState.mediaTitle
color: Theme.textPrimary
font.family: Theme.fontSans
font.bold: true
font.pixelSize: 14
Layout.fillWidth: true
elide: Text.ElideRight
}
Text {
text: GlobalState.mediaArtist
color: Theme.textSecondary
font.family: Theme.fontSans
font.pixelSize: 12
Layout.fillWidth: true
elide: Text.ElideRight
}
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 16
Text {
text: "⏮"
font.pixelSize: 20
color: Theme.textSecondary
MouseArea {
anchors.fill: parent
anchors.margins: -10
hoverEnabled: true
onEntered: parent.color = Theme.accentVolt
onExited: parent.color = Theme.textSecondary
onClicked: GlobalState.mediaPrevious()
}
}
Text {
text: GlobalState.mediaPlaying ? "⏸" : "▶"
font.pixelSize: 24
color: Theme.textSecondary
MouseArea {
anchors.fill: parent
anchors.margins: -10
hoverEnabled: true
onEntered: parent.color = Theme.accentVolt
onExited: parent.color = Theme.textSecondary
onClicked: GlobalState.mediaPlayPause()
}
}
Text {
text: "⏭"
font.pixelSize: 20
color: Theme.textSecondary
MouseArea {
anchors.fill: parent
anchors.margins: -10
hoverEnabled: true
onEntered: parent.color = Theme.accentVolt
onExited: parent.color = Theme.textSecondary
onClicked: GlobalState.mediaNext()
}
}
}
}
// Section 8: Calendar
ColumnLayout {
id: sectionCalendar
Layout.fillWidth: true
spacing: 16
SectionHeader { sectionNum: "08"; title: "Calendar" }
RowLayout {
spacing: 12
Text {
text: GlobalState.timeString
font.family: Theme.fontMono
font.pixelSize: 22
color: Theme.accentVolt
}
Text {
text: GlobalState.fullDateString
font.family: Theme.fontSans
font.pixelSize: 13
color: Theme.textSecondary
}
}
GridLayout {
columns: 7
columnSpacing: 4
rowSpacing: 4
Layout.alignment: Qt.AlignHCenter
Repeater {
model: ["S", "M", "T", "W", "T", "F", "S"]
Text {
text: modelData
font.family: Theme.fontMono
font.pixelSize: 11
color: Theme.textMuted
horizontalAlignment: Text.AlignHCenter
Layout.preferredWidth: 32
}
}
Repeater {
model: 31
Rectangle {
property bool isToday: (index + 1) === new Date().getDate()
Layout.preferredWidth: 32
Layout.preferredHeight: 32
color: isToday ? Theme.accentVolt : "transparent"
radius: isToday ? 0 : 4
Text {
anchors.centerIn: parent
text: index + 1
color: parent.isToday ? "#ffffff" : Theme.textSecondary
font.family: Theme.fontMono
font.pixelSize: 12
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: if (!parent.isToday) parent.color = Qt.rgba(1, 1, 1, 0.04)
onExited: if (!parent.isToday) parent.color = "transparent"
}
}
}
}
}
Item { Layout.preferredHeight: 20 } // Bottom padding
}
}
}
// Auto-scroll to active section when changed
Connections {
target: GlobalState
function onSystemHudActiveSectionChanged() {
if (!GlobalState.systemHudVisible) return;
let targetY = 0;
if (GlobalState.systemHudActiveSection === "volume") targetY = sectionVolume.y;
else if (GlobalState.systemHudActiveSection === "brightness") targetY = sectionBrightness.y;
else if (GlobalState.systemHudActiveSection === "performance") targetY = sectionPerformance.y;
else if (GlobalState.systemHudActiveSection === "network") targetY = sectionNetwork.y;
else if (GlobalState.systemHudActiveSection === "battery") targetY = sectionBattery.y;
else if (GlobalState.systemHudActiveSection === "nowplaying") targetY = sectionNowPlaying.y;
else if (GlobalState.systemHudActiveSection === "calendar") targetY = sectionCalendar.y;
if (targetY > 0) {
// Ensure targetY doesn't scroll past max possible scroll
let maxY = Math.max(0, contentLayout.height - scrollView.height);
scrollView.ScrollBar.vertical.position = Math.min(targetY, maxY) / contentLayout.height;
}
}
}
}

View File

@@ -1,257 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import Quickshell.Io
import "../theme"
import ".."
PanelWindow {
id: systemInfoDialog
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: visible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
anchors {
top: true
bottom: true
left: true
right: true
}
visible: GlobalState.aboutMacVisible
color: Qt.rgba(0, 0, 0, 0.35)
// Close on click outside or Escape
MouseArea {
anchors.fill: parent
onClicked: GlobalState.aboutMacVisible = false
}
Item {
anchors.fill: parent
focus: true
Keys.onEscapePressed: GlobalState.aboutMacVisible = false
}
property string cpuName: ""
property string gpuName: ""
// Process to get CPU info
Process {
id: cpuProcess
command: ["sh", "-c", "grep 'model name' /proc/cpuinfo | head -n1 | cut -d':' -f2 | xargs"]
running: true
stdout: StdioCollector {
onDataChanged: {
const val = String(data).trim();
if (val.length > 0) systemInfoDialog.cpuName = val;
}
}
}
// Process to get GPU info
Process {
id: gpuProcess
command: ["sh", "-c", "nvidia-smi --query-gpu=name --format=csv,noheader | head -n1"]
running: true
stdout: StdioCollector {
onDataChanged: {
const val = String(data).trim();
if (val.length > 0) systemInfoDialog.gpuName = val;
}
}
}
Rectangle {
id: dialogBox
width: 480
height: 320
anchors.centerIn: parent
color: Theme.glassBg
border.color: Theme.glassBorder
border.width: 1
radius: 0
MouseArea {
anchors.fill: parent
// Eat clicks so they don't close the dialog
}
ColumnLayout {
anchors.fill: parent
spacing: 0
// Header bar
Item {
Layout.fillWidth: true
Layout.preferredHeight: 40
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: Theme.glassBorder
}
Rectangle {
id: closeBtn
width: 40
height: 40
anchors.left: parent.left
color: closeMouseArea.containsMouse ? Theme.accentCoral : "transparent"
Text {
anchors.centerIn: parent
text: "✕"
color: closeMouseArea.containsMouse ? Theme.textPrimary : Theme.textSecondary
font.pixelSize: 14
}
MouseArea {
id: closeMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: GlobalState.aboutMacVisible = false
}
}
Text {
anchors.centerIn: parent
text: "System Info"
font.family: Theme.fontSerif
font.italic: true
font.pixelSize: 15
color: Theme.textPrimary
}
}
// Body
Item {
Layout.fillWidth: true
Layout.fillHeight: true
Column {
anchors.centerIn: parent
spacing: 8
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "Silicon Volt"
font.family: Theme.fontSerif
font.italic: true
font.pixelSize: 24
color: Theme.accentVolt
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "NixOS // Hyprland"
font.family: Theme.fontMono
font.pixelSize: 12
color: Theme.textMuted
bottomPadding: 16
}
Grid {
anchors.horizontalCenter: parent.horizontalCenter
columns: 2
spacing: 12
rowSpacing: 8
// HOST
Text { width: 100; horizontalAlignment: Text.AlignRight; text: "HOST"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted }
Text { text: "hades"; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary }
// CPU
Text { width: 100; horizontalAlignment: Text.AlignRight; text: "CPU"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted }
Text { id: cpuText; text: systemInfoDialog.cpuName || "Loading..."; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary }
// MEMORY
Text { width: 100; horizontalAlignment: Text.AlignRight; text: "MEMORY"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted }
Text { text: GlobalState.memUsedGb + " / " + GlobalState.memTotalGb + " GB"; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary }
// GPU
Text { width: 100; horizontalAlignment: Text.AlignRight; text: "GPU"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted }
Text { id: gpuText; text: systemInfoDialog.gpuName || "Loading..."; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary }
// VRAM
Text { width: 100; horizontalAlignment: Text.AlignRight; text: "VRAM"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted }
Text { text: GlobalState.gpuVramUsed + " / " + GlobalState.gpuVramTotal + " GB"; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary }
// WM
Text { width: 100; horizontalAlignment: Text.AlignRight; text: "WM"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted }
Text { text: "Hyprland"; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary }
// OS
Text { width: 100; horizontalAlignment: Text.AlignRight; text: "OS"; font.family: Theme.fontMono; font.pixelSize: 11; color: Theme.textMuted }
Text { text: "NixOS"; font.family: Theme.fontSans; font.pixelSize: 13; color: Theme.textSecondary }
}
}
}
// Footer bar
Item {
Layout.fillWidth: true
Layout.preferredHeight: 44
Rectangle {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: Theme.glassBorder
}
component FooterButton: Rectangle {
id: btn
property string text: ""
property alias command: proc.command
width: btnText.width + 24
height: 28
anchors.verticalCenter: parent.verticalCenter
color: btnMouse.containsMouse ? Qt.rgba(Theme.accentVolt.r, Theme.accentVolt.g, Theme.accentVolt.b, 0.1) : "transparent"
border.color: Theme.glassBorder
border.width: 1
radius: 0
Text {
id: btnText
anchors.centerIn: parent
text: btn.text
font.family: Theme.fontSans
font.pixelSize: 12
color: btnMouse.containsMouse ? Theme.accentVolt : Theme.textMuted
}
MouseArea {
id: btnMouse
anchors.fill: parent
hoverEnabled: true
onClicked: proc.running = true
}
Process {
id: proc
}
}
FooterButton {
anchors.left: parent.left
anchors.leftMargin: 16
text: "System Report..."
command: ["foot", "-e", "btop"]
}
FooterButton {
anchors.right: parent.right
anchors.rightMargin: 16
text: "Software Update..."
command: ["foot", "-e", "sudo", "nixos-rebuild", "dry-activate"]
}
}
}
}
}

View File

@@ -1,177 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import "../theme"
import ".."
// Nerd Font glyphs (JetBrainsMono Nerd Font, installed system-wide)
// \uF1EB wifi \uF796 ethernet \uF538 memory \uF1C0 disk
// \uF028 volume \uF185 brightness \uF240 battery \uF0E7 charging
// \uF017 clock \uF2DB cpu
PanelWindow {
id: root
property var modelData
screen: modelData
anchors {
top: true
left: true
right: true
}
margins.left: Theme.sidebarWidth
implicitHeight: Theme.topStripHeight
color: Qt.rgba(Theme.bgPrimary.r, Theme.bgPrimary.g, Theme.bgPrimary.b, 0.92)
exclusiveZone: Theme.topStripHeight
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "quickshell-topstrip"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
Rectangle {
id: bottomBorder
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
height: 1
color: Theme.glassBorder
}
// --------------------------------------------------------------------
// Helpers to compose rich-text status segments
// --------------------------------------------------------------------
function pad(num) {
if (num === undefined || num === null) return "00";
return num < 10 ? "0" + num : num.toString();
}
function seg(icon, value, iconColor, valueColor) {
return "<font color=\"" + iconColor + "\">" + icon + "</font><font color=\"" + valueColor + "\"> " + value + "</font>";
}
function buildStats() {
const c = "#e5e2e1";
const dim = "#adaaaa";
const volt = "#CFFC00";
const coral = "#FF7A59";
const sep = "<font color=\"#2a2929\"> // </font>";
const parts = [];
// Network: type icon + SSID (wifi) + IP
if (GlobalState.wifiConnected) {
const icon = GlobalState.connectionType === "wifi" ? "\uF1EB" : "\uF796";
const label = (GlobalState.connectionType === "wifi" && GlobalState.wifiSsid !== "") ? GlobalState.wifiSsid : "eth";
parts.push(seg(icon, label + " " + (GlobalState.ipAddress !== "" ? GlobalState.ipAddress : "\u2014"), volt, c));
} else {
parts.push(seg("\uF1EB", "offline", coral, dim));
}
// Memory (used GB / total GB)
parts.push(seg("\uF538", GlobalState.memUsedGb + "G / " + GlobalState.memTotalGb + "G",
GlobalState.memPercent > 85 ? coral : dim, c));
// Disk (root filesystem percent)
parts.push(seg("\uF1C0", GlobalState.diskPercent + "%",
GlobalState.diskPercent > 90 ? coral : dim, c));
// Audio (mute state flips icon + color)
if (GlobalState.volumeMuted) {
parts.push(seg("\uF026", "mute", coral, dim));
} else {
parts.push(seg("\uF028", GlobalState.volume + "%", dim, c));
}
// Display brightness
parts.push(seg("\uF185", GlobalState.brightness + "%", dim, c));
// Battery (only when present, e.g. laptops)
if (GlobalState.batteryPresent) {
const icon = GlobalState.batteryCharging ? "\uF0E7" : "\uF240";
parts.push(seg(icon, GlobalState.batteryPercent + "%",
GlobalState.batteryPercent < 20 ? coral : dim, c));
}
return parts.join(sep);
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: 16
anchors.rightMargin: 16
spacing: 16
// Left Section: workspace indicator
Text {
id: workspaceIndicator
Layout.alignment: Qt.AlignVCenter
textFormat: Text.RichText
font.family: "JetBrainsMono Nerd Font"
font.pixelSize: 12
text: {
const name = GlobalState.workspaceNames[GlobalState.activeWorkspace] !== undefined
? GlobalState.workspaceNames[GlobalState.activeWorkspace] : "main";
return "<font color=\"#CFFC00\">" + pad(GlobalState.activeWorkspace) + "</font>"
+ "<font color=\"#2a2929\"> // </font>"
+ "<font color=\"#adaaaa\">" + name + "</font>";
}
}
// Center Section: focused window title
Text {
id: windowTitle
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
horizontalAlignment: Text.AlignHCenter
font.family: Theme.fontSerif
font.italic: true
font.pixelSize: Theme.fontSizeLarge
color: Theme.textSecondary
elide: Text.ElideRight
maximumLineCount: 1
text: GlobalState.activeWindowTitle
Behavior on color {
ColorAnimation { duration: 150 }
}
}
// Right Section: live system stats (click to open the System HUD)
Text {
id: stats
Layout.alignment: Qt.AlignVCenter
textFormat: Text.RichText
font.family: "JetBrainsMono Nerd Font"
font.pixelSize: 11
text: buildStats()
MouseArea {
id: statsMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: GlobalState.toggleSystemHud()
}
}
// Clock
Text {
id: clock
Layout.alignment: Qt.AlignVCenter
textFormat: Text.RichText
font.family: "JetBrainsMono Nerd Font"
font.pixelSize: 12
text: "<font color=\"#adaaaa\">\uF017</font>"
+ "<font color=\"#ffffff\"> " + GlobalState.timeString + "</font>"
}
}
}

View File

@@ -1,148 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import "../theme"
import ".."
Rectangle {
id: root
width: 240
height: 110
radius: Theme.radiusMedium
color: Theme.menuDropdownBg
border.color: Theme.menuDropdownBorder
border.width: 1
ColumnLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 8
RowLayout {
Layout.fillWidth: true
Text {
text: "Sound"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeNormal
font.weight: Font.DemiBold
color: Theme.textPrimary
Layout.fillWidth: true
}
Text {
text: `${GlobalState.volume}%`
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textSecondary
}
}
// Custom Catalina Volume Slider
Item {
Layout.fillWidth: true
Layout.preferredHeight: 24
Rectangle {
id: track
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: 6
radius: 3
color: Qt.rgba(1.0, 1.0, 1.0, 0.2)
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: track.width * (GlobalState.volume / 100.0)
radius: 3
color: Theme.accentBlue
}
}
Rectangle {
id: thumb
x: Math.max(0, Math.min(track.width - width, (track.width * (GlobalState.volume / 100.0)) - width / 2))
anchors.verticalCenter: parent.verticalCenter
width: 16
height: 16
radius: 8
color: "#FFFFFF"
border.color: Qt.rgba(0, 0, 0, 0.2)
border.width: 1
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
preventStealing: true
function updateFromMouse(mouse) {
const frac = Math.max(0.0, Math.min(1.0, mouse.x / width));
GlobalState.setVolume(Math.round(frac * 100));
}
onPressed: (mouse) => updateFromMouse(mouse)
onPositionChanged: (mouse) => {
if (pressed) updateFromMouse(mouse);
}
}
}
// Bottom Controls
RowLayout {
Layout.fillWidth: true
spacing: 8
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 24
radius: Theme.radiusSmall
color: muteMouse.containsMouse ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : Qt.rgba(1.0, 1.0, 1.0, 0.08)
Text {
anchors.centerIn: parent
text: GlobalState.volumeMuted ? "Unmute" : "Mute"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textPrimary
}
MouseArea {
id: muteMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: GlobalState.toggleVolumeMute()
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 24
radius: Theme.radiusSmall
color: prefMouse.containsMouse ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : Qt.rgba(1.0, 1.0, 1.0, 0.08)
Text {
anchors.centerIn: parent
text: "Sound Preferences..."
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textPrimary
}
MouseArea {
id: prefMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
GlobalState.volumePopupVisible = false;
GlobalState.launchApp("pavucontrol");
}
}
}
}
}
}

View File

@@ -1,102 +0,0 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import "../theme"
import ".."
Rectangle {
id: root
width: 240
implicitHeight: wifiCol.implicitHeight + 24
radius: Theme.radiusMedium
color: Theme.menuDropdownBg
border.color: Theme.menuDropdownBorder
border.width: 1
ColumnLayout {
id: wifiCol
anchors.fill: parent
anchors.margins: 12
spacing: 8
RowLayout {
Layout.fillWidth: true
Text {
text: "Wi-Fi"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeNormal
font.weight: Font.DemiBold
color: Theme.textPrimary
Layout.fillWidth: true
}
Text {
text: GlobalState.wifiConnected ? "On" : "Off"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: GlobalState.wifiConnected ? Theme.accentGreen : Theme.textTertiary
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Qt.rgba(1.0, 1.0, 1.0, 0.12)
}
// Active network info
ColumnLayout {
Layout.fillWidth: true
spacing: 3
Text {
text: GlobalState.wifiConnected ? `Connected to: ${GlobalState.wifiSsid}` : "No Network Connected"
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.textPrimary
elide: Text.ElideRight
Layout.fillWidth: true
}
Text {
visible: GlobalState.ipAddress !== "" && GlobalState.ipAddress !== "Offline"
text: `IP: ${GlobalState.ipAddress}`
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textSecondary
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Qt.rgba(1.0, 1.0, 1.0, 0.12)
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 24
radius: Theme.radiusSmall
color: netMouse.containsMouse ? Qt.rgba(1.0, 1.0, 1.0, 0.15) : Qt.rgba(1.0, 1.0, 1.0, 0.08)
Text {
anchors.centerIn: parent
text: "Network Preferences..."
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
color: Theme.textPrimary
}
MouseArea {
id: netMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
GlobalState.wifiPopupVisible = false;
GlobalState.launchApp("nm-connection-editor || foot -e nmtui");
}
}
}
}
}

View File

@@ -0,0 +1,96 @@
{
"appearance": {
"colorSource": "default",
"caelestia": {
"autoRefresh": true,
"refreshInterval": 2000,
"accentProfile": "vibrant"
},
"rounding": {
"unsharpen": 2,
"verysmall": 8,
"small": 12,
"normal": 17,
"large": 23,
"full": 9999,
"screenRounding": 23,
"windowRounding": 18
},
"font": {
"family": {
"main": "sans-serif",
"title": "sans-serif",
"expressive": "sans-serif"
},
"pixelSize": {
"smaller": 12,
"small": 15,
"normal": 16,
"larger": 19,
"huge": 22
}
},
"animation": {
"duration": {
"elementMove": 500,
"elementMoveEnter": 400,
"elementMoveFast": 200
}
},
"sizes": {
"elevationMargin": 10
}
},
"overview": {
"rows": 2,
"columns": 5,
"scale": 0.16,
"enable": true,
"hideEmptyRows": true,
"closeOnFocusLoss": true,
"useWorkspaceMap": false,
"workspaceMap": [0, 10],
"orderRightLeft": false,
"orderBottomUp": false,
"previewsEnabled": true,
"previewMode": "live",
"includeInactiveMonitorPreviews": true,
"previewRecaptureDelayMs": 60,
"showSpecialWorkspaces": true,
"specialWorkspaces": [],
"specialWorkspaceColumns": 5,
"emptyWorkspaceWallpaper": "",
"specialEmptyWorkspaceWallpaper": "",
"effects": {
"enableBackdrop": false,
"backdropOpacity": 0.28,
"panelOpacity": 0.92,
"workspaceOpacity": 0.86,
"emptyWorkspaceWallpaperOverlayOpacity": 0.18,
"windowOverlayOpacity": 0.22,
"enableBlur": false,
"glassMode": false,
"glassTintStrength": 0.35,
"glassBorderOpacity": 0.72,
"glassShineOpacity": 0.14
},
"workspaceSpacing": 5,
"backgroundPadding": 10,
"workspaceNumberBaseSize": 250
},
"position": {
"topMargin": 100
},
"windowPreview": {
"showIcons": true,
"iconToWindowRatio": 0.25,
"iconToWindowRatioCompact": 0.45,
"xwaylandIndicatorToIconRatio": 0.35,
"inactiveMonitorOpacity": 0.4,
"cropToFill": false
},
"hacks": {
"arbitraryRaceConditionDelay": 150,
"hyprlandEventDebounceMs": 40
}
}

View File

@@ -0,0 +1,42 @@
{
"appearance": {
"colorSource": "default",
"rounding": {
"unsharpen": 0,
"verysmall": 0,
"small": 0,
"normal": 0,
"large": 0,
"full": 9999,
"screenRounding": 0,
"windowRounding": 0
},
"font": {
"family": {
"main": "IBM Plex Sans",
"title": "Instrument Serif",
"expressive": "IBM Plex Sans"
},
"pixelSize": {
"smaller": 11,
"small": 13,
"normal": 15,
"larger": 19,
"huge": 24
}
},
"animation": {
"duration": {
"elementMove": 400,
"elementMoveEnter": 400,
"elementMoveFast": 200
}
}
},
"overview": {
"effects": {
"enableBackdrop": true,
"backdropOpacity": 0.35
}
}
}

View File

@@ -0,0 +1,252 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Hyprland
import "../../common"
import "../../services"
import "."
Scope {
id: overviewScope
Variants {
id: overviewVariants
model: Quickshell.screens
PanelWindow {
id: root
required property var modelData
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen)
property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id)
property bool blurEnabled: Config.options.overview.effects.enableBlur
property bool backdropEnabled: Config.options.overview.effects.enableBackdrop
property real backdropOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.backdropOpacity))
property bool closeOnFocusLoss: Config.options.overview.closeOnFocusLoss ?? true
screen: modelData
visible: GlobalStates.overviewOpen
WlrLayershell.namespace: blurEnabled ? "quickshell:overview-blur" : "quickshell:overview"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
color: "transparent"
anchors {
top: true
bottom: true
left: true
right: true
}
HyprlandFocusGrab {
id: grab
windows: [root]
property bool canBeActive: root.monitorIsFocused
active: false
onCleared: () => {
// Only the monitor that owns the grab may close the overview
if (root.closeOnFocusLoss && !active && canBeActive)
GlobalStates.overviewOpen = false;
}
}
Connections {
target: GlobalStates
function onOverviewOpenChanged() {
if (GlobalStates.overviewOpen) {
delayedGrabTimer.start();
}
}
}
// Re-evaluate grab ownership when focused monitor changes
Connections {
target: Hyprland
function onFocusedMonitorChanged() {
if (!GlobalStates.overviewOpen)
return;
// Transfer the grab to the newly focused monitor
if (root.monitorIsFocused && !grab.active) {
grab.active = true;
} else if (!root.monitorIsFocused && grab.active) {
grab.active = false;
}
}
}
Timer {
id: delayedGrabTimer
interval: Config.options.hacks.arbitraryRaceConditionDelay
repeat: false
onTriggered: {
if (!grab.canBeActive)
return;
grab.active = GlobalStates.overviewOpen;
}
}
// Keep the layershell surface full-screen so backdrop/blur are not constrained by content size.
implicitWidth: screen.width
implicitHeight: screen.height
Item {
id: keyHandler
anchors.fill: parent
visible: GlobalStates.overviewOpen
focus: GlobalStates.overviewOpen
z: 0
Rectangle {
id: backdropLayer
anchors.fill: parent
visible: root.backdropEnabled
color: "#000000"
opacity: root.backdropOpacity
z: 0
}
MouseArea {
id: outsideClickCatcher
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
enabled: root.closeOnFocusLoss && GlobalStates.overviewOpen
z: 0
onPressed: mouse => {
GlobalStates.overviewOpen = false;
mouse.accepted = true;
}
}
Keys.onPressed: event => {
// close: Escape or Enter
if (event.key === Qt.Key_Escape || event.key === Qt.Key_Return) {
GlobalStates.overviewOpen = false;
event.accepted = true;
return;
}
// Helper: compute current group bounds
const workspacesPerGroup = Config.options.overview.rows * Config.options.overview.columns;
const currentId = Hyprland.focusedMonitor?.activeWorkspace?.id ?? 1;
const useWorkspaceMap = Config.options.overview.useWorkspaceMap;
const workspaceMap = Config.options.overview.workspaceMap ?? [];
const focusedMonitorId = Hyprland.focusedMonitor?.id ?? root.monitor?.id ?? 0;
const workspaceOffset = useWorkspaceMap ? Number(workspaceMap[focusedMonitorId] ?? 0) : 0;
const currentGroup = Math.floor((currentId - workspaceOffset - 1) / workspacesPerGroup);
const minWorkspaceId = currentGroup * workspacesPerGroup + 1 + workspaceOffset;
const maxWorkspaceId = minWorkspaceId + workspacesPerGroup - 1;
const rows = Config.options.overview.rows;
const columns = Config.options.overview.columns;
const reverseColumns = Config.options.overview.orderRightLeft;
const reverseRows = Config.options.overview.orderBottomUp;
const clampedIndex = Math.max(0, Math.min(workspacesPerGroup - 1, currentId - minWorkspaceId));
const currentNormalRow = Math.floor(clampedIndex / columns);
const currentNormalColumn = clampedIndex % columns;
function toVisualRow(normalRow) {
return reverseRows ? (rows - normalRow - 1) : normalRow;
}
function toVisualColumn(normalColumn) {
return reverseColumns ? (columns - normalColumn - 1) : normalColumn;
}
function toNormalRow(visualRow) {
return reverseRows ? (rows - visualRow - 1) : visualRow;
}
function toNormalColumn(visualColumn) {
return reverseColumns ? (columns - visualColumn - 1) : visualColumn;
}
let targetVisualRow = toVisualRow(currentNormalRow);
let targetVisualColumn = toVisualColumn(currentNormalColumn);
let targetId = null;
// Arrow keys and vim-style hjkl
if (event.key === Qt.Key_Left || event.key === Qt.Key_H) {
targetVisualColumn = (targetVisualColumn - 1 + columns) % columns;
} else if (event.key === Qt.Key_Right || event.key === Qt.Key_L) {
targetVisualColumn = (targetVisualColumn + 1) % columns;
} else if (event.key === Qt.Key_Up || event.key === Qt.Key_K) {
targetVisualRow = (targetVisualRow - 1 + rows) % rows;
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_J) {
targetVisualRow = (targetVisualRow + 1) % rows;
}
// Number keys: jump to workspace within the current group
// 1-9 map to positions 1-9, 0 maps to position 10
else if (event.key >= Qt.Key_1 && event.key <= Qt.Key_9) {
const position = event.key - Qt.Key_0; // 1-9
if (position <= workspacesPerGroup) {
targetId = minWorkspaceId + position - 1;
}
} else if (event.key === Qt.Key_0) {
// 0 = 10th workspace in the group (if group has 10+ workspaces)
if (workspacesPerGroup >= 10) {
targetId = minWorkspaceId + 9; // 10th position = offset 9
}
}
if (targetId === null && (
event.key === Qt.Key_Left || event.key === Qt.Key_H ||
event.key === Qt.Key_Right || event.key === Qt.Key_L ||
event.key === Qt.Key_Up || event.key === Qt.Key_K ||
event.key === Qt.Key_Down || event.key === Qt.Key_J
)) {
const targetNormalRow = toNormalRow(targetVisualRow);
const targetNormalColumn = toNormalColumn(targetVisualColumn);
targetId = minWorkspaceId + targetNormalRow * columns + targetNormalColumn;
}
if (targetId !== null) {
const clampedTarget = Math.max(minWorkspaceId, Math.min(maxWorkspaceId, targetId));
if (Hyprland.usingLua) {
Hyprland.dispatch(`hl.dsp.focus({workspace = '${clampedTarget}'})`);
} else {
Hyprland.dispatch("workspace " + clampedTarget);
}
event.accepted = true;
}
}
}
ColumnLayout {
id: columnLayout
visible: GlobalStates.overviewOpen
z: 1
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
topMargin: Config.options.position.topMargin
}
Loader {
id: overviewLoader
active: Config?.options.overview.enable ?? true
sourceComponent: OverviewWidget {
panelWindow: root
visible: true
}
}
}
}
}
IpcHandler {
target: "overview"
function toggle() {
GlobalStates.overviewOpen = !GlobalStates.overviewOpen;
}
function close() {
GlobalStates.overviewOpen = false;
}
function open() {
GlobalStates.overviewOpen = true;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,264 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import "../../common"
import "../../common/functions"
import "../../services"
Item { // Window
id: root
property var toplevel
property var windowData
property var monitorData
property var widgetMonitorData
property var scale
property var availableWorkspaceWidth
property var availableWorkspaceHeight
property real positionBaseX: (monitorData?.x ?? 0) + (monitorData?.reserved?.[0] ?? 0)
property real positionBaseY: (monitorData?.y ?? 0) + (monitorData?.reserved?.[1] ?? 0)
property int recaptureToken: 0
property bool restrictToWorkspace: true
property real widthRatio: {
if (!widgetMonitorData || !monitorData)
return 1;
const widgetWidth = (widgetMonitorData.transform % 2 === 1) ? (widgetMonitorData.height ?? 1) : (widgetMonitorData.width ?? 1);
const sourceWidth = (monitorData.transform % 2 === 1) ? (monitorData.height ?? 1) : (monitorData.width ?? 1);
const sourceScale = monitorData.scale ?? 1;
const widgetScale = widgetMonitorData.scale ?? 1;
return (widgetWidth * sourceScale) / (sourceWidth * widgetScale);
}
property real heightRatio: {
if (!widgetMonitorData || !monitorData)
return 1;
const widgetHeight = (widgetMonitorData.transform % 2 === 1) ? (widgetMonitorData.width ?? 1) : (widgetMonitorData.height ?? 1);
const sourceHeight = (monitorData.transform % 2 === 1) ? (monitorData.width ?? 1) : (monitorData.height ?? 1);
const sourceScale = monitorData.scale ?? 1;
const widgetScale = widgetMonitorData.scale ?? 1;
return (widgetHeight * sourceScale) / (sourceHeight * widgetScale);
}
property real initX: Math.max(((windowData?.at[0] ?? 0) - positionBaseX) * root.scale * geometryScaleX, 0) + xOffset
property real initY: Math.max(((windowData?.at[1] ?? 0) - positionBaseY) * root.scale * geometryScaleY, 0) + yOffset
property real xOffset: 0
property real yOffset: 0
property int widgetMonitorId: 0
property real geometryScaleX: widthRatio
property real geometryScaleY: heightRatio
property var targetWindowWidth: (windowData?.size[0] ?? 100) * scale * geometryScaleX
property var targetWindowHeight: (windowData?.size[1] ?? 100) * scale * geometryScaleY
property bool hovered: false
property bool pressed: false
property bool showIcons: Config.options.windowPreview.showIcons
property var iconToWindowRatio: Config.options.windowPreview.iconToWindowRatio
property var xwaylandIndicatorToIconRatio: Config.options.windowPreview.xwaylandIndicatorToIconRatio
property var iconToWindowRatioCompact: Config.options.windowPreview.iconToWindowRatioCompact
property bool cropToFill: Config.options.windowPreview.cropToFill
property bool previewsEnabled: Config.options.overview.previewsEnabled
property bool includeInactiveMonitorPreviews: Config.options.overview.includeInactiveMonitorPreviews
property int previewRecaptureDelayMs: Config.options.overview.previewRecaptureDelayMs
property real windowOverlayOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.windowOverlayOpacity))
property bool glassMode: Config.options.overview.effects.glassMode
property real glassShineOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.glassShineOpacity))
property real effectiveWindowOverlayOpacity: glassMode ? Math.min(windowOverlayOpacity, 0.10) : windowOverlayOpacity
property string previewModeRaw: Config.options.overview.previewMode
property string previewMode: {
const mode = `${previewModeRaw ?? "live"}`.trim().toLowerCase();
return (mode === "event" || mode === "snapshot") ? "event" : "live";
}
property bool livePreviewEnabled: previewsEnabled && previewMode === "live"
property bool shouldCapturePreview: {
if (!GlobalStates.overviewOpen || !previewsEnabled || !previewCaptureEnabled)
return false;
if (includeInactiveMonitorPreviews)
return true;
return (windowData?.monitor ?? -1) === widgetMonitorId;
}
property var entry: {
DesktopEntries.applications.values; // re-run when the entry index updates
return DesktopEntries.heuristicLookup(windowData?.class);
}
property string iconName: {
const raw = `${entry?.icon ?? ""}`.trim();
const withoutProviderPrefix = raw.replace(/^image:\/\/icon\//, "");
const withoutQuery = withoutProviderPrefix.split("?")[0].trim();
return withoutQuery.length > 0 ? withoutQuery : "application-x-executable";
}
property var iconPath: Quickshell.iconPath(iconName, "image-missing")
property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth
property bool indicateXWayland: windowData?.xwayland ?? false
property bool previewCaptureEnabled: true
property bool initialized: false
property bool dragInProgress: false
property bool suspendPositionAnimation: false
property bool animateSize: true
x: initX
y: initY
width: Math.min(targetWindowWidth, availableWorkspaceWidth)
height: Math.min(targetWindowHeight, availableWorkspaceHeight)
opacity: (windowData?.monitor ?? -1) == widgetMonitorId ? 1 : Config.options.windowPreview.inactiveMonitorOpacity
visible: {
const thisWsId = windowData?.workspace?.id;
const isFullscreen = (windowData?.fullscreen ?? 0) > 0;
if (isFullscreen || thisWsId === undefined) return true;
return !HyprlandData.windowList.some(w => w.workspace?.id === thisWsId && (w.fullscreen ?? 0) > 0);
}
clip: true
Component.onCompleted: Qt.callLater(() => root.initialized = true)
Behavior on x {
enabled: root.initialized && !root.dragInProgress && !root.suspendPositionAnimation
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
Behavior on y {
enabled: root.initialized && !root.dragInProgress && !root.suspendPositionAnimation
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
Behavior on width {
enabled: root.initialized && root.animateSize && !root.dragInProgress && !root.suspendPositionAnimation
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
Behavior on height {
enabled: root.initialized && root.animateSize && !root.dragInProgress && !root.suspendPositionAnimation
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
// Opaque background for windows on the active monitor.
// The simplest solution for making those windows fully opaque and not interacting with actual
// windows behind the overview, e.g., applying blur to them.
Rectangle {
visible: (root.windowData?.monitor ?? -1) === root.widgetMonitorId
anchors.fill: parent
radius: Appearance.rounding.windowRounding * root.scale
color: root.glassMode
? ColorUtils.mix(Appearance.colors.colLayer2, Appearance.colors.colLayer0, 0.38)
: Appearance.colors.colLayer2
}
ScreencopyView {
id: windowPreview
readonly property real srcAspect: {
const w = root.windowData?.size?.[0] ?? 0;
const h = root.windowData?.size?.[1] ?? 0;
return (w > 0 && h > 0) ? (w / h) : 1;
}
anchors.centerIn: parent
width: root.cropToFill
? Math.max(parent.width, parent.height * srcAspect)
: Math.min(parent.width, parent.height * srcAspect)
height: root.cropToFill
? Math.max(parent.height, parent.width / srcAspect)
: Math.min(parent.height, parent.width / srcAspect)
captureSource: shouldCapturePreview ? root.toplevel : null
live: livePreviewEnabled
layer.enabled: true
layer.smooth: true
layer.effect: MultiEffect {
maskEnabled: true
maskSource: previewMask
maskThresholdMin: 0.5
maskSpreadAtMin: 1.0
}
}
Rectangle {
anchors.fill: parent
radius: Appearance.rounding.windowRounding * root.scale
color: pressed ? ColorUtils.applyAlpha(Appearance.colors.colLayer2Active, Math.min(1, root.effectiveWindowOverlayOpacity + 0.30)) :
hovered ? ColorUtils.applyAlpha(Appearance.colors.colLayer2Hover, Math.min(1, root.effectiveWindowOverlayOpacity + 0.20)) :
ColorUtils.applyAlpha(
root.glassMode ? ColorUtils.mix(Appearance.colors.colLayer2, Appearance.colors.colLayer0, 0.38) : Appearance.colors.colLayer2,
root.effectiveWindowOverlayOpacity
)
border.color: root.glassMode
? ColorUtils.applyAlpha(Appearance.m3colors.m3outline, 0.62)
: ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.7)
border.width: 1
Rectangle {
visible: root.glassMode
anchors.fill: parent
radius: parent.radius
color: "transparent"
gradient: Gradient {
GradientStop { position: 0.0; color: ColorUtils.applyAlpha("#FFFFFF", root.glassShineOpacity * 0.24) }
GradientStop { position: 0.5; color: ColorUtils.applyAlpha("#FFFFFF", 0.0) }
GradientStop { position: 1.0; color: ColorUtils.applyAlpha("#000000", root.glassShineOpacity * 0.14) }
}
}
Rectangle {
visible: root.glassMode
anchors.fill: parent
anchors.margins: 1
radius: Math.max(parent.radius - 1, 0)
color: "transparent"
border.width: 1
border.color: ColorUtils.applyAlpha("#FFFFFF", root.glassShineOpacity * 0.32)
}
ColumnLayout {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
spacing: Appearance.font.pixelSize.smaller * 0.5
Image {
id: windowIcon
visible: root.showIcons
property var iconSize: {
const renderedSize = Math.min(root.width, root.height);
return renderedSize * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / (root.monitorData?.scale ?? 1);
}
Layout.alignment: Qt.AlignHCenter
source: root.iconPath
width: iconSize
height: iconSize
sourceSize: Qt.size(Math.max(1, Math.round(iconSize)), Math.max(1, Math.round(iconSize)))
}
}
}
Item {
id: previewMask
width: windowPreview.width
height: windowPreview.height
anchors.centerIn: parent
visible: false
layer.enabled: true
layer.smooth: true
Rectangle {
anchors.centerIn: parent
width: root.width
height: root.height
radius: Appearance.rounding.windowRounding * root.scale
}
}
function refreshCapture() {
if (!GlobalStates.overviewOpen || livePreviewEnabled || !previewsEnabled)
return;
root.previewCaptureEnabled = false;
previewResetTimer.restart();
}
Timer {
id: previewResetTimer
interval: Math.max(1, previewRecaptureDelayMs)
repeat: false
onTriggered: root.previewCaptureEnabled = true
}
onRecaptureTokenChanged: {
if (recaptureToken > 0)
root.refreshCapture();
}
}

View File

@@ -0,0 +1,3 @@
Overview 1.0 Overview.qml
OverviewWidget 1.0 OverviewWidget.qml
OverviewWindow 1.0 OverviewWindow.qml

View File

@@ -1 +0,0 @@
singleton GlobalState 1.0 GlobalState.qml

View File

@@ -0,0 +1,41 @@
import QtQuick
QtObject {
id: m3
property color m3primary: "{{colors.primary.default.hex}}"
property color m3onPrimary: "{{colors.on_primary.default.hex}}"
property color m3primaryContainer: "{{colors.primary_container.default.hex}}"
property color m3onPrimaryContainer: "{{colors.on_primary_container.default.hex}}"
property color m3secondary: "{{colors.secondary.default.hex}}"
property color m3onSecondary: "{{colors.on_secondary.default.hex}}"
property color m3secondaryContainer: "{{colors.secondary_container.default.hex}}"
property color m3onSecondaryContainer: "{{colors.on_secondary_container.default.hex}}"
property color m3background: "{{colors.background.default.hex}}"
property color m3onBackground: "{{colors.on_background.default.hex}}"
property color m3surface: "{{colors.surface.default.hex}}"
property color m3surfaceContainerLow: "{{colors.surface_container_low.default.hex}}"
property color m3surfaceContainer: "{{colors.surface_container.default.hex}}"
property color m3surfaceContainerHigh: "{{colors.surface_container_high.default.hex}}"
property color m3surfaceContainerHighest: "{{colors.surface_container_highest.default.hex}}"
property color m3onSurface: "{{colors.on_surface.default.hex}}"
property color m3surfaceVariant: "{{colors.surface_variant.default.hex}}"
property color m3onSurfaceVariant: "{{colors.on_surface_variant.default.hex}}"
property color m3inverseSurface: "{{colors.inverse_surface.default.hex}}"
property color m3inverseOnSurface: "{{colors.inverse_on_surface.default.hex}}"
property color m3outline: "{{colors.outline.default.hex}}"
property color m3outlineVariant: "{{colors.outline_variant.default.hex}}"
property color m3shadow: "#000000"
}

View File

@@ -1,66 +0,0 @@
#!/usr/bin/env bash
# Scans .desktop files and outputs JSON array of apps
dirs=(
"/run/current-system/sw/share/applications"
"$HOME/.nix-profile/share/applications"
"$HOME/.local/share/applications"
"/usr/share/applications"
)
declare -A seen
apps="["
first=true
# Add default curated Mac/system apps first
curated=(
'{"name":"Finder","exec":"foot -e sh -c \"cd ~; lf || yazi || ls -la; exec zsh\"","icon":"finder","comment":"File Manager"}'
'{"name":"Terminal","exec":"foot","icon":"terminal","comment":"Command Line Interface"}'
'{"name":"Brave Browser","exec":"brave","icon":"browser","comment":"Web Browser"}'
'{"name":"Neovim","exec":"foot -e nvim","icon":"code","comment":"Text & Code Editor"}'
'{"name":"System Preferences","exec":"pavucontrol","icon":"preferences","comment":"Audio & System Settings"}'
'{"name":"Activity Monitor","exec":"foot -e btop","icon":"preferences","comment":"Resource Monitor"}'
'{"name":"Lock Screen","exec":"hyprlock","icon":"preferences","comment":"Lock current session"}'
'{"name":"Restart System","exec":"systemctl reboot","icon":"preferences","comment":"Reboot NixOS"}'
'{"name":"Shut Down","exec":"systemctl poweroff","icon":"preferences","comment":"Power off computer"}'
)
for app in "${curated[@]}"; do
if [ "$first" = true ]; then
apps="$apps$app"
first=false
else
apps="$apps,$app"
fi
done
for dir in "${dirs[@]}"; do
if [ -d "$dir" ]; then
for f in "$dir"/*.desktop; do
[ -e "$f" ] || continue
# Check NoDisplay
if grep -q "^NoDisplay=true" "$f" 2>/dev/null; then
continue
fi
name=$(grep -m 1 "^Name=" "$f" 2>/dev/null | cut -d= -f2- | tr -d '\r\n')
exec_cmd=$(grep -m 1 "^Exec=" "$f" 2>/dev/null | cut -d= -f2- | sed -E 's/%[fFuUdDnNickvm]//g' | tr -d '\r\n')
icon=$(grep -m 1 "^Icon=" "$f" 2>/dev/null | cut -d= -f2- | tr -d '\r\n')
comment=$(grep -m 1 "^Comment=" "$f" 2>/dev/null | cut -d= -f2- | tr -d '\r\n')
if [ -n "$name" ] && [ -n "$exec_cmd" ] && [ -z "${seen[$name]}" ]; then
seen[$name]=1
# Escape quotes
name_esc=$(echo "$name" | sed 's/"/\\"/g')
exec_esc=$(echo "$exec_cmd" | sed 's/"/\\"/g')
icon_esc=$(echo "$icon" | sed 's/"/\\"/g')
comment_esc=$(echo "$comment" | sed 's/"/\\"/g')
entry="{\"name\":\"$name_esc\",\"exec\":\"$exec_esc\",\"icon\":\"$icon_esc\",\"comment\":\"$comment_esc\"}"
apps="$apps,$entry"
fi
done
fi
done
apps="$apps]"
echo "$apps"

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env bash
# Read audio volume & mute state
volume=50
muted=false
if command -v pactl &>/dev/null; then
vol_str=$(pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null | grep -Po '[0-9]+(?=%)' | head -n 1)
if [ -n "$vol_str" ]; then
volume=$vol_str
fi
mute_str=$(pactl get-sink-mute @DEFAULT_SINK@ 2>/dev/null)
if echo "$mute_str" | grep -q "yes"; then
muted=true
fi
elif command -v pamixer &>/dev/null; then
volume=$(pamixer --get-volume 2>/dev/null || echo 50)
muted=$(pamixer --get-mute 2>/dev/null || echo false)
fi
echo "{\"volume\": $volume, \"muted\": $muted}"

View File

@@ -1,16 +0,0 @@
#!/usr/bin/env bash
# Read battery state
bat_path=$(find /sys/class/power_supply/ -name "BAT*" 2>/dev/null | head -n 1)
if [ -n "$bat_path" ] && [ -d "$bat_path" ]; then
capacity=$(cat "$bat_path/capacity" 2>/dev/null || echo 100)
status=$(cat "$bat_path/status" 2>/dev/null || echo "Full")
is_charging=false
if [ "$status" = "Charging" ] || [ "$status" = "Full" ]; then
is_charging=true
fi
echo "{\"present\": true, \"percent\": $capacity, \"status\": \"$status\", \"charging\": $is_charging}"
else
# Desktop without battery
echo "{\"present\": false, \"percent\": 100, \"status\": \"AC\", \"charging\": true}"
fi

View File

@@ -1,12 +0,0 @@
#!/usr/bin/env bash
# Read brightness
percent=100
if command -v brightnessctl &>/dev/null; then
percent=$(brightnessctl -m 2>/dev/null | cut -d, -f4 | tr -d '%' | head -n 1)
if [ -z "$percent" ]; then
percent=100
fi
fi
echo "{\"brightness\": ${percent:-100}}"

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env bash
# Read /proc/stat CPU utilization
read -r cpu u n s i io ir st g gn < /proc/stat
total1=$((u + n + s + i + io + ir + st + g + gn))
idle1=$((i + io))
sleep 0.2
read -r cpu u n s i io ir st g gn < /proc/stat
total2=$((u + n + s + i + io + ir + st + g + gn))
idle2=$((i + io))
total_diff=$((total2 - total1))
idle_diff=$((idle2 - idle1))
if [ "$total_diff" -gt 0 ]; then
cpu_usage=$(( (total_diff - idle_diff) * 100 / total_diff ))
else
cpu_usage=0
fi
echo "$cpu_usage"

View File

@@ -1,14 +0,0 @@
#!/usr/bin/env bash
# Read memory usage from /proc/meminfo
mem_total=$(grep MemTotal /proc/meminfo | awk '{print $2}')
mem_avail=$(grep MemAvailable /proc/meminfo | awk '{print $2}')
if [ -n "$mem_total" ] && [ -n "$mem_avail" ] && [ "$mem_total" -gt 0 ]; then
mem_used=$((mem_total - mem_avail))
mem_percent=$((mem_used * 100 / mem_total))
used_gb=$(awk "BEGIN {printf \"%.1f\", $mem_used/1048576}")
total_gb=$(awk "BEGIN {printf \"%.1f\", $mem_total/1048576}")
echo "{\"percent\": $mem_percent, \"used_gb\": \"$used_gb\", \"total_gb\": \"$total_gb\"}"
else
echo "{\"percent\": 0, \"used_gb\": \"0\", \"total_gb\": \"0\"}"
fi

View File

@@ -1,30 +0,0 @@
#!/usr/bin/env bash
# Read WiFi status
ssid=""
ip=""
if command -v nmcli &>/dev/null; then
active_line=$(nmcli -t -f active,ssid,signal dev wifi 2>/dev/null | grep '^yes' | head -n 1)
if [ -n "$active_line" ]; then
ssid=$(echo "$active_line" | cut -d':' -f2)
signal=$(echo "$active_line" | cut -d':' -f3)
fi
fi
if [ -z "$ssid" ]; then
# Fallback to iwgetid or /proc/net/wireless
if command -v iwgetid &>/dev/null; then
ssid=$(iwgetid -r 2>/dev/null)
fi
fi
# Get default IPv4
ip=$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{print $7; exit}')
if [ -n "$ssid" ]; then
echo "{\"connected\": true, \"ssid\": \"$ssid\", \"ip\": \"${ip:-N/A}\"}"
elif [ -n "$ip" ]; then
echo "{\"connected\": true, \"ssid\": \"Ethernet\", \"ip\": \"$ip\"}"
else
echo "{\"connected\": false, \"ssid\": \"Not Connected\", \"ip\": \"None\"}"
fi

View File

@@ -0,0 +1,11 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Singleton {
id: root
property bool overviewOpen: false
property bool superReleaseMightTrigger: true
}

View File

@@ -0,0 +1,209 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import "../common"
/**
* Provides access to some Hyprland data not available in Quickshell.Hyprland.
*/
Singleton {
id: root
property var windowList: []
property var addresses: []
property var windowByAddress: ({})
property var workspaces: []
property var allWorkspaces: []
property var workspaceIds: []
property var workspaceById: ({})
property var activeWorkspace: null
property var monitors: []
property var layers: ({})
property bool pendingWindowsUpdate: false
property bool pendingMonitorsUpdate: false
property bool pendingLayersUpdate: false
property bool pendingWorkspacesUpdate: false
property bool pendingActiveWorkspaceUpdate: false
function updateWindowList() {
getClients.running = true;
}
function updateLayers() {
getLayers.running = true;
}
function updateMonitors() {
getMonitors.running = true;
}
function updateWorkspaces() {
getWorkspaces.running = true;
getActiveWorkspace.running = true;
}
function updateAll() {
scheduleUpdates(true, true, true, true, true);
}
function scheduleUpdates(windows, monitors, layers, workspaces, activeWorkspace) {
pendingWindowsUpdate = pendingWindowsUpdate || !!windows;
pendingMonitorsUpdate = pendingMonitorsUpdate || !!monitors;
pendingLayersUpdate = pendingLayersUpdate || !!layers;
pendingWorkspacesUpdate = pendingWorkspacesUpdate || !!workspaces;
pendingActiveWorkspaceUpdate = pendingActiveWorkspaceUpdate || !!activeWorkspace;
const debounceMs = Math.max(0, Config.options.hacks.hyprlandEventDebounceMs);
if (debounceMs === 0) {
flushPendingUpdates();
} else {
eventDebounceTimer.interval = debounceMs;
eventDebounceTimer.restart();
}
}
function flushPendingUpdates() {
if (pendingWindowsUpdate) {
pendingWindowsUpdate = false;
updateWindowList();
}
if (pendingMonitorsUpdate) {
pendingMonitorsUpdate = false;
updateMonitors();
}
if (pendingLayersUpdate) {
pendingLayersUpdate = false;
updateLayers();
}
if (pendingWorkspacesUpdate) {
pendingWorkspacesUpdate = false;
getWorkspaces.running = true;
}
if (pendingActiveWorkspaceUpdate) {
pendingActiveWorkspaceUpdate = false;
getActiveWorkspace.running = true;
}
}
function biggestWindowForWorkspace(workspaceId) {
const windowsInThisWorkspace = HyprlandData.windowList.filter(w => w.workspace.id == workspaceId);
return windowsInThisWorkspace.reduce((maxWin, win) => {
const maxArea = (maxWin?.size?.[0] ?? 0) * (maxWin?.size?.[1] ?? 0);
const winArea = (win?.size?.[0] ?? 0) * (win?.size?.[1] ?? 0);
return winArea > maxArea ? win : maxWin;
}, null);
}
Component.onCompleted: {
scheduleUpdates(true, true, true, true, true);
flushPendingUpdates();
}
Connections {
target: Hyprland
function onRawEvent(event) {
const eventName = `${event?.name ?? event?.event ?? event?.type ?? ""}`;
if (["openlayer", "closelayer", "screencast"].includes(eventName))
return;
if (eventName === "openwindow" || eventName === "closewindow" || eventName === "movewindow" || eventName === "movewindowv2" || eventName === "windowtitle") {
scheduleUpdates(true, false, false, true, false);
return;
}
if (eventName === "workspace" || eventName === "workspacev2" || eventName === "focusedmon" || eventName === "focusedmonv2" || eventName === "activewindow" || eventName === "activewindowv2") {
scheduleUpdates(false, false, false, true, true);
return;
}
if (eventName.startsWith("monitor") || eventName === "configreloaded") {
scheduleUpdates(true, true, false, true, true);
return;
}
scheduleUpdates(true, true, true, true, true);
}
}
Timer {
id: eventDebounceTimer
interval: Math.max(0, Config.options.hacks.hyprlandEventDebounceMs)
repeat: false
onTriggered: root.flushPendingUpdates()
}
Process {
id: getClients
command: ["hyprctl", "clients", "-j"]
stdout: StdioCollector {
id: clientsCollector
onStreamFinished: {
root.windowList = JSON.parse(clientsCollector.text);
let tempWinByAddress = {};
for (var i = 0; i < root.windowList.length; ++i) {
var win = root.windowList[i];
tempWinByAddress[win.address] = win;
}
root.windowByAddress = tempWinByAddress;
root.addresses = root.windowList.map(win => win.address);
}
}
}
Process {
id: getMonitors
command: ["hyprctl", "monitors", "-j"]
stdout: StdioCollector {
id: monitorsCollector
onStreamFinished: {
root.monitors = JSON.parse(monitorsCollector.text);
}
}
}
Process {
id: getLayers
command: ["hyprctl", "layers", "-j"]
stdout: StdioCollector {
id: layersCollector
onStreamFinished: {
root.layers = JSON.parse(layersCollector.text);
}
}
}
Process {
id: getWorkspaces
command: ["hyprctl", "workspaces", "-j"]
stdout: StdioCollector {
id: workspacesCollector
onStreamFinished: {
const rawWorkspaces = JSON.parse(workspacesCollector.text);
root.allWorkspaces = rawWorkspaces;
root.workspaces = rawWorkspaces.filter(ws => ws.id >= 1 && ws.id <= 100);
let tempWorkspaceById = {};
for (var i = 0; i < root.workspaces.length; ++i) {
var ws = root.workspaces[i];
tempWorkspaceById[ws.id] = ws;
}
root.workspaceById = tempWorkspaceById;
root.workspaceIds = root.workspaces.map(ws => ws.id);
}
}
}
Process {
id: getActiveWorkspace
command: ["hyprctl", "activeworkspace", "-j"]
stdout: StdioCollector {
id: activeWorkspaceCollector
onStreamFinished: {
root.activeWorkspace = JSON.parse(activeWorkspaceCollector.text);
}
}
}
}

View File

@@ -0,0 +1,2 @@
singleton HyprlandData 1.0 HyprlandData.qml
singleton GlobalStates 1.0 GlobalStates.qml

View File

@@ -1,84 +1,25 @@
//@ pragma UseQApplication
//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic
//@ pragma Env QT_QPA_PLATFORMTHEME=
import "./modules/overview/"
import "./services/"
import "./common/"
import "./common/functions/"
import "./common/widgets/"
import QtQuick
import Quickshell
import Quickshell.Io
import "theme"
import "components"
import "."
import Quickshell.Hyprland
Scope {
id: root
ShellRoot {
Connections {
target: Quickshell
// =========================================================================
// IPC HANDLERS (Allows controlling shell via `qs ipc call <target> <method>`)
// =========================================================================
IpcHandler {
target: "spotlight"
function toggle() {
GlobalState.toggleSpotlight();
}
function open() {
GlobalState.spotlightVisible = true;
}
function close() {
GlobalState.spotlightVisible = false;
function onReloadCompleted() {
Quickshell.inhibitReloadPopup();
}
}
IpcHandler {
target: "systemHud"
function toggle() {
GlobalState.toggleSystemHud();
}
function open() {
GlobalState.systemHudVisible = true;
}
function close() {
GlobalState.systemHudVisible = false;
}
}
IpcHandler {
target: "systemInfo"
function toggle() {
GlobalState.toggleAboutMac();
}
}
// =========================================================================
// LEFT SIDEBAR (Primary monitor only)
// =========================================================================
SidePanel {
screen: Quickshell.screens[0]
}
// =========================================================================
// TOP STATUS STRIP (All monitors)
// =========================================================================
Variants {
model: Quickshell.screens
TopStrip {}
}
// =========================================================================
// GLOBAL MODAL OVERLAYS
// =========================================================================
// Spotlight Search Overlay
Spotlight {
id: spotlightOverlay
}
// System HUD Flyout Panel
SystemHUD {
id: systemHudPanel
}
// Quick Menu (Power/Session Menu)
QuickMenu {
id: quickMenuOverlay
}
// System Info Dialog
SystemInfo {
id: systemInfoDialog
}
Overview {}
}

View File

@@ -1,46 +0,0 @@
pragma Singleton
import QtQuick
QtObject {
// Colors
readonly property color bgPrimary: "#131313"
readonly property color bgSecondary: "#0e0e0e"
readonly property color glassBg: "#1c1b1b"
readonly property color glassBorder: Qt.rgba(1.0, 1.0, 1.0, 0.08)
readonly property color glassBorderHover: Qt.rgba(1.0, 1.0, 1.0, 0.18)
readonly property color accentVolt: "#CFFC00"
readonly property color accentVoltGlow: Qt.rgba(0.812, 0.988, 0.0, 0.25)
readonly property color accentVoltLight: "#e3ff66"
readonly property color accentBlue: "#00BFFF"
readonly property color accentCoral: "#FF7A59"
readonly property color textPrimary: "#FFFFFF"
readonly property color textSecondary: "#e5e2e1"
readonly property color textMuted: "#ADAAAA"
readonly property color textDisabled: Qt.rgba(1.0, 1.0, 1.0, 0.25)
// Typography
readonly property string fontSerif: "Instrument Serif, Georgia, serif"
readonly property string fontSans: "IBM Plex Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif"
readonly property string fontMono: "Nanum Gothic Coding, JetBrains Mono, monospace"
readonly property int fontSizeSmall: 11
readonly property int fontSizeNormal: 13
readonly property int fontSizeLarge: 15
readonly property int fontSizeTitle: 19
readonly property int fontSizeDisplay: 24
readonly property int fontSizeWorkspace: 12
// Geometry
readonly property int radiusNone: 0
readonly property int sidebarWidth: 52
readonly property int topStripHeight: 28
readonly property int hudWidth: 360
readonly property int iconSize: 36
readonly property int iconSizeSmall: 20
readonly property int workspaceIndicatorWidth: 2
// Animation Durations
readonly property int animSmooth: 400
readonly property int animFast: 200
readonly property int animBounce: 600
}

View File

@@ -1 +0,0 @@
singleton Theme 1.0 Theme.qml

View File

@@ -7,48 +7,58 @@ format = "$os$directory$git_branch$git_status$nix_shell$rust$c$python$cmd_durati
[os]
disabled = false
style = "bold #e5e2e1"
symbols = { NixOS = " " }
symbols = { NixOS = "󱄅 " }
[directory]
truncation_length = 3
truncate_to_repo = true
style = "bold #CFFC00"
format = "[$path]($style) "
format = "[$path]($style) "
[git_branch]
symbol = " "
symbol = " "
style = "bold #00BFFF"
format = "on [$symbol$branch]($style) "
[git_status]
style = "bold #FF7A59"
format = "([$all_status$ahead_behind]($style) )"
conflicted = " "
ahead = " "
behind = " "
diverged = " "
untracked = " "
stashed = " "
modified = " "
staged = " [](bold #CFFC00)"
renamed = " "
deleted = " "
[nix_shell]
symbol = " "
symbol = " "
style = "bold #e3ff66"
format = "via [$symbol$state]($style) "
[rust]
symbol = " "
symbol = " "
style = "bold #e3ff66"
format = "[$symbol($version )]($style)"
[c]
symbol = " "
symbol = " "
style = "bold #00BFFF"
format = "[$symbol($version )]($style)"
[python]
symbol = " "
symbol = " "
style = "bold #CFFC00"
format = "[$symbol($version )]($style)"
[cmd_duration]
min_time = 2000
style = "bold #FF7A59"
format = "took [$duration]($style) "
format = "[ $duration]($style) "
[character]
success_symbol = "[➜](bold #CFFC00) "
error_symbol = "[➜](bold #FF7A59) "
success_symbol = "[❯ ](bold #CFFC00) "
error_symbol = "[❯ ](bold #FF7A59) "