Compare commits
6 Commits
47c0c9c719
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 9dcd863ee1 | |||
| d1e243dd12 | |||
| 8010cea0bc | |||
| fb743d7353 | |||
| 598a43eace | |||
| 367e21ef95 |
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Nix build results
|
||||||
|
result
|
||||||
|
result-*
|
||||||
|
|
||||||
|
# home-manager backup files (backupFileExtension = "backup")
|
||||||
|
*.backup
|
||||||
|
|
||||||
|
# direnv
|
||||||
|
.direnv/
|
||||||
76
README.md
@@ -24,13 +24,26 @@ rebuild switch # zsh helper: sudo nixos-rebuild switch --flake ~/NixOS-Configs
|
|||||||
## Adding a new host (e.g. a laptop)
|
## Adding a new host (e.g. a laptop)
|
||||||
|
|
||||||
1. Create `hosts/<name>/` with `default.nix`, `hardware-configuration.nix`, and
|
1. Create `hosts/<name>/` with `default.nix`, `hardware-configuration.nix`, and
|
||||||
(optionally) `disko.nix`; import the shared modules (`modules/core`,
|
(optionally) `disko.nix`. Shared modules (`modules/core`, `modules/graphical`,
|
||||||
`modules/graphical`, `modules/apps`).
|
`modules/apps`) are already pulled in by `commonModules` in `flake.nix` — the
|
||||||
|
host only adds its own hardware/disko config.
|
||||||
2. Register it in `flake.nix` under `nixosConfigurations`.
|
2. Register it in `flake.nix` under `nixosConfigurations`.
|
||||||
3. Declare host-specific displays in `hosts/<name>/default.nix`:
|
3. Turn optional features on per machine via the shared `my.*` options:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
home-manager.users.anish.volt.monitors = [
|
# hosts/<name>/default.nix
|
||||||
|
my = {
|
||||||
|
username = "anish"; # default; change for a different primary user
|
||||||
|
gaming.enable = false; # Steam, Gamemode, MangoHud
|
||||||
|
ai.local.enable = false; # Ollama, ComfyUI, Open WebUI
|
||||||
|
pi.enable = false; # pi coding agent (home-manager)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Declare host-specific displays in `hosts/<name>/default.nix`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
home-manager.users.anish.hypr.monitors = [
|
||||||
{
|
{
|
||||||
output = "eDP-1"; # or "desc:<panel description>"
|
output = "eDP-1"; # or "desc:<panel description>"
|
||||||
mode = "2880x1800@120Hz"; # or "preferred"
|
mode = "2880x1800@120Hz"; # or "preferred"
|
||||||
@@ -42,6 +55,57 @@ home-manager.users.anish.volt.monitors = [
|
|||||||
];
|
];
|
||||||
```
|
```
|
||||||
|
|
||||||
The `volt.monitors` default (any output @ preferred) keeps every machine working
|
The `hypr.monitors` default (any output @ preferred) keeps every machine working
|
||||||
out of the box; hosts override only what they need. Hosts without an NVIDIA GPU
|
out of the box; hosts override only what they need. Hosts without an NVIDIA GPU
|
||||||
simply don't import `modules/hardware/nvidia.nix` — all vendor env vars live there.
|
simply don't import `modules/hardware/nvidia.nix` — all vendor env vars live there,
|
||||||
|
while the vendor-neutral `hardware.graphics` stack lives in `modules/graphical`.
|
||||||
|
|
||||||
|
## Feature toggles (`my.*`)
|
||||||
|
|
||||||
|
Optional app modules are always imported (via `modules/apps/default.nix`) but only
|
||||||
|
activate when a host sets the corresponding flag, so hosts never need to edit
|
||||||
|
import lists:
|
||||||
|
|
||||||
|
| Option | Enables |
|
||||||
|
| --- | --- |
|
||||||
|
| `my.username` | Primary user (users, home-manager, getty autologin) |
|
||||||
|
| `my.flakeDir` | Checkout path used by the `rebuild` helper and `zshconf`/`hyprconf` aliases |
|
||||||
|
| `my.gaming.enable` | Steam, Gamemode, MangoHud |
|
||||||
|
| `my.ai.local.enable` | Ollama (CUDA), ComfyUI, Open WebUI |
|
||||||
|
| `my.pi.enable` | pi coding agent (home-manager) |
|
||||||
|
|
||||||
|
Hardware modules remain explicit imports per host (e.g. `modules/hardware/nvidia.nix`).
|
||||||
|
|
||||||
|
## Theming
|
||||||
|
|
||||||
|
The Silicon Volt palette lives in one place, `home/theme.nix`, and is passed to
|
||||||
|
every home-manager module through `extraSpecialArgs`. Tools that only accept a
|
||||||
|
raw config file (starship, rofi, the herdr picker) carry `@color@` placeholders
|
||||||
|
that are substituted from the same palette at build time, so changing an accent
|
||||||
|
means editing one file.
|
||||||
|
|
||||||
|
## Editing configs: live vs generated
|
||||||
|
|
||||||
|
Most configs are generated from Nix and symlinked read-only from the store, so
|
||||||
|
they need a `rebuild switch` to change:
|
||||||
|
|
||||||
|
- `foot.ini`, `hyprlock.conf`, `hypridle.conf`, `mako/config`, `starship.toml`,
|
||||||
|
`rofi/config.rasi`, `.zshenv`/`.zshrc` (Nix attrs, themed)
|
||||||
|
|
||||||
|
Three configs are instead symlinked **straight from the repo** with
|
||||||
|
`mkOutOfStoreSymlink`, so edits apply without a rebuild:
|
||||||
|
|
||||||
|
| Live file | Applies on |
|
||||||
|
| --- | --- |
|
||||||
|
| `home/hypr/hyprland.lua` | `hyprctl reload` |
|
||||||
|
| `home/hypr/quickshell/**` | quickshell restart |
|
||||||
|
| `home/nvim/config/**` (incl. `lazy-lock.json`) | next nvim start; `:Lazy update` writes here |
|
||||||
|
|
||||||
|
Because `hyprland.lua` is live, the palette and per-host monitors are emitted as
|
||||||
|
generated fragments, `~/.config/hypr/theme.lua` and
|
||||||
|
`~/.config/hypr/monitors.lua`, which the live file loads with `dofile`. Edit the
|
||||||
|
Lua directly for anything else.
|
||||||
|
|
||||||
|
The tradeoff: live files are not part of the Nix closure, so the repo must exist
|
||||||
|
at `my.flakeDir` for the symlinks to resolve, and uncommitted edits can drift
|
||||||
|
from git. Keep them committed like any other tracked file.
|
||||||
|
|||||||
25
flake.nix
@@ -25,7 +25,6 @@
|
|||||||
url = "github:utensils/comfyui-nix";
|
url = "github:utensils/comfyui-nix";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
herdr = {
|
herdr = {
|
||||||
url = "github:herdrdev/herdr";
|
url = "github:herdrdev/herdr";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
@@ -40,32 +39,28 @@
|
|||||||
inputs:
|
inputs:
|
||||||
let
|
let
|
||||||
system = "x86_64-linux";
|
system = "x86_64-linux";
|
||||||
|
theme = import ./home/theme.nix;
|
||||||
|
|
||||||
commonModules = [
|
commonModules = [
|
||||||
./modules/core
|
./modules/core
|
||||||
./modules/graphical
|
./modules/graphical
|
||||||
./modules/apps
|
./modules/apps
|
||||||
inputs.home-manager.nixosModules.home-manager
|
inputs.home-manager.nixosModules.home-manager
|
||||||
|
(
|
||||||
|
{ config, ... }:
|
||||||
{
|
{
|
||||||
nixpkgs.overlays = [
|
|
||||||
(final: prev: {
|
|
||||||
unstable = import inputs.nixpkgs-unstable {
|
|
||||||
inherit (final) system;
|
|
||||||
config.allowUnfree = true;
|
|
||||||
};
|
|
||||||
})
|
|
||||||
inputs.antigravity.overlays.default
|
|
||||||
inputs.comfy-ui.overlays.default
|
|
||||||
];
|
|
||||||
|
|
||||||
home-manager = {
|
home-manager = {
|
||||||
useGlobalPkgs = true;
|
useGlobalPkgs = true;
|
||||||
useUserPackages = true;
|
useUserPackages = true;
|
||||||
extraSpecialArgs = { inherit inputs; };
|
extraSpecialArgs = {
|
||||||
|
inherit inputs theme;
|
||||||
|
my = config.my;
|
||||||
|
};
|
||||||
backupFileExtension = "backup";
|
backupFileExtension = "backup";
|
||||||
users.anish = import ./home;
|
users.${config.my.username} = import ./home;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
)
|
||||||
];
|
];
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
@@ -80,6 +75,8 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
formatter.${system} = inputs.nixpkgs.legacyPackages.${system}.nixfmt;
|
||||||
|
|
||||||
devShells.${system}.default =
|
devShells.${system}.default =
|
||||||
let
|
let
|
||||||
pkgs = import inputs.nixpkgs {
|
pkgs = import inputs.nixpkgs {
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ in
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
xdg.configFile."brave/policies/managed/policies.json".text = builtins.toJSON bravePolicies;
|
xdg.configFile."brave/policies/managed/policies.json".text = builtins.toJSON bravePolicies;
|
||||||
xdg.configFile."BraveSoftware/Brave-Browser/policies/managed/policies.json".text = builtins.toJSON bravePolicies;
|
xdg.configFile."BraveSoftware/Brave-Browser/policies/managed/policies.json".text =
|
||||||
|
builtins.toJSON bravePolicies;
|
||||||
xdg.configFile."chromium/policies/managed/policies.json".text = builtins.toJSON bravePolicies;
|
xdg.configFile."chromium/policies/managed/policies.json".text = builtins.toJSON bravePolicies;
|
||||||
}
|
}
|
||||||
|
|||||||
104
home/foot.nix
@@ -1,14 +1,56 @@
|
|||||||
{ pkgs, inputs, ... }:
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
inputs,
|
||||||
|
theme,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
herdrPkg = if (inputs ? herdr && inputs.herdr ? packages && inputs.herdr.packages ? ${pkgs.stdenv.hostPlatform.system})
|
herdrPkg =
|
||||||
then inputs.herdr.packages.${pkgs.stdenv.hostPlatform.system}.default
|
if
|
||||||
else (pkgs.herdr or null);
|
(
|
||||||
|
inputs ? herdr
|
||||||
|
&& inputs.herdr ? packages
|
||||||
|
&& inputs.herdr.packages ? ${pkgs.stdenv.hostPlatform.system}
|
||||||
|
)
|
||||||
|
then
|
||||||
|
inputs.herdr.packages.${pkgs.stdenv.hostPlatform.system}.default
|
||||||
|
else
|
||||||
|
(pkgs.herdr or null);
|
||||||
|
|
||||||
herdrPicker = pkgs.writeShellScriptBin "herdr-picker" (builtins.readFile ./scripts/herdr-picker.sh);
|
# Inject the shared palette into the picker script (see home/theme.nix).
|
||||||
|
herdrPicker = pkgs.writeShellScriptBin "herdr-picker" (
|
||||||
|
lib.replaceStrings
|
||||||
|
[
|
||||||
|
"@bg@"
|
||||||
|
"@glass@"
|
||||||
|
"@fg@"
|
||||||
|
"@fgBright@"
|
||||||
|
"@muted@"
|
||||||
|
"@volt@"
|
||||||
|
"@voltLight@"
|
||||||
|
"@coral@"
|
||||||
|
"@blue@"
|
||||||
|
"@surface@"
|
||||||
|
]
|
||||||
|
[
|
||||||
|
theme.colors.bgPrimary
|
||||||
|
theme.colors.glassBg
|
||||||
|
theme.colors.textSecondary
|
||||||
|
theme.colors.textPrimary
|
||||||
|
theme.colors.textMuted
|
||||||
|
theme.colors.accentVolt
|
||||||
|
theme.colors.accentVoltLight
|
||||||
|
theme.colors.accentCoral
|
||||||
|
theme.colors.accentBlue
|
||||||
|
theme.colors.surfaceVariant
|
||||||
|
]
|
||||||
|
(builtins.readFile ./scripts/herdr-picker.sh)
|
||||||
|
);
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
home.packages = (if herdrPkg != null then [ herdrPkg ] else []) ++ [
|
home.packages = (if herdrPkg != null then [ herdrPkg ] else [ ]) ++ [
|
||||||
herdrPicker
|
herdrPicker
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -33,42 +75,44 @@ in
|
|||||||
style = "beam";
|
style = "beam";
|
||||||
blink = "yes";
|
blink = "yes";
|
||||||
beam-thickness = 1.5;
|
beam-thickness = 1.5;
|
||||||
color = "0e0e0e cffc00";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mouse = {
|
mouse = {
|
||||||
hide-when-typing = "yes";
|
hide-when-typing = "yes";
|
||||||
};
|
};
|
||||||
|
|
||||||
colors = {
|
# Silicon Volt palette, sourced from home/theme.nix.
|
||||||
|
colors-dark = {
|
||||||
alpha = 0.92;
|
alpha = 0.92;
|
||||||
background = "131313";
|
background = theme.bare.bgPrimary;
|
||||||
foreground = "e5e2e1";
|
foreground = theme.bare.textSecondary;
|
||||||
|
|
||||||
# Silicon Volt palette (anish.govind.cc)
|
# Cursor text and cursor (background) colors
|
||||||
regular0 = "1c1b1b"; # Glass Black
|
cursor = "${theme.bare.bgSecondary} ${theme.bare.accentVolt}";
|
||||||
regular1 = "ff7a59"; # Coral Red
|
|
||||||
regular2 = "cffc00"; # Electric Lime (green)
|
regular0 = theme.bare.bgSecondary;
|
||||||
regular3 = "e3ff66"; # Volt Light (yellow)
|
regular1 = theme.bare.accentCoral;
|
||||||
regular4 = "00bfff"; # Sky Blue
|
regular2 = theme.bare.accentVolt;
|
||||||
regular5 = "c6c6c7"; # Neutral Gray (magenta)
|
regular3 = theme.bare.accentVoltLight;
|
||||||
regular6 = "66d9ff"; # Light Cyan
|
regular4 = theme.bare.accentBlue;
|
||||||
regular7 = "e5e2e1"; # Light Gray
|
regular5 = theme.bare.neutralGray;
|
||||||
|
regular6 = theme.bare.lightCyan;
|
||||||
|
regular7 = theme.bare.textSecondary;
|
||||||
|
|
||||||
# Bright Colors
|
# Bright Colors
|
||||||
bright0 = "5c5a59"; # Dim Gray
|
bright0 = theme.bare.surfaceVariant;
|
||||||
bright1 = "ff9a82"; # Bright Coral
|
bright1 = theme.bare.brightCoral;
|
||||||
bright2 = "dfff66"; # Bright Lime
|
bright2 = theme.bare.brightLime;
|
||||||
bright3 = "f0ff8c"; # Bright Volt
|
bright3 = theme.bare.brightVolt;
|
||||||
bright4 = "66d9ff"; # Bright Blue
|
bright4 = theme.bare.lightCyan;
|
||||||
bright5 = "e0e0e1"; # Bright Gray
|
bright5 = theme.bare.brightGray;
|
||||||
bright6 = "99e6ff"; # Bright Cyan
|
bright6 = theme.bare.brightCyan;
|
||||||
bright7 = "ffffff"; # Pure White
|
bright7 = theme.bare.textPrimary;
|
||||||
|
|
||||||
# Selection & URLs - volt lime selection like the site
|
# Selection & URLs - volt lime selection like the site
|
||||||
selection-foreground = "0e0e0e";
|
selection-foreground = theme.bare.bgSecondary;
|
||||||
selection-background = "cffc00";
|
selection-background = theme.bare.accentVolt;
|
||||||
urls = "00bfff";
|
urls = theme.bare.accentBlue;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
{ pkgs, lib, config, ... }:
|
{
|
||||||
|
pkgs,
|
||||||
|
config,
|
||||||
|
theme,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
# Deterministic Silicon Volt wallpaper: rendered from SVG at build time,
|
# Deterministic Silicon Volt wallpaper: rendered from SVG at build time,
|
||||||
@@ -13,6 +18,7 @@ in
|
|||||||
./hyprlock.nix
|
./hyprlock.nix
|
||||||
./hypridle.nix
|
./hypridle.nix
|
||||||
./quickshell.nix
|
./quickshell.nix
|
||||||
|
./services.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
home.packages = with pkgs; [
|
home.packages = with pkgs; [
|
||||||
@@ -20,33 +26,41 @@ in
|
|||||||
grim
|
grim
|
||||||
slurp
|
slurp
|
||||||
wl-clipboard
|
wl-clipboard
|
||||||
cliphist
|
|
||||||
brightnessctl
|
brightnessctl
|
||||||
jq
|
jq
|
||||||
|
|
||||||
# Desktop Environment / Hyprland ecosystem
|
# Desktop Environment / Hyprland ecosystem
|
||||||
hyprpaper
|
hyprpaper
|
||||||
hyprpolkitagent
|
|
||||||
];
|
];
|
||||||
|
|
||||||
# Build-time rendered wallpaper
|
# Build-time rendered wallpaper
|
||||||
xdg.dataFile."backgrounds/silicon-volt.png".source = wallpaper;
|
xdg.dataFile."backgrounds/silicon-volt.png".source = wallpaper;
|
||||||
|
|
||||||
# hyprpaper: load the Silicon Volt wallpaper on every output
|
# hyprpaper: load the Silicon Volt wallpaper on every output. The
|
||||||
xdg.configFile."hypr/hyprpaper.conf".text = ''
|
# home-manager module writes hypr/hyprpaper.conf and runs the daemon as a
|
||||||
preload = ${config.home.homeDirectory}/.local/share/backgrounds/silicon-volt.png
|
# systemd user service tied to the Wayland session.
|
||||||
wallpaper = ,${config.home.homeDirectory}/.local/share/backgrounds/silicon-volt.png
|
services.hyprpaper = {
|
||||||
splash = false
|
enable = true;
|
||||||
'';
|
settings = {
|
||||||
|
splash = false;
|
||||||
|
preload = "${config.home.homeDirectory}/.local/share/backgrounds/silicon-volt.png";
|
||||||
|
wallpaper = [
|
||||||
|
{
|
||||||
|
monitor = "";
|
||||||
|
path = "${config.home.homeDirectory}/.local/share/backgrounds/silicon-volt.png";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
# Notifications: flat brutalist-glass cards with volt accents
|
# Notifications: flat brutalist-glass cards with volt accents
|
||||||
services.mako = {
|
services.mako = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
||||||
settings = {
|
settings = {
|
||||||
background-color = "#1c1b1bee";
|
background-color = "${theme.colors.glassBg}ee";
|
||||||
text-color = "#e5e2e1";
|
text-color = theme.colors.textSecondary;
|
||||||
border-color = "#cffc00";
|
border-color = theme.colors.accentVolt;
|
||||||
border-size = 1;
|
border-size = 1;
|
||||||
border-radius = 0;
|
border-radius = 0;
|
||||||
width = 420;
|
width = 420;
|
||||||
@@ -59,13 +73,13 @@ in
|
|||||||
max-icon-size = 48;
|
max-icon-size = 48;
|
||||||
|
|
||||||
"urgency=critical" = {
|
"urgency=critical" = {
|
||||||
border-color = "#ff7a59";
|
border-color = theme.colors.accentCoral;
|
||||||
text-color = "#ffffff";
|
text-color = theme.colors.textPrimary;
|
||||||
default-timeout = 0;
|
default-timeout = 0;
|
||||||
};
|
};
|
||||||
"urgency=low" = {
|
"urgency=low" = {
|
||||||
border-color = "#2a2929";
|
border-color = theme.colors.surfaceVariant;
|
||||||
text-color = "#adaaaa";
|
text-color = theme.colors.textMuted;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
282
home/hypr/hyprland.lua
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
-- Silicon Volt Hyprland configuration (hand-maintained).
|
||||||
|
--
|
||||||
|
-- home-manager symlinks this file straight from the repo, so edits apply
|
||||||
|
-- on `hyprctl reload` without a rebuild. The generated fragments
|
||||||
|
-- `theme.lua` (from home/theme.nix) and `monitors.lua` (from hypr.monitors)
|
||||||
|
-- live next to this file and are loaded below.
|
||||||
|
|
||||||
|
local configDir = (os.getenv("XDG_CONFIG_HOME") or (os.getenv("HOME") .. "/.config")) .. "/hypr"
|
||||||
|
local theme = dofile(configDir .. "/theme.lua")
|
||||||
|
local monitors = dofile(configDir .. "/monitors.lua")
|
||||||
|
|
||||||
|
|
||||||
|
-- settings.locals
|
||||||
|
local browser = "brave"
|
||||||
|
local mainMod = "SUPER"
|
||||||
|
local terminal = "foot"
|
||||||
|
|
||||||
|
-- settings.curve
|
||||||
|
hl.curve("myBezier", {
|
||||||
|
["points"] = {
|
||||||
|
{
|
||||||
|
0.05,
|
||||||
|
0.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
0.1,
|
||||||
|
1.05
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["type"] = "bezier"
|
||||||
|
})
|
||||||
|
|
||||||
|
-- settings.animation
|
||||||
|
hl.animation({
|
||||||
|
["bezier"] = "myBezier",
|
||||||
|
["enabled"] = true,
|
||||||
|
["leaf"] = "windows",
|
||||||
|
["speed"] = 7
|
||||||
|
})
|
||||||
|
hl.animation({
|
||||||
|
["bezier"] = "default",
|
||||||
|
["enabled"] = true,
|
||||||
|
["leaf"] = "windowsOut",
|
||||||
|
["speed"] = 7,
|
||||||
|
["style"] = "popin 80%"
|
||||||
|
})
|
||||||
|
hl.animation({
|
||||||
|
["bezier"] = "default",
|
||||||
|
["enabled"] = true,
|
||||||
|
["leaf"] = "border",
|
||||||
|
["speed"] = 10
|
||||||
|
})
|
||||||
|
hl.animation({
|
||||||
|
["bezier"] = "default",
|
||||||
|
["enabled"] = true,
|
||||||
|
["leaf"] = "borderangle",
|
||||||
|
["speed"] = 8
|
||||||
|
})
|
||||||
|
hl.animation({
|
||||||
|
["bezier"] = "default",
|
||||||
|
["enabled"] = true,
|
||||||
|
["leaf"] = "fade",
|
||||||
|
["speed"] = 7
|
||||||
|
})
|
||||||
|
hl.animation({
|
||||||
|
["bezier"] = "default",
|
||||||
|
["enabled"] = true,
|
||||||
|
["leaf"] = "workspaces",
|
||||||
|
["speed"] = 6
|
||||||
|
})
|
||||||
|
|
||||||
|
-- settings.bind
|
||||||
|
hl.bind((mainMod .. " + Q"), (hl.dsp.exec_cmd(terminal)))
|
||||||
|
hl.bind((mainMod .. " + C"), (hl.dsp.window.close()))
|
||||||
|
hl.bind((mainMod .. " + SPACE"), (hl.dsp.exec_cmd("rofi -show combi -show-icons")))
|
||||||
|
hl.bind((mainMod .. " + N"), (hl.dsp.exec_cmd("swaync-client -t -sw")))
|
||||||
|
hl.bind((mainMod .. " + TAB"), (hl.dsp.exec_cmd("qs ipc call overview toggle")))
|
||||||
|
hl.bind((mainMod .. " + V"), (hl.dsp.window.float({ action = "toggle" })))
|
||||||
|
hl.bind((mainMod .. " + R"), (hl.dsp.exec_cmd("rofi -show combi")))
|
||||||
|
hl.bind((mainMod .. " + G"), (hl.dsp.group.toggle()))
|
||||||
|
hl.bind((mainMod .. " + F"), (hl.dsp.window.fullscreen({ fullscreen = 0 })))
|
||||||
|
hl.bind((mainMod .. " + h"), (hl.dsp.focus({ direction = "left" })))
|
||||||
|
hl.bind((mainMod .. " + l"), (hl.dsp.focus({ direction = "right" })))
|
||||||
|
hl.bind((mainMod .. " + k"), (hl.dsp.focus({ direction = "up" })))
|
||||||
|
hl.bind((mainMod .. " + j"), (hl.dsp.focus({ direction = "down" })))
|
||||||
|
hl.bind((mainMod .. " + e"), (hl.dsp.group.next()))
|
||||||
|
hl.bind((mainMod .. " + d"), (hl.dsp.group.prev()))
|
||||||
|
hl.bind((mainMod .. " + SHIFT + h"), (hl.dsp.window.move({ direction = "left" })))
|
||||||
|
hl.bind((mainMod .. " + SHIFT + l"), (hl.dsp.window.move({ direction = "right" })))
|
||||||
|
hl.bind((mainMod .. " + SHIFT + k"), (hl.dsp.window.move({ direction = "up" })))
|
||||||
|
hl.bind((mainMod .. " + SHIFT + j"), (hl.dsp.window.move({ direction = "down" })))
|
||||||
|
hl.bind((mainMod .. " + mouse_down"), (hl.dsp.focus({ workspace = "e+1" })))
|
||||||
|
hl.bind((mainMod .. " + mouse_up"), (hl.dsp.focus({ workspace = "e-1" })))
|
||||||
|
hl.bind((mainMod .. " + S"), (hl.dsp.exec_cmd("hyprlock")))
|
||||||
|
hl.bind((mainMod .. " + b"), (hl.dsp.exec_cmd(browser)))
|
||||||
|
hl.bind((mainMod .. " + RETURN"), (hl.dsp.workspace.toggle_special("scratch")))
|
||||||
|
hl.bind((mainMod .. " + SHIFT + RETURN"), (hl.dsp.window.move({ workspace = "special:scratch" })))
|
||||||
|
hl.bind((mainMod .. " + M"), (hl.dsp.workspace.toggle_special("messaging")))
|
||||||
|
hl.bind((mainMod .. " + mouse:272"), (hl.dsp.window.drag()), {
|
||||||
|
["mouse"] = true
|
||||||
|
})
|
||||||
|
hl.bind((mainMod .. " + mouse:273"), (hl.dsp.window.resize()), {
|
||||||
|
["mouse"] = true
|
||||||
|
})
|
||||||
|
hl.bind("XF86AudioRaiseVolume", (hl.dsp.exec_cmd("pactl -- set-sink-volume @DEFAULT_SINK@ +5%")), {
|
||||||
|
["repeating"] = true
|
||||||
|
})
|
||||||
|
hl.bind("XF86AudioLowerVolume", (hl.dsp.exec_cmd("pactl -- set-sink-volume @DEFAULT_SINK@ -5%")), {
|
||||||
|
["locked"] = true,
|
||||||
|
["repeating"] = true
|
||||||
|
})
|
||||||
|
hl.bind("XF86AudioMute", (hl.dsp.exec_cmd("pactl -- set-sink-mute @DEFAULT_SINK@ toggle")), {
|
||||||
|
["locked"] = true,
|
||||||
|
["repeating"] = true
|
||||||
|
})
|
||||||
|
hl.bind("XF86MonBrightnessUp", (hl.dsp.exec_cmd("brightnessctl set 5%+")), {
|
||||||
|
["locked"] = true,
|
||||||
|
["repeating"] = true
|
||||||
|
})
|
||||||
|
hl.bind("XF86MonBrightnessDown", (hl.dsp.exec_cmd("brightnessctl set 5%-")), {
|
||||||
|
["locked"] = true,
|
||||||
|
["repeating"] = true
|
||||||
|
})
|
||||||
|
hl.bind("switch:off:Lid Switch", (hl.dsp.exec_cmd("m=$(hyprctl monitors | awk '$1==\"Monitor\"{print $2; exit}'); [ -n \"$m\" ] && hyprctl keyword monitor \"$m, preferred, auto, auto\"")), {
|
||||||
|
["locked"] = true
|
||||||
|
})
|
||||||
|
hl.bind("switch:on:Lid Switch", (hl.dsp.exec_cmd("m=$(hyprctl monitors | awk '$1==\"Monitor\"{print $2; exit}'); [ -n \"$m\" ] && hyprctl keyword monitor \"$m, disable\"")), {
|
||||||
|
["locked"] = true
|
||||||
|
})
|
||||||
|
hl.bind((mainMod .. " + 1"), (hl.dsp.focus({ workspace = 1 })))
|
||||||
|
hl.bind((mainMod .. " + SHIFT + 1"), (hl.dsp.window.move({ workspace = 1 })))
|
||||||
|
hl.bind((mainMod .. " + 2"), (hl.dsp.focus({ workspace = 2 })))
|
||||||
|
hl.bind((mainMod .. " + SHIFT + 2"), (hl.dsp.window.move({ workspace = 2 })))
|
||||||
|
hl.bind((mainMod .. " + 3"), (hl.dsp.focus({ workspace = 3 })))
|
||||||
|
hl.bind((mainMod .. " + SHIFT + 3"), (hl.dsp.window.move({ workspace = 3 })))
|
||||||
|
hl.bind((mainMod .. " + 4"), (hl.dsp.focus({ workspace = 4 })))
|
||||||
|
hl.bind((mainMod .. " + SHIFT + 4"), (hl.dsp.window.move({ workspace = 4 })))
|
||||||
|
hl.bind((mainMod .. " + 5"), (hl.dsp.focus({ workspace = 5 })))
|
||||||
|
hl.bind((mainMod .. " + SHIFT + 5"), (hl.dsp.window.move({ workspace = 5 })))
|
||||||
|
hl.bind((mainMod .. " + 6"), (hl.dsp.focus({ workspace = 6 })))
|
||||||
|
hl.bind((mainMod .. " + SHIFT + 6"), (hl.dsp.window.move({ workspace = 6 })))
|
||||||
|
hl.bind((mainMod .. " + 7"), (hl.dsp.focus({ workspace = 7 })))
|
||||||
|
hl.bind((mainMod .. " + SHIFT + 7"), (hl.dsp.window.move({ workspace = 7 })))
|
||||||
|
hl.bind((mainMod .. " + 8"), (hl.dsp.focus({ workspace = 8 })))
|
||||||
|
hl.bind((mainMod .. " + SHIFT + 8"), (hl.dsp.window.move({ workspace = 8 })))
|
||||||
|
hl.bind((mainMod .. " + 9"), (hl.dsp.focus({ workspace = 9 })))
|
||||||
|
hl.bind((mainMod .. " + SHIFT + 9"), (hl.dsp.window.move({ workspace = 9 })))
|
||||||
|
hl.bind((mainMod .. " + 0"), (hl.dsp.focus({ workspace = 10 })))
|
||||||
|
hl.bind((mainMod .. " + SHIFT + 0"), (hl.dsp.window.move({ workspace = 10 })))
|
||||||
|
|
||||||
|
-- settings.config
|
||||||
|
hl.config({
|
||||||
|
["animations"] = {
|
||||||
|
["enabled"] = true
|
||||||
|
},
|
||||||
|
["cursor"] = {
|
||||||
|
["inactive_timeout"] = 3,
|
||||||
|
["no_hardware_cursors"] = false
|
||||||
|
},
|
||||||
|
["decoration"] = {
|
||||||
|
["blur"] = {
|
||||||
|
["brightness"] = 0.9,
|
||||||
|
["contrast"] = 1.1,
|
||||||
|
["enabled"] = true,
|
||||||
|
["ignore_opacity"] = true,
|
||||||
|
["new_optimizations"] = true,
|
||||||
|
["passes"] = 2,
|
||||||
|
["size"] = 4,
|
||||||
|
["vibrancy"] = 0.1
|
||||||
|
},
|
||||||
|
["rounding"] = 4,
|
||||||
|
["shadow"] = {
|
||||||
|
["color"] = "rgba(00000066)",
|
||||||
|
["enabled"] = true,
|
||||||
|
["range"] = 12,
|
||||||
|
["render_power"] = 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["dwindle"] = {
|
||||||
|
["preserve_split"] = true
|
||||||
|
},
|
||||||
|
["general"] = {
|
||||||
|
["border_size"] = 2,
|
||||||
|
["col"] = {
|
||||||
|
["active_border"] = {
|
||||||
|
["angle"] = 45,
|
||||||
|
["colors"] = {
|
||||||
|
"rgba(" .. theme.bare.accentVolt .. "ee)",
|
||||||
|
"rgba(" .. theme.bare.accentVoltLight .. "ee)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["inactive_border"] = "rgba(" .. theme.bare.textPrimary .. "14)"
|
||||||
|
},
|
||||||
|
["gaps_in"] = 5,
|
||||||
|
["gaps_out"] = 10,
|
||||||
|
["layout"] = "dwindle"
|
||||||
|
},
|
||||||
|
["group"] = {
|
||||||
|
["col"] = {
|
||||||
|
["border_active"] = {
|
||||||
|
["angle"] = 45,
|
||||||
|
["colors"] = {
|
||||||
|
"rgba(" .. theme.bare.accentVolt .. "ee)",
|
||||||
|
"rgba(" .. theme.bare.accentVoltLight .. "ee)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["border_inactive"] = "rgba(" .. theme.bare.textPrimary .. "14)"
|
||||||
|
},
|
||||||
|
["groupbar"] = {
|
||||||
|
["col"] = {
|
||||||
|
["active"] = "rgba(" .. theme.bare.accentVolt .. "22)",
|
||||||
|
["inactive"] = "rgba(" .. theme.bare.textPrimary .. "08)"
|
||||||
|
},
|
||||||
|
["font_family"] = "JetBrainsMono Nerd Font",
|
||||||
|
["font_size"] = 14
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["input"] = {
|
||||||
|
["follow_mouse"] = 1,
|
||||||
|
["kb_layout"] = "us",
|
||||||
|
["repeat_delay"] = 300,
|
||||||
|
["repeat_rate"] = 50,
|
||||||
|
["sensitivity"] = 0,
|
||||||
|
["touchpad"] = {
|
||||||
|
["natural_scroll"] = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["master"] = {
|
||||||
|
["new_status"] = "master"
|
||||||
|
},
|
||||||
|
["misc"] = {
|
||||||
|
["disable_hyprland_logo"] = true,
|
||||||
|
["disable_splash_rendering"] = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
-- settings.env
|
||||||
|
hl.env("XCURSOR_SIZE", "24")
|
||||||
|
hl.env("XDG_SESSION_TYPE", "wayland")
|
||||||
|
|
||||||
|
-- monitors (generated from hypr.monitors)
|
||||||
|
for _, m in ipairs(monitors) do
|
||||||
|
hl.monitor(m)
|
||||||
|
end
|
||||||
|
-- settings.window_rule
|
||||||
|
hl.window_rule({
|
||||||
|
["match"] = {
|
||||||
|
["class"] = "Signal"
|
||||||
|
},
|
||||||
|
["name"] = "signal-messaging",
|
||||||
|
["workspace"] = "special:messaging"
|
||||||
|
})
|
||||||
|
hl.window_rule({
|
||||||
|
["match"] = {
|
||||||
|
["class"] = "vesktop"
|
||||||
|
},
|
||||||
|
["name"] = "vesktop-messaging",
|
||||||
|
["workspace"] = "special:messaging"
|
||||||
|
})
|
||||||
|
hl.window_rule({
|
||||||
|
["float"] = false,
|
||||||
|
["match"] = {
|
||||||
|
["class"] = "ghidra-Ghidra"
|
||||||
|
},
|
||||||
|
["name"] = "ghidra-tile"
|
||||||
|
})
|
||||||
|
|
||||||
|
-- settings.workspace_rule
|
||||||
|
hl.workspace_rule({
|
||||||
|
["on_created_empty"] = "[] togglegroup",
|
||||||
|
["workspace"] = "name:messaging"
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
-- Start the systemd user session target after importing the Wayland
|
||||||
|
-- environment, so the desktop services declared in home/hypr/services.nix
|
||||||
|
-- and home/hypr/quickshell.nix (hypridle, hyprpaper, mako, cliphist,
|
||||||
|
-- quickshell, polkit agent) are pulled in. This replaces the hook that the
|
||||||
|
-- home-manager hyprland module used to generate.
|
||||||
|
hl.on("hyprland.start", function()
|
||||||
|
hl.exec_cmd("dbus-update-activation-environment --systemd DISPLAY HYPRLAND_INSTANCE_SIGNATURE WAYLAND_DISPLAY XDG_CURRENT_DESKTOP XDG_SESSION_TYPE && systemctl --user start graphical-session.target")
|
||||||
|
end)
|
||||||
@@ -1,20 +1,62 @@
|
|||||||
{ pkgs, lib, config, ... }:
|
{
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
theme,
|
||||||
|
my,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
mkLua = lib.generators.mkLuaInline;
|
# Minimal Lua value renderer for the generated fragments.
|
||||||
|
toLua =
|
||||||
|
v:
|
||||||
|
if builtins.isString v then
|
||||||
|
''"${v}"''
|
||||||
|
else if builtins.isBool v then
|
||||||
|
(if v then "true" else "false")
|
||||||
|
else if builtins.isInt v || builtins.isFloat v then
|
||||||
|
toString v
|
||||||
|
else if v == null then
|
||||||
|
"nil"
|
||||||
|
else if builtins.isList v then
|
||||||
|
"{ " + lib.concatMapStringsSep ", " toLua v + " }"
|
||||||
|
else if builtins.isAttrs v then
|
||||||
|
"{ " + lib.concatStringsSep ", " (lib.mapAttrsToList (k: val: "${k} = ${toLua val}") v) + " }"
|
||||||
|
else
|
||||||
|
throw "toLua: unsupported value type";
|
||||||
|
|
||||||
|
themeLua = "return " + toLua { inherit (theme) colors bare; };
|
||||||
|
|
||||||
|
monitorsLua =
|
||||||
|
"return "
|
||||||
|
+ toLua (
|
||||||
|
map (
|
||||||
|
m:
|
||||||
|
{
|
||||||
|
inherit (m)
|
||||||
|
output
|
||||||
|
mode
|
||||||
|
position
|
||||||
|
scale
|
||||||
|
;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (m.vrr != null) { inherit (m) vrr; }
|
||||||
|
) config.hypr.monitors
|
||||||
|
);
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Per-host monitor configuration.
|
# Per-host monitor configuration.
|
||||||
#
|
#
|
||||||
# Hosts declare their displays once, in hosts/<host>/default.nix, via:
|
# Hosts declare their displays once, in hosts/<host>/default.nix, via:
|
||||||
# home-manager.users.anish.volt.monitors = [ { ... } ];
|
# home-manager.users.<my.username>.hypr.monitors = [ { ... } ];
|
||||||
# and the setting flows into Hyprland here. The default (empty output +
|
# The list is rendered to ~/.config/hypr/monitors.lua, which the
|
||||||
# preferred mode) works on any machine with any display.
|
# hand-maintained hyprland.lua pulls in with require("monitors").
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
options.volt.monitors = lib.mkOption {
|
options.hypr.monitors = lib.mkOption {
|
||||||
description = "Hyprland monitor rules for this host";
|
description = "Hyprland monitor rules for this host";
|
||||||
type = lib.types.listOf (lib.types.submodule {
|
type = lib.types.listOf (
|
||||||
|
lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
output = lib.mkOption {
|
output = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
@@ -40,362 +82,26 @@ in
|
|||||||
description = "Optional per-monitor VRR setting (0/1/on-demand).";
|
description = "Optional per-monitor VRR setting (0/1/on-demand).";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
}
|
||||||
|
);
|
||||||
default = [
|
default = [
|
||||||
{ output = ""; mode = "preferred"; position = "auto"; scale = "auto"; }
|
{
|
||||||
|
output = "";
|
||||||
|
mode = "preferred";
|
||||||
|
position = "auto";
|
||||||
|
scale = "auto";
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
wayland.windowManager.hyprland.settings.monitor = map (m:
|
# Live, hand-maintained config: symlinked straight to the repo so edits
|
||||||
{ inherit (m) output mode position scale; }
|
# apply on `hyprctl reload` without a rebuild.
|
||||||
// (lib.optionalAttrs (m.vrr != null) { inherit (m) vrr; })
|
xdg.configFile."hypr/hyprland.lua".source =
|
||||||
) config.volt.monitors;
|
config.lib.file.mkOutOfStoreSymlink "${my.flakeDir}/home/hypr/hyprland.lua";
|
||||||
|
|
||||||
wayland.windowManager.hyprland = {
|
# Generated fragments required by the live config.
|
||||||
enable = true;
|
xdg.configFile."hypr/theme.lua".text = themeLua;
|
||||||
configType = "lua";
|
xdg.configFile."hypr/monitors.lua".text = monitorsLua;
|
||||||
package = null; # Hyprland package is managed system-wide via NixOS programs.hyprland
|
|
||||||
|
|
||||||
settings = {
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
# 1. Variables & Modifiers (Rendered as Lua local variables)
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
mainMod = { _var = "SUPER"; };
|
|
||||||
terminal = { _var = "foot"; };
|
|
||||||
browser = { _var = "brave"; };
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
# 2. Monitors (hl.monitor) - declared per-host via volt.monitors
|
|
||||||
# (see hosts/<host>/default.nix); defaults to any display @ preferred.
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
# 3. Environment Variables (hl.env)
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
env = [
|
|
||||||
{ _args = [ "XCURSOR_SIZE" "24" ]; }
|
|
||||||
{ _args = [ "XDG_SESSION_TYPE" "wayland" ]; }
|
|
||||||
];
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
# 4. Main Configuration (hl.config) - Strictly validates against Hyprland 0.55+ schema
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
config = {
|
|
||||||
input = {
|
|
||||||
kb_layout = "us";
|
|
||||||
repeat_delay = 300;
|
|
||||||
repeat_rate = 50;
|
|
||||||
follow_mouse = 1;
|
|
||||||
sensitivity = 0;
|
|
||||||
touchpad = {
|
|
||||||
natural_scroll = false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
cursor = {
|
|
||||||
inactive_timeout = 3;
|
|
||||||
# Hardware cursors ON (explicit). Note: this replaces the old
|
|
||||||
# WLR_NO_HARDWARE_CURSORS=1, which forced software rendering.
|
|
||||||
no_hardware_cursors = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
general = {
|
|
||||||
gaps_in = 5;
|
|
||||||
gaps_out = 10;
|
|
||||||
border_size = 2;
|
|
||||||
col = {
|
|
||||||
# Silicon Volt: electric lime gradient on focus, glass gray at rest
|
|
||||||
active_border = {
|
|
||||||
colors = [ "rgba(cffc00ee)" "rgba(e3ff66ee)" ];
|
|
||||||
angle = 45;
|
|
||||||
};
|
|
||||||
inactive_border = "rgba(ffffff14)";
|
|
||||||
};
|
|
||||||
layout = "dwindle";
|
|
||||||
};
|
|
||||||
|
|
||||||
group = {
|
|
||||||
groupbar = {
|
|
||||||
font_size = 14;
|
|
||||||
font_family = "JetBrainsMono Nerd Font";
|
|
||||||
col = {
|
|
||||||
active = "rgba(cffc0022)";
|
|
||||||
inactive = "rgba(ffffff08)";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
col = {
|
|
||||||
border_active = {
|
|
||||||
colors = [ "rgba(cffc00ee)" "rgba(e3ff66ee)" ];
|
|
||||||
angle = 45;
|
|
||||||
};
|
|
||||||
border_inactive = "rgba(ffffff14)";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
misc = {
|
|
||||||
disable_hyprland_logo = true;
|
|
||||||
disable_splash_rendering = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
decoration = {
|
|
||||||
rounding = 4;
|
|
||||||
|
|
||||||
# Subtle glass blur to match the brutalist-glass aesthetic
|
|
||||||
blur = {
|
|
||||||
enabled = true;
|
|
||||||
size = 4;
|
|
||||||
passes = 2;
|
|
||||||
new_optimizations = true;
|
|
||||||
ignore_opacity = true;
|
|
||||||
contrast = 1.1;
|
|
||||||
brightness = 0.9;
|
|
||||||
vibrancy = 0.1;
|
|
||||||
};
|
|
||||||
|
|
||||||
shadow = {
|
|
||||||
enabled = true;
|
|
||||||
range = 12;
|
|
||||||
render_power = 3;
|
|
||||||
color = "rgba(00000066)";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
animations = {
|
|
||||||
enabled = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
dwindle = {
|
|
||||||
preserve_split = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
master = {
|
|
||||||
new_status = "master";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
# 5. Curves & Animations (hl.curve & hl.animation)
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
curve = [
|
|
||||||
{
|
|
||||||
_args = [
|
|
||||||
"myBezier"
|
|
||||||
{
|
|
||||||
type = "bezier";
|
|
||||||
points = [ [ 0.05 0.9 ] [ 0.1 1.05 ] ];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
animation = [
|
|
||||||
{ leaf = "windows"; enabled = true; speed = 7; bezier = "myBezier"; }
|
|
||||||
{ leaf = "windowsOut"; enabled = true; speed = 7; bezier = "default"; style = "popin 80%"; }
|
|
||||||
{ leaf = "border"; enabled = true; speed = 10; bezier = "default"; }
|
|
||||||
{ leaf = "borderangle"; enabled = true; speed = 8; bezier = "default"; }
|
|
||||||
{ leaf = "fade"; enabled = true; speed = 7; bezier = "default"; }
|
|
||||||
{ leaf = "workspaces"; enabled = true; speed = 6; bezier = "default"; }
|
|
||||||
];
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
# 6. Autostart Daemons & Apps (hl.on)
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
on = [
|
|
||||||
{
|
|
||||||
_args = [
|
|
||||||
"hyprland.start"
|
|
||||||
(mkLua ''function()
|
|
||||||
hl.exec_cmd("quickshell")
|
|
||||||
hl.exec_cmd("hyprpaper")
|
|
||||||
hl.exec_cmd("mako")
|
|
||||||
hl.exec_cmd("wl-paste --type text --watch cliphist store")
|
|
||||||
hl.exec_cmd("wl-paste --type image --watch cliphist store")
|
|
||||||
hl.exec_cmd("${pkgs.hyprpolkitagent}/libexec/hyprpolkitagent")
|
|
||||||
end'')
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
# 7. Keybindings (hl.bind)
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
bind = [
|
|
||||||
# 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 .. \" + 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()") ]; }
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + F\"") (mkLua "hl.dsp.window.fullscreen({ fullscreen = 0 })") ]; }
|
|
||||||
|
|
||||||
# Move focus with mainMod + vim
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + h\"") (mkLua "hl.dsp.focus({ direction = \"left\" })") ]; }
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + l\"") (mkLua "hl.dsp.focus({ direction = \"right\" })") ]; }
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + k\"") (mkLua "hl.dsp.focus({ direction = \"up\" })") ]; }
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + j\"") (mkLua "hl.dsp.focus({ direction = \"down\" })") ]; }
|
|
||||||
|
|
||||||
# Move focus within a grouped page
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + e\"") (mkLua "hl.dsp.group.next()") ]; }
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + d\"") (mkLua "hl.dsp.group.prev()") ]; }
|
|
||||||
|
|
||||||
# Move window or group with vim keys
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + SHIFT + h\"") (mkLua "hl.dsp.window.move({ direction = \"left\" })") ]; }
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + SHIFT + l\"") (mkLua "hl.dsp.window.move({ direction = \"right\" })") ]; }
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + SHIFT + k\"") (mkLua "hl.dsp.window.move({ direction = \"up\" })") ]; }
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + SHIFT + j\"") (mkLua "hl.dsp.window.move({ direction = \"down\" })") ]; }
|
|
||||||
|
|
||||||
# Scroll through existing workspaces with mainMod + scroll
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + mouse_down\"") (mkLua "hl.dsp.focus({ workspace = \"e+1\" })") ]; }
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + mouse_up\"") (mkLua "hl.dsp.focus({ workspace = \"e-1\" })") ]; }
|
|
||||||
|
|
||||||
# Lock shortcut
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + S\"") (mkLua "hl.dsp.exec_cmd(\"hyprlock\")") ]; }
|
|
||||||
|
|
||||||
# Quick access applications
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + b\"") (mkLua "hl.dsp.exec_cmd(browser)") ]; }
|
|
||||||
|
|
||||||
# Scratch workspace
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + RETURN\"") (mkLua "hl.dsp.workspace.toggle_special(\"scratch\")") ]; }
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + SHIFT + RETURN\"") (mkLua "hl.dsp.window.move({ workspace = \"special:scratch\" })") ]; }
|
|
||||||
|
|
||||||
# Messaging workspace
|
|
||||||
{ _args = [ (mkLua "mainMod .. \" + M\"") (mkLua "hl.dsp.workspace.toggle_special(\"messaging\")") ]; }
|
|
||||||
|
|
||||||
# Move/resize windows with mouse and dragging
|
|
||||||
{
|
|
||||||
_args = [
|
|
||||||
(mkLua "mainMod .. \" + mouse:272\"")
|
|
||||||
(mkLua "hl.dsp.window.drag()")
|
|
||||||
{ mouse = true; }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
_args = [
|
|
||||||
(mkLua "mainMod .. \" + mouse:273\"")
|
|
||||||
(mkLua "hl.dsp.window.resize()")
|
|
||||||
{ mouse = true; }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
# Function Row Keys
|
|
||||||
{
|
|
||||||
_args = [
|
|
||||||
"XF86AudioRaiseVolume"
|
|
||||||
(mkLua "hl.dsp.exec_cmd(\"pactl -- set-sink-volume @DEFAULT_SINK@ +5%\")")
|
|
||||||
{ repeating = true; }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
_args = [
|
|
||||||
"XF86AudioLowerVolume"
|
|
||||||
(mkLua "hl.dsp.exec_cmd(\"pactl -- set-sink-volume @DEFAULT_SINK@ -5%\")")
|
|
||||||
{ repeating = true; locked = true; }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
_args = [
|
|
||||||
"XF86AudioMute"
|
|
||||||
(mkLua "hl.dsp.exec_cmd(\"pactl -- set-sink-mute @DEFAULT_SINK@ toggle\")")
|
|
||||||
{ repeating = true; locked = true; }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
_args = [
|
|
||||||
"XF86MonBrightnessUp"
|
|
||||||
(mkLua "hl.dsp.exec_cmd(\"brightnessctl set 5%+\")")
|
|
||||||
{ repeating = true; locked = true; }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
_args = [
|
|
||||||
"XF86MonBrightnessDown"
|
|
||||||
(mkLua "hl.dsp.exec_cmd(\"brightnessctl set 5%-\")")
|
|
||||||
{ repeating = true; locked = true; }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
# Lid switch - generically detects the embedded (eDP*) panel on any laptop
|
|
||||||
{
|
|
||||||
_args = [
|
|
||||||
"switch:off:Lid Switch"
|
|
||||||
(mkLua "hl.dsp.exec_cmd(\"m=$(hyprctl monitors | awk '$1==\\\"Monitor\\\"{print $2; exit}'); [ -n \\\"$m\\\" ] && hyprctl keyword monitor \\\"$m, preferred, auto, auto\\\"\")")
|
|
||||||
{ locked = true; }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
_args = [
|
|
||||||
"switch:on:Lid Switch"
|
|
||||||
(mkLua "hl.dsp.exec_cmd(\"m=$(hyprctl monitors | awk '$1==\\\"Monitor\\\"{print $2; exit}'); [ -n \\\"$m\\\" ] && hyprctl keyword monitor \\\"$m, disable\\\"\")")
|
|
||||||
{ locked = true; }
|
|
||||||
];
|
|
||||||
}
|
|
||||||
]
|
|
||||||
# Workspace 1-9 switching and moving
|
|
||||||
++ (lib.concatMap (i: [
|
|
||||||
{
|
|
||||||
_args = [
|
|
||||||
(mkLua "mainMod .. \" + ${toString i}\"")
|
|
||||||
(mkLua "hl.dsp.focus({ workspace = ${toString i} })")
|
|
||||||
];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
_args = [
|
|
||||||
(mkLua "mainMod .. \" + SHIFT + ${toString i}\"")
|
|
||||||
(mkLua "hl.dsp.window.move({ workspace = ${toString i} })")
|
|
||||||
];
|
|
||||||
}
|
|
||||||
]) (lib.range 1 9))
|
|
||||||
# Workspace 10 switching and moving (mapped to key 0)
|
|
||||||
++ [
|
|
||||||
{
|
|
||||||
_args = [
|
|
||||||
(mkLua "mainMod .. \" + 0\"")
|
|
||||||
(mkLua "hl.dsp.focus({ workspace = 10 })")
|
|
||||||
];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
_args = [
|
|
||||||
(mkLua "mainMod .. \" + SHIFT + 0\"")
|
|
||||||
(mkLua "hl.dsp.window.move({ workspace = 10 })")
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
# 8. Workspace Rules (hl.workspace_rule)
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
workspace_rule = [
|
|
||||||
{
|
|
||||||
workspace = "name:messaging";
|
|
||||||
on_created_empty = "[] togglegroup";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
# 9. Window Rules (hl.window_rule)
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
window_rule = [
|
|
||||||
{
|
|
||||||
name = "signal-messaging";
|
|
||||||
match = { class = "Signal"; };
|
|
||||||
workspace = "special:messaging";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "vesktop-messaging";
|
|
||||||
match = { class = "vesktop"; };
|
|
||||||
workspace = "special:messaging";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "ghidra-tile";
|
|
||||||
match = { class = "ghidra-Ghidra"; };
|
|
||||||
float = false;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{ pkgs, ... }:
|
{ theme, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
programs.hyprlock = {
|
programs.hyprlock = {
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
{
|
{
|
||||||
monitor = "";
|
monitor = "";
|
||||||
text = "$TIME";
|
text = "$TIME";
|
||||||
color = "rgba(ffffffdd)";
|
color = "rgba(${theme.bare.textPrimary}dd)";
|
||||||
font_size = 90;
|
font_size = 90;
|
||||||
font_family = "JetBrainsMono Nerd Font";
|
font_family = "JetBrainsMono Nerd Font";
|
||||||
position = "0, 220";
|
position = "0, 220";
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
{
|
{
|
||||||
monitor = "";
|
monitor = "";
|
||||||
text = "cmd[update:1000] echo \" $(date +'%A, %B %d') \"";
|
text = "cmd[update:1000] echo \" $(date +'%A, %B %d') \"";
|
||||||
color = "rgba(adaaaaff)";
|
color = "rgba(${theme.bare.textMuted}ff)";
|
||||||
font_size = 20;
|
font_size = 20;
|
||||||
font_family = "JetBrainsMono Nerd Font";
|
font_family = "JetBrainsMono Nerd Font";
|
||||||
position = "0, 120";
|
position = "0, 120";
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
{
|
{
|
||||||
monitor = "";
|
monitor = "";
|
||||||
text = "NIXOS // SILICON VOLT";
|
text = "NIXOS // SILICON VOLT";
|
||||||
color = "rgba(cffc00ee)";
|
color = "rgba(${theme.bare.accentVolt}ee)";
|
||||||
font_size = 14;
|
font_size = 14;
|
||||||
font_family = "JetBrainsMono Nerd Font";
|
font_family = "JetBrainsMono Nerd Font";
|
||||||
position = "0, 60";
|
position = "0, 60";
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
{
|
{
|
||||||
monitor = "";
|
monitor = "";
|
||||||
text = "cmd[update:3600000] echo \" $USER @ $(hostname) \"";
|
text = "cmd[update:3600000] echo \" $USER @ $(hostname) \"";
|
||||||
color = "rgba(adaaaa99)";
|
color = "rgba(${theme.bare.textMuted}99)";
|
||||||
font_size = 13;
|
font_size = 13;
|
||||||
font_family = "JetBrainsMono Nerd Font";
|
font_family = "JetBrainsMono Nerd Font";
|
||||||
position = "0, -160";
|
position = "0, -160";
|
||||||
@@ -84,11 +84,11 @@
|
|||||||
dots_spacing = 0.2;
|
dots_spacing = 0.2;
|
||||||
dots_center = true;
|
dots_center = true;
|
||||||
# Volt lime outline, dark glass interior
|
# Volt lime outline, dark glass interior
|
||||||
outer_color = "rgba(cffc00ee)";
|
outer_color = "rgba(${theme.bare.accentVolt}ee)";
|
||||||
inner_color = "rgba(1c1b1b, 0.9)";
|
inner_color = "rgba(${theme.bare.glassBg}, 0.9)";
|
||||||
check_color = "rgba(e3ff66ee)";
|
check_color = "rgba(${theme.bare.accentVoltLight}ee)";
|
||||||
fail_color = "rgba(ff7a59ee)";
|
fail_color = "rgba(${theme.bare.accentCoral}ee)";
|
||||||
font_color = "rgba(e5e2e1ff)";
|
font_color = "rgba(${theme.bare.textSecondary}ff)";
|
||||||
fail_text = "$FAIL (<b>attempts: $ATTEMPTS</b>)";
|
fail_text = "$FAIL (<b>attempts: $ATTEMPTS</b>)";
|
||||||
fade_on_empty = false;
|
fade_on_empty = false;
|
||||||
placeholder_text = "<i> Enter Password...</i>";
|
placeholder_text = "<i> Enter Password...</i>";
|
||||||
|
|||||||
@@ -1,25 +1,40 @@
|
|||||||
{ pkgs, inputs, ... }:
|
{
|
||||||
|
pkgs,
|
||||||
|
inputs,
|
||||||
|
config,
|
||||||
|
my,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
quickshellPkg = if (inputs ? quickshell && inputs.quickshell ? packages && inputs.quickshell.packages ? ${pkgs.stdenv.hostPlatform.system})
|
quickshellPkg =
|
||||||
then inputs.quickshell.packages.${pkgs.stdenv.hostPlatform.system}.default
|
if
|
||||||
else (pkgs.quickshell or pkgs.qt6.qtbase);
|
(
|
||||||
|
inputs ? quickshell
|
||||||
|
&& inputs.quickshell ? packages
|
||||||
|
&& inputs.quickshell.packages ? ${pkgs.stdenv.hostPlatform.system}
|
||||||
|
)
|
||||||
|
then
|
||||||
|
inputs.quickshell.packages.${pkgs.stdenv.hostPlatform.system}.default
|
||||||
|
else
|
||||||
|
(pkgs.quickshell or pkgs.qt6.qtbase);
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
home.packages = with pkgs; [
|
# Quickshell overview module for Hyprland (Super+Tab workspace overview).
|
||||||
quickshellPkg
|
# Vendored from: https://github.com/Shanu-Kumawat/quickshell-overview
|
||||||
brightnessctl
|
# Symlinked straight from the repo so edits are live; placed at the
|
||||||
pamixer
|
# quickshell config root so a bare `qs` finds shell.qml.
|
||||||
playerctl
|
xdg.configFile."quickshell".source =
|
||||||
libnotify
|
config.lib.file.mkOutOfStoreSymlink "${my.flakeDir}/home/hypr/quickshell";
|
||||||
grim
|
|
||||||
slurp
|
|
||||||
wl-clipboard
|
|
||||||
];
|
|
||||||
|
|
||||||
# Link Quickshell configuration into ~/.config/quickshell
|
# Run quickshell as a systemd user service tied to the Wayland session.
|
||||||
xdg.configFile."quickshell" = {
|
programs.quickshell = {
|
||||||
source = ./quickshell;
|
enable = true;
|
||||||
recursive = true;
|
package = quickshellPkg;
|
||||||
|
systemd.enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
home.packages = with pkgs; [
|
||||||
|
qt6.qtwayland
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
702
home/hypr/quickshell/README.md
Normal 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.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📸 Preview
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
https://github.com/user-attachments/assets/e8f392d7-d831-4dec-9cd3-fb93d1ccc21c
|
||||||
|
|
||||||
|
> *Workspace overview showing live window previews with drag-and-drop support*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
- 🖼️ Visual workspace overview showing all workspaces and windows
|
||||||
|
- 🖥️ Multi-monitor support with proper scaling and vertical/rotated monitors [in experimental branch]
|
||||||
|
- 📐 Smart row hiding - optionally hide empty workspace rows
|
||||||
|
- 🎯 Click windows to focus them
|
||||||
|
- 🖱️ Middle-click windows to close them
|
||||||
|
- 🔄 Drag and drop windows between workspaces
|
||||||
|
- ⌨️ Keyboard navigation (Arrow keys, vim keys, number shortcuts)
|
||||||
|
- 🖱️ Auto-close on focus loss / outside click
|
||||||
|
- 💡 Hover tooltips showing window information
|
||||||
|
- 🎨 Material Design 3 theming
|
||||||
|
- ⚡ Smooth animations and transitions
|
||||||
|
|
||||||
|
## 📦 Installation
|
||||||
|
|
||||||
|
### Arch Linux (AUR)
|
||||||
|
|
||||||
|
For Arch Linux users, you can install directly from the AUR:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using yay
|
||||||
|
yay -S quickshell-overview-git
|
||||||
|
|
||||||
|
# Using paru
|
||||||
|
paru -S quickshell-overview-git
|
||||||
|
```
|
||||||
|
|
||||||
|
On AUR installs, module files are package-managed under:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/etc/xdg/quickshell/overview/
|
||||||
|
```
|
||||||
|
|
||||||
|
Put your custom settings in:
|
||||||
|
|
||||||
|
```text
|
||||||
|
~/.config/quickshell/overview/config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Then add the keybind and auto-start to your Hyprland config (see Setup steps 2-4 below).
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- **Hyprland** compositor
|
||||||
|
- **Quickshell** ([installation guide](https://quickshell.org/docs/v0.1.0/guide/install-setup/))
|
||||||
|
- **Qt 6** with modules: QtQuick, QtQuick.Controls
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
1. **Install module files** (choose one):
|
||||||
|
- **Git clone (manual install):**
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/Shanu-Kumawat/quickshell-overview ~/.config/quickshell/overview
|
||||||
|
```
|
||||||
|
- **AUR package:** use the command above (`yay -S quickshell-overview-git` or `paru -S ...`)
|
||||||
|
|
||||||
|
2. **Add keybind** to your Hyprland config:
|
||||||
|
|
||||||
|
*For Hyprland 0.55+ (`~/.config/hypr/hyprland.lua`):*
|
||||||
|
```lua
|
||||||
|
hl.bind("SUPER + TAB", hl.dsp.exec_cmd("qs ipc -c overview call overview toggle"))
|
||||||
|
```
|
||||||
|
*For Hyprland 0.54 and older (`~/.config/hypr/hyprland.conf`):*
|
||||||
|
```conf
|
||||||
|
bind = Super, TAB, exec, qs ipc -c overview call overview toggle
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Auto-start** the overview (add to Hyprland config):
|
||||||
|
|
||||||
|
*For Hyprland 0.55+ (`~/.config/hypr/hyprland.lua`):*
|
||||||
|
```lua
|
||||||
|
hl.on("hyprland.start", function ()
|
||||||
|
hl.exec_cmd("qs -c overview")
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
*For Hyprland 0.54 and older (`~/.config/hypr/hyprland.conf`):*
|
||||||
|
```conf
|
||||||
|
exec-once = qs -c overview
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Reload Hyprland**:
|
||||||
|
```bash
|
||||||
|
hyprctl reload
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Start (if needed)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
qs -c overview &
|
||||||
|
```
|
||||||
|
|
||||||
|
### NixOS
|
||||||
|
|
||||||
|
For NixOS users, ensure Quickshell has access to required Qt6 modules:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# In your configuration.nix or home-manager config
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
quickshell
|
||||||
|
qt6.qtwayland
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're using home-manager:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
home.packages = with pkgs; [
|
||||||
|
quickshell
|
||||||
|
qt6.qtwayland
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎮 Usage
|
||||||
|
|
||||||
|
| Action | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| **Super + Tab** | Toggle the overview |
|
||||||
|
| **Arrow Keys / h/l** | Navigate left/right within current row* |
|
||||||
|
| **Up/Down / j/k** | Navigate between workspace rows |
|
||||||
|
| **1-9, 0** | Jump to Nth workspace in current group (0 = 10th) |
|
||||||
|
| **Mouse wheel on grid** | Move across all normal workspaces, wrapping from last to first |
|
||||||
|
| **Escape / Enter** | Close the overview |
|
||||||
|
| **Click outside overview** | Close the overview when `overview.closeOnFocusLoss` is enabled (default) |
|
||||||
|
| **Click workspace** | Switch to that workspace |
|
||||||
|
| **Click window** | Focus that window |
|
||||||
|
| **Middle-click window** | Close that window |
|
||||||
|
| **Drag window** | Move window to different workspace |
|
||||||
|
|
||||||
|
> *When `hideEmptyRows` is enabled, left/right navigation wraps within the current visible row for better UX
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Configuration
|
||||||
|
|
||||||
|
> **⚠️ Want to change size, position, workspace count, or toggles?**
|
||||||
|
> Create/edit `~/.config/quickshell/overview/config.json`.
|
||||||
|
|
||||||
|
`Config.qml` inside the module is now treated as defaults. User overrides are read from:
|
||||||
|
|
||||||
|
- `$XDG_CONFIG_HOME/quickshell/overview/config.json`
|
||||||
|
- fallback: `~/.config/quickshell/overview/config.json`
|
||||||
|
|
||||||
|
> **Note:** After editing `config.json`, manually restart overview for changes to apply:
|
||||||
|
> `qs ipc -c overview call overview close && qs -c overview`
|
||||||
|
|
||||||
|
### Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.config/quickshell/overview
|
||||||
|
cp /etc/xdg/quickshell/overview/config.example.json ~/.config/quickshell/overview/config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
If you installed from git clone instead of AUR, copy from your repo path:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp ~/.config/quickshell/overview/config.example.json ~/.config/quickshell/overview/config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Workspace Grid
|
||||||
|
|
||||||
|
Edit `~/.config/quickshell/overview/config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"overview": {
|
||||||
|
"rows": 2,
|
||||||
|
"columns": 5,
|
||||||
|
"scale": 0.16,
|
||||||
|
"enable": true,
|
||||||
|
"hideEmptyRows": true,
|
||||||
|
"closeOnFocusLoss": true,
|
||||||
|
"useWorkspaceMap": false,
|
||||||
|
"workspaceMap": [0, 10],
|
||||||
|
"orderRightLeft": false,
|
||||||
|
"orderBottomUp": false,
|
||||||
|
"previewsEnabled": true,
|
||||||
|
"previewMode": "live",
|
||||||
|
"includeInactiveMonitorPreviews": true,
|
||||||
|
"previewRecaptureDelayMs": 60,
|
||||||
|
"showSpecialWorkspaces": true,
|
||||||
|
"specialWorkspaces": [],
|
||||||
|
"specialWorkspaceColumns": 5,
|
||||||
|
"emptyWorkspaceWallpaper": "",
|
||||||
|
"specialEmptyWorkspaceWallpaper": "",
|
||||||
|
"effects": {
|
||||||
|
"enableBackdrop": false,
|
||||||
|
"backdropOpacity": 0.28,
|
||||||
|
"panelOpacity": 0.92,
|
||||||
|
"workspaceOpacity": 0.86,
|
||||||
|
"emptyWorkspaceWallpaperOverlayOpacity": 0.18,
|
||||||
|
"windowOverlayOpacity": 0.22,
|
||||||
|
"enableBlur": false,
|
||||||
|
"glassMode": false,
|
||||||
|
"glassTintStrength": 0.35,
|
||||||
|
"glassBorderOpacity": 0.72,
|
||||||
|
"glassShineOpacity": 0.14
|
||||||
|
},
|
||||||
|
"workspaceSpacing": 5,
|
||||||
|
"backgroundPadding": 10,
|
||||||
|
"workspaceNumberBaseSize": 250
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Common adjustments:**
|
||||||
|
- **Too small?** Increase `scale` (try 0.20 or 0.25)
|
||||||
|
- **Too big?** Decrease `scale` (try 0.12 or 0.14)
|
||||||
|
- **More workspaces?** Change `rows` and `columns` (e.g., 3 rows × 4 columns = 12 workspaces)
|
||||||
|
- **Reverse order?** Set `orderRightLeft` and/or `orderBottomUp` to `true`
|
||||||
|
- **Prefer the overview to stay open after outside clicks/focus changes?** Set `closeOnFocusLoss` to `false`
|
||||||
|
- **Per-monitor workspace groups?** Enable `useWorkspaceMap` and set `workspaceMap` (e.g. `[0,10]`)
|
||||||
|
- **Show special workspaces below grid?** Keep `showSpecialWorkspaces: true` and optionally prefill `specialWorkspaces`
|
||||||
|
- **Lower memory use?** Set `previewMode` to `event` and `includeInactiveMonitorPreviews` to `false`
|
||||||
|
- **Transparency / blur?** Tune `overview.effects.*` (details below)
|
||||||
|
|
||||||
|
**Hide empty workspace rows:**
|
||||||
|
- Set `hideEmptyRows: true` to automatically hide rows that have no windows
|
||||||
|
- Keeps your overview clean by only showing rows with active workspaces
|
||||||
|
- The current workspace row is always visible, even if empty
|
||||||
|
- Arrow key navigation (left/right) stays within the current row when enabled
|
||||||
|
- Great for 2-row setups where you rarely use workspaces 6-10
|
||||||
|
|
||||||
|
**Close on focus loss / outside click:**
|
||||||
|
- `closeOnFocusLoss` defaults to `true`
|
||||||
|
- When enabled, clicking outside the overview closes it, similar to menus, dropdowns, and launchers
|
||||||
|
- The overview also closes when its Hyprland focus grab is cleared
|
||||||
|
- Set `closeOnFocusLoss: false` if you want the previous behavior where the overview can remain open after focus changes
|
||||||
|
|
||||||
|
### Position
|
||||||
|
|
||||||
|
Edit `~/.config/quickshell/overview/config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"position": {
|
||||||
|
"topMargin": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Increase `topMargin` to move the overview down. Decrease it to move up.
|
||||||
|
|
||||||
|
### Window Preview
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"windowPreview": {
|
||||||
|
"showIcons": true,
|
||||||
|
"iconToWindowRatio": 0.25,
|
||||||
|
"iconToWindowRatioCompact": 0.45,
|
||||||
|
"xwaylandIndicatorToIconRatio": 0.35,
|
||||||
|
"inactiveMonitorOpacity": 0.4,
|
||||||
|
"cropToFill": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `cropToFill`: crop full-screen windows to fill the workspace preview when `true`; keep the full window in preview with possible horizontal/vertical "padding" bars when `false`
|
||||||
|
|
||||||
|
### Performance Tuning
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"overview": {
|
||||||
|
"previewsEnabled": true,
|
||||||
|
"previewMode": "live",
|
||||||
|
"includeInactiveMonitorPreviews": true,
|
||||||
|
"previewRecaptureDelayMs": 60
|
||||||
|
},
|
||||||
|
"hacks": {
|
||||||
|
"hyprlandEventDebounceMs": 40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `overview.previewsEnabled`: turn all window screencopy previews on/off
|
||||||
|
- `overview.previewMode`: `live` (best visuals, more RAM) or `event` (lower RAM, refreshes on window events)
|
||||||
|
- `overview.includeInactiveMonitorPreviews`: when `false`, only current monitor windows get preview capture
|
||||||
|
- `overview.previewRecaptureDelayMs`: delay used for event-mode snapshot refresh (lower = faster updates)
|
||||||
|
- `hacks.hyprlandEventDebounceMs`: coalesces Hyprland event refreshes to reduce command churn
|
||||||
|
|
||||||
|
### Special Workspaces
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"overview": {
|
||||||
|
"showSpecialWorkspaces": true,
|
||||||
|
"specialWorkspaces": ["stash", "music", "scratch"],
|
||||||
|
"specialWorkspaceColumns": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `showSpecialWorkspaces`: renders special workspaces in a strip under the normal grid
|
||||||
|
- `specialWorkspaces`: optional preconfigured special workspace names (without the `special:` prefix)
|
||||||
|
- `specialWorkspaceColumns`: how many special tiles per row before wrapping
|
||||||
|
- `emptyWorkspaceWallpaper`: optional image path used as the background for normal workspace tiles
|
||||||
|
- `specialEmptyWorkspaceWallpaper`: optional image path used as the background for special workspace tiles
|
||||||
|
|
||||||
|
Interaction behavior:
|
||||||
|
- Preconfigured special workspaces appear in the overview even when they are empty
|
||||||
|
- The special strip shows active special workspaces plus any names you preconfigure
|
||||||
|
- This is useful for fixed workflows like `stash`, `music`, or `scratch`
|
||||||
|
- Click a special tile to run `togglespecialworkspace <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:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 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>
|
||||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
359
home/hypr/quickshell/common/Appearance.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
194
home/hypr/quickshell/common/Config.qml
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
68
home/hypr/quickshell/common/functions/ColorUtils.qml
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
1
home/hypr/quickshell/common/functions/qmldir
Normal file
@@ -0,0 +1 @@
|
|||||||
|
singleton ColorUtils 1.0 ColorUtils.qml
|
||||||
7
home/hypr/quickshell/common/qmldir
Normal 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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
16
home/hypr/quickshell/common/widgets/StyledText.qml
Normal 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"
|
||||||
|
}
|
||||||
23
home/hypr/quickshell/common/widgets/StyledToolTip.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
49
home/hypr/quickshell/common/widgets/StyledToolTipContent.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
home/hypr/quickshell/common/widgets/qmldir
Normal 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
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
96
home/hypr/quickshell/config.example.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
42
home/hypr/quickshell/config.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
252
home/hypr/quickshell/modules/overview/Overview.qml
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1214
home/hypr/quickshell/modules/overview/OverviewWidget.qml
Normal file
264
home/hypr/quickshell/modules/overview/OverviewWindow.qml
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
3
home/hypr/quickshell/modules/overview/qmldir
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Overview 1.0 Overview.qml
|
||||||
|
OverviewWidget 1.0 OverviewWidget.qml
|
||||||
|
OverviewWindow 1.0 OverviewWindow.qml
|
||||||
@@ -1 +0,0 @@
|
|||||||
singleton GlobalState 1.0 GlobalState.qml
|
|
||||||
41
home/hypr/quickshell/quickshell-overview.qml
Normal 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"
|
||||||
|
}
|
||||||
@@ -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"
|
|
||||||
@@ -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}"
|
|
||||||
@@ -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
|
|
||||||
@@ -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}}"
|
|
||||||
@@ -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"
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
11
home/hypr/quickshell/services/GlobalStates.qml
Normal 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
|
||||||
|
}
|
||||||
209
home/hypr/quickshell/services/HyprlandData.qml
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
home/hypr/quickshell/services/qmldir
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
singleton HyprlandData 1.0 HyprlandData.qml
|
||||||
|
singleton GlobalStates 1.0 GlobalStates.qml
|
||||||
@@ -1,84 +1,25 @@
|
|||||||
|
//@ pragma UseQApplication
|
||||||
|
//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic
|
||||||
|
//@ pragma Env QT_QPA_PLATFORMTHEME=
|
||||||
|
|
||||||
|
import "./modules/overview/"
|
||||||
|
import "./services/"
|
||||||
|
import "./common/"
|
||||||
|
import "./common/functions/"
|
||||||
|
import "./common/widgets/"
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Hyprland
|
||||||
import "theme"
|
|
||||||
import "components"
|
|
||||||
import "."
|
|
||||||
|
|
||||||
Scope {
|
ShellRoot {
|
||||||
id: root
|
Connections {
|
||||||
|
target: Quickshell
|
||||||
|
|
||||||
// =========================================================================
|
function onReloadCompleted() {
|
||||||
// IPC HANDLERS (Allows controlling shell via `qs ipc call <target> <method>`)
|
Quickshell.inhibitReloadPopup();
|
||||||
// =========================================================================
|
|
||||||
IpcHandler {
|
|
||||||
target: "spotlight"
|
|
||||||
function toggle() {
|
|
||||||
GlobalState.toggleSpotlight();
|
|
||||||
}
|
|
||||||
function open() {
|
|
||||||
GlobalState.spotlightVisible = true;
|
|
||||||
}
|
|
||||||
function close() {
|
|
||||||
GlobalState.spotlightVisible = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcHandler {
|
Overview {}
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
singleton Theme 1.0 Theme.qml
|
|
||||||
37
home/hypr/services.nix
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
# The Wayland session target used by the home-manager Wayland modules
|
||||||
|
# (resolves to graphical-session.target). start-hyprland activates it.
|
||||||
|
sessionTarget = config.wayland.systemd.target;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# PolicyKit agent (replaces the old hl.exec_cmd autostart).
|
||||||
|
services.hyprpolkitagent.enable = true;
|
||||||
|
|
||||||
|
# Clipboard history. The module runs both the text and image watchers as
|
||||||
|
# systemd user services and adds the cliphist package.
|
||||||
|
services.cliphist.enable = true;
|
||||||
|
|
||||||
|
# mako has no systemd service in home-manager, so declare one here.
|
||||||
|
# Configuration lives next to the rest of the desktop theme in default.nix.
|
||||||
|
systemd.user.services.mako = {
|
||||||
|
Unit = {
|
||||||
|
Description = "mako notification daemon";
|
||||||
|
After = [ sessionTarget ];
|
||||||
|
PartOf = [ sessionTarget ];
|
||||||
|
};
|
||||||
|
|
||||||
|
Service = {
|
||||||
|
ExecStart = "${pkgs.mako}/bin/mako";
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = 2;
|
||||||
|
};
|
||||||
|
|
||||||
|
Install.WantedBy = [ sessionTarget ];
|
||||||
|
};
|
||||||
|
}
|
||||||
39
home/nvim/config/lazy-lock.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"LazyVim": { "branch": "main", "commit": "c10948c50b18fae7f256433afdef09e432410480" },
|
||||||
|
"SchemaStore.nvim": { "branch": "main", "commit": "7e3f6dd6ef6da93f74974521c81d8d5bff0443c5" },
|
||||||
|
"blink.cmp": { "branch": "main", "commit": "78336bc89ee5365633bcf754d93df01678b5c08f" },
|
||||||
|
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
|
||||||
|
"catppuccin": { "branch": "main", "commit": "edefef779ab08ce1a4a404713e3012b0d202bd35" },
|
||||||
|
"conform.nvim": { "branch": "master", "commit": "016802de402556da54c36bd7359b441266b01cdd" },
|
||||||
|
"crates.nvim": { "branch": "main", "commit": "694357861ec9ebf12475ddcdd04ea45a0923c32d" },
|
||||||
|
"flash.nvim": { "branch": "main", "commit": "b6346946d10d07998efee029fb0f7a593806d0cd" },
|
||||||
|
"friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" },
|
||||||
|
"gitsigns.nvim": { "branch": "main", "commit": "5be654f2232c10ddcad19c1607a67b6b4b78fc29" },
|
||||||
|
"grug-far.nvim": { "branch": "main", "commit": "11595bf747edc270bce2069d1020502ad4ae56cf" },
|
||||||
|
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
|
||||||
|
"lazydev.nvim": { "branch": "main", "commit": "ff2cbcba459b637ec3fd165a2be59b7bbaeedf0d" },
|
||||||
|
"lualine.nvim": { "branch": "master", "commit": "221ce6b2d999187044529f49da6554a92f740a96" },
|
||||||
|
"mason-lspconfig.nvim": { "branch": "main", "commit": "67029ccdac1ef8941e13b826417bc0ffac24cc86" },
|
||||||
|
"mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" },
|
||||||
|
"mini.ai": { "branch": "main", "commit": "25248c6aa002391936a6200f12d1466015987133" },
|
||||||
|
"mini.icons": { "branch": "main", "commit": "98faae31e9be1cc054ae63485e58ceb185efcad0" },
|
||||||
|
"mini.pairs": { "branch": "main", "commit": "b1c5a726921b7a8c9321e9a7a208aa0571de5810" },
|
||||||
|
"noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" },
|
||||||
|
"nui.nvim": { "branch": "main", "commit": "a25dd472316d806eb38a5dd667c2e40da5570c75" },
|
||||||
|
"nvim-lint": { "branch": "master", "commit": "9b99aad869102ac01775187abd8d49017b148612" },
|
||||||
|
"nvim-lspconfig": { "branch": "master", "commit": "51dbf5359da86721662c87ca10eb73add973737b" },
|
||||||
|
"nvim-treesitter": { "branch": "main", "commit": "074aa4422bf029908338e855d0c0f71470a971bb" },
|
||||||
|
"nvim-treesitter-textobjects": { "branch": "main", "commit": "898ee307df58f854d11cd7edd06472574d48014e" },
|
||||||
|
"nvim-ts-autotag": { "branch": "main", "commit": "88c1453db4ba7dd24131086fe51fdf74e587d275" },
|
||||||
|
"persistence.nvim": { "branch": "main", "commit": "b20b2a7887bd39c1a356980b45e03250f3dce49c" },
|
||||||
|
"plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" },
|
||||||
|
"rustaceanvim": { "branch": "main", "commit": "f659765f93e207b8c44ee4a90eb88bd6e93bf05f" },
|
||||||
|
"snacks.nvim": { "branch": "main", "commit": "882c996cf28183f4d63640de0b4c02ec886d01f2" },
|
||||||
|
"telescope.nvim": { "branch": "master", "commit": "427b576c16792edad01a92b89721d923c19ad60f" },
|
||||||
|
"todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" },
|
||||||
|
"tokyonight.nvim": { "branch": "main", "commit": "cdc07ac78467a233fd62c493de29a17e0cf2b2b6" },
|
||||||
|
"trouble.nvim": { "branch": "main", "commit": "bd67efe408d4816e25e8491cc5ad4088e708a69a" },
|
||||||
|
"ts-comments.nvim": { "branch": "main", "commit": "a59d6092213447450191122c9346f309161504cb" },
|
||||||
|
"venv-selector.nvim": { "branch": "main", "commit": "cc4bb3975de8835291f9bb45889e96c6b2795fc4" },
|
||||||
|
"which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
|
||||||
|
}
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
{ config, pkgs, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
my,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
treesitterParsers = pkgs.symlinkJoin {
|
treesitterParsers = pkgs.symlinkJoin {
|
||||||
@@ -61,15 +67,20 @@ in
|
|||||||
];
|
];
|
||||||
|
|
||||||
extraWrapperArgs = [
|
extraWrapperArgs = [
|
||||||
"--set" "NVIM_TREESITTER_PARSERS" "${treesitterParsers}"
|
"--set"
|
||||||
|
"NVIM_TREESITTER_PARSERS"
|
||||||
|
"${treesitterParsers}"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
# Symlink LazyVim configuration to ~/.config/nvim recursively
|
# Live LazyVim config symlinked straight from the repo: edits (and
|
||||||
xdg.configFile."nvim" = {
|
# `:Lazy update` writes to lazy-lock.json) apply without a rebuild.
|
||||||
source = ./config;
|
xdg.configFile."nvim/init.lua".source =
|
||||||
recursive = true;
|
config.lib.file.mkOutOfStoreSymlink "${my.flakeDir}/home/nvim/config/init.lua";
|
||||||
};
|
xdg.configFile."nvim/lua".source =
|
||||||
|
config.lib.file.mkOutOfStoreSymlink "${my.flakeDir}/home/nvim/config/lua";
|
||||||
|
xdg.configFile."nvim/lazy-lock.json".source =
|
||||||
|
config.lib.file.mkOutOfStoreSymlink "${my.flakeDir}/home/nvim/config/lazy-lock.json";
|
||||||
|
|
||||||
# Symlink precompiled Treesitter parsers directly into ~/.config/nvim/parser
|
# Symlink precompiled Treesitter parsers directly into ~/.config/nvim/parser
|
||||||
xdg.configFile."nvim/parser".source = "${treesitterParsers}/parser";
|
xdg.configFile."nvim/parser".source = "${treesitterParsers}/parser";
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
{ inputs, config, ... }:
|
{ inputs, my, ... }:
|
||||||
{
|
{
|
||||||
imports = [ inputs.pi.homeModules.default ];
|
imports = [ inputs.pi.homeModules.default ];
|
||||||
programs.pi.coding-agent = {
|
programs.pi.coding-agent.enable = my.pi.enable;
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,55 @@
|
|||||||
{ pkgs, ... }:
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
theme,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
# Rofi script-mode indexer: exposes every executable user script
|
# Rofi script-mode indexer: exposes every executable user script
|
||||||
# (shell, python, whatever) so the launcher finds "everything".
|
# (shell, python, whatever) so the launcher finds "everything".
|
||||||
rofiScripts = pkgs.writeShellScriptBin "rofi-scripts"
|
rofiScripts = pkgs.writeShellScriptBin "rofi-scripts" (builtins.readFile ./rofi/rofi-scripts.sh);
|
||||||
(builtins.readFile ./rofi/rofi-scripts.sh);
|
|
||||||
|
# Inject the shared palette into the rasi theme (see home/theme.nix).
|
||||||
|
# `/. +` turns the generated derivation into a path, which the rofi module
|
||||||
|
# treats as a theme file to link into its data directory.
|
||||||
|
rofiThemeText = pkgs.writeText "volt.rasi" (
|
||||||
|
lib.replaceStrings
|
||||||
|
[
|
||||||
|
"@bg0@"
|
||||||
|
"@bg1@"
|
||||||
|
"@fg0@"
|
||||||
|
"@fg1@"
|
||||||
|
"@fg2@"
|
||||||
|
"@volt@"
|
||||||
|
"@voltDim@"
|
||||||
|
"@coral@"
|
||||||
|
"@border0@"
|
||||||
|
]
|
||||||
|
[
|
||||||
|
"${theme.colors.bgSecondary}f2"
|
||||||
|
"${theme.colors.glassBg}f2"
|
||||||
|
theme.colors.textSecondary
|
||||||
|
theme.colors.textPrimary
|
||||||
|
theme.colors.textMuted
|
||||||
|
theme.colors.accentVolt
|
||||||
|
"${theme.colors.accentVolt}1a"
|
||||||
|
theme.colors.accentCoral
|
||||||
|
"${theme.colors.textPrimary}14"
|
||||||
|
]
|
||||||
|
(builtins.readFile ./rofi/volt.rasi)
|
||||||
|
);
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
# Install the generated theme under the rofi theme search path and select
|
||||||
|
# it by name (passing a derivation directly is treated as a rasi attrset).
|
||||||
|
xdg.dataFile."rofi/themes/volt.rasi".source = rofiThemeText;
|
||||||
|
|
||||||
programs.rofi = {
|
programs.rofi = {
|
||||||
enable = true;
|
enable = true;
|
||||||
terminal = "${pkgs.foot}/bin/foot";
|
terminal = "${pkgs.foot}/bin/foot";
|
||||||
cycle = true;
|
cycle = true;
|
||||||
theme = ./rofi/volt.rasi;
|
theme = "volt";
|
||||||
|
|
||||||
extraConfig = {
|
extraConfig = {
|
||||||
# Combi mode searches .desktop apps (drun), binaries (run),
|
# Combi mode searches .desktop apps (drun), binaries (run),
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
// Flat brutalist-glass: dark glass surfaces, volt-lime selection, 0 radius.
|
// Flat brutalist-glass: dark glass surfaces, volt-lime selection, 0 radius.
|
||||||
|
|
||||||
* {
|
* {
|
||||||
bg0: rgba( 14, 14, 14, 95 % );
|
bg0: @bg0@;
|
||||||
bg1: rgba( 28, 27, 27, 95 % );
|
bg1: @bg1@;
|
||||||
fg0: #e5e2e1;
|
fg0: @fg0@;
|
||||||
fg1: #ffffff;
|
fg1: @fg1@;
|
||||||
fg2: #adaaaa;
|
fg2: @fg2@;
|
||||||
volt: #CFFC00;
|
volt: @volt@;
|
||||||
volt-dim: rgba( 207, 252, 0, 10 % );
|
volt-dim: @voltDim@;
|
||||||
coral: #FF7A59;
|
coral: @coral@;
|
||||||
border0: rgba( 255, 255, 255, 8 % );
|
border0: @border0@;
|
||||||
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
text-color: @fg0;
|
text-color: @fg0;
|
||||||
|
|||||||
@@ -48,9 +48,9 @@ if command -v fzf &>/dev/null; then
|
|||||||
--layout=reverse \
|
--layout=reverse \
|
||||||
--border=rounded \
|
--border=rounded \
|
||||||
--prompt=" Herdr Session > " \
|
--prompt=" Herdr Session > " \
|
||||||
--color=fg:#e5e2e1,bg:#131313,hl:#CFFC00,fg+:#ffffff,bg+:#1c1b1b,hl+:#e3ff66 \
|
--color=fg:@fg@,bg:@bg@,hl:@volt@,fg+:@fgBright@,bg+:@glass@,hl+:@voltLight@ \
|
||||||
--color=info:#ADAAAA,prompt:#CFFC00,pointer:#FF7A59,marker:#CFFC00,spinner:#00BFFF \
|
--color=info:@muted@,prompt:@volt@,pointer:@coral@,marker:@volt@,spinner:@blue@ \
|
||||||
--color=border:#2a2929,gutter:#131313 \
|
--color=border:@surface@,gutter:@bg@ \
|
||||||
--header="Select Herdr session to attach / create | Esc for plain shell" \
|
--header="Select Herdr session to attach / create | Esc for plain shell" \
|
||||||
--header-first)
|
--header-first)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -6,49 +6,59 @@ format = "$os$directory$git_branch$git_status$nix_shell$rust$c$python$cmd_durati
|
|||||||
|
|
||||||
[os]
|
[os]
|
||||||
disabled = false
|
disabled = false
|
||||||
style = "bold #e5e2e1"
|
style = "bold @text@"
|
||||||
symbols = { NixOS = " " }
|
symbols = { NixOS = " " }
|
||||||
|
|
||||||
[directory]
|
[directory]
|
||||||
truncation_length = 3
|
truncation_length = 3
|
||||||
truncate_to_repo = true
|
truncate_to_repo = true
|
||||||
style = "bold #CFFC00"
|
style = "bold @volt@"
|
||||||
format = "[$path]($style) "
|
format = "[ $path]($style) "
|
||||||
|
|
||||||
[git_branch]
|
[git_branch]
|
||||||
symbol = " "
|
symbol = " "
|
||||||
style = "bold #00BFFF"
|
style = "bold @blue@"
|
||||||
format = "on [$symbol$branch]($style) "
|
format = "on [$symbol$branch]($style) "
|
||||||
|
|
||||||
[git_status]
|
[git_status]
|
||||||
style = "bold #FF7A59"
|
style = "bold @coral@"
|
||||||
format = "([$all_status$ahead_behind]($style) )"
|
format = "([$all_status$ahead_behind]($style) )"
|
||||||
|
conflicted = " "
|
||||||
|
ahead = " "
|
||||||
|
behind = " "
|
||||||
|
diverged = " "
|
||||||
|
untracked = " "
|
||||||
|
stashed = " "
|
||||||
|
modified = " "
|
||||||
|
staged = " [](bold @volt@)"
|
||||||
|
renamed = " "
|
||||||
|
deleted = " "
|
||||||
|
|
||||||
[nix_shell]
|
[nix_shell]
|
||||||
symbol = " "
|
symbol = " "
|
||||||
style = "bold #e3ff66"
|
style = "bold @voltLight@"
|
||||||
format = "via [$symbol$state]($style) "
|
format = "via [$symbol$state]($style) "
|
||||||
|
|
||||||
[rust]
|
[rust]
|
||||||
symbol = " "
|
symbol = " "
|
||||||
style = "bold #e3ff66"
|
style = "bold @voltLight@"
|
||||||
format = "[$symbol($version )]($style)"
|
format = "[$symbol($version )]($style)"
|
||||||
|
|
||||||
[c]
|
[c]
|
||||||
symbol = " "
|
symbol = " "
|
||||||
style = "bold #00BFFF"
|
style = "bold @blue@"
|
||||||
format = "[$symbol($version )]($style)"
|
format = "[$symbol($version )]($style)"
|
||||||
|
|
||||||
[python]
|
[python]
|
||||||
symbol = " "
|
symbol = " "
|
||||||
style = "bold #CFFC00"
|
style = "bold @volt@"
|
||||||
format = "[$symbol($version )]($style)"
|
format = "[$symbol($version )]($style)"
|
||||||
|
|
||||||
[cmd_duration]
|
[cmd_duration]
|
||||||
min_time = 2000
|
min_time = 2000
|
||||||
style = "bold #FF7A59"
|
style = "bold @coral@"
|
||||||
format = "took [$duration]($style) "
|
format = "[ $duration]($style) "
|
||||||
|
|
||||||
[character]
|
[character]
|
||||||
success_symbol = "[➜](bold #CFFC00) "
|
success_symbol = "[❯ ](bold @volt@) "
|
||||||
error_symbol = "[➜](bold #FF7A59) "
|
error_symbol = "[❯ ](bold @coral@) "
|
||||||
|
|||||||
36
home/theme.nix
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Silicon Volt palette. Single source of truth for the shared home-manager
|
||||||
|
# configuration. This is a plain attrset expression, not a home-manager module:
|
||||||
|
# it is passed to every home module through `extraSpecialArgs`.
|
||||||
|
#
|
||||||
|
# theme.colors.<name> -> "#rrggbb" (most tools)
|
||||||
|
# theme.bare.<name> -> "rrggbb" (foot and other bare-hex tools)
|
||||||
|
let
|
||||||
|
colors = {
|
||||||
|
bgPrimary = "#131313";
|
||||||
|
bgSecondary = "#0e0e0e";
|
||||||
|
glassBg = "#1c1b1b";
|
||||||
|
surfaceVariant = "#2a2929";
|
||||||
|
textPrimary = "#ffffff";
|
||||||
|
textSecondary = "#e5e2e1";
|
||||||
|
textMuted = "#adaaaa";
|
||||||
|
accentVolt = "#cffc00";
|
||||||
|
accentVoltLight = "#e3ff66";
|
||||||
|
accentCoral = "#ff7a59";
|
||||||
|
accentBlue = "#00bfff";
|
||||||
|
|
||||||
|
# Foot-specific ANSI shades
|
||||||
|
neutralGray = "#c6c6c7";
|
||||||
|
lightCyan = "#66d9ff";
|
||||||
|
brightCoral = "#ff9a82";
|
||||||
|
brightLime = "#dfff66";
|
||||||
|
brightVolt = "#f0ff8c";
|
||||||
|
brightGray = "#e0e0e1";
|
||||||
|
brightCyan = "#99e6ff";
|
||||||
|
};
|
||||||
|
|
||||||
|
stripHash = c: builtins.substring 1 (builtins.stringLength c) c;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit colors;
|
||||||
|
bare = builtins.mapAttrs (_: stripHash) colors;
|
||||||
|
}
|
||||||
26
home/zsh.nix
@@ -2,6 +2,8 @@
|
|||||||
config,
|
config,
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
|
theme,
|
||||||
|
my,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
|
||||||
@@ -23,9 +25,9 @@
|
|||||||
enableZshIntegration = true;
|
enableZshIntegration = true;
|
||||||
defaultOptions = [
|
defaultOptions = [
|
||||||
"--color=dark"
|
"--color=dark"
|
||||||
"--color=fg:#e5e2e1,bg:-1,hl:#CFFC00,fg+:#ffffff,bg+:#1c1b1b,hl+:#e3ff66"
|
"--color=fg:${theme.colors.textSecondary},bg:-1,hl:${theme.colors.accentVolt},fg+:${theme.colors.textPrimary},bg+:${theme.colors.glassBg},hl+:${theme.colors.accentVoltLight}"
|
||||||
"--color=info:#ADAAAA,prompt:#CFFC00,pointer:#FF7A59,marker:#CFFC00,spinner:#00BFFF,header:#00BFFF"
|
"--color=info:${theme.colors.textMuted},prompt:${theme.colors.accentVolt},pointer:${theme.colors.accentCoral},marker:${theme.colors.accentVolt},spinner:${theme.colors.accentBlue},header:${theme.colors.accentBlue}"
|
||||||
"--color=border:#2a2929,bg+:-1,gutter:-1"
|
"--color=border:${theme.colors.surfaceVariant},bg+:-1,gutter:-1"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -41,7 +43,18 @@
|
|||||||
programs.starship = {
|
programs.starship = {
|
||||||
enable = true;
|
enable = true;
|
||||||
enableZshIntegration = true;
|
enableZshIntegration = true;
|
||||||
settings = builtins.fromTOML (builtins.readFile ./starship/starship.toml);
|
settings = builtins.fromTOML (
|
||||||
|
lib.replaceStrings
|
||||||
|
[ "@text@" "@volt@" "@blue@" "@coral@" "@voltLight@" ]
|
||||||
|
[
|
||||||
|
theme.colors.textSecondary
|
||||||
|
theme.colors.accentVolt
|
||||||
|
theme.colors.accentBlue
|
||||||
|
theme.colors.accentCoral
|
||||||
|
theme.colors.accentVoltLight
|
||||||
|
]
|
||||||
|
(builtins.readFile ./starship/starship.toml)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
programs.zsh = {
|
programs.zsh = {
|
||||||
@@ -95,6 +108,7 @@
|
|||||||
ZSH_TMUX_FIXTERM = "true";
|
ZSH_TMUX_FIXTERM = "true";
|
||||||
GPG_TTY = "$(tty)";
|
GPG_TTY = "$(tty)";
|
||||||
PNPM_HOME = "$HOME/.local/share/pnpm";
|
PNPM_HOME = "$HOME/.local/share/pnpm";
|
||||||
|
FLAKE_CONFIG = my.flakeDir;
|
||||||
};
|
};
|
||||||
|
|
||||||
shellAliases = {
|
shellAliases = {
|
||||||
@@ -104,8 +118,8 @@
|
|||||||
l = "ls -al";
|
l = "ls -al";
|
||||||
clear = "clear -x";
|
clear = "clear -x";
|
||||||
rm = "rm -I";
|
rm = "rm -I";
|
||||||
zshconf = "$EDITOR $HOME/NixOS-Configs/home/zsh.nix";
|
zshconf = "$EDITOR ${my.flakeDir}/home/zsh.nix";
|
||||||
hyprconf = "$EDITOR $HOME/NixOS-Configs/home/hypr/hyprland.nix";
|
hyprconf = "$EDITOR ${my.flakeDir}/home/hypr/hyprland.nix";
|
||||||
setup = "source ./setup_env.sh";
|
setup = "source ./setup_env.sh";
|
||||||
gpuenv = "__NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia";
|
gpuenv = "__NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia";
|
||||||
digital = "$HOME/Tools/Digital/Digital.sh";
|
digital = "$HOME/Tools/Digital/Digital.sh";
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ fi
|
|||||||
|
|
||||||
# Auto-tmux configuration
|
# Auto-tmux configuration
|
||||||
use_auto_tmux () {
|
use_auto_tmux () {
|
||||||
! ([ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ] || $(pgrep -x "i3" > /dev/null || pgrep -x "sway" > /dev/null || pgrep -x "Hyprland" > /dev/null))
|
! ([ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ] || pgrep -x i3 >/dev/null || pgrep -x sway >/dev/null || pgrep -x Hyprland >/dev/null)
|
||||||
}
|
}
|
||||||
|
|
||||||
if use_auto_tmux; then
|
if use_auto_tmux; then
|
||||||
@@ -47,7 +47,7 @@ rebuild () {
|
|||||||
shift 2>/dev/null || true
|
shift 2>/dev/null || true
|
||||||
|
|
||||||
local config_dir="${FLAKE_CONFIG:-$HOME/NixOS-Configs}"
|
local config_dir="${FLAKE_CONFIG:-$HOME/NixOS-Configs}"
|
||||||
local host="hades"
|
local host="${REBUILD_HOST:-$(hostname)}"
|
||||||
local target="$config_dir#$host"
|
local target="$config_dir#$host"
|
||||||
|
|
||||||
case "$action" in
|
case "$action" in
|
||||||
|
|||||||
@@ -11,24 +11,28 @@
|
|||||||
inputs.disko.nixosModules.disko
|
inputs.disko.nixosModules.disko
|
||||||
./disko.nix
|
./disko.nix
|
||||||
./hardware-configuration.nix
|
./hardware-configuration.nix
|
||||||
./display-configuration.nix
|
|
||||||
../../modules/hardware/nvidia.nix
|
../../modules/hardware/nvidia.nix
|
||||||
../../modules/apps/gaming.nix
|
|
||||||
../../modules/apps/local_ai.nix
|
|
||||||
];
|
];
|
||||||
|
|
||||||
networking.hostName = "hades";
|
networking.hostName = "hades";
|
||||||
|
|
||||||
|
# Opt this machine into the shared optional feature modules.
|
||||||
|
my = {
|
||||||
|
gaming.enable = true;
|
||||||
|
ai.local.enable = true;
|
||||||
|
pi.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Host display configuration (absorbed by home/hypr/hyprland.nix)
|
# Host display configuration (absorbed by home/hypr/hyprland.nix)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
home-manager.users.anish.volt.monitors = [
|
home-manager.users.${config.my.username}.hypr.monitors = [
|
||||||
{
|
{
|
||||||
# Samsung Odyssey Ark - matched by description so it survives port changes
|
# Samsung Odyssey Ark - matched by description so it survives port changes
|
||||||
output = "desc:Samsung Electric Company Odyssey Ark H1AK500000";
|
output = "desc:Samsung Electric Company Odyssey Ark H1AK500000";
|
||||||
mode = "3840x2160@164.99Hz";
|
mode = "3840x2160@164.99Hz";
|
||||||
position = "auto";
|
position = "auto";
|
||||||
scale = 1.25;
|
scale = 1;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
# Anything else plugged into hades: sensible automatic fallback
|
# Anything else plugged into hades: sensible automatic fallback
|
||||||
@@ -44,7 +48,7 @@
|
|||||||
|
|
||||||
# Ensure /games directory is accessible by default user
|
# Ensure /games directory is accessible by default user
|
||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d /games 0755 anish users -"
|
"d /games 0755 ${config.my.username} users -"
|
||||||
];
|
];
|
||||||
|
|
||||||
powerManagement.cpuFreqGovernor = "performance";
|
powerManagement.cpuFreqGovernor = "performance";
|
||||||
@@ -58,7 +62,6 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
mangohud
|
|
||||||
nvtopPackages.nvidia
|
nvtopPackages.nvidia
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,10 @@
|
|||||||
type = "filesystem";
|
type = "filesystem";
|
||||||
format = "vfat";
|
format = "vfat";
|
||||||
mountpoint = "/boot";
|
mountpoint = "/boot";
|
||||||
mountOptions = [ "fmask=0077" "dmask=0077" ];
|
mountOptions = [
|
||||||
|
"fmask=0077"
|
||||||
|
"dmask=0077"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
root = {
|
root = {
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
# Host-specific display configurations can be placed here if needed.
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,27 @@
|
|||||||
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||||
# and may be overwritten by future invocations. Please make changes
|
# and may be overwritten by future invocations. Please make changes
|
||||||
# to /etc/nixos/configuration.nix instead.
|
# to /etc/nixos/configuration.nix instead.
|
||||||
{ config, lib, pkgs, modulesPath, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
modulesPath,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
(modulesPath + "/installer/scan/not-detected.nix")
|
(modulesPath + "/installer/scan/not-detected.nix")
|
||||||
];
|
];
|
||||||
|
|
||||||
boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "ahci" "thunderbolt" "usbhid" "sd_mod" ];
|
boot.initrd.availableKernelModules = [
|
||||||
|
"nvme"
|
||||||
|
"xhci_pci"
|
||||||
|
"ahci"
|
||||||
|
"thunderbolt"
|
||||||
|
"usbhid"
|
||||||
|
"sd_mod"
|
||||||
|
];
|
||||||
boot.initrd.kernelModules = [ ];
|
boot.initrd.kernelModules = [ ];
|
||||||
boot.kernelModules = [ "kvm-amd" ];
|
boot.kernelModules = [ "kvm-amd" ];
|
||||||
boot.extraModulePackages = [ ];
|
boot.extraModulePackages = [ ];
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
# Browser is configured and managed via Home Manager (home/brave.nix)
|
|
||||||
}
|
|
||||||
@@ -3,9 +3,10 @@
|
|||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./terminal.nix
|
./terminal.nix
|
||||||
./browser.nix
|
|
||||||
./communication.nix
|
./communication.nix
|
||||||
./dev.nix
|
./dev.nix
|
||||||
./virt.nix
|
./virt.nix
|
||||||
|
./gaming.nix
|
||||||
|
./local-ai.nix
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
{ pkgs, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
{
|
{
|
||||||
|
config = lib.mkIf config.my.gaming.enable {
|
||||||
programs.steam = {
|
programs.steam = {
|
||||||
enable = true;
|
enable = true;
|
||||||
remotePlay.openFirewall = false;
|
remotePlay.openFirewall = false;
|
||||||
@@ -12,4 +18,5 @@
|
|||||||
mangohud
|
mangohud
|
||||||
gamemode
|
gamemode
|
||||||
];
|
];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
45
modules/apps/local-ai.nix
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
inputs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
inputs.comfy-ui.nixosModules.default
|
||||||
|
];
|
||||||
|
|
||||||
|
config = lib.mkIf config.my.ai.local.enable {
|
||||||
|
services = {
|
||||||
|
comfyui = {
|
||||||
|
enable = true;
|
||||||
|
gpuSupport = "cuda";
|
||||||
|
enableManager = true;
|
||||||
|
port = 8188;
|
||||||
|
listenAddress = "0.0.0.0";
|
||||||
|
dataDir = "/var/lib/comfyui";
|
||||||
|
user = "comfyui";
|
||||||
|
group = "comfyui";
|
||||||
|
createUser = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
ollama = {
|
||||||
|
enable = true;
|
||||||
|
package = pkgs.unstable.ollama-cuda;
|
||||||
|
host = "0.0.0.0";
|
||||||
|
port = 11434;
|
||||||
|
user = "ollama";
|
||||||
|
group = "ollama";
|
||||||
|
home = "/var/lib/ollama";
|
||||||
|
};
|
||||||
|
|
||||||
|
open-webui.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
unstable.ollama-cuda
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
{ pkgs, inputs, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
inputs.comfy-ui.nixosModules.default
|
|
||||||
];
|
|
||||||
|
|
||||||
services = {
|
|
||||||
comfyui = {
|
|
||||||
enable = true;
|
|
||||||
gpuSupport = "cuda";
|
|
||||||
enableManager = true;
|
|
||||||
port = 8188;
|
|
||||||
listenAddress = "0.0.0.0";
|
|
||||||
dataDir = "/var/lib/comfyui";
|
|
||||||
user = "comfyui";
|
|
||||||
group = "comfyui";
|
|
||||||
createUser = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
ollama = {
|
|
||||||
enable = true;
|
|
||||||
package = pkgs.unstable.ollama-cuda;
|
|
||||||
host = "0.0.0.0";
|
|
||||||
port = 11434;
|
|
||||||
user = "ollama";
|
|
||||||
group = "ollama";
|
|
||||||
home = "/var/lib/ollama";
|
|
||||||
};
|
|
||||||
|
|
||||||
open-webui.enable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.systemPackages = with pkgs; [
|
|
||||||
unstable.ollama-cuda
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{ pkgs, ... }:
|
{ pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
# Foot terminal is configured and managed via Home Manager (home/foot.nix)
|
# Terminal is managed elsewhere; system-level CLI helpers live here.
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
zoxide
|
zoxide
|
||||||
fzf
|
fzf
|
||||||
|
|||||||