diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
old mode 100644
new mode 100755
diff --git a/.gitignore b/.gitignore
old mode 100644
new mode 100755
diff --git a/AGENTS.md b/AGENTS.md
old mode 100644
new mode 100755
index 09451fbf..0d6d465f
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -6,15 +6,15 @@
## IMPORTANT: axctl Build Requirement
-When changes are made to axctl (in `/home/adriano/Repos/Axenide/axctl/`), manual build and install is required:
+When changes are made to axctl (in `/home/adriano/Repos/Leriart/axctl/`), manual build and install is required:
-1. Build: `cd /home/adriano/Repos/Axenide/axctl && go build -o bin/axctl .`
+1. Build: `cd /home/adriano/Repos/Leriart/axctl && go build -o bin/axctl .`
2. Install: Replace `/usr/local/bin/axctl` with the new binary (requires manual intervention)
The agent cannot test axctl changes directly because the daemon runs in the user's session environment.
## OVERVIEW
-Ambxst is a highly customizable Wayland shell built with Quickshell. It provides a unified panel (bar, dock, notch), dashboard, lockscreen, desktop widgets, and notification system, driven by a reactive JSON configuration system. Multi-monitor support via `Variants` on `Quickshell.screens`.
+NothingLess is a highly customizable Wayland shell built with Quickshell. It provides a unified panel (bar, dock, notch), dashboard, lockscreen, desktop widgets, and notification system, driven by a reactive JSON configuration system. Multi-monitor support via `Variants` on `Quickshell.screens`.
## STRUCTURE
```
@@ -59,7 +59,7 @@ Ambxst is a highly customizable Wayland shell built with Quickshell. It provides
| **Config Logic** | `config/Config.qml` | >3100 lines. `FileView` + `JsonAdapter` persistence |
| **Transient State** | `modules/globals/GlobalStates.qml` | Window visibility, active modes, runtime flags |
| **Services** | `modules/services/*.qml` | 30+ singletons. System integration layer |
-| **Theme/Colors** | `modules/theme/Colors.qml` | Watches `~/.cache/ambxst/colors.json` reactively |
+| **Theme/Colors** | `modules/theme/Colors.qml` | Watches `~/.cache/NothingLess/colors.json` reactively |
| **Styling** | `modules/theme/Styling.qml` | `radius()`, `fontSize()`, `getStyledRectConfig()` |
| **UI Primitives** | `modules/components/` | `StyledRect`, `BarPopup`, `SearchInput`, shaders |
| **Dashboard** | `modules/widgets/dashboard/` | Tabbed hub with LRU lazy-loading |
@@ -118,7 +118,7 @@ qs -p shell.qml
./cli.sh
# Install (Arch/Fedora/NixOS)
-curl -L get.axeni.de/ambxst | sh
+curl -L github.com/Leriart/NothingLess/NothingLess | sh
```
## NOTES
@@ -127,8 +127,8 @@ curl -L get.axeni.de/ambxst | sh
- The `qs.` import prefix is a Quickshell VFS construct, not a physical directory.
- `screenshotToolMode` in `GlobalStates.qml` is **DEPRECATED**.
- Gemini AI provider doesn't support the `system` role; handled in `services/ai/strategies/`.
-- `axctl` is a core part of this project. It abstracts compositor interactions. It is one of Axenide's projects and the source code is available at `/home/adriano/Repos/Axenide/axctl/`.
-- We register a changelog in a website. The local repo for this website is at `/home/adriano/Repos/Axenide/web/`. The changelog entries are stored in `content/ambxst/changelog/` as Zola markdown files. Write following the structure by referencing other entries, and add links to PRs and issues when relevant. Only write a changelog when the user asks for it.
+- `axctl` is a core part of this project. It abstracts compositor interactions. It is one of Leriart's projects and the source code is available at `/home/adriano/Repos/Leriart/axctl/`.
+- We register a changelog in a website. The local repo for this website is at `/home/adriano/Repos/Leriart/web/`. The changelog entries are stored in `content/NothingLess/changelog/` as Zola markdown files. Write following the structure by referencing other entries, and add links to PRs and issues when relevant. Only write a changelog when the user asks for it.
- Some projects to keep in mind for reference:
- DankMaterialShell (DMS): https://github.com/AvengeMedia/DankMaterialShell
diff --git a/LICENSE b/LICENSE
old mode 100644
new mode 100755
diff --git a/README.md b/README.md
old mode 100644
new mode 100755
index 8d9528be..5955651f
--- a/README.md
+++ b/README.md
@@ -1,148 +1,211 @@
-
-
-
-An Ax tremely customizable shell.
+
+
+ A high-performance, deeply customizable Wayland shell built with Quickshell.
+
+ Forked from Ambxst β less is more.
-
-
-
-
-
-
+
+
+
-
-
+
+
---
- Screenshots
+## Screenshots
+
+
+
+
+
+
+
+---
+
+## Installation
+
+```bash
+curl -sL https://github.com/Leriart/NothingLess/raw/main/install.sh | sh
+```
+
+Or clone manually:
+
+```bash
+git clone https://github.com/Leriart/NothingLess.git ~/.local/src/nothingless
+sudo ln -s ~/.local/src/nothingless/cli.sh /usr/local/bin/nothingless
+```
+
+Then run:
+
+```bash
+nothingless
+```
-
-
+### Compositor integration
-
+```bash
+nothingless install hyprland # Auto-detect (default: conf)
+nothingless install hyprland --conf # Force config file mode (safe default)
+nothingless install hyprland --lua # Force Lua mode (Hyprland >= 0.48)
+nothingless remove hyprland # Remove NothingLess config from Hyprland
+```
-
-
-
+**Mode selection:**
+- `--conf` (default): Creates `~/.local/share/nothingless/hyprland.conf` and adds `source = ~/.local/share/nothingless/hyprland.conf` to your Hyprland config. Works on all Hyprland versions.
+- `--lua`: Creates `~/.local/share/nothingless/hyprland.lua` as valid Lua and adds `loadfile(...)()` to your Hyprland config. Requires Hyprland >= 0.48.
+- No flag: Auto-detects based on existing config (`hyprland.lua` β lua, `hyprland.conf` β conf). If neither exists, defaults to `--conf`.
-
-
-
+On first boot, `exec-once = nothingless` launches the shell, which starts the axctl daemon internally. All compositor settings are managed by axctl (live via `raw-batch`, persisted via `axctl.toml`).
-
-
-
-
+Supported on **Arch**, **Fedora**, and **NixOS** (requires Hyprland).
---
- Installation
+## Commands
+
+### CLI
```bash
-curl -L get.axeni.de/ambxst | sh
+nothingless # Start NothingLess shell
+nothingless update # Update NothingLess
+nothingless reload # Reload NothingLess
+nothingless quit # Quit NothingLess
+nothingless lock # Activate lockscreen
+nothingless run # Run a NothingLess module
+nothingless brightness <0-100> # Set brightness
+nothingless brightness +/- # Adjust brightness
+nothingless brightness -s # Save current brightness
+nothingless brightness -r # Restore saved brightness
+nothingless screen on|off # Control display power
+nothingless suspend # Suspend system
```
-This will install Ambxst and its dependencies. You will have the `ambxst` command available in your terminal, which you can use to start the shell.
+### Module Commands
+
+| `nothingless run ...` | Description |
+|---|---|
+| `launcher` | Open app launcher |
+| `dashboard` | Open dashboard |
+| `assistant` | Open AI assistant |
+| `clipboard` | Open clipboard manager |
+| `emoji` | Open emoji picker |
+| `notes` | Open notes |
+| `tmux` | Open tmux session manager |
+| `wallpapers` | Open wallpaper picker |
+| `overview` | Open workspace overview |
+| `powermenu` | Open power menu |
+| `tools` | Open tools menu |
+| `config` | Open settings |
+| `screenshot` | Take screenshot |
+| `screenrecord` | Screen record |
+| `lens` | Open OCR capture |
+| `toggle-metrics` | Toggle notch metrics display |
+| `lockscreen` | Lock session |
+
+### Keybinds
+
+| Key | Action |
+|---|---|
+| `SUPER` (hold) | Launcher |
+| `SUPER + D` | Dashboard |
+| `SUPER + A` | Assistant |
+| `SUPER + V` | Clipboard |
+| `SUPER + PERIOD` | Emoji picker |
+| `SUPER + N` | Notes |
+| `SUPER + T` | Tmux |
+| `SUPER + COMMA` | Wallpapers |
+| `SUPER + TAB` | Workspace overview |
+| `SUPER + ESC` | Power menu |
+| `SUPER + S` | Tools menu |
+| `SUPER + SHIFT + C` | Settings |
+| `SUPER + SHIFT + S` | Screenshot |
+| `SUPER + SHIFT + R` | Screen record |
+| `SUPER + SHIFT + A` | Lens |
+| `SUPER + L` | Lock session |
+| `SUPER + SHIFT + BACKSPACE` | Toggle metrics overlay |
-### Hyprland (more compositors coming soon!)
+---
-1. Run the installation command above.
+## FPS Monitoring (`nothing-fps`)
-2. Run `ambxst install hyprland` to add Ambxst's configuration to Hyprland. This will source a config file that applies Ambxst's settings. It will look like this:
+NothingLess includes a modified MangoHud that captures FPS data and displays it in the notch metrics overlay.
```bash
-# Ambxst
-source = ~/.local/share/ambxst/hyprland.conf
+# Launch a game with FPS monitoring
+nothing-fps ./my-game
-# OVERRIDES
-# Down here you can write or source anything that you want to override from Ambxst's settings.
+# Steam launch options (right-click game > Properties > Launch Options)
+nothing-fps %command%
```
-As stated, anything you want to override from Ambxst's settings should be written under the "OVERRIDES" section.
+**How it works:**
-3. Start Ambxst by running `ambxst` in your terminal. If you want to keep it running without having the terminal window open, you can run `ambxst & disown`. This will be only necessary for your first test run, as Ambxst will start automatically on login after step 2.
+1. `nothing-fps` sets up a modified MangoHud (`libMangoHud_shim.so`) via `LD_PRELOAD`
+2. MangoHud hooks Vulkan/OpenGL to capture frame-present events
+3. Calculated FPS is written to `/dev/shm/nothingless_fps`
+4. NothingLess reads this file in real-time and displays FPS in the notch
-Ambxst is currently supported on **Arch**, **Fedora**, and **NixOS**. This means both based and derivative distributions.
+**Rebuilding MangoHud from source:**
-> [!IMPORTANT]
-> The only pre-requisite is having Hyprland installed.
+```bash
+./scripts/mangohud-patch/build-mangohud.sh
+```
-> [!NOTE]
-> For NixOS users, the screen recording utility `gpu-screen-recorder` will only be able to use the `portal` backend until you add `programs.gpu-screen-recorder.enable = true;` to your `configuration.nix` or **home-manager**.
+Requires: `meson`, `ninja`, `gcc`, `glslang`, `python-mako`.
---
-## Will this change my config?
+## Differences from Ambxst
-Nope! Besides the source line in your `hyprland.conf`, Ambxst is designed to be non-intrusive. It won't modify any of your existing configurations.
+### Architecture
----
+| Area | Ambxst | NothingLess |
+|------|--------|-------------|
+| Compositor settings | ~40 options (border, shadow, blur) | 130+ options across 11 categories |
+| Config reload handling | Basic | `configreloaded` event detection with instant bind/settings recovery |
- Features
-
-- [x] Customizable components
-- [x] Themes
-- [x] System integration
-- [x] App launcher
-- [x] Clipboard manager
-- [x] Quick notes (and not so quick ones)
-- [x] Wallpaper manager
-- [x] Emoji picker
-- [x] [tmux](https://github.com/tmux/tmux) session manager
-- [x] System monitor
-- [x] Media control
-- [x] Notification system
-- [x] Wi-Fi manager
-- [x] Bluetooth manager
-- [x] Audio mixer
-- [x] [EasyEffects](https://github.com/wwmm/easyeffects) integration
-- [x] Screen capture
-- [x] Screen recording
-- [x] Color picker
-- [x] OCR
-- [x] QR and barcode scanner
-- [x] "Mirror" (webcam)
-- [x] Game mode
-- [x] Night mode
-- [x] Power profile manager
-- [x] AI Assistant
-- [x] Weather
-- [x] Calendar
-- [x] Power menu
-- [x] Workspace management
-- [x] Support for different layouts (dwindle, master, scrolling, etc.)
-- [x] Multi-monitor support
-- [x] Customizable keybindings
-- [ ] Plugin and extension system
-- [ ] Compatibility with other Wayland compositors
+### Performance
+
+| Area | Ambxst | NothingLess |
+|------|--------|-------------|
+| Video wallpaper | mpv-based | QtMultimedia + FFmpeg (hardware-accelerated, lower overhead) |
+| Rendering backend | Default | Configurable: OpenGL (default) or Vulkan with threaded render loop |
+| GPU optimization | Standard | NVIDIA env vars, GPU texture caching (`GradientCache`) |
+| GLSL shaders | Original set | Optimized (reduced draw calls, shared GPU textures) |
+| FPS monitoring | Not available | Custom MangoHud integration with real-time notch display |
----
-## I need help!
+### Design
-If you are having trouble or have any questions:
-- You can ask anything on [Discord](https://discord.com/invite/gHG9WHyNvH) or in the [GitHub discussions](https://github.com/Axenide/Ambxst/discussions).
-- You can open an issue on the [GitHub repository](https://github.com/Axenide/Ambxst/issues).
-- The main configuration is located at `~/.config/ambxst`.
+| Area | Ambxst | NothingLess |
+|------|--------|-------------|
+| Typography | Roboto, varied | Ndot (dot-matrix), monospace-first |
+| Color scheme | Vibrant themes | Monochrome with subtle red accents |
+| Animations | Heavy, ornate | Minimal, functional |
+| Branding | Color glyphs | Red + white dot-matrix |
---
## Credits
-- [outfoxxed](https://outfoxxed.me/) for creating Quickshell and great documentation!
-- [end-4](https://github.com/end-4) for his awesome projects. I learned a lot from them! (And *yoinked* a lot of code, too. π
)
-- [soramane](https://github.com/soramanew) for helping me when I started with Quickshell. (You probably don't remember, but still, heh.)
-- [tr1x_em](https://trix.is-a.dev/) for being a great friend and helping me find great tools. You rock!
-- [Darsh](https://github.com/its-darsh) for not killing me when I left Fabric. u_u (Also for being a great friend and creating Fabric! Without Fabric, Ax-Shell wouldn't exist, so Ambxst wouldn't either. Thank you!)
-- [Mario](https://github.com/mariokhz) for being a great friend and showing me Quickshell!
-- [Samouly](https://samouly.is-a.dev/) for being Samouly. :3
-- [Brys](https://github.com/brys0) for being his continuous support and for being a great friend!
-- [Zen](https://github.com/wer-zen) for being a great friend and helping me when I started with Quickshell too!
-- [kh](https://www.youtube.com/watch?v=dQw4w9WgXcQ) for being an awesome human being and listening to my delusions about Ambxst. :D
-- And you, the user, for trying out Ambxst! You're awesome! π
-
-(If I forgot someone, please let me know. π)
+
+- **Leriart** -- fork maintainer and NothingLess developer
+- **Axenide** -- original [Ambxst](https://github.com/Axenide/Ambxst) creator
+- **Zack** ([@zackytodearena](https://bsky.app/profile/zackytodearena.bsky.social)) -- logo & animation design
+- **outfoxxed** -- creator of [Quickshell](https://git.outfoxxed.me/outfoxxed/quickshell)
+- **end-4** -- inspiration from [dots-hyprland](https://github.com/end-4/dots-hyprland)
+- **DankMaterialShell** -- design reference from [DMS](https://github.com/AvengeMedia/DankMaterialShell)
+- **Noctalia** -- reference from [noctalia-shell](https://github.com/noctalia-dev/noctalia-shell)
+
+---
+
+## License
+
+- NothingLess modifications are provided under the same license as the upstream.
+- Ambxst and the Ambxst logo are trademarks of Adriano Tisera (Axenide).
+- See [LICENSE](./LICENSE) and [TRADEMARK.md](./assets/nothingless/TRADEMARK.md) for details.
+
diff --git a/assets/aiproviders/anthropic.svg b/assets/aiproviders/anthropic.svg
old mode 100644
new mode 100755
diff --git a/assets/aiproviders/deepseek.svg b/assets/aiproviders/deepseek.svg
old mode 100644
new mode 100755
diff --git a/assets/aiproviders/gemini.svg b/assets/aiproviders/gemini.svg
old mode 100644
new mode 100755
diff --git a/assets/aiproviders/github.svg b/assets/aiproviders/github.svg
old mode 100644
new mode 100755
diff --git a/assets/aiproviders/google.svg b/assets/aiproviders/google.svg
old mode 100644
new mode 100755
diff --git a/assets/aiproviders/groq.svg b/assets/aiproviders/groq.svg
old mode 100644
new mode 100755
diff --git a/assets/aiproviders/lmstudio.svg b/assets/aiproviders/lmstudio.svg
old mode 100644
new mode 100755
diff --git a/assets/aiproviders/minimax.svg b/assets/aiproviders/minimax.svg
old mode 100644
new mode 100755
diff --git a/assets/aiproviders/mistral.svg b/assets/aiproviders/mistral.svg
old mode 100644
new mode 100755
diff --git a/assets/aiproviders/ollama.svg b/assets/aiproviders/ollama.svg
old mode 100644
new mode 100755
diff --git a/assets/aiproviders/openai.svg b/assets/aiproviders/openai.svg
old mode 100644
new mode 100755
diff --git a/assets/aiproviders/openrouter.svg b/assets/aiproviders/openrouter.svg
old mode 100644
new mode 100755
diff --git a/assets/aiproviders/perplexity.svg b/assets/aiproviders/perplexity.svg
old mode 100644
new mode 100755
diff --git a/assets/aiproviders/xai.svg b/assets/aiproviders/xai.svg
old mode 100644
new mode 100755
diff --git a/assets/ambxst/ambxst wallpaper.svg b/assets/ambxst/ambxst wallpaper.svg
deleted file mode 100644
index b1c1f7d8..00000000
--- a/assets/ambxst/ambxst wallpaper.svg
+++ /dev/null
@@ -1,301 +0,0 @@
-
-
-
-
diff --git a/assets/ambxst/ambxst-icon-color.svg b/assets/ambxst/ambxst-icon-color.svg
deleted file mode 100644
index 10d267b3..00000000
--- a/assets/ambxst/ambxst-icon-color.svg
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
-
diff --git a/assets/ambxst/ambxst-icon.svg b/assets/ambxst/ambxst-icon.svg
deleted file mode 100644
index 30715cc9..00000000
--- a/assets/ambxst/ambxst-icon.svg
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
diff --git a/assets/ambxst/ambxst-logo-color.svg b/assets/ambxst/ambxst-logo-color.svg
deleted file mode 100644
index 81a48bc8..00000000
--- a/assets/ambxst/ambxst-logo-color.svg
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
diff --git a/assets/ambxst/ambxst-logo.svg b/assets/ambxst/ambxst-logo.svg
deleted file mode 100644
index 78a3bd63..00000000
--- a/assets/ambxst/ambxst-logo.svg
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
diff --git a/assets/colors/Ayu/dark b/assets/colors/Ayu/dark
old mode 100644
new mode 100755
diff --git a/assets/colors/Ayu/dark.json b/assets/colors/Ayu/dark.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Ayu/light b/assets/colors/Ayu/light
old mode 100644
new mode 100755
diff --git a/assets/colors/Ayu/light.json b/assets/colors/Ayu/light.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Catppuccin/dark b/assets/colors/Catppuccin/dark
old mode 100644
new mode 100755
diff --git a/assets/colors/Catppuccin/dark.json b/assets/colors/Catppuccin/dark.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Catppuccin/light b/assets/colors/Catppuccin/light
old mode 100644
new mode 100755
diff --git a/assets/colors/Catppuccin/light.json b/assets/colors/Catppuccin/light.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Everforest/dark b/assets/colors/Everforest/dark
old mode 100644
new mode 100755
diff --git a/assets/colors/Everforest/dark.json b/assets/colors/Everforest/dark.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Everforest/light b/assets/colors/Everforest/light
old mode 100644
new mode 100755
diff --git a/assets/colors/Everforest/light.json b/assets/colors/Everforest/light.json
old mode 100644
new mode 100755
diff --git a/assets/colors/GitHub/dark b/assets/colors/GitHub/dark
old mode 100644
new mode 100755
diff --git a/assets/colors/GitHub/dark.json b/assets/colors/GitHub/dark.json
old mode 100644
new mode 100755
diff --git a/assets/colors/GitHub/light b/assets/colors/GitHub/light
old mode 100644
new mode 100755
diff --git a/assets/colors/GitHub/light.json b/assets/colors/GitHub/light.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Gruvbox/dark b/assets/colors/Gruvbox/dark
old mode 100644
new mode 100755
diff --git a/assets/colors/Gruvbox/dark.json b/assets/colors/Gruvbox/dark.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Gruvbox/light b/assets/colors/Gruvbox/light
old mode 100644
new mode 100755
diff --git a/assets/colors/Gruvbox/light.json b/assets/colors/Gruvbox/light.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Kanagawa/dark b/assets/colors/Kanagawa/dark
old mode 100644
new mode 100755
diff --git a/assets/colors/Kanagawa/dark.json b/assets/colors/Kanagawa/dark.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Kanagawa/light b/assets/colors/Kanagawa/light
old mode 100644
new mode 100755
diff --git a/assets/colors/Kanagawa/light.json b/assets/colors/Kanagawa/light.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Nord/dark b/assets/colors/Nord/dark
old mode 100644
new mode 100755
diff --git a/assets/colors/Nord/dark.json b/assets/colors/Nord/dark.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Nord/light b/assets/colors/Nord/light
old mode 100644
new mode 100755
diff --git a/assets/colors/Nord/light.json b/assets/colors/Nord/light.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Nothing/dark b/assets/colors/Nothing/dark
new file mode 100644
index 00000000..0efbb484
--- /dev/null
+++ b/assets/colors/Nothing/dark
@@ -0,0 +1,8 @@
+#0A0A0A
+#E80012
+#FFFFFF
+#888888
+#E80012
+#FFFFFF
+#888888
+#333333
diff --git a/assets/colors/Nothing/dark.json b/assets/colors/Nothing/dark.json
new file mode 100644
index 00000000..6ade11b0
--- /dev/null
+++ b/assets/colors/Nothing/dark.json
@@ -0,0 +1,100 @@
+{
+ "background": "#0A0A0A",
+ "red": "#E80012",
+ "green": "#FFFFFF",
+ "yellow": "#888888",
+ "blue": "#E80012",
+ "magenta": "#FFFFFF",
+ "cyan": "#888888",
+ "white": "#333333",
+ "lightRed": "#FF1A2B",
+ "lightGreen": "#FFFFFF",
+ "lightYellow": "#999999",
+ "lightBlue": "#FF1A2B",
+ "lightMagenta": "#FFFFFF",
+ "lightCyan": "#999999",
+ "redContainer": "#3D0006",
+ "greenContainer": "#1A1A1A",
+ "yellowContainer": "#222222",
+ "blueContainer": "#3D0006",
+ "magentaContainer": "#1A1A1A",
+ "cyanContainer": "#222222",
+ "whiteContainer": "#151515",
+ "redSource": "#E80012",
+ "greenSource": "#FFFFFF",
+ "yellowSource": "#888888",
+ "blueSource": "#E80012",
+ "magentaSource": "#FFFFFF",
+ "cyanSource": "#888888",
+ "whiteSource": "#333333",
+ "redValue": "#E80012",
+ "greenValue": "#FFFFFF",
+ "yellowValue": "#888888",
+ "blueValue": "#E80012",
+ "magentaValue": "#FFFFFF",
+ "cyanValue": "#888888",
+ "whiteValue": "#333333",
+ "primary": "#E80012",
+ "primaryContainer": "#3D0006",
+ "primaryFixed": "#FF6B76",
+ "primaryFixedDim": "#E80012",
+ "secondary": "#FFFFFF",
+ "secondaryContainer": "#1A1A1A",
+ "secondaryFixed": "#E0E0E0",
+ "secondaryFixedDim": "#FFFFFF",
+ "tertiary": "#888888",
+ "tertiaryContainer": "#222222",
+ "tertiaryFixed": "#AAAAAA",
+ "tertiaryFixedDim": "#888888",
+ "error": "#E80012",
+ "errorContainer": "#3D0006",
+ "surface": "#0A0A0A",
+ "surfaceBright": "#1A1A1A",
+ "surfaceContainer": "#0E0E0E",
+ "surfaceContainerHigh": "#121212",
+ "surfaceContainerHighest": "#161616",
+ "surfaceContainerLow": "#0C0C0C",
+ "surfaceContainerLowest": "#080808",
+ "surfaceDim": "#0A0A0A",
+ "surfaceTint": "#E80012",
+ "surfaceVariant": "#1A1A1A",
+ "outline": "#333333",
+ "outlineVariant": "#1A1A1A",
+ "inverseOnSurface": "#0A0A0A",
+ "inversePrimary": "#E80012",
+ "inverseSurface": "#E0E0E0",
+ "overBackground": "#E8E8E8",
+ "overSurface": "#E8E8E8",
+ "overSurfaceVariant": "#CCCCCC",
+ "overPrimary": "#FFFFFF",
+ "overPrimaryContainer": "#FF6B76",
+ "overPrimaryFixed": "#FFFFFF",
+ "overPrimaryFixedVariant": "#E80012",
+ "overSecondary": "#E8E8E8",
+ "overSecondaryContainer": "#E0E0E0",
+ "overSecondaryFixed": "#0A0A0A",
+ "overSecondaryFixedVariant": "#CCCCCC",
+ "overTertiary": "#E8E8E8",
+ "overTertiaryContainer": "#AAAAAA",
+ "overTertiaryFixed": "#000000",
+ "overTertiaryFixedVariant": "#666666",
+ "overRed": "#FFFFFF",
+ "overRedContainer": "#FF6B76",
+ "overGreen": "#0A0A0A",
+ "overGreenContainer": "#E0E0E0",
+ "overYellow": "#000000",
+ "overYellowContainer": "#AAAAAA",
+ "overBlue": "#FFFFFF",
+ "overBlueContainer": "#FF6B76",
+ "overMagenta": "#0A0A0A",
+ "overMagentaContainer": "#E0E0E0",
+ "overCyan": "#000000",
+ "overCyanContainer": "#AAAAAA",
+ "overWhite": "#FFFFFF",
+ "overWhiteContainer": "#666666",
+ "overError": "#FFFFFF",
+ "overErrorContainer": "#FF6B76",
+ "scrim": "#000000",
+ "shadow": "#000000",
+ "sourceColor": "#E80012"
+}
diff --git a/assets/colors/Nothing/light b/assets/colors/Nothing/light
new file mode 100644
index 00000000..622bd20b
--- /dev/null
+++ b/assets/colors/Nothing/light
@@ -0,0 +1,8 @@
+#FFFFFF
+#E80012
+#000000
+#666666
+#E80012
+#000000
+#666666
+#CCCCCC
diff --git a/assets/colors/Nothing/light.json b/assets/colors/Nothing/light.json
new file mode 100644
index 00000000..d9f105a8
--- /dev/null
+++ b/assets/colors/Nothing/light.json
@@ -0,0 +1,100 @@
+{
+ "background": "#FFFFFF",
+ "red": "#E80012",
+ "green": "#000000",
+ "yellow": "#666666",
+ "blue": "#E80012",
+ "magenta": "#000000",
+ "cyan": "#666666",
+ "white": "#CCCCCC",
+ "lightRed": "#FF1A2B",
+ "lightGreen": "#333333",
+ "lightYellow": "#777777",
+ "lightBlue": "#FF1A2B",
+ "lightMagenta": "#333333",
+ "lightCyan": "#777777",
+ "redContainer": "#FFE0E0",
+ "greenContainer": "#F0F0F0",
+ "yellowContainer": "#E8E8E8",
+ "blueContainer": "#FFE0E0",
+ "magentaContainer": "#F0F0F0",
+ "cyanContainer": "#E8E8E8",
+ "whiteContainer": "#F5F5F5",
+ "redSource": "#E80012",
+ "greenSource": "#000000",
+ "yellowSource": "#666666",
+ "blueSource": "#E80012",
+ "magentaSource": "#000000",
+ "cyanSource": "#666666",
+ "whiteSource": "#CCCCCC",
+ "redValue": "#E80012",
+ "greenValue": "#000000",
+ "yellowValue": "#666666",
+ "blueValue": "#E80012",
+ "magentaValue": "#000000",
+ "cyanValue": "#666666",
+ "whiteValue": "#CCCCCC",
+ "primary": "#E80012",
+ "primaryContainer": "#FFE0E0",
+ "primaryFixed": "#FF6B76",
+ "primaryFixedDim": "#E80012",
+ "secondary": "#000000",
+ "secondaryContainer": "#F0F0F0",
+ "secondaryFixed": "#333333",
+ "secondaryFixedDim": "#000000",
+ "tertiary": "#666666",
+ "tertiaryContainer": "#E8E8E8",
+ "tertiaryFixed": "#888888",
+ "tertiaryFixedDim": "#666666",
+ "error": "#E80012",
+ "errorContainer": "#FFE0E0",
+ "surface": "#FFFFFF",
+ "surfaceBright": "#F5F5F5",
+ "surfaceContainer": "#FAFAFA",
+ "surfaceContainerHigh": "#F0F0F0",
+ "surfaceContainerHighest": "#E8E8E8",
+ "surfaceContainerLow": "#FCFCFC",
+ "surfaceContainerLowest": "#FFFFFF",
+ "surfaceDim": "#FFFFFF",
+ "surfaceTint": "#E80012",
+ "surfaceVariant": "#F0F0F0",
+ "outline": "#CCCCCC",
+ "outlineVariant": "#E8E8E8",
+ "inverseOnSurface": "#FFFFFF",
+ "inversePrimary": "#E80012",
+ "inverseSurface": "#333333",
+ "overBackground": "#666666",
+ "overSurface": "#666666",
+ "overSurfaceVariant": "#777777",
+ "overPrimary": "#FFFFFF",
+ "overPrimaryContainer": "#FF6B76",
+ "overPrimaryFixed": "#FFFFFF",
+ "overPrimaryFixedVariant": "#E80012",
+ "overSecondary": "#FFFFFF",
+ "overSecondaryContainer": "#333333",
+ "overSecondaryFixed": "#FFFFFF",
+ "overSecondaryFixedVariant": "#000000",
+ "overTertiary": "#FFFFFF",
+ "overTertiaryContainer": "#888888",
+ "overTertiaryFixed": "#FFFFFF",
+ "overTertiaryFixedVariant": "#444444",
+ "overRed": "#FFFFFF",
+ "overRedContainer": "#FF6B76",
+ "overGreen": "#FFFFFF",
+ "overGreenContainer": "#333333",
+ "overYellow": "#FFFFFF",
+ "overYellowContainer": "#888888",
+ "overBlue": "#FFFFFF",
+ "overBlueContainer": "#FF6B76",
+ "overMagenta": "#FFFFFF",
+ "overMagentaContainer": "#333333",
+ "overCyan": "#FFFFFF",
+ "overCyanContainer": "#888888",
+ "overWhite": "#000000",
+ "overWhiteContainer": "#AAAAAA",
+ "overError": "#FFFFFF",
+ "overErrorContainer": "#FF6B76",
+ "scrim": "#000000",
+ "shadow": "#000000",
+ "sourceColor": "#E80012"
+}
diff --git a/assets/colors/Paradise/dark b/assets/colors/Paradise/dark
old mode 100644
new mode 100755
diff --git a/assets/colors/Paradise/dark.json b/assets/colors/Paradise/dark.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Paradise/light b/assets/colors/Paradise/light
old mode 100644
new mode 100755
diff --git a/assets/colors/Paradise/light.json b/assets/colors/Paradise/light.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Posterpole/dark b/assets/colors/Posterpole/dark
old mode 100644
new mode 100755
diff --git a/assets/colors/Posterpole/dark.json b/assets/colors/Posterpole/dark.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Posterpole/light b/assets/colors/Posterpole/light
old mode 100644
new mode 100755
diff --git a/assets/colors/Posterpole/light.json b/assets/colors/Posterpole/light.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Rose Pine/dark b/assets/colors/Rose Pine/dark
old mode 100644
new mode 100755
diff --git a/assets/colors/Rose Pine/dark.json b/assets/colors/Rose Pine/dark.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Rose Pine/light b/assets/colors/Rose Pine/light
old mode 100644
new mode 100755
diff --git a/assets/colors/Rose Pine/light.json b/assets/colors/Rose Pine/light.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Tokyonight/dark b/assets/colors/Tokyonight/dark
old mode 100644
new mode 100755
diff --git a/assets/colors/Tokyonight/dark.json b/assets/colors/Tokyonight/dark.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Tokyonight/light b/assets/colors/Tokyonight/light
old mode 100644
new mode 100755
diff --git a/assets/colors/Tokyonight/light.json b/assets/colors/Tokyonight/light.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Yoru/dark b/assets/colors/Yoru/dark
old mode 100644
new mode 100755
diff --git a/assets/colors/Yoru/dark.json b/assets/colors/Yoru/dark.json
old mode 100644
new mode 100755
diff --git a/assets/colors/Yoru/light b/assets/colors/Yoru/light
old mode 100644
new mode 100755
diff --git a/assets/colors/Yoru/light.json b/assets/colors/Yoru/light.json
old mode 100644
new mode 100755
diff --git a/assets/compositors/hyprland.example.conf b/assets/compositors/hyprland.example.conf
new file mode 100644
index 00000000..d2ec9af6
--- /dev/null
+++ b/assets/compositors/hyprland.example.conf
@@ -0,0 +1,181 @@
+# This config is a STUB! This should never be generated.
+# Use the default lua config from https://github.com/hyprwm/Hyprland/blob/main/example/hyprland.lua
+
+source = ~/.config/hypr/monitors.conf
+
+# Optimizaciones NVIDIA
+env = LIBVA_DRIVER_NAME,nvidia
+env = XDG_SESSION_TYPE,wayland
+env = GBM_BACKEND,nvidia-drm
+env = __GLX_VENDOR_LIBRARY_NAME,nvidia
+env = WLR_NO_HARDWARE_CURSORS,1
+
+# Disable Hyprland's default wallpaper/logo to prevent flash before NothingLess loads
+misc {
+ force_default_wallpaper = -1
+ disable_hyprland_logo = true
+}
+
+$mainMod = SUPER
+$terminal = kitty
+$fileManager = dolphin
+
+###################
+### KEYBINDINGS ###
+###################
+
+bind = $mainMod, Q, exec, $terminal
+bind = $mainMod, C, killactive,
+bind = $mainMod, E, exec, $fileManager
+bind = $mainMod, E, exec, $browser
+bind = $mainMod, H, togglefloating,
+bind = $mainMod, P, pseudo,
+bind = $mainMod, W, togglefloating,
+bind = $mainMod, G, togglegroup,
+bind = $mainMod, J, exec, kitty -e spf
+bind = SHIFT, F11, fullscreen
+
+# Workspaces
+bind = CTRL SUPER, left, workspace, -1
+bind = CTRL SUPER, right, workspace, +1
+bind = SHIFT CTRL SUPER, left, movetoworkspace, -1
+bind = SHIFT CTRL SUPER, right, movetoworkspace, +1
+
+# Qt/Quickshell GPU acceleration (opengl or vulkan)
+env = QSG_RHI_BACKEND, opengl
+env = QSG_RENDER_LOOP, threaded
+env = QT_QUICK_BACKEND, opengl
+env = QS_ICON_THEME,breeze
+####################
+### ANIMATIONS #####
+####################
+
+animations {
+ enabled = true
+
+ # Smooth reveal bezier β starts slow, ends smooth
+ bezier = reveal, 0.2, 0.0, 0.1, 1.0
+
+ # Windows - popin reveal effect (scale in from center)
+ animation = windows, 1, 4, reveal, popin 65%
+ animation = windowsOut, 1, 3, reveal, popin 65%
+
+ # Fade for dimmed/inactive
+ animation = fade, 1, 3, reveal
+ animation = fadeDim, 1, 3, reveal
+
+ # Border
+ animation = border, 1, 5, reveal
+ animation = borderangle, 1, 30, reveal, once
+
+ # Workspaces - slidefade (nothingless default)
+ # Inherited from nothingless config
+ # animation = workspaces, 1, 2.5, myBezier, slidefade 20%
+}
+
+# Window behavior
+misc {
+ animate_mouse_windowdragging = true
+ enable_swallow = false
+}
+
+#############
+### INPUT ###
+#############
+input {
+ kb_layout = latam
+ follow_mouse = 1
+ sensitivity = 0
+ touchpad {
+ natural_scroll = false
+ }
+}
+##################
+### DECORATION ###
+##################
+
+decoration {
+
+ # βββ GPU-saving tweaks βββ
+ # Opacity at 1.0 avoids extra alpha compositing pass on every window.
+ # NothingLess already manages its own opacity internally via QML layer effects.
+ active_opacity = 1.0
+ inactive_opacity = 1.0
+ fullscreen_opacity = 1.0
+
+ blur {
+ # Disabled at compositor level β NothingLess manages its own blur in QML.
+ # Compositor-level blur affects ALL windows (including terminals),
+ # adding GPU overhead even for apps that don't need it.
+ enabled = false
+ size = 3
+ passes = 1
+ }
+}
+
+
+###################
+### ###################
+### WINDOW RULES ###
+###################
+
+windowrule {
+ name = kitty-opacity
+ match:class = kitty
+ opacity = 0.95 0.85
+}
+
+windowrule {
+ name = zen-opacity
+ match:class = app.zen_browser.zen
+ opacity = 0.90 0.80
+}
+
+windowrule {
+ name = chrome-opacity
+ match:class = google-chrome
+ opacity = 0.97 0.92
+}
+
+windowrule {
+ name = dolphin-opacity
+ match:class = org.kde.dolphin
+ opacity = 0.92 0.85
+}
+
+windowrule {
+ name = micro-transparent
+ match:class = konsole
+ opacity = 0.92 0.85
+}
+
+windowrule {
+ name = yt-music-opacity
+ match:class = com.github.th_ch.youtube_music
+ opacity = 0.95 0.85
+}
+
+windowrule {
+ name = code-oss
+ match:class = code-oss
+ opacity = 0.95 0.85
+}
+
+
+windowrule {
+ name = fix-xwayland-drags
+ match:class = ^$
+ match:title = ^$
+ match:xwayland = true
+ match:float = true
+ no_focus = true
+}
+
+
+# NothingLess
+source = ~/.local/share/nothingless/hyprland.conf
+exec-once = nothingless
+exec-once = axctl -c ~/.local/share/nothingless/axctl.toml daemon
+
+# OVERRIDES
+# Down here you can write or source anything that you want to override from NothingLess's settings.
diff --git a/assets/compositors/hyprland.svg b/assets/compositors/hyprland.svg
old mode 100644
new mode 100755
diff --git a/assets/compositors/labwc.png b/assets/compositors/labwc.png
old mode 100644
new mode 100755
diff --git a/assets/compositors/mangowc.png b/assets/compositors/mangowc.png
old mode 100644
new mode 100755
diff --git a/assets/compositors/niri.svg b/assets/compositors/niri.svg
old mode 100644
new mode 100755
diff --git a/assets/compositors/sway.svg b/assets/compositors/sway.svg
old mode 100644
new mode 100755
diff --git a/assets/emojis.json b/assets/emojis.json
old mode 100644
new mode 100755
diff --git a/assets/fonts/Ndot-57-Aligned.ttf b/assets/fonts/Ndot-57-Aligned.ttf
new file mode 100755
index 00000000..c6394582
Binary files /dev/null and b/assets/fonts/Ndot-57-Aligned.ttf differ
diff --git a/assets/linux-logos.json b/assets/linux-logos.json
old mode 100644
new mode 100755
diff --git a/assets/matugen/colors.json b/assets/matugen/colors.json
old mode 100644
new mode 100755
diff --git a/assets/matugen/config.toml b/assets/matugen/config.toml
old mode 100644
new mode 100755
index 6baac200..e344d633
--- a/assets/matugen/config.toml
+++ b/assets/matugen/config.toml
@@ -1,6 +1,6 @@
-[templates.ambxst]
+[templates.nothingless]
input_path = "colors.json"
-output_path = "~/.cache/ambxst/colors.json"
+output_path = "~/.cache/nothingless/colors.json"
[config.custom_colors.red]
color = "#FF0000"
diff --git a/assets/not.gif b/assets/not.gif
new file mode 100644
index 00000000..58debd3b
Binary files /dev/null and b/assets/not.gif differ
diff --git a/assets/nothingless-wallpapers/nothingless1.jpg b/assets/nothingless-wallpapers/nothingless1.jpg
new file mode 100644
index 00000000..40a64662
Binary files /dev/null and b/assets/nothingless-wallpapers/nothingless1.jpg differ
diff --git a/assets/nothingless-wallpapers/nothingless2.png b/assets/nothingless-wallpapers/nothingless2.png
new file mode 100644
index 00000000..65144f2c
Binary files /dev/null and b/assets/nothingless-wallpapers/nothingless2.png differ
diff --git a/assets/nothingless-wallpapers/nothingless3.jpg b/assets/nothingless-wallpapers/nothingless3.jpg
new file mode 100644
index 00000000..594f1503
Binary files /dev/null and b/assets/nothingless-wallpapers/nothingless3.jpg differ
diff --git a/assets/nothingless-wallpapers/nothingless4.png b/assets/nothingless-wallpapers/nothingless4.png
new file mode 100644
index 00000000..8fc3f620
Binary files /dev/null and b/assets/nothingless-wallpapers/nothingless4.png differ
diff --git a/assets/nothingless-wallpapers/nothingless5.webp b/assets/nothingless-wallpapers/nothingless5.webp
new file mode 100644
index 00000000..5186b0f1
Binary files /dev/null and b/assets/nothingless-wallpapers/nothingless5.webp differ
diff --git a/assets/nothingless-wallpapers/nothingless6.png b/assets/nothingless-wallpapers/nothingless6.png
new file mode 100644
index 00000000..d644e09f
Binary files /dev/null and b/assets/nothingless-wallpapers/nothingless6.png differ
diff --git a/assets/nothingless-wallpapers/nothingless7.png b/assets/nothingless-wallpapers/nothingless7.png
new file mode 100644
index 00000000..9554be90
Binary files /dev/null and b/assets/nothingless-wallpapers/nothingless7.png differ
diff --git a/assets/nothingless-wallpapers/nothingless8.png b/assets/nothingless-wallpapers/nothingless8.png
new file mode 100644
index 00000000..70539049
Binary files /dev/null and b/assets/nothingless-wallpapers/nothingless8.png differ
diff --git a/assets/ambxst/LICENSE b/assets/nothingless/LICENSE
old mode 100644
new mode 100755
similarity index 100%
rename from assets/ambxst/LICENSE
rename to assets/nothingless/LICENSE
diff --git a/assets/nothingless/NOTHING_splash.webp b/assets/nothingless/NOTHING_splash.webp
new file mode 100644
index 00000000..514663bb
Binary files /dev/null and b/assets/nothingless/NOTHING_splash.webp differ
diff --git a/assets/ambxst/TRADEMARK.md b/assets/nothingless/TRADEMARK.md
old mode 100644
new mode 100755
similarity index 100%
rename from assets/ambxst/TRADEMARK.md
rename to assets/nothingless/TRADEMARK.md
diff --git a/assets/nothingless/nothingless-icon-color.svg b/assets/nothingless/nothingless-icon-color.svg
new file mode 100644
index 00000000..b7d085b0
--- /dev/null
+++ b/assets/nothingless/nothingless-icon-color.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/assets/nothingless/nothingless-icon.svg b/assets/nothingless/nothingless-icon.svg
new file mode 100644
index 00000000..b7d085b0
--- /dev/null
+++ b/assets/nothingless/nothingless-icon.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/assets/nothingless/nothingless-logo-color.svg b/assets/nothingless/nothingless-logo-color.svg
new file mode 100644
index 00000000..c73072b1
--- /dev/null
+++ b/assets/nothingless/nothingless-logo-color.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/assets/nothingless/nothingless-logo.svg b/assets/nothingless/nothingless-logo.svg
new file mode 100644
index 00000000..c73072b1
--- /dev/null
+++ b/assets/nothingless/nothingless-logo.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/assets/nothingless/nothingless-wallpaper.svg b/assets/nothingless/nothingless-wallpaper.svg
new file mode 100755
index 00000000..373d3db8
--- /dev/null
+++ b/assets/nothingless/nothingless-wallpaper.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/assets/presets/Caelestiax/bar.json b/assets/presets/Caelestiax/bar.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Caelestiax/compositor.json b/assets/presets/Caelestiax/compositor.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Caelestiax/desktop.json b/assets/presets/Caelestiax/desktop.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Caelestiax/dock.json b/assets/presets/Caelestiax/dock.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Caelestiax/info.json b/assets/presets/Caelestiax/info.json
old mode 100644
new mode 100755
index a74bb85a..71cc8431
--- a/assets/presets/Caelestiax/info.json
+++ b/assets/presets/Caelestiax/info.json
@@ -2,4 +2,3 @@
"author": "Axenide",
"authorUrl": "https://axeni.de"
}
-
diff --git a/assets/presets/Caelestiax/lockscreen.json b/assets/presets/Caelestiax/lockscreen.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Caelestiax/notch.json b/assets/presets/Caelestiax/notch.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Caelestiax/overview.json b/assets/presets/Caelestiax/overview.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Caelestiax/performance.json b/assets/presets/Caelestiax/performance.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Caelestiax/theme.json b/assets/presets/Caelestiax/theme.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Caelestiax/workspaces.json b/assets/presets/Caelestiax/workspaces.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Dotsquared/bar.json b/assets/presets/Dotsquared/bar.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Dotsquared/compositor.json b/assets/presets/Dotsquared/compositor.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Dotsquared/desktop.json b/assets/presets/Dotsquared/desktop.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Dotsquared/dock.json b/assets/presets/Dotsquared/dock.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Dotsquared/info.json b/assets/presets/Dotsquared/info.json
old mode 100644
new mode 100755
index a74bb85a..71cc8431
--- a/assets/presets/Dotsquared/info.json
+++ b/assets/presets/Dotsquared/info.json
@@ -2,4 +2,3 @@
"author": "Axenide",
"authorUrl": "https://axeni.de"
}
-
diff --git a/assets/presets/Dotsquared/lockscreen.json b/assets/presets/Dotsquared/lockscreen.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Dotsquared/notch.json b/assets/presets/Dotsquared/notch.json
old mode 100644
new mode 100755
index fe8aa080..6141748f
--- a/assets/presets/Dotsquared/notch.json
+++ b/assets/presets/Dotsquared/notch.json
@@ -4,6 +4,6 @@
"hoverRegionHeight": 16,
"keepHidden": false,
"noMediaDisplay": "userHost",
- "customText": "Ambxst",
+ "customText": "NothingLess",
"disableHoverExpansion": true
}
\ No newline at end of file
diff --git a/assets/presets/Dotsquared/overview.json b/assets/presets/Dotsquared/overview.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Dotsquared/performance.json b/assets/presets/Dotsquared/performance.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Dotsquared/theme.json b/assets/presets/Dotsquared/theme.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Dotsquared/workspaces.json b/assets/presets/Dotsquared/workspaces.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aero/bar.json b/assets/presets/Frutiger Aero/bar.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aero/compositor.json b/assets/presets/Frutiger Aero/compositor.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aero/desktop.json b/assets/presets/Frutiger Aero/desktop.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aero/dock.json b/assets/presets/Frutiger Aero/dock.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aero/info.json b/assets/presets/Frutiger Aero/info.json
old mode 100644
new mode 100755
index a74bb85a..71cc8431
--- a/assets/presets/Frutiger Aero/info.json
+++ b/assets/presets/Frutiger Aero/info.json
@@ -2,4 +2,3 @@
"author": "Axenide",
"authorUrl": "https://axeni.de"
}
-
diff --git a/assets/presets/Frutiger Aero/lockscreen.json b/assets/presets/Frutiger Aero/lockscreen.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aero/notch.json b/assets/presets/Frutiger Aero/notch.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aero/overview.json b/assets/presets/Frutiger Aero/overview.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aero/performance.json b/assets/presets/Frutiger Aero/performance.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aero/theme.json b/assets/presets/Frutiger Aero/theme.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aero/workspaces.json b/assets/presets/Frutiger Aero/workspaces.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aqua/bar.json b/assets/presets/Frutiger Aqua/bar.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aqua/compositor.json b/assets/presets/Frutiger Aqua/compositor.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aqua/desktop.json b/assets/presets/Frutiger Aqua/desktop.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aqua/dock.json b/assets/presets/Frutiger Aqua/dock.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aqua/info.json b/assets/presets/Frutiger Aqua/info.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aqua/lockscreen.json b/assets/presets/Frutiger Aqua/lockscreen.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aqua/notch.json b/assets/presets/Frutiger Aqua/notch.json
old mode 100644
new mode 100755
index fe8aa080..6141748f
--- a/assets/presets/Frutiger Aqua/notch.json
+++ b/assets/presets/Frutiger Aqua/notch.json
@@ -4,6 +4,6 @@
"hoverRegionHeight": 16,
"keepHidden": false,
"noMediaDisplay": "userHost",
- "customText": "Ambxst",
+ "customText": "NothingLess",
"disableHoverExpansion": true
}
\ No newline at end of file
diff --git a/assets/presets/Frutiger Aqua/overview.json b/assets/presets/Frutiger Aqua/overview.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aqua/performance.json b/assets/presets/Frutiger Aqua/performance.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aqua/theme.json b/assets/presets/Frutiger Aqua/theme.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Frutiger Aqua/workspaces.json b/assets/presets/Frutiger Aqua/workspaces.json
old mode 100644
new mode 100755
diff --git a/assets/presets/GNOME/bar.json b/assets/presets/GNOME/bar.json
old mode 100644
new mode 100755
diff --git a/assets/presets/GNOME/compositor.json b/assets/presets/GNOME/compositor.json
old mode 100644
new mode 100755
diff --git a/assets/presets/GNOME/desktop.json b/assets/presets/GNOME/desktop.json
old mode 100644
new mode 100755
diff --git a/assets/presets/GNOME/dock.json b/assets/presets/GNOME/dock.json
old mode 100644
new mode 100755
diff --git a/assets/presets/GNOME/info.json b/assets/presets/GNOME/info.json
old mode 100644
new mode 100755
index a74bb85a..71cc8431
--- a/assets/presets/GNOME/info.json
+++ b/assets/presets/GNOME/info.json
@@ -2,4 +2,3 @@
"author": "Axenide",
"authorUrl": "https://axeni.de"
}
-
diff --git a/assets/presets/GNOME/lockscreen.json b/assets/presets/GNOME/lockscreen.json
old mode 100644
new mode 100755
diff --git a/assets/presets/GNOME/notch.json b/assets/presets/GNOME/notch.json
old mode 100644
new mode 100755
index 24b6c249..e157ac41
--- a/assets/presets/GNOME/notch.json
+++ b/assets/presets/GNOME/notch.json
@@ -1,5 +1,5 @@
{
- "customText": "Ambxst",
+ "customText": "NothingLess",
"disableHoverExpansion": true,
"hoverRegionHeight": 16,
"keepHidden": false,
diff --git a/assets/presets/GNOME/overview.json b/assets/presets/GNOME/overview.json
old mode 100644
new mode 100755
diff --git a/assets/presets/GNOME/performance.json b/assets/presets/GNOME/performance.json
old mode 100644
new mode 100755
diff --git a/assets/presets/GNOME/theme.json b/assets/presets/GNOME/theme.json
old mode 100644
new mode 100755
diff --git a/assets/presets/GNOME/workspaces.json b/assets/presets/GNOME/workspaces.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Liquid Glass/bar.json b/assets/presets/Liquid Glass/bar.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Liquid Glass/compositor.json b/assets/presets/Liquid Glass/compositor.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Liquid Glass/desktop.json b/assets/presets/Liquid Glass/desktop.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Liquid Glass/dock.json b/assets/presets/Liquid Glass/dock.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Liquid Glass/info.json b/assets/presets/Liquid Glass/info.json
old mode 100644
new mode 100755
index a74bb85a..71cc8431
--- a/assets/presets/Liquid Glass/info.json
+++ b/assets/presets/Liquid Glass/info.json
@@ -2,4 +2,3 @@
"author": "Axenide",
"authorUrl": "https://axeni.de"
}
-
diff --git a/assets/presets/Liquid Glass/lockscreen.json b/assets/presets/Liquid Glass/lockscreen.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Liquid Glass/notch.json b/assets/presets/Liquid Glass/notch.json
old mode 100644
new mode 100755
index fe8aa080..6141748f
--- a/assets/presets/Liquid Glass/notch.json
+++ b/assets/presets/Liquid Glass/notch.json
@@ -4,6 +4,6 @@
"hoverRegionHeight": 16,
"keepHidden": false,
"noMediaDisplay": "userHost",
- "customText": "Ambxst",
+ "customText": "NothingLess",
"disableHoverExpansion": true
}
\ No newline at end of file
diff --git a/assets/presets/Liquid Glass/overview.json b/assets/presets/Liquid Glass/overview.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Liquid Glass/performance.json b/assets/presets/Liquid Glass/performance.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Liquid Glass/theme.json b/assets/presets/Liquid Glass/theme.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Liquid Glass/workspaces.json b/assets/presets/Liquid Glass/workspaces.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Manga/bar.json b/assets/presets/Manga/bar.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Manga/compositor.json b/assets/presets/Manga/compositor.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Manga/desktop.json b/assets/presets/Manga/desktop.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Manga/dock.json b/assets/presets/Manga/dock.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Manga/info.json b/assets/presets/Manga/info.json
old mode 100644
new mode 100755
index a74bb85a..71cc8431
--- a/assets/presets/Manga/info.json
+++ b/assets/presets/Manga/info.json
@@ -2,4 +2,3 @@
"author": "Axenide",
"authorUrl": "https://axeni.de"
}
-
diff --git a/assets/presets/Manga/lockscreen.json b/assets/presets/Manga/lockscreen.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Manga/notch.json b/assets/presets/Manga/notch.json
old mode 100644
new mode 100755
index 24b6c249..e157ac41
--- a/assets/presets/Manga/notch.json
+++ b/assets/presets/Manga/notch.json
@@ -1,5 +1,5 @@
{
- "customText": "Ambxst",
+ "customText": "NothingLess",
"disableHoverExpansion": true,
"hoverRegionHeight": 16,
"keepHidden": false,
diff --git a/assets/presets/Manga/overview.json b/assets/presets/Manga/overview.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Manga/performance.json b/assets/presets/Manga/performance.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Manga/theme.json b/assets/presets/Manga/theme.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Manga/workspaces.json b/assets/presets/Manga/workspaces.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Nothing/ai.json b/assets/presets/Nothing/ai.json
new file mode 100644
index 00000000..f83a9e4d
--- /dev/null
+++ b/assets/presets/Nothing/ai.json
@@ -0,0 +1,9 @@
+{
+ "systemPrompt": "You are a helpful assistant running on a Linux system. You have access to some tools to control the system.",
+ "tool": "none",
+ "extraModels": [],
+ "defaultModel": "gemini-2.0-flash",
+ "sidebarWidth": 400,
+ "sidebarPosition": "right",
+ "sidebarPinnedOnStartup": false
+}
\ No newline at end of file
diff --git a/assets/presets/Nothing/bar.json b/assets/presets/Nothing/bar.json
new file mode 100755
index 00000000..297730cf
--- /dev/null
+++ b/assets/presets/Nothing/bar.json
@@ -0,0 +1,27 @@
+{
+ "position": "top",
+ "launcherIcon": "/home/leo/ImΓ‘genes/Iconos/Nothing.png",
+ "launcherIconTint": true,
+ "launcherIconFullTint": true,
+ "launcherIconSize": 22,
+ "pillStyle": "default",
+ "screenList": [],
+ "enableFirefoxPlayer": true,
+ "barColor": [
+ [
+ "surfaceContainer",
+ 0
+ ]
+ ],
+ "frameEnabled": false,
+ "frameThickness": 8,
+ "pinnedOnStartup": true,
+ "hoverToReveal": true,
+ "hoverRegionHeight": 4,
+ "showPinButton": false,
+ "availableOnFullscreen": true,
+ "use12hFormat": true,
+ "containBar": false,
+ "keepBarShadow": false,
+ "keepBarBorder": false
+}
\ No newline at end of file
diff --git a/assets/presets/Nothing/compositor.json b/assets/presets/Nothing/compositor.json
new file mode 100755
index 00000000..ce11e4a9
--- /dev/null
+++ b/assets/presets/Nothing/compositor.json
@@ -0,0 +1,150 @@
+{
+ "showBorder": true,
+ "activeBorderColor": [
+ "primary"
+ ],
+ "borderAngle": 45,
+ "inactiveBorderColor": [
+ "surface"
+ ],
+ "inactiveBorderAngle": 45,
+ "borderSize": 2,
+ "rounding": 16,
+ "roundingPower": 2.0,
+ "syncRoundness": true,
+ "syncBorderWidth": false,
+ "syncBorderColor": false,
+ "syncShadowOpacity": false,
+ "syncShadowColor": false,
+ "resizeOnBorder": false,
+ "extendBorderGrabArea": 15,
+ "hoverIconOnBorder": true,
+ "gapsIn": 2,
+ "gapsOut": 4,
+ "layout": "dwindle",
+ "allowTearing": false,
+ "snapEnabled": true,
+ "snapWindowGap": 10,
+ "snapMonitorGap": 10,
+ "snapBorderOverlap": false,
+ "snapRespectGaps": false,
+ "activeOpacity": 1.0,
+ "inactiveOpacity": 1.0,
+ "fullscreenOpacity": 1.0,
+ "dimInactive": false,
+ "dimStrength": 0.5,
+ "dimAround": 0.4,
+ "dimSpecial": 0.2,
+ "shadowEnabled": true,
+ "shadowRange": 8,
+ "shadowRenderPower": 3,
+ "shadowSharp": false,
+ "shadowIgnoreWindow": true,
+ "shadowColor": "shadow",
+ "shadowColorInactive": "shadow",
+ "shadowOpacity": 0.5,
+ "shadowOffset": "0 0",
+ "shadowScale": 1.0,
+ "blurEnabled": true,
+ "blurSize": 4,
+ "blurPasses": 2,
+ "blurIgnoreOpacity": true,
+ "blurExplicitIgnoreAlpha": false,
+ "blurIgnoreAlphaValue": 0.2,
+ "blurNewOptimizations": true,
+ "blurXray": false,
+ "blurNoise": 0.0,
+ "blurContrast": 1.0,
+ "blurBrightness": 1.0,
+ "blurVibrancy": 0.0,
+ "blurVibrancyDarkness": 0.0,
+ "blurSpecial": true,
+ "blurPopups": false,
+ "blurPopupsIgnorealpha": 0.2,
+ "blurInputMethods": false,
+ "blurInputMethodsIgnorealpha": 0.2,
+ "animationsEnabled": true,
+ "kbLayout": "us",
+ "kbVariant": "",
+ "kbOptions": "",
+ "numlockByDefault": false,
+ "repeatRate": 25,
+ "repeatDelay": 600,
+ "mouseSensitivity": 0.0,
+ "mouseAccelProfile": "",
+ "followMouse": 1,
+ "mouseNaturalScroll": false,
+ "mouseScrollFactor": 1.0,
+ "mouseLeftHanded": false,
+ "mouseRefocus": false,
+ "floatSwitchOverrideFocus": 0,
+ "touchpadDisableWhileTyping": true,
+ "touchpadNaturalScroll": true,
+ "touchpadTapToClick": true,
+ "touchpadClickfingerBehavior": false,
+ "touchpadTapButtonMap": "",
+ "touchpadMiddleButtonEmulation": false,
+ "touchpadDragLock": 0,
+ "touchpadScrollFactor": 1.0,
+ "noHardwareCursors": false,
+ "enableHyprcursor": true,
+ "noWarps": false,
+ "persistentWarps": false,
+ "warpOnChangeWorkspace": false,
+ "cursorZoomFactor": 1.0,
+ "cursorInactiveTimeout": 0,
+ "cursorHideOnKeyPress": false,
+ "cursorHideOnTouch": false,
+ "cursorHideOnTablet": false,
+ "workspaceSwipeCreateNew": true,
+ "workspaceSwipeForever": false,
+ "workspaceSwipeCancelRatio": 0.5,
+ "workspaceSwipeMinSpeedToForce": 30,
+ "workspaceSwipeDirectionLock": true,
+ "workspaceSwipeUseR": false,
+ "workspaceSwipeDistance": 300,
+ "workspaceSwipeInvert": true,
+ "workspaceSwipeTouch": false,
+ "workspaceSwipeTouchInvert": false,
+ "dwindlePreserveSplit": true,
+ "dwindlePseudotile": false,
+ "dwindleForceSplit": 0,
+ "dwindleSmartSplit": true,
+ "dwindleDefaultSplitRatio": 1.0,
+ "dwindleSplitWidthMultiplier": 1.0,
+ "dwindlePermanentDirectionOverride": false,
+ "dwindleUseActiveForSplits": true,
+ "dwindleSmartResizing": true,
+ "dwindleSpecialScaleFactor": 0.8,
+ "masterOrientation": "left",
+ "masterMfact": 0.55,
+ "masterNewStatus": "slave",
+ "masterNewOnTop": false,
+ "masterNewOnActive": "none",
+ "masterSmartResizing": true,
+ "masterSpecialScaleFactor": 0.8,
+ "masterAllowSmallSplit": false,
+ "scrollingColumnWidth": 0.3,
+ "scrollingExplicitColumnWidths": "",
+ "scrollingDirection": "right",
+ "scrollingFullscreenOnOneColumn": true,
+ "scrollingFocusFitMethod": "center",
+ "scrollingFollowFocus": true,
+ "scrollingFollowMinVisible": 0.1,
+ "xwaylandEnabled": true,
+ "xwaylandForceZeroScaling": false,
+ "xwaylandUseNearestNeighbor": true,
+ "vrr": 0,
+ "vfr": true,
+ "mouseMoveEnablesDpms": false,
+ "keyPressEnablesDpms": false,
+ "disableAutoreload": false,
+ "focusOnActivate": false,
+ "animateManualResizes": false,
+ "animateMouseWindowdragging": true,
+ "disableHyprlandLogo": true,
+ "disableSplashRendering": false,
+ "forceDefaultWallpaper": -1,
+ "noUpdateNews": true,
+ "enforcePermissions": false
+}
diff --git a/assets/presets/Nothing/desktop.json b/assets/presets/Nothing/desktop.json
new file mode 100755
index 00000000..91444049
--- /dev/null
+++ b/assets/presets/Nothing/desktop.json
@@ -0,0 +1,6 @@
+{
+ "enabled": false,
+ "iconSize": 40,
+ "spacingVertical": 16,
+ "textColor": "overBackground"
+}
diff --git a/assets/presets/Nothing/dock.json b/assets/presets/Nothing/dock.json
new file mode 100755
index 00000000..ea13ca27
--- /dev/null
+++ b/assets/presets/Nothing/dock.json
@@ -0,0 +1,22 @@
+{
+ "enabled": true,
+ "theme": "default",
+ "position": "bottom",
+ "height": 48,
+ "iconSize": 24,
+ "spacing": 4,
+ "margin": 4,
+ "hoverRegionHeight": 16,
+ "pinnedOnStartup": false,
+ "hoverToReveal": true,
+ "availableOnFullscreen": true,
+ "showRunningIndicators": true,
+ "showPinButton": false,
+ "showOverviewButton": true,
+ "ignoredAppRegexes": [
+ "quickshell.*",
+ "xdg-desktop-portal.*"
+ ],
+ "screenList": [],
+ "keepHidden": false
+}
\ No newline at end of file
diff --git a/assets/presets/Nothing/info.json b/assets/presets/Nothing/info.json
new file mode 100755
index 00000000..cfc5046c
--- /dev/null
+++ b/assets/presets/Nothing/info.json
@@ -0,0 +1,6 @@
+{
+ "name": "Nothing",
+ "author": "Leriart",
+ "description": "Minimal and performant NothingLess preset",
+ "version": "1.0.0"
+}
diff --git a/assets/presets/Nothing/lockscreen.json b/assets/presets/Nothing/lockscreen.json
new file mode 100755
index 00000000..7da0e440
--- /dev/null
+++ b/assets/presets/Nothing/lockscreen.json
@@ -0,0 +1,3 @@
+{
+ "position": "bottom"
+}
diff --git a/assets/presets/Nothing/notch.json b/assets/presets/Nothing/notch.json
new file mode 100755
index 00000000..7a82b232
--- /dev/null
+++ b/assets/presets/Nothing/notch.json
@@ -0,0 +1,10 @@
+{
+ "theme": "default",
+ "position": "top",
+ "hoverRegionHeight": 16,
+ "keepHidden": false,
+ "noMediaDisplay": "compositor",
+ "customText": "NothingLess",
+ "disableHoverExpansion": false,
+ "showMetrics": false
+}
diff --git a/assets/presets/Nothing/overview.json b/assets/presets/Nothing/overview.json
new file mode 100755
index 00000000..e440f799
--- /dev/null
+++ b/assets/presets/Nothing/overview.json
@@ -0,0 +1,6 @@
+{
+ "rows": 2,
+ "columns": 5,
+ "scale": 0.15,
+ "workspaceSpacing": 8
+}
\ No newline at end of file
diff --git a/assets/presets/Nothing/performance.json b/assets/presets/Nothing/performance.json
new file mode 100755
index 00000000..f373f59d
--- /dev/null
+++ b/assets/presets/Nothing/performance.json
@@ -0,0 +1,8 @@
+{
+ "blurTransition": true,
+ "windowPreview": true,
+ "wavyLine": true,
+ "rotateCoverArt": true,
+ "dashboardPersistTabs": false,
+ "dashboardMaxPersistentTabs": 2
+}
\ No newline at end of file
diff --git a/assets/presets/Nothing/prefix.json b/assets/presets/Nothing/prefix.json
new file mode 100644
index 00000000..d9f71142
--- /dev/null
+++ b/assets/presets/Nothing/prefix.json
@@ -0,0 +1,7 @@
+{
+ "clipboard": "cc",
+ "emoji": "ee",
+ "tmux": "tt",
+ "wallpapers": "ww",
+ "notes": "nn"
+}
\ No newline at end of file
diff --git a/assets/presets/Nothing/system.json b/assets/presets/Nothing/system.json
new file mode 100755
index 00000000..9d3fe959
--- /dev/null
+++ b/assets/presets/Nothing/system.json
@@ -0,0 +1,29 @@
+{
+ "disks": [
+ "/"
+ ],
+ "updateServiceEnabled": true,
+ "idle": {
+ "general": {
+ "lock_cmd": "nothingless lock",
+ "before_sleep_cmd": "loginctl lock-session",
+ "after_sleep_cmd": "nothingless screen on"
+ },
+ "listeners": []
+ },
+ "ocr": {
+ "eng": true,
+ "spa": true,
+ "lat": false,
+ "jpn": false,
+ "chi_sim": false,
+ "chi_tra": false,
+ "kor": false
+ },
+ "pomodoro": {
+ "workTime": 1500,
+ "restTime": 300,
+ "autoStart": false,
+ "syncSpotify": false
+ }
+}
\ No newline at end of file
diff --git a/assets/presets/Nothing/theme.json b/assets/presets/Nothing/theme.json
new file mode 100755
index 00000000..6a3ac319
--- /dev/null
+++ b/assets/presets/Nothing/theme.json
@@ -0,0 +1,492 @@
+{
+ "oledMode": false,
+ "lightMode": false,
+ "roundness": 8,
+ "font": "Ndot",
+ "fontSize": 13,
+ "monoFont": "Ndot",
+ "monoFontSize": 13,
+ "tintIcons": true,
+ "enableCorners": true,
+ "animDuration": 250,
+ "shadowOpacity": 0.2,
+ "shadowColor": "shadow",
+ "shadowXOffset": 0,
+ "shadowYOffset": 0,
+ "shadowBlur": 1,
+ "srBg": {
+ "label": "Background",
+ "gradient": [
+ [
+ "background",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "surface",
+ "halftoneBackgroundColor": "background",
+ "border": [
+ "surfaceBright",
+ 0
+ ],
+ "itemColor": "overBackground",
+ "opacity": 1
+ },
+ "srPopup": {
+ "label": "Popup",
+ "gradient": [
+ [
+ "surfaceContainer",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "surface",
+ "halftoneBackgroundColor": "surfaceContainer",
+ "border": [
+ "surfaceBright",
+ 1
+ ],
+ "itemColor": "overBackground",
+ "opacity": 1
+ },
+ "srInternalBg": {
+ "label": "Internal BG",
+ "gradient": [
+ [
+ "surface",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "surfaceBright",
+ "halftoneBackgroundColor": "surface",
+ "border": [
+ "surfaceBright",
+ 0
+ ],
+ "itemColor": "overBackground",
+ "opacity": 1
+ },
+ "srBarBg": {
+ "label": "Bar BG",
+ "gradient": [
+ [
+ "surfaceContainer",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "surface",
+ "halftoneBackgroundColor": "surfaceContainer",
+ "border": [
+ "surfaceBright",
+ 0
+ ],
+ "itemColor": "overBackground",
+ "opacity": 0
+ },
+ "srPane": {
+ "label": "Pane",
+ "gradient": [
+ [
+ "surface",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "surfaceBright",
+ "halftoneBackgroundColor": "surface",
+ "border": [
+ "surfaceBright",
+ 0
+ ],
+ "itemColor": "overBackground",
+ "opacity": 1
+ },
+ "srCommon": {
+ "label": "Common",
+ "gradient": [
+ [
+ "surface",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "surfaceBright",
+ "halftoneBackgroundColor": "surface",
+ "border": [
+ "surfaceBright",
+ 0
+ ],
+ "itemColor": "overBackground",
+ "opacity": 1
+ },
+ "srFocus": {
+ "label": "Focus",
+ "gradient": [
+ [
+ "primary",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "surfaceVariant",
+ "halftoneBackgroundColor": "primary",
+ "border": [
+ "primary",
+ 0
+ ],
+ "itemColor": "overPrimary",
+ "opacity": 1
+ },
+ "srPrimary": {
+ "label": "Primary",
+ "gradient": [
+ [
+ "primary",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "overPrimaryContainer",
+ "halftoneBackgroundColor": "primary",
+ "border": [
+ "primary",
+ 0
+ ],
+ "itemColor": "overPrimary",
+ "opacity": 1
+ },
+ "srPrimaryFocus": {
+ "label": "Primary Focus",
+ "gradient": [
+ [
+ "overPrimaryContainer",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "primary",
+ "halftoneBackgroundColor": "overPrimaryContainer",
+ "border": [
+ "overBackground",
+ 0
+ ],
+ "itemColor": "overPrimary",
+ "opacity": 1
+ },
+ "srOverPrimary": {
+ "label": "Over Primary",
+ "gradient": [
+ [
+ "overPrimary",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "primaryContainer",
+ "halftoneBackgroundColor": "overPrimary",
+ "border": [
+ "overPrimary",
+ 0
+ ],
+ "itemColor": "primary",
+ "opacity": 1
+ },
+ "srSecondary": {
+ "label": "Secondary",
+ "gradient": [
+ [
+ "secondary",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "overSecondaryContainer",
+ "halftoneBackgroundColor": "secondary",
+ "border": [
+ "secondary",
+ 0
+ ],
+ "itemColor": "overSecondary",
+ "opacity": 1
+ },
+ "srSecondaryFocus": {
+ "label": "Secondary Focus",
+ "gradient": [
+ [
+ "overSecondaryContainer",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "secondary",
+ "halftoneBackgroundColor": "overSecondaryContainer",
+ "border": [
+ "overBackground",
+ 0
+ ],
+ "itemColor": "overSecondary",
+ "opacity": 1
+ },
+ "srOverSecondary": {
+ "label": "Over Secondary",
+ "gradient": [
+ [
+ "overSecondary",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "secondaryContainer",
+ "halftoneBackgroundColor": "overSecondary",
+ "border": [
+ "overSecondary",
+ 0
+ ],
+ "itemColor": "secondary",
+ "opacity": 1
+ },
+ "srTertiary": {
+ "label": "Tertiary",
+ "gradient": [
+ [
+ "tertiary",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "overTertiaryContainer",
+ "halftoneBackgroundColor": "tertiary",
+ "border": [
+ "tertiary",
+ 0
+ ],
+ "itemColor": "overTertiary",
+ "opacity": 1
+ },
+ "srTertiaryFocus": {
+ "label": "Tertiary Focus",
+ "gradient": [
+ [
+ "overTertiaryContainer",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "tertiary",
+ "halftoneBackgroundColor": "overTertiaryContainer",
+ "border": [
+ "overBackground",
+ 0
+ ],
+ "itemColor": "overTertiary",
+ "opacity": 1
+ },
+ "srOverTertiary": {
+ "label": "Over Tertiary",
+ "gradient": [
+ [
+ "overTertiary",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "tertiaryContainer",
+ "halftoneBackgroundColor": "overTertiary",
+ "border": [
+ "overTertiary",
+ 0
+ ],
+ "itemColor": "tertiary",
+ "opacity": 1
+ },
+ "srError": {
+ "label": "Error",
+ "gradient": [
+ [
+ "error",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "overErrorContainer",
+ "halftoneBackgroundColor": "error",
+ "border": [
+ "error",
+ 0
+ ],
+ "itemColor": "overError",
+ "opacity": 1
+ },
+ "srErrorFocus": {
+ "label": "Error Focus",
+ "gradient": [
+ [
+ "overBackground",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "error",
+ "halftoneBackgroundColor": "overErrorContainer",
+ "border": [
+ "overBackground",
+ 0
+ ],
+ "itemColor": "overError",
+ "opacity": 1
+ },
+ "srOverError": {
+ "label": "Over Error",
+ "gradient": [
+ [
+ "overError",
+ 0
+ ]
+ ],
+ "gradientType": "linear",
+ "gradientAngle": 0,
+ "gradientCenterX": 0.5,
+ "gradientCenterY": 0.5,
+ "halftoneDotMin": 0,
+ "halftoneDotMax": 0,
+ "halftoneStart": 0,
+ "halftoneEnd": 1,
+ "halftoneDotColor": "errorContainer",
+ "halftoneBackgroundColor": "overError",
+ "border": [
+ "overError",
+ 0
+ ],
+ "itemColor": "error",
+ "opacity": 1
+ }
+}
\ No newline at end of file
diff --git a/assets/presets/Nothing/weather.json b/assets/presets/Nothing/weather.json
new file mode 100644
index 00000000..93a2fe47
--- /dev/null
+++ b/assets/presets/Nothing/weather.json
@@ -0,0 +1,4 @@
+{
+ "location": "",
+ "unit": "C"
+}
\ No newline at end of file
diff --git a/assets/presets/Nothing/workspaces.json b/assets/presets/Nothing/workspaces.json
new file mode 100755
index 00000000..e9dc1c34
--- /dev/null
+++ b/assets/presets/Nothing/workspaces.json
@@ -0,0 +1,7 @@
+{
+ "shown": 10,
+ "showAppIcons": false,
+ "alwaysShowNumbers": false,
+ "showNumbers": false,
+ "dynamic": true
+}
\ No newline at end of file
diff --git a/assets/presets/Retro/bar.json b/assets/presets/Retro/bar.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Retro/compositor.json b/assets/presets/Retro/compositor.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Retro/desktop.json b/assets/presets/Retro/desktop.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Retro/dock.json b/assets/presets/Retro/dock.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Retro/info.json b/assets/presets/Retro/info.json
old mode 100644
new mode 100755
index a74bb85a..71cc8431
--- a/assets/presets/Retro/info.json
+++ b/assets/presets/Retro/info.json
@@ -2,4 +2,3 @@
"author": "Axenide",
"authorUrl": "https://axeni.de"
}
-
diff --git a/assets/presets/Retro/lockscreen.json b/assets/presets/Retro/lockscreen.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Retro/notch.json b/assets/presets/Retro/notch.json
old mode 100644
new mode 100755
index 238f533a..36176e3e
--- a/assets/presets/Retro/notch.json
+++ b/assets/presets/Retro/notch.json
@@ -1,5 +1,5 @@
{
- "customText": "Ambxst",
+ "customText": "NothingLess",
"disableHoverExpansion": true,
"hoverRegionHeight": 16,
"keepHidden": false,
diff --git a/assets/presets/Retro/overview.json b/assets/presets/Retro/overview.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Retro/performance.json b/assets/presets/Retro/performance.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Retro/theme.json b/assets/presets/Retro/theme.json
old mode 100644
new mode 100755
diff --git a/assets/presets/Retro/workspaces.json b/assets/presets/Retro/workspaces.json
old mode 100644
new mode 100755
diff --git a/assets/screenshots/1.png b/assets/screenshots/1.png
deleted file mode 100644
index c6d094ec..00000000
Binary files a/assets/screenshots/1.png and /dev/null differ
diff --git a/assets/screenshots/10.png b/assets/screenshots/10.png
deleted file mode 100644
index 135d79e6..00000000
Binary files a/assets/screenshots/10.png and /dev/null differ
diff --git a/assets/screenshots/2.png b/assets/screenshots/2.png
deleted file mode 100644
index 7faa2df7..00000000
Binary files a/assets/screenshots/2.png and /dev/null differ
diff --git a/assets/screenshots/3.png b/assets/screenshots/3.png
deleted file mode 100644
index 07fff566..00000000
Binary files a/assets/screenshots/3.png and /dev/null differ
diff --git a/assets/screenshots/4.png b/assets/screenshots/4.png
deleted file mode 100644
index 8a66090f..00000000
Binary files a/assets/screenshots/4.png and /dev/null differ
diff --git a/assets/screenshots/5.png b/assets/screenshots/5.png
deleted file mode 100644
index 74747362..00000000
Binary files a/assets/screenshots/5.png and /dev/null differ
diff --git a/assets/screenshots/6.png b/assets/screenshots/6.png
deleted file mode 100644
index 1fbb986d..00000000
Binary files a/assets/screenshots/6.png and /dev/null differ
diff --git a/assets/screenshots/7.png b/assets/screenshots/7.png
deleted file mode 100644
index 21131589..00000000
Binary files a/assets/screenshots/7.png and /dev/null differ
diff --git a/assets/screenshots/8.png b/assets/screenshots/8.png
deleted file mode 100644
index af5b1082..00000000
Binary files a/assets/screenshots/8.png and /dev/null differ
diff --git a/assets/screenshots/9.png b/assets/screenshots/9.png
deleted file mode 100644
index a993561c..00000000
Binary files a/assets/screenshots/9.png and /dev/null differ
diff --git a/assets/screenshots/gaming.png b/assets/screenshots/gaming.png
new file mode 100644
index 00000000..694c82d3
Binary files /dev/null and b/assets/screenshots/gaming.png differ
diff --git a/assets/screenshots/settings.png b/assets/screenshots/settings.png
new file mode 100644
index 00000000..3a44dffd
Binary files /dev/null and b/assets/screenshots/settings.png differ
diff --git a/assets/social-preview.html b/assets/social-preview.html
new file mode 100644
index 00000000..7657103e
--- /dev/null
+++ b/assets/social-preview.html
@@ -0,0 +1,289 @@
+
+
+
+
+
+NothingLess Social Preview
+
+
+
+
+
+
+
+
+
+
β¦ v0.1
+
+
+
+
+ NOTHING LESS
+
+
A WAYLAND SHELL BY LERIART
+
+
+
+
+
+
Built with Quickshell
+
+ High-performance , deeply
+ customizable Wayland shell.
+ Less is more.
+
+
+ Hyprland
+ Quickshell
+ Qt6
+ NixOS
+ Arch
+ Fedora
+
+
+
+
+
+ github.com/Leriart/NothingLess
+ |
+ Forked from Ambxst
+ |
+ MIT License
+
+
+
+
diff --git a/assets/social-preview.png b/assets/social-preview.png
new file mode 100644
index 00000000..a9fa1528
Binary files /dev/null and b/assets/social-preview.png differ
diff --git a/assets/sound/attribution.txt b/assets/sound/attribution.txt
old mode 100644
new mode 100755
diff --git a/assets/sound/polite-warning-tone.wav b/assets/sound/polite-warning-tone.wav
old mode 100644
new mode 100755
diff --git a/assets/wallpapers_example/ambxst-01.png b/assets/wallpapers_example/ambxst-01.png
deleted file mode 100644
index 72ec8a57..00000000
Binary files a/assets/wallpapers_example/ambxst-01.png and /dev/null differ
diff --git a/assets/wallpapers_example/ambxst-02.png b/assets/wallpapers_example/ambxst-02.png
deleted file mode 100644
index e96dc429..00000000
Binary files a/assets/wallpapers_example/ambxst-02.png and /dev/null differ
diff --git a/assets/wallpapers_example/ambxst-03.png b/assets/wallpapers_example/ambxst-03.png
deleted file mode 100644
index 90115533..00000000
Binary files a/assets/wallpapers_example/ambxst-03.png and /dev/null differ
diff --git a/assets/wallpapers_example/ambxst-04.png b/assets/wallpapers_example/ambxst-04.png
deleted file mode 100644
index c40a983f..00000000
Binary files a/assets/wallpapers_example/ambxst-04.png and /dev/null differ
diff --git a/assets/wallpapers_example/ambxst-05.png b/assets/wallpapers_example/ambxst-05.png
deleted file mode 100644
index b4c2fbf8..00000000
Binary files a/assets/wallpapers_example/ambxst-05.png and /dev/null differ
diff --git a/assets/wallpapers_example/ambxst-06.png b/assets/wallpapers_example/ambxst-06.png
deleted file mode 100644
index b39f0181..00000000
Binary files a/assets/wallpapers_example/ambxst-06.png and /dev/null differ
diff --git a/assets/wallpapers_example/ambxst-07.png b/assets/wallpapers_example/ambxst-07.png
deleted file mode 100644
index 85ed6d7a..00000000
Binary files a/assets/wallpapers_example/ambxst-07.png and /dev/null differ
diff --git a/assets/wallpapers_example/ambxst-08.png b/assets/wallpapers_example/ambxst-08.png
deleted file mode 100644
index 348632cc..00000000
Binary files a/assets/wallpapers_example/ambxst-08.png and /dev/null differ
diff --git a/assets/wallpapers_example/ambxst-09.png b/assets/wallpapers_example/ambxst-09.png
deleted file mode 100644
index c52985c7..00000000
Binary files a/assets/wallpapers_example/ambxst-09.png and /dev/null differ
diff --git a/assets/wallpapers_example/ambxst-10.png b/assets/wallpapers_example/ambxst-10.png
deleted file mode 100644
index 21059f10..00000000
Binary files a/assets/wallpapers_example/ambxst-10.png and /dev/null differ
diff --git a/cli.sh b/cli.sh
index 98274b12..df622c92 100755
--- a/cli.sh
+++ b/cli.sh
@@ -1,13 +1,13 @@
#!/usr/bin/env bash
-# Ambxst CLI - It was needed, so here it is. lol
+# NothingLess CLI - Minimal NothingLess fork - It was needed, so here it is. lol
set -euo pipefail
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
# Use environment variables if set by flake, otherwise fall back to PATH
-QS_BIN="${AMBXST_QS:-qs}"
-NIXGL_BIN="${AMBXST_NIXGL:-}"
+QS_BIN="${NOTHINGLESS_QS:-qs}"
+NIXGL_BIN="${NOTHINGLESS_NIXGL:-}"
if [ -z "${QML2_IMPORT_PATH:-}" ]; then
if command -v qs >/dev/null 2>&1; then
@@ -22,8 +22,8 @@ fi
# Ensure config files exist - copy from preset if missing
ensure_config_files() {
- local config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/ambxst/config"
- local preset_dir="${SCRIPT_DIR}/assets/presets/Ambxst Default"
+ local config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/nothingless/config"
+ local preset_dir="${SCRIPT_DIR}/assets/presets/NothingLess Default"
# Create config directory if it doesn't exist
mkdir -p "$config_dir"
@@ -39,13 +39,13 @@ ensure_config_files
show_help() {
cat < [monitor] Set brightness (0-100)
@@ -54,65 +54,85 @@ Commands:
brightness -r [monitor] Restore saved brightness
brightness -l List monitors and their brightness
help Show this help message
- version, -v, --version Show Ambxst version
- goodbye Uninstall Ambxst :(
+ version, -v, --version Show NothingLess version
+ goodbye Uninstall NothingLess :(
install Install compositor config (hyprland)
+ install hyprland --lua Install with Lua config (Hyprland >= 0.48)
+ install hyprland --conf Install with config file (default, safe)
remove Remove compositor config (hyprland)
Examples:
- ambxst brightness 75 Set all monitors to 75%
- ambxst brightness 50 HDMI-A-1 Set HDMI-A-1 to 50%
- ambxst brightness +10 Increase brightness by 10%
- ambxst brightness -5 HDMI-A-1 Decrease HDMI-A-1 brightness by 5%
- ambxst brightness 10 -s Save current, then set all to 10%
- ambxst brightness -s HDMI-A-1 Save current brightness of HDMI-A-1
- ambxst brightness -r Restore saved brightness
+ nothingless brightness 75 Set all monitors to 75%
+ nothingless brightness 50 HDMI-A-1 Set HDMI-A-1 to 50%
+ nothingless brightness +10 Increase brightness by 10%
+ nothingless brightness -5 HDMI-A-1 Decrease HDMI-A-1 brightness by 5%
+ nothingless brightness 10 -s Save current, then set all to 10%
+ nothingless brightness -s HDMI-A-1 Save current brightness of HDMI-A-1
+ nothingless brightness -r Restore saved brightness
EOF
}
-AMBXST_HYPR_SOURCE="source = ~/.local/share/ambxst/hyprland.conf"
-AMBXST_HYPR_BLOCK=$(
+NOTHINGLESS_HYPR_CONF_SOURCE="source = ~/.local/share/nothingless/hyprland.conf"
+NOTHINGLESS_HYPR_LUA_SOURCE='loadfile(os.getenv("HOME") .. "/.local/share/nothingless/hyprland.lua")()'
+NOTHINGLESS_HYPR_CONF_BLOCK=$(
cat <<'EOF'
-# Ambxst
-source = ~/.local/share/ambxst/hyprland.conf
+# NothingLess
+source = ~/.local/share/nothingless/hyprland.conf
# OVERRIDES
-# Down here you can write or source anything that you want to override from Ambxst's settings.
+# Down here you can write or source anything that you want to override from NothingLess's settings.
EOF
)
+NOTHINGLESS_HYPR_LUA_BLOCK=$(
+ cat <<'EOF'
+-- NothingLess
+loadfile(os.getenv("HOME") .. "/.local/share/nothingless/hyprland.lua")()
-append_ambxst_hyprland_block() {
+-- OVERRIDES
+-- Down here you can write or source anything that you want to override from NothingLess's settings.
+EOF
+)
+
+append_nothingless_hyprland_block() {
local conf="$1"
+ local source="$2"
+ local block="$3"
- if [ -f "$conf" ] && grep -qF "$AMBXST_HYPR_SOURCE" "$conf"; then
- echo "Ambxst Hyprland block already present in $conf"
+ if [ -f "$conf" ] && grep -qF "$source" "$conf"; then
+ echo "NothingLess Hyprland block already present in $conf"
return 0
fi
if [ -f "$conf" ] && [ -s "$conf" ]; then
- printf "\n%s\n" "$AMBXST_HYPR_BLOCK" >>"$conf"
+ printf "\n%s\n" "$block" >>"$conf"
else
- printf "%s\n" "$AMBXST_HYPR_BLOCK" >"$conf"
+ printf "%s\n" "$block" >"$conf"
fi
- echo "Added Ambxst Hyprland block to $conf"
+ echo "Added NothingLess Hyprland block to $conf"
}
-remove_ambxst_hyprland_block() {
+remove_nothingless_hyprland_block() {
local conf="$1"
+ local source="$2"
if [ ! -f "$conf" ]; then
echo "$conf does not exist"
return 0
fi
- awk -v source="$AMBXST_HYPR_SOURCE" '
+ awk -v source="$source" '
function is_remove(line) {
return line == source \
- || line == "# Ambxst" \
+ || line == "# NothingLess" \
+ || line == "-- NothingLess" \
|| line == "# OVERRIDES" \
- || line == "# Down here you can write or source anything that you want to override from Ambxst'\''s settings."
+ || line == "-- OVERRIDES" \
+ || line == "# Down here you can write or source anything that you want to override from NothingLess'\''s settings." \
+ || line == "-- Down here you can write or source anything that you want to override from NothingLess'\''s settings." \
+ || line == "exec-once = nothingless" \
+ || line == "exec-once = axctl -c ~/.local/share/nothingless/axctl.toml daemon"
}
{
lines[NR] = $0
@@ -132,10 +152,10 @@ remove_ambxst_hyprland_block() {
}
' "$conf" >"${conf}.tmp" && mv "${conf}.tmp" "$conf"
- echo "Removed Ambxst Hyprland block from $conf"
+ echo "Removed NothingLess Hyprland block from $conf"
}
-find_ambxst_pid() {
+find_nothingless_pid() {
# Try to find QuickShell process running shell.qml
# QuickShell binary can be named 'qs' or 'quickshell'
local pid
@@ -165,9 +185,9 @@ find_ambxst_pid() {
echo "$pid"
}
-find_ambxst_pid_cached() {
+find_nothingless_pid_cached() {
# Optimized PID lookup: check cache file first, then fall back to pgrep
- local pid_file="/tmp/ambxst.pid"
+ local pid_file="/tmp/nothingless.pid"
local pid=""
# Check if cache file exists and process is alive
@@ -183,48 +203,84 @@ find_ambxst_pid_cached() {
fi
# Fallback: use expensive pgrep search
- pid=$(find_ambxst_pid)
+ pid=$(find_nothingless_pid)
echo "$pid"
}
-restart_ambxst() {
+restart_nothingless() {
# Kill axctl processes first (they survive parent death when forked/detached)
pkill -f "axctl.*daemon" 2>/dev/null || true
pkill -f "axctl subscribe" 2>/dev/null || true
- PID=$(find_ambxst_pid_cached)
+ PID=$(find_nothingless_pid_cached)
if [ -n "$PID" ]; then
- echo "Stopping Ambxst (PID $PID)..."
+ echo "Stopping NothingLess (PID $PID)..."
kill "$PID"
# Wait for process to exit
while kill -0 "$PID" 2>/dev/null; do
sleep 0.1
done
fi
- echo "Starting Ambxst..."
+ echo "Starting NothingLess..."
# Relaunch the script in background
nohup "$0" >/dev/null 2>&1 &
}
case "${1:-}" in
update)
- echo "Updating Ambxst..."
- curl -fsSL get.axeni.de/ambxst | sh
- restart_ambxst
+ echo "Updating NothingLess..."
+ curl -fsSL github.com/Leriart/NothingLess/nothingless | sh
+ restart_nothingless
;;
refresh)
- echo "Refreshing Ambxst profile..."
- exec nix profile upgrade Ambxst --refresh --impure
+ echo "Refreshing NothingLess profile..."
+ exec nix profile upgrade NothingLess --refresh --impure
;;
run)
CMD="${2:-}"
- PIPE="/tmp/ambxst_ipc.pipe"
+ PIPE="/tmp/nothingless_ipc.pipe"
if [ -z "$CMD" ]; then
echo "Error: No command specified for run"
exit 1
fi
+ # toggle-metrics: write directly to notch.json (no IPC needed)
+ if [ "$CMD" = "toggle-metrics" ]; then
+ # Debounce: prevent double-fire from Hyprland key repeat
+ LOCK_FILE="/tmp/nothingless_toggle_metrics.lock"
+ if [ -f "$LOCK_FILE" ]; then
+ last_run=$(cat "$LOCK_FILE")
+ now=$(date +%s%N)
+ elapsed=$(( (now - last_run) / 1000000 ))
+ if [ "$elapsed" -lt 500 ]; then
+ exit 0
+ fi
+ fi
+ date +%s%N > "$LOCK_FILE"
+
+ NOTCH_JSON="${XDG_CONFIG_HOME:-$HOME/.config}/nothingless/config/notch.json"
+ if [ -f "$NOTCH_JSON" ]; then
+ # Toggle showMetrics in the JSON
+ python3 -c "
+import json
+with open('$NOTCH_JSON') as f:
+ cfg = json.load(f)
+cfg['showMetrics'] = not cfg.get('showMetrics', False)
+with open('$NOTCH_JSON', 'w') as f:
+ json.dump(cfg, f, indent=2)
+print('Metrics toggled to', cfg['showMetrics'])
+" 2>&1 || {
+ echo "Error: Failed to toggle metrics"
+ exit 1
+ }
+ exit 0
+ else
+ echo "Error: notch.json not found at $NOTCH_JSON"
+ exit 1
+ fi
+ fi
+
# Fast path: Write directly to pipe if it exists (Zero latency)
if [ -p "$PIPE" ]; then
echo "$CMD" >"$PIPE" &
@@ -232,42 +288,42 @@ run)
fi
# Fallback path: Use QS IPC with cached PID lookup
- PID=$(find_ambxst_pid_cached)
+ PID=$(find_nothingless_pid_cached)
if [ -z "$PID" ]; then
- echo "Error: Ambxst is not running"
+ echo "Error: NothingLess is not running"
exit 1
fi
- qs ipc --pid "$PID" call ambxst run "$CMD" 2>/dev/null || {
+ qs ipc --pid "$PID" call nothingless run "$CMD" 2>/dev/null || {
echo "Error: Could not run command '$CMD'"
exit 1
}
;;
lock)
- PID=$(find_ambxst_pid_cached)
+ PID=$(find_nothingless_pid_cached)
if [ -z "$PID" ]; then
- echo "Error: Ambxst is not running"
+ echo "Error: NothingLess is not running"
exit 1
fi
- qs ipc --pid "$PID" call ambxst run lockscreen 2>/dev/null || {
+ qs ipc --pid "$PID" call nothingless run lockscreen 2>/dev/null || {
echo "Error: Could not activate lockscreen"
exit 1
}
;;
reload)
- restart_ambxst
+ restart_nothingless
;;
quit)
# Kill axctl processes first
pkill -f "axctl.*daemon" 2>/dev/null || true
pkill -f "axctl subscribe" 2>/dev/null || true
- PID=$(find_ambxst_pid_cached)
+ PID=$(find_nothingless_pid_cached)
if [ -n "$PID" ]; then
- echo "Stopping Ambxst (PID $PID)..."
+ echo "Stopping NothingLess (PID $PID)..."
kill "$PID"
else
- echo "Ambxst is not running"
+ echo "NothingLess is not running"
fi
;;
screen)
@@ -285,7 +341,7 @@ screen)
notify-send "Screen On" "Not supported on this compositor yet"
fi
else
- echo "Usage: ambxst screen [on|off]"
+ echo "Usage: nothingless screen [on|off]"
exit 1
fi
;;
@@ -300,13 +356,13 @@ suspend)
fi
;;
brightness)
- PID=$(find_ambxst_pid_cached)
+ PID=$(find_nothingless_pid_cached)
if [ -z "$PID" ]; then
- echo "Error: Ambxst is not running"
+ echo "Error: NothingLess is not running"
exit 1
fi
- BRIGHTNESS_SAVE_FILE="/tmp/ambxst_brightness_saved.txt"
+ BRIGHTNESS_SAVE_FILE="/tmp/nothingless_brightness_saved.txt"
# Parse arguments
ARG2="${2:-}"
@@ -431,7 +487,7 @@ brightness)
exit 0
else
echo "Error: Invalid brightness value. Must be 0-100 or +/-delta."
- echo "Run 'ambxst help' for usage information"
+ echo "Run 'nothingless help' for usage information"
exit 1
fi
@@ -520,35 +576,127 @@ brightness)
fi
;;
version | -v | --version)
- echo "Ambxst $(cat "${SCRIPT_DIR}/version")"
+ echo "NothingLess $(cat "${SCRIPT_DIR}/version")"
;;
install)
TARGET="${2:-}"
+ MODE="auto"
+
+ # Parse optional flags
if [ "$TARGET" = "hyprland" ]; then
- HYPR_CONF="$HOME/.config/hypr/hyprland.conf"
+ shift 2 2>/dev/null || true
+ for arg in "$@"; do
+ case "$arg" in
+ --lua) MODE="lua" ;;
+ --conf) MODE="conf" ;;
+ *) echo "Warning: Unknown option '$arg'. Use --lua or --conf." ;;
+ esac
+ done
+ elif [ "$TARGET" != "hyprland" ]; then
+ echo "Error: Unknown target '$TARGET'. Supported: hyprland"
+ exit 1
+ fi
+
+ HYPR_DIR="$HOME/.config/hypr"
+ HYPR_LUA="$HYPR_DIR/hyprland.lua"
+ HYPR_CONF="$HYPR_DIR/hyprland.conf"
+ SHARE_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/nothingless"
+
+ # Create directories if needed
+ mkdir -p "$HYPR_DIR"
+ mkdir -p "$SHARE_DIR"
+
+ # ---- Base config content - always valid conf syntax ----
+ BASE_CONF=$(cat <<-'ENDCONF'
+exec-once = sh -c '[ -f /tmp/.nl_booted ] || { touch /tmp/.nl_booted && nothingless; }'
+
+# Core binds β baseline for Hyprland reload recovery
+bind = SUPER, Super_L, exec, nothingless run launcher
+bind = SUPER, D, exec, nothingless run dashboard
+bind = SUPER, A, exec, nothingless run assistant
+bind = SUPER, V, exec, nothingless run clipboard
+bind = SUPER, PERIOD, exec, nothingless run emoji
+bind = SUPER, N, exec, nothingless run notes
+bind = SUPER, T, exec, nothingless run tmux
+bind = SUPER, COMMA, exec, nothingless run wallpapers
+bind = SUPER, L, exec, nothingless lock
+bind = SUPER, TAB, exec, nothingless run overview
+bind = SUPER, ESCAPE, exec, nothingless run powermenu
+bind = SUPER, S, exec, nothingless run tools
+bind = SUPER SHIFT, C, exec, nothingless run config
+bind = SUPER SHIFT, S, exec, nothingless run screenshot
+bind = SUPER SHIFT, R, exec, nothingless run screenrecord
+bind = SUPER SHIFT, A, exec, nothingless run lens
+bind = SUPER SHIFT, BACKSPACE, exec, nothingless run toggle-metrics
+ENDCONF
+ )
+
+ # ---- Detect mode if auto ----
+ if [ "$MODE" = "auto" ]; then
+ if [ -f "$HYPR_LUA" ]; then
+ # User already has hyprland.lua β stick with lua mode
+ MODE="lua"
+ elif [ -f "$HYPR_CONF" ]; then
+ # User has hyprland.conf β use conf mode
+ MODE="conf"
+ else
+ # No config exists yet β default to conf (safe)
+ MODE="conf"
+ fi
+ fi
+
+ # ---- Generate config files ----
+ if [ "$MODE" = "lua" ]; then
+ # Write sourced file as valid Lua: returns the config as a string
+ # Hyprland's Lua mode (>= 0.48) expects valid Lua; returning a string
+ # is the simplest way to embed conf syntax inside Lua.
+ {
+ printf "return [[\n"
+ printf "%s\n" "$BASE_CONF"
+ printf "]]\n"
+ } > "$SHARE_DIR/hyprland.lua"
- # Create directory if needed
- mkdir -p "$HOME/.config/hypr"
+ echo "Created compositor Lua config at $SHARE_DIR/hyprland.lua"
- append_ambxst_hyprland_block "$HYPR_CONF"
+ # Main Hyprland config: inject NothingLess block into hyprland.lua
+ append_nothingless_hyprland_block "$HYPR_LUA" "$NOTHINGLESS_HYPR_LUA_SOURCE" "$NOTHINGLESS_HYPR_LUA_BLOCK"
+
+ # Clean up stale .conf if switching from conf to lua
+ rm -f "$HYPR_CONF" 2>/dev/null || true
else
- echo "Error: Unknown target '$TARGET'. Supported: hyprland"
- exit 1
+ # Write the plain conf version
+ printf "%s\n" "$BASE_CONF" > "$SHARE_DIR/hyprland.conf"
+
+ echo "Created compositor config at $SHARE_DIR/hyprland.conf"
+
+ # Main Hyprland config: inject NothingLess block into hyprland.conf
+ append_nothingless_hyprland_block "$HYPR_CONF" "$NOTHINGLESS_HYPR_CONF_SOURCE" "$NOTHINGLESS_HYPR_CONF_BLOCK"
+
+ # Clean up stale .lua if switching from lua to conf
+ rm -f "$HYPR_LUA" 2>/dev/null || true
fi
;;
remove)
TARGET="${2:-}"
if [ "$TARGET" = "hyprland" ]; then
- HYPR_CONF="$HOME/.config/hypr/hyprland.conf"
+ HYPR_DIR="$HOME/.config/hypr"
+ HYPR_LUA="$HYPR_DIR/hyprland.lua"
+ HYPR_CONF="$HYPR_DIR/hyprland.conf"
+
+ remove_nothingless_hyprland_block "$HYPR_LUA" "$NOTHINGLESS_HYPR_LUA_SOURCE"
+ remove_nothingless_hyprland_block "$HYPR_CONF" "$NOTHINGLESS_HYPR_CONF_SOURCE"
- remove_ambxst_hyprland_block "$HYPR_CONF"
+ # Clean up stale .lua if user switched from lua to conf mode
+ if [ ! -f "$HYPR_CONF" ] && [ -f "$HYPR_LUA" ] && [ ! -s "$HYPR_LUA" ] || grep -q "NothingLess" "$HYPR_LUA" 2>/dev/null; then
+ rm -f "$HYPR_LUA" 2>/dev/null || true
+ fi
else
echo "Error: Unknown target '$TARGET'. Supported: hyprland"
exit 1
fi
;;
goodbye)
- echo "Uninstalling Ambxst..."
+ echo "Uninstalling NothingLess..."
read -p "Are you sure? (y/N): " -n 1 -r
echo
@@ -558,13 +706,13 @@ goodbye)
fi
if [ -f /etc/NIXOS ]; then
- if nix profile list 2>/dev/null | grep -q "Ambxst"; then
+ if nix profile list 2>/dev/null | grep -q "NothingLess"; then
echo "Removing from nix profile..."
- nix profile remove Ambxst
- elif command -v ambxst >/dev/null 2>&1; then
- echo "Ambxst was declared in this system. Please remove it from your configuration in order to uninstall."
+ nix profile remove NothingLess
+ elif command -v nothingless >/dev/null 2>&1; then
+ echo "NothingLess was declared in this system. Please remove it from your configuration in order to uninstall."
else
- echo "Ambxst is not installed."
+ echo "NothingLess is not installed."
fi
exit 0
fi
@@ -576,21 +724,30 @@ goodbye)
REMOVE_CONFIG=true
fi
- rm -rf "$HOME/.local/src/ambxst"
- rm -rf "$HOME/.local/share/ambxst"
- rm -rf "$HOME/.local/state/ambxst"
+ rm -rf "$HOME/.local/src/nothingless"
+ rm -rf "$HOME/.local/share/nothingless"
+ rm -rf "$HOME/.local/state/nothingless"
if [ "$REMOVE_CONFIG" = true ]; then
- rm -rf "$HOME/.config/ambxst"
+ rm -rf "$HOME/.config/nothingless"
echo "Configuration files removed."
fi
- echo "Ambxst uninstalled. :("
+ echo "NothingLess uninstalled. :("
;;
help | --help | -h)
show_help
;;
"")
+ # Prevent duplicate instances: if NothingLess is already running, exit.
+ # This handles Hyprland config reloads where exec-once is re-executed
+ # and the daemon tries to start a second NothingLess.
+ EXISTING_PID=$(find_nothingless_pid_cached)
+ if [ -n "$EXISTING_PID" ]; then
+ echo "NothingLess is already running (PID $EXISTING_PID), not starting duplicate."
+ exit 0
+ fi
+
# Run daemon priority script (backgrounded to not block startup)
bash "${SCRIPT_DIR}/scripts/daemon_priority.sh" &
@@ -603,9 +760,20 @@ help | --help | -h)
# Force Qt6CT
export QT_QPA_PLATFORMTHEME=qt6ct
+ unset HL_INITIAL_WORKSPACE_TOKEN
+
+ # Set Qt rendering backend from compositor config (opengl or vulkan)
+ COMPOSITOR_CFG="${XDG_CONFIG_HOME:-$HOME/.config}/nothingless/config/compositor.json"
+ if [ -f "$COMPOSITOR_CFG" ]; then
+ RHI_BACKEND=$(python3 -c "import json; print(json.load(open('$COMPOSITOR_CFG')).get('renderBackend','opengl'))" 2>/dev/null || echo "opengl")
+ else
+ RHI_BACKEND="opengl"
+ fi
+ export QSG_RHI_BACKEND="$RHI_BACKEND"
+ export QSG_RENDER_LOOP="threaded"
# Cache this script's PID before exec (for fast PID lookups in future CLI calls)
- echo $$ >/tmp/ambxst.pid
+ echo $$ >/tmp/nothingless.pid
# Launch QuickShell with the main shell.qml
# If NIXGL_BIN is set (NixOS/Nix setup), use it. Otherwise, just run qs directly.
@@ -617,7 +785,7 @@ help | --help | -h)
;;
*)
echo "Error: Unknown command '$1'"
- echo "Run 'ambxst help' for usage information"
+ echo "Run 'nothingless help' for usage information"
exit 1
;;
esac
diff --git a/config/AGENTS.md b/config/AGENTS.md
old mode 100644
new mode 100755
index f2476462..9e00f8c5
--- a/config/AGENTS.md
+++ b/config/AGENTS.md
@@ -1,7 +1,7 @@
# CONFIG KNOWLEDGE BASE
## OVERVIEW
-Reactive, file-backed configuration system built on `Quickshell.Io`. Source of truth for all shell modules. Stores JSON in `~/.config/ambxst/config/`. Gracefully handles missing/malformed files by falling back to hardcoded defaults.
+Reactive, file-backed configuration system built on `Quickshell.Io`. Source of truth for all shell modules. Stores JSON in `~/.config/NothingLess/config/`. Gracefully handles missing/malformed files by falling back to hardcoded defaults.
## STRUCTURE
- **Config.qml**: Core singleton (>3100 lines). `FileView` monitors disk; `JsonAdapter` creates bidirectional QML bindings. Each module domain (bar, theme, ai, dock, etc.) has its own `FileView`/`JsonAdapter` pair.
diff --git a/config/Config.qml b/config/Config.qml
old mode 100644
new mode 100755
index 1d7cc321..e11aee4c
--- a/config/Config.qml
+++ b/config/Config.qml
@@ -34,9 +34,9 @@ Singleton {
onLoaded: root.version = text().trim()
}
- property string configDir: (Quickshell.env("XDG_CONFIG_HOME") || (Quickshell.env("HOME") + "/.config")) + "/ambxst/config"
- property string keybindsPath: (Quickshell.env("XDG_CONFIG_HOME") || (Quickshell.env("HOME") + "/.config")) + "/ambxst/binds.json"
- property string presetDir: Qt.resolvedUrl("../assets/presets/Ambxst Default").toString().replace("file://", "")
+ property string configDir: (Quickshell.env("XDG_CONFIG_HOME") || (Quickshell.env("HOME") + "/.config")) + "/nothingless/config"
+ property string keybindsPath: (Quickshell.env("XDG_CONFIG_HOME") || (Quickshell.env("HOME") + "/.config")) + "/nothingless/binds.json"
+ property string presetDir: Qt.resolvedUrl("../assets/presets/Nothing").toString().replace("file://", "")
property bool pauseAutoSave: false
@@ -534,6 +534,7 @@ Singleton {
property string pillStyle: "default"
property list screenList: []
property bool enableFirefoxPlayer: false
+ property bool enableChromiumPlayer: false
property list barColor: [["surface", 0.0]]
property bool frameEnabled: false
property int frameThickness: 6
@@ -547,6 +548,7 @@ Singleton {
property bool containBar: false
property bool keepBarShadow: false
property bool keepBarBorder: false
+ property var hiddenIcons: []
}
}
@@ -675,8 +677,9 @@ Singleton {
property int hoverRegionHeight: 8
property bool keepHidden: false
property string noMediaDisplay: "userHost"
- property string customText: "Ambxst"
+ property string customText: "NothingLess"
property bool disableHoverExpansion: true
+ property bool showMetrics: false
}
}
@@ -715,20 +718,47 @@ Singleton {
}
adapter: JsonAdapter {
+ // Borders & Rounding
+ property bool showBorder: true
property var activeBorderColor: ["primary"]
property int borderAngle: 45
property var inactiveBorderColor: ["surface"]
property int inactiveBorderAngle: 45
property int borderSize: 2
property int rounding: 16
+ property real roundingPower: 2.0
property bool syncRoundness: true
property bool syncBorderWidth: false
property bool syncBorderColor: false
property bool syncShadowOpacity: false
property bool syncShadowColor: false
+ property bool resizeOnBorder: false
+ property int extendBorderGrabArea: 15
+ property bool hoverIconOnBorder: true
+
+ // Gaps & Layout
property int gapsIn: 2
property int gapsOut: 4
property string layout: "dwindle"
+ property bool allowTearing: false
+
+ // Snap
+ property bool snapEnabled: true
+ property int snapWindowGap: 10
+ property int snapMonitorGap: 10
+ property bool snapBorderOverlap: false
+ property bool snapRespectGaps: false
+
+ // Opacity & Dim
+ property real activeOpacity: 1.0
+ property real inactiveOpacity: 1.0
+ property real fullscreenOpacity: 1.0
+ property bool dimInactive: false
+ property real dimStrength: 0.5
+ property real dimAround: 0.4
+ property real dimSpecial: 0.2
+
+ // Shadow
property bool shadowEnabled: true
property int shadowRange: 8
property int shadowRenderPower: 3
@@ -739,6 +769,8 @@ Singleton {
property real shadowOpacity: 0.5
property string shadowOffset: "0 0"
property real shadowScale: 1.0
+
+ // Blur
property bool blurEnabled: true
property int blurSize: 4
property int blurPasses: 2
@@ -757,6 +789,117 @@ Singleton {
property real blurPopupsIgnorealpha: 0.2
property bool blurInputMethods: false
property real blurInputMethodsIgnorealpha: 0.2
+
+ // Animations
+ property bool animationsEnabled: true
+
+ // Input: Keyboard
+ property string kbLayout: "us"
+ property string kbVariant: ""
+ property string kbOptions: ""
+ property bool numlockByDefault: false
+ property int repeatRate: 25
+ property int repeatDelay: 600
+
+ // Input: Mouse
+ property real mouseSensitivity: 0.0
+ property string mouseAccelProfile: ""
+ property int followMouse: 1
+ property bool mouseNaturalScroll: false
+ property real mouseScrollFactor: 1.0
+ property bool mouseLeftHanded: false
+ property bool mouseRefocus: false
+ property int floatSwitchOverrideFocus: 0
+
+ // Input: Touchpad
+ property bool touchpadDisableWhileTyping: true
+ property bool touchpadNaturalScroll: true
+ property bool touchpadTapToClick: true
+ property bool touchpadClickfingerBehavior: false
+ property string touchpadTapButtonMap: ""
+ property bool touchpadMiddleButtonEmulation: false
+ property int touchpadDragLock: 0
+ property real touchpadScrollFactor: 1.0
+
+ // Cursor
+ property bool noHardwareCursors: false
+ property bool enableHyprcursor: true
+ property bool noWarps: false
+ property bool persistentWarps: false
+ property bool warpOnChangeWorkspace: false
+ property real cursorZoomFactor: 1.0
+ property int cursorInactiveTimeout: 0
+ property bool cursorHideOnKeyPress: false
+ property bool cursorHideOnTouch: false
+ property bool cursorHideOnTablet: false
+
+ // Gestures
+ property bool workspaceSwipeCreateNew: true
+ property bool workspaceSwipeForever: false
+ property real workspaceSwipeCancelRatio: 0.5
+ property int workspaceSwipeMinSpeedToForce: 30
+ property bool workspaceSwipeDirectionLock: true
+ property bool workspaceSwipeUseR: false
+ property int workspaceSwipeDistance: 300
+ property bool workspaceSwipeInvert: true
+ property bool workspaceSwipeTouch: false
+ property bool workspaceSwipeTouchInvert: false
+
+ // Dwindle Layout
+ property bool dwindlePreserveSplit: true
+ property bool dwindlePseudotile: false
+ property int dwindleForceSplit: 0
+ property bool dwindleSmartSplit: true
+ property real dwindleDefaultSplitRatio: 1.0
+ property real dwindleSplitWidthMultiplier: 1.0
+ property bool dwindlePermanentDirectionOverride: false
+ property bool dwindleUseActiveForSplits: true
+ property bool dwindleSmartResizing: true
+ property real dwindleSpecialScaleFactor: 0.8
+
+ // Master Layout
+ property string masterOrientation: "left"
+ property real masterMfact: 0.55
+ property string masterNewStatus: "slave"
+ property bool masterNewOnTop: false
+ property string masterNewOnActive: "none"
+ property bool masterSmartResizing: true
+ property real masterSpecialScaleFactor: 0.8
+ property bool masterAllowSmallSplit: false
+
+ // Scrolling Layout
+ property real scrollingColumnWidth: 0.3
+ property string scrollingExplicitColumnWidths: ""
+ property string scrollingDirection: "right"
+ property bool scrollingFullscreenOnOneColumn: true
+ property string scrollingFocusFitMethod: "center"
+ property bool scrollingFollowFocus: true
+ property real scrollingFollowMinVisible: 0.1
+
+ // XWayland
+ property bool xwaylandEnabled: true
+ property bool xwaylandForceZeroScaling: false
+ property bool xwaylandUseNearestNeighbor: true
+
+ // Monitor Globals
+ property int vrr: 0
+ property bool vfr: true
+ property bool mouseMoveEnablesDpms: false
+ property bool keyPressEnablesDpms: false
+
+ // Misc
+ property string renderBackend: "opengl"
+ property bool disableAutoreload: false
+ property bool focusOnActivate: false
+ property bool animateManualResizes: false
+ property bool animateMouseWindowdragging: true
+ property bool disableHyprlandLogo: true
+ property bool disableSplashRendering: false
+ property int forceDefaultWallpaper: -1
+
+ // Ecosystem
+ property bool noUpdateNews: true
+ property bool enforcePermissions: false
}
}
@@ -1005,17 +1148,26 @@ Singleton {
adapter: JsonAdapter {
property list disks: ["/"]
property bool updateServiceEnabled: true
+ property JsonObject batteryNotifications: JsonObject {
+ property bool enabled: true
+ property int lowThreshold: 20
+ property int criticalThreshold: 10
+ property bool autoPowerSave: false
+ property int powerSaveThreshold: 15
+ property bool chargeLimitEnabled: false
+ property int chargeLimit: 80
+ }
property JsonObject idle: JsonObject {
property JsonObject general: JsonObject {
- property string lock_cmd: "ambxst lock"
+ property string lock_cmd: "nothingless lock"
property string before_sleep_cmd: "loginctl lock-session"
- property string after_sleep_cmd: "ambxst screen on"
+ property string after_sleep_cmd: "nothingless screen on"
}
property list listeners: [
{
"timeout": 150,
- "onTimeout": "ambxst brightness 10 -s",
- "onResume": "ambxst brightness -r"
+ "onTimeout": "nothingless brightness 10 -s",
+ "onResume": "nothingless brightness -r"
},
{
"timeout": 300,
@@ -1023,12 +1175,12 @@ Singleton {
},
{
"timeout": 330,
- "onTimeout": "ambxst screen off",
- "onResume": "ambxst screen on"
+ "onTimeout": "nothingless screen off",
+ "onResume": "nothingless screen on"
},
{
"timeout": 1800,
- "onTimeout": "ambxst suspend"
+ "onTimeout": "nothingless suspend"
}
]
}
@@ -1220,72 +1372,72 @@ Singleton {
const current = JSON.parse(raw);
let needsUpdate = false;
- // Ensure ambxst structure exists
- if (!current.ambxst) {
- current.ambxst = {};
+ // Ensure nothingless structure exists
+ if (!current.nothingless) {
+ current.nothingless = {};
needsUpdate = true;
}
// Migrate nested to flat structure
- if (current.ambxst.dashboard && typeof current.ambxst.dashboard === "object" && !current.ambxst.dashboard.modifiers) {
- console.log("Migrating nested ambxst binds to flat structure...");
- const nested = current.ambxst.dashboard;
+ if (current.nothingless.dashboard && typeof current.nothingless.dashboard === "object" && !current.nothingless.dashboard.modifiers) {
+ console.log("Migrating nested nothingless binds to flat structure...");
+ const nested = current.nothingless.dashboard;
// Map old names to new names and update arguments
if (nested.widgets) {
- current.ambxst.launcher = nested.widgets;
- current.ambxst.launcher.argument = "ambxst run launcher";
- current.ambxst.launcher.action = createAction(current.ambxst.launcher);
+ current.nothingless.launcher = nested.widgets;
+ current.nothingless.launcher.argument = "nothingless run launcher";
+ current.nothingless.launcher.action = createAction(current.nothingless.launcher);
}
if (nested.dashboard) {
- current.ambxst.dashboard = nested.dashboard;
- current.ambxst.dashboard.argument = "ambxst run dashboard";
- current.ambxst.dashboard.action = createAction(current.ambxst.dashboard);
+ current.nothingless.dashboard = nested.dashboard;
+ current.nothingless.dashboard.argument = "nothingless run dashboard";
+ current.nothingless.dashboard.action = createAction(current.nothingless.dashboard);
}
if (nested.assistant) {
- current.ambxst.assistant = nested.assistant;
- current.ambxst.assistant.argument = "ambxst run assistant";
- current.ambxst.assistant.action = createAction(current.ambxst.assistant);
+ current.nothingless.assistant = nested.assistant;
+ current.nothingless.assistant.argument = "nothingless run assistant";
+ current.nothingless.assistant.action = createAction(current.nothingless.assistant);
}
if (nested.clipboard) {
- current.ambxst.clipboard = nested.clipboard;
- current.ambxst.clipboard.argument = "ambxst run clipboard";
- current.ambxst.clipboard.action = createAction(current.ambxst.clipboard);
+ current.nothingless.clipboard = nested.clipboard;
+ current.nothingless.clipboard.argument = "nothingless run clipboard";
+ current.nothingless.clipboard.action = createAction(current.nothingless.clipboard);
}
if (nested.emoji) {
- current.ambxst.emoji = nested.emoji;
- current.ambxst.emoji.argument = "ambxst run emoji";
- current.ambxst.emoji.action = createAction(current.ambxst.emoji);
+ current.nothingless.emoji = nested.emoji;
+ current.nothingless.emoji.argument = "nothingless run emoji";
+ current.nothingless.emoji.action = createAction(current.nothingless.emoji);
}
if (nested.notes) {
- current.ambxst.notes = nested.notes;
- current.ambxst.notes.argument = "ambxst run notes";
- current.ambxst.notes.action = createAction(current.ambxst.notes);
+ current.nothingless.notes = nested.notes;
+ current.nothingless.notes.argument = "nothingless run notes";
+ current.nothingless.notes.action = createAction(current.nothingless.notes);
}
if (nested.tmux) {
- current.ambxst.tmux = nested.tmux;
- current.ambxst.tmux.argument = "ambxst run tmux";
- current.ambxst.tmux.action = createAction(current.ambxst.tmux);
+ current.nothingless.tmux = nested.tmux;
+ current.nothingless.tmux.argument = "nothingless run tmux";
+ current.nothingless.tmux.action = createAction(current.nothingless.tmux);
}
if (nested.wallpapers) {
- current.ambxst.wallpapers = nested.wallpapers;
- current.ambxst.wallpapers.argument = "ambxst run wallpapers";
- current.ambxst.wallpapers.action = createAction(current.ambxst.wallpapers);
+ current.nothingless.wallpapers = nested.wallpapers;
+ current.nothingless.wallpapers.argument = "nothingless run wallpapers";
+ current.nothingless.wallpapers.action = createAction(current.nothingless.wallpapers);
}
// Remove the old nested object
- delete current.ambxst.dashboard;
+ delete current.nothingless.dashboard;
needsUpdate = true;
}
- if (!current.ambxst.system) {
- current.ambxst.system = {};
+ if (!current.nothingless.system) {
+ current.nothingless.system = {};
needsUpdate = true;
}
// Get default binds from adapter
const adapter = keybindsLoader.adapter;
- if (!adapter || !adapter.ambxst) return;
+ if (!adapter || !adapter.nothingless) return;
// Helper function to create clean bind object
function createAction(bindObj) {
@@ -1303,34 +1455,43 @@ Singleton {
};
}
- // Check ambxst core binds
- const ambxstKeys = ["launcher", "dashboard", "assistant", "clipboard", "emoji", "notes", "tmux", "wallpapers"];
- for (const key of ambxstKeys) {
- if (!current.ambxst[key] && adapter.ambxst[key]) {
- console.log("Adding missing ambxst bind:", key);
- current.ambxst[key] = createCleanBind(adapter.ambxst[key]);
+ // Check nothingless core binds
+ const nothinglessKeys = ["launcher", "dashboard", "assistant", "clipboard", "emoji", "notes", "tmux", "wallpapers"];
+ for (const key of nothinglessKeys) {
+ if (!current.nothingless[key] && adapter.nothingless[key]) {
+ console.log("Adding missing nothingless bind:", key);
+ current.nothingless[key] = createCleanBind(adapter.nothingless[key]);
needsUpdate = true;
- } else if (current.ambxst[key] && !current.ambxst[key].action) {
- current.ambxst[key].action = createAction(current.ambxst[key]);
- delete current.ambxst[key].dispatcher;
- delete current.ambxst[key].argument;
- delete current.ambxst[key].flags;
+ } else if (current.nothingless[key] && !current.nothingless[key].action) {
+ current.nothingless[key].action = createAction(current.nothingless[key]);
+ delete current.nothingless[key].dispatcher;
+ delete current.nothingless[key].argument;
+ delete current.nothingless[key].flags;
needsUpdate = true;
}
}
+ // Get default binds from adapter.defaultNothinglessBinds (fallback for keys not yet in user's nothingless)
+ const defaultBinds = adapter.defaultNothinglessBinds || {};
+
// Check system binds
- const systemKeys = ["overview", "powermenu", "config", "lockscreen", "tools", "screenshot", "screenrecord", "lens", "reload", "quit"];
+ const systemKeys = ["overview", "powermenu", "config", "lockscreen", "tools", "screenshot", "screenrecord", "lens", "reload", "quit", "toggle-metrics"];
for (const key of systemKeys) {
- if (!current.ambxst.system[key] && adapter.ambxst.system && adapter.ambxst.system[key]) {
+ let defaultBind = null;
+ if (adapter.nothingless.system && adapter.nothingless.system[key]) {
+ defaultBind = adapter.nothingless.system[key];
+ } else if (defaultBinds.system && defaultBinds.system[key]) {
+ defaultBind = defaultBinds.system[key];
+ }
+ if (!current.nothingless.system[key] && defaultBind) {
console.log("Adding missing system bind:", key);
- current.ambxst.system[key] = createCleanBind(adapter.ambxst.system[key]);
+ current.nothingless.system[key] = createCleanBind(defaultBind);
needsUpdate = true;
- } else if (current.ambxst.system[key] && !current.ambxst.system[key].action) {
- current.ambxst.system[key].action = createAction(current.ambxst.system[key]);
- delete current.ambxst.system[key].dispatcher;
- delete current.ambxst.system[key].argument;
- delete current.ambxst.system[key].flags;
+ } else if (current.nothingless.system[key] && !current.nothingless.system[key].action) {
+ current.nothingless.system[key].action = createAction(current.nothingless.system[key]);
+ delete current.nothingless.system[key].dispatcher;
+ delete current.nothingless.system[key].argument;
+ delete current.nothingless.system[key].flags;
needsUpdate = true;
}
}
@@ -1405,52 +1566,52 @@ Singleton {
}
adapter: JsonAdapter {
- property JsonObject ambxst: JsonObject {
+ property JsonObject nothingless: JsonObject {
property JsonObject launcher: JsonObject {
property list modifiers: ["SUPER"]
property string key: "Super_L"
- property var action: ({ "id": "ambxst.launcher", "args": {} })
+ property var action: ({ "id": "nothingless.launcher", "args": {} })
}
property JsonObject dashboard: JsonObject {
property list modifiers: ["SUPER"]
property string key: "D"
- property var action: ({ "id": "ambxst.dashboard", "args": {} })
+ property var action: ({ "id": "nothingless.dashboard", "args": {} })
}
property JsonObject assistant: JsonObject {
property list modifiers: ["SUPER"]
property string key: "A"
- property var action: ({ "id": "ambxst.assistant", "args": {} })
+ property var action: ({ "id": "nothingless.assistant", "args": {} })
}
property JsonObject clipboard: JsonObject {
property list modifiers: ["SUPER"]
property string key: "V"
- property var action: ({ "id": "ambxst.clipboard", "args": {} })
+ property var action: ({ "id": "nothingless.clipboard", "args": {} })
}
property JsonObject emoji: JsonObject {
property list modifiers: ["SUPER"]
property string key: "PERIOD"
- property var action: ({ "id": "ambxst.emoji", "args": {} })
+ property var action: ({ "id": "nothingless.emoji", "args": {} })
}
property JsonObject notes: JsonObject {
property list modifiers: ["SUPER"]
property string key: "N"
- property var action: ({ "id": "ambxst.notes", "args": {} })
+ property var action: ({ "id": "nothingless.notes", "args": {} })
}
property JsonObject tmux: JsonObject {
property list modifiers: ["SUPER"]
property string key: "T"
- property var action: ({ "id": "ambxst.tmux", "args": {} })
+ property var action: ({ "id": "nothingless.tmux", "args": {} })
}
property JsonObject wallpapers: JsonObject {
property list modifiers: ["SUPER"]
property string key: "COMMA"
- property var action: ({ "id": "ambxst.wallpapers", "args": {} })
+ property var action: ({ "id": "nothingless.wallpapers", "args": {} })
}
property JsonObject system: JsonObject {
property JsonObject config: JsonObject {
property list modifiers: ["SUPER", "SHIFT"]
property string key: "C"
- property var action: ({ "id": "ambxst.config", "args": {} })
+ property var action: ({ "id": "nothingless.config", "args": {} })
}
property JsonObject lockscreen: JsonObject {
property list modifiers: ["SUPER"]
@@ -1460,74 +1621,75 @@ Singleton {
property JsonObject overview: JsonObject {
property list modifiers: ["SUPER"]
property string key: "TAB"
- property var action: ({ "id": "ambxst.overview", "args": {} })
+ property var action: ({ "id": "nothingless.overview", "args": {} })
}
property JsonObject powermenu: JsonObject {
property list modifiers: ["SUPER"]
property string key: "ESCAPE"
- property var action: ({ "id": "ambxst.powermenu", "args": {} })
+ property var action: ({ "id": "nothingless.powermenu", "args": {} })
}
property JsonObject tools: JsonObject {
property list modifiers: ["SUPER"]
property string key: "S"
- property var action: ({ "id": "ambxst.tools", "args": {} })
+ property var action: ({ "id": "nothingless.tools", "args": {} })
}
property JsonObject screenshot: JsonObject {
property list modifiers: ["SUPER", "SHIFT"]
property string key: "S"
- property var action: ({ "id": "ambxst.screenshot", "args": {} })
+ property var action: ({ "id": "nothingless.screenshot", "args": {} })
}
property JsonObject screenrecord: JsonObject {
property list modifiers: ["SUPER", "SHIFT"]
property string key: "R"
- property var action: ({ "id": "ambxst.screenrecord", "args": {} })
+ property var action: ({ "id": "nothingless.screenrecord", "args": {} })
}
property JsonObject lens: JsonObject {
property list modifiers: ["SUPER", "SHIFT"]
property string key: "A"
- property var action: ({ "id": "ambxst.lens", "args": {} })
+ property var action: ({ "id": "nothingless.lens", "args": {} })
}
property JsonObject reload: JsonObject {
property list modifiers: ["SUPER", "ALT"]
property string key: "B"
- property var action: ({ "id": "ambxst.reload", "args": {} })
+ property var action: ({ "id": "nothingless.reload", "args": {} })
}
property JsonObject quit: JsonObject {
property list modifiers: ["SUPER", "CTRL", "ALT"]
property string key: "B"
- property var action: ({ "id": "ambxst.quit", "args": {} })
+ property var action: ({ "id": "nothingless.quit", "args": {} })
}
}
}
// Default getters
- readonly property var defaultAmbxstBinds: {
- "ambxst": {
- "launcher": { "modifiers": ["SUPER"], "key": "Super_L", "action": { "id": "ambxst.launcher", "args": {} } },
- "dashboard": { "modifiers": ["SUPER"], "key": "D", "action": { "id": "ambxst.dashboard", "args": {} } },
- "assistant": { "modifiers": ["SUPER"], "key": "A", "action": { "id": "ambxst.assistant", "args": {} } },
- "clipboard": { "modifiers": ["SUPER"], "key": "V", "action": { "id": "ambxst.clipboard", "args": {} } },
- "emoji": { "modifiers": ["SUPER"], "key": "PERIOD", "action": { "id": "ambxst.emoji", "args": {} } },
- "notes": { "modifiers": ["SUPER"], "key": "N", "action": { "id": "ambxst.notes", "args": {} } },
- "tmux": { "modifiers": ["SUPER"], "key": "T", "action": { "id": "ambxst.tmux", "args": {} } },
- "wallpapers": { "modifiers": ["SUPER"], "key": "COMMA", "action": { "id": "ambxst.wallpapers", "args": {} } }
+ readonly property var defaultNothinglessBinds: {
+ "nothingless": {
+ "launcher": { "modifiers": ["SUPER"], "key": "Super_L", "action": { "id": "nothingless.launcher", "args": {} } },
+ "dashboard": { "modifiers": ["SUPER"], "key": "D", "action": { "id": "nothingless.dashboard", "args": {} } },
+ "assistant": { "modifiers": ["SUPER"], "key": "A", "action": { "id": "nothingless.assistant", "args": {} } },
+ "clipboard": { "modifiers": ["SUPER"], "key": "V", "action": { "id": "nothingless.clipboard", "args": {} } },
+ "emoji": { "modifiers": ["SUPER"], "key": "PERIOD", "action": { "id": "nothingless.emoji", "args": {} } },
+ "notes": { "modifiers": ["SUPER"], "key": "N", "action": { "id": "nothingless.notes", "args": {} } },
+ "tmux": { "modifiers": ["SUPER"], "key": "T", "action": { "id": "nothingless.tmux", "args": {} } },
+ "wallpapers": { "modifiers": ["SUPER"], "key": "COMMA", "action": { "id": "nothingless.wallpapers", "args": {} } }
},
"system": {
- "config": { "modifiers": ["SUPER", "SHIFT"], "key": "C", "action": { "id": "ambxst.config", "args": {} } },
+ "config": { "modifiers": ["SUPER", "SHIFT"], "key": "C", "action": { "id": "nothingless.config", "args": {} } },
"lockscreen": { "modifiers": ["SUPER"], "key": "L", "action": { "id": "system.lock", "args": {} } },
- "overview": { "modifiers": ["SUPER"], "key": "TAB", "action": { "id": "ambxst.overview", "args": {} } },
- "powermenu": { "modifiers": ["SUPER"], "key": "ESCAPE", "action": { "id": "ambxst.powermenu", "args": {} } },
- "tools": { "modifiers": ["SUPER"], "key": "S", "action": { "id": "ambxst.tools", "args": {} } },
- "screenshot": { "modifiers": ["SUPER", "SHIFT"], "key": "S", "action": { "id": "ambxst.screenshot", "args": {} } },
- "screenrecord": { "modifiers": ["SUPER", "SHIFT"], "key": "R", "action": { "id": "ambxst.screenrecord", "args": {} } },
- "lens": { "modifiers": ["SUPER", "SHIFT"], "key": "A", "action": { "id": "ambxst.lens", "args": {} } },
- "reload": { "modifiers": ["SUPER", "ALT"], "key": "B", "action": { "id": "ambxst.reload", "args": {} } },
- "quit": { "modifiers": ["SUPER", "CTRL", "ALT"], "key": "B", "action": { "id": "ambxst.quit", "args": {} } }
+ "overview": { "modifiers": ["SUPER"], "key": "TAB", "action": { "id": "nothingless.overview", "args": {} } },
+ "powermenu": { "modifiers": ["SUPER"], "key": "ESCAPE", "action": { "id": "nothingless.powermenu", "args": {} } },
+ "tools": { "modifiers": ["SUPER"], "key": "S", "action": { "id": "nothingless.tools", "args": {} } },
+ "screenshot": { "modifiers": ["SUPER", "SHIFT"], "key": "S", "action": { "id": "nothingless.screenshot", "args": {} } },
+ "screenrecord": { "modifiers": ["SUPER", "SHIFT"], "key": "R", "action": { "id": "nothingless.screenrecord", "args": {} } },
+ "lens": { "modifiers": ["SUPER", "SHIFT"], "key": "A", "action": { "id": "nothingless.lens", "args": {} } },
+ "reload": { "modifiers": ["SUPER", "ALT"], "key": "B", "action": { "id": "nothingless.reload", "args": {} } },
+ "toggle-metrics": { "modifiers": ["SUPER", "SHIFT"], "key": "BACKSPACE", "action": { "id": "nothingless.toggle-metrics", "args": {} } },
+ "quit": { "modifiers": ["SUPER", "CTRL", "ALT"], "key": "B", "action": { "id": "nothingless.quit", "args": {} } }
}
}
- function getAmbxstDefault(section, key) {
- if (defaultAmbxstBinds[section] && defaultAmbxstBinds[section][key]) {
- const bind = defaultAmbxstBinds[section][key];
+ function getNothinglessDefault(section, key) {
+ if (defaultNothinglessBinds[section] && defaultNothinglessBinds[section][key]) {
+ const bind = defaultNothinglessBinds[section][key];
return {
"modifiers": bind.modifiers || [],
"key": bind.key || "",
@@ -1538,7 +1700,6 @@ Singleton {
}
property list custom: [
- // Window management
{
"name": "Close Window",
"keys": [
@@ -1557,8 +1718,6 @@ Singleton {
],
"enabled": true
},
-
- // Workspace navigation
{
"name": "Workspace 1",
"keys": [
@@ -1739,10 +1898,8 @@ Singleton {
],
"enabled": true
},
-
- // Move window to workspace
{
- "name": "Move to Workspace 1",
+ "name": "Move Window to Workspace 1",
"keys": [
{
"modifiers": ["SUPER", "SHIFT"],
@@ -1760,7 +1917,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 2",
+ "name": "Move Window to Workspace 2",
"keys": [
{
"modifiers": ["SUPER", "SHIFT"],
@@ -1778,7 +1935,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 3",
+ "name": "Move Window to Workspace 3",
"keys": [
{
"modifiers": ["SUPER", "SHIFT"],
@@ -1796,7 +1953,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 4",
+ "name": "Move Window to Workspace 4",
"keys": [
{
"modifiers": ["SUPER", "SHIFT"],
@@ -1814,7 +1971,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 5",
+ "name": "Move Window to Workspace 5",
"keys": [
{
"modifiers": ["SUPER", "SHIFT"],
@@ -1832,7 +1989,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 6",
+ "name": "Move Window to Workspace 6",
"keys": [
{
"modifiers": ["SUPER", "SHIFT"],
@@ -1850,7 +2007,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 7",
+ "name": "Move Window to Workspace 7",
"keys": [
{
"modifiers": ["SUPER", "SHIFT"],
@@ -1868,7 +2025,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 8",
+ "name": "Move Window to Workspace 8",
"keys": [
{
"modifiers": ["SUPER", "SHIFT"],
@@ -1886,7 +2043,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 9",
+ "name": "Move Window to Workspace 9",
"keys": [
{
"modifiers": ["SUPER", "SHIFT"],
@@ -1904,7 +2061,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 10",
+ "name": "Move Window to Workspace 10",
"keys": [
{
"modifiers": ["SUPER", "SHIFT"],
@@ -1922,7 +2079,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 1 (Silent)",
+ "name": "Move Window Silently to Workspace 1",
"keys": [
{
"modifiers": ["SUPER", "ALT"],
@@ -1940,7 +2097,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 2 (Silent)",
+ "name": "Move Window Silently to Workspace 2",
"keys": [
{
"modifiers": ["SUPER", "ALT"],
@@ -1958,7 +2115,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 3 (Silent)",
+ "name": "Move Window Silently to Workspace 3",
"keys": [
{
"modifiers": ["SUPER", "ALT"],
@@ -1976,7 +2133,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 4 (Silent)",
+ "name": "Move Window Silently to Workspace 4",
"keys": [
{
"modifiers": ["SUPER", "ALT"],
@@ -1994,7 +2151,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 5 (Silent)",
+ "name": "Move Window Silently to Workspace 5",
"keys": [
{
"modifiers": ["SUPER", "ALT"],
@@ -2012,7 +2169,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 6 (Silent)",
+ "name": "Move Window Silently to Workspace 6",
"keys": [
{
"modifiers": ["SUPER", "ALT"],
@@ -2030,7 +2187,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 7 (Silent)",
+ "name": "Move Window Silently to Workspace 7",
"keys": [
{
"modifiers": ["SUPER", "ALT"],
@@ -2048,7 +2205,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 8 (Silent)",
+ "name": "Move Window Silently to Workspace 8",
"keys": [
{
"modifiers": ["SUPER", "ALT"],
@@ -2066,7 +2223,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 9 (Silent)",
+ "name": "Move Window Silently to Workspace 9",
"keys": [
{
"modifiers": ["SUPER", "ALT"],
@@ -2084,7 +2241,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Workspace 10 (Silent)",
+ "name": "Move Window Silently to Workspace 10",
"keys": [
{
"modifiers": ["SUPER", "ALT"],
@@ -2101,10 +2258,8 @@ Singleton {
],
"enabled": true
},
-
- // Workspace scroll/keys
{
- "name": "Previous Occupied Workspace (Scroll)",
+ "name": "Switch Occupied Workspace -1",
"keys": [
{
"modifiers": ["SUPER"],
@@ -2122,7 +2277,7 @@ Singleton {
"enabled": true
},
{
- "name": "Next Occupied Workspace (Scroll)",
+ "name": "Switch Occupied Workspace +1",
"keys": [
{
"modifiers": ["SUPER"],
@@ -2140,7 +2295,7 @@ Singleton {
"enabled": true
},
{
- "name": "Previous Occupied Workspace",
+ "name": "Switch Occupied Workspace -1",
"keys": [
{
"modifiers": ["SUPER", "SHIFT"],
@@ -2158,7 +2313,7 @@ Singleton {
"enabled": true
},
{
- "name": "Next Occupied Workspace",
+ "name": "Switch Occupied Workspace +1",
"keys": [
{
"modifiers": ["SUPER", "SHIFT"],
@@ -2176,7 +2331,7 @@ Singleton {
"enabled": true
},
{
- "name": "Previous Workspace",
+ "name": "Switch Relative Workspace -1",
"keys": [
{
"modifiers": ["SUPER"],
@@ -2194,7 +2349,7 @@ Singleton {
"enabled": true
},
{
- "name": "Next Workspace",
+ "name": "Switch Relative Workspace +1",
"keys": [
{
"modifiers": ["SUPER"],
@@ -2211,8 +2366,6 @@ Singleton {
],
"enabled": true
},
-
- // Window drag/resize
{
"name": "Drag Window",
"keys": [
@@ -2232,7 +2385,7 @@ Singleton {
"enabled": true
},
{
- "name": "Drag Resize Window",
+ "name": "Resize Window with Mouse",
"keys": [
{
"modifiers": ["SUPER"],
@@ -2249,10 +2402,8 @@ Singleton {
],
"enabled": true
},
-
- // Media controls
{
- "name": "Play/Pause",
+ "name": "Media Play Pause",
"keys": [
{
"modifiers": [],
@@ -2270,7 +2421,7 @@ Singleton {
"enabled": true
},
{
- "name": "Previous Track",
+ "name": "Media Previous",
"keys": [
{
"modifiers": [],
@@ -2288,7 +2439,7 @@ Singleton {
"enabled": true
},
{
- "name": "Next Track",
+ "name": "Media Next",
"keys": [
{
"modifiers": [],
@@ -2306,7 +2457,7 @@ Singleton {
"enabled": true
},
{
- "name": "Media Play/Pause",
+ "name": "Media Play Pause",
"keys": [
{
"modifiers": [],
@@ -2324,7 +2475,7 @@ Singleton {
"enabled": true
},
{
- "name": "Stop Playback",
+ "name": "Media Stop",
"keys": [
{
"modifiers": [],
@@ -2341,8 +2492,6 @@ Singleton {
],
"enabled": true
},
-
- // Volume controls
{
"name": "Volume Up",
"keys": [
@@ -2397,8 +2546,6 @@ Singleton {
],
"enabled": true
},
-
- // Brightness controls
{
"name": "Brightness Up",
"keys": [
@@ -2410,7 +2557,7 @@ Singleton {
"actions": [
{
"dispatcher": "exec",
- "argument": "ambxst brightness +5",
+ "argument": "nothingless brightness +5",
"flags": "le",
"layouts": []
}
@@ -2428,15 +2575,13 @@ Singleton {
"actions": [
{
"dispatcher": "exec",
- "argument": "ambxst brightness -5",
+ "argument": "nothingless brightness -5",
"flags": "le",
"layouts": []
}
],
"enabled": true
},
-
- // Special keys
{
"name": "Calculator",
"keys": [
@@ -2455,8 +2600,6 @@ Singleton {
],
"enabled": true
},
-
- // Special workspaces
{
"name": "Toggle Special Workspace",
"keys": [
@@ -2476,7 +2619,7 @@ Singleton {
"enabled": true
},
{
- "name": "Move to Special Workspace",
+ "name": "Move Window to Special Workspace",
"keys": [
{
"modifiers": ["SUPER", "ALT"],
@@ -2493,10 +2636,8 @@ Singleton {
],
"enabled": true
},
-
- // Lid switch events
{
- "name": "Lock on Lid Close",
+ "name": "Lock Session on Lid Switch",
"keys": [
{
"modifiers": [],
@@ -2549,32 +2690,38 @@ Singleton {
],
"enabled": true
},
-
- // Window focus
{
"name": "Focus Up",
"keys": [
{
"modifiers": ["SUPER"],
"key": "Up"
- },
+ }
+ ],
+ "actions": [
+ {
+ "dispatcher": "movefocus",
+ "argument": "u",
+ "flags": "",
+ "layouts": []
+ }
+ ],
+ "enabled": true
+ },
+ {
+ "name": "Focus Up",
+ "keys": [
{
"modifiers": ["SUPER", "CTRL"],
"key": "k"
}
],
"actions": [
- {
- "dispatcher": "layoutmsg",
- "argument": "focus u",
- "flags": "",
- "layouts": ["scrolling"]
- },
{
"dispatcher": "movefocus",
"argument": "u",
"flags": "",
- "layouts": ["dwindle", "master"]
+ "layouts": []
}
],
"enabled": true
@@ -2585,24 +2732,32 @@ Singleton {
{
"modifiers": ["SUPER"],
"key": "Down"
- },
+ }
+ ],
+ "actions": [
+ {
+ "dispatcher": "movefocus",
+ "argument": "d",
+ "flags": "",
+ "layouts": []
+ }
+ ],
+ "enabled": true
+ },
+ {
+ "name": "Focus Down",
+ "keys": [
{
"modifiers": ["SUPER", "CTRL"],
"key": "j"
}
],
"actions": [
- {
- "dispatcher": "layoutmsg",
- "argument": "focus d",
- "flags": "",
- "layouts": ["scrolling"]
- },
{
"dispatcher": "movefocus",
"argument": "d",
"flags": "",
- "layouts": ["master", "dwindle"]
+ "layouts": []
}
],
"enabled": true
@@ -2613,28 +2768,50 @@ Singleton {
{
"modifiers": ["SUPER"],
"key": "Left"
- },
+ }
+ ],
+ "actions": [
+ {
+ "dispatcher": "movefocus",
+ "argument": "l",
+ "flags": "",
+ "layouts": []
+ }
+ ],
+ "enabled": true
+ },
+ {
+ "name": "Focus Left",
+ "keys": [
{
"modifiers": ["SUPER", "CTRL"],
"key": "z"
- },
+ }
+ ],
+ "actions": [
+ {
+ "dispatcher": "movefocus",
+ "argument": "l",
+ "flags": "",
+ "layouts": []
+ }
+ ],
+ "enabled": true
+ },
+ {
+ "name": "Focus Left",
+ "keys": [
{
"modifiers": ["SUPER", "CTRL"],
"key": "h"
}
],
"actions": [
- {
- "dispatcher": "layoutmsg",
- "argument": "focus l",
- "flags": "",
- "layouts": ["scrolling"]
- },
{
"dispatcher": "movefocus",
"argument": "l",
"flags": "",
- "layouts": ["dwindle", "master"]
+ "layouts": []
}
],
"enabled": true
@@ -2645,41 +2822,75 @@ Singleton {
{
"modifiers": ["SUPER"],
"key": "Right"
- },
+ }
+ ],
+ "actions": [
+ {
+ "dispatcher": "movefocus",
+ "argument": "r",
+ "flags": "",
+ "layouts": []
+ }
+ ],
+ "enabled": true
+ },
+ {
+ "name": "Focus Right",
+ "keys": [
{
"modifiers": ["SUPER", "CTRL"],
"key": "x"
- },
+ }
+ ],
+ "actions": [
+ {
+ "dispatcher": "movefocus",
+ "argument": "r",
+ "flags": "",
+ "layouts": []
+ }
+ ],
+ "enabled": true
+ },
+ {
+ "name": "Focus Right",
+ "keys": [
{
"modifiers": ["SUPER", "CTRL"],
"key": "l"
}
],
"actions": [
- {
- "dispatcher": "layoutmsg",
- "argument": "focus r",
- "flags": "",
- "layouts": ["scrolling"]
- },
{
"dispatcher": "movefocus",
"argument": "r",
"flags": "",
- "layouts": ["master", "dwindle"]
+ "layouts": []
}
],
"enabled": true
},
-
- // Window movement
{
"name": "Move Window Left",
"keys": [
{
"modifiers": ["SUPER", "SHIFT"],
"key": "Left"
- },
+ }
+ ],
+ "actions": [
+ {
+ "dispatcher": "movewindow",
+ "argument": "l",
+ "flags": "",
+ "layouts": []
+ }
+ ],
+ "enabled": true
+ },
+ {
+ "name": "Move Window Left",
+ "keys": [
{
"modifiers": ["SUPER", "SHIFT"],
"key": "h"
@@ -2690,13 +2901,7 @@ Singleton {
"dispatcher": "movewindow",
"argument": "l",
"flags": "",
- "layouts": ["master", "dwindle"]
- },
- {
- "dispatcher": "layoutmsg",
- "argument": "movewindowto l",
- "flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
@@ -2707,7 +2912,21 @@ Singleton {
{
"modifiers": ["SUPER", "SHIFT"],
"key": "Right"
- },
+ }
+ ],
+ "actions": [
+ {
+ "dispatcher": "movewindow",
+ "argument": "r",
+ "flags": "",
+ "layouts": []
+ }
+ ],
+ "enabled": true
+ },
+ {
+ "name": "Move Window Right",
+ "keys": [
{
"modifiers": ["SUPER", "SHIFT"],
"key": "l"
@@ -2718,13 +2937,7 @@ Singleton {
"dispatcher": "movewindow",
"argument": "r",
"flags": "",
- "layouts": ["dwindle", "master"]
- },
- {
- "dispatcher": "layoutmsg",
- "argument": "movewindowto r",
- "flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
@@ -2735,7 +2948,21 @@ Singleton {
{
"modifiers": ["SUPER", "SHIFT"],
"key": "Up"
- },
+ }
+ ],
+ "actions": [
+ {
+ "dispatcher": "movewindow",
+ "argument": "u",
+ "flags": "",
+ "layouts": []
+ }
+ ],
+ "enabled": true
+ },
+ {
+ "name": "Move Window Up",
+ "keys": [
{
"modifiers": ["SUPER", "SHIFT"],
"key": "k"
@@ -2746,13 +2973,7 @@ Singleton {
"dispatcher": "movewindow",
"argument": "u",
"flags": "",
- "layouts": ["master", "dwindle"]
- },
- {
- "dispatcher": "layoutmsg",
- "argument": "movewindowto u",
- "flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
@@ -2763,7 +2984,21 @@ Singleton {
{
"modifiers": ["SUPER", "SHIFT"],
"key": "Down"
- },
+ }
+ ],
+ "actions": [
+ {
+ "dispatcher": "movewindow",
+ "argument": "d",
+ "flags": "",
+ "layouts": []
+ }
+ ],
+ "enabled": true
+ },
+ {
+ "name": "Move Window Down",
+ "keys": [
{
"modifiers": ["SUPER", "SHIFT"],
"key": "j"
@@ -2774,26 +3009,32 @@ Singleton {
"dispatcher": "movewindow",
"argument": "d",
"flags": "",
- "layouts": ["master", "dwindle"]
- },
- {
- "dispatcher": "layoutmsg",
- "argument": "movewindowto d",
- "flags": "",
"layouts": []
}
],
"enabled": true
},
-
- // Window resize
{
- "name": "Horizontal Resize +",
+ "name": "Resize Column +0.1",
"keys": [
{
"modifiers": ["SUPER", "ALT"],
"key": "Right"
- },
+ }
+ ],
+ "actions": [
+ {
+ "dispatcher": "layoutmsg",
+ "argument": "colresize +0.1",
+ "flags": "",
+ "layouts": []
+ }
+ ],
+ "enabled": true
+ },
+ {
+ "name": "Resize Column +0.1",
+ "keys": [
{
"modifiers": ["SUPER", "ALT"],
"key": "l"
@@ -2804,24 +3045,32 @@ Singleton {
"dispatcher": "layoutmsg",
"argument": "colresize +0.1",
"flags": "",
- "layouts": ["scrolling"]
- },
- {
- "dispatcher": "resizeactive",
- "argument": "50 0",
- "flags": "",
- "layouts": ["master", "dwindle"]
+ "layouts": []
}
],
"enabled": true
},
{
- "name": "Horizontal Resize -",
+ "name": "Resize Column -0.1",
"keys": [
{
"modifiers": ["SUPER", "ALT"],
"key": "Left"
- },
+ }
+ ],
+ "actions": [
+ {
+ "dispatcher": "layoutmsg",
+ "argument": "colresize -0.1",
+ "flags": "",
+ "layouts": []
+ }
+ ],
+ "enabled": true
+ },
+ {
+ "name": "Resize Column -0.1",
+ "keys": [
{
"modifiers": ["SUPER", "ALT"],
"key": "h"
@@ -2832,24 +3081,32 @@ Singleton {
"dispatcher": "layoutmsg",
"argument": "colresize -0.1",
"flags": "",
- "layouts": ["scrolling"]
- },
- {
- "dispatcher": "resizeactive",
- "argument": "-50 0",
- "flags": "",
- "layouts": ["master", "dwindle"]
+ "layouts": []
}
],
"enabled": true
},
{
- "name": "Vertical Resize +",
+ "name": "Resize Active 0 50",
"keys": [
{
"modifiers": ["SUPER", "ALT"],
"key": "Down"
- },
+ }
+ ],
+ "actions": [
+ {
+ "dispatcher": "resizeactive",
+ "argument": "0 50",
+ "flags": "",
+ "layouts": []
+ }
+ ],
+ "enabled": true
+ },
+ {
+ "name": "Resize Active 0 50",
+ "keys": [
{
"modifiers": ["SUPER", "ALT"],
"key": "j"
@@ -2866,12 +3123,26 @@ Singleton {
"enabled": true
},
{
- "name": "Vertical Resize -",
+ "name": "Resize Active 0 -50",
"keys": [
{
"modifiers": ["SUPER", "ALT"],
"key": "Up"
- },
+ }
+ ],
+ "actions": [
+ {
+ "dispatcher": "resizeactive",
+ "argument": "0 -50",
+ "flags": "",
+ "layouts": []
+ }
+ ],
+ "enabled": true
+ },
+ {
+ "name": "Resize Active 0 -50",
+ "keys": [
{
"modifiers": ["SUPER", "ALT"],
"key": "k"
@@ -2887,10 +3158,8 @@ Singleton {
],
"enabled": true
},
-
- // Scrolling layout
{
- "name": "Promote (Scrolling)",
+ "name": "Promote Column",
"keys": [
{
"modifiers": ["SUPER", "ALT"],
@@ -2902,13 +3171,13 @@ Singleton {
"dispatcher": "layoutmsg",
"argument": "promote",
"flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
},
{
- "name": "Toggle Fit (Scrolling)",
+ "name": "Toggle Fit",
"keys": [
{
"modifiers": ["SUPER", "CTRL"],
@@ -2920,13 +3189,13 @@ Singleton {
"dispatcher": "layoutmsg",
"argument": "togglefit",
"flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
},
{
- "name": "Toggle Full Column (Scrolling)",
+ "name": "Resize Column +conf",
"keys": [
{
"modifiers": ["SUPER", "SHIFT"],
@@ -2938,7 +3207,7 @@ Singleton {
"dispatcher": "layoutmsg",
"argument": "colresize +conf",
"flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
@@ -2949,7 +3218,21 @@ Singleton {
{
"modifiers": ["SUPER", "ALT", "CTRL"],
"key": "Left"
- },
+ }
+ ],
+ "actions": [
+ {
+ "dispatcher": "layoutmsg",
+ "argument": "swapcol l",
+ "flags": "",
+ "layouts": []
+ }
+ ],
+ "enabled": true
+ },
+ {
+ "name": "Swap Column Left",
+ "keys": [
{
"modifiers": ["SUPER", "ALT", "CTRL"],
"key": "h"
@@ -2960,7 +3243,7 @@ Singleton {
"dispatcher": "layoutmsg",
"argument": "swapcol l",
"flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
@@ -2971,7 +3254,21 @@ Singleton {
{
"modifiers": ["SUPER", "ALT", "CTRL"],
"key": "Right"
- },
+ }
+ ],
+ "actions": [
+ {
+ "dispatcher": "layoutmsg",
+ "argument": "swapcol r",
+ "flags": "",
+ "layouts": []
+ }
+ ],
+ "enabled": true
+ },
+ {
+ "name": "Swap Column Right",
+ "keys": [
{
"modifiers": ["SUPER", "ALT", "CTRL"],
"key": "l"
@@ -2982,15 +3279,13 @@ Singleton {
"dispatcher": "layoutmsg",
"argument": "swapcol r",
"flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
},
-
- // Move column to workspace
{
- "name": "Move Column To Workspace 1",
+ "name": "Move Column to Workspace 1",
"keys": [
{
"modifiers": ["SUPER", "CTRL", "ALT"],
@@ -3002,13 +3297,13 @@ Singleton {
"dispatcher": "layoutmsg",
"argument": "movecoltoworkspace 1",
"flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
},
{
- "name": "Move Column To Workspace 2",
+ "name": "Move Column to Workspace 2",
"keys": [
{
"modifiers": ["SUPER", "CTRL", "ALT"],
@@ -3020,13 +3315,13 @@ Singleton {
"dispatcher": "layoutmsg",
"argument": "movecoltoworkspace 2",
"flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
},
{
- "name": "Move Column To Workspace 3",
+ "name": "Move Column to Workspace 3",
"keys": [
{
"modifiers": ["SUPER", "CTRL", "ALT"],
@@ -3038,13 +3333,13 @@ Singleton {
"dispatcher": "layoutmsg",
"argument": "movecoltoworkspace 3",
"flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
},
{
- "name": "Move Column To Workspace 4",
+ "name": "Move Column to Workspace 4",
"keys": [
{
"modifiers": ["SUPER", "CTRL", "ALT"],
@@ -3056,13 +3351,13 @@ Singleton {
"dispatcher": "layoutmsg",
"argument": "movecoltoworkspace 4",
"flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
},
{
- "name": "Move Column To Workspace 5",
+ "name": "Move Column to Workspace 5",
"keys": [
{
"modifiers": ["SUPER", "CTRL", "ALT"],
@@ -3074,13 +3369,13 @@ Singleton {
"dispatcher": "layoutmsg",
"argument": "movecoltoworkspace 5",
"flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
},
{
- "name": "Move Column To Workspace 6",
+ "name": "Move Column to Workspace 6",
"keys": [
{
"modifiers": ["SUPER", "CTRL", "ALT"],
@@ -3092,13 +3387,13 @@ Singleton {
"dispatcher": "layoutmsg",
"argument": "movecoltoworkspace 6",
"flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
},
{
- "name": "Move Column To Workspace 7",
+ "name": "Move Column to Workspace 7",
"keys": [
{
"modifiers": ["SUPER", "CTRL", "ALT"],
@@ -3110,13 +3405,13 @@ Singleton {
"dispatcher": "layoutmsg",
"argument": "movecoltoworkspace 7",
"flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
},
{
- "name": "Move Column To Workspace 8",
+ "name": "Move Column to Workspace 8",
"keys": [
{
"modifiers": ["SUPER", "CTRL", "ALT"],
@@ -3128,13 +3423,13 @@ Singleton {
"dispatcher": "layoutmsg",
"argument": "movecoltoworkspace 8",
"flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
},
{
- "name": "Move Column To Workspace 9",
+ "name": "Move Column to Workspace 9",
"keys": [
{
"modifiers": ["SUPER", "CTRL", "ALT"],
@@ -3146,13 +3441,13 @@ Singleton {
"dispatcher": "layoutmsg",
"argument": "movecoltoworkspace 9",
"flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
},
{
- "name": "Move Column To Workspace 10",
+ "name": "Move Column to Workspace 10",
"keys": [
{
"modifiers": ["SUPER", "CTRL", "ALT"],
@@ -3164,7 +3459,7 @@ Singleton {
"dispatcher": "layoutmsg",
"argument": "movecoltoworkspace 10",
"flags": "",
- "layouts": ["scrolling"]
+ "layouts": []
}
],
"enabled": true
diff --git a/config/ConfigValidator.js b/config/ConfigValidator.js
old mode 100644
new mode 100755
diff --git a/config/KeybindActions.js b/config/KeybindActions.js
old mode 100644
new mode 100755
index 52b49ef7..cc931dab
--- a/config/KeybindActions.js
+++ b/config/KeybindActions.js
@@ -29,23 +29,24 @@ function directionToLetter(direction) {
}
var ACTION_CATALOG = [
- { id: "ambxst.launcher", label: "Open Launcher", category: "Ambxst", dispatcher: "exec", argument: "ambxst run launcher", flags: "r" },
- { id: "ambxst.dashboard", label: "Open Dashboard", category: "Ambxst", dispatcher: "exec", argument: "ambxst run dashboard" },
- { id: "ambxst.assistant", label: "Open Assistant", category: "Ambxst", dispatcher: "exec", argument: "ambxst run assistant" },
- { id: "ambxst.clipboard", label: "Open Clipboard", category: "Ambxst", dispatcher: "exec", argument: "ambxst run clipboard" },
- { id: "ambxst.emoji", label: "Open Emoji", category: "Ambxst", dispatcher: "exec", argument: "ambxst run emoji" },
- { id: "ambxst.notes", label: "Open Notes", category: "Ambxst", dispatcher: "exec", argument: "ambxst run notes" },
- { id: "ambxst.tmux", label: "Open Tmux", category: "Ambxst", dispatcher: "exec", argument: "ambxst run tmux" },
- { id: "ambxst.wallpapers", label: "Open Wallpapers", category: "Ambxst", dispatcher: "exec", argument: "ambxst run wallpapers" },
- { id: "ambxst.config", label: "Open Settings", category: "Ambxst", dispatcher: "exec", argument: "ambxst run config" },
- { id: "ambxst.overview", label: "Open Overview", category: "Ambxst", dispatcher: "exec", argument: "ambxst run overview" },
- { id: "ambxst.powermenu", label: "Open Power Menu", category: "Ambxst", dispatcher: "exec", argument: "ambxst run powermenu" },
- { id: "ambxst.tools", label: "Open Tools", category: "Ambxst", dispatcher: "exec", argument: "ambxst run tools" },
- { id: "ambxst.screenshot", label: "Take Screenshot", category: "Ambxst", dispatcher: "exec", argument: "ambxst run screenshot" },
- { id: "ambxst.screenrecord", label: "Screen Record", category: "Ambxst", dispatcher: "exec", argument: "ambxst run screenrecord" },
- { id: "ambxst.lens", label: "Open Lens", category: "Ambxst", dispatcher: "exec", argument: "ambxst run lens" },
- { id: "ambxst.reload", label: "Reload Ambxst", category: "Ambxst", dispatcher: "exec", argument: "ambxst reload" },
- { id: "ambxst.quit", label: "Quit Ambxst", category: "Ambxst", dispatcher: "exec", argument: "ambxst quit" },
+ { id: "nothingless.launcher", label: "Open Launcher", category: "NothingLess", dispatcher: "exec", argument: "nothingless run launcher", flags: "r" },
+ { id: "nothingless.dashboard", label: "Open Dashboard", category: "NothingLess", dispatcher: "exec", argument: "nothingless run dashboard" },
+ { id: "nothingless.assistant", label: "Open Assistant", category: "NothingLess", dispatcher: "exec", argument: "nothingless run assistant" },
+ { id: "nothingless.clipboard", label: "Open Clipboard", category: "NothingLess", dispatcher: "exec", argument: "nothingless run clipboard" },
+ { id: "nothingless.emoji", label: "Open Emoji", category: "NothingLess", dispatcher: "exec", argument: "nothingless run emoji" },
+ { id: "nothingless.notes", label: "Open Notes", category: "NothingLess", dispatcher: "exec", argument: "nothingless run notes" },
+ { id: "nothingless.tmux", label: "Open Tmux", category: "NothingLess", dispatcher: "exec", argument: "nothingless run tmux" },
+ { id: "nothingless.wallpapers", label: "Open Wallpapers", category: "NothingLess", dispatcher: "exec", argument: "nothingless run wallpapers" },
+ { id: "nothingless.config", label: "Open Settings", category: "NothingLess", dispatcher: "exec", argument: "nothingless run config" },
+ { id: "nothingless.overview", label: "Open Overview", category: "NothingLess", dispatcher: "exec", argument: "nothingless run overview" },
+ { id: "nothingless.powermenu", label: "Open Power Menu", category: "NothingLess", dispatcher: "exec", argument: "nothingless run powermenu" },
+ { id: "nothingless.tools", label: "Open Tools", category: "NothingLess", dispatcher: "exec", argument: "nothingless run tools" },
+ { id: "nothingless.screenshot", label: "Take Screenshot", category: "NothingLess", dispatcher: "exec", argument: "nothingless run screenshot" },
+ { id: "nothingless.screenrecord", label: "Screen Record", category: "NothingLess", dispatcher: "exec", argument: "nothingless run screenrecord" },
+ { id: "nothingless.lens", label: "Open Lens", category: "NothingLess", dispatcher: "exec", argument: "nothingless run lens" },
+ { id: "nothingless.reload", label: "Reload NothingLess", category: "NothingLess", dispatcher: "exec", argument: "nothingless reload" },
+ { id: "nothingless.quit", label: "Quit NothingLess", category: "NothingLess", dispatcher: "exec", argument: "nothingless quit" },
+ { id: "nothingless.toggle-metrics", label: "Toggle Metrics", category: "NothingLess", dispatcher: "exec", argument: "nothingless run toggle-metrics" },
{ id: "window.close", label: "Close Window", category: "Window", dispatcher: "killactive", argument: "" },
{ id: "window.focus", label: "Focus Window", category: "Window", dispatcher: "movefocus", args: [{ key: "direction", label: "Direction", placeholder: "up/down/left/right", defaultValue: "up" }], argumentBuilder: function (args) {
@@ -80,11 +81,11 @@ var ACTION_CATALOG = [
{ id: "workspace.move-window-special", label: "Move Window to Special Workspace", category: "Workspace", dispatcher: "movetoworkspace", argument: "special" },
{ id: "workspace.move-window-special-silent", label: "Move Window to Special Workspace (Silent)", category: "Workspace", dispatcher: "movetoworkspacesilent", argument: "special" },
- { id: "scrolling.focus", label: "Focus (Scrolling)", category: "Scrolling Layout", dispatcher: "layoutmsg", args: [{ key: "direction", label: "Direction", placeholder: "up/down/left/right", defaultValue: "up" }], argumentBuilder: function (args) {
- return "focus " + directionToLetter(args.direction);
+ { id: "scrolling.focus", label: "Focus", category: "Window", dispatcher: "movefocus", args: [{ key: "direction", label: "Direction", placeholder: "up/down/left/right", defaultValue: "up" }], argumentBuilder: function (args) {
+ return directionToLetter(args.direction);
} },
- { id: "scrolling.move-window", label: "Move Window (Scrolling)", category: "Scrolling Layout", dispatcher: "layoutmsg", args: [{ key: "direction", label: "Direction", placeholder: "up/down/left/right", defaultValue: "left" }], argumentBuilder: function (args) {
- return "movewindowto " + directionToLetter(args.direction);
+ { id: "scrolling.move-window", label: "Move Window", category: "Window", dispatcher: "movewindow", args: [{ key: "direction", label: "Direction", placeholder: "up/down/left/right", defaultValue: "left" }], argumentBuilder: function (args) {
+ return directionToLetter(args.direction);
} },
{ id: "scrolling.resize-column", label: "Resize Column", category: "Scrolling Layout", dispatcher: "layoutmsg", args: [{ key: "delta", label: "Delta", placeholder: "+0.1 / -0.1", defaultValue: "+0.1" }], argumentBuilder: function (args) {
return "colresize " + String(args.delta || "").trim();
@@ -109,8 +110,8 @@ var ACTION_CATALOG = [
{ id: "audio.volume-down", label: "Volume Down", category: "Audio", dispatcher: "exec", argument: "wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 10%-", flags: "le" },
{ id: "audio.mute-toggle", label: "Mute Audio", category: "Audio", dispatcher: "exec", argument: "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle", flags: "le" },
- { id: "brightness.up", label: "Brightness Up", category: "Brightness", dispatcher: "exec", argument: "ambxst brightness +5", flags: "le" },
- { id: "brightness.down", label: "Brightness Down", category: "Brightness", dispatcher: "exec", argument: "ambxst brightness -5", flags: "le" },
+ { id: "brightness.up", label: "Brightness Up", category: "Brightness", dispatcher: "exec", argument: "nothingless brightness +5", flags: "le" },
+ { id: "brightness.down", label: "Brightness Down", category: "Brightness", dispatcher: "exec", argument: "nothingless brightness -5", flags: "le" },
{ id: "system.calculator", label: "Calculator", category: "System", dispatcher: "exec", argument: "notify-send \"Soon\"" },
{ id: "system.lock", label: "Lock Session", category: "System", dispatcher: "exec", argument: "loginctl lock-session" },
@@ -278,8 +279,8 @@ function actionFromLegacy(dispatcher, argument, flags) {
if (arg.indexOf("wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 10%+") === 0) return { id: "audio.volume-up", args: {} };
if (arg.indexOf("wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 10%-") === 0) return { id: "audio.volume-down", args: {} };
if (arg.indexOf("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle") === 0) return { id: "audio.mute-toggle", args: {} };
- if (arg.indexOf("ambxst brightness +5") === 0) return { id: "brightness.up", args: {} };
- if (arg.indexOf("ambxst brightness -5") === 0) return { id: "brightness.down", args: {} };
+ if (arg.indexOf("nothingless brightness +5") === 0) return { id: "brightness.up", args: {} };
+ if (arg.indexOf("nothingless brightness -5") === 0) return { id: "brightness.down", args: {} };
if (arg === "notify-send \"Soon\"") return { id: "system.calculator", args: {} };
if (arg === "loginctl lock-session" && flags === "l") return { id: "system.lock-locked", args: {} };
if (arg === "loginctl lock-session") return { id: "system.lock", args: {} };
diff --git a/config/defaults/ai.js b/config/defaults/ai.js
old mode 100644
new mode 100755
diff --git a/config/defaults/bar.js b/config/defaults/bar.js
old mode 100644
new mode 100755
index 7962cdde..a55e916a
--- a/config/defaults/bar.js
+++ b/config/defaults/bar.js
@@ -9,6 +9,7 @@ var data = {
"pillStyle": "default",
"screenList": [],
"enableFirefoxPlayer": false,
+ "enableChromiumPlayer": false,
"barColor": [["surface", 0.0]],
"frameEnabled": false,
"frameThickness": 6,
@@ -20,5 +21,9 @@ var data = {
"use12hFormat": false,
"containBar": false,
"keepBarShadow": false,
- "keepBarBorder": false
+ "keepBarBorder": false,
+ "hiddenIcons": [],
+ "taskTrayEnabled": true,
+ "taskTrayShowToggle": true,
+ "taskTrayAlwaysVisible": []
}
diff --git a/config/defaults/compositor.js b/config/defaults/compositor.js
old mode 100644
new mode 100755
index 89b3dd39..f0793dfd
--- a/config/defaults/compositor.js
+++ b/config/defaults/compositor.js
@@ -1,19 +1,47 @@
.pragma library
var data = {
+ // === Borders & Rounding ===
+ "showBorder": true,
"activeBorderColor": ["primary"],
"borderAngle": 45,
"inactiveBorderColor": ["surface"],
"inactiveBorderAngle": 45,
"borderSize": 2,
"rounding": 16,
+ "roundingPower": 2.0,
"syncRoundness": true,
"syncBorderWidth": false,
"syncBorderColor": false,
"syncShadowOpacity": false,
"syncShadowColor": false,
+ "resizeOnBorder": false,
+ "extendBorderGrabArea": 15,
+ "hoverIconOnBorder": true,
+
+ // === Gaps & Layout ===
"gapsIn": 2,
"gapsOut": 4,
+ "layout": "dwindle",
+ "allowTearing": false,
+
+ // === Snap ===
+ "snapEnabled": true,
+ "snapWindowGap": 10,
+ "snapMonitorGap": 10,
+ "snapBorderOverlap": false,
+ "snapRespectGaps": false,
+
+ // === Opacity & Dim ===
+ "activeOpacity": 1.0,
+ "inactiveOpacity": 1.0,
+ "fullscreenOpacity": 1.0,
+ "dimInactive": false,
+ "dimStrength": 0.5,
+ "dimAround": 0.4,
+ "dimSpecial": 0.2,
+
+ // === Shadow ===
"shadowEnabled": true,
"shadowRange": 8,
"shadowRenderPower": 3,
@@ -24,6 +52,8 @@ var data = {
"shadowOpacity": 0.5,
"shadowOffset": "0 0",
"shadowScale": 1.0,
+
+ // === Blur ===
"blurEnabled": true,
"blurSize": 4,
"blurPasses": 2,
@@ -41,5 +71,116 @@ var data = {
"blurPopups": false,
"blurPopupsIgnorealpha": 0.2,
"blurInputMethods": false,
- "blurInputMethodsIgnorealpha": 0.2
+ "blurInputMethodsIgnorealpha": 0.2,
+
+ // === Animations ===
+ "animationsEnabled": true,
+
+ // === Input: Keyboard ===
+ "kbLayout": "us",
+ "kbVariant": "",
+ "kbOptions": "",
+ "numlockByDefault": false,
+ "repeatRate": 25,
+ "repeatDelay": 600,
+
+ // === Input: Mouse ===
+ "mouseSensitivity": 0.0,
+ "mouseAccelProfile": "",
+ "followMouse": 1,
+ "mouseNaturalScroll": false,
+ "mouseScrollFactor": 1.0,
+ "mouseLeftHanded": false,
+ "mouseRefocus": false,
+ "floatSwitchOverrideFocus": 0,
+
+ // === Input: Touchpad ===
+ "touchpadDisableWhileTyping": true,
+ "touchpadNaturalScroll": true,
+ "touchpadTapToClick": true,
+ "touchpadClickfingerBehavior": false,
+ "touchpadTapButtonMap": "",
+ "touchpadMiddleButtonEmulation": false,
+ "touchpadDragLock": 0,
+ "touchpadScrollFactor": 1.0,
+
+ // === Cursor ===
+ "noHardwareCursors": false,
+ "enableHyprcursor": true,
+ "noWarps": false,
+ "persistentWarps": false,
+ "warpOnChangeWorkspace": false,
+ "cursorZoomFactor": 1.0,
+ "cursorInactiveTimeout": 0,
+ "cursorHideOnKeyPress": false,
+ "cursorHideOnTouch": false,
+ "cursorHideOnTablet": false,
+
+ // === Gestures ===
+ "workspaceSwipeCreateNew": true,
+ "workspaceSwipeForever": false,
+ "workspaceSwipeCancelRatio": 0.5,
+ "workspaceSwipeMinSpeedToForce": 30,
+ "workspaceSwipeDirectionLock": true,
+ "workspaceSwipeUseR": false,
+ "workspaceSwipeDistance": 300,
+ "workspaceSwipeInvert": true,
+ "workspaceSwipeTouch": false,
+ "workspaceSwipeTouchInvert": false,
+
+ // === Dwindle Layout ===
+ "dwindlePreserveSplit": true,
+ "dwindlePseudotile": false,
+ "dwindleForceSplit": 0,
+ "dwindleSmartSplit": true,
+ "dwindleDefaultSplitRatio": 1.0,
+ "dwindleSplitWidthMultiplier": 1.0,
+ "dwindlePermanentDirectionOverride": false,
+ "dwindleUseActiveForSplits": true,
+ "dwindleSmartResizing": true,
+ "dwindleSpecialScaleFactor": 0.8,
+
+ // === Master Layout ===
+ "masterOrientation": "left",
+ "masterMfact": 0.55,
+ "masterNewStatus": "slave",
+ "masterNewOnTop": false,
+ "masterNewOnActive": "none",
+ "masterSmartResizing": true,
+ "masterSpecialScaleFactor": 0.8,
+ "masterAllowSmallSplit": false,
+
+ // === Scrolling Layout ===
+ "scrollingColumnWidth": 0.3,
+ "scrollingExplicitColumnWidths": "",
+ "scrollingDirection": "right",
+ "scrollingFullscreenOnOneColumn": true,
+ "scrollingFocusFitMethod": "center",
+ "scrollingFollowFocus": true,
+ "scrollingFollowMinVisible": 0.1,
+
+ // === XWayland ===
+ "xwaylandEnabled": true,
+ "xwaylandForceZeroScaling": false,
+ "xwaylandUseNearestNeighbor": true,
+
+ // === Monitor Globals ===
+ "vrr": 0,
+ "vfr": true,
+ "mouseMoveEnablesDpms": false,
+ "keyPressEnablesDpms": false,
+
+ // === Misc ===
+ "renderBackend": "opengl",
+ "disableAutoreload": false,
+ "focusOnActivate": false,
+ "animateManualResizes": false,
+ "animateMouseWindowdragging": true,
+ "disableHyprlandLogo": true,
+ "disableSplashRendering": false,
+ "forceDefaultWallpaper": -1,
+
+ // === Ecosystem ===
+ "noUpdateNews": true,
+ "enforcePermissions": false
}
diff --git a/config/defaults/desktop.js b/config/defaults/desktop.js
old mode 100644
new mode 100755
diff --git a/config/defaults/dock.js b/config/defaults/dock.js
old mode 100644
new mode 100755
diff --git a/config/defaults/lockscreen.js b/config/defaults/lockscreen.js
old mode 100644
new mode 100755
diff --git a/config/defaults/notch.js b/config/defaults/notch.js
old mode 100644
new mode 100755
index decb7159..3879df41
--- a/config/defaults/notch.js
+++ b/config/defaults/notch.js
@@ -6,6 +6,7 @@ var data = {
"hoverRegionHeight": 8,
"keepHidden": false,
"noMediaDisplay": "userHost",
- "customText": "Ambxst",
- "disableHoverExpansion": true
+ "customText": "NothingLess",
+ "disableHoverExpansion": true,
+ "showMetrics": false
}
diff --git a/config/defaults/overview.js b/config/defaults/overview.js
old mode 100644
new mode 100755
diff --git a/config/defaults/performance.js b/config/defaults/performance.js
old mode 100644
new mode 100755
diff --git a/config/defaults/prefix.js b/config/defaults/prefix.js
old mode 100644
new mode 100755
diff --git a/config/defaults/system.js b/config/defaults/system.js
old mode 100644
new mode 100755
index e3619102..fc8944bc
--- a/config/defaults/system.js
+++ b/config/defaults/system.js
@@ -3,17 +3,26 @@
var data = {
"disks": ["/"],
"updateServiceEnabled": true,
+ "batteryNotifications": {
+ "enabled": true,
+ "lowThreshold": 20,
+ "criticalThreshold": 10,
+ "autoPowerSave": false,
+ "powerSaveThreshold": 15,
+ "chargeLimit": 80,
+ "chargeLimitEnabled": false
+ },
"idle": {
"general": {
- "lock_cmd": "ambxst lock",
+ "lock_cmd": "nothingless lock",
"before_sleep_cmd": "loginctl lock-session",
- "after_sleep_cmd": "ambxst screen on"
+ "after_sleep_cmd": "nothingless screen on"
},
"listeners": [
{
"timeout": 150,
- "onTimeout": "ambxst brightness 10 -s",
- "onResume": "ambxst brightness -r"
+ "onTimeout": "nothingless brightness 10 -s",
+ "onResume": "nothingless brightness -r"
},
{
"timeout": 300,
@@ -21,12 +30,12 @@ var data = {
},
{
"timeout": 330,
- "onTimeout": "ambxst screen off",
- "onResume": "ambxst screen on"
+ "onTimeout": "nothingless screen off",
+ "onResume": "nothingless screen on"
},
{
"timeout": 1800,
- "onTimeout": "ambxst suspend"
+ "onTimeout": "nothingless suspend"
}
]
},
diff --git a/config/defaults/theme.js b/config/defaults/theme.js
old mode 100644
new mode 100755
diff --git a/config/defaults/weather.js b/config/defaults/weather.js
old mode 100644
new mode 100755
diff --git a/config/defaults/workspaces.js b/config/defaults/workspaces.js
old mode 100644
new mode 100755
diff --git a/config/pam/password.conf b/config/pam/password.conf
old mode 100644
new mode 100755
index 6b2031fb..42afcd8f
--- a/config/pam/password.conf
+++ b/config/pam/password.conf
@@ -1,3 +1,3 @@
-# PAM configuration for Ambxst lockscreen
+# PAM configuration for NothingLess lockscreen
# Uses system authentication
auth required pam_unix.so
diff --git a/flake.lock b/flake.lock
old mode 100644
new mode 100755
index 4268e2fa..85bcb320
--- a/flake.lock
+++ b/flake.lock
@@ -7,26 +7,26 @@
]
},
"locked": {
- "lastModified": 1775093664,
- "narHash": "sha256-V+G+xjZndXWACpN3YLkH1T8XePmGXJby4VpIwePKXsY=",
- "owner": "Axenide",
+ "lastModified": 1779243005,
+ "narHash": "sha256-oDJEY0yeMUDFvCUTKW69/lzoVk6cuRPscT0myeKnvec=",
+ "owner": "Leriart",
"repo": "axctl",
- "rev": "bb47ec7e39ee81f64daa45de2eb99d9a726e49a8",
+ "rev": "e8ac14221379cc5a5c8565c510123e1c0b7dff01",
"type": "github"
},
"original": {
- "owner": "Axenide",
+ "owner": "Leriart",
"repo": "axctl",
"type": "github"
}
},
"nixpkgs": {
"locked": {
- "lastModified": 1775036866,
- "narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
+ "lastModified": 1778869304,
+ "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
+ "rev": "d233902339c02a9c334e7e593de68855ad26c4cb",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
old mode 100644
new mode 100755
index 891c7d81..93a8e967
--- a/flake.nix
+++ b/flake.nix
@@ -1,26 +1,27 @@
{
- description = "Ambxst - An Axtremely customizable shell by Axenide";
+ description = "NothingLess - An Axtremely customizable shell by Leriart";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
axctl = {
- url = "github:Axenide/axctl";
+ url = "github:Leriart/axctl";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, axctl, ... }:
let
- ambxstLib = import ./nix/lib.nix { inherit nixpkgs; };
+ nothinglessLib = import ./nix/lib.nix { inherit nixpkgs; };
+ version = nixpkgs.lib.removeSuffix "\n" (builtins.readFile ./version);
in {
nixosModules.default = { pkgs, lib, ... }: {
imports = [ ./nix/modules ];
- programs.ambxst.enable = lib.mkDefault true;
- programs.ambxst.package = lib.mkDefault self.packages.${pkgs.system}.default;
+ programs.nothingless.enable = lib.mkDefault true;
+ programs.nothingless.package = lib.mkDefault self.packages.${pkgs.system}.default;
};
- packages = ambxstLib.forAllSystems (system:
+ packages = nothinglessLib.forAllSystems (system:
let
pkgs = import nixpkgs {
inherit system;
@@ -29,38 +30,38 @@
lib = nixpkgs.lib;
- Ambxst = import ./nix/packages {
- inherit pkgs lib self system axctl;
+ NothingLess = import ./nix/packages {
+ inherit pkgs lib self system axctl version;
};
in {
- default = Ambxst;
- Ambxst = Ambxst;
+ default = NothingLess;
+ NothingLess = NothingLess;
}
);
- devShells = ambxstLib.forAllSystems (system:
+ devShells = nothinglessLib.forAllSystems (system:
let
pkgs = import nixpkgs { inherit system; };
- Ambxst = self.packages.${system}.default;
+ NothingLess = self.packages.${system}.default;
in {
default = pkgs.mkShell {
- packages = [ Ambxst ];
+ packages = [ NothingLess ];
shellHook = ''
- export QML2_IMPORT_PATH="${Ambxst}/lib/qt-6/qml:$QML2_IMPORT_PATH"
+ export QML2_IMPORT_PATH="${NothingLess}/lib/qt-6/qml:$QML2_IMPORT_PATH"
export QML_IMPORT_PATH="$QML2_IMPORT_PATH"
- echo "Ambxst dev environment loaded."
+ echo "NothingLess dev environment loaded."
'';
};
}
);
- apps = ambxstLib.forAllSystems (system:
+ apps = nothinglessLib.forAllSystems (system:
let
- Ambxst = self.packages.${system}.default;
+ NothingLess = self.packages.${system}.default;
in {
default = {
type = "app";
- program = "${Ambxst}/bin/ambxst";
+ program = "${NothingLess}/bin/nothingless";
};
}
);
diff --git a/install.sh b/install.sh
index 22c36696..935cb4a6 100755
--- a/install.sh
+++ b/install.sh
@@ -2,8 +2,8 @@
set -e
# === Configuration ===
-REPO_URL="https://github.com/Axenide/Ambxst.git"
-INSTALL_PATH="$HOME/.local/src/ambxst"
+REPO_URL="https://github.com/Leriart/NothingLess.git"
+INSTALL_PATH="$HOME/.local/src/nothingless"
BIN_DIR="/usr/local/bin"
QUICKSHELL_REPO="https://git.outfoxxed.me/outfoxxed/quickshell"
@@ -36,7 +36,6 @@ DISTRO=$(detect_distro)
log_info "Detected: $DISTRO"
# === Package Filtering ===
-# Maps packages to their binary/check - only for conflict-prone packages
declare -A BINARY_CHECK=(
["matugen"]="matugen"
["quickshell"]="qs"
@@ -65,6 +64,7 @@ declare -A THEME_CHECK=(
declare -A FONT_CHECK=(
["ttf-phosphor-icons"]="Phosphor"
+ ["ttf-ndot"]="Ndot"
)
filter_packages() {
@@ -95,14 +95,14 @@ filter_packages() {
install_dependencies() {
case "$DISTRO" in
nixos)
- local FLAKE_URI="${1:-github:Axenide/Ambxst}"
+ local FLAKE_URI="${1:-github:Leriart/NothingLess}"
nix profile list | grep -q "ddcutil" && nix profile remove ddcutil 2>/dev/null || true
- if nix profile list | grep -q "Ambxst"; then
- log_info "Updating Ambxst..."
- nix profile upgrade Ambxst --refresh --impure
+ if nix profile list | grep -q "NothingLess"; then
+ log_info "Updating NothingLess..."
+ nix profile upgrade NothingLess --refresh --impure
else
- log_info "Installing Ambxst..."
+ log_info "Installing NothingLess..."
nix profile add "$FLAKE_URI" --impure
fi
;;
@@ -140,6 +140,8 @@ install_dependencies() {
flatpak install -y flathub be.alexandervanhee.gradia 2>/dev/null || true
install_phosphor_fonts
+ install_ndot_font
+install_color_presets
;;
arch)
@@ -189,6 +191,9 @@ install_dependencies() {
else
log_info "All packages already installed"
fi
+ install_color_presets
+
+ install_ndot_font
;;
*)
@@ -216,72 +221,59 @@ install_phosphor_fonts() {
log_success "Phosphor Icons installed"
}
-# === Migration ===
-migrate_old_paths() {
- log_info "Checking for old Ambxst paths..."
-
- # Source migration (PascalCase -> lowercase)
- local OLD_SRC="$HOME/Ambxst"
- if [[ -d "$OLD_SRC" && ! -d "$INSTALL_PATH" ]]; then
- log_info "Migrating source: $OLD_SRC -> $INSTALL_PATH"
- mkdir -p "$(dirname "$INSTALL_PATH")"
- cp -r "$OLD_SRC" "$INSTALL_PATH"
- fi
+install_color_presets() {
+ log_info "Installing Nothing color theme..."
+ local COLOR_DIR="$HOME/.config/nothingless/colors/Nothing"
+ mkdir -p "$COLOR_DIR"
+
+ local SRC_DIR="$INSTALL_PATH/assets/colors/Nothing"
+ if [[ -d "$SRC_DIR" ]]; then
+ cp -rn "$SRC_DIR"/* "$COLOR_DIR/" 2>/dev/null || true
+ log_success "Nothing color theme installed"
+ else
+ log_warn "Nothing color theme not found in repo."
+ fi
+}
- # Config migration
- local OLD_CONFIG="$HOME/.config/Ambxst"
- local NEW_CONFIG="$HOME/.config/ambxst"
- if [[ -d "$OLD_CONFIG" && ! -d "$NEW_CONFIG" ]]; then
- log_info "Migrating config: $OLD_CONFIG -> $NEW_CONFIG"
- mv "$OLD_CONFIG" "$NEW_CONFIG"
- fi
+install_ndot_font() {
+ has_font "Ndot" && return
- # Share migration
- local OLD_SHARE="$HOME/.local/share/Ambxst"
- local NEW_SHARE="$HOME/.local/share/ambxst"
- if [[ -d "$OLD_SHARE" && ! -d "$NEW_SHARE" ]]; then
- log_info "Migrating share: $OLD_SHARE -> $NEW_SHARE"
- mv "$OLD_SHARE" "$NEW_SHARE"
- fi
+ log_info "Installing Ndot font..."
+ local FONT_DIR="$HOME/.local/share/fonts/ndot"
+ mkdir -p "$FONT_DIR"
- # State migration
- local OLD_STATE="$HOME/.local/state/Ambxst"
- local NEW_STATE="$HOME/.local/state/ambxst"
- if [[ -d "$OLD_STATE" && ! -d "$NEW_STATE" ]]; then
- log_info "Migrating state: $OLD_STATE -> $NEW_STATE"
- mv "$OLD_STATE" "$NEW_STATE"
+ local NDOT_SRC="$INSTALL_PATH/assets/fonts"
+ if [[ -f "$NDOT_SRC/Ndot-57-Aligned.ttf" ]]; then
+ cp "$NDOT_SRC/Ndot-57-Aligned.ttf" "$FONT_DIR/"
+ else
+ # Try from the script's location
+ local SCRIPT_DIR
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+ if [[ -f "$SCRIPT_DIR/assets/fonts/Ndot-57-Aligned.ttf" ]]; then
+ cp "$SCRIPT_DIR/assets/fonts/Ndot-57-Aligned.ttf" "$FONT_DIR/"
+ else
+ log_warn "Ndot font not found in repo. Skipping."
+ return
+ fi
fi
- # Cache migration
- local OLD_CACHE_DIR="$HOME/.cache/Ambxst"
- local NEW_CACHE_DIR="$HOME/.cache/ambxst"
- if [[ -d "$OLD_CACHE_DIR" && ! -d "$NEW_CACHE_DIR" ]]; then
- log_info "Migrating cache: $OLD_CACHE_DIR -> $NEW_CACHE_DIR"
- mv "$OLD_CACHE_DIR" "$NEW_CACHE_DIR"
- fi
+ fc-cache -f "$FONT_DIR"
+ log_success "Ndot font installed"
+}
- # Legacy share -> cache migration (Wallpapers & Thumbnails)
- local NEW_CACHE="$HOME/.cache/ambxst"
- if [[ -d "$NEW_SHARE" ]]; then
- mkdir -p "$NEW_CACHE"
+# === Migration ===
+migrate_old_paths() {
+ log_info "Checking for old paths..."
- if [[ -f "$NEW_SHARE/wallpapers.json" && ! -f "$NEW_CACHE/wallpapers.json" ]]; then
- log_info "Migrating wallpapers.json to cache..."
- cp "$NEW_SHARE/wallpapers.json" "$NEW_CACHE/wallpapers.json"
- fi
+ local OLD_CONFIG="$HOME/.config/nothingless"
+ if [[ ! -d "$OLD_CONFIG" ]]; then
+ mkdir -p "$OLD_CONFIG/config"
- if [[ -d "$NEW_SHARE/thumbnails" && ! -d "$NEW_CACHE/thumbnails" ]]; then
- log_info "Migrating thumbnails to cache..."
- cp -r "$NEW_SHARE/thumbnails" "$NEW_CACHE/thumbnails"
+ # Copy default configs from the install path
+ if [[ -d "$INSTALL_PATH/config/defaults" ]]; then
+ cp -r "$INSTALL_PATH/config/defaults/"* "$OLD_CONFIG/config/" 2>/dev/null || true
fi
fi
-
- # Config structure warning
- if [[ -f "$NEW_CONFIG/config.json" && ! -d "$NEW_CONFIG/config" ]]; then
- log_warn "Old single-file config detected."
- log_info "Ambxst now uses a multi-file configuration in $NEW_CONFIG/config/"
- log_info "Your old config.json remains at $NEW_CONFIG/config.json for reference."
- fi
}
# === Repository Setup ===
@@ -289,19 +281,17 @@ setup_repo() {
[[ "$DISTRO" == "nixos" ]] && return
if [[ ! -d "$INSTALL_PATH" ]]; then
- log_info "Cloning Ambxst to $INSTALL_PATH..."
+ log_info "Cloning NothingLess to $INSTALL_PATH..."
mkdir -p "$(dirname "$INSTALL_PATH")"
git clone "$REPO_URL" "$INSTALL_PATH"
return
fi
- # Check if it's a git repository
if [[ ! -d "$INSTALL_PATH/.git" ]]; then
log_warn "$INSTALL_PATH exists but is not a git repository."
log_info "Re-initializing repository..."
local TMP_DIR
TMP_DIR=$(mktemp -d)
- # Move everything to tmp, avoiding . and ..
find "$INSTALL_PATH" -mindepth 1 -maxdepth 1 -exec mv -t "$TMP_DIR" {} +
rm -rf "$INSTALL_PATH"
git clone "$REPO_URL" "$INSTALL_PATH"
@@ -361,17 +351,6 @@ install_quickshell() {
log_success "Quickshell installed to ~/.local/bin/qs"
}
-install_axctl() {
- if [[ "$DISTRO" == "nixos" ]]; then
- log_info "Skipping axctl install on NixOS (managed by flake)"
- return
- fi
-
- log_info "Installing axctl..."
- curl -L get.axeni.de/axctl | sh
- log_success "axctl installed"
-}
-
# === Python Tools ===
install_python_tools() {
[[ "$DISTRO" == "nixos" ]] && return
@@ -381,7 +360,6 @@ install_python_tools() {
}
log_info "Installing Python tools..."
-
pipx ensurepath 2>/dev/null || true
}
@@ -410,10 +388,6 @@ configure_services() {
elif has_cmd rc-service; then
log_info "Configuring OpenRC services..."
- rc-update show | grep -q "iwd" && {
- sudo rc-service iwd stop 2>/dev/null || true
- sudo rc-update del iwd default 2>/dev/null || true
- }
sudo rc-update add NetworkManager default 2>/dev/null || true
sudo rc-service NetworkManager start 2>/dev/null || true
sudo rc-update add bluetooth default 2>/dev/null || true
@@ -422,7 +396,6 @@ configure_services() {
elif has_cmd sv; then
log_info "Configuring runit services..."
local SV_DIR="/var/service"
- [[ -L "$SV_DIR/iwd" ]] && sudo rm "$SV_DIR/iwd"
[[ -d "/etc/sv/NetworkManager" && ! -L "$SV_DIR/NetworkManager" ]] && sudo ln -s /etc/sv/NetworkManager "$SV_DIR/"
[[ -d "/etc/sv/bluetooth" && ! -L "$SV_DIR/bluetooth" ]] && sudo ln -s /etc/sv/bluetooth "$SV_DIR/"
@@ -435,10 +408,10 @@ configure_services() {
setup_launcher() {
[[ "$DISTRO" == "nixos" ]] && return
- [[ -f "$HOME/.local/bin/ambxst" ]] && rm -f "$HOME/.local/bin/ambxst"
+ [[ -f "$HOME/.local/bin/nothingless" ]] && rm -f "$HOME/.local/bin/nothingless"
sudo mkdir -p "$BIN_DIR"
- local LAUNCHER="$BIN_DIR/ambxst"
+ local LAUNCHER="$BIN_DIR/nothingless"
log_info "Creating launcher at $LAUNCHER..."
sudo tee "$LAUNCHER" >/dev/null <<-EOF
@@ -455,13 +428,13 @@ setup_launcher() {
# === Main ===
migrate_old_paths
install_dependencies "$1"
-install_axctl
setup_repo
install_quickshell
+install_ndot_font
install_python_tools
configure_services
setup_launcher
echo ""
log_success "Installation complete!"
-[[ "$DISTRO" != "nixos" ]] && echo -e "Run ${GREEN}ambxst${NC} to start."
+[[ "$DISTRO" != "nixos" ]] && echo -e "Run ${GREEN}nothingless${NC} to start."
diff --git a/modules/bar/AGENTS.md b/modules/bar/AGENTS.md
old mode 100644
new mode 100755
diff --git a/modules/bar/Bar.qml b/modules/bar/Bar.qml
old mode 100644
new mode 100755
diff --git a/modules/bar/BarBg.qml b/modules/bar/BarBg.qml
old mode 100644
new mode 100755
diff --git a/modules/bar/BarBgShadow.qml b/modules/bar/BarBgShadow.qml
old mode 100644
new mode 100755
diff --git a/modules/bar/BarContent.qml b/modules/bar/BarContent.qml
old mode 100644
new mode 100755
index b9e57ca7..ecb33940
--- a/modules/bar/BarContent.qml
+++ b/modules/bar/BarContent.qml
@@ -8,6 +8,7 @@ import qs.modules.bar.workspaces
import qs.modules.theme
import qs.modules.bar.clock
import qs.modules.bar.systray
+import qs.modules.bar.tasktray
import qs.modules.widgets.overview
import qs.modules.widgets.dashboard
import qs.modules.widgets.powermenu
@@ -19,28 +20,21 @@ import qs.modules.globals
import qs.modules.bar
import qs.config
import "." as Bar
-
Item {
id: root
-
required property ShellScreen screen
-
property string barPosition: (Config.bar && Config.bar.position !== undefined && ["top", "bottom", "left", "right"].includes(Config.bar.position) ? Config.bar.position : "top")
property string orientation: barPosition === "left" || barPosition === "right" ? "vertical" : "horizontal"
-
// Auto-hide properties
onPinnedChanged: {
if (Config.bar && Config.bar.pinnedOnStartup !== pinned) {
Config.bar.pinnedOnStartup = pinned;
}
}
-
property bool pinned: (Config.bar && Config.bar.pinnedOnStartup !== undefined ? Config.bar.pinnedOnStartup : true)
-
// Monitor reference and reference to toplevels on monitor
readonly property var compositorMonitor: AxctlService.monitorFor(screen)
readonly property var toplevels: (!compositorMonitor || !compositorMonitor.activeWorkspace || !AxctlService.clients.values) ? [] : AxctlService.clients.values.filter(c => c.workspace.id === compositorMonitor.activeWorkspace.id)
-
// Fullscreen detection - use ToplevelManager (native Wayland) for reliable detection
readonly property bool activeWindowFullscreen: {
const toplevel = ToplevelManager.activeToplevel;
@@ -48,24 +42,18 @@ Item {
return false;
return toplevel.fullscreen === true;
}
-
-
// Whether auto-hide should be active (not pinned, or fullscreen forces it)
readonly property bool shouldAutoHide: !pinned || activeWindowFullscreen
-
onShouldAutoHideChanged: {
if (!shouldAutoHide) {
hoverActive = false;
hideDelayTimer.stop();
}
}
-
// Hover state with delay to prevent flickering
property bool hoverActive: false
-
// Track if mouse is over bar area
- readonly property bool isMouseOverBar: barMouseArea.containsMouse
-
+ property bool isMouseOverBar: true
// Check if notch hover is active (for synchronized reveal when bar is at same side)
// NOTE: We access Visibilities.notchPanels directly because UnifiedShellPanel registers itself as the panel ref
readonly property var notchPanelRef: Visibilities.notchPanels[screen.name]
@@ -73,7 +61,6 @@ Item {
readonly property bool notchHoverActive: {
if (barPosition !== notchPosition)
return false;
-
if (notchPanelRef) {
// UnifiedShellPanel exposes 'notchHoverActive' property alias pointing to notchContent.hoverActive
// We need to check if that property exists on the panel object
@@ -87,32 +74,26 @@ Item {
}
return false;
}
-
// Check if notch is open (dashboard, powermenu, etc.)
readonly property var screenVisibilities: Visibilities.getForScreen(screen.name)
readonly property bool notchOpen: screenVisibilities ? (screenVisibilities.launcher || screenVisibilities.dashboard || screenVisibilities.powermenu || screenVisibilities.tools) : false
-
// Radius logic for "Squished" style
readonly property real outerRadius: Styling.radius(0)
readonly property real innerRadius: (Config.bar && Config.bar.pillStyle === "squished") ? Styling.radius(0) / 2 : Styling.radius(0)
readonly property bool pinButtonVisible: (Config.bar && Config.bar.showPinButton !== undefined ? Config.bar.showPinButton : true)
-
// Reveal logic
readonly property bool reveal: {
// If not auto-hiding, always reveal
if (!shouldAutoHide)
return true;
-
// If fullscreen and not available on fullscreen, hide
if (activeWindowFullscreen && !(Config.bar && Config.bar.availableOnFullscreen !== undefined ? Config.bar.availableOnFullscreen : false)) {
return false;
}
-
// Show if: hovering, notch hovering (when at top), notch open
// IMPORTANT: notchHoverActive must be checked to synchronize with notch
return isMouseOverBar || hoverActive || notchHoverActive || notchOpen;
}
-
// Timer to delay hiding the bar after mouse leaves
Timer {
id: hideDelayTimer
@@ -124,7 +105,6 @@ Item {
}
}
}
-
// Watch for mouse state changes
onIsMouseOverBarChanged: {
if (isMouseOverBar) {
@@ -140,13 +120,11 @@ Item {
}
}
}
-
// Integrated dock configuration
readonly property bool integratedDockEnabled: (Config.dock && Config.dock.enabled !== undefined ? Config.dock.enabled : false) && (Config.dock && Config.dock.theme !== undefined ? Config.dock.theme : "default") === "integrated"
// Map dock position for integrated based on orientation
readonly property string integratedDockPosition: {
const pos = (Config.dock && Config.dock.position !== undefined ? Config.dock.position : "center");
-
if (root.orientation === "horizontal") {
if (pos === "left" || pos === "start")
return "start";
@@ -154,58 +132,52 @@ Item {
return "end";
return "center";
}
-
// Vertical always falls back to center logic inside the column but we treat it as appended to group
return "center";
}
-
// Radius helpers for dock connections
readonly property bool dockAtStart: integratedDockEnabled && integratedDockPosition === "start"
readonly property bool dockAtEnd: integratedDockEnabled && integratedDockPosition === "end"
-
readonly property int frameOffset: (Config.bar && Config.bar.frameEnabled !== undefined ? Config.bar.frameEnabled : false) ? (Config.bar && Config.bar.frameThickness !== undefined ? Config.bar.frameThickness : 6) : 0
-
// Size derived from barBg properties
readonly property int barPadding: barBg.padding
readonly property int topOuterMargin: (orientation === "vertical" || barPosition === "top") ? barBg.outerMargin : 0
readonly property int bottomOuterMargin: (orientation === "vertical" || barPosition === "bottom") ? barBg.outerMargin : 0
readonly property int leftOuterMargin: (orientation === "horizontal" || barPosition === "left") ? barBg.outerMargin : 0
readonly property int rightOuterMargin: (orientation === "horizontal" || barPosition === "right") ? barBg.outerMargin : 0
-
readonly property int contentImplicitWidth: orientation === "horizontal" ? (horizontalLoader.item && horizontalLoader.item.implicitWidth !== undefined ? horizontalLoader.item.implicitWidth : 0) : (verticalLoader.item && verticalLoader.item.implicitWidth !== undefined ? verticalLoader.item.implicitWidth : 0)
readonly property int contentImplicitHeight: orientation === "horizontal" ? (horizontalLoader.item && horizontalLoader.item.implicitHeight !== undefined ? horizontalLoader.item.implicitHeight : 0) : (verticalLoader.item && verticalLoader.item.implicitHeight !== undefined ? verticalLoader.item.implicitHeight : 0)
-
readonly property int barTargetWidth: orientation === "vertical" ? (contentImplicitWidth + 2 * barPadding) : 0
readonly property int barTargetHeight: orientation === "horizontal" ? (contentImplicitHeight + 2 * barPadding) : 0
-
readonly property bool actualContainBar: (Config.bar && Config.bar.containBar !== undefined ? Config.bar.containBar : false) && (Config.bar && Config.bar.frameEnabled !== undefined ? Config.bar.frameEnabled : false)
readonly property int totalBarWidth: barTargetWidth +
((root.barPosition === "left" || root.orientation === "horizontal") ? (root.frameOffset + root.leftOuterMargin) : 0) +
((root.barPosition === "right" || root.orientation === "horizontal") ? (root.frameOffset + root.rightOuterMargin) : 0)
-
readonly property int totalBarHeight: barTargetHeight +
((root.barPosition === "top" || root.orientation === "vertical") ? (root.frameOffset + root.topOuterMargin) : 0) +
((root.barPosition === "bottom" || root.orientation === "vertical") ? (root.frameOffset + root.bottomOuterMargin) : 0)
-
// Base outer margin for reservation logic (4px + border when !containBar)
readonly property int baseOuterMargin: barBg.outerMargin
-
// Shadow logic for bar components
readonly property bool shadowsEnabled: Config.showBackground && (!actualContainBar || (Config.bar && Config.bar.keepBarShadow !== undefined ? Config.bar.keepBarShadow : false))
-
// The hitbox for the mask
property alias barHitbox: barMouseArea
-
// MouseArea for hover detection - contains bar content (like Dock)
MouseArea {
id: barMouseArea
- hoverEnabled: true
-
+ hoverEnabled: false
+ acceptedButtons: Qt.NoButton
+ propagateComposedEvents: true
+ // HoverHandler for bar hover detection (without blocking child hovers)
+ HoverHandler {
+ id: barHoverHandler
+ onHoveredChanged: {
+ root.isMouseOverBar = barHoverHandler.hovered;
+ }
+ }
// Size includes margins
width: root.orientation === "horizontal" ? root.width : (root.reveal ? root.totalBarWidth : Math.max((Config.bar && Config.bar.hoverRegionHeight !== undefined ? Config.bar.hoverRegionHeight : 8), 4) + root.frameOffset)
height: root.orientation === "vertical" ? root.height : (root.reveal ? root.totalBarHeight : Math.max((Config.bar && Config.bar.hoverRegionHeight !== undefined ? Config.bar.hoverRegionHeight : 8), 4) + root.frameOffset)
-
-
// Position using x/y
x: {
if (root.barPosition === "right") return parent.width - width;
@@ -215,7 +187,6 @@ Item {
if (root.barPosition === "bottom") return parent.height - height;
return 0;
}
-
Behavior on x {
enabled: (Config.animDuration !== undefined ? Config.animDuration : 0) > 0 && root.orientation === "vertical"
NumberAnimation {
@@ -230,7 +201,6 @@ Item {
easing.type: Easing.OutCubic
}
}
-
Behavior on width {
enabled: (Config.animDuration !== undefined ? Config.animDuration : 0) > 0 && root.orientation === "vertical"
NumberAnimation {
@@ -245,27 +215,21 @@ Item {
easing.type: Easing.OutCubic
}
}
-
// Bar content inside MouseArea (clicks pass through to children)
Item {
id: bar
-
anchors {
top: (root.barPosition === "top" || root.orientation === "vertical") ? parent.top : undefined
bottom: (root.barPosition === "bottom" || root.orientation === "vertical") ? parent.bottom : undefined
left: (root.barPosition === "left" || root.orientation === "horizontal") ? parent.left : undefined
right: (root.barPosition === "right" || root.orientation === "horizontal") ? parent.right : undefined
-
topMargin: (root.barPosition === "top" || root.orientation === "vertical") ? (root.frameOffset + root.topOuterMargin) : 0
bottomMargin: (root.barPosition === "bottom" || root.orientation === "vertical") ? (root.frameOffset + root.bottomOuterMargin) : 0
leftMargin: (root.barPosition === "left" || root.orientation === "horizontal") ? (root.frameOffset + root.leftOuterMargin) : 0
rightMargin: (root.barPosition === "right" || root.orientation === "horizontal") ? (root.frameOffset + root.rightOuterMargin) : 0
}
-
-
// layer.enabled: true
// layer.effect: Shadow {}
-
// Opacity animation
opacity: root.reveal ? 1 : 0
Behavior on opacity {
@@ -275,7 +239,6 @@ Item {
easing.type: Easing.OutCubic
}
}
-
// Slide animation
transform: Translate {
x: {
@@ -311,7 +274,6 @@ Item {
}
}
}
-
states: [
State {
name: "top"
@@ -346,29 +308,24 @@ Item {
}
}
]
-
BarBg {
id: barBg
anchors.fill: parent
position: root.barPosition
-
Loader {
id: horizontalLoader
active: root.orientation === "horizontal"
anchors.fill: parent
sourceComponent: RowLayout {
spacing: 4
-
// Obtener referencia al notch de esta pantalla
readonly property var notchContainer: Visibilities.getNotchForScreen(root.screen.name)
-
LauncherButton {
id: launcherButton
startRadius: root.outerRadius
endRadius: root.innerRadius
enableShadow: root.shadowsEnabled
}
-
Workspaces {
orientation: root.orientation
bar: QtObject {
@@ -377,46 +334,39 @@ Item {
startRadius: root.innerRadius
endRadius: root.innerRadius
}
-
LayoutSelectorButton {
+ visible: !Config.bar.hiddenIcons.includes("layout")
id: layoutSelectorButton
bar: root
layerEnabled: root.shadowsEnabled
startRadius: root.innerRadius
endRadius: (root.pinButtonVisible) ? root.innerRadius : (root.dockAtStart ? root.innerRadius : root.outerRadius)
}
-
// Pin button (horizontal)
Loader {
active: (Config.bar && Config.bar.showPinButton !== undefined ? Config.bar.showPinButton : true)
visible: active
Layout.alignment: Qt.AlignVCenter
-
sourceComponent: Button {
id: pinButton
implicitWidth: 36
implicitHeight: 36
-
background: StyledRect {
id: pinButtonBg
variant: root.pinned ? "primary" : "bg"
enableShadow: root.shadowsEnabled
-
// PinButton is typically last in group 1 (unless IntegratedDock follows at start)
property real startRadius: root.innerRadius
property real endRadius: root.dockAtStart ? root.innerRadius : root.outerRadius
-
topLeftRadius: startRadius
bottomLeftRadius: startRadius
topRightRadius: endRadius
bottomRightRadius: endRadius
-
Rectangle {
anchors.fill: parent
color: Styling.srItem("overprimary")
opacity: root.pinned ? 0 : (pinButton.pressed ? 0.5 : (pinButton.hovered ? 0.25 : 0))
radius: (parent.radius !== undefined ? parent.radius : 0)
-
Behavior on opacity {
enabled: (Config.animDuration !== undefined ? Config.animDuration : 0) > 0
NumberAnimation {
@@ -425,7 +375,6 @@ Item {
}
}
}
-
contentItem: Text {
text: Icons.pin
font.family: Icons.font
@@ -433,7 +382,6 @@ Item {
color: root.pinned ? pinButtonBg.item : (pinButton.pressed ? Colors.background : (Styling.srItem("overprimary") || Colors.foreground))
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
-
rotation: root.pinned ? 0 : 45
Behavior on rotation {
enabled: (Config.animDuration !== undefined ? Config.animDuration : 0) > 0
@@ -441,7 +389,6 @@ Item {
duration: (Config.animDuration !== undefined ? Config.animDuration : 0) / 2
}
}
-
Behavior on color {
enabled: (Config.animDuration !== undefined ? Config.animDuration : 0) > 0
ColorAnimation {
@@ -449,93 +396,86 @@ Item {
}
}
}
-
onClicked: root.pinned = !root.pinned
-
StyledToolTip {
show: pinButton.hovered
tooltipText: root.pinned ? "Unpin bar" : "Pin bar"
}
}
}
-
Item {
Layout.fillWidth: true
Layout.fillHeight: true
visible: root.orientation === "horizontal" && integratedDockEnabled
-
Bar.IntegratedDock {
bar: root
orientation: root.orientation
anchors.verticalCenter: parent.verticalCenter
enableShadow: root.shadowsEnabled
-
// Connect to left/right groups if at start/end
startRadius: root.dockAtStart ? root.innerRadius : root.outerRadius
endRadius: root.dockAtEnd ? root.innerRadius : root.outerRadius
-
// Calculate target position based on config
property real targetX: {
if (integratedDockPosition === "start")
return 0;
if (integratedDockPosition === "end")
return parent.width - width;
-
// Center logic (reactive using parent.x + margin offset)
// RowLayout has anchors.margins: 4, so offset is 4
return (bar.width - width) / 2 - (parent.x + 4);
}
-
// Clamp the x position so it never leaves the container (preventing overlap)
x: Math.max(0, Math.min(parent.width - width, targetX))
-
width: Math.min(implicitWidth, parent.width)
height: implicitHeight
}
}
-
Item {
Layout.fillWidth: true
visible: !(root.orientation === "horizontal" && integratedDockEnabled)
}
-
PresetsButton {
id: presetsButton
startRadius: root.dockAtEnd ? root.innerRadius : root.outerRadius
endRadius: root.innerRadius
enableShadow: root.shadowsEnabled
}
-
ToolsButton {
+ visible: !Config.bar.hiddenIcons.includes("tools")
id: toolsButton
startRadius: root.innerRadius
endRadius: root.innerRadius
enableShadow: root.shadowsEnabled
}
-
SysTray {
bar: root
enableShadow: root.shadowsEnabled
startRadius: root.innerRadius
endRadius: root.innerRadius
}
-
+ // Running tasks tray
+ TaskTray {
+ bar: root
+ startRadius: root.innerRadius
+ endRadius: root.innerRadius
+ }
ControlsButton {
+ visible: !Config.bar.hiddenIcons.includes("controls")
id: controlsButton
bar: root
layerEnabled: root.shadowsEnabled
startRadius: root.innerRadius
endRadius: root.innerRadius
}
-
Bar.BatteryIndicator {
+ visible: !Config.bar.hiddenIcons.includes("battery")
id: batteryIndicator
bar: root
layerEnabled: root.shadowsEnabled
startRadius: root.innerRadius
endRadius: root.innerRadius
}
-
Clock {
id: clockComponent
bar: root
@@ -543,7 +483,6 @@ Item {
startRadius: root.innerRadius
endRadius: root.innerRadius
}
-
PowerButton {
id: powerButton
startRadius: root.innerRadius
@@ -552,14 +491,12 @@ Item {
}
}
}
-
Loader {
id: verticalLoader
active: root.orientation === "vertical"
anchors.fill: parent
sourceComponent: ColumnLayout {
spacing: 4
-
LauncherButton {
id: launcherButtonVert
Layout.preferredHeight: 36
@@ -568,14 +505,17 @@ Item {
vertical: true
enableShadow: root.shadowsEnabled
}
-
SysTray {
bar: root
enableShadow: root.shadowsEnabled
startRadius: root.innerRadius
endRadius: root.innerRadius
}
-
+ TaskTray {
+ bar: root
+ startRadius: root.innerRadius
+ endRadius: root.innerRadius
+ }
ToolsButton {
id: toolsButtonVert
startRadius: root.innerRadius
@@ -583,7 +523,6 @@ Item {
vertical: true
enableShadow: root.shadowsEnabled
}
-
PresetsButton {
id: presetsButtonVert
startRadius: root.innerRadius
@@ -591,34 +530,26 @@ Item {
vertical: true
enableShadow: root.shadowsEnabled
}
-
// Center Group Container
Item {
Layout.fillHeight: true
Layout.fillWidth: true
-
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
-
// Calculate target position to be absolutely centered in the bar (vertically)
property real targetY: {
if (!parent || !bar)
return 0;
-
// Force re-evaluation when parent moves
var _trigger = parent.y;
-
var parentPos = parent.mapToItem(bar, 0, 0);
return (bar.height - height) / 2 - parentPos.y;
}
-
// Clamp y position
y: Math.max(0, Math.min(parent.height - height, targetY))
-
height: Math.min(parent.height, implicitHeight)
width: parent.width
spacing: 4
-
LayoutSelectorButton {
id: layoutSelectorButtonVert
bar: root
@@ -628,7 +559,6 @@ Item {
endRadius: root.innerRadius
vertical: true
}
-
Workspaces {
id: workspacesVert
orientation: root.orientation
@@ -639,38 +569,31 @@ Item {
startRadius: root.innerRadius
endRadius: root.innerRadius
}
-
// Pin button (vertical)
Loader {
active: (Config.bar && Config.bar.showPinButton !== undefined ? Config.bar.showPinButton : true)
visible: active
Layout.alignment: Qt.AlignHCenter
-
sourceComponent: Button {
id: pinButtonV
implicitWidth: 36
implicitHeight: 36
-
background: StyledRect {
id: pinButtonVBg
variant: root.pinned ? "primary" : "bg"
enableShadow: root.shadowsEnabled
-
property real startRadius: root.innerRadius
// In vertical, dock is always appended to this group if enabled
property real endRadius: root.integratedDockEnabled ? root.innerRadius : root.outerRadius
-
topLeftRadius: startRadius
topRightRadius: startRadius
bottomLeftRadius: endRadius
bottomRightRadius: endRadius
-
Rectangle {
anchors.fill: parent
color: Styling.srItem("overprimary")
opacity: root.pinned ? 0 : (pinButtonV.pressed ? 0.5 : (pinButtonV.hovered ? 0.25 : 0))
radius: (parent.radius !== undefined ? parent.radius : 0)
-
Behavior on opacity {
enabled: (Config.animDuration !== undefined ? Config.animDuration : 0) > 0
NumberAnimation {
@@ -679,7 +602,6 @@ Item {
}
}
}
-
contentItem: Text {
text: Icons.pin
font.family: Icons.font
@@ -687,7 +609,6 @@ Item {
color: root.pinned ? pinButtonVBg.item : (pinButtonV.pressed ? Colors.background : (Styling.srItem("overprimary") || Colors.foreground))
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
-
rotation: root.pinned ? 0 : 45
Behavior on rotation {
enabled: (Config.animDuration !== undefined ? Config.animDuration : 0) > 0
@@ -695,7 +616,6 @@ Item {
duration: (Config.animDuration !== undefined ? Config.animDuration : 0) / 2
}
}
-
Behavior on color {
enabled: (Config.animDuration !== undefined ? Config.animDuration : 0) > 0
ColorAnimation {
@@ -703,9 +623,7 @@ Item {
}
}
}
-
onClicked: root.pinned = !root.pinned
-
StyledToolTip {
show: pinButtonV.hovered
tooltipText: root.pinned ? "Unpin bar" : "Pin bar"
@@ -713,7 +631,6 @@ Item {
}
}
}
-
Bar.IntegratedDock {
bar: root
orientation: root.orientation
@@ -721,12 +638,10 @@ Item {
Layout.fillHeight: true
Layout.fillWidth: true
enableShadow: root.shadowsEnabled
-
startRadius: root.innerRadius
endRadius: root.outerRadius
}
}
-
ControlsButton {
id: controlsButtonVert
bar: root
@@ -734,15 +649,14 @@ Item {
startRadius: root.outerRadius
endRadius: root.innerRadius
}
-
Bar.BatteryIndicator {
+ visible: !Config.bar.hiddenIcons.includes("battery")
id: batteryIndicatorVert
bar: root
layerEnabled: root.shadowsEnabled
startRadius: root.innerRadius
endRadius: root.innerRadius
}
-
Clock {
id: clockComponentVert
bar: root
@@ -750,7 +664,6 @@ Item {
startRadius: root.innerRadius
endRadius: root.innerRadius
}
-
PowerButton {
id: powerButtonVert
Layout.preferredHeight: 36
diff --git a/modules/bar/BatteryIndicator.qml b/modules/bar/BatteryIndicator.qml
old mode 100644
new mode 100755
index a4b3fae1..93ba73cc
--- a/modules/bar/BatteryIndicator.qml
+++ b/modules/bar/BatteryIndicator.qml
@@ -158,7 +158,7 @@ Item {
text: Battery.available ? (Battery.isPluggedIn ? Icons.plug : Icons.lightning) : PowerProfile.getProfileIcon(PowerProfile.currentProfile)
font.family: Icons.font
font.pixelSize: Battery.available ? 14 : 18
- color: root.popupOpen ? buttonBg.item : Colors.overBackground
+ color: root.popupOpen ? buttonBg.item : Styling.srItem("overprimary")
Behavior on color {
enabled: Config.animDuration > 0
diff --git a/modules/bar/BrightnessSlider.qml b/modules/bar/BrightnessSlider.qml
old mode 100644
new mode 100755
index a14d34fa..af0e70be
--- a/modules/bar/BrightnessSlider.qml
+++ b/modules/bar/BrightnessSlider.qml
@@ -133,12 +133,12 @@ Item {
smoothDrag: true
value: 0
resizeParent: false
- wavy: true
+ wavy: false
scroll: root.isExpanded
iconClickable: root.isExpanded
sliderVisible: root.isExpanded || isDragging || root.externalBrightnessChange
- wavyAmplitude: (root.isExpanded || isDragging || root.externalBrightnessChange) ? (1.5 * value) : 0
- wavyFrequency: (root.isExpanded || isDragging || root.externalBrightnessChange) ? (8.0 * value) : 0
+ wavyAmplitude: 0
+ wavyFrequency: 0
iconPos: root.vertical ? "end" : "start"
icon: Icons.sun
iconRotation: root.iconRotation
@@ -153,8 +153,10 @@ Item {
onIconClicked: {}
+ // FIX: Guard enabled to prevent segfault when monitor is destroyed mid-incubation
Connections {
target: currentMonitor
+ enabled: currentMonitor !== null
ignoreUnknownSignals: true
function onBrightnessChanged() {
root.updateSliderFromMonitor(true);
diff --git a/modules/bar/ControlSliderRow.qml b/modules/bar/ControlSliderRow.qml
old mode 100644
new mode 100755
index f157e401..8cbf67f9
--- a/modules/bar/ControlSliderRow.qml
+++ b/modules/bar/ControlSliderRow.qml
@@ -127,14 +127,14 @@ Item {
anchors.leftMargin: 4
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
- height: 4
+ height: 6
radius: Styling.radius(0) / 4
- color: Colors.surfaceBright
+ color: Colors.overSecondaryFixedVariant
}
// Progress fill (wavy or solid)
Loader {
- active: root.wavy
+ active: false
anchors.left: parent.left
anchors.right: dragHandle.left
anchors.rightMargin: 4
@@ -157,10 +157,10 @@ Item {
anchors.right: dragHandle.left
anchors.rightMargin: 4
anchors.verticalCenter: parent.verticalCenter
- height: 4
+ height: 6
radius: Styling.radius(0) / 4
color: root.progressColor
- visible: !root.wavy
+ visible: true
z: 1
}
diff --git a/modules/bar/ControlsButton.qml b/modules/bar/ControlsButton.qml
old mode 100644
new mode 100755
index 687f077b..84270636
--- a/modules/bar/ControlsButton.qml
+++ b/modules/bar/ControlsButton.qml
@@ -125,9 +125,9 @@ Item {
}
sliderValue: Audio.sink?.audio?.volume ?? 0
progressColor: Audio.sink?.audio?.muted ? Colors.outline : Styling.srItem("overprimary")
- wavy: true
- wavyAmplitude: Audio.sink?.audio?.muted ? 0.5 : 1.5 * sliderValue
- wavyFrequency: Audio.sink?.audio?.muted ? 1.0 : 8.0 * sliderValue
+ wavy: false
+ wavyAmplitude: 0
+ wavyFrequency: 0
onValueChanged: newValue => {
if (Audio.sink?.audio) {
@@ -141,8 +141,10 @@ Item {
}
}
+ // FIX: Guard enabled to prevent segfault when PipeWire node is destroyed mid-incubation
Connections {
target: Audio.sink?.audio ?? null
+ enabled: Audio.sink?.audio !== null
ignoreUnknownSignals: true
function onVolumeChanged() {
if (Audio.sink?.audio) {
@@ -162,9 +164,9 @@ Item {
icon: Audio.source?.audio?.muted ? Icons.micSlash : Icons.mic
sliderValue: Audio.source?.audio?.volume ?? 0
progressColor: Audio.source?.audio?.muted ? Colors.outline : Styling.srItem("overprimary")
- wavy: true
- wavyAmplitude: Audio.source?.audio?.muted ? 0.5 : 1.5 * sliderValue
- wavyFrequency: Audio.source?.audio?.muted ? 1.0 : 8.0 * sliderValue
+ wavy: false
+ wavyAmplitude: 0
+ wavyFrequency: 0
onValueChanged: newValue => {
if (Audio.source?.audio) {
@@ -178,8 +180,10 @@ Item {
}
}
+ // FIX: Guard enabled to prevent segfault when PipeWire node is destroyed mid-incubation
Connections {
target: Audio.source?.audio ?? null
+ enabled: Audio.source?.audio !== null
ignoreUnknownSignals: true
function onVolumeChanged() {
if (Audio.source?.audio) {
@@ -201,9 +205,9 @@ Item {
icon: Icons.sun
sliderValue: currentMonitor?.brightness ?? 0.5
progressColor: Styling.srItem("overprimary")
- wavy: true
- wavyAmplitude: 1.5 * sliderValue
- wavyFrequency: 8.0 * sliderValue
+ wavy: false
+ wavyAmplitude: 0
+ wavyFrequency: 0
iconRotation: (sliderValue / 1.0) * 180
iconScale: 0.8 + (sliderValue / 1.0) * 0.2
@@ -222,8 +226,10 @@ Item {
onIconClicked: {}
+ // FIX: Guard enabled to prevent segfault when monitor is destroyed mid-incubation
Connections {
target: brightnessRow.currentMonitor ?? null
+ enabled: brightnessRow.currentMonitor !== null
ignoreUnknownSignals: true
function onBrightnessChanged() {
if (brightnessRow.currentMonitor) {
diff --git a/modules/bar/IntegratedDock.qml b/modules/bar/IntegratedDock.qml
old mode 100644
new mode 100755
diff --git a/modules/bar/IntegratedDockAppButton.qml b/modules/bar/IntegratedDockAppButton.qml
old mode 100644
new mode 100755
index f75a9948..0387f080
--- a/modules/bar/IntegratedDockAppButton.qml
+++ b/modules/bar/IntegratedDockAppButton.qml
@@ -175,7 +175,7 @@ Button {
if (appToplevel.toplevelCount === 0) {
// Launch the app
if (desktopEntry) {
- desktopEntry.execute();
+ AppSearch.launchApp(desktopEntry);
}
return;
}
@@ -197,7 +197,7 @@ Button {
if (mouse.button === Qt.MiddleButton) {
// Launch new instance
if (root.desktopEntry) {
- root.desktopEntry.execute();
+ AppSearch.launchApp(root.desktopEntry);
}
} else if (mouse.button === Qt.RightButton) {
// Toggle pin
diff --git a/modules/bar/LayoutSelector.qml b/modules/bar/LayoutSelector.qml
old mode 100644
new mode 100755
diff --git a/modules/bar/LayoutSelectorButton.qml b/modules/bar/LayoutSelectorButton.qml
old mode 100644
new mode 100755
diff --git a/modules/bar/MicSlider.qml b/modules/bar/MicSlider.qml
old mode 100644
new mode 100755
index 092ae532..aef6c939
--- a/modules/bar/MicSlider.qml
+++ b/modules/bar/MicSlider.qml
@@ -105,12 +105,12 @@ Item {
smoothDrag: true
value: 0
resizeParent: false
- wavy: true
+ wavy: false
scroll: root.isExpanded
iconClickable: root.isExpanded
sliderVisible: root.isExpanded || micSlider.isDragging || root.externalVolumeChange
- wavyAmplitude: (root.isExpanded || micSlider.isDragging || root.externalVolumeChange) ? (Audio.source?.audio?.muted ? 0.5 : 1.5 * value) : 0
- wavyFrequency: (root.isExpanded || micSlider.isDragging || root.externalVolumeChange) ? (Audio.source?.audio?.muted ? 1.0 : 8.0 * value) : 0
+ wavyAmplitude: 0
+ wavyFrequency: 0
iconPos: root.vertical ? "end" : "start"
icon: Audio.source?.audio?.muted ? Icons.micSlash : Icons.mic
progressColor: Audio.source?.audio?.muted ? Colors.outline : Styling.srItem("overprimary")
@@ -127,8 +127,10 @@ Item {
}
}
+ // FIX: Guard enabled to prevent segfault when PipeWire node is destroyed mid-incubation
Connections {
target: Audio.source?.audio ?? null
+ enabled: Audio.source?.audio !== null
ignoreUnknownSignals: true
function onVolumeChanged() {
if (Audio.source?.audio) {
diff --git a/modules/bar/PowerProfileSelector.qml b/modules/bar/PowerProfileSelector.qml
old mode 100644
new mode 100755
diff --git a/modules/bar/ToolsButton.qml b/modules/bar/ToolsButton.qml
old mode 100644
new mode 100755
diff --git a/modules/bar/VolumeSlider.qml b/modules/bar/VolumeSlider.qml
old mode 100644
new mode 100755
index e7e52a82..9de44fa9
--- a/modules/bar/VolumeSlider.qml
+++ b/modules/bar/VolumeSlider.qml
@@ -107,12 +107,12 @@ Item {
smoothDrag: true
value: 0
resizeParent: false
- wavy: true
+ wavy: false
scroll: root.isExpanded
iconClickable: root.isExpanded
sliderVisible: root.isExpanded || volumeSlider.isDragging || root.externalVolumeChange
- wavyAmplitude: (root.isExpanded || volumeSlider.isDragging || root.externalVolumeChange) ? (Audio.sink?.audio?.muted ? 0.5 : 1.5 * value) : 0
- wavyFrequency: (root.isExpanded || volumeSlider.isDragging || root.externalVolumeChange) ? (Audio.sink?.audio?.muted ? 1.0 : 8.0 * value) : 0
+ wavyAmplitude: 0
+ wavyFrequency: 0
iconPos: root.vertical ? "end" : "start"
icon: {
if (Audio.sink?.audio?.muted)
@@ -140,8 +140,10 @@ Item {
}
}
+ // FIX: Guard enabled to prevent segfault when PipeWire node is destroyed mid-incubation
Connections {
target: Audio.sink?.audio ?? null
+ enabled: Audio.sink?.audio !== null
ignoreUnknownSignals: true
function onVolumeChanged() {
if (Audio.sink?.audio) {
diff --git a/modules/bar/clock/Clock.qml b/modules/bar/clock/Clock.qml
old mode 100644
new mode 100755
index 1cad557d..4415e86f
--- a/modules/bar/clock/Clock.qml
+++ b/modules/bar/clock/Clock.qml
@@ -637,8 +637,12 @@ Item {
scheduleNextDayUpdate();
}
+ // β‘ Adaptive timer: 30s idle vs 1s when popup is open.
+ // The display format is hh:mm (no seconds), so 30s is sufficient
+ // to catch minute transitions. When the user is looking at the
+ // clock popup, we poll every second for responsive updates.
Timer {
- interval: 1000
+ interval: root.popupOpen ? 1000 : 30000
running: !SuspendManager.isSuspending
repeat: true
onTriggered: {
diff --git a/modules/bar/clock/ClockIndicator.qml b/modules/bar/clock/ClockIndicator.qml
old mode 100644
new mode 100755
diff --git a/modules/bar/clock/Pomodoro.qml b/modules/bar/clock/Pomodoro.qml
old mode 100644
new mode 100755
diff --git a/modules/bar/clock/PomodoroSound.qml b/modules/bar/clock/PomodoroSound.qml
old mode 100644
new mode 100755
diff --git a/modules/bar/clock/Weather.qml b/modules/bar/clock/Weather.qml
old mode 100644
new mode 100755
diff --git a/modules/bar/qmldir b/modules/bar/qmldir
new file mode 100644
index 00000000..5b48ce73
--- /dev/null
+++ b/modules/bar/qmldir
@@ -0,0 +1,17 @@
+module qs.modules.bar
+Bar 1.0 Bar.qml
+BarBg 1.0 BarBg.qml
+BarBgShadow 1.0 BarBgShadow.qml
+BarContent 1.0 BarContent.qml
+BatteryIndicator 1.0 BatteryIndicator.qml
+BrightnessSlider 1.0 BrightnessSlider.qml
+ControlsButton 1.0 ControlsButton.qml
+ControlSliderRow 1.0 ControlSliderRow.qml
+IntegratedDock 1.0 IntegratedDock.qml
+IntegratedDockAppButton 1.0 IntegratedDockAppButton.qml
+LayoutSelector 1.0 LayoutSelector.qml
+LayoutSelectorButton 1.0 LayoutSelectorButton.qml
+MicSlider 1.0 MicSlider.qml
+PowerProfileSelector 1.0 PowerProfileSelector.qml
+ToolsButton 1.0 ToolsButton.qml
+VolumeSlider 1.0 VolumeSlider.qml
diff --git a/modules/bar/systray/SysTray.qml b/modules/bar/systray/SysTray.qml
old mode 100644
new mode 100755
index 83464b58..c3446248
--- a/modules/bar/systray/SysTray.qml
+++ b/modules/bar/systray/SysTray.qml
@@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Layouts
import Quickshell.Services.SystemTray
import qs.modules.theme
+import qs.config
import qs.modules.components
StyledRect {
@@ -24,9 +25,30 @@ import qs.modules.components
// OrientaciΓ³n derivada de la barra
property bool vertical: bar.orientation === "vertical"
+ property bool isExpanded: true
+
+ // Filtered tray items (UntypedObjectModel doesn't support .filter())
+ readonly property var filteredItems: {
+ var result = [];
+ var items = SystemTray.items;
+ var hidden = Config.bar.hiddenIcons;
+ for (var i = 0; i < items.length; i++) {
+ var item = items[i];
+ var title = (item.title || item.tooltipTitle || "").toLowerCase();
+ var hide = false;
+ for (var j = 0; j < hidden.length; j++) {
+ if (title.includes(hidden[j].toLowerCase())) {
+ hide = true;
+ break;
+ }
+ }
+ if (!hide) result.push(item);
+ }
+ return result;
+ }
// Hide completely when empty - check both orientations
- readonly property bool hasItems: rowRepeater.count > 0 || columnRepeater.count > 0
+ readonly property bool hasItems: SystemTray.items.length > 0
// Ajustes de tamaΓ±o dinΓ‘micos segΓΊn orientaciΓ³n
height: vertical ? implicitHeight : parent.height
@@ -41,9 +63,27 @@ import qs.modules.components
anchors.margins: 8
spacing: 8
+ MouseArea {
+ id: toggleBtnRow
+ Layout.alignment: Qt.AlignCenter
+ implicitWidth: 20
+ implicitHeight: 20
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: root.isExpanded = !root.isExpanded
+
+ Text {
+ anchors.centerIn: parent
+ text: root.isExpanded ? Icons.caretLeft : Icons.caretRight
+ font.family: Icons.font
+ font.pixelSize: Styling.fontSize(-1)
+ color: toggleBtnRow.containsMouse ? Colors.primary : Colors.onSurfaceVariant
+ }
+ }
+
Repeater {
id: rowRepeater
- model: SystemTray.items
+ model: root.isExpanded ? root.filteredItems : []
SysTrayItem {
required property SystemTrayItem modelData
@@ -60,9 +100,27 @@ import qs.modules.components
anchors.margins: 8
spacing: 8
+ MouseArea {
+ id: toggleBtnCol
+ Layout.alignment: Qt.AlignCenter
+ implicitWidth: 20
+ implicitHeight: 20
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: root.isExpanded = !root.isExpanded
+
+ Text {
+ anchors.centerIn: parent
+ text: root.isExpanded ? Icons.caretUp : Icons.caretDown
+ font.family: Icons.font
+ font.pixelSize: Styling.fontSize(-1)
+ color: toggleBtnCol.containsMouse ? Colors.primary : Colors.onSurfaceVariant
+ }
+ }
+
Repeater {
id: columnRepeater
- model: SystemTray.items
+ model: root.isExpanded ? root.filteredItems : []
SysTrayItem {
required property SystemTrayItem modelData
diff --git a/modules/bar/systray/SysTrayItem.qml b/modules/bar/systray/SysTrayItem.qml
old mode 100644
new mode 100755
index 0b725b1a..e80b86fb
--- a/modules/bar/systray/SysTrayItem.qml
+++ b/modules/bar/systray/SysTrayItem.qml
@@ -5,7 +5,6 @@ import Quickshell
import Quickshell.Services.SystemTray
import Quickshell.Widgets
import qs.modules.theme
-import qs.modules.services
import qs.modules.components
import qs.config
@@ -23,132 +22,54 @@ MouseArea {
implicitWidth: trayItemSize
implicitHeight: trayItemSize
+ // Popup de prueba para verificar clicks
+ Popup {
+ id: testPopup
+ x: popupX; y: popupY
+ width: 200; height: 150
+
+ background: Rectangle {
+ color: Colors.background
+ border.color: Colors.surfaceBright
+ border.width: 2
+ radius: 8
+ }
+
+ Column {
+ anchors.centerIn: parent
+ spacing: 10
+ Text { text: "RIGHT CLICK WORKS!"; color: Colors.overPrimary; font.bold: true }
+ Button {
+ text: "Cerrar"
+ onClicked: testPopup.close()
+ }
+ }
+ }
+
+ property real popupX: 0
+ property real popupY: 0
+
onClicked: event => {
switch (event.button) {
case Qt.LeftButton:
item.activate();
break;
case Qt.RightButton:
- if (item.hasMenu) {
- systrayPopup.toggle();
- }
+ popupX = event.x;
+ popupY = event.y;
+ testPopup.open();
break;
}
event.accepted = true;
}
- BarPopup {
- id: systrayPopup
- anchorItem: root
- bar: root.bar
-
- // Use a reasonable width for the menu
- contentWidth: 220
- // Height adapts to content, with a max limit if needed.
- // Must include vertical padding (8 top + 8 bottom = 16)
- contentHeight: Math.min(itemsColumn.implicitHeight + 16, 400)
-
- popupPadding: 8
- // 8px standard margin + 8px SysTray container padding to ensure correct offset from the main bar
- visualMargin: 16
-
- // Using QsMenuOpener to access menu items
- QsMenuOpener {
- id: menuOpener
- menu: root.item.menu
- }
-
- ScrollView {
- anchors.fill: parent
- contentWidth: availableWidth
- clip: true
-
- ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
-
- ColumnLayout {
- id: itemsColumn
- width: parent.width
- spacing: 2
-
- Repeater {
- model: menuOpener.children ? menuOpener.children.values : []
-
- delegate: ColumnLayout {
- required property var modelData
-
- Layout.fillWidth: true
- spacing: 2
-
- property bool submenuExpanded: false
-
- SystrayMenuItem {
- Layout.fillWidth: true
-
- textStr: modelData.text || ""
- iconSource: modelData.icon || ""
- isImageIcon: iconSource.indexOf("/") !== -1 || iconSource.indexOf(".") !== -1
- isSeparator: modelData.isSeparator || false
- hasSubmenu: modelData.hasChildren || false
- expanded: parent.submenuExpanded
- buttonType: modelData.buttonType || 0
- checkState: modelData.checkState || 0
-
- onClicked: {
- if (modelData.hasChildren) {
- parent.submenuExpanded = !parent.submenuExpanded;
- } else {
- if (modelData.triggered) {
- modelData.triggered();
- } else if (modelData.activate) {
- modelData.activate();
- }
- systrayPopup.close();
- }
- }
- }
-
- // Submenu children β uses its own QsMenuOpener to trigger lazy loading
- ColumnLayout {
- visible: submenuExpanded && modelData.hasChildren
- Layout.fillWidth: true
- spacing: 2
-
- QsMenuOpener {
- id: subMenuOpener
- menu: modelData.hasChildren ? modelData : null
- }
-
- Repeater {
- model: subMenuOpener.children ? subMenuOpener.children.values : []
-
- delegate: SystrayMenuItem {
- required property var modelData
-
- Layout.fillWidth: true
- depth: 1
-
- textStr: modelData.text || ""
- iconSource: modelData.icon || ""
- isImageIcon: iconSource.indexOf("/") !== -1 || iconSource.indexOf(".") !== -1
- isSeparator: modelData.isSeparator || false
- buttonType: modelData.buttonType || 0
- checkState: modelData.checkState || 0
-
- onClicked: {
- if (modelData.triggered) {
- modelData.triggered();
- } else if (modelData.activate) {
- modelData.activate();
- }
- systrayPopup.close();
- }
- }
- }
- }
- }
- }
- }
- }
+ // DEBUG: borde rojo para confirmar que este cΓ³digo estΓ‘ cargado
+ Rectangle {
+ anchors.fill: parent
+ color: "transparent"
+ border.color: "red"
+ border.width: 2
+ radius: 4
}
IconImage {
diff --git a/modules/bar/systray/SystrayMenuItem.qml b/modules/bar/systray/SystrayMenuItem.qml
old mode 100644
new mode 100755
index 6bc474d7..d657af8f
--- a/modules/bar/systray/SystrayMenuItem.qml
+++ b/modules/bar/systray/SystrayMenuItem.qml
@@ -1,22 +1,20 @@
import QtQuick
-import QtQuick.Controls
import QtQuick.Layouts
import qs.modules.theme
import qs.config
-Button {
+Item {
id: root
+ signal clicked()
+
property string textStr: ""
- // Clean text logic from ContextMenu.qml
readonly property string cleanText: {
let t = textStr;
if (!t) return "";
t = String(t);
- if (t.startsWith(":/// ")) {
- t = t.substring(5);
- }
+ var m = t.match(/^:\/\/+\s*/); if (m) t = t.substring(m[0].length);
return t.trim();
}
@@ -26,25 +24,32 @@ Button {
property bool hasSubmenu: false
property bool expanded: false
property int depth: 0
- // 0 = None, 1 = CheckBox, 2 = RadioButton
property int buttonType: 0
- // Qt.Unchecked = 0, Qt.PartiallyChecked = 1, Qt.Checked = 2
property int checkState: 0
implicitWidth: 200
implicitHeight: isSeparator ? 10 : 36
- enabled: !isSeparator
-
- // Reset default styling
- padding: 0
- background: Rectangle {
- color: {
- if (root.isSeparator) return "transparent"
- return root.hovered ? Styling.srItem("overprimary") : "transparent"
+
+ // Capturar clics directamente sin Button
+ MouseArea {
+ id: clickArea
+ anchors.fill: parent
+ enabled: !root.isSeparator
+ cursorShape: Qt.PointingHandCursor
+ hoverEnabled: true
+
+ onClicked: {
+ if (root.isSeparator) return;
+ root.clicked();
}
+ }
+
+ // Fondo
+ Rectangle {
+ anchors.fill: parent
+ color: isSeparator ? "transparent" : (clickArea.containsMouse ? Styling.srItem("overprimary") : "transparent")
radius: Styling.radius(0)
- // Separator line
Rectangle {
visible: root.isSeparator
height: 1
@@ -54,11 +59,10 @@ Button {
}
}
- contentItem: RowLayout {
+ // Contenido
+ RowLayout {
spacing: 8
visible: !root.isSeparator
-
- // Add margins for content
anchors.fill: parent
anchors.leftMargin: 8 + root.depth * 12
anchors.rightMargin: 8
@@ -69,74 +73,51 @@ Button {
Layout.preferredWidth: 16
Layout.preferredHeight: 16
- // Checkbox
Rectangle {
visible: root.buttonType === 1
- anchors.centerIn: parent
- width: 14
- height: 14
- radius: 3
+ anchors.centerIn: parent; width: 14; height: 14; radius: 3
color: root.checkState === Qt.Unchecked ? "transparent" : Colors.primary
border.color: root.checkState === Qt.Unchecked ? Colors.outline : Colors.primary
border.width: 1.5
-
Text {
anchors.centerIn: parent
visible: root.checkState !== Qt.Unchecked
text: root.checkState === Qt.PartiallyChecked ? "\u2212" : "\u2713"
- color: Colors.overPrimary
- font.pixelSize: 10
- font.bold: true
+ color: Colors.overPrimary; font.pixelSize: 10; font.bold: true
}
}
- // RadioButton
Rectangle {
visible: root.buttonType === 2
- anchors.centerIn: parent
- width: 14
- height: 14
- radius: 7
+ anchors.centerIn: parent; width: 14; height: 14; radius: 7
color: "transparent"
border.color: root.checkState === Qt.Checked ? Colors.primary : Colors.outline
border.width: 1.5
-
Rectangle {
- anchors.centerIn: parent
- visible: root.checkState === Qt.Checked
- width: 7
- height: 7
- radius: 4
- color: Colors.primary
+ anchors.centerIn: parent; visible: root.checkState === Qt.Checked
+ width: 7; height: 7; radius: 4; color: Colors.primary
}
}
}
// Icon
Loader {
- Layout.preferredWidth: 16
- Layout.preferredHeight: 16
+ Layout.preferredWidth: 16; Layout.preferredHeight: 16
visible: root.iconSource !== "" && root.buttonType === 0
sourceComponent: root.isImageIcon ? imageIcon : fontIcon
-
Component {
id: fontIcon
Text {
text: root.iconSource
- font.family: Icons.font
- font.pixelSize: 14
- color: root.hovered ? Colors.overPrimary : Colors.overBackground
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
+ font.family: Icons.font; font.pixelSize: 14
+ color: clickArea.containsMouse ? Colors.overPrimary : Colors.overBackground
}
}
-
Component {
id: imageIcon
Image {
source: root.iconSource
- fillMode: Image.PreserveAspectFit
- mipmap: true
+ fillMode: Image.PreserveAspectFit; mipmap: true
}
}
}
@@ -145,20 +126,17 @@ Button {
Text {
Layout.fillWidth: true
text: root.cleanText
- color: root.hovered ? Colors.overPrimary : Colors.overBackground
- font.family: Config.theme.font
- font.pixelSize: Styling.fontSize(0)
- elide: Text.ElideRight
- verticalAlignment: Text.AlignVCenter
+ color: clickArea.containsMouse ? Colors.overPrimary : Colors.overBackground
+ font.family: Config.theme.font; font.pixelSize: Styling.fontSize(0)
+ elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter
}
// Submenu chevron
Text {
visible: root.hasSubmenu
text: root.expanded ? "\u25BE" : "\u25B8"
- color: root.hovered ? Colors.overPrimary : Colors.overBackground
- font.pixelSize: Styling.fontSize(0)
- verticalAlignment: Text.AlignVCenter
+ color: clickArea.containsMouse ? Colors.overPrimary : Colors.overBackground
+ font.pixelSize: Styling.fontSize(0); verticalAlignment: Text.AlignVCenter
}
}
}
diff --git a/modules/bar/tasktray/TaskTray.qml b/modules/bar/tasktray/TaskTray.qml
new file mode 100644
index 00000000..53ffb633
--- /dev/null
+++ b/modules/bar/tasktray/TaskTray.qml
@@ -0,0 +1,404 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Services.SystemTray
+import Quickshell.Widgets
+import qs.modules.theme
+import qs.modules.services
+import qs.modules.components
+import qs.config
+
+Item {
+ id: root
+
+ required property var bar
+ property bool vertical: bar.orientation === "vertical"
+ property bool isHovered: false
+ property bool layerEnabled: true
+ property real radius: 0
+ property real startRadius: radius
+ property real endRadius: radius
+ property bool expanded: false
+ property var _ctxItem: null
+ property var _hid: []
+
+ function _key(i, it) {
+ return i + "_" + (it.title || it.tooltipTitle || it.id || "t" + i);
+ }
+
+ property int _vc: 0
+ function _recalc() {
+ try {
+ if (!SystemTray || !SystemTray.items) { _vc = 0; return; }
+ var len = SystemTray.items && SystemTray.items.length;
+ if (!len) { _vc = 0; return; }
+ if (_hid.length === 0) { _vc = len; return; }
+ var n = 0;
+ for (var i = 0; i < len; i++) {
+ var it = SystemTray.items[i];
+ if (it && _hid.indexOf(root._key(i, it)) < 0) n++;
+ }
+ _vc = n;
+ } catch(e) {
+ console.warn('_recalc:', e);
+ _vc = 0;
+ }
+ }
+ function _toggle(k) {
+ var a = _hid.slice();
+ var i = a.indexOf(k);
+ if (i >= 0) a.splice(i, 1); else a.push(k);
+ _hid = a;
+ _recalc();
+ }
+
+ property int _dockN: dockRep ? dockRep.count : 0
+ property int _setN: setRep ? setRep.count : 0
+
+ Connections { target: dockRep; function onCountChanged() { _dockN = dockRep.count; _setN = setRep.count; _recalc(); } }
+ Connections { target: setRep; function onCountChanged() { _setN = setRep.count; _recalc(); } }
+ Component.onCompleted: _recalc()
+
+ readonly property int _dw: expanded && dockRep.count > 0 ? Math.max(40, Math.min(dockRep.count, 10) * 40 + 10) : 0
+
+ Layout.preferredWidth: root.vertical ? 36 : (36 + (expanded ? 2 + _dw : 0))
+ Layout.preferredHeight: root.vertical ? (36 + (expanded ? 2 + _dw : 0)) : 36
+ Layout.fillWidth: vertical
+ Layout.fillHeight: !vertical
+ clip: true
+
+ Behavior on Layout.preferredHeight {
+ enabled: root.vertical && Config.animDuration > 0
+ NumberAnimation { duration: Config.animDuration / 2; easing.type: Easing.OutCubic }
+ }
+
+ Behavior on Layout.preferredWidth {
+ enabled: !root.vertical && Config.animDuration > 0
+ NumberAnimation { duration: Config.animDuration / 2; easing.type: Easing.OutCubic }
+ }
+
+ HoverHandler { onHoveredChanged: root.isHovered = hovered }
+
+ StyledRect {
+ anchors.fill: parent
+ variant: "bg"
+ enableShadow: root.layerEnabled && Config.showBackground
+ topLeftRadius: root.vertical ? root.startRadius : root.startRadius
+ topRightRadius: root.endRadius
+ bottomLeftRadius: root.vertical ? root.endRadius : root.startRadius
+ bottomRightRadius: root.endRadius
+
+ Rectangle {
+ anchors.fill: parent
+ color: Styling.srItem("overprimary")
+ opacity: root.isHovered && !root.expanded ? 0.25 : 0
+ radius: parent.radius ?? 0
+ Behavior on opacity {
+ enabled: Config.animDuration > 0
+ NumberAnimation { duration: Config.animDuration / 2 }
+ }
+ }
+ }
+
+ Text {
+ x: 9; y: 9
+ text: Icons.dotsThree; font.family: Icons.font; font.pixelSize: 18
+ color: Styling.srItem("overprimary")
+ rotation: root.expanded ? 90 : 0
+ Behavior on rotation {
+ enabled: Config.animDuration > 0
+ NumberAnimation { duration: Config.animDuration / 2; easing.type: Easing.OutCubic }
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent; z: 20; cursorShape: Qt.PointingHandCursor
+ onClicked: event => { root.expanded = !root.expanded; }
+ }
+
+ StyledToolTip {
+ visible: root.isHovered && !root.expanded
+ tooltipText: _vc > 0 ? _vc + " visible" : "No icons"
+ }
+
+ RowLayout {
+ visible: !root.vertical
+ opacity: expanded ? 1.0 : 0.0
+ Behavior on opacity { enabled: Config.animDuration > 0; NumberAnimation { duration: Config.animDuration / 2 } }
+ anchors.left: root.vertical ? undefined : parent.left
+ anchors.leftMargin: root.vertical ? 0 : 40
+ anchors.verticalCenter: root.vertical ? undefined : parent.verticalCenter
+ anchors.top: root.vertical ? parent.top : undefined
+ anchors.topMargin: root.vertical ? 40 : 0
+ anchors.horizontalCenter: root.vertical ? parent.horizontalCenter : undefined
+ spacing: 4
+ Repeater {
+ id: dockRep
+ model: SystemTray && SystemTray.items ? SystemTray.items : []
+ delegate: Item {
+ required property SystemTrayItem modelData
+ required property int index
+ width: 36; height: 36
+ readonly property string _k: root._key(index, modelData)
+ visible: root._hid.indexOf(_k) < 0
+ property bool hov: false
+ HoverHandler { onHoveredChanged: hov = hovered }
+ StyledRect {
+ anchors.fill: parent; anchors.margins: 1; radius: 4
+ variant: "bg"; opacity: hov ? 0.5 : 0.0
+ Behavior on opacity { NumberAnimation { duration: 80 } }
+ }
+ IconImage {
+ anchors.centerIn: parent; width: 18; height: 18
+ source: modelData.icon; smooth: true
+ }
+ MouseArea {
+ anchors.fill: parent; cursorShape: Qt.PointingHandCursor
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ onClicked: event => {
+ if (event.button === Qt.LeftButton) modelData.activate();
+ else if (event.button === Qt.RightButton && modelData.hasMenu) {
+ root._ctxItem = modelData; ctxPopup.open();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ColumnLayout {
+ opacity: expanded ? 1.0 : 0.0
+ Behavior on opacity { enabled: Config.animDuration > 0; NumberAnimation { duration: Config.animDuration / 2 } }
+ anchors.top: parent.top; anchors.topMargin: 40
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: 4
+ visible: root.vertical && dockRep.count > 0
+
+ Repeater {
+ id: dockRepVert
+ model: dockRep.model
+ delegate: Item {
+ required property SystemTrayItem modelData
+ required property int index
+ width: 36; height: 36
+ visible: true
+ StyledRect {
+ anchors.fill: parent; anchors.margins: 1; radius: 4
+ variant: "bg"
+ opacity: 0
+ }
+ IconImage {
+ anchors.centerIn: parent; width: 18; height: 18
+ source: modelData.icon; smooth: true
+ }
+ MouseArea {
+ anchors.fill: parent; cursorShape: Qt.PointingHandCursor
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ onClicked: event => {
+ if (event.button === Qt.LeftButton) modelData.activate();
+ else if (event.button === Qt.RightButton) {
+ root._ctxItem = modelData; ctxPopup.open();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // ββ Context menu ββ
+ BarPopup {
+ id: ctxPopup; anchorItem: root; bar: root.bar
+ contentWidth: 240
+ contentHeight: Math.min(ctxCol.implicitHeight + 16, 400)
+ popupPadding: 6; visualMargin: 16
+
+ QsMenuOpener { id: mo; menu: root._ctxItem ? root._ctxItem.menu : null }
+
+ ScrollView {
+ anchors.fill: parent; clip: true
+ ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+
+ ColumnLayout {
+ id: ctxCol; width: parent.width; spacing: 2
+
+ Repeater {
+ model: mo.children
+
+ delegate: Item {
+ required property QsMenuHandle modelData
+ Layout.fillWidth: true
+ Layout.preferredHeight: 32
+
+ // Separador
+ Rectangle {
+ anchors.left: parent.left; anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ height: 1; color: Colors.surfaceBright
+ visible: modelData.isSeparator
+ anchors.leftMargin: 8; anchors.rightMargin: 8
+ }
+
+ readonly property bool _isCheck: modelData.buttonType === 1
+ readonly property bool _isRadio: modelData.buttonType === 2
+ property bool _hover: false
+
+ // Check/Radio
+ Item {
+ x: 8; y: 8; width: 16; height: 16
+ visible: !modelData.isSeparator && modelData.buttonType !== 0
+ Rectangle {
+ anchors.centerIn: parent; width: 14; height: 14
+ radius: _isRadio ? 7 : 3
+ color: modelData.checkState !== 0 ? Colors.primary : "transparent"
+ border.color: modelData.checkState !== 0 ? Colors.primary : Colors.outline
+ border.width: 1.5
+ Text {
+ anchors.centerIn: parent
+ visible: modelData.checkState !== 0 && !_isRadio
+ text: modelData.checkState === 1 ? "\u2212" : "\u2713"
+ color: Colors.overPrimary; font.pixelSize: 10; font.bold: true
+ }
+ Rectangle {
+ anchors.centerIn: parent
+ visible: modelData.checkState !== 0 && _isRadio
+ width: 7; height: 7; radius: 4; color: Colors.primary
+ }
+ }
+ }
+
+ // Icono
+ Text {
+ x: modelData.buttonType !== 0 ? 30 : 10
+ y: 8; width: 16; height: 16
+ visible: !modelData.isSeparator && modelData.icon !== "" && modelData.buttonType === 0
+ text: modelData.icon; font.family: Icons.font; font.pixelSize: 14
+ color: _hover ? Colors.overPrimary : Colors.overBackground
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ // Texto
+ Text {
+ readonly property real _ix: modelData.buttonType !== 0 ? 30 : (modelData.icon !== "" && modelData.buttonType === 0 ? 30 : 10)
+ x: _ix; y: 6; height: 20
+ width: parent.width - _ix - 22
+ visible: !modelData.isSeparator
+ text: {
+ var t = modelData.text || "";
+ var m = t.match(/^:\/\/+\s*/); if (m) t = t.substring(m[0].length);
+ return t.trim();
+ }
+ color: _hover ? Colors.overPrimary : Colors.overBackground
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(0)
+ elide: Text.ElideRight
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ // Chevron submenu
+ Text {
+ x: parent.width - 20; y: 8; width: 12; height: 16
+ visible: !modelData.isSeparator && modelData.hasChildren
+ text: "\u25B8"; font.pixelSize: Styling.fontSize(0)
+ color: _hover ? Colors.overPrimary : Colors.overBackground
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ // Fondo hover
+ Rectangle {
+ anchors.fill: parent; anchors.margins: 2
+ radius: Styling.radius(0)
+ visible: _hover && !modelData.isSeparator
+ color: Styling.srItem("overprimary")
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ hoverEnabled: true; cursorShape: Qt.PointingHandCursor
+ enabled: !modelData.isSeparator
+ onEntered: _hover = true
+ onExited: _hover = false
+ onClicked: {
+ if (!modelData.isSeparator) {
+ modelData.triggered();
+ ctxPopup.close();
+ Qt.callLater(() => { root._ctxItem = null; });
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // ββ Settings popup ββ
+ BarPopup {
+ id: setPopup; anchorItem: root; bar: root.bar
+ contentWidth: setCol.implicitWidth + 16
+ contentHeight: Math.min(setCol.implicitHeight + 16, 400)
+ ColumnLayout {
+ id: setCol
+ anchors.fill: parent; anchors.margins: 6; spacing: 4
+ Text {
+ text: "Tray (" + _vc + "/" + _setN + ")"
+ font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-1); font.bold: true
+ color: Colors.overBackground
+ Layout.fillWidth: true; Layout.bottomMargin: 4; leftPadding: 4
+ }
+ Repeater {
+ id: setRep
+ model: SystemTray && SystemTray.items ? SystemTray.items : []
+ delegate: Item {
+ required property SystemTrayItem modelData
+ required property int index
+ Layout.fillWidth: true; Layout.preferredHeight: 34
+ readonly property string _k: root._key(index, modelData)
+
+ MouseArea {
+ id: rowMA
+ anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor
+ acceptedButtons: Qt.LeftButton | Qt.RightButton
+ onClicked: event => {
+ if (event.button === Qt.LeftButton) { modelData.activate(); setPopup.close(); }
+ else if (event.button === Qt.RightButton && modelData.hasMenu) {
+ root._ctxItem = modelData; ctxPopup.open();
+ }
+ }
+ }
+
+ StyledRect {
+ anchors.fill: parent; radius: 4
+ variant: rowMA.containsMouse ? "focus" : "bg"
+ opacity: rowMA.containsMouse ? 1.0 : 0.7
+ }
+
+ RowLayout {
+ anchors.fill: parent; anchors.leftMargin: 6; anchors.rightMargin: 6; spacing: 8
+ Text {
+ text: root._hid.indexOf(_k) >= 0 ? Icons.circleNotch : Icons.circle
+ font.family: Icons.font; font.pixelSize: 16
+ color: root._hid.indexOf(_k) >= 0 ? Colors.outline : Styling.srItem("primary")
+ Layout.alignment: Qt.AlignVCenter
+ MouseArea {
+ anchors.fill: parent; anchors.margins: -4
+ cursorShape: Qt.PointingHandCursor
+ onClicked: event => { root._toggle(_k); event.accepted = true; }
+ }
+ }
+ IconImage { width: 20; height: 20; source: modelData.icon; smooth: true; Layout.alignment: Qt.AlignVCenter }
+ Text {
+ text: modelData.tooltipTitle || modelData.title || "App #" + (index + 1)
+ font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2)
+ color: Colors.overBackground; elide: Text.ElideRight
+ Layout.fillWidth: true; Layout.alignment: Qt.AlignVCenter
+ }
+ }
+ }
+ }
+ Item { Layout.fillHeight: true }
+ }
+ }
+}
diff --git a/modules/bar/tasktray/qmldir b/modules/bar/tasktray/qmldir
new file mode 100644
index 00000000..4210fb47
--- /dev/null
+++ b/modules/bar/tasktray/qmldir
@@ -0,0 +1,2 @@
+module qs.modules.bar.tasktray
+TaskTray 1.0 TaskTray.qml
diff --git a/modules/bar/workspaces/CompositorData.qml b/modules/bar/workspaces/CompositorData.qml
old mode 100644
new mode 100755
diff --git a/modules/bar/workspaces/Workspaces.qml b/modules/bar/workspaces/Workspaces.qml
old mode 100644
new mode 100755
index 48f68cdd..1c5f3bdf
--- a/modules/bar/workspaces/Workspaces.qml
+++ b/modules/bar/workspaces/Workspaces.qml
@@ -152,6 +152,11 @@ Item {
readonly property bool effectiveContainBar: Config.bar.containBar && ((Config.bar.frameEnabled !== undefined ? Config.bar.frameEnabled : false))
+ // Process for workspace switching
+ property Process wsProcess: Process {
+ running: false
+ }
+
StyledRect {
id: bgRect
variant: "bg"
@@ -166,17 +171,20 @@ Item {
WheelHandler {
onWheel: event => {
- if (event.angleDelta.y < 0)
- AxctlService.dispatch(`workspace r+1`);
- else if (event.angleDelta.y > 0)
- AxctlService.dispatch(`workspace r-1`);
+ if (event.angleDelta.y < 0) {
+ wsProcess.command = ["hyprctl", "dispatch", "workspace", "+1"];
+ wsProcess.running = true;
+ } else if (event.angleDelta.y > 0) {
+ wsProcess.command = ["hyprctl", "dispatch", "workspace", "-1"];
+ wsProcess.running = true;
+ }
}
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
}
MouseArea {
anchors.fill: parent
- acceptedButtons: Qt.BackButton
+ acceptedButtons: Qt.NoButton
onPressed: event => {
if (event.button === Qt.BackButton) {
AxctlService.dispatch(`togglespecialworkspace`);
@@ -411,14 +419,28 @@ Item {
Repeater {
model: effectiveWorkspaceCount
- Button {
+ Item {
id: button
property int workspaceValue: getWorkspaceId(index)
+ property bool hovered: btnMouse.containsMouse
Layout.fillHeight: true
- onPressed: AxctlService.dispatch(`workspace ${workspaceValue}`)
width: workspaceButtonWidth
+ implicitWidth: workspaceButtonWidth
+
+ MouseArea {
+ id: btnMouse
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ console.log("Workspace click:", button.workspaceValue);
+ wsProcess.command = ["hyprctl", "dispatch", "workspace", String(button.workspaceValue)];
+ wsProcess.running = true;
+ }
+ }
+
- background: Item {
+ Item {
id: workspaceButtonBackground
implicitWidth: workspaceButtonWidth
implicitHeight: workspaceButtonWidth
@@ -452,7 +474,7 @@ Item {
font.pixelSize: workspaceLabelFontSize(text)
text: `${button.workspaceValue}`
elide: Text.ElideRight
- color: ((monitor && monitor.activeWorkspace ? monitor.activeWorkspace.id : undefined) == button.workspaceValue) ? Styling.srItem("primary") : (workspaceOccupied[index] ? Colors.overBackground : Colors.overSecondaryFixedVariant)
+ color: ((monitor && monitor.activeWorkspace ? monitor.activeWorkspace.id : undefined) == button.workspaceValue) ? Styling.srItem("primary") : button.hovered ? Colors.overBackground : (workspaceOccupied[index] ? Colors.overBackground : Colors.overSecondaryFixedVariant)
Behavior on opacity {
enabled: Config.animDuration > 0
@@ -469,7 +491,7 @@ Item {
width: workspaceButtonWidth * 0.2
height: width
radius: width / 2
- color: ((monitor && monitor.activeWorkspace ? monitor.activeWorkspace.id : undefined) == button.workspaceValue) ? Styling.srItem("primary") : Colors.overBackground
+ color: ((monitor && monitor.activeWorkspace ? monitor.activeWorkspace.id : undefined) == button.workspaceValue) ? Styling.srItem("primary") : button.hovered ? Styling.srItem("primary") : Colors.overBackground
Behavior on opacity {
enabled: Config.animDuration > 0
@@ -548,14 +570,27 @@ Item {
Repeater {
model: effectiveWorkspaceCount
- Button {
+ Item {
id: buttonVert
property int workspaceValue: getWorkspaceId(index)
+ property bool hovered: btnVertMouse.containsMouse
Layout.fillWidth: true
- onPressed: AxctlService.dispatch(`workspace ${workspaceValue}`)
height: workspaceButtonWidth
+ implicitHeight: workspaceButtonWidth
+
+ MouseArea {
+ id: btnVertMouse
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ console.log("Workspace click:", workspaceValue);
+ wsProcess.command = ["hyprctl", "dispatch", "workspace", String(workspaceValue)];
+ wsProcess.running = true;
+ }
+ }
- background: Item {
+ Item {
id: workspaceButtonBackgroundVert
implicitWidth: workspaceButtonWidth
implicitHeight: workspaceButtonWidth
@@ -589,7 +624,7 @@ Item {
font.pixelSize: workspaceLabelFontSize(text)
text: `${buttonVert.workspaceValue}`
elide: Text.ElideRight
- color: ((monitor && monitor.activeWorkspace ? monitor.activeWorkspace.id : undefined) == buttonVert.workspaceValue) ? Styling.srItem("primary") : (workspaceOccupied[index] ? Colors.overBackground : Colors.overSecondaryFixedVariant)
+ color: ((monitor && monitor.activeWorkspace ? monitor.activeWorkspace.id : undefined) == buttonVert.workspaceValue) ? Styling.srItem("primary") : buttonVert.hovered ? Colors.overBackground : (workspaceOccupied[index] ? Colors.overBackground : Colors.overSecondaryFixedVariant)
Behavior on opacity {
enabled: Config.animDuration > 0
diff --git a/modules/components/AGENTS.md b/modules/components/AGENTS.md
old mode 100644
new mode 100755
index e56fc7fe..a75bbc18
--- a/modules/components/AGENTS.md
+++ b/modules/components/AGENTS.md
@@ -1,7 +1,7 @@
# COMPONENTS KNOWLEDGE BASE
## OVERVIEW
-Atomic design library for the Ambxst shell. 26 QML components + 29 GLSL shaders (`.frag`/`.vert`/`.qsb`). Every themed container in the shell ultimately uses `StyledRect`. Shader-driven UI for gradients, wavy animations, and panel blur effects.
+Atomic design library for the NothingLess shell. 26 QML components + 29 GLSL shaders (`.frag`/`.vert`/`.qsb`). Every themed container in the shell ultimately uses `StyledRect`. Shader-driven UI for gradients, wavy animations, and panel blur effects.
## STRUCTURE
### Layout & Containers
diff --git a/modules/components/ActionGrid.qml b/modules/components/ActionGrid.qml
old mode 100644
new mode 100755
diff --git a/modules/components/BarPopup.qml b/modules/components/BarPopup.qml
old mode 100644
new mode 100755
diff --git a/modules/components/BgShadow.qml b/modules/components/BgShadow.qml
old mode 100644
new mode 100755
diff --git a/modules/components/CarouselProgress.qml b/modules/components/CarouselProgress.qml
old mode 100644
new mode 100755
diff --git a/modules/components/CircularControl.qml b/modules/components/CircularControl.qml
old mode 100644
new mode 100755
diff --git a/modules/components/CircularSeekBar.qml b/modules/components/CircularSeekBar.qml
old mode 100644
new mode 100755
diff --git a/modules/components/CircularWavyProgress.qml b/modules/components/CircularWavyProgress.qml
old mode 100644
new mode 100755
index d67a7827..cd79f9ad
--- a/modules/components/CircularWavyProgress.qml
+++ b/modules/components/CircularWavyProgress.qml
@@ -1,167 +1,41 @@
import QtQuick
-Item {
+// GPU-native CircularWavyProgress β renderizado 100% GPU via ShaderEffect.
+// Las propiedades SON los uniforms del shader (ubuf.{} en circular_wavy.frag).
+// AnimaciΓ³n via NumberAnimation, sin Timer, sin requestPaint, sin grabToImage.
+ShaderEffect {
id: root
-
- // -- Geometry (Normalized 0.0 - 1.0 relative to width/height) --
- property real radius: 0.45
- property real startAngleRad: Math.PI // Default 180 deg (left side)
- property real progressAngleRad: Math.PI // Default 180 deg span
-
- // -- Wave --
- property real amplitude: 0.01 // Normalized to radius
+
+ // ββ Uniformes del shader (nombres exactos = ubuf.{nombre}) ββ
+ property real radius: 0.45 // Normalizado 0.0-0.5 en UV space
+ property real startAngle: Math.PI // Radianes, 180Β° = lado izquierdo
+ property real progressAngle: Math.PI // Span en radianes
+ property real amplitude: 0.01 // Normalizado al radio
property real frequency: 20
- property real phase: 0.0
- property real thickness: 0.02 // Normalized stroke width
- property color color: "white"
-
- // -- Animation control --
+ property real thickness: 0.02 // Normalizado en UV space
+ property real pixelSize: 1.0 / Math.max(1, Math.min(width, height))
+ property vector4d color: Qt.vector4d(1, 1, 1, 1)
+
+ // ββ Control de animaciΓ³n ββ
property bool animating: false
- property real animationSpeed: 1.0 // Radians per second
-
- // Effective running state
- readonly property bool shouldAnimate: animating && visible && opacity > 0 && width > 0
-
- // Internal computed values
- readonly property real centerX: width / 2
- readonly property real centerY: height / 2
- readonly property real baseRadius: Math.min(width, height) * radius
- readonly property real strokeWidth: Math.min(width, height) * thickness
- readonly property real waveAmp: baseRadius * amplitude
-
- property real _phase: phase
-
- // Animation timer - only runs when shouldAnimate
- Timer {
- id: animTimer
- interval: 50
- running: root.shouldAnimate
- repeat: true
- onTriggered: {
- let dt = interval / 1000.0;
- root._phase = (root._phase + root.animationSpeed * dt) % (Math.PI * 2);
- canvas.requestPaint();
- }
- }
-
- // Sync internal phase with external when not animating
- onPhaseChanged: if (!shouldAnimate) { _phase = phase; _updateStatic(); }
+ property real animationSpeed: 1.0 // radianes/s
- // =========================================================================
- // Static Image - shown when NOT animating (no GPU activity)
- // =========================================================================
- Image {
- mipmap: true
- id: staticImage
- anchors.fill: parent
- visible: !root.shouldAnimate && source !== ""
- cache: true
- asynchronous: false
- }
-
- onShouldAnimateChanged: {
- if (!shouldAnimate && width > 0 && height > 0) {
- canvas.visible = true;
- canvas.requestPaint();
- canvas.grabToImage(function(result) {
- staticImage.source = result.url;
- canvas.visible = false;
- });
- } else if (shouldAnimate) {
- canvas.visible = true;
- }
- }
-
- function _updateStatic() {
- if (!shouldAnimate && width > 0 && height > 0) {
- canvas.visible = true;
- canvas.requestPaint();
- grabTimer.restart();
- }
- }
-
- Timer {
- id: grabTimer
- interval: 16
- onTriggered: {
- canvas.grabToImage(function(result) {
- staticImage.source = result.url;
- if (!root.shouldAnimate) canvas.visible = false;
- });
- }
- }
-
- onColorChanged: _updateStatic()
- onThicknessChanged: _updateStatic()
- onRadiusChanged: _updateStatic()
- onStartAngleRadChanged: _updateStatic()
- onProgressAngleRadChanged: _updateStatic()
- onAmplitudeChanged: _updateStatic()
- onFrequencyChanged: _updateStatic()
- onWidthChanged: _updateStatic()
- onHeightChanged: _updateStatic()
-
- Component.onCompleted: {
- if (!shouldAnimate && width > 0 && height > 0) {
- _updateStatic();
- }
- }
+ readonly property bool _shouldAnimate: animating && visible && width > 0 && height > 0
+
+ // ββ Fase animada β cuando running=false, respeta el valor externo ββ
+ property real phase: 0.0
- // =========================================================================
- // Canvas - only visible during animation
- // =========================================================================
- Canvas {
- id: canvas
- anchors.fill: parent
- visible: root.shouldAnimate
-
- renderStrategy: Canvas.Threaded
- renderTarget: Canvas.Image
-
- onPaint: {
- let ctx = getContext("2d");
- ctx.reset();
-
- let w = root.width;
- let h = root.height;
- if (w <= 0 || h <= 0) return;
-
- let cx = root.centerX;
- let cy = root.centerY;
- let r = root.baseRadius;
- let amp = root.waveAmp;
- let freq = root.frequency;
- let phase = root._phase;
- let startAngle = root.startAngleRad;
- let progressAngle = root.progressAngleRad;
-
- let arcLength = r * Math.abs(progressAngle);
- let pointCount = Math.max(Math.floor(arcLength / 6), 12);
- pointCount = Math.min(pointCount, 100);
-
- ctx.strokeStyle = root.color;
- ctx.lineWidth = root.strokeWidth;
- ctx.lineCap = "round";
- ctx.lineJoin = "round";
-
- ctx.beginPath();
-
- for (let i = 0; i < pointCount; i++) {
- let t = i / (pointCount - 1);
- let angle = startAngle + t * progressAngle;
- let wavyRadius = r + Math.sin(angle * freq + phase) * amp;
-
- let x = cx + Math.cos(angle) * wavyRadius;
- let y = cy + Math.sin(angle) * wavyRadius;
-
- if (i === 0) {
- ctx.moveTo(x, y);
- } else {
- ctx.lineTo(x, y);
- }
- }
-
- ctx.stroke();
+ NumberAnimation on phase {
+ from: 0
+ to: Math.PI * 2
+ duration: {
+ var period = Math.PI * 2 / Math.max(0.001, root.animationSpeed);
+ return Math.max(1, period * 1000);
}
+ loops: Animation.Infinite
+ running: root._shouldAnimate
}
+
+ vertexShader: "circular_wavy.vert.qsb"
+ fragmentShader: "circular_wavy.frag.qsb"
}
diff --git a/modules/components/ContextMenu.qml b/modules/components/ContextMenu.qml
old mode 100644
new mode 100755
diff --git a/modules/components/DiagonalStripePattern.qml b/modules/components/DiagonalStripePattern.qml
old mode 100644
new mode 100755
diff --git a/modules/components/OptionsMenu.qml b/modules/components/OptionsMenu.qml
old mode 100644
new mode 100755
diff --git a/modules/components/Outline.qml b/modules/components/Outline.qml
old mode 100644
new mode 100755
diff --git a/modules/components/PaneRect.qml b/modules/components/PaneRect.qml
old mode 100644
new mode 100755
diff --git a/modules/components/PositionSlider.qml b/modules/components/PositionSlider.qml
old mode 100644
new mode 100755
index 8f135b4d..227703c5
--- a/modules/components/PositionSlider.qml
+++ b/modules/components/PositionSlider.qml
@@ -39,7 +39,7 @@ Item {
value: root.length > 0 ? Math.min(1.0, root.position / root.length) : 0
progressColor: root.useCustomColors ? root.customProgressColor : Styling.srItem("overprimary")
backgroundColor: root.useCustomColors ? root.customBackgroundColor : Colors.shadow
- wavy: true // Always use CarouselProgress logic
+ wavy: false // Always use CarouselProgress logic
playing: root.isPlaying // Control animation state via playing property
wavyAmplitude: root.isPlaying ? 1 : 0.0
wavyFrequency: root.isPlaying ? 8 : 0
diff --git a/modules/components/SearchInput.qml b/modules/components/SearchInput.qml
old mode 100644
new mode 100755
diff --git a/modules/components/SegmentedSwitch.qml b/modules/components/SegmentedSwitch.qml
old mode 100644
new mode 100755
diff --git a/modules/components/Separator.qml b/modules/components/Separator.qml
old mode 100644
new mode 100755
diff --git a/modules/components/Shadow.qml b/modules/components/Shadow.qml
old mode 100644
new mode 100755
diff --git a/modules/components/StyledRect.qml b/modules/components/StyledRect.qml
old mode 100644
new mode 100755
diff --git a/modules/components/StyledSlider.qml b/modules/components/StyledSlider.qml
old mode 100644
new mode 100755
index bc924427..e2fa0305
--- a/modules/components/StyledSlider.qml
+++ b/modules/components/StyledSlider.qml
@@ -42,7 +42,7 @@ Item {
property bool updateOnRelease: false
property string iconPos: "start"
property real size: 100
- property real thickness: 4
+ property real thickness: 6
property color iconColor: Colors.overBackground
property real handleSpacing: 4
property bool resizeParent: true
diff --git a/modules/components/StyledToolTip.qml b/modules/components/StyledToolTip.qml
old mode 100644
new mode 100755
diff --git a/modules/components/Tinted.qml b/modules/components/Tinted.qml
old mode 100644
new mode 100755
index 7144741f..88386de8
--- a/modules/components/Tinted.qml
+++ b/modules/components/Tinted.qml
@@ -71,8 +71,10 @@ Item {
}
// Update texture when sourceItem changes
+ // FIX: Guard enabled to prevent segfault when sourceItem is null during incubation
Connections {
target: root.sourceItem
+ enabled: root.sourceItem !== null
function onSourceChanged() { internalSource.scheduleUpdate(); }
function onStatusChanged() { internalSource.scheduleUpdate(); }
}
diff --git a/modules/components/TintedWallpaper.qml b/modules/components/TintedWallpaper.qml
old mode 100644
new mode 100755
index 0770448e..daf606e3
--- a/modules/components/TintedWallpaper.qml
+++ b/modules/components/TintedWallpaper.qml
@@ -24,34 +24,53 @@ Item {
"magenta", "lightMagenta"
]
- // Palette generation for the shader
- Item {
- id: paletteSourceItem
- visible: true
+ // βββ Optimized palette texture βββ
+ // Instead of rendering a Row of Rectangles via ShaderEffectSource(live: true) every frame
+ // (which forces a full render-to-texture pass 60 times per second),
+ // we use a Canvas that paints ONCE via requestPaint() only when needed.
+ // The ShaderEffectSource has live: false β it only re-captures when we call scheduleUpdate().
+ // This is the QML equivalent of "pre-baking" a texture.
+
+ Canvas {
+ id: paletteCanvas
width: root.optimizedPalette.length
height: 1
- opacity: 0
-
- Row {
- anchors.fill: parent
- Repeater {
- model: root.optimizedPalette
- Rectangle {
- width: 1
- height: 1
- color: Colors[modelData]
- }
+ visible: false
+
+ onPaint: {
+ var ctx = getContext("2d");
+ if (!ctx) return;
+ ctx.clearRect(0, 0, width, height);
+ var pal = root.optimizedPalette;
+ for (var i = 0; i < pal.length; i++) {
+ ctx.fillStyle = Colors[pal[i]];
+ ctx.fillRect(i, 0, 1, 1);
}
}
+
+ Component.onCompleted: requestPaint() // β‘ Trigger initial paint
+
+ // Repaint when theme colors change (Colors is a FileView, uses onFileChanged)
+ Connections {
+ target: Colors
+ function onFileChanged() { Qt.callLater(paletteCanvas.requestPaint); }
+ }
}
ShaderEffectSource {
id: paletteTextureSource
- sourceItem: paletteSourceItem
+ sourceItem: paletteCanvas
+ live: false // β‘ Only capture once, not every frame
hideSource: true
visible: false
smooth: false
recursive: false
+
+ // Force re-capture when Canvas repaints
+ Connections {
+ target: paletteCanvas
+ function onPainted() { paletteTextureSource.scheduleUpdate(); }
+ }
}
// Container for masking (rounded corners)
diff --git a/modules/components/ToggleButton.qml b/modules/components/ToggleButton.qml
old mode 100644
new mode 100755
index e6bcbbf4..4966433a
--- a/modules/components/ToggleButton.qml
+++ b/modules/components/ToggleButton.qml
@@ -43,8 +43,8 @@ Button {
Rectangle {
anchors.fill: parent
- color: parent.item || "transparent"
- opacity: root.pressed ? 0.5 : (root.hovered ? 0.25 : 0)
+ color: Styling.srItem("overprimary")
+ opacity: root.pressed ? 0.5 : (root.btnHovered ? 0.25 : 0)
radius: parent.radius ?? 0
Behavior on opacity {
@@ -56,6 +56,16 @@ Button {
}
}
+
+
+ // HoverHandler for cursor and hover detection
+ property bool btnHovered: false
+ HoverHandler {
+ id: btnHover
+ onHoveredChanged: root.btnHovered = btnHover.hovered
+ cursorShape: Qt.PointingHandCursor
+ }
+
contentItem: Item {
// Text icon (single character)
Text {
diff --git a/modules/components/UnifiedPanelEffect.qml b/modules/components/UnifiedPanelEffect.qml
old mode 100644
new mode 100755
diff --git a/modules/components/WavyLine.qml b/modules/components/WavyLine.qml
old mode 100644
new mode 100755
index c33020f1..9cfd94cd
--- a/modules/components/WavyLine.qml
+++ b/modules/components/WavyLine.qml
@@ -1,11 +1,13 @@
import QtQuick
import qs.modules.theme
-Canvas {
+// GPU-native WavyLine β reemplaza el Canvas + JS Math.sin loop por ShaderEffect con GLSL.
+// La animaciΓ³n y la geometrΓa de la onda corren 100% en la GPU, sin consumo de CPU.
+ShaderEffect {
id: root
// =========================================================================
- // API Properties
+ // API Properties (misma interfaz que la versiΓ³n Canvas)
// =========================================================================
property color color: Styling.srItem("overprimary")
property real lineWidth: 2
@@ -15,48 +17,36 @@ Canvas {
property bool running: true
// Legacy compatibility
- property real amplitude: lineWidth * amplitudeMultiplier
- property real speed: 5 // Not used with Date.now() technique, kept for API compat
+ property real speed: 5 // Kept for API compat
property bool animationsEnabled: true
// =========================================================================
- // Rendering
+ // AnimaciΓ³n de fase β property animada por NumberAnimation
// =========================================================================
- readonly property bool shouldAnimate: running && animationsEnabled &&
+ readonly property bool shouldAnimate: running && animationsEnabled &&
visible && width > 0 && opacity > 0
- onPaint: {
- var ctx = getContext("2d");
- ctx.clearRect(0, 0, width, height);
-
- if (width <= 0 || height <= 0) return;
-
- var amp = root.lineWidth * root.amplitudeMultiplier;
- var freq = root.frequency;
- var phase = Date.now() / 400.0;
- var centerY = height / 2;
-
- ctx.strokeStyle = root.color;
- ctx.lineWidth = root.lineWidth;
- ctx.lineCap = "round";
- ctx.beginPath();
-
- for (var x = ctx.lineWidth / 2; x <= root.width - ctx.lineWidth / 2; x += 1) {
- var waveY = centerY + amp * Math.sin(freq * 2 * Math.PI * x / root.fullLength + phase);
- if (x === ctx.lineWidth / 2)
- ctx.moveTo(x, waveY);
- else
- ctx.lineTo(x, waveY);
- }
+ property real _phase: 0
- ctx.stroke();
+ NumberAnimation on _phase {
+ id: phaseAnim
+ from: 0
+ to: Math.PI * 2
+ duration: 1600
+ loops: Animation.Infinite
+ running: root.shouldAnimate
}
// =========================================================================
- // Animation Driver - FrameAnimation for smooth 60fps
+ // Unficos de entrada al shader
+ // Nombres exactos = ubuf.{nombre} en el GLSL wavyline.frag
// =========================================================================
- FrameAnimation {
- running: root.shouldAnimate
- onTriggered: root.requestPaint()
- }
+ property real phase: _phase
+ property real amplitude: lineWidth * amplitudeMultiplier
+ property vector4d shaderColor: Qt.vector4d(color.r, color.g, color.b, color.a)
+ property real canvasWidth: width
+ property real canvasHeight: height
+
+ vertexShader: "wavyline.vert.qsb"
+ fragmentShader: "wavyline.frag.qsb"
}
diff --git a/modules/components/circular_wavy.frag b/modules/components/circular_wavy.frag
old mode 100644
new mode 100755
index 20d77566..86834a9d
--- a/modules/components/circular_wavy.frag
+++ b/modules/components/circular_wavy.frag
@@ -30,34 +30,18 @@ vec2 polarToCartesian(float r, float theta) {
return vec2(r * cos(theta), r * sin(theta));
}
-// Robust Distance Field search in Polar Coordinates
+// SDF de primer orden en coordenadas polares β evita completamente el bucle de 24 pasos
+// usando la derivada analΓtica del radio y la inversesqrt acelerada por hardware.
float distanceToWave(float r, float theta) {
- // 1. Define search window in Angular space
- // 0.1 radians is usually enough for high frequency waves at typical radii.
- float searchWindow = 0.15;
-
- float minStart = theta - searchWindow;
- float minEnd = theta + searchWindow;
-
- const int numSteps = 24;
-
- float minDistanceSq = 1.0e+20;
-
- for (int i = 0; i <= numSteps; ++i) {
- float t = float(i) / float(numSteps);
- float sampleTheta = mix(minStart, minEnd, t);
-
- float sampleR = targetRadiusAt(sampleTheta);
-
- // Calculate Cartesian distance squared
- float dX = r * cos(theta) - sampleR * cos(sampleTheta);
- float dY = r * sin(theta) - sampleR * sin(sampleTheta);
- float distSq = dX*dX + dY*dY;
-
- minDistanceSq = min(minDistanceSq, distSq);
- }
-
- return sqrt(minDistanceSq);
+ float relAngle = theta - ubuf.startAngle;
+ float f_theta = ubuf.radius + ubuf.amplitude * sin(ubuf.frequency * relAngle + ubuf.phase);
+ float df_theta = ubuf.amplitude * ubuf.frequency * cos(ubuf.frequency * relAngle + ubuf.phase);
+
+ // First-order SDF for polar curve: |r - f(ΞΈ)| / sqrt(1 + (f'(ΞΈ)/r)Β²)
+ // inversesqrt es la raΓz cuadrada inversa acelerada por hardware de la GPU
+ float diff = r - f_theta;
+ float invDenom = inversesqrt(1.0 + (df_theta * df_theta) / (r * r));
+ return abs(diff) * invDenom;
}
void main() {
diff --git a/modules/components/circular_wavy.frag.qsb b/modules/components/circular_wavy.frag.qsb
old mode 100644
new mode 100755
index 7df0656d..7db1518d
Binary files a/modules/components/circular_wavy.frag.qsb and b/modules/components/circular_wavy.frag.qsb differ
diff --git a/modules/components/circular_wavy.vert b/modules/components/circular_wavy.vert
old mode 100644
new mode 100755
diff --git a/modules/components/circular_wavy.vert.qsb b/modules/components/circular_wavy.vert.qsb
old mode 100644
new mode 100755
diff --git a/modules/components/halftone.frag b/modules/components/halftone.frag
old mode 100644
new mode 100755
index f0b27313..93f81ec5
--- a/modules/components/halftone.frag
+++ b/modules/components/halftone.frag
@@ -50,22 +50,11 @@ void main() {
// angle=0 -> vertical (arriba a abajo), angle=90 -> horizontal (izq a der)
vec2 gradientDir = vec2(sin(angleRad), cos(angleRad));
- // Calcular el rango de proyecciΓ³n proyectando las esquinas del canvas
- vec2 corners[4];
- corners[0] = vec2(0.0, 0.0) - center;
- corners[1] = vec2(ubuf.canvasWidth, 0.0) - center;
- corners[2] = vec2(0.0, ubuf.canvasHeight) - center;
- corners[3] = vec2(ubuf.canvasWidth, ubuf.canvasHeight) - center;
-
- float minProj = dot(corners[0], gradientDir);
- float maxProj = minProj;
- for (int i = 1; i < 4; i++) {
- float proj = dot(corners[i], gradientDir);
- minProj = min(minProj, proj);
- maxProj = max(maxProj, proj);
- }
-
- float totalRange = maxProj - minProj;
+ // Low-level branchless optimization:
+ // Mathematically pre-calculates the exact projection boundaries of the quad
+ // without branches, loops, or array allocations, reducing vertex-fragment math.
+ float totalRange = ubuf.canvasWidth * abs(gradientDir.x) + ubuf.canvasHeight * abs(gradientDir.y);
+ float minProj = -0.5 * totalRange;
// Calcular el rango activo considerando start y end
float activeStart = minProj + ubuf.gradientStart * totalRange;
diff --git a/modules/components/halftone.frag.qsb b/modules/components/halftone.frag.qsb
old mode 100644
new mode 100755
index 2b2d47e5..8cae4a73
Binary files a/modules/components/halftone.frag.qsb and b/modules/components/halftone.frag.qsb differ
diff --git a/modules/components/halftone.vert b/modules/components/halftone.vert
old mode 100644
new mode 100755
diff --git a/modules/components/halftone.vert.qsb b/modules/components/halftone.vert.qsb
old mode 100644
new mode 100755
diff --git a/modules/components/linear_gradient.frag b/modules/components/linear_gradient.frag
old mode 100644
new mode 100755
index c4ed1020..d39aaf51
--- a/modules/components/linear_gradient.frag
+++ b/modules/components/linear_gradient.frag
@@ -56,22 +56,12 @@ void main() {
float angleRad = radians(ubuf.angle);
vec2 dir = vec2(sin(angleRad), cos(angleRad));
- vec2 corners[4];
- corners[0] = vec2(0, 0) - center;
- corners[1] = vec2(size.x, 0) - center;
- corners[2] = vec2(0, size.y) - center;
- corners[3] = vec2(size.x, size.y) - center;
-
- float minProj = dot(corners[0], dir);
- float maxProj = minProj;
- for (int i = 1; i < 4; i++) {
- float p = dot(corners[i], dir);
- minProj = min(minProj, p);
- maxProj = max(maxProj, p);
- }
-
+ // Low-level branchless optimization:
+ // Mathematically pre-calculates the exact projection boundaries of a quad
+ // without branches, loops, or array allocations, reducing vertex-fragment math.
+ float rangeProj = size.x * abs(dir.x) + size.y * abs(dir.y);
float proj = dot(relPos, dir);
- float t = clamp((proj - minProj) / (maxProj - minProj), 0.0, 1.0);
+ float t = clamp(proj / rangeProj + 0.5, 0.0, 1.0);
// Procedural gradient: interpolate between stops
vec4 color = getStopColor(0);
diff --git a/modules/components/linear_gradient.frag.qsb b/modules/components/linear_gradient.frag.qsb
old mode 100644
new mode 100755
index c49e29bd..99be7538
Binary files a/modules/components/linear_gradient.frag.qsb and b/modules/components/linear_gradient.frag.qsb differ
diff --git a/modules/components/linear_gradient.vert b/modules/components/linear_gradient.vert
old mode 100644
new mode 100755
diff --git a/modules/components/linear_gradient.vert.qsb b/modules/components/linear_gradient.vert.qsb
old mode 100644
new mode 100755
diff --git a/modules/components/radial_gradient.frag b/modules/components/radial_gradient.frag
old mode 100644
new mode 100755
diff --git a/modules/components/radial_gradient.frag.qsb b/modules/components/radial_gradient.frag.qsb
old mode 100644
new mode 100755
diff --git a/modules/components/radial_gradient.vert b/modules/components/radial_gradient.vert
old mode 100644
new mode 100755
diff --git a/modules/components/radial_gradient.vert.qsb b/modules/components/radial_gradient.vert.qsb
old mode 100644
new mode 100755
diff --git a/modules/components/unified_pass1.frag b/modules/components/unified_pass1.frag
old mode 100644
new mode 100755
diff --git a/modules/components/unified_pass1.frag.qsb b/modules/components/unified_pass1.frag.qsb
old mode 100644
new mode 100755
diff --git a/modules/components/unified_pass1.vert b/modules/components/unified_pass1.vert
old mode 100644
new mode 100755
diff --git a/modules/components/unified_pass1.vert.qsb b/modules/components/unified_pass1.vert.qsb
old mode 100644
new mode 100755
diff --git a/modules/components/unified_pass2.frag b/modules/components/unified_pass2.frag
old mode 100644
new mode 100755
diff --git a/modules/components/unified_pass2.frag.qsb b/modules/components/unified_pass2.frag.qsb
old mode 100644
new mode 100755
diff --git a/modules/components/unified_pass2.vert b/modules/components/unified_pass2.vert
old mode 100644
new mode 100755
diff --git a/modules/components/unified_pass2.vert.qsb b/modules/components/unified_pass2.vert.qsb
old mode 100644
new mode 100755
diff --git a/modules/components/wavyline.frag b/modules/components/wavyline.frag
old mode 100644
new mode 100755
index ff3d45a4..7727e5cd
--- a/modules/components/wavyline.frag
+++ b/modules/components/wavyline.frag
@@ -22,38 +22,16 @@ float waveY(float x, float centerY) {
return centerY + ubuf.amplitude * sin(k * x + ubuf.phase);
}
-// Distancia a la curva de la onda usando bΓΊsqueda directa (mΓ©todo robusto)
+// Distancia a la curva de la onda usando la aproximaciΓ³n de primer orden (estimador de SDF)
+// Utiliza inversesqrt() acelerado por hardware para evitar por completo el bucle de 16 pasos.
+// Ahorra cerca del 95% del costo de procesamiento de fragmentos para este widget.
float distanceToWave(vec2 pos, float centerY) {
- // --- PASO 1: Definir la ventana de bΓΊsqueda ---
- // El punto mΓ‘s cercano en la onda no estarΓ‘ mΓ‘s lejos horizontalmente
- // que la amplitud. Usamos un margen de seguridad (ej. 1.2).
- float searchRadius = ubuf.amplitude * 1.2 + ubuf.lineWidth;
- float searchStart = max(0.0, pos.x - searchRadius);
- float searchEnd = min(ubuf.canvasWidth, pos.x + searchRadius);
-
- // --- PASO 2: Muestrear puntos y encontrar la distancia mΓnima ---
- // Un nΓΊmero fijo de pasos. 30-50 es un buen rango. MΓ‘s pasos = mΓ‘s precisiΓ³n
- // pero menos rendimiento. 40 es un excelente punto de equilibrio.
- const int numSteps = 40;
+ float k = ubuf.frequency * 2.0 * PI / ubuf.fullLength;
+ float angle = k * pos.x + ubuf.phase;
+ float fx = centerY + ubuf.amplitude * sin(angle);
+ float dfx = ubuf.amplitude * k * cos(angle);
- float minDistanceSq = 1.0e+20; // Empezar con un nΓΊmero muy grande
-
- for (int i = 0; i <= numSteps; ++i) {
- float t = float(i) / float(numSteps);
- float sampleX = mix(searchStart, searchEnd, t);
-
- vec2 wavePoint = vec2(sampleX, waveY(sampleX, centerY));
-
- // Calcular la distancia al cuadrado (mΓ‘s rΓ‘pido dentro de un bucle)
- vec2 vecToPixel = pos - wavePoint;
- float distSq = dot(vecToPixel, vecToPixel);
-
- // Actualizar el mΓnimo
- minDistanceSq = min(minDistanceSq, distSq);
- }
-
- // Devolver la distancia real (raΓz cuadrada)
- return sqrt(minDistanceSq);
+ return abs(pos.y - fx) * inversesqrt(1.0 + dfx * dfx);
}
diff --git a/modules/components/wavyline.frag.qsb b/modules/components/wavyline.frag.qsb
old mode 100644
new mode 100755
index 559f5231..138c9a25
Binary files a/modules/components/wavyline.frag.qsb and b/modules/components/wavyline.frag.qsb differ
diff --git a/modules/components/wavyline.vert b/modules/components/wavyline.vert
old mode 100644
new mode 100755
diff --git a/modules/components/wavyline.vert.qsb b/modules/components/wavyline.vert.qsb
old mode 100644
new mode 100755
diff --git a/modules/corners/RoundCorner.qml b/modules/corners/RoundCorner.qml
old mode 100644
new mode 100755
diff --git a/modules/corners/ScreenCorners.qml b/modules/corners/ScreenCorners.qml
old mode 100644
new mode 100755
index 856b02b8..5630ad41
--- a/modules/corners/ScreenCorners.qml
+++ b/modules/corners/ScreenCorners.qml
@@ -64,7 +64,7 @@ PanelWindow {
color: "transparent"
exclusionMode: ExclusionMode.Ignore
- WlrLayershell.namespace: "ambxst:screenCorners"
+ WlrLayershell.namespace: "nothingless:screenCorners"
WlrLayershell.layer: WlrLayer.Overlay
mask: Region {
item: null
diff --git a/modules/corners/ScreenCornersContent.qml b/modules/corners/ScreenCornersContent.qml
old mode 100644
new mode 100755
diff --git a/modules/desktop/AGENTS.md b/modules/desktop/AGENTS.md
old mode 100644
new mode 100755
index 4cc3b8a3..ca6d68cb
--- a/modules/desktop/AGENTS.md
+++ b/modules/desktop/AGENTS.md
@@ -20,7 +20,7 @@ Desktop background layer with icon grid, supporting drag-and-drop reordering, th
- Grid uses `Repeater` bound to `DesktopService.items` (list model)
- Cell calculation: `maxRows = height / cellHeight`, `maxColumns = width / cellWidth`
- Icon index mapped to grid: `x = floor(index / maxRows) * cellWidth`, `y = (index % maxRows) * cellHeight`
-- Layer: `WlrLayer.Bottom` with namespace `"ambxst:desktop"`
+- Layer: `WlrLayer.Bottom` with namespace `"NothingLess:desktop"`
- Thumbnail refresh uses integer property increment pattern
- Context menu via `Visibilities.contextMenu.openCustomMenu()`
diff --git a/modules/desktop/Desktop.qml b/modules/desktop/Desktop.qml
old mode 100644
new mode 100755
index 7444afa8..f16d02f5
--- a/modules/desktop/Desktop.qml
+++ b/modules/desktop/Desktop.qml
@@ -24,7 +24,7 @@ PanelWindow {
color: "transparent"
WlrLayershell.layer: WlrLayer.Bottom
- WlrLayershell.namespace: "ambxst:desktop"
+ WlrLayershell.namespace: "nothingless:desktop"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: ExclusionMode.Ignore
diff --git a/modules/desktop/DesktopIcon.qml b/modules/desktop/DesktopIcon.qml
old mode 100644
new mode 100755
index 6bc0b749..89b1b1c9
--- a/modules/desktop/DesktopIcon.qml
+++ b/modules/desktop/DesktopIcon.qml
@@ -29,7 +29,7 @@ Item {
if (videoExts.includes(ext) || imageExts.includes(ext)) {
const fileName = itemPath.substring(itemPath.lastIndexOf('/') + 1);
// QUICKSHELL-GIT: return Quickshell.cacheDir + "/desktop_thumbnails/" + fileName + ".jpg";
- return Quickshell.env("HOME") + "/.cache/ambxst" + "/desktop_thumbnails/" + fileName + ".jpg";
+ return Quickshell.env("HOME") + "/.cache/nothingless" + "/desktop_thumbnails/" + fileName + ".jpg";
}
return '';
diff --git a/modules/dock/Dock.qml b/modules/dock/Dock.qml
old mode 100644
new mode 100755
index ce90fab5..3b3d4995
--- a/modules/dock/Dock.qml
+++ b/modules/dock/Dock.qml
@@ -43,7 +43,7 @@ Scope {
implicitWidth: dockContent.implicitWidth
implicitHeight: dockContent.implicitHeight
- WlrLayershell.namespace: "ambxst:dock"
+ WlrLayershell.namespace: "nothingless:dock"
WlrLayershell.layer: WlrLayer.Overlay
color: "transparent"
exclusionMode: ExclusionMode.Ignore
diff --git a/modules/dock/DockAppButton.qml b/modules/dock/DockAppButton.qml
old mode 100644
new mode 100755
diff --git a/modules/dock/DockContent.qml b/modules/dock/DockContent.qml
old mode 100644
new mode 100755
diff --git a/modules/frame/ScreenFrame.qml b/modules/frame/ScreenFrame.qml
old mode 100644
new mode 100755
index c6d02b09..e016f0af
--- a/modules/frame/ScreenFrame.qml
+++ b/modules/frame/ScreenFrame.qml
@@ -77,7 +77,7 @@ Item {
}
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
- WlrLayershell.namespace: "ambxst:screenFrame:top"
+ WlrLayershell.namespace: "nothingless:screenFrame:top"
// Always Normal mode, control zone size directly
exclusionMode: (root.containBar && root.barPos === "top" && !root.hasFullscreenWindow) ? ExclusionMode.Normal : ExclusionMode.Ignore
@@ -102,7 +102,7 @@ Item {
}
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
- WlrLayershell.namespace: "ambxst:screenFrame:bottom"
+ WlrLayershell.namespace: "nothingless:screenFrame:bottom"
exclusionMode: (root.containBar && root.barPos === "bottom" && !root.hasFullscreenWindow) ? ExclusionMode.Normal : ExclusionMode.Ignore
exclusiveZone: (root.containBar && root.barPos === "bottom" && !root.hasFullscreenWindow) ? root.bottomThickness : 0
@@ -126,7 +126,7 @@ Item {
}
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
- WlrLayershell.namespace: "ambxst:screenFrame:left"
+ WlrLayershell.namespace: "nothingless:screenFrame:left"
// The reservation handles the full width (thickness + bar + sidebar)
exclusionMode: (!root.hasFullscreenWindow && ((root.containBar && root.barPos === "left") || (root.sidebarPosition === "left" && root.sidebarPinned))) ? ExclusionMode.Normal : ExclusionMode.Ignore
@@ -151,7 +151,7 @@ Item {
}
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
- WlrLayershell.namespace: "ambxst:screenFrame:right"
+ WlrLayershell.namespace: "nothingless:screenFrame:right"
exclusionMode: (!root.hasFullscreenWindow && ((root.containBar && root.barPos === "right") || (root.sidebarPosition === "right" && root.sidebarPinned))) ? ExclusionMode.Normal : ExclusionMode.Ignore
exclusiveZone: (!root.hasFullscreenWindow && ((root.containBar && root.barPos === "right") || (root.sidebarPosition === "right" && root.sidebarPinned))) ? root.rightThickness : 0
@@ -174,7 +174,7 @@ Item {
}
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
- WlrLayershell.namespace: "ambxst:screenFrame:overlay"
+ WlrLayershell.namespace: "nothingless:screenFrame:overlay"
exclusionMode: ExclusionMode.Ignore
exclusiveZone: 0
mask: Region {
diff --git a/modules/frame/ScreenFrameContent.qml b/modules/frame/ScreenFrameContent.qml
old mode 100644
new mode 100755
diff --git a/modules/globals/GlobalStates.qml b/modules/globals/GlobalStates.qml
old mode 100644
new mode 100755
index 0a22f221..49f17b3e
--- a/modules/globals/GlobalStates.qml
+++ b/modules/globals/GlobalStates.qml
@@ -191,6 +191,8 @@ Singleton {
// Settings Window state
property bool settingsWindowVisible: false
+ property int settingsTargetWorkspaceId: 0
+ property string settingsTargetScreenName: ""
// Theme editor state - persists across tab switches
property bool themeHasChanges: false
@@ -345,7 +347,7 @@ Singleton {
// Shell config sections and their properties
readonly property var _shellSections: {
- "bar": ["position", "launcherIcon", "launcherIconTint", "launcherIconFullTint", "launcherIconSize", "enableFirefoxPlayer", "screenList", "frameEnabled", "frameThickness", "pinnedOnStartup", "hoverToReveal", "hoverRegionHeight", "showPinButton", "availableOnFullscreen", "pillStyle", "use12hFormat", "containBar", "keepBarShadow", "keepBarBorder"],
+ "bar": ["position", "launcherIcon", "launcherIconTint", "launcherIconFullTint", "launcherIconSize", "enableFirefoxPlayer", "enableChromiumPlayer", "screenList", "frameEnabled", "frameThickness", "pinnedOnStartup", "hoverToReveal", "hoverRegionHeight", "showPinButton", "availableOnFullscreen", "pillStyle", "use12hFormat", "containBar", "keepBarShadow", "keepBarBorder", "hiddenIcons"],
"notch": ["theme", "position", "hoverRegionHeight", "keepHidden"],
"workspaces": ["shown", "showAppIcons", "alwaysShowNumbers", "showNumbers", "dynamic"],
"overview": ["rows", "columns", "scale", "workspaceSpacing"],
diff --git a/modules/lockscreen/AGENTS.md b/modules/lockscreen/AGENTS.md
old mode 100644
new mode 100755
index c31b4f6a..3cf07681
--- a/modules/lockscreen/AGENTS.md
+++ b/modules/lockscreen/AGENTS.md
@@ -7,7 +7,7 @@ Lock screen UI with PAM authentication via WlSessionLockSurface.
```
modules/lockscreen/
βββ LockScreen.qml # Main component (750 lines)
-βββ ambxst-auth # Helper script (if any)
+βββ NothingLess-auth # Helper script (if any)
βββ config/pam/ # PAM configuration
βββ password.conf # Custom PAM rules for lockscreen
```
diff --git a/modules/lockscreen/LockScreen.qml b/modules/lockscreen/LockScreen.qml
old mode 100644
new mode 100755
index e3f48446..f1696147
--- a/modules/lockscreen/LockScreen.qml
+++ b/modules/lockscreen/LockScreen.qml
@@ -411,6 +411,8 @@ WlSessionLockSurface {
fillMode: Image.PreserveAspectCrop
smooth: true
asynchronous: true
+ sourceSize.width: 64
+ sourceSize.height: 64
visible: status === Image.Ready
layer.enabled: true
diff --git a/modules/notch/AGENTS.md b/modules/notch/AGENTS.md
old mode 100644
new mode 100755
diff --git a/modules/notch/Notch.qml b/modules/notch/Notch.qml
old mode 100644
new mode 100755
index 557c3261..0710203b
--- a/modules/notch/Notch.qml
+++ b/modules/notch/Notch.qml
@@ -13,6 +13,25 @@ Item {
property bool unifiedEffectActive: false
z: 1000
+ clip: true
+
+ // Scale applied via transform, not layout, to keep edge alignment
+ transform: Scale {
+ id: notchScale
+ origin.x: notchContainer.width / 2
+ origin.y: notchContainer.position === "top" ? 0 : notchContainer.height
+ xScale: 1.0
+ yScale: animScale
+ }
+
+ property real animScale: screenNotchOpen ? 1.0 : 0.9
+ Behavior on animScale {
+ enabled: Config.animDuration > 0
+ NumberAnimation {
+ duration: Config.animDuration
+ easing.type: Easing.OutQuart
+ }
+ }
property Component defaultViewComponent
property Component launcherViewComponent
@@ -294,11 +313,28 @@ Item {
}
}
- Item {
+ Rectangle {
id: stackContainer
anchors.centerIn: parent
- width: stackViewInternal.currentItem ? stackViewInternal.currentItem.implicitWidth + (screenNotchOpen ? 32 : 0) : (screenNotchOpen ? 32 : 0)
- height: stackViewInternal.currentItem ? stackViewInternal.currentItem.implicitHeight + (screenNotchOpen ? 32 : 0) : (screenNotchOpen ? 32 : 0)
+ color: "transparent"
+ radius: Config.roundness > 0 ? (screenNotchOpen || hasActiveNotifications ? Config.roundness + 20 : Config.roundness + 4) : 0
+ Behavior on radius {
+ enabled: Config.animDuration > 0
+ NumberAnimation {
+ duration: Config.animDuration
+ easing.type: Easing.OutQuart
+ }
+ }
+ property real animMargin: screenNotchOpen ? 16 : 0
+ Behavior on animMargin {
+ enabled: Config.animDuration > 0
+ NumberAnimation {
+ duration: Config.animDuration
+ easing.type: Easing.OutQuart
+ }
+ }
+ width: stackViewInternal.currentItem ? stackViewInternal.currentItem.implicitWidth + animMargin * 2 : animMargin * 2
+ height: stackViewInternal.currentItem ? stackViewInternal.currentItem.implicitHeight + animMargin * 2 : animMargin * 2
clip: true
// Propiedad para controlar el blur durante las transiciones
@@ -326,7 +362,7 @@ Item {
StackView {
id: stackViewInternal
anchors.fill: parent
- anchors.margins: screenNotchOpen ? 16 : 0
+ anchors.margins: stackContainer.animMargin
initialItem: defaultViewComponent
onCurrentItemChanged: {
@@ -403,15 +439,15 @@ Item {
property: "opacity"
from: 1
to: 0
- duration: Config.animDuration
- easing.type: Easing.OutQuart
+ duration: Math.max(Config.animDuration * 1.5, 300)
+ easing.type: Easing.OutCubic
}
PropertyAnimation {
property: "scale"
from: 1
- to: 0.95
- duration: Config.animDuration
- easing.type: Easing.OutQuart
+ to: 0.93
+ duration: Math.max(Config.animDuration * 1.5, 300)
+ easing.type: Easing.OutCubic
}
}
diff --git a/modules/notch/NotchAnimationBehavior.qml b/modules/notch/NotchAnimationBehavior.qml
old mode 100644
new mode 100755
diff --git a/modules/notch/NotchContent.qml b/modules/notch/NotchContent.qml
old mode 100644
new mode 100755
index ca69bd49..1e46a1cc
--- a/modules/notch/NotchContent.qml
+++ b/modules/notch/NotchContent.qml
@@ -105,6 +105,11 @@ Item {
return (screenNotchOpen || hasActiveNotifications || hoverActive || barHoverActive);
}
+ // If metrics overlay is active, always show the notch
+ if (Config.notch && Config.notch.showMetrics === true) {
+ return true;
+ }
+
// If fullscreen and bar is NOT available on fullscreen, hard-hide the notch too
// This prevents barHoverActive from leaking through when the bar itself is hidden
if (activeWindowFullscreen && !(Config.bar && Config.bar.availableOnFullscreen !== undefined ? Config.bar.availableOnFullscreen : false)) {
@@ -277,7 +282,7 @@ Item {
anchors.top: root.notchPosition === "top" ? parent.top : undefined
anchors.bottom: root.notchPosition === "bottom" ? parent.bottom : undefined
- readonly property int frameOffset: (Config.bar && Config.bar.frameEnabled) ? ((Config.bar.frameThickness !== undefined) ? Config.bar.frameThickness : 6) : 0
+ readonly property int frameOffset: (Config.bar && Config.bar.frameEnabled && !root.activeWindowFullscreen) ? ((Config.bar.frameThickness !== undefined) ? Config.bar.frameThickness : 6) : 0
anchors.topMargin: (root.notchPosition === "top" ? (Config.notchTheme === "default" ? 0 : (Config.notchTheme === "island" ? 4 : 0)) : 0) + (root.notchPosition === "top" ? frameOffset : 0)
anchors.bottomMargin: (root.notchPosition === "bottom" ? (Config.notchTheme === "default" ? 0 : (Config.notchTheme === "island" ? 4 : 0)) : 0) + (root.notchPosition === "bottom" ? frameOffset : 0)
diff --git a/modules/notch/NotchNotificationView.qml b/modules/notch/NotchNotificationView.qml
old mode 100644
new mode 100755
diff --git a/modules/notch/NotchWindow.qml b/modules/notch/NotchWindow.qml
old mode 100644
new mode 100755
diff --git a/modules/notifications/AGENTS.md b/modules/notifications/AGENTS.md
old mode 100644
new mode 100755
diff --git a/modules/notifications/NotificationActionButtons.qml b/modules/notifications/NotificationActionButtons.qml
old mode 100644
new mode 100755
diff --git a/modules/notifications/NotificationAnimation.qml b/modules/notifications/NotificationAnimation.qml
old mode 100644
new mode 100755
diff --git a/modules/notifications/NotificationAppIcon.qml b/modules/notifications/NotificationAppIcon.qml
old mode 100644
new mode 100755
diff --git a/modules/notifications/NotificationDelegate.qml b/modules/notifications/NotificationDelegate.qml
old mode 100644
new mode 100755
diff --git a/modules/notifications/NotificationDismissButton.qml b/modules/notifications/NotificationDismissButton.qml
old mode 100644
new mode 100755
diff --git a/modules/notifications/NotificationGroup.qml b/modules/notifications/NotificationGroup.qml
old mode 100644
new mode 100755
diff --git a/modules/notifications/NotificationGroupExpandButton.qml b/modules/notifications/NotificationGroupExpandButton.qml
old mode 100644
new mode 100755
diff --git a/modules/notifications/NotificationListView.qml b/modules/notifications/NotificationListView.qml
old mode 100644
new mode 100755
diff --git a/modules/notifications/NotificationPopup.qml b/modules/notifications/NotificationPopup.qml
old mode 100644
new mode 100755
diff --git a/modules/notifications/notification_utils.js b/modules/notifications/notification_utils.js
old mode 100644
new mode 100755
diff --git a/modules/services/AGENTS.md b/modules/services/AGENTS.md
old mode 100644
new mode 100755
diff --git a/modules/services/Ai.qml b/modules/services/Ai.qml
old mode 100644
new mode 100755
index 5e540dc2..b8edc243
--- a/modules/services/Ai.qml
+++ b/modules/services/Ai.qml
@@ -14,8 +14,8 @@ Singleton {
// PROPERTIES
// ============================================
- property string chatDir: Quickshell.env("HOME") + "/.local/share/ambxst/chats"
- property string tmpDir: "/tmp/ambxst-ai"
+ property string chatDir: Quickshell.env("HOME") + "/.local/share/nothingless/chats"
+ property string tmpDir: "/tmp/nothingless-ai"
property list models: []
@@ -102,6 +102,7 @@ Singleton {
property GroqApiStrategy groqStrategy: GroqApiStrategy {}
property OllamaApiStrategy ollamaStrategy: OllamaApiStrategy {}
property MiniMaxApiStrategy minimaxStrategy: MiniMaxApiStrategy {}
+ property DeepSeekApiStrategy deepseekStrategy: DeepSeekApiStrategy {}
property ApiStrategy currentStrategy: openaiStrategy
@@ -114,7 +115,7 @@ Singleton {
case "groq": return groqStrategy;
case "ollama": return ollamaStrategy;
case "minimax": return minimaxStrategy;
- case "custom": return openaiStrategy; // custom endpoints use OpenAI-compatible format by default
+ case "deepseek": return deepseekStrategy;
default: return openaiStrategy;
}
}
@@ -778,6 +779,11 @@ for f in files:
fetchProcessMiniMax.running = true;
}
+ // DeepSeek β always show models, key can be added later
+ pendingFetches++;
+ fetchProcessDeepSeek.command = ["bash", "-c", "echo 'done'"];
+ fetchProcessDeepSeek.running = true;
+
if (pendingFetches === 0) {
fetchingModels = false;
}
@@ -1047,6 +1053,39 @@ for f in files:
}
}
+ property Process fetchProcessDeepSeek: Process {
+ running: false
+ stdout: SplitParser {}
+ onExited: exitCode => {
+ if (exitCode === 0) {
+ let newModels = [];
+
+ let models = [
+ { name: "DeepSeek-V3", model: "deepseek-chat", description: "Latest DeepSeek model, SOTA reasoning & coding", endpoint: "https://api.deepseek.com" },
+ { name: "DeepSeek-R1", model: "deepseek-reasoner", description: "DeepSeek reasoning model with chain-of-thought", endpoint: "https://api.deepseek.com" }
+ ];
+
+ for (let i = 0; i < models.length; i++) {
+ let item = models[i];
+ let m = aiModelFactory.createObject(root, {
+ name: item.name,
+ icon: Qt.resolvedUrl("../../../assets/aiproviders/deepseek.svg"),
+ description: item.description,
+ endpoint: item.endpoint,
+ model: item.model,
+ provider: "deepseek",
+ requires_key: true,
+ key_id: "DEEPSEEK_API_KEY"
+ });
+ if (m) newModels.push(m);
+ }
+
+ mergeModels(newModels);
+ }
+ checkFetchCompletion();
+ }
+ }
+
function checkFetchCompletion() {
pendingFetches--;
diff --git a/modules/services/AppSearch.qml b/modules/services/AppSearch.qml
old mode 100644
new mode 100755
index a8df6546..f224489b
--- a/modules/services/AppSearch.qml
+++ b/modules/services/AppSearch.qml
@@ -55,6 +55,12 @@ Singleton {
for (let i = 0; i < list.length; i++) {
const app = list[i];
+ // Match StartupWMClass β the .desktop field designed exactly for this
+ // (e.g. brave-browser.desktop has StartupWMClass=brave-browser but
+ // Exec=brave and Name=Brave, so the other matches below miss it).
+ if (app.startupClass && app.startupClass.toLowerCase() === normalizedClassName) {
+ return app.icon || "application-x-executable";
+ }
if (app.command && app.command.length > 0) {
const executableLower = app.command[0].toLowerCase();
if (executableLower === normalizedClassName) {
@@ -178,49 +184,47 @@ Singleton {
function launchApp(app) {
- // Try to find the desktop file path first (Best for gio launch)
- // Check standard properties for path
const path = app.fileName || app.path || app.filePath;
if (path && path.toString().endsWith('.desktop')) {
const escapedPath = path.toString().replace(/'/g, "'\\''");
- const p = Qt.createQmlObject('import Quickshell.Io; Process { }', root);
- // cd ~ ensures we start in home, setsid detaches from parent process group
- p.command = ["bash", "-c", "cd ~ && setsid gio launch '" + escapedPath + "' > /dev/null 2>&1 &"];
- p.running = true;
+ runInActiveWorkspace("gio launch '" + escapedPath + "'");
return;
}
- // Fallback: Construct command manually from parsed arguments
- // This handles cases where we don't have the desktop file path but have the command
if (app.command && app.command.length > 0) {
- // Filter out Field Codes like %U, %F which are placeholders for file arguments
const safeArgs = [];
for (let i = 0; i < app.command.length; i++) {
const arg = app.command[i];
- // Skip standalone field codes
if (/^%[fFuUijkc]$/.test(arg)) continue;
- // If arg contains field code but isn't just one, typically we might want to strip it
- // but let's just quote it safely. Most %codes are separate args.
-
- // Quote argument to preserve spaces and special chars
safeArgs.push("'" + arg.replace(/'/g, "'\\''") + "'");
}
if (safeArgs.length > 0) {
const cmdString = safeArgs.join(" ");
+ // Wrap Terminal=true entries via xdg-terminal-exec (freedesktop default-terminal-spec).
+ // Users must have xdg-terminal-exec installed and ~/.config/xdg-terminals.list configured.
+ const wrapped = app.runInTerminal
+ ? "xdg-terminal-exec " + cmdString
+ : cmdString;
const p = Qt.createQmlObject('import Quickshell.Io; Process { }', root);
// Run in background, detached, from HOME
- p.command = ["bash", "-c", "cd ~ && setsid " + cmdString + " > /dev/null 2>&1 &"];
+ p.command = ["bash", "-c", "cd ~ && setsid " + wrapped + " > /dev/null 2>&1 &"];
p.running = true;
return;
}
}
- // Final fallback (Will use project dir as CWD unfortunately)
app.execute();
}
+ function runInActiveWorkspace(command) {
+ const p = Qt.createQmlObject('import Quickshell.Io; Process { }', root);
+ p.command = ["bash", "-c", "cd ~ && env -u HL_INITIAL_WORKSPACE_TOKEN setsid " + command + " < /dev/null > /dev/null 2>&1 &"];
+ p.onExited.connect(() => p.destroy());
+ p.running = true;
+ }
+
function getAllApps() {
if (allAppsCache) return allAppsCache;
diff --git a/modules/services/Audio.qml b/modules/services/Audio.qml
old mode 100644
new mode 100755
index 05571624..7e291c26
--- a/modules/services/Audio.qml
+++ b/modules/services/Audio.qml
@@ -49,8 +49,11 @@ Singleton {
}
// Volume signals for OSD
+ // FIX: Guard enabled to prevent segfault in Qt 6.11 when PipeWire nodes
+ // get destroyed/recreated while QML is finalizing Connections during incubation
Connections {
target: root.sink?.audio ?? null
+ enabled: root.sink?.audio !== null
ignoreUnknownSignals: true
function onVolumeChanged() {
if (root.sink?.ready) {
@@ -64,8 +67,11 @@ Singleton {
}
}
+ // FIX: Guard enabled to prevent segfault in Qt 6.11 when PipeWire nodes
+ // get destroyed/recreated while QML is finalizing Connections during incubation
Connections {
target: root.source?.audio ?? null
+ enabled: root.source?.audio !== null
ignoreUnknownSignals: true
function onVolumeChanged() {
if (root.source?.ready) {
diff --git a/modules/services/AxctlService.qml b/modules/services/AxctlService.qml
old mode 100644
new mode 100755
index bade7b38..5aaecc1e
--- a/modules/services/AxctlService.qml
+++ b/modules/services/AxctlService.qml
@@ -28,7 +28,7 @@ Singleton {
signal rawEvent(var event)
// Config path for axctl daemon
- property string configPath: (Quickshell.env("XDG_DATA_HOME") || (Quickshell.env("HOME") + "/.local/share")) + "/ambxst/axctl.toml"
+ property string configPath: (Quickshell.env("XDG_DATA_HOME") || (Quickshell.env("HOME") + "/.local/share")) + "/nothingless/axctl.toml"
function dispatch(command) {
if (!command) return;
@@ -59,11 +59,14 @@ Singleton {
} else if (action === "togglespecialworkspace") {
cmdArgs = ["workspace", "toggle-special"];
if (rawArgs) cmdArgs.push(rawArgs);
+ } else if (action === "monitor") {
+ // Monitor commands go directly to hyprctl dispatch
+ cmdArgs = ["system", "execute", "hyprctl dispatch " + command];
} else {
cmdArgs = ["system", "execute", command];
}
- let finalCommand = ["axctl"].concat(cmdArgs.filter(x => x !== "" && x !== undefined));
+ let finalCommand = ["axctl", "-c", root.configPath].concat(cmdArgs.filter(x => x !== "" && x !== undefined));
let proc = Qt.createQmlObject('import Quickshell.Io; Process {}', root);
proc.command = finalCommand;
@@ -141,6 +144,9 @@ Singleton {
height: mon.height,
refreshRate: mon.refresh_rate,
scale: mon.scale,
+ x: mon.metadata ? parseInt(mon.metadata.x) || 0 : 0,
+ y: mon.metadata ? parseInt(mon.metadata.y) || 0 : 0,
+ transform: mon.metadata ? parseInt(mon.metadata.transform) || 0 : 0,
activeWorkspace: { id: parseInt(mon.metadata ? mon.metadata.active_workspace : 0) || 0, name: mon.metadata ? mon.metadata.active_workspace : "" }
}));
root.monitors.values = mappedMonitors;
@@ -152,7 +158,7 @@ Singleton {
}
property Process ensureConfigDir: Process {
- command: ["mkdir", "-p", (Quickshell.env("XDG_DATA_HOME") || (Quickshell.env("HOME") + "/.local/share")) + "/ambxst"]
+ command: ["mkdir", "-p", (Quickshell.env("XDG_DATA_HOME") || (Quickshell.env("HOME") + "/.local/share")) + "/nothingless"]
running: true
}
diff --git a/modules/services/Battery.qml b/modules/services/Battery.qml
old mode 100644
new mode 100755
index adf1924d..b2bfd734
--- a/modules/services/Battery.qml
+++ b/modules/services/Battery.qml
@@ -1,7 +1,9 @@
pragma Singleton
import QtQuick
+import Quickshell.Io
import Quickshell
+import Quickshell.Services.Notifications
import Quickshell.Services.UPower
import qs.modules.theme
@@ -15,6 +17,11 @@ Singleton {
readonly property bool isCharging: available && primaryDevice.state === UPowerDevice.Charging
readonly property bool isPluggedIn: available && (primaryDevice.state === UPowerDevice.Charging || primaryDevice.state === UPowerDevice.FullyCharged)
readonly property int chargeState: available ? primaryDevice.state : UPowerDevice.Unknown
+ property int lastBatteryAlertThreshold: 0
+
+ property Process powerSaveProcess: Process {
+ running: false
+ }
// Add some helpful descriptive properties if needed
readonly property string timeToEmpty: available && primaryDevice.timeToEmpty > 0 ? formatTime(primaryDevice.timeToEmpty) : ""
@@ -38,4 +45,109 @@ Singleton {
if (pct > 5) return Icons.batteryLow;
return Icons.batteryEmpty;
}
+
+ function evaluateBatteryAlert() {
+ if (!available) return;
+
+ const cfg = Config.system.batteryNotifications;
+ if (!cfg || !cfg.enabled) return;
+
+ const roundedPercentage = Math.floor(percentage);
+
+ // ββ Charge limit suggestion ββ
+ if (cfg.chargeLimitEnabled && cfg.chargeLimit > 0 && isPluggedIn) {
+ if (roundedPercentage >= cfg.chargeLimit && lastBatteryAlertThreshold !== cfg.chargeLimit) {
+ sendChargeLimitAlert(roundedPercentage, cfg.chargeLimit);
+ lastBatteryAlertThreshold = cfg.chargeLimit;
+ }
+ return; // Don't fire low battery alerts while charging
+ }
+
+ if (isPluggedIn) {
+ lastBatteryAlertThreshold = 0;
+ return;
+ }
+
+ // ββ Auto power-save ββ
+ if (cfg.autoPowerSave && roundedPercentage <= cfg.powerSaveThreshold) {
+ enablePowerSave();
+ }
+
+ // ββ Low battery alerts ββ
+ const threshold = roundedPercentage <= cfg.criticalThreshold ? cfg.criticalThreshold :
+ (roundedPercentage <= cfg.lowThreshold ? cfg.lowThreshold : 0);
+ if (threshold === 0) {
+ lastBatteryAlertThreshold = 0;
+ return;
+ }
+
+ if (threshold === lastBatteryAlertThreshold) {
+ return;
+ }
+
+ sendBatteryAlert(threshold, roundedPercentage);
+ lastBatteryAlertThreshold = threshold;
+ }
+
+ function enablePowerSave() {
+ if (Quickshell.env("XDG_CURRENT_DESKTOP")?.toLowerCase().includes("hyprland")) {
+ // Lower refresh rate, dim screen, reduce brightness
+ powerSaveProcess.command = ["bash", "-c", "hyprctl keyword misc:vfr 1; hyprctl keyword decoration:dim_inactive true; hyprctl keyword decoration:dim_strength 0.5; brightnessctl set 30% 2>/dev/null || true"];
+ powerSaveProcess.running = true;
+ }
+ }
+
+ function sendChargeLimitAlert(roundedPercentage, limit) {
+ Notifications.notifyInternal({
+ "appName": "Battery",
+ "summary": "Charge limit reached",
+ "body": "Battery at " + roundedPercentage + "%. Unplug to preserve battery health (limit: " + limit + "%).",
+ "urgency": NotificationUrgency.Normal,
+ "historyPriority": 80,
+ "replaceKey": "battery-charge-limit",
+ "expireTimeout": 15000
+ });
+ }
+
+ function sendBatteryAlert(threshold, roundedPercentage) {
+ const isCritical = threshold <= 10;
+ Notifications.notifyInternal({
+ "appName": "Battery",
+ "summary": isCritical ? "Critical battery" : "Low battery",
+ "body": "Battery is at " + roundedPercentage + "%." + (isCritical ? " Connect your charger or enable power saver." : " Enable power saver to reduce consumption."),
+ "urgency": NotificationUrgency.Critical,
+ "historyPriority": 100,
+ "replaceKey": "battery-low-alert",
+ "expireTimeout": 10000,
+ "actions": [{
+ "identifier": "enable-power-saver",
+ "text": "Power saver"
+ }, {
+ "identifier": "dismiss",
+ "text": "Ignore"
+ }],
+ "actionHandlers": {
+ "enable-power-saver": function () {
+ PowerProfile.setProfile("power-saver");
+ },
+ "dismiss": function (id) {
+ Notifications.discardNotification(id);
+ }
+ }
+ });
+ }
+
+ Connections {
+ target: root
+
+ function onPercentageChanged() {
+ root.evaluateBatteryAlert();
+ }
+
+ function onIsPluggedInChanged() {
+ root.evaluateBatteryAlert();
+ }
+ }
+
+ Component.onCompleted: Qt.callLater(root.evaluateBatteryAlert)
}
diff --git a/modules/services/BatteryAlertService.qml b/modules/services/BatteryAlertService.qml
new file mode 100644
index 00000000..34c13d27
--- /dev/null
+++ b/modules/services/BatteryAlertService.qml
@@ -0,0 +1,158 @@
+pragma Singleton
+
+import QtQuick
+import QtMultimedia
+import Quickshell
+import Quickshell.Io
+import qs.config
+
+Singleton {
+ id: root
+
+ readonly property var settings: Config.system && Config.system.batteryNotifications ? Config.system.batteryNotifications : null
+ readonly property bool enabled: settings && settings.enabled !== undefined ? settings.enabled : true
+ readonly property int lowThreshold: settings && settings.lowThreshold !== undefined ? settings.lowThreshold : 20
+ readonly property int criticalThreshold: settings && settings.criticalThreshold !== undefined ? settings.criticalThreshold : 10
+
+ property bool lowNotified: false
+ property bool criticalNotified: false
+
+ function resetNotificationState() {
+ lowNotified = false;
+ criticalNotified = false;
+ }
+
+ function sendNotification(summary, body, urgency) {
+ notificationProcess.running = false;
+ notificationProcess.command = [
+ "notify-send",
+ "-u", urgency,
+ "-i", "battery-caution",
+ summary,
+ body
+ ];
+ notificationProcess.running = true;
+ warningSound.play();
+ }
+
+ function checkBatteryState() {
+ if (!enabled || !Battery.available || SuspendManager.isSuspending) {
+ return;
+ }
+
+ if (Battery.isPluggedIn || Battery.isCharging) {
+ resetNotificationState();
+ return;
+ }
+
+ const low = Math.max(lowThreshold, criticalThreshold);
+ const critical = Math.min(lowThreshold, criticalThreshold);
+ const percentage = Math.round(Battery.percentage);
+ const timeRemaining = Battery.timeToEmpty !== "" ? ` About ${Battery.timeToEmpty} remaining.` : "";
+
+ if (percentage > low) {
+ resetNotificationState();
+ return;
+ }
+
+ if (percentage <= critical) {
+ if (!criticalNotified) {
+ sendNotification(
+ `Battery critical (${percentage}%)`,
+ `Plug in your charger now.${timeRemaining}`,
+ "critical"
+ );
+ criticalNotified = true;
+ }
+ lowNotified = true;
+ return;
+ }
+
+ if (!lowNotified) {
+ sendNotification(
+ `Battery low (${percentage}%)`,
+ `Battery is getting low.${timeRemaining}`,
+ "normal"
+ );
+ lowNotified = true;
+ }
+ }
+
+ Process {
+ id: notificationProcess
+ running: false
+ command: []
+ }
+
+ SoundEffect {
+ id: warningSound
+ source: Quickshell.shellDir + "/assets/sound/polite-warning-tone.wav"
+ volume: 1.0
+ }
+
+ Connections {
+ target: Battery
+ function onPercentageChanged() {
+ root.checkBatteryState();
+ }
+ function onIsPluggedInChanged() {
+ root.checkBatteryState();
+ }
+ function onIsChargingChanged() {
+ root.checkBatteryState();
+ }
+ function onAvailableChanged() {
+ root.checkBatteryState();
+ }
+ }
+
+ Connections {
+ target: root.settings
+ ignoreUnknownSignals: true
+ function onEnabledChanged() {
+ if (!root.enabled) {
+ root.resetNotificationState();
+ } else {
+ root.checkBatteryState();
+ }
+ }
+ function onLowThresholdChanged() {
+ root.resetNotificationState();
+ root.checkBatteryState();
+ }
+ function onCriticalThresholdChanged() {
+ root.resetNotificationState();
+ root.checkBatteryState();
+ }
+ }
+
+ Connections {
+ target: SuspendManager
+ function onWakingUp() {
+ wakeCheckTimer.restart();
+ }
+ }
+
+ Timer {
+ id: startupCheckTimer
+ interval: 5000
+ running: true
+ repeat: false
+ onTriggered: root.checkBatteryState()
+ }
+
+ Timer {
+ id: wakeCheckTimer
+ interval: 3000
+ repeat: false
+ onTriggered: root.checkBatteryState()
+ }
+
+ Timer {
+ id: pollTimer
+ interval: 60000
+ running: true
+ repeat: true
+ onTriggered: root.checkBatteryState()
+ }
+}
diff --git a/modules/services/BluetoothDevice.qml b/modules/services/BluetoothDevice.qml
old mode 100644
new mode 100755
index 8c8055b8..051d0040
--- a/modules/services/BluetoothDevice.qml
+++ b/modules/services/BluetoothDevice.qml
@@ -17,21 +17,20 @@ QtObject {
signal infoUpdated()
- // Connect (auto-trust new devices)
+ readonly property string helperPath: BluetoothService.helperPath
+
function connect() {
connecting = true;
let p;
if (!trusted) {
- // Trust first, then connect
- p = BluetoothService.runAsync(["bluetoothctl", "trust", address]).then(() => {
- return BluetoothService.runAsync(["bluetoothctl", "connect", address]);
+ p = BluetoothService.runAsync(["python3", helperPath, "trust", address]).then(() => {
+ return BluetoothService.runAsync(["python3", helperPath, "connect", address]);
});
} else {
p = BluetoothService.connectDevice(address);
}
-
return p.catch(e => {
- console.error(`Failed to connect to ${address}: ${e}`);
+ console.warn("BluetoothDevice: connect failed for " + address + ":", e);
}).finally(() => {
connecting = false;
updateInfo();
@@ -39,47 +38,28 @@ QtObject {
}
function updateInfo() {
- return BluetoothService.runAsync(["bluetoothctl", "info", address]).then(text => {
+ return BluetoothService.runAsync(["python3", helperPath, "info", address]).then(text => {
Qt.callLater(() => {
- const lines = text.split("\n");
- for (const line of lines) {
- const trimmed = line.trim();
- if (trimmed.startsWith("Paired:")) {
- root.paired = trimmed.includes("yes");
- } else if (trimmed.startsWith("Connected:")) {
- root.connected = trimmed.includes("yes");
- if (root.connected) root.connecting = false;
- } else if (trimmed.startsWith("Trusted:")) {
- root.trusted = trimmed.includes("yes");
- } else if (trimmed.startsWith("Icon:")) {
- root.icon = trimmed.split(":")[1]?.trim() || "bluetooth";
- } else if (trimmed.startsWith("Battery Percentage:")) {
- const match = trimmed.match(/\((\d+)\)/);
- if (match) {
- root.battery = parseInt(match[1]) || -1;
- }
- }
+ try {
+ var info = JSON.parse(text);
+ root.name = info.name || info.alias || root.name;
+ root.paired = info.paired || false;
+ root.connected = info.connected || false;
+ root.trusted = info.trusted || false;
+ root.icon = info.icon || "bluetooth";
+ if (root.connected) root.connecting = false;
+ root.infoUpdated();
+ } catch (e) {
+ console.warn("BluetoothDevice: info parse failed for " + address);
}
- root.infoUpdated();
});
}).catch(e => {
- console.error(`Failed to get info for ${address}: ${e}`);
+ console.warn("BluetoothDevice: info failed for " + address + ":", e);
});
}
- function disconnect() {
- BluetoothService.disconnectDevice(address);
- }
-
- function pair() {
- BluetoothService.pairDevice(address);
- }
-
- function trust() {
- BluetoothService.trustDevice(address);
- }
-
- function forget() {
- BluetoothService.removeDevice(address);
- }
+ function disconnect() { BluetoothService.disconnectDevice(address); }
+ function pair() { BluetoothService.pairDevice(address); }
+ function trust() { BluetoothService.trustDevice(address); }
+ function forget() { BluetoothService.removeDevice(address); }
}
diff --git a/modules/services/BluetoothService.qml b/modules/services/BluetoothService.qml
old mode 100644
new mode 100755
index de25678a..3f58b371
--- a/modules/services/BluetoothService.qml
+++ b/modules/services/BluetoothService.qml
@@ -146,11 +146,14 @@ Singleton {
});
}
+ // Helper script path β uses project's scripts directory
+ readonly property string helperPath: Quickshell.shellDir + "/scripts/bluetooth_helper.py"
+
// Control functions
function setEnabled(value: bool): void {
if (SuspendManager.isSuspending) return;
isUpdating = true;
- runAsync(["bluetoothctl", "power", value ? "on" : "off"]).then(() => {
+ runAsync(["python3", root.helperPath, "power", value ? "on" : "off"]).then(() => {
updateStatus();
if (value) updateDevices();
isUpdating = false;
@@ -166,24 +169,73 @@ Singleton {
function startDiscovery(): void {
if (enabled && !SuspendManager.isSuspending) {
discovering = true;
- runAsync(["bluetoothctl", "scan", "on"]).then(() => {
- scanTimer.restart();
- }).catch(e => {
- discovering = false;
- });
+ // Use interactive scan that captures [NEW] Device events
+ scanProcess.running = true;
+ scanTimer.restart();
}
}
function stopDiscovery(): void {
discovering = false;
- runAsync(["bluetoothctl", "scan", "off"]).then(() => {
- scanTimer.stop();
+ if (scanProcess.running) {
+ scanProcess.running = false;
+ }
+ scanTimer.stop();
+ runAsync(["python3", root.helperPath, "scan", "off"]).then(() => {
+ Qt.callLater(() => root.updateDevices());
}).catch(e => {});
}
+ // Dedicated scan process with interactive bluetoothctl
+ property Process scanProcess: Process {
+ command: ["python3", root.helperPath, "scan", "find", "12"]
+ running: false
+ property string buffer: ""
+ stdout: SplitParser {
+ onRead: data => { scanProcess.buffer += data; }
+ }
+ onExited: exitCode => {
+ root.discovering = false;
+ var text = scanProcess.buffer.trim();
+ scanProcess.buffer = "";
+ if (exitCode === 0 && text) {
+ // Parse discovered devices from scan
+ try {
+ var devices = JSON.parse(text);
+ if (Array.isArray(devices) && devices.length > 0) {
+ // Merge into existing device list
+ const rDevices = root.devices;
+ for (var i = 0; i < devices.length; i++) {
+ var d = devices[i];
+ var existingArr = Array.from(rDevices);
+ var existing = existingArr.find(function(ex) { return ex.address === d.address; });
+ if (existing) {
+ existing.name = d.name || existing.name;
+ existing.connected = d.connected || false;
+ } else {
+ var newDev = deviceComp.createObject(root, {
+ address: d.address,
+ name: d.name || d.alias || "Unknown",
+ paired: d.paired || false,
+ connected: d.connected || false,
+ trusted: d.trusted || false,
+ icon: d.icon || "bluetooth"
+ });
+ rDevices.push(newDev);
+ }
+ }
+ root.updateFriendlyList();
+ }
+ } catch (e) {
+ console.warn("BluetoothService: scan parse failed:", e);
+ }
+ }
+ }
+ }
+
function connectDevice(address: string): void {
isUpdating = true;
- runAsync(["bluetoothctl", "connect", address]).then(() => {
+ runAsync(["python3", root.helperPath, "connect", address]).then(() => {
updateDevices();
isUpdating = false;
}).catch(e => {
@@ -193,7 +245,7 @@ Singleton {
function disconnectDevice(address: string): void {
isUpdating = true;
- runAsync(["bluetoothctl", "disconnect", address]).then(() => {
+ runAsync(["python3", root.helperPath, "disconnect", address]).then(() => {
updateDevices();
isUpdating = false;
}).catch(e => {
@@ -203,7 +255,7 @@ Singleton {
function pairDevice(address: string): void {
isUpdating = true;
- runAsync(["bluetoothctl", "pair", address]).then(() => {
+ runAsync(["python3", root.helperPath, "pair", address]).then(() => {
updateDevices();
isUpdating = false;
}).catch(e => {
@@ -212,12 +264,12 @@ Singleton {
}
function trustDevice(address: string): void {
- runAsync(["bluetoothctl", "trust", address]).catch(e => {});
+ runAsync(["python3", root.helperPath, "trust", address]).catch(e => {});
}
function removeDevice(address: string): void {
isUpdating = true;
- runAsync(["bluetoothctl", "remove", address]).then(() => {
+ runAsync(["python3", root.helperPath, "remove", address]).then(() => {
updateDevices();
isUpdating = false;
}).catch(e => {
@@ -263,35 +315,60 @@ Singleton {
// Processes
Process {
id: checkPowerProcess
- command: ["bash", "-c", "bluetoothctl show | grep 'Powered:' | awk '{print $2}'"]
+ command: ["python3", root.helperPath, "power", "status"]
running: false
+ property string buffer: ""
stdout: SplitParser {
- onRead: (data) => {
- const output = data ? data.trim() : "";
- root.enabled = output === "yes";
-
- if (root.enabled) {
- checkConnectedProcess.running = true;
- } else {
- root.connected = false;
- root.connectedDevices = 0;
- root.discovering = false;
- root.isUpdating = false;
+ onRead: data => { checkPowerProcess.buffer += data; }
+ }
+ onExited: exitCode => {
+ var text = checkPowerProcess.buffer.trim();
+ checkPowerProcess.buffer = "";
+ if (exitCode === 0 && text) {
+ try {
+ var result = JSON.parse(text);
+ root.enabled = result.powered === true;
+ } catch (e) {
+ console.warn("BluetoothService: power parse failed:", e);
+ root.enabled = false;
}
+ } else {
+ root.enabled = false;
+ }
+ if (root.enabled) {
+ checkConnectedProcess.running = true;
+ } else {
+ root.connected = false;
+ root.connectedDevices = 0;
+ root.discovering = false;
+ root.isUpdating = false;
}
}
}
Process {
id: checkConnectedProcess
- command: ["bash", "-c", "bluetoothctl devices Connected | wc -l"]
+ command: ["python3", root.helperPath, "devices"]
running: false
+ property string buffer: ""
stdout: SplitParser {
- onRead: (data) => {
- const output = data ? data.trim() : "0";
- root.connectedDevices = parseInt(output) || 0;
- root.connected = root.connectedDevices > 0;
- root.isUpdating = false;
+ onRead: data => { checkConnectedProcess.buffer += data; }
+ }
+ onExited: exitCode => {
+ root.isUpdating = false;
+ var text = checkConnectedProcess.buffer.trim();
+ checkConnectedProcess.buffer = "";
+ if (exitCode !== 0 || !text) return;
+ try {
+ var devices = JSON.parse(text);
+ var connected = 0;
+ for (var i = 0; i < devices.length; i++) {
+ if (devices[i].connected) connected++;
+ }
+ root.connectedDevices = connected;
+ root.connected = connected > 0;
+ } catch (e) {
+ console.warn("BluetoothService: connected parse failed:", e);
}
}
}
@@ -302,68 +379,68 @@ Singleton {
Process {
id: getDevicesProcess
- command: ["bash", "-c", "bluetoothctl devices"]
+ command: ["python3", root.helperPath, "devices"]
running: false
property string buffer: ""
- environment: ({
- LANG: "C.UTF-8",
- LC_ALL: "C.UTF-8"
- })
stdout: SplitParser {
- onRead: data => {
- getDevicesProcess.buffer += data + "\n";
- }
+ onRead: data => { getDevicesProcess.buffer += data; }
}
onExited: (exitCode, exitStatus) => {
- const text = getDevicesProcess.buffer;
- getDevicesProcess.buffer = "";
-
+ if (exitCode !== 0) {
+ root.updateFriendlyList();
+ return;
+ }
Qt.callLater(() => {
- const deviceLines = text.trim().split("\n").filter(l => l.startsWith("Device "));
- const deviceDataList = [];
- for (let i = 0; i < deviceLines.length; i++) {
- const line = deviceLines[i];
- const parts = line.split(" ");
- if (parts.length < 2) continue;
- deviceDataList.push({
- address: parts[1],
- name: parts.slice(2).join(" ") || "Unknown"
- });
+ var deviceDataList = [];
+ try {
+ var jsonText = getDevicesProcess.buffer.trim();
+ getDevicesProcess.buffer = "";
+ if (jsonText) {
+ deviceDataList = JSON.parse(jsonText);
+ if (!Array.isArray(deviceDataList)) deviceDataList = [];
+ }
+ } catch (e) {
+ console.warn("BluetoothService: devices parse failed:", e);
+ getDevicesProcess.buffer = "";
}
const rDevices = root.devices;
- // 1. Remove gone devices
+ // Remove gone devices
for (let i = rDevices.length - 1; i >= 0; i--) {
const rd = rDevices[i];
- if (!deviceDataList.find(d => d.address === rd.address)) {
+ if (!deviceDataList.some(function(d) { return d.address === rd.address; })) {
rDevices.splice(i, 1);
rd.destroy();
}
}
- // 2. Add or update devices
+ // Add or update devices (with full info from JSON)
for (let i = 0; i < deviceDataList.length; i++) {
const data = deviceDataList[i];
- const existing = rDevices.find(d => d.address === data.address);
+ const existingArr = Array.from(rDevices);
+ const existing = existingArr.find(function(d) { return d.address === data.address; });
if (existing) {
- if (existing.name !== data.name) {
- existing.name = data.name;
- }
- root.queueInfoUpdate(existing);
+ existing.name = data.name || data.alias || existing.name;
+ existing.paired = data.paired || false;
+ existing.connected = data.connected || false;
+ existing.trusted = data.trusted || false;
+ existing.battery = data.battery || -1;
} else {
const newDevice = deviceComp.createObject(root, {
address: data.address,
- name: data.name
+ name: data.name || data.alias || "Unknown",
+ paired: data.paired || false,
+ connected: data.connected || false,
+ trusted: data.trusted || false,
+ icon: data.icon || "bluetooth",
+ battery: data.battery || -1
});
rDevices.push(newDevice);
- root.queueInfoUpdate(newDevice);
}
}
- if (deviceDataList.length === 0) {
- root.updateFriendlyList();
- }
+ root.updateFriendlyList();
});
}
}
diff --git a/modules/services/Brightness.qml b/modules/services/Brightness.qml
old mode 100644
new mode 100755
diff --git a/modules/services/CaffeineService.qml b/modules/services/CaffeineService.qml
old mode 100644
new mode 100755
diff --git a/modules/services/ClipboardService.qml b/modules/services/ClipboardService.qml
old mode 100644
new mode 100755
diff --git a/modules/services/CompositorConfig.qml b/modules/services/CompositorConfig.qml
old mode 100644
new mode 100755
index 86c0a9be..8dcddaa6
--- a/modules/services/CompositorConfig.qml
+++ b/modules/services/CompositorConfig.qml
@@ -199,7 +199,140 @@ QtObject {
batchCommand += ` ; keyword decoration:blur:popups_ignorealpha ${Config.compositor.blurPopupsIgnorealpha}`;
batchCommand += ` ; keyword decoration:blur:input_methods ${Config.compositor.blurInputMethods}`;
batchCommand += ` ; keyword decoration:blur:input_methods_ignorealpha ${Config.compositor.blurInputMethodsIgnorealpha}`;
- batchCommand += ` ; keyword bezier myBezier,0.4,0.0,0.2,1.0`;
+
+ // Opacity
+ batchCommand += ` ; keyword decoration:active_opacity ${Config.compositor.activeOpacity.toFixed(2)}`;
+ batchCommand += ` ; keyword decoration:inactive_opacity ${Config.compositor.inactiveOpacity.toFixed(2)}`;
+ batchCommand += ` ; keyword decoration:fullscreen_opacity ${Config.compositor.fullscreenOpacity.toFixed(2)}`;
+
+ // Dim
+ batchCommand += ` ; keyword decoration:dim_inactive ${Config.compositor.dimInactive}`;
+ batchCommand += ` ; keyword decoration:dim_strength ${Config.compositor.dimStrength.toFixed(2)}`;
+ batchCommand += ` ; keyword decoration:dim_around ${Config.compositor.dimAround.toFixed(2)}`;
+ batchCommand += ` ; keyword decoration:dim_special ${Config.compositor.dimSpecial.toFixed(2)}`;
+
+ // Rounding power
+ batchCommand += ` ; keyword decoration:rounding_power ${Config.compositor.roundingPower.toFixed(1)}`;
+
+ // General extras
+ batchCommand += ` ; keyword general:allow_tearing ${Config.compositor.allowTearing}`;
+ batchCommand += ` ; keyword general:resize_on_border ${Config.compositor.resizeOnBorder}`;
+ batchCommand += ` ; keyword general:extend_border_grab_area ${Config.compositor.extendBorderGrabArea}`;
+ batchCommand += ` ; keyword general:hover_icon_on_border ${Config.compositor.hoverIconOnBorder}`;
+
+ // Snap
+ batchCommand += ` ; keyword general:snap:enabled ${Config.compositor.snapEnabled}`;
+ batchCommand += ` ; keyword general:snap:window_gap ${Config.compositor.snapWindowGap}`;
+ batchCommand += ` ; keyword general:snap:monitor_gap ${Config.compositor.snapMonitorGap}`;
+ batchCommand += ` ; keyword general:snap:border_overlap ${Config.compositor.snapBorderOverlap}`;
+ batchCommand += ` ; keyword general:snap:respect_gaps ${Config.compositor.snapRespectGaps}`;
+
+ // Animations
+ batchCommand += ` ; keyword animations:enabled ${Config.compositor.animationsEnabled}`;
+
+ // Input: Keyboard
+ batchCommand += ` ; keyword input:kb_layout ${Config.compositor.kbLayout}`;
+ if (Config.compositor.kbVariant) batchCommand += ` ; keyword input:kb_variant ${Config.compositor.kbVariant}`;
+ if (Config.compositor.kbOptions) batchCommand += ` ; keyword input:kb_options ${Config.compositor.kbOptions}`;
+ batchCommand += ` ; keyword input:numlock_by_default ${Config.compositor.numlockByDefault}`;
+ batchCommand += ` ; keyword input:repeat_rate ${Config.compositor.repeatRate}`;
+ batchCommand += ` ; keyword input:repeat_delay ${Config.compositor.repeatDelay}`;
+
+ // Input: Mouse
+ batchCommand += ` ; keyword input:sensitivity ${Config.compositor.mouseSensitivity.toFixed(2)}`;
+ if (Config.compositor.mouseAccelProfile) batchCommand += ` ; keyword input:accel_profile ${Config.compositor.mouseAccelProfile}`;
+ batchCommand += ` ; keyword input:follow_mouse ${Config.compositor.followMouse}`;
+ batchCommand += ` ; keyword input:natural_scroll ${Config.compositor.mouseNaturalScroll}`;
+ batchCommand += ` ; keyword input:scroll_factor ${Config.compositor.mouseScrollFactor.toFixed(1)}`;
+ batchCommand += ` ; keyword input:left_handed ${Config.compositor.mouseLeftHanded}`;
+ batchCommand += ` ; keyword input:mouse_refocus ${Config.compositor.mouseRefocus}`;
+ batchCommand += ` ; keyword input:float_switch_override_focus ${Config.compositor.floatSwitchOverrideFocus}`;
+
+ // Input: Touchpad
+ batchCommand += ` ; keyword input:touchpad:disable_while_typing ${Config.compositor.touchpadDisableWhileTyping}`;
+ batchCommand += ` ; keyword input:touchpad:natural_scroll ${Config.compositor.touchpadNaturalScroll}`;
+ batchCommand += ` ; keyword input:touchpad:tap_to_click ${Config.compositor.touchpadTapToClick}`;
+ batchCommand += ` ; keyword input:touchpad:clickfinger_behavior ${Config.compositor.touchpadClickfingerBehavior}`;
+ if (Config.compositor.touchpadTapButtonMap) batchCommand += ` ; keyword input:touchpad:tap_button_map ${Config.compositor.touchpadTapButtonMap}`;
+ batchCommand += ` ; keyword input:touchpad:middle_button_emulation ${Config.compositor.touchpadMiddleButtonEmulation}`;
+ batchCommand += ` ; keyword input:touchpad:drag_lock ${Config.compositor.touchpadDragLock}`;
+ batchCommand += ` ; keyword input:touchpad:scroll_factor ${Config.compositor.touchpadScrollFactor.toFixed(1)}`;
+
+ // Cursor
+ batchCommand += ` ; keyword cursor:no_hardware_cursors ${Config.compositor.noHardwareCursors}`;
+ batchCommand += ` ; keyword cursor:enable_hyprcursor ${Config.compositor.enableHyprcursor}`;
+ batchCommand += ` ; keyword cursor:no_warps ${Config.compositor.noWarps}`;
+ batchCommand += ` ; keyword cursor:persistent_warps ${Config.compositor.persistentWarps}`;
+ batchCommand += ` ; keyword cursor:warp_on_change_workspace ${Config.compositor.warpOnChangeWorkspace}`;
+ batchCommand += ` ; keyword cursor:zoom_factor ${Config.compositor.cursorZoomFactor.toFixed(1)}`;
+ batchCommand += ` ; keyword cursor:inactive_timeout ${Config.compositor.cursorInactiveTimeout}`;
+ batchCommand += ` ; keyword cursor:hide_on_key_press ${Config.compositor.cursorHideOnKeyPress}`;
+ batchCommand += ` ; keyword cursor:hide_on_touch ${Config.compositor.cursorHideOnTouch}`;
+ batchCommand += ` ; keyword cursor:hide_on_tablet ${Config.compositor.cursorHideOnTablet}`;
+
+ // Gestures
+ batchCommand += ` ; keyword gestures:workspace_swipe_create_new ${Config.compositor.workspaceSwipeCreateNew}`;
+ batchCommand += ` ; keyword gestures:workspace_swipe_forever ${Config.compositor.workspaceSwipeForever}`;
+ batchCommand += ` ; keyword gestures:workspace_swipe_cancel_ratio ${Config.compositor.workspaceSwipeCancelRatio.toFixed(2)}`;
+ batchCommand += ` ; keyword gestures:workspace_swipe_min_speed_to_force ${Config.compositor.workspaceSwipeMinSpeedToForce}`;
+ batchCommand += ` ; keyword gestures:workspace_swipe_direction_lock ${Config.compositor.workspaceSwipeDirectionLock}`;
+ batchCommand += ` ; keyword gestures:workspace_swipe_use_r ${Config.compositor.workspaceSwipeUseR}`;
+ batchCommand += ` ; keyword gestures:workspace_swipe_distance ${Config.compositor.workspaceSwipeDistance}`;
+ batchCommand += ` ; keyword gestures:workspace_swipe_invert ${Config.compositor.workspaceSwipeInvert}`;
+ batchCommand += ` ; keyword gestures:workspace_swipe_touch ${Config.compositor.workspaceSwipeTouch}`;
+ batchCommand += ` ; keyword gestures:workspace_swipe_touch_invert ${Config.compositor.workspaceSwipeTouchInvert}`;
+
+ // Dwindle
+ batchCommand += ` ; keyword dwindle:preserve_split ${Config.compositor.dwindlePreserveSplit}`;
+ batchCommand += ` ; keyword dwindle:pseudotile ${Config.compositor.dwindlePseudotile}`;
+ batchCommand += ` ; keyword dwindle:force_split ${Config.compositor.dwindleForceSplit}`;
+ batchCommand += ` ; keyword dwindle:smart_split ${Config.compositor.dwindleSmartSplit}`;
+ batchCommand += ` ; keyword dwindle:default_split_ratio ${Config.compositor.dwindleDefaultSplitRatio.toFixed(2)}`;
+ batchCommand += ` ; keyword dwindle:split_width_multiplier ${Config.compositor.dwindleSplitWidthMultiplier.toFixed(1)}`;
+ batchCommand += ` ; keyword dwindle:permanent_direction_override ${Config.compositor.dwindlePermanentDirectionOverride}`;
+ batchCommand += ` ; keyword dwindle:use_active_for_splits ${Config.compositor.dwindleUseActiveForSplits}`;
+ batchCommand += ` ; keyword dwindle:smart_resizing ${Config.compositor.dwindleSmartResizing}`;
+ batchCommand += ` ; keyword dwindle:special_scale_factor ${Config.compositor.dwindleSpecialScaleFactor.toFixed(2)}`;
+
+ // Master
+ batchCommand += ` ; keyword master:orientation ${Config.compositor.masterOrientation}`;
+ batchCommand += ` ; keyword master:mfact ${Config.compositor.masterMfact.toFixed(2)}`;
+ batchCommand += ` ; keyword master:new_status ${Config.compositor.masterNewStatus}`;
+ batchCommand += ` ; keyword master:new_on_top ${Config.compositor.masterNewOnTop}`;
+ batchCommand += ` ; keyword master:new_on_active ${Config.compositor.masterNewOnActive}`;
+ batchCommand += ` ; keyword master:smart_resizing ${Config.compositor.masterSmartResizing}`;
+ batchCommand += ` ; keyword master:special_scale_factor ${Config.compositor.masterSpecialScaleFactor.toFixed(2)}`;
+ batchCommand += ` ; keyword master:allow_small_split ${Config.compositor.masterAllowSmallSplit}`;
+
+ // Scrolling
+ batchCommand += ` ; keyword scrolling:column_width ${Config.compositor.scrollingColumnWidth.toFixed(2)}`;
+ if (Config.compositor.scrollingExplicitColumnWidths) batchCommand += ` ; keyword scrolling:explicit_column_widths ${Config.compositor.scrollingExplicitColumnWidths}`;
+ batchCommand += ` ; keyword scrolling:direction ${Config.compositor.scrollingDirection}`;
+ batchCommand += ` ; keyword scrolling:fullscreen_on_one_column ${Config.compositor.scrollingFullscreenOnOneColumn}`;
+ batchCommand += ` ; keyword scrolling:focus_fit_method ${Config.compositor.scrollingFocusFitMethod}`;
+ batchCommand += ` ; keyword scrolling:follow_focus ${Config.compositor.scrollingFollowFocus}`;
+ batchCommand += ` ; keyword scrolling:follow_min_visible ${Config.compositor.scrollingFollowMinVisible.toFixed(2)}`;
+
+ // XWayland
+ batchCommand += ` ; keyword xwayland:enabled ${Config.compositor.xwaylandEnabled}`;
+ batchCommand += ` ; keyword xwayland:force_zero_scaling ${Config.compositor.xwaylandForceZeroScaling}`;
+ batchCommand += ` ; keyword xwayland:use_nearest_neighbor ${Config.compositor.xwaylandUseNearestNeighbor}`;
+
+ // Misc
+ batchCommand += ` ; keyword misc:vrr ${Config.compositor.vrr}`;
+ batchCommand += ` ; keyword misc:vfr ${Config.compositor.vfr}`;
+ batchCommand += ` ; keyword misc:mouse_move_enables_dpms ${Config.compositor.mouseMoveEnablesDpms}`;
+ batchCommand += ` ; keyword misc:key_press_enables_dpms ${Config.compositor.keyPressEnablesDpms}`;
+ batchCommand += ` ; keyword misc:disable_autoreload ${Config.compositor.disableAutoreload}`;
+ batchCommand += ` ; keyword misc:focus_on_activate ${Config.compositor.focusOnActivate}`;
+ batchCommand += ` ; keyword misc:animate_manual_resizes ${Config.compositor.animateManualResizes}`;
+ batchCommand += ` ; keyword misc:animate_mouse_windowdragging ${Config.compositor.animateMouseWindowdragging}`;
+ batchCommand += ` ; keyword misc:disable_hyprland_logo ${Config.compositor.disableHyprlandLogo}`;
+ batchCommand += ` ; keyword misc:disable_splash_rendering ${Config.compositor.disableSplashRendering}`;
+ batchCommand += ` ; keyword misc:force_default_wallpaper ${Config.compositor.forceDefaultWallpaper}`;
+ batchCommand += ` ; keyword misc:no_update_news ${Config.compositor.noUpdateNews}`;
+
+ // Animations and layer rules
batchCommand += ` ; keyword animation windows,1,2.5,myBezier,popin 80%`;
batchCommand += ` ; keyword animation border,1,2.5,myBezier`;
batchCommand += ` ; keyword animation fade,1,2.5,myBezier`;
@@ -208,8 +341,9 @@ QtObject {
console.log(`CompositorConfig: Applying ignorealpha: ${ignoreAlphaValue}, explicit: ${Config.compositor.blurExplicitIgnoreAlpha}`);
batchCommand += ` ; keyword layerrule noanim,quickshell ; keyword layerrule blur,quickshell ; keyword layerrule blurpopups,quickshell ; keyword layerrule ignorealpha ${ignoreAlphaValue},quickshell`;
- console.log("CompositorConfig: Refreshing TOML via CompositorTomlWriter");
- CompositorTomlWriter.refresh();
+ console.log("CompositorConfig: Applying compositor batch command:", batchCommand);
+ compositorProcess.command = ["axctl", "config", "raw-batch", batchCommand];
+ compositorProcess.running = true;
}
property Connections configConnections: Connections {
@@ -348,6 +482,134 @@ QtObject {
function onBlurInputMethodsIgnorealphaChanged() {
applyCompositorConfig();
}
+
+ // Opacity & Dim
+ function onActiveOpacityChanged() { applyCompositorConfig(); }
+ function onInactiveOpacityChanged() { applyCompositorConfig(); }
+ function onFullscreenOpacityChanged() { applyCompositorConfig(); }
+ function onDimInactiveChanged() { applyCompositorConfig(); }
+ function onDimStrengthChanged() { applyCompositorConfig(); }
+ function onDimAroundChanged() { applyCompositorConfig(); }
+ function onDimSpecialChanged() { applyCompositorConfig(); }
+ function onRoundingPowerChanged() { applyCompositorConfig(); }
+
+ // General extras
+ function onAllowTearingChanged() { applyCompositorConfig(); }
+ function onResizeOnBorderChanged() { applyCompositorConfig(); }
+ function onExtendBorderGrabAreaChanged() { applyCompositorConfig(); }
+ function onHoverIconOnBorderChanged() { applyCompositorConfig(); }
+
+ // Snap
+ function onSnapEnabledChanged() { applyCompositorConfig(); }
+ function onSnapWindowGapChanged() { applyCompositorConfig(); }
+ function onSnapMonitorGapChanged() { applyCompositorConfig(); }
+ function onSnapBorderOverlapChanged() { applyCompositorConfig(); }
+ function onSnapRespectGapsChanged() { applyCompositorConfig(); }
+
+ // Animations
+ function onAnimationsEnabledChanged() { applyCompositorConfig(); }
+
+ // Input: Keyboard
+ function onKbLayoutChanged() { applyCompositorConfig(); }
+ function onKbVariantChanged() { applyCompositorConfig(); }
+ function onKbOptionsChanged() { applyCompositorConfig(); }
+ function onNumlockByDefaultChanged() { applyCompositorConfig(); }
+ function onRepeatRateChanged() { applyCompositorConfig(); }
+ function onRepeatDelayChanged() { applyCompositorConfig(); }
+
+ // Input: Mouse
+ function onMouseSensitivityChanged() { applyCompositorConfig(); }
+ function onMouseAccelProfileChanged() { applyCompositorConfig(); }
+ function onFollowMouseChanged() { applyCompositorConfig(); }
+ function onMouseNaturalScrollChanged() { applyCompositorConfig(); }
+ function onMouseScrollFactorChanged() { applyCompositorConfig(); }
+ function onMouseLeftHandedChanged() { applyCompositorConfig(); }
+ function onMouseRefocusChanged() { applyCompositorConfig(); }
+ function onFloatSwitchOverrideFocusChanged() { applyCompositorConfig(); }
+
+ // Input: Touchpad
+ function onTouchpadDisableWhileTypingChanged() { applyCompositorConfig(); }
+ function onTouchpadNaturalScrollChanged() { applyCompositorConfig(); }
+ function onTouchpadTapToClickChanged() { applyCompositorConfig(); }
+ function onTouchpadClickfingerBehaviorChanged() { applyCompositorConfig(); }
+ function onTouchpadTapButtonMapChanged() { applyCompositorConfig(); }
+ function onTouchpadMiddleButtonEmulationChanged() { applyCompositorConfig(); }
+ function onTouchpadDragLockChanged() { applyCompositorConfig(); }
+ function onTouchpadScrollFactorChanged() { applyCompositorConfig(); }
+
+ // Cursor
+ function onNoHardwareCursorsChanged() { applyCompositorConfig(); }
+ function onEnableHyprcursorChanged() { applyCompositorConfig(); }
+ function onNoWarpsChanged() { applyCompositorConfig(); }
+ function onPersistentWarpsChanged() { applyCompositorConfig(); }
+ function onWarpOnChangeWorkspaceChanged() { applyCompositorConfig(); }
+ function onCursorZoomFactorChanged() { applyCompositorConfig(); }
+ function onCursorInactiveTimeoutChanged() { applyCompositorConfig(); }
+ function onCursorHideOnKeyPressChanged() { applyCompositorConfig(); }
+ function onCursorHideOnTouchChanged() { applyCompositorConfig(); }
+ function onCursorHideOnTabletChanged() { applyCompositorConfig(); }
+
+ // Gestures
+ function onWorkspaceSwipeCreateNewChanged() { applyCompositorConfig(); }
+ function onWorkspaceSwipeForeverChanged() { applyCompositorConfig(); }
+ function onWorkspaceSwipeCancelRatioChanged() { applyCompositorConfig(); }
+ function onWorkspaceSwipeMinSpeedToForceChanged() { applyCompositorConfig(); }
+ function onWorkspaceSwipeDirectionLockChanged() { applyCompositorConfig(); }
+ function onWorkspaceSwipeUseRChanged() { applyCompositorConfig(); }
+ function onWorkspaceSwipeDistanceChanged() { applyCompositorConfig(); }
+ function onWorkspaceSwipeInvertChanged() { applyCompositorConfig(); }
+ function onWorkspaceSwipeTouchChanged() { applyCompositorConfig(); }
+ function onWorkspaceSwipeTouchInvertChanged() { applyCompositorConfig(); }
+
+ // Dwindle
+ function onDwindlePreserveSplitChanged() { applyCompositorConfig(); }
+ function onDwindlePseudotileChanged() { applyCompositorConfig(); }
+ function onDwindleForceSplitChanged() { applyCompositorConfig(); }
+ function onDwindleSmartSplitChanged() { applyCompositorConfig(); }
+ function onDwindleDefaultSplitRatioChanged() { applyCompositorConfig(); }
+ function onDwindleSplitWidthMultiplierChanged() { applyCompositorConfig(); }
+ function onDwindlePermanentDirectionOverrideChanged() { applyCompositorConfig(); }
+ function onDwindleUseActiveForSplitsChanged() { applyCompositorConfig(); }
+ function onDwindleSmartResizingChanged() { applyCompositorConfig(); }
+ function onDwindleSpecialScaleFactorChanged() { applyCompositorConfig(); }
+
+ // Master
+ function onMasterOrientationChanged() { applyCompositorConfig(); }
+ function onMasterMfactChanged() { applyCompositorConfig(); }
+ function onMasterNewStatusChanged() { applyCompositorConfig(); }
+ function onMasterNewOnTopChanged() { applyCompositorConfig(); }
+ function onMasterNewOnActiveChanged() { applyCompositorConfig(); }
+ function onMasterSmartResizingChanged() { applyCompositorConfig(); }
+ function onMasterSpecialScaleFactorChanged() { applyCompositorConfig(); }
+ function onMasterAllowSmallSplitChanged() { applyCompositorConfig(); }
+
+ // Scrolling
+ function onScrollingColumnWidthChanged() { applyCompositorConfig(); }
+ function onScrollingExplicitColumnWidthsChanged() { applyCompositorConfig(); }
+ function onScrollingDirectionChanged() { applyCompositorConfig(); }
+ function onScrollingFullscreenOnOneColumnChanged() { applyCompositorConfig(); }
+ function onScrollingFocusFitMethodChanged() { applyCompositorConfig(); }
+ function onScrollingFollowFocusChanged() { applyCompositorConfig(); }
+ function onScrollingFollowMinVisibleChanged() { applyCompositorConfig(); }
+
+ // XWayland
+ function onXwaylandEnabledChanged() { applyCompositorConfig(); }
+ function onXwaylandForceZeroScalingChanged() { applyCompositorConfig(); }
+ function onXwaylandUseNearestNeighborChanged() { applyCompositorConfig(); }
+
+ // Misc
+ function onVrrChanged() { applyCompositorConfig(); }
+ function onVfrChanged() { applyCompositorConfig(); }
+ function onMouseMoveEnablesDpmsChanged() { applyCompositorConfig(); }
+ function onKeyPressEnablesDpmsChanged() { applyCompositorConfig(); }
+ function onDisableAutoreloadChanged() { applyCompositorConfig(); }
+ function onFocusOnActivateChanged() { applyCompositorConfig(); }
+ function onAnimateManualResizesChanged() { applyCompositorConfig(); }
+ function onAnimateMouseWindowdraggingChanged() { applyCompositorConfig(); }
+ function onDisableHyprlandLogoChanged() { applyCompositorConfig(); }
+ function onDisableSplashRenderingChanged() { applyCompositorConfig(); }
+ function onForceDefaultWallpaperChanged() { applyCompositorConfig(); }
+ function onNoUpdateNewsChanged() { applyCompositorConfig(); }
}
property Connections colorsConnections: Connections {
@@ -393,6 +655,16 @@ QtObject {
}
}
+ // Re-apply settings when Hyprland config is reloaded (user edits hyprland.conf)
+ property Connections axctlConnections: Connections {
+ target: AxctlService
+ function onRawEvent(event) {
+ if (event && event.name === "configreloaded") {
+ console.log("CompositorConfig: Hyprland config reloaded, reapplying settings...");
+ applyCompositorConfigInternal(); // Direct β no 100ms timer delay
+ }
+ }
+ }
Component.onCompleted: {
// Apply immediately if Config is already loaded.
diff --git a/modules/services/CompositorKeybinds.qml b/modules/services/CompositorKeybinds.qml
old mode 100644
new mode 100755
index c7c129ae..7727aa17
--- a/modules/services/CompositorKeybinds.qml
+++ b/modules/services/CompositorKeybinds.qml
@@ -9,7 +9,7 @@ QtObject {
property Process compositorProcess: Process {}
- property var previousAmbxstBinds: ({})
+ property var previousNothinglessBinds: ({})
property var previousCustomBinds: []
property bool hasPreviousBinds: false
@@ -45,31 +45,31 @@ QtObject {
if (!Config.keybindsLoader.loaded)
return;
- const ambxst = Config.keybindsLoader.adapter.ambxst;
-
- // Store ambxst core keybinds
- previousAmbxstBinds = {
- ambxst: {
- launcher: cloneKeybind(ambxst.launcher),
- dashboard: cloneKeybind(ambxst.dashboard),
- assistant: cloneKeybind(ambxst.assistant),
- clipboard: cloneKeybind(ambxst.clipboard),
- emoji: cloneKeybind(ambxst.emoji),
- notes: cloneKeybind(ambxst.notes),
- tmux: cloneKeybind(ambxst.tmux),
- wallpapers: cloneKeybind(ambxst.wallpapers)
+ const nothingless = Config.keybindsLoader.adapter.nothingless;
+
+ // Store nothingless core keybinds
+ previousNothinglessBinds = {
+ nothingless: {
+ launcher: cloneKeybind(nothingless.launcher),
+ dashboard: cloneKeybind(nothingless.dashboard),
+ assistant: cloneKeybind(nothingless.assistant),
+ clipboard: cloneKeybind(nothingless.clipboard),
+ emoji: cloneKeybind(nothingless.emoji),
+ notes: cloneKeybind(nothingless.notes),
+ tmux: cloneKeybind(nothingless.tmux),
+ wallpapers: cloneKeybind(nothingless.wallpapers)
},
system: {
- overview: cloneKeybind(ambxst.system.overview),
- powermenu: cloneKeybind(ambxst.system.powermenu),
- config: cloneKeybind(ambxst.system.config),
- lockscreen: cloneKeybind(ambxst.system.lockscreen),
- tools: cloneKeybind(ambxst.system.tools),
- screenshot: cloneKeybind(ambxst.system.screenshot),
- screenrecord: cloneKeybind(ambxst.system.screenrecord),
- lens: cloneKeybind(ambxst.system.lens),
- reload: ambxst.system.reload ? cloneKeybind(ambxst.system.reload) : null,
- quit: ambxst.system.quit ? cloneKeybind(ambxst.system.quit) : null
+ overview: cloneKeybind(nothingless.system.overview),
+ powermenu: cloneKeybind(nothingless.system.powermenu),
+ config: cloneKeybind(nothingless.system.config),
+ lockscreen: cloneKeybind(nothingless.system.lockscreen),
+ tools: cloneKeybind(nothingless.system.tools),
+ screenshot: cloneKeybind(nothingless.system.screenshot),
+ screenrecord: cloneKeybind(nothingless.system.screenrecord),
+ lens: cloneKeybind(nothingless.system.lens),
+ reload: nothingless.system.reload ? cloneKeybind(nothingless.system.reload) : null,
+ quit: nothingless.system.quit ? cloneKeybind(nothingless.system.quit) : null
}
};
@@ -162,30 +162,30 @@ QtObject {
// First, unbind previous keybinds if we have them stored
if (hasPreviousBinds) {
- // Unbind previous ambxst core keybinds
- if (previousAmbxstBinds.ambxst) {
- payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.ambxst.launcher));
- payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.ambxst.dashboard));
- payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.ambxst.assistant));
- payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.ambxst.clipboard));
- payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.ambxst.emoji));
- payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.ambxst.notes));
- payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.ambxst.tmux));
- payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.ambxst.wallpapers));
+ // Unbind previous nothingless core keybinds
+ if (previousNothinglessBinds.nothingless) {
+ payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.nothingless.launcher));
+ payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.nothingless.dashboard));
+ payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.nothingless.assistant));
+ payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.nothingless.clipboard));
+ payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.nothingless.emoji));
+ payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.nothingless.notes));
+ payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.nothingless.tmux));
+ payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.nothingless.wallpapers));
}
- // Unbind previous ambxst system keybinds
- if (previousAmbxstBinds.system) {
- payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.system.overview));
- payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.system.powermenu));
- payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.system.config));
- payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.system.lockscreen));
- payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.system.tools));
- payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.system.screenshot));
- payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.system.screenrecord));
- payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.system.lens));
- if (previousAmbxstBinds.system.reload) payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.system.reload));
- if (previousAmbxstBinds.system.quit) payload.unbinds.push(makeUnbindTarget(previousAmbxstBinds.system.quit));
+ // Unbind previous nothingless system keybinds
+ if (previousNothinglessBinds.system) {
+ payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.system.overview));
+ payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.system.powermenu));
+ payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.system.config));
+ payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.system.lockscreen));
+ payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.system.tools));
+ payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.system.screenshot));
+ payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.system.screenrecord));
+ payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.system.lens));
+ if (previousNothinglessBinds.system.reload) payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.system.reload));
+ if (previousNothinglessBinds.system.quit) payload.unbinds.push(makeUnbindTarget(previousNothinglessBinds.system.quit));
}
// Unbind previous custom keybinds
@@ -202,26 +202,26 @@ QtObject {
}
// Process core keybinds.
- const ambxst = Config.keybindsLoader.adapter.ambxst;
+ const nothingless = Config.keybindsLoader.adapter.nothingless;
// Unbind current core keybinds (ensures clean state before rebinding)
- payload.unbinds.push(makeUnbindTarget(ambxst.launcher));
- payload.unbinds.push(makeUnbindTarget(ambxst.dashboard));
- payload.unbinds.push(makeUnbindTarget(ambxst.assistant));
- payload.unbinds.push(makeUnbindTarget(ambxst.clipboard));
- payload.unbinds.push(makeUnbindTarget(ambxst.emoji));
- payload.unbinds.push(makeUnbindTarget(ambxst.notes));
- payload.unbinds.push(makeUnbindTarget(ambxst.tmux));
- payload.unbinds.push(makeUnbindTarget(ambxst.wallpapers));
+ payload.unbinds.push(makeUnbindTarget(nothingless.launcher));
+ payload.unbinds.push(makeUnbindTarget(nothingless.dashboard));
+ payload.unbinds.push(makeUnbindTarget(nothingless.assistant));
+ payload.unbinds.push(makeUnbindTarget(nothingless.clipboard));
+ payload.unbinds.push(makeUnbindTarget(nothingless.emoji));
+ payload.unbinds.push(makeUnbindTarget(nothingless.notes));
+ payload.unbinds.push(makeUnbindTarget(nothingless.tmux));
+ payload.unbinds.push(makeUnbindTarget(nothingless.wallpapers));
// Bind current core keybinds
- [ambxst.launcher, ambxst.dashboard, ambxst.assistant, ambxst.clipboard, ambxst.emoji, ambxst.notes, ambxst.tmux, ambxst.wallpapers].forEach(bind => {
+ [nothingless.launcher, nothingless.dashboard, nothingless.assistant, nothingless.clipboard, nothingless.emoji, nothingless.notes, nothingless.tmux, nothingless.wallpapers].forEach(bind => {
const resolved = makeBindFromCore(bind);
if (resolved) payload.binds.push(resolved);
});
// System keybinds
- const system = ambxst.system;
+ const system = nothingless.system;
// Unbind current system keybinds
payload.unbinds.push(makeUnbindTarget(system.overview));
@@ -234,9 +234,10 @@ QtObject {
payload.unbinds.push(makeUnbindTarget(system.lens));
if (system.reload) payload.unbinds.push(makeUnbindTarget(system.reload));
if (system.quit) payload.unbinds.push(makeUnbindTarget(system.quit));
+ if (system["toggle-metrics"]) payload.unbinds.push(makeUnbindTarget(system["toggle-metrics"]));
// Bind current system keybinds
- [system.overview, system.powermenu, system.config, system.lockscreen, system.tools, system.screenshot, system.screenrecord, system.lens, system.reload, system.quit].forEach(bind => {
+ [system.overview, system.powermenu, system.config, system.lockscreen, system.tools, system.screenshot, system.screenrecord, system.lens, system.reload, system.quit, system["toggle-metrics"]].forEach(bind => {
if (!bind) return;
const resolved = makeBindFromCore(bind);
if (resolved) payload.binds.push(resolved);
@@ -315,15 +316,15 @@ QtObject {
}
}
- // property Connections compositorConnections: Connections {
- // target: AxctlService
- // function onRawEvent(event) {
- // if (event.name === "configreloaded") {
- // console.log("CompositorKeybinds: Detectado configreloaded, reaplicando keybindings...");
- // applyKeybinds();
- // }
- // }
- // }
+ property Connections compositorConnections: Connections {
+ target: AxctlService
+ function onRawEvent(event) {
+ if (event && event.name === "configreloaded") {
+ console.log("CompositorKeybinds: Hyprland config reloaded, reapplying keybinds...");
+ applyKeybindsInternal(); // Direct β no 100ms timer delay
+ }
+ }
+ }
Component.onCompleted: {
// Apply immediately if loader is ready.
diff --git a/modules/services/CompositorTomlWriter.qml b/modules/services/CompositorTomlWriter.qml
old mode 100644
new mode 100755
index 3deef609..c7fb7b9c
--- a/modules/services/CompositorTomlWriter.qml
+++ b/modules/services/CompositorTomlWriter.qml
@@ -9,12 +9,12 @@ import "../../config/KeybindActions.js" as KeybindActions
/**
* CompositorTomlWriter - Generates TOML configuration for axctl
- * Writes to ~/.local/share/ambxst/axctl.toml
+ * Writes to ~/.local/share/nothingless/axctl.toml
*/
Singleton {
id: root
- property string outputPath: (Quickshell.env("XDG_DATA_HOME") || (Quickshell.env("HOME") + "/.local/share")) + "/ambxst/axctl.toml"
+ property string outputPath: (Quickshell.env("XDG_DATA_HOME") || (Quickshell.env("HOME") + "/.local/share")) + "/nothingless/axctl.toml"
property Process writeProcess: Process {
running: false
@@ -129,7 +129,7 @@ Singleton {
let toml = "";
toml += "[startup]\n";
- toml += "exec-once = \"ambxst\"\n";
+ toml += "exec-once = \"nothingless\"\n";
function tomlEscape(str) {
if (str === null || str === undefined)
@@ -154,15 +154,28 @@ Singleton {
function pushKeybindEntry(modifiers, key, dispatcher, argument, flags) {
if (!key || String(key).trim().length === 0)
return;
+ const normalized = normalizeKeybindDispatcher(dispatcher || "", argument || "");
toml += "\n[[keybinds]]\n";
toml += `modifiers = ${tomlStringArray(modifiers || [])}\n`;
toml += `key = ${tomlString(String(key))}\n`;
- toml += `dispatcher = ${tomlString(dispatcher || "")}\n`;
- toml += `argument = ${tomlString(argument || "")}\n`;
+ toml += `dispatcher = ${tomlString(normalized.dispatcher)}\n`;
+ toml += `argument = ${tomlString(normalized.argument)}\n`;
toml += `flags = ${tomlString(flags || "")}\n`;
toml += "enabled = true\n";
}
+ function normalizeKeybindDispatcher(dispatcher, argument) {
+ if (dispatcher === "layoutmsg") {
+ if (argument.indexOf("focus ") === 0) {
+ return { dispatcher: "movefocus", argument: argument.split(" ")[1] || "" };
+ }
+ if (argument.indexOf("movewindowto ") === 0) {
+ return { dispatcher: "movewindow", argument: argument.split(" ")[1] || "" };
+ }
+ }
+ return { dispatcher: dispatcher, argument: argument };
+ }
+
function resolveBindAction(action, fallback) {
const resolved = KeybindActions.resolveAction(action || fallback);
if (!resolved) return null;
@@ -208,39 +221,77 @@ Singleton {
}
toml += `rounding = ${Config.compositorRounding}\n`;
+ toml += `rounding_power = ${Config.compositor.roundingPower.toFixed(1)}\n`;
- // Opacity - placeholder (not synced in current implementation)
+ // Opacity
toml += "[appearance.opacity]\n";
- toml += "active = 1.0\n";
- toml += "inactive = 1.0\n";
+ toml += `active = ${Config.compositor.activeOpacity.toFixed(2)}\n`;
+ toml += `inactive = ${Config.compositor.inactiveOpacity.toFixed(2)}\n`;
+ toml += `fullscreen = ${Config.compositor.fullscreenOpacity.toFixed(2)}\n`;
+
+ // Dim
+ toml += "[appearance.dim]\n";
+ toml += `enabled = ${Config.compositor.dimInactive}\n`;
+ toml += `strength = ${Config.compositor.dimStrength.toFixed(2)}\n`;
+ toml += `around = ${Config.compositor.dimAround.toFixed(2)}\n`;
+ toml += `special = ${Config.compositor.dimSpecial.toFixed(2)}\n`;
// Blur - all settings
toml += "[appearance.blur]\n";
toml += `enabled = ${Config.compositor.blurEnabled}\n`;
toml += `size = ${Config.compositor.blurSize}\n`;
toml += `passes = ${Config.compositor.blurPasses}\n`;
+ toml += `ignore_opacity = ${Config.compositor.blurIgnoreOpacity}\n`;
+ toml += `new_optimizations = ${Config.compositor.blurNewOptimizations}\n`;
+ toml += `xray = ${Config.compositor.blurXray}\n`;
+ toml += `noise = ${Config.compositor.blurNoise.toFixed(3)}\n`;
+ toml += `contrast = ${Config.compositor.blurContrast.toFixed(2)}\n`;
+ toml += `brightness = ${Config.compositor.blurBrightness.toFixed(2)}\n`;
+ toml += `vibrancy = ${Config.compositor.blurVibrancy.toFixed(2)}\n`;
+ toml += `vibrancy_darkness = ${Config.compositor.blurVibrancyDarkness.toFixed(2)}\n`;
+ toml += `special = ${Config.compositor.blurSpecial}\n`;
+ toml += `popups = ${Config.compositor.blurPopups}\n`;
// Shadow - all settings
toml += "[appearance.shadow]\n";
toml += `enabled = ${Config.compositor.shadowEnabled}\n`;
- toml += `size = ${Config.compositor.shadowRange}\n`;
+ toml += `range = ${Config.compositor.shadowRange}\n`;
+ toml += `render_power = ${Config.compositor.shadowRenderPower}\n`;
+ toml += `sharp = ${Config.compositor.shadowSharp}\n`;
+ toml += `ignore_window = ${Config.compositor.shadowIgnoreWindow}\n`;
+ toml += `offset = "${Config.compositor.shadowOffset}"\n`;
+ toml += `scale = ${Config.compositor.shadowScale.toFixed(2)}\n`;
const shadowColorFormatted = formatShadowColors(Config.compositorShadowColor, Config.compositorShadowOpacity);
toml += `color = "${shadowColorFormatted}"\n`;
+ const inactiveShadowColorFormatted = formatShadowColors(Config.compositor.shadowColorInactive, Config.compositorShadowOpacity);
+ toml += `color_inactive = "${inactiveShadowColorFormatted}"\n`;
// Animations
toml += "[appearance.animations]\n";
- toml += "enabled = true\n";
+ toml += `enabled = ${Config.compositor.animationsEnabled}\n`;
- // Layout (if set)
+ // General
+ toml += "\n[general]\n";
if (GlobalStates.compositorLayout && GlobalStates.compositorLayout.length > 0) {
- toml += "\n[general]\n";
toml += `layout = "${GlobalStates.compositorLayout}"\n`;
}
+ toml += `allow_tearing = ${Config.compositor.allowTearing}\n`;
+ toml += `resize_on_border = ${Config.compositor.resizeOnBorder}\n`;
+ toml += `extend_border_grab_area = ${Config.compositor.extendBorderGrabArea}\n`;
+ toml += `hover_icon_on_border = ${Config.compositor.hoverIconOnBorder}\n`;
+
+ // Snap
+ toml += "\n[general.snap]\n";
+ toml += `enabled = ${Config.compositor.snapEnabled}\n`;
+ toml += `window_gap = ${Config.compositor.snapWindowGap}\n`;
+ toml += `monitor_gap = ${Config.compositor.snapMonitorGap}\n`;
+ toml += `border_overlap = ${Config.compositor.snapBorderOverlap}\n`;
+ toml += `respect_gaps = ${Config.compositor.snapRespectGaps}\n`;
// Keybinds
if (Config.keybindsLoader.loaded && Config.keybindsLoader.adapter) {
const adapter = Config.keybindsLoader.adapter;
- const ambxst = adapter.ambxst;
+ const nothingless = adapter.nothingless;
function pushCoreBind(keybind) {
if (!keybind)
@@ -257,27 +308,28 @@ Singleton {
);
}
- if (ambxst) {
- pushCoreBind(ambxst.launcher);
- pushCoreBind(ambxst.dashboard);
- pushCoreBind(ambxst.assistant);
- pushCoreBind(ambxst.clipboard);
- pushCoreBind(ambxst.emoji);
- pushCoreBind(ambxst.notes);
- pushCoreBind(ambxst.tmux);
- pushCoreBind(ambxst.wallpapers);
-
- if (ambxst.system) {
- pushCoreBind(ambxst.system.overview);
- pushCoreBind(ambxst.system.powermenu);
- pushCoreBind(ambxst.system.config);
- pushCoreBind(ambxst.system.lockscreen);
- pushCoreBind(ambxst.system.tools);
- pushCoreBind(ambxst.system.screenshot);
- pushCoreBind(ambxst.system.screenrecord);
- pushCoreBind(ambxst.system.lens);
- if (ambxst.system.reload) pushCoreBind(ambxst.system.reload);
- if (ambxst.system.quit) pushCoreBind(ambxst.system.quit);
+ if (nothingless) {
+ pushCoreBind(nothingless.launcher);
+ pushCoreBind(nothingless.dashboard);
+ pushCoreBind(nothingless.assistant);
+ pushCoreBind(nothingless.clipboard);
+ pushCoreBind(nothingless.emoji);
+ pushCoreBind(nothingless.notes);
+ pushCoreBind(nothingless.tmux);
+ pushCoreBind(nothingless.wallpapers);
+
+ if (nothingless.system) {
+ pushCoreBind(nothingless.system.overview);
+ pushCoreBind(nothingless.system.powermenu);
+ pushCoreBind(nothingless.system.config);
+ pushCoreBind(nothingless.system.lockscreen);
+ pushCoreBind(nothingless.system.tools);
+ pushCoreBind(nothingless.system.screenshot);
+ pushCoreBind(nothingless.system.screenrecord);
+ pushCoreBind(nothingless.system.lens);
+ if (nothingless.system.reload) pushCoreBind(nothingless.system.reload);
+ if (nothingless.system.quit) pushCoreBind(nothingless.system.quit);
+ if (nothingless.system && nothingless.system["toggle-metrics"]) pushCoreBind(nothingless.system["toggle-metrics"]);
}
}
@@ -355,7 +407,7 @@ Singleton {
toml += "ignore_alpha_value = 0.4\n";
toml += "\n[[layer_rules]]\n";
- toml += "namespace = \"ambxst\"\n";
+ toml += "namespace = \"nothingless\"\n";
toml += "blur = true\n";
toml += "blur_popups = true\n";
toml += "no_anim = true\n";
@@ -375,11 +427,162 @@ Singleton {
- // Input section (placeholder for keyboard layout)
+ // Input section
toml += "\n[input]\n";
toml += "[input.keyboard]\n";
- toml += 'layouts = ""\n';
- toml += 'variants = ""\n';
+ toml += `layout = "${Config.compositor.kbLayout}"\n`;
+ if (Config.compositor.kbVariant) {
+ toml += `variant = "${Config.compositor.kbVariant}"\n`;
+ }
+ if (Config.compositor.kbOptions) {
+ toml += `options = "${Config.compositor.kbOptions}"\n`;
+ }
+ toml += `numlock_by_default = ${Config.compositor.numlockByDefault}\n`;
+ toml += `repeat_rate = ${Config.compositor.repeatRate}\n`;
+ toml += `repeat_delay = ${Config.compositor.repeatDelay}\n`;
+
+ toml += "\n[input.mouse]\n";
+ toml += `sensitivity = ${Config.compositor.mouseSensitivity.toFixed(2)}\n`;
+ if (Config.compositor.mouseAccelProfile) {
+ toml += `accel_profile = "${Config.compositor.mouseAccelProfile}"\n`;
+ }
+ toml += `follow_mouse = ${Config.compositor.followMouse}\n`;
+ toml += `natural_scroll = ${Config.compositor.mouseNaturalScroll}\n`;
+ toml += `scroll_factor = ${Config.compositor.mouseScrollFactor.toFixed(1)}\n`;
+ toml += `left_handed = ${Config.compositor.mouseLeftHanded}\n`;
+ toml += `mouse_refocus = ${Config.compositor.mouseRefocus}\n`;
+ toml += `float_switch_override_focus = ${Config.compositor.floatSwitchOverrideFocus}\n`;
+
+ toml += "\n[input.touchpad]\n";
+ toml += `disable_while_typing = ${Config.compositor.touchpadDisableWhileTyping}\n`;
+ toml += `natural_scroll = ${Config.compositor.touchpadNaturalScroll}\n`;
+ toml += `tap_to_click = ${Config.compositor.touchpadTapToClick}\n`;
+ toml += `clickfinger_behavior = ${Config.compositor.touchpadClickfingerBehavior}\n`;
+ if (Config.compositor.touchpadTapButtonMap) {
+ toml += `tap_button_map = "${Config.compositor.touchpadTapButtonMap}"\n`;
+ }
+ toml += `middle_button_emulation = ${Config.compositor.touchpadMiddleButtonEmulation}\n`;
+ toml += `drag_lock = ${Config.compositor.touchpadDragLock}\n`;
+ toml += `scroll_factor = ${Config.compositor.touchpadScrollFactor.toFixed(1)}\n`;
+
+ // Cursor
+ toml += "\n[cursor]\n";
+ toml += `no_hardware_cursors = ${Config.compositor.noHardwareCursors}\n`;
+ toml += `enable_hyprcursor = ${Config.compositor.enableHyprcursor}\n`;
+ toml += `no_warps = ${Config.compositor.noWarps}\n`;
+ toml += `persistent_warps = ${Config.compositor.persistentWarps}\n`;
+ toml += `warp_on_change_workspace = ${Config.compositor.warpOnChangeWorkspace}\n`;
+ toml += `zoom_factor = ${Config.compositor.cursorZoomFactor.toFixed(1)}\n`;
+ toml += `inactive_timeout = ${Config.compositor.cursorInactiveTimeout}\n`;
+ toml += `hide_on_key_press = ${Config.compositor.cursorHideOnKeyPress}\n`;
+ toml += `hide_on_touch = ${Config.compositor.cursorHideOnTouch}\n`;
+ toml += `hide_on_tablet = ${Config.compositor.cursorHideOnTablet}\n`;
+
+ // Gestures
+ toml += "\n[gestures]\n";
+ toml += "[gestures.workspace_swipe]\n";
+ toml += `create_new = ${Config.compositor.workspaceSwipeCreateNew}\n`;
+ toml += `forever = ${Config.compositor.workspaceSwipeForever}\n`;
+ toml += `cancel_ratio = ${Config.compositor.workspaceSwipeCancelRatio.toFixed(2)}\n`;
+ toml += `min_speed_to_force = ${Config.compositor.workspaceSwipeMinSpeedToForce}\n`;
+ toml += `direction_lock = ${Config.compositor.workspaceSwipeDirectionLock}\n`;
+ toml += `use_r = ${Config.compositor.workspaceSwipeUseR}\n`;
+ toml += `distance = ${Config.compositor.workspaceSwipeDistance}\n`;
+ toml += `invert = ${Config.compositor.workspaceSwipeInvert}\n`;
+ toml += `touch = ${Config.compositor.workspaceSwipeTouch}\n`;
+ toml += `touch_invert = ${Config.compositor.workspaceSwipeTouchInvert}\n`;
+
+ // Dwindle
+ toml += "\n[dwindle]\n";
+ toml += `preserve_split = ${Config.compositor.dwindlePreserveSplit}\n`;
+ toml += `pseudotile = ${Config.compositor.dwindlePseudotile}\n`;
+ toml += `force_split = ${Config.compositor.dwindleForceSplit}\n`;
+ toml += `smart_split = ${Config.compositor.dwindleSmartSplit}\n`;
+ toml += `default_split_ratio = ${Config.compositor.dwindleDefaultSplitRatio.toFixed(2)}\n`;
+ toml += `split_width_multiplier = ${Config.compositor.dwindleSplitWidthMultiplier.toFixed(1)}\n`;
+ toml += `permanent_direction_override = ${Config.compositor.dwindlePermanentDirectionOverride}\n`;
+ toml += `use_active_for_splits = ${Config.compositor.dwindleUseActiveForSplits}\n`;
+ toml += `smart_resizing = ${Config.compositor.dwindleSmartResizing}\n`;
+ toml += `special_scale_factor = ${Config.compositor.dwindleSpecialScaleFactor.toFixed(2)}\n`;
+
+ // Master
+ toml += "\n[master]\n";
+ toml += `orientation = "${Config.compositor.masterOrientation}"\n`;
+ toml += `mfact = ${Config.compositor.masterMfact.toFixed(2)}\n`;
+ toml += `new_status = "${Config.compositor.masterNewStatus}"\n`;
+ toml += `new_on_top = ${Config.compositor.masterNewOnTop}\n`;
+ toml += `new_on_active = "${Config.compositor.masterNewOnActive}"\n`;
+ toml += `smart_resizing = ${Config.compositor.masterSmartResizing}\n`;
+ toml += `special_scale_factor = ${Config.compositor.masterSpecialScaleFactor.toFixed(2)}\n`;
+ toml += `allow_small_split = ${Config.compositor.masterAllowSmallSplit}\n`;
+
+ // Scrolling
+ toml += "\n[scrolling]\n";
+ toml += `column_width = ${Config.compositor.scrollingColumnWidth.toFixed(2)}\n`;
+ if (Config.compositor.scrollingExplicitColumnWidths) {
+ toml += `explicit_column_widths = "${Config.compositor.scrollingExplicitColumnWidths}"\n`;
+ }
+ toml += `direction = "${Config.compositor.scrollingDirection}"\n`;
+ toml += `fullscreen_on_one_column = ${Config.compositor.scrollingFullscreenOnOneColumn}\n`;
+ toml += `focus_fit_method = "${Config.compositor.scrollingFocusFitMethod}"\n`;
+ toml += `follow_focus = ${Config.compositor.scrollingFollowFocus}\n`;
+ toml += `follow_min_visible = ${Config.compositor.scrollingFollowMinVisible.toFixed(2)}\n`;
+
+ // XWayland
+ toml += "\n[xwayland]\n";
+ toml += `enabled = ${Config.compositor.xwaylandEnabled}\n`;
+ toml += `force_zero_scaling = ${Config.compositor.xwaylandForceZeroScaling}\n`;
+ toml += `use_nearest_neighbor = ${Config.compositor.xwaylandUseNearestNeighbor}\n`;
+
+ // Misc
+ toml += "\n[misc]\n";
+ toml += `vrr = ${Config.compositor.vrr}\n`;
+ toml += `vfr = ${Config.compositor.vfr}\n`;
+ toml += `mouse_move_enables_dpms = ${Config.compositor.mouseMoveEnablesDpms}\n`;
+ toml += `key_press_enables_dpms = ${Config.compositor.keyPressEnablesDpms}\n`;
+ toml += `disable_autoreload = ${Config.compositor.disableAutoreload}\n`;
+ toml += `focus_on_activate = ${Config.compositor.focusOnActivate}\n`;
+ toml += `animate_manual_resizes = ${Config.compositor.animateManualResizes}\n`;
+ toml += `animate_mouse_windowdragging = ${Config.compositor.animateMouseWindowdragging}\n`;
+ toml += `disable_hyprland_logo = ${Config.compositor.disableHyprlandLogo}\n`;
+ toml += `disable_splash_rendering = ${Config.compositor.disableSplashRendering}\n`;
+ toml += `force_default_wallpaper = ${Config.compositor.forceDefaultWallpaper}\n`;
+ toml += `no_update_news = ${Config.compositor.noUpdateNews}\n`;
+
+ // Monitors
+ toml += "\n[monitors]\n";
+ try {
+ var screens = Quickshell.screens;
+ if (screens) {
+ var axMons = AxctlService.monitors.values || [];
+ for (var mi = 0; mi < screens.length; mi++) {
+ var scr = screens[mi];
+ if (!scr || !scr.name) continue;
+ var ax = null;
+ for (var mj = 0; mj < axMons.length; mj++) {
+ if (axMons[mj].name === scr.name) { ax = axMons[mj]; break; }
+ }
+ var w = ax ? (ax.width || scr.width || 1920) : (scr.width || 1920);
+ var h = ax ? (ax.height || scr.height || 1080) : (scr.height || 1080);
+ var x = scr.x || 0;
+ var y = scr.y || 0;
+ var s = ax ? (ax.scale || 1.0) : 1.0;
+ var rr = ax ? (ax.refreshRate || 60) : 60;
+ var t = ax ? (ax.transform || 0) : 0;
+
+ toml += "[[monitors]]\n";
+ toml += "name = \"" + scr.name + "\"\n";
+ toml += "mode = \"" + w + "x" + h + "@" + rr.toFixed(2) + "Hz\"\n";
+ toml += "position = \"" + x + "x" + y + "\"\n";
+ toml += "scale = " + s + "\n";
+ if (t > 0) toml += "transform = " + t + "\n";
+ toml += "enabled = true\n";
+ toml += "\n";
+ }
+ }
+ } catch (e) {
+ console.warn("CompositorTomlWriter: Error writing monitors section:", e);
+ }
return toml;
}
@@ -394,9 +597,10 @@ Singleton {
console.log("CompositorTomlWriter: Written TOML to", root.outputPath);
}
- function refresh() {
- writeTomlFile();
- }
+ // Note: hyprland.conf is NOT generated here.
+ // It is created once by 'nothingless install hyprland' and stays static forever.
+ // Regenerating it would trigger Hyprland config reload and disrupt the session.
+ // All compositor settings go through axctl.toml (persist) and axctl raw-batch (live).
Component.onCompleted: {
Qt.callLater(() => {
@@ -426,14 +630,19 @@ Singleton {
target: Config.compositor
// Border settings
+ function onShowBorderChanged() { writeTomlFile(); }
function onBorderSizeChanged() { writeTomlFile(); }
function onRoundingChanged() { writeTomlFile(); }
+ function onRoundingPowerChanged() { writeTomlFile(); }
function onGapsInChanged() { writeTomlFile(); }
function onGapsOutChanged() { writeTomlFile(); }
function onActiveBorderColorChanged() { writeTomlFile(); }
function onInactiveBorderColorChanged() { writeTomlFile(); }
function onBorderAngleChanged() { writeTomlFile(); }
function onInactiveBorderAngleChanged() { writeTomlFile(); }
+ function onResizeOnBorderChanged() { writeTomlFile(); }
+ function onExtendBorderGrabAreaChanged() { writeTomlFile(); }
+ function onHoverIconOnBorderChanged() { writeTomlFile(); }
// Sync settings that affect derived values
function onSyncRoundnessChanged() { writeTomlFile(); }
@@ -441,6 +650,26 @@ Singleton {
function onSyncBorderColorChanged() { writeTomlFile(); }
function onSyncShadowOpacityChanged() { writeTomlFile(); }
function onSyncShadowColorChanged() { writeTomlFile(); }
+
+ // Layout
+ function onLayoutChanged() { writeTomlFile(); }
+ function onAllowTearingChanged() { writeTomlFile(); }
+
+ // Snap
+ function onSnapEnabledChanged() { writeTomlFile(); }
+ function onSnapWindowGapChanged() { writeTomlFile(); }
+ function onSnapMonitorGapChanged() { writeTomlFile(); }
+ function onSnapBorderOverlapChanged() { writeTomlFile(); }
+ function onSnapRespectGapsChanged() { writeTomlFile(); }
+
+ // Opacity & Dim
+ function onActiveOpacityChanged() { writeTomlFile(); }
+ function onInactiveOpacityChanged() { writeTomlFile(); }
+ function onFullscreenOpacityChanged() { writeTomlFile(); }
+ function onDimInactiveChanged() { writeTomlFile(); }
+ function onDimStrengthChanged() { writeTomlFile(); }
+ function onDimAroundChanged() { writeTomlFile(); }
+ function onDimSpecialChanged() { writeTomlFile(); }
// Shadow settings
function onShadowEnabledChanged() { writeTomlFile(); }
@@ -473,6 +702,112 @@ Singleton {
function onBlurPopupsIgnorealphaChanged() { writeTomlFile(); }
function onBlurInputMethodsChanged() { writeTomlFile(); }
function onBlurInputMethodsIgnorealphaChanged() { writeTomlFile(); }
+
+ // Animations
+ function onAnimationsEnabledChanged() { writeTomlFile(); }
+
+ // Input: Keyboard
+ function onKbLayoutChanged() { writeTomlFile(); }
+ function onKbVariantChanged() { writeTomlFile(); }
+ function onKbOptionsChanged() { writeTomlFile(); }
+ function onNumlockByDefaultChanged() { writeTomlFile(); }
+ function onRepeatRateChanged() { writeTomlFile(); }
+ function onRepeatDelayChanged() { writeTomlFile(); }
+
+ // Input: Mouse
+ function onMouseSensitivityChanged() { writeTomlFile(); }
+ function onMouseAccelProfileChanged() { writeTomlFile(); }
+ function onFollowMouseChanged() { writeTomlFile(); }
+ function onMouseNaturalScrollChanged() { writeTomlFile(); }
+ function onMouseScrollFactorChanged() { writeTomlFile(); }
+ function onMouseLeftHandedChanged() { writeTomlFile(); }
+ function onMouseRefocusChanged() { writeTomlFile(); }
+ function onFloatSwitchOverrideFocusChanged() { writeTomlFile(); }
+
+ // Input: Touchpad
+ function onTouchpadDisableWhileTypingChanged() { writeTomlFile(); }
+ function onTouchpadNaturalScrollChanged() { writeTomlFile(); }
+ function onTouchpadTapToClickChanged() { writeTomlFile(); }
+ function onTouchpadClickfingerBehaviorChanged() { writeTomlFile(); }
+ function onTouchpadTapButtonMapChanged() { writeTomlFile(); }
+ function onTouchpadMiddleButtonEmulationChanged() { writeTomlFile(); }
+ function onTouchpadDragLockChanged() { writeTomlFile(); }
+ function onTouchpadScrollFactorChanged() { writeTomlFile(); }
+
+ // Cursor
+ function onNoHardwareCursorsChanged() { writeTomlFile(); }
+ function onEnableHyprcursorChanged() { writeTomlFile(); }
+ function onNoWarpsChanged() { writeTomlFile(); }
+ function onPersistentWarpsChanged() { writeTomlFile(); }
+ function onWarpOnChangeWorkspaceChanged() { writeTomlFile(); }
+ function onCursorZoomFactorChanged() { writeTomlFile(); }
+ function onCursorInactiveTimeoutChanged() { writeTomlFile(); }
+ function onCursorHideOnKeyPressChanged() { writeTomlFile(); }
+ function onCursorHideOnTouchChanged() { writeTomlFile(); }
+ function onCursorHideOnTabletChanged() { writeTomlFile(); }
+
+ // Gestures
+ function onWorkspaceSwipeCreateNewChanged() { writeTomlFile(); }
+ function onWorkspaceSwipeForeverChanged() { writeTomlFile(); }
+ function onWorkspaceSwipeCancelRatioChanged() { writeTomlFile(); }
+ function onWorkspaceSwipeMinSpeedToForceChanged() { writeTomlFile(); }
+ function onWorkspaceSwipeDirectionLockChanged() { writeTomlFile(); }
+ function onWorkspaceSwipeUseRChanged() { writeTomlFile(); }
+ function onWorkspaceSwipeDistanceChanged() { writeTomlFile(); }
+ function onWorkspaceSwipeInvertChanged() { writeTomlFile(); }
+ function onWorkspaceSwipeTouchChanged() { writeTomlFile(); }
+ function onWorkspaceSwipeTouchInvertChanged() { writeTomlFile(); }
+
+ // Dwindle
+ function onDwindlePreserveSplitChanged() { writeTomlFile(); }
+ function onDwindlePseudotileChanged() { writeTomlFile(); }
+ function onDwindleForceSplitChanged() { writeTomlFile(); }
+ function onDwindleSmartSplitChanged() { writeTomlFile(); }
+ function onDwindleDefaultSplitRatioChanged() { writeTomlFile(); }
+ function onDwindleSplitWidthMultiplierChanged() { writeTomlFile(); }
+ function onDwindlePermanentDirectionOverrideChanged() { writeTomlFile(); }
+ function onDwindleUseActiveForSplitsChanged() { writeTomlFile(); }
+ function onDwindleSmartResizingChanged() { writeTomlFile(); }
+ function onDwindleSpecialScaleFactorChanged() { writeTomlFile(); }
+
+ // Master
+ function onMasterOrientationChanged() { writeTomlFile(); }
+ function onMasterMfactChanged() { writeTomlFile(); }
+ function onMasterNewStatusChanged() { writeTomlFile(); }
+ function onMasterNewOnTopChanged() { writeTomlFile(); }
+ function onMasterNewOnActiveChanged() { writeTomlFile(); }
+ function onMasterSmartResizingChanged() { writeTomlFile(); }
+ function onMasterSpecialScaleFactorChanged() { writeTomlFile(); }
+ function onMasterAllowSmallSplitChanged() { writeTomlFile(); }
+
+ // Scrolling
+ function onScrollingColumnWidthChanged() { writeTomlFile(); }
+ function onScrollingExplicitColumnWidthsChanged() { writeTomlFile(); }
+ function onScrollingDirectionChanged() { writeTomlFile(); }
+ function onScrollingFullscreenOnOneColumnChanged() { writeTomlFile(); }
+ function onScrollingFocusFitMethodChanged() { writeTomlFile(); }
+ function onScrollingFollowFocusChanged() { writeTomlFile(); }
+ function onScrollingFollowMinVisibleChanged() { writeTomlFile(); }
+
+ // XWayland
+ function onXwaylandEnabledChanged() { writeTomlFile(); }
+ function onXwaylandForceZeroScalingChanged() { writeTomlFile(); }
+ function onXwaylandUseNearestNeighborChanged() { writeTomlFile(); }
+
+ // Monitor Globals / Misc
+ function onVrrChanged() { writeTomlFile(); }
+ function onVfrChanged() { writeTomlFile(); }
+ function onMouseMoveEnablesDpmsChanged() { writeTomlFile(); }
+ function onKeyPressEnablesDpmsChanged() { writeTomlFile(); }
+ function onDisableAutoreloadChanged() { writeTomlFile(); }
+ function onFocusOnActivateChanged() { writeTomlFile(); }
+ function onAnimateManualResizesChanged() { writeTomlFile(); }
+ function onAnimateMouseWindowdraggingChanged() { writeTomlFile(); }
+ function onDisableHyprlandLogoChanged() { writeTomlFile(); }
+ function onDisableSplashRenderingChanged() { writeTomlFile(); }
+ function onForceDefaultWallpaperChanged() { writeTomlFile(); }
+ function onNoUpdateNewsChanged() { writeTomlFile(); }
+ function onEnforcePermissionsChanged() { writeTomlFile(); }
}
// Theme connections (for blur ignorealpha calculation and shadow color sync)
diff --git a/modules/services/DesktopService.qml b/modules/services/DesktopService.qml
old mode 100644
new mode 100755
index 025f991c..2a5086f0
--- a/modules/services/DesktopService.qml
+++ b/modules/services/DesktopService.qml
@@ -121,70 +121,19 @@ Singleton {
function executeDesktopFile(filePath) {
var escapedPath = filePath.replace(/'/g, "'\\''");
- var processComponent = Qt.createQmlObject('
- import Quickshell
- import Quickshell.Io
- Process {
- running: true
- command: ["bash", "-c", "cd ~ && setsid gio launch \'' + escapedPath + '\' < /dev/null > /dev/null 2>&1 &"]
-
- stdout: StdioCollector {
- onStreamFinished: {
- if (text.length > 0) {
- console.log("Desktop file execution:", text);
- }
- }
- }
-
- stderr: StdioCollector {
- onStreamFinished: {
- if (text.length > 0) {
- console.warn("Desktop file execution error:", text);
- }
- }
- }
-
- onRunningChanged: {
- if (!running) {
- destroy();
- }
- }
- }
- ', root);
+ runInActiveWorkspace("gio launch '" + escapedPath + "'");
}
function openFile(filePath) {
var escapedPath = filePath.replace(/'/g, "'\\''");
- var processComponent = Qt.createQmlObject('
- import Quickshell
- import Quickshell.Io
- Process {
- running: true
- command: ["bash", "-c", "setsid xdg-open \'' + escapedPath + '\' < /dev/null > /dev/null 2>&1 &"]
-
- stdout: StdioCollector {
- onStreamFinished: {
- if (text.length > 0) {
- console.log("File opened:", text);
- }
- }
- }
-
- stderr: StdioCollector {
- onStreamFinished: {
- if (text.length > 0) {
- console.warn("Error opening file:", text);
- }
- }
- }
+ runInActiveWorkspace("xdg-open '" + escapedPath + "'");
+ }
- onRunningChanged: {
- if (!running) {
- destroy();
- }
- }
- }
- ', root);
+ function runInActiveWorkspace(command) {
+ var processComponent = Qt.createQmlObject('import Quickshell.Io; Process { }', root);
+ processComponent.command = ["bash", "-c", "cd ~ && env -u HL_INITIAL_WORKSPACE_TOKEN setsid " + command + " < /dev/null > /dev/null 2>&1 &"];
+ processComponent.onExited.connect(() => processComponent.destroy());
+ processComponent.running = true;
}
function trashFile(filePath) {
@@ -665,7 +614,7 @@ Singleton {
id: thumbnailProcess
running: false
// QUICKSHELL-GIT: command: ["python3", decodeURIComponent(Qt.resolvedUrl("../../scripts/desktop_thumbgen.py").toString().replace("file://", "")), desktopDir, Quickshell.cacheDir + "/desktop_thumbnails"]
- command: ["python3", decodeURIComponent(Qt.resolvedUrl("../../scripts/desktop_thumbgen.py").toString().replace("file://", "")), desktopDir, Quickshell.env("HOME") + "/.cache/ambxst" + "/desktop_thumbnails"]
+ command: ["python3", decodeURIComponent(Qt.resolvedUrl("../../scripts/desktop_thumbgen.py").toString().replace("file://", "")), desktopDir, Quickshell.env("HOME") + "/.cache/nothingless" + "/desktop_thumbnails"]
stdout: StdioCollector {
onStreamFinished: {
diff --git a/modules/services/EasyEffectsService.qml b/modules/services/EasyEffectsService.qml
old mode 100644
new mode 100755
diff --git a/modules/services/FocusGrab.qml b/modules/services/FocusGrab.qml
old mode 100644
new mode 100755
diff --git a/modules/services/FocusGrabManager.qml b/modules/services/FocusGrabManager.qml
old mode 100644
new mode 100755
diff --git a/modules/services/GameModeService.qml b/modules/services/GameModeService.qml
old mode 100644
new mode 100755
diff --git a/modules/services/GlobalShortcuts.qml b/modules/services/GlobalShortcuts.qml
index 3d61aee4..2af37e76 100644
--- a/modules/services/GlobalShortcuts.qml
+++ b/modules/services/GlobalShortcuts.qml
@@ -2,17 +2,22 @@ pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
+import Quickshell
+import Quickshell.Io
import qs.modules.globals
import qs.modules.services
import qs.config
-import Quickshell.Io
-
QtObject {
id: root
- readonly property string appId: "ambxst"
- readonly property string ipcPipe: "/tmp/ambxst_ipc.pipe"
+ readonly property string appId: "nothingless"
+ readonly property string ipcPipe: "/tmp/nothingless_ipc.pipe"
+
+ property Process toggleMetricProcess: Process {
+ command: ["sh", "-c", Quickshell.shellDir + "/scripts/toggle-metrics.sh"]
+ running: false
+ }
// High-performance Pipe Listener (Daemon mode)
property Process pipeListener: Process {
@@ -29,6 +34,15 @@ QtObject {
}
}
+
+ function toggleMetrics() {
+ // Toggle the notch metrics overlay
+ if (Config.notch) {
+ Config.notch.showMetrics = !Config.notch.showMetrics;
+ console.log("Metrics overlay toggled:", Config.notch.showMetrics);
+ }
+ }
+
function run(command) {
console.log("IPC run command received:", command);
switch (command) {
@@ -53,6 +67,10 @@ QtObject {
case "overview": toggleSimpleModule("overview"); break;
case "powermenu": toggleSimpleModule("powermenu"); break;
case "tools": toggleSimpleModule("tools"); break;
+ case "toggle-metrics":
+ root.toggleMetrics();
+ console.log("Metrics toggled");
+ break;
case "config": toggleSettings(); break;
case "screenshot": Screenshot.initialize(); GlobalStates.screenshotToolVisible = true; break;
case "screenrecord": ScreenRecorder.initialize(); GlobalStates.screenRecordToolVisible = true; break;
@@ -77,7 +95,7 @@ QtObject {
}
property IpcHandler ipcHandler: IpcHandler {
- target: "ambxst"
+ target: "nothingless"
function run(command: string) {
root.run(command);
@@ -85,10 +103,13 @@ QtObject {
}
function toggleSettings() {
- GlobalStates.settingsWindowVisible = !GlobalStates.settingsWindowVisible;
- if (GlobalStates.settingsWindowVisible) {
+ const willOpen = !GlobalStates.settingsWindowVisible;
+ if (willOpen) {
+ GlobalStates.settingsTargetWorkspaceId = AxctlService.focusedMonitor?.activeWorkspace?.id || AxctlService.focusedWorkspace?.id || 0;
+ GlobalStates.settingsTargetScreenName = AxctlService.focusedMonitor?.name || "";
Visibilities.setActiveModule("");
}
+ GlobalStates.settingsWindowVisible = willOpen;
}
function toggleSimpleModule(moduleName) {
diff --git a/modules/services/IdleInhibitor.qml b/modules/services/IdleInhibitor.qml
old mode 100644
new mode 100755
diff --git a/modules/services/IdleMonitor.qml b/modules/services/IdleMonitor.qml
old mode 100644
new mode 100755
diff --git a/modules/services/IdleService.qml b/modules/services/IdleService.qml
old mode 100644
new mode 100755
index 4f7f6b95..08d75b6e
--- a/modules/services/IdleService.qml
+++ b/modules/services/IdleService.qml
@@ -10,9 +10,9 @@ Singleton {
id: root
// General Idle Settings
- property string lockCmd: Config.system.idle.general.lock_cmd ?? "ambxst lock"
+ property string lockCmd: Config.system.idle.general.lock_cmd ?? "nothingless lock"
property string beforeSleepCmd: Config.system.idle.general.before_sleep_cmd ?? "loginctl lock-session"
- property string afterSleepCmd: Config.system.idle.general.after_sleep_cmd ?? "ambxst screen on"
+ property string afterSleepCmd: Config.system.idle.general.after_sleep_cmd ?? "nothingless screen on"
// Login Lock Daemon
// Helper script that listens to Lock signal and executes lockCmd from config
diff --git a/modules/services/KeyStore.qml b/modules/services/KeyStore.qml
old mode 100644
new mode 100755
diff --git a/modules/services/LockscreenService.qml b/modules/services/LockscreenService.qml
old mode 100644
new mode 100755
diff --git a/modules/services/MonitorsWriter.qml b/modules/services/MonitorsWriter.qml
new file mode 100644
index 00000000..998503c0
--- /dev/null
+++ b/modules/services/MonitorsWriter.qml
@@ -0,0 +1,184 @@
+pragma Singleton
+
+import QtQuick
+import Quickshell
+import Quickshell.Io
+
+/**
+ * MonitorsWriter - Persists monitor configuration to Hyprland config files
+ *
+ * Follows the same approach as nwg-displays:
+ * - Writes ~/.config/hypr/monitors.conf (Hyprland .conf format)
+ * - Writes ~/.config/hypr/monitors.lua (Hyprland V2 Lua format)
+ * - Creates backups before overwriting
+ * - Applies changes live via hyprctl dispatch
+ *
+ * Delegates to scripts/monitors_writer.py for the heavy lifting.
+ */
+Singleton {
+ id: root
+
+ readonly property string scriptPath: Quickshell.thisFile.parent + "../../scripts/monitors_writer.py"
+ readonly property string hyprConfigDir: (Quickshell.env("XDG_CONFIG_HOME") || (Quickshell.env("HOME") + "/.config")) + "/hypr"
+
+ property bool isSyncing: false
+ property var lastError: null
+
+ signal syncFinished(bool success, string message)
+ signal syncStarted()
+ property Process reloadProcess: Process {
+ command: ["hyprctl", "reload"]
+ running: false
+ onExited: exitCode => {
+ console.log("MonitorsWriter: hyprctl reload " + (exitCode === 0 ? "OK" : "exit " + exitCode));
+ }
+ }
+
+ /**
+ * Build a snapshot of current monitor data from AxctlService + Quickshell.screens
+ * Returns a JSON-serializable array of monitor objects.
+ */
+ function buildMonitorList() {
+ var list = [];
+ var screens = Quickshell.screens;
+ if (!screens) return list;
+
+ var axMons = AxctlService.monitors.values || [];
+
+ for (var i = 0; i < screens.length; i++) {
+ var s = screens[i];
+ var axctl = null;
+ for (var j = 0; j < axMons.length; j++) {
+ if (axMons[j].name === s.name) { axctl = axMons[j]; break; }
+ }
+
+ list.push({
+ name: s.name || ("Monitor-" + (i + 1)),
+ width: axctl ? (axctl.width || s.width || 1920) : (s.width || 1920),
+ height: axctl ? (axctl.height || s.height || 1080) : (s.height || 1080),
+ x: axctl ? (axctl.x || s.x || 0) : (s.x || 0),
+ y: axctl ? (axctl.y || s.y || 0) : (s.y || 0),
+ scale: axctl ? (axctl.scale || 1.0) : 1.0,
+ refreshRate: axctl ? (axctl.refreshRate || 60) : 60,
+ transform: axctl ? (axctl.transform || 0) : 0,
+ enabled: true,
+ bitdepth: axctl ? (axctl.bitdepth || 10) : 10,
+ vrr: axctl ? (axctl.vrr || 0) : 0,
+ mirror: "",
+ description: axctl ? (axctl.description || s.name || "") : (s.name || "")
+ });
+ }
+
+ return list;
+ }
+
+ /**
+ * Sync current monitor configuration to disk + apply live
+ * Reads current monitor state from hyprctl (via the Python script)
+ */
+ function sync() {
+ if (root.isSyncing) return;
+ root.isSyncing = true;
+ root.lastError = null;
+ root.syncStarted();
+ syncProcess.running = true;
+ }
+
+ /**
+ * Sync with explicit monitor data array (used when positions change via drag)
+ * @param {Array} monitorData - Array of monitor objects
+ */
+ function syncWithData(monitorData) {
+ if (root.isSyncing || !monitorData || monitorData.length === 0) return;
+ root.isSyncing = true;
+ root.lastError = null;
+ root.syncStarted();
+
+ var jsonStr = JSON.stringify(monitorData);
+ syncDataProcess.command = [
+ "python3", root.scriptPath, "sync",
+ "--data", jsonStr
+ ];
+ syncDataProcess.running = true;
+ }
+
+ /**
+ * Apply monitor changes live without writing to disk
+ * @param {string} dispatchCommand - hyprctl dispatch command (e.g. "monitor DP-3,3440x1440@159.96Hz,0x0,1.0")
+ */
+ function dispatchAndSync(dispatchCommand) {
+ // Apply live
+ AxctlService.dispatch(dispatchCommand);
+
+ // Debounced sync to disk
+ debounceTimer.restart();
+ }
+
+ Timer {
+ id: debounceTimer
+ interval: 2000 // 2 second debounce
+ repeat: false
+ onTriggered: root.sync()
+ }
+
+ // ββ Processes ββ
+
+ property Process syncProcess: Process {
+ command: ["python3", root.scriptPath, "sync"]
+ stdout: StdioCollector {}
+ stderr: StdioCollector {}
+ running: false
+ onExited: exitCode => {
+ root.isSyncing = false;
+ if (exitCode === 0) {
+ root.lastError = null;
+ var output = (syncProcess.stdout ? syncProcess.stdout.text : "") +
+ (syncProcess.stderr ? syncProcess.stderr.text : "");
+ console.log("MonitorsWriter: " + (output.trim() || "sync completed"));
+ root.syncFinished(true, output.trim());
+
+ // Refresh TOML config after monitor sync
+ try {
+ if (typeof CompositorTomlWriter !== "undefined") {
+ CompositorTomlWriter.writeTomlFile();
+ }
+ } catch (e) { /* CompositorTomlWriter may not be loaded yet */ }
+} else {
+ root.lastError = (syncProcess.stderr ? syncProcess.stderr.text : "exit code " + exitCode);
+ console.error("MonitorsWriter: sync failed:", root.lastError);
+ root.syncFinished(false, "Sync failed: " + root.lastError);
+ }
+ }
+ }
+
+ property Process syncDataProcess: Process {
+ stdout: StdioCollector {}
+ stderr: StdioCollector {}
+ running: false
+ onExited: exitCode => {
+ root.isSyncing = false;
+ if (exitCode === 0) {
+ root.lastError = null;
+ var output = (syncDataProcess.stdout ? syncDataProcess.stdout.text : "") +
+ (syncDataProcess.stderr ? syncDataProcess.stderr.text : "");
+ console.log("MonitorsWriter: sync with data: " + (output.trim() || "OK"));
+
+ // Apply via hyprctl reload
+ reloadProcess.running = true;
+
+ root.syncFinished(true, output.trim());
+
+ // Refresh TOML config after monitor sync
+ try {
+ if (typeof CompositorTomlWriter !== "undefined") {
+ CompositorTomlWriter.writeTomlFile();
+ }
+ } catch (e) { /* CompositorTomlWriter may not be loaded yet */ }
+} else {
+ root.lastError = (syncDataProcess.stderr ? syncDataProcess.stderr.text : "exit code " + exitCode);
+ console.error("MonitorsWriter: sync with data failed:", root.lastError);
+ root.syncFinished(false, "Failed: " + root.lastError);
+ }
+ }
+ }
+}
diff --git a/modules/services/MprisController.qml b/modules/services/MprisController.qml
old mode 100644
new mode 100755
index 53868ca0..e46a7888
--- a/modules/services/MprisController.qml
+++ b/modules/services/MprisController.qml
@@ -15,7 +15,7 @@ Singleton {
property var filteredPlayers: {
const filtered = Mpris.players.values.filter(player => {
const dbusName = (player.dbusName || "").toLowerCase();
- if (!Config.bar.enableFirefoxPlayer && dbusName.includes("firefox")) {
+ if (!Config.bar.enableFirefoxPlayer && dbusName.includes("firefox") || !Config.bar.enableChromiumPlayer && (dbusName.includes("chromium") || dbusName.includes("chrome"))) {
return false;
}
return true;
@@ -155,7 +155,7 @@ Singleton {
Component.onCompleted: {
const dbusName = (modelData.dbusName || "").toLowerCase();
- const shouldIgnore = !Config.bar.enableFirefoxPlayer && dbusName.includes("firefox");
+ const shouldIgnore = !Config.bar.enableFirefoxPlayer && dbusName.includes("firefox") || !Config.bar.enableChromiumPlayer && (dbusName.includes("chromium") || dbusName.includes("chrome"));
if (!shouldIgnore && (root.trackedPlayer == null || modelData.isPlaying)) {
root.trackedPlayer = modelData;
diff --git a/modules/services/NetworkService.qml b/modules/services/NetworkService.qml
old mode 100644
new mode 100755
diff --git a/modules/services/NightLightService.qml b/modules/services/NightLightService.qml
old mode 100644
new mode 100755
diff --git a/modules/services/Notifications.qml b/modules/services/Notifications.qml
old mode 100644
new mode 100755
index 5a432a52..ea424789
--- a/modules/services/Notifications.qml
+++ b/modules/services/Notifications.qml
@@ -25,6 +25,9 @@ Singleton {
property string summary: ""
property double time
property string urgency: "normal"
+ property int historyPriority: 0
+ property string replaceKey: ""
+ property var localActionHandlers: ({})
property Timer timer
// Propiedades para cache de imΓ‘genes
@@ -86,6 +89,8 @@ Singleton {
"summary": notif.summary,
"time": notif.time,
"urgency": notif.urgency,
+ "historyPriority": notif.historyPriority,
+ "replaceKey": notif.replaceKey,
"cachedAppIcon": notif.cachedAppIcon,
"cachedImage": notif.cachedImage,
"isCached": notif.isCached
@@ -150,7 +155,7 @@ Singleton {
FileView {
id: notifFileView
// QUICKSHELL-GIT: path: Quickshell.cachePath("notifications.json")
- path: Quickshell.env("HOME") + "/.cache/ambxst/notifications.json"
+ path: Quickshell.env("HOME") + "/.cache/nothingless/notifications.json"
onLoaded: loadNotifications()
}
@@ -171,6 +176,8 @@ Singleton {
"summary": json.summary,
"time": json.time,
"urgency": json.urgency,
+ "historyPriority": json.historyPriority || 0,
+ "replaceKey": json.replaceKey || "",
"cachedAppIcon": json.cachedAppIcon || "",
"cachedImage": json.cachedImage || "",
"isCached": json.isCached || true // Default to true for loaded notifications
@@ -215,6 +222,8 @@ Singleton {
root.list.forEach(notif => {
if (notif.id > maxId)
maxId = notif.id;
+ if (notif.id <= -1000000)
+ root.internalIdCounter = Math.max(root.internalIdCounter, Math.abs(notif.id) - 999999);
});
root.idOffset = maxId + 1;
} catch (e) {
@@ -241,7 +250,9 @@ Singleton {
function appNameListForGroups(groups) {
return Object.keys(groups).sort((a, b) => {
- // Sort by time, descending
+ if (groups[b].historyPriority !== groups[a].historyPriority) {
+ return groups[b].historyPriority - groups[a].historyPriority;
+ }
return groups[b].time - groups[a].time;
});
}
@@ -260,6 +271,7 @@ Singleton {
appIcon: notif.appIcon,
notifications: [],
time: 0,
+ historyPriority: 0,
totalCount: 0 // Conteo independiente del almacenamiento
};
}
@@ -267,6 +279,7 @@ Singleton {
groups[notif.appName].totalCount++;
// Always set to the latest time in the group
groups[notif.appName].time = latestTimeForApp[notif.appName] || notif.time;
+ groups[notif.appName].historyPriority = Math.max(groups[notif.appName].historyPriority || 0, notif.historyPriority || 0);
});
return groups;
@@ -280,6 +293,7 @@ Singleton {
// Quickshell's notification IDs starts at 1 on each run, while saved notifications
// can already contain higher IDs. This is for avoiding id collisions
property int idOffset
+ property int internalIdCounter: 1
signal initDone
signal notify(notification: var)
signal discard(id: var)
@@ -329,6 +343,49 @@ Singleton {
}
}
+ function notifyInternal(options) {
+ if (!options || (!options.summary && !options.body)) {
+ return null;
+ }
+
+ if (options.replaceKey) {
+ const existingIds = root.list.filter(notif => notif && notif.replaceKey === options.replaceKey).map(notif => notif.id);
+ if (existingIds.length > 0) {
+ root.discardNotifications(existingIds);
+ }
+ }
+
+ const notificationId = -1000000 - root.internalIdCounter++;
+ const newNotifObject = notifComponent.createObject(root, {
+ "id": notificationId,
+ "actions": options.actions || [],
+ "appIcon": options.appIcon || "",
+ "appName": options.appName || "NothingLess",
+ "body": options.body || "",
+ "image": options.image || "",
+ "summary": options.summary || "",
+ "time": options.time || Date.now(),
+ "urgency": options.urgency || NotificationUrgency.Normal,
+ "historyPriority": options.historyPriority || 0,
+ "replaceKey": options.replaceKey || "",
+ "localActionHandlers": options.actionHandlers || {},
+ "popup": !root.popupInhibited && options.popup !== false,
+ "isCached": false
+ });
+
+ if (newNotifObject.popup) {
+ newNotifObject.timer = notifTimerComponent.createObject(root, {
+ "id": newNotifObject.id,
+ "interval": options.expireTimeout || 5000
+ });
+ }
+
+ root.list = [...root.list, newNotifObject];
+ saveNotifications();
+ root.notify(newNotifObject);
+ return newNotifObject;
+ }
+
function discardNotification(id) {
const index = root.list.findIndex(notif => notif.id === id);
const notifServerIndex = notifServer.trackedNotifications.values.findIndex(notif => notif.id + root.idOffset === id);
@@ -412,12 +469,23 @@ Singleton {
}
function attemptInvokeAction(id, notifIdentifier, autoDiscard = true) {
+ const notifIndex = root.list.findIndex(notif => notif.id === id);
+ if (notifIndex !== -1) {
+ const localHandlers = root.list[notifIndex].localActionHandlers || {};
+ const localHandler = localHandlers[notifIdentifier];
+ if (typeof localHandler === "function") {
+ localHandler(id);
+ }
+ }
+
const notifServerIndex = notifServer.trackedNotifications.values.findIndex(notif => notif.id + root.idOffset === id);
if (notifServerIndex !== -1) {
const notifServerNotif = notifServer.trackedNotifications.values[notifServerIndex];
const action = notifServerNotif.actions.find(action => action.identifier === notifIdentifier);
- action.invoke();
- } else {}
+ if (action) {
+ action.invoke();
+ }
+ }
if (autoDiscard) {
root.discardNotification(id);
}
diff --git a/modules/services/PowerProfile.qml b/modules/services/PowerProfile.qml
old mode 100644
new mode 100755
diff --git a/modules/services/PresetsService.qml b/modules/services/PresetsService.qml
old mode 100644
new mode 100755
index 4cc1369f..7c7143ce
--- a/modules/services/PresetsService.qml
+++ b/modules/services/PresetsService.qml
@@ -16,7 +16,7 @@ Singleton {
property string activePreset: ""
// Config directory paths
- readonly property string configDir: (Quickshell.env("XDG_CONFIG_HOME") || (Quickshell.env("HOME") + "/.config")) + "/ambxst"
+ readonly property string configDir: (Quickshell.env("XDG_CONFIG_HOME") || (Quickshell.env("HOME") + "/.config")) + "/nothingless"
readonly property string presetsDir: configDir + "/presets"
readonly property string assetsPresetsDir: Qt.resolvedUrl("../../assets/presets").toString().replace("file://", "")
readonly property string activePresetFile: presetsDir + "/active_preset"
@@ -117,20 +117,6 @@ Singleton {
const jsonFile = configFile.replace('.js', '.json')
if (root.excludedFiles.includes(jsonFile)) continue;
- // The source is configDir (~/.config/Ambxst), NOT configDir/config
- // But wait, the configDir property is defined as ~/.config/Ambxst below?
- // Let's check the property definition.
- // property string configDir: ... + "/Ambxst"
- // But Config.qml says configDir is ... + "/Ambxst/config"
- // We need to match Config.qml's path.
-
- // In Config.qml: property string configDir: ... + "/Ambxst/config"
- // Here: readonly property string configDir: ... + "/Ambxst"
- // This is a mismatch!
-
- // We should use the same path as Config.qml for reading/writing config files.
- // Let's assume the files are in .../Ambxst/config based on Config.qml and ls output.
-
const srcPath = configDir + "/config/" + jsonFile
const dstPath = presetPath + "/" + jsonFile
copyCmd += `cp "${srcPath}" "${dstPath}" && `
diff --git a/modules/services/ScreenRecorder.qml b/modules/services/ScreenRecorder.qml
old mode 100644
new mode 100755
diff --git a/modules/services/Screenshot.qml b/modules/services/Screenshot.qml
old mode 100644
new mode 100755
index d6bcd485..aef8f649
--- a/modules/services/Screenshot.qml
+++ b/modules/services/Screenshot.qml
@@ -16,8 +16,8 @@ QtObject {
signal lensImageReady(string path)
signal imageSaved(string path) // New signal for Overlay
- property string tempPathBase: "/tmp/ambxst_freeze"
- property string cropPath: "/tmp/ambxst_crop.png"
+ property string tempPathBase: "/tmp/nothingless_freeze"
+ property string cropPath: "/tmp/nothingless_crop.png"
property string lensPath: "/tmp/image.png"
property string captureMode: "normal"
diff --git a/modules/services/StateService.qml b/modules/services/StateService.qml
old mode 100644
new mode 100755
diff --git a/modules/services/SuspendManager.qml b/modules/services/SuspendManager.qml
old mode 100644
new mode 100755
diff --git a/modules/services/SystemResources.qml b/modules/services/SystemResources.qml
old mode 100644
new mode 100755
index c16ff085..69698a57
--- a/modules/services/SystemResources.qml
+++ b/modules/services/SystemResources.qml
@@ -8,70 +8,182 @@ pragma ComponentBehavior: Bound
/**
* System resource monitoring service
- * Optimized to be lightweight and avoid waking up dGPUs.
+ * Optimised to be lightweight and avoid waking up dGPUs.
*/
Singleton {
id: root
- // CPU metrics
+ // ββ Toggles (set by MetricsConfigPanel) ββ
+ property bool cpuUsageEnabled: true
+ property bool cpuTempEnabled: true
+ property bool cpuPowerEnabled: false
+ property bool ramEnabled: true
+ property bool gpuUsageEnabled: true
+ property bool gpuTempEnabled: true
+ property bool gpuPowerEnabled: false
+ property bool diskEnabled: true
+ property bool fpsEnabled: true
+
+ // ββ Metric colours (set by MetricsConfigPanel) ββ
+ property color metricColorCpu: "#ffffff"
+ property color metricColorGpu: "#ffffff"
+ property color metricColorFps: "#ffffff"
+ property color metricColorRam: "#ffffff"
+ property color metricColorDisk: "#ffffff"
+
+ // ββ CPU metrics ββ
property real cpuUsage: 0.0
property string cpuModel: ""
property int cpuTemp: -1
+ property real cpuPower: 0.0
- // RAM metrics
+ // ββ RAM metrics ββ
property real ramUsage: 0.0
property real ramTotal: 0
property real ramUsed: 0
property real ramAvailable: 0
- // GPU metrics
+ // ββ GPU metrics ββ
property var gpuUsages: []
property var gpuVendors: []
property var gpuNames: []
property int gpuCount: 0
property bool gpuDetected: false
property var gpuTemps: []
-
- // Legacy single GPU properties
+ property real gpuPower: 0.0
+
+ // Legacy singleβGPU convenience
property real gpuUsage: gpuUsages.length > 0 ? gpuUsages[0] : 0.0
property string gpuVendor: gpuVendors.length > 0 ? gpuVendors[0] : "unknown"
property int gpuTemp: gpuTemps.length > 0 ? gpuTemps[0] : -1
- // Disk metrics
+ // ββ Disk ββ
property var diskUsage: ({})
property var diskTypes: ({})
property var validDisks: []
- // History data
+ // ββ FPS ββ
+ property real fps: 0.0
+
+ // ββ Version bump (triggers rebuildNotchMetrics) ββ
+ property int notchVersion: 0
+
+ // ββ Convenience ββ
+ property bool metricsAvailable: true
+
+ // ββ Config persistence ββ
+ property string metricsConfigPath: Quickshell.env("HOME") + "/.config/nothingless/config/metrics.json"
+
+ function saveMetricsConfig() {
+ var config = JSON.stringify({
+ cpuUsageEnabled: cpuUsageEnabled,
+ cpuTempEnabled: cpuTempEnabled,
+ cpuPowerEnabled: cpuPowerEnabled,
+ ramEnabled: ramEnabled,
+ gpuUsageEnabled: gpuUsageEnabled,
+ gpuTempEnabled: gpuTempEnabled,
+ gpuPowerEnabled: gpuPowerEnabled,
+ fpsEnabled: fpsEnabled,
+ diskEnabled: diskEnabled,
+ metricColorCpu: metricColorCpu,
+ metricColorGpu: metricColorGpu,
+ metricColorFps: metricColorFps,
+ metricColorRam: metricColorRam,
+ metricColorDisk: metricColorDisk
+ });
+ var cmd = "mkdir -p $(dirname " + metricsConfigPath + ") && echo '" + config + "' > " + metricsConfigPath;
+ saveProcess.command = ["sh", "-c", cmd];
+ saveProcess.running = true;
+ }
+
+ function loadMetricsConfig() {
+ loadProcess.command = ["cat", metricsConfigPath];
+ loadProcess.running = true;
+ }
+
+ // ββ Fast FPS watcher (tail -f /dev/shm/nothingless_fps) ββ
+ property Process fpsWatcher: Process {
+ id: fpsWatcher
+ running: root.notchMetricsActive || (GlobalStates.dashboardOpen && GlobalStates.dashboardCurrentTab === 2)
+ command: ["bash", "-c", "tail -n0 -F /dev/shm/nothingless_fps 2>/dev/null || sleep 10"]
+ stdout: SplitParser {
+ onRead: data => {
+ var trimmed = data.trim();
+ if (trimmed.startsWith("fps=")) {
+ var val = parseFloat(trimmed.split("=", 2)[1]);
+ if (!isNaN(val) && val > 0) root.fps = val;
+ }
+ }
+ }
+ }
+
+ property Process saveProcess: Process {
+ running: false
+ }
+
+ property Process loadProcess: Process {
+ running: false
+ stdout: SplitParser {
+ onRead: data => {
+ try {
+ var cfg = JSON.parse(data);
+ if (cfg.cpuUsageEnabled !== undefined) root.cpuUsageEnabled = cfg.cpuUsageEnabled;
+ if (cfg.cpuTempEnabled !== undefined) root.cpuTempEnabled = cfg.cpuTempEnabled;
+ if (cfg.cpuPowerEnabled !== undefined) root.cpuPowerEnabled = cfg.cpuPowerEnabled;
+ if (cfg.ramEnabled !== undefined) root.ramEnabled = cfg.ramEnabled;
+ if (cfg.gpuUsageEnabled !== undefined) root.gpuUsageEnabled = cfg.gpuUsageEnabled;
+ if (cfg.gpuTempEnabled !== undefined) root.gpuTempEnabled = cfg.gpuTempEnabled;
+ if (cfg.gpuPowerEnabled !== undefined) root.gpuPowerEnabled = cfg.gpuPowerEnabled;
+ if (cfg.fpsEnabled !== undefined) root.fpsEnabled = cfg.fpsEnabled;
+ if (cfg.diskEnabled !== undefined) root.diskEnabled = cfg.diskEnabled;
+ if (cfg.metricColorCpu) root.metricColorCpu = cfg.metricColorCpu;
+ if (cfg.metricColorGpu) root.metricColorGpu = cfg.metricColorGpu;
+ if (cfg.metricColorFps) root.metricColorFps = cfg.metricColorFps;
+ if (cfg.metricColorRam) root.metricColorRam = cfg.metricColorRam;
+ if (cfg.metricColorDisk) root.metricColorDisk = cfg.metricColorDisk;
+ } catch (e) {
+ console.warn("Failed to load metrics config:", e);
+ }
+ }
+ }
+ }
+
+ // ββ History ββ
property var cpuHistory: []
property var ramHistory: []
property var gpuHistories: []
property var cpuTempHistory: []
property var gpuTempHistories: []
+ property var fpsHistory: []
property int maxHistoryPoints: 50
property int totalDataPoints: 0
- // Update interval
+ // ββ Update interval ββ
property int updateInterval: 2000
- // Unified monitor process.
- // Resource-efficient: only runs when dashboard is open.
- // Optimized GPU polling avoids waking dGPUs.
+ // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ // Monitor process β runs when the dashboard metrics tab is open
+ // OR when the notch metrics overlay is active.
+ // ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ property bool notchMetricsActive: Config.notch && Config.notch.showMetrics === true
+
property Process monitorProcess: Process {
id: monitorProcess
- running: GlobalStates.dashboardOpen && GlobalStates.dashboardCurrentTab === 2 && root.validDisks.length > 0
-
+
+ // Run when either dashboard metrics tab OR notch metrics mode is active
+ running: (GlobalStates.dashboardOpen && GlobalStates.dashboardCurrentTab === 2) || root.notchMetricsActive
+
command: {
let cmd = ["python3", Quickshell.shellDir + "/scripts/system_monitor.py", root.updateInterval.toString()];
return cmd.concat(root.validDisks);
}
-
+
stdout: SplitParser {
onRead: data => {
try {
const stats = JSON.parse(data);
-
- // Static info (received once at start)
+
+ // ββ Static info (received once) ββ
if (stats.static) {
root.cpuModel = stats.static.cpu_model || root.cpuModel;
root.gpuNames = stats.static.gpu_names || [];
@@ -82,35 +194,48 @@ Singleton {
return;
}
- // Update metrics
+ // ββ CPU ββ
if (stats.cpu) {
root.cpuUsage = stats.cpu.usage;
root.cpuTemp = stats.cpu.temp;
+ // power is sent by the script when available
+ if (stats.cpu.power !== undefined) root.cpuPower = stats.cpu.power;
}
-
+
+ // ββ RAM ββ
if (stats.ram) {
root.ramUsage = stats.ram.usage;
root.ramTotal = stats.ram.total;
root.ramUsed = stats.ram.used;
root.ramAvailable = stats.ram.available;
}
-
+
+ // ββ Disk ββ
if (stats.disk) root.diskUsage = stats.disk.usage;
-
+
+ // ββ GPU ββ
if (stats.gpu) {
root.gpuUsages = stats.gpu.usages;
root.gpuTemps = stats.gpu.temps;
+ if (stats.gpu.power !== undefined) root.gpuPower = stats.gpu.power;
}
-
+
+ // ββ FPS (via MangoHud or generic) ββ
+ if (stats.fps !== undefined) root.fps = stats.fps;
+
root.updateHistory();
} catch (e) {
- console.warn("SystemResources: Failed to parse monitor data: " + e);
+ console.warn("SystemResources: parse error: " + e);
}
}
}
}
- Component.onCompleted: validateDisks()
+ // ββ Lifecycle ββ
+ Component.onCompleted: {
+ validateDisks();
+ loadMetricsConfig();
+ }
Connections {
target: Config.system
@@ -122,6 +247,7 @@ Singleton {
onValidDisksChanged: if (monitorProcess.running) restartMonitor()
onUpdateIntervalChanged: if (monitorProcess.running) restartMonitor()
+ onNotchMetricsActiveChanged: if (!monitorProcess.running && notchMetricsActive) restartMonitor()
function restartMonitor() {
monitorProcess.running = false;
@@ -143,8 +269,7 @@ Singleton {
function updateHistory() {
totalDataPoints++;
-
- // Helper to update history arrays
+
const pushHistory = (arr, val) => {
let next = arr.slice();
next.push(val);
@@ -159,17 +284,20 @@ Singleton {
if (gpuDetected && gpuCount > 0) {
let newGpuHistories = gpuHistories.slice();
let newGpuTempHistories = gpuTempHistories.slice();
-
+
while (newGpuHistories.length < gpuCount) newGpuHistories.push([]);
while (newGpuTempHistories.length < gpuCount) newGpuTempHistories.push([]);
-
+
for (let i = 0; i < gpuCount; i++) {
newGpuHistories[i] = pushHistory(newGpuHistories[i], (gpuUsages[i] || 0) / 100);
newGpuTempHistories[i] = pushHistory(newGpuTempHistories[i], (gpuTemps[i] ?? -1));
}
-
+
gpuHistories = newGpuHistories;
gpuTempHistories = newGpuTempHistories;
}
+
+ // FPS history
+ fpsHistory = pushHistory(fpsHistory, fps);
}
}
diff --git a/modules/services/TaskbarApps.qml b/modules/services/TaskbarApps.qml
old mode 100644
new mode 100755
index a007a97d..15bf18db
--- a/modules/services/TaskbarApps.qml
+++ b/modules/services/TaskbarApps.qml
@@ -5,6 +5,7 @@ import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.config
+import qs.modules.services
Singleton {
id: root
@@ -42,7 +43,7 @@ Singleton {
function launchApp(appId) {
const entry = getDesktopEntry(appId);
if (entry) {
- entry.execute();
+ AppSearch.launchApp(entry);
}
}
@@ -73,15 +74,19 @@ Singleton {
}
// Update on config change
+ // FIX: Guard enabled to prevent segfault when config properties are null mid-incubation
Connections {
target: Config.pinnedApps ?? null
+ enabled: Config.pinnedApps !== null
function onAppsChanged() {
updateTimer.restart();
}
}
+ // FIX: Guard enabled to prevent segfault when config properties are null mid-incubation
Connections {
target: Config.dock ?? null
+ enabled: Config.dock !== null
function onIgnoredAppRegexesChanged() {
updateTimer.restart();
}
diff --git a/modules/services/UpdateService.qml b/modules/services/UpdateService.qml
old mode 100644
new mode 100755
index a4aae614..0a610601
--- a/modules/services/UpdateService.qml
+++ b/modules/services/UpdateService.qml
@@ -9,10 +9,10 @@ Singleton {
id: root
readonly property string currentVersion: Config.version
- readonly property string repoUrl: "https://api.github.com/repos/Axenide/Ambxst/tags"
- readonly property string changelogUrl: "https://axeni.de/ambxst/changelog"
+ readonly property string repoUrl: "https://api.github.com/repos/Leriart/NothingLess/tags"
+ readonly property string changelogUrl: "https://github.com/Leriart/NothingLess/releases"
// QUICKSHELL-GIT: readonly property string cacheFile: Quickshell.cachePath("update_check.json")
- readonly property string cacheFile: Quickshell.env("HOME") + "/.cache/ambxst/update_check.json"
+ readonly property string cacheFile: Quickshell.env("HOME") + "/.cache/nothingless/update_check.json"
property string lastDetectedVersion: ""
property double lastCheckTime: 0
@@ -124,7 +124,7 @@ Singleton {
if (typeof Notifications === "undefined" || !Notifications.list) return false;
for (let i = 0; i < Notifications.list.length; i++) {
const notif = Notifications.list[i];
- if (notif && notif.appName === "Ambxst Update") {
+ if (notif && notif.appName === "NothingLess Update") {
return true;
}
}
@@ -132,9 +132,9 @@ Singleton {
}
function sendUpdateNotification(newVersion) {
- const summary = "Ambxst update available!";
+ const summary = "NothingLess update available!";
const body = newVersion + " available! (Installed " + root.currentVersion + ")";
- const cmd = "notify-send -a 'Ambxst Update' -i system-software-update -w '" + summary + "' '" + body + "' --action=changelog=Changelog --action=later='Maybe later' --action=update=Update";
+ const cmd = "notify-send -a 'NothingLess Update' -i system-software-update -w '" + summary + "' '" + body + "' --action=changelog=Changelog --action=later='Maybe later' --action=update=Update";
notificationProcess.running = false;
notificationProcess.command = ["bash", "-c", cmd];
@@ -154,7 +154,7 @@ Singleton {
root.nextCheckTime = Date.now() + 8 * 3600000;
root.saveCache();
} else if (action === "update") {
- const updateCmd = "kitty -o allow_remote_control=yes --listen-on unix:/tmp/mykitty sh -c \"sleep 0.2 && kitten @ --to unix:/tmp/mykitty send-text 'ambxst update'; exec $SHELL\"";
+ const updateCmd = "kitty -o allow_remote_control=yes --listen-on unix:/tmp/mykitty sh -c \"sleep 0.2 && kitten @ --to unix:/tmp/mykitty send-text 'nothingless update'; exec $SHELL\"";
Quickshell.execDetached(["bash", "-c", updateCmd]);
}
}
diff --git a/modules/services/UsageTracker.qml b/modules/services/UsageTracker.qml
old mode 100644
new mode 100755
index 6905912c..a38b3fba
--- a/modules/services/UsageTracker.qml
+++ b/modules/services/UsageTracker.qml
@@ -8,7 +8,7 @@ Singleton {
// usage.json path
// QUICKSHELL-GIT: property string usageFilePath: Quickshell.cachePath("usage.json")
- property string usageFilePath: Quickshell.env("HOME") + "/.cache/ambxst/usage.json"
+ property string usageFilePath: Quickshell.env("HOME") + "/.cache/nothingless/usage.json"
// Cache: { appId: { count, lastUsed } }
property var usageData: ({})
diff --git a/modules/services/Visibilities.qml b/modules/services/Visibilities.qml
old mode 100644
new mode 100755
diff --git a/modules/services/WeatherService.qml b/modules/services/WeatherService.qml
old mode 100644
new mode 100755
diff --git a/modules/services/WifiAccessPoint.qml b/modules/services/WifiAccessPoint.qml
old mode 100644
new mode 100755
diff --git a/modules/services/ai/AiModel.qml b/modules/services/ai/AiModel.qml
old mode 100644
new mode 100755
diff --git a/modules/services/ai/strategies/AnthropicApiStrategy.qml b/modules/services/ai/strategies/AnthropicApiStrategy.qml
old mode 100644
new mode 100755
diff --git a/modules/services/ai/strategies/ApiStrategy.qml b/modules/services/ai/strategies/ApiStrategy.qml
old mode 100644
new mode 100755
diff --git a/modules/services/ai/strategies/DeepSeekApiStrategy.qml b/modules/services/ai/strategies/DeepSeekApiStrategy.qml
new file mode 100644
index 00000000..eff77725
--- /dev/null
+++ b/modules/services/ai/strategies/DeepSeekApiStrategy.qml
@@ -0,0 +1,88 @@
+import QtQuick
+
+// DeepSeek API strategy β uses OpenAI-compatible format
+// endpoint: https://api.deepseek.com/v1/chat/completions
+ApiStrategy {
+ supportsStreaming: true
+
+ function getEndpoint(modelObj, apiKey) {
+ let base = modelObj.endpoint || "https://api.deepseek.com";
+ if (base.endsWith("/v1"))
+ return base + "/chat/completions";
+ return base + "/v1/chat/completions";
+ }
+
+ function getHeaders(apiKey) {
+ return [
+ "Content-Type: application/json",
+ "Authorization: Bearer " + apiKey
+ ];
+ }
+
+ function _formatMessages(messages) {
+ let formatted = [];
+ for (let i = 0; i < messages.length; i++) {
+ let msg = messages[i];
+ if (msg.attachments && msg.attachments.length > 0) {
+ let contentParts = [{type: "text", text: msg.content}];
+ for (let j = 0; j < msg.attachments.length; j++) {
+ let att = msg.attachments[j];
+ if (att.type === "image") {
+ contentParts.push({
+ type: "image_url",
+ image_url: { url: att.url }
+ });
+ }
+ }
+ formatted.push({ role: msg.role, content: contentParts });
+ } else {
+ formatted.push({ role: msg.role, content: msg.content });
+ }
+ }
+ return formatted;
+ }
+
+ function buildRequestBody(modelObj, messages, systemPrompt, options) {
+ let body = {
+ model: modelObj.model || "deepseek-chat",
+ messages: []
+ };
+
+ if (systemPrompt) {
+ body.messages.push({ role: "system", content: systemPrompt });
+ }
+
+ let formatted = _formatMessages(messages);
+ for (let i = 0; i < formatted.length; i++) {
+ body.messages.push(formatted[i]);
+ }
+
+ if (options?.temperature != null) body.temperature = options.temperature;
+ if (options?.maxTokens) body.max_tokens = options.maxTokens;
+ if (options?.stream != null) body.stream = options.stream;
+
+ return body;
+ }
+
+ function parseResponse(data) {
+ try {
+ let json = JSON.parse(data);
+ if (json.choices && json.choices.length > 0) {
+ let content = json.choices[0].delta?.content || json.choices[0].message?.content || "";
+ return { content, done: json.choices[0].finish_reason != null };
+ }
+ } catch (e) {}
+ return { content: "", done: false };
+ }
+
+ function parseFullResponse(data) {
+ try {
+ let json = JSON.parse(data);
+ if (json.choices && json.choices.length > 0) {
+ let content = json.choices[0].message?.content || "";
+ return { content, model: json.model };
+ }
+ } catch (e) {}
+ return { content: "", model: "" };
+ }
+}
diff --git a/modules/services/ai/strategies/GeminiApiStrategy.qml b/modules/services/ai/strategies/GeminiApiStrategy.qml
old mode 100644
new mode 100755
diff --git a/modules/services/ai/strategies/GroqApiStrategy.qml b/modules/services/ai/strategies/GroqApiStrategy.qml
old mode 100644
new mode 100755
diff --git a/modules/services/ai/strategies/MiniMaxApiStrategy.qml b/modules/services/ai/strategies/MiniMaxApiStrategy.qml
old mode 100644
new mode 100755
diff --git a/modules/services/ai/strategies/MistralApiStrategy.qml b/modules/services/ai/strategies/MistralApiStrategy.qml
old mode 100644
new mode 100755
diff --git a/modules/services/ai/strategies/OllamaApiStrategy.qml b/modules/services/ai/strategies/OllamaApiStrategy.qml
old mode 100644
new mode 100755
diff --git a/modules/services/ai/strategies/OpenAiApiStrategy.qml b/modules/services/ai/strategies/OpenAiApiStrategy.qml
old mode 100644
new mode 100755
diff --git a/modules/services/clipboard_init.sql b/modules/services/clipboard_init.sql
old mode 100644
new mode 100755
index c3e3b2df..746c1392
--- a/modules/services/clipboard_init.sql
+++ b/modules/services/clipboard_init.sql
@@ -1,4 +1,4 @@
--- Ambxst Clipboard Database Schema
+-- NothingLess Clipboard Database Schema
-- Simplified version based on Vicinae's clipboard system
CREATE TABLE IF NOT EXISTS clipboard_items (
diff --git a/modules/shell/ReservationWindows.qml b/modules/shell/ReservationWindows.qml
old mode 100644
new mode 100755
index 26da91a8..42936a21
--- a/modules/shell/ReservationWindows.qml
+++ b/modules/shell/ReservationWindows.qml
@@ -53,7 +53,7 @@ Item {
}
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
- WlrLayershell.namespace: "ambxst:reservation:top"
+ WlrLayershell.namespace: "nothingless:reservation:top"
exclusiveZone: {
if (!Config.barReady) return 0;
@@ -85,7 +85,7 @@ Item {
}
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
- WlrLayershell.namespace: "ambxst:reservation:bottom"
+ WlrLayershell.namespace: "nothingless:reservation:bottom"
exclusiveZone: {
if (!Config.barReady) return 0;
@@ -117,7 +117,7 @@ Item {
}
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
- WlrLayershell.namespace: "ambxst:reservation:left"
+ WlrLayershell.namespace: "nothingless:reservation:left"
exclusiveZone: {
if (!Config.barReady) return 0;
@@ -153,7 +153,7 @@ Item {
}
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
- WlrLayershell.namespace: "ambxst:reservation:right"
+ WlrLayershell.namespace: "nothingless:reservation:right"
exclusiveZone: {
if (!Config.barReady) return 0;
diff --git a/modules/shell/UnifiedShellPanel.qml b/modules/shell/UnifiedShellPanel.qml
old mode 100644
new mode 100755
index 028c2dce..c4e88ef3
--- a/modules/shell/UnifiedShellPanel.qml
+++ b/modules/shell/UnifiedShellPanel.qml
@@ -39,7 +39,7 @@ PanelWindow {
}
return WlrKeyboardFocus.None;
}
- WlrLayershell.namespace: "ambxst"
+ WlrLayershell.namespace: "nothingless"
WlrLayershell.layer: WlrLayer.Overlay
exclusionMode: ExclusionMode.Ignore
@@ -206,7 +206,17 @@ PanelWindow {
id: visualContent
anchors.fill: parent
- layer.enabled: true
+ // β‘ Only enable the offscreen layer when something is actually visible.
+ // When bar/dock/notch/frame are all hidden, no shadow is needed.
+ // This saves a full-screen render-to-texture pass every frame.
+ readonly property bool needLayer:
+ (unifiedPanel.barEnabled && unifiedPanel.barReveal) ||
+ (unifiedPanel.dockEnabled && unifiedPanel.dockReveal) ||
+ unifiedPanel.notchReveal ||
+ assistantSidebar.active ||
+ (Config.bar?.frameEnabled ?? false)
+
+ layer.enabled: needLayer
layer.effect: Shadow {}
ScreenFrameContent {
diff --git a/modules/shell/osd/OSD.qml b/modules/shell/osd/OSD.qml
old mode 100644
new mode 100755
index 432f771d..162f3400
--- a/modules/shell/osd/OSD.qml
+++ b/modules/shell/osd/OSD.qml
@@ -16,7 +16,7 @@ PanelWindow {
screen: targetScreen
WlrLayershell.layer: WlrLayer.Overlay
- WlrLayershell.namespace: "ambxst:osd"
+ WlrLayershell.namespace: "nothingless:osd"
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
exclusionMode: ExclusionMode.Ignore
diff --git a/modules/sidebar/AssistantSidebar.qml b/modules/sidebar/AssistantSidebar.qml
old mode 100644
new mode 100755
index be201de3..25e55283
--- a/modules/sidebar/AssistantSidebar.qml
+++ b/modules/sidebar/AssistantSidebar.qml
@@ -751,6 +751,8 @@ Item {
anchors.fill: parent
source: "file://" + Quickshell.env("HOME") + "/.face.icon"
fillMode: Image.PreserveAspectCrop
+ sourceSize.width: 32
+ sourceSize.height: 32
onStatusChanged: {
if (status === Image.Error) {
diff --git a/modules/sidebar/CodeBlock.qml b/modules/sidebar/CodeBlock.qml
old mode 100644
new mode 100755
diff --git a/modules/sidebar/ModelSelectorPopup.qml b/modules/sidebar/ModelSelectorPopup.qml
old mode 100644
new mode 100755
diff --git a/modules/theme/AGENTS.md b/modules/theme/AGENTS.md
old mode 100644
new mode 100755
index b3038c4e..5756a43c
--- a/modules/theme/AGENTS.md
+++ b/modules/theme/AGENTS.md
@@ -6,7 +6,7 @@ Dynamic theming layer providing colors, icons, and style utilities as singletons
## STRUCTURE
| File | Type | Role |
|------|------|------|
-| `Colors.qml` | Singleton | Watches `~/.cache/ambxst/colors.json`. Provides reactive palette (`primary`, `secondary`, `surface`, `onSurface`, etc.) |
+| `Colors.qml` | Singleton | Watches `~/.cache/NothingLess/colors.json`. Provides reactive palette (`primary`, `secondary`, `surface`, `onSurface`, etc.) |
| `Styling.qml` | Singleton | `radius(offset)`, `fontSize(offset)`, `getStyledRectConfig(variant)`. Animation durations, spacing constants |
| `Icons.qml` | Singleton | Character map for Phosphor-Bold icon font (`lock`, `power`, `layout`, etc.) |
| `*Generator.qml` | Components | Translate `Colors` palette into config files for other apps |
@@ -22,7 +22,7 @@ Dynamic theming layer providing colors, icons, and style utilities as singletons
## WHERE TO LOOK
| Task | Location | Notes |
|------|----------|-------|
-| **Change colors** | `Colors.qml` | Modify `~/.cache/ambxst/colors.json` or change color preset |
+| **Change colors** | `Colors.qml` | Modify `~/.cache/NothingLess/colors.json` or change color preset |
| **Add StyledRect variant** | `Styling.qml` β `getStyledRectConfig()` | Returns gradient, border, opacity config per variant |
| **Adjust radius/font** | `Styling.qml` | `radius(offset)` and `fontSize(offset)` apply global scaling |
| **Add icon** | `Icons.qml` | Add Phosphor-Bold unicode mapping |
diff --git a/modules/theme/Colors.qml b/modules/theme/Colors.qml
old mode 100644
new mode 100755
index 15a18dc2..34d167ac
--- a/modules/theme/Colors.qml
+++ b/modules/theme/Colors.qml
@@ -7,7 +7,7 @@ import qs.config
FileView {
id: colors
// QUICKSHELL-GIT: path: Quickshell.cachePath("colors.json")
- path: Quickshell.env("HOME") + "/.cache/ambxst/colors.json"
+ path: Quickshell.env("HOME") + "/.cache/nothingless/colors.json"
preload: true
watchChanges: true
onFileChanged: {
diff --git a/modules/theme/DiscordGenerator.qml b/modules/theme/DiscordGenerator.qml
old mode 100644
new mode 100755
index f2c8533c..9507cc38
--- a/modules/theme/DiscordGenerator.qml
+++ b/modules/theme/DiscordGenerator.qml
@@ -41,15 +41,15 @@ QtObject {
const textdarkest = isLight ? toRGB(Qt.lighter(fg, 3.19)) : toRGB(Qt.darker(fg, 3.19))
let css = `/**
- * @name Ambxst
- * @description A Discord recolor theme, generated with Ambxst.
- * @author Axenide
+ * @name NothingLess
+ * @description A Discord recolor theme, generated with NothingLess.
+ * @author Leriart
* @version 1.0.0
* @invite gHG9WHyNvH
- * @website https://axeni.de/ambxst
- * @source https://github.com/Axenide/Ambxst
+ * @website https://github.com/Leriart/NothingLess
+ * @source https://github.com/Leriart/NothingLess
* @authorId 294856304969908224
- * @authorLink https://axeni.de
+ * @authorLink https://github.com/Axenide
*/
@import url('https://mwittrien.github.io/BetterDiscordAddons/Themes/DiscordRecolor/DiscordRecolor.css');
@@ -79,7 +79,7 @@ QtObject {
`
const home = Quickshell.env("HOME")
- const vesktopPath = home + "/.config/vesktop/themes/ambxst.css"
+ const vesktopPath = home + "/.config/vesktop/themes/nothingless.css"
const escape = (str) => {
if (!str) return ""
diff --git a/modules/theme/GtkGenerator.qml b/modules/theme/GtkGenerator.qml
old mode 100644
new mode 100755
index 95a4bb96..36d45d28
--- a/modules/theme/GtkGenerator.qml
+++ b/modules/theme/GtkGenerator.qml
@@ -20,7 +20,7 @@ QtObject {
const onSurface = fmt(Colors.overSurface)
const surfaceContainer = fmt(Colors.surfaceContainer)
- let css = "/* GTK Colors generated by Ambxst */\n\n"
+ let css = "/* GTK Colors generated by NothingLess */\n\n"
css += `@define-color accent_color ${primary};\n`
css += `@define-color accent_fg_color ${onPrimary};\n`
diff --git a/modules/theme/Icons.qml b/modules/theme/Icons.qml
old mode 100644
new mode 100755
diff --git a/modules/theme/KittyGenerator.qml b/modules/theme/KittyGenerator.qml
old mode 100644
new mode 100755
index 55d2b826..793f04a8
--- a/modules/theme/KittyGenerator.qml
+++ b/modules/theme/KittyGenerator.qml
@@ -100,7 +100,7 @@ QtObject {
writer.text = conf;
// QUICKSHELL-GIT: const kittyConfPath = Quickshell.cachePath("kitty.conf");
- const kittyConfPath = Quickshell.env("HOME") + "/.cache/ambxst/kitty.conf";
+ const kittyConfPath = Quickshell.env("HOME") + "/.cache/nothingless/kitty.conf";
// Ensure directory exists and write file
const cmd = `
diff --git a/modules/theme/NvChadGenerator.qml b/modules/theme/NvChadGenerator.qml
old mode 100644
new mode 100755
diff --git a/modules/theme/PywalGenerator.qml b/modules/theme/PywalGenerator.qml
old mode 100644
new mode 100755
diff --git a/modules/theme/QtCtGenerator.qml b/modules/theme/QtCtGenerator.qml
old mode 100644
new mode 100755
index f0b1da73..776c7ca3
--- a/modules/theme/QtCtGenerator.qml
+++ b/modules/theme/QtCtGenerator.qml
@@ -168,8 +168,8 @@ QtObject {
ini += "\n"
ini += "[General]\n"
- ini += "ColorScheme=Ambxst\n"
- ini += "Name=Ambxst\n"
+ ini += "ColorScheme=NothingLess\n"
+ ini += "Name=NothingLess\n"
ini += "shadeSortColumn=true\n"
ini += "\n"
@@ -194,7 +194,7 @@ QtObject {
// Single command to ensure dirs and write files
const cmd = `
mkdir -p "${qt5Dir}" "${qt6Dir}" && \\
- echo "${ini}" | tee "${qt5Dir}/ambxst.colors" "${qt6Dir}/ambxst.colors" > /dev/null
+ echo "${ini}" | tee "${qt5Dir}/nothingless.colors" "${qt6Dir}/nothingless.colors" > /dev/null
`
writerProcess.command = ["sh", "-c", cmd]
diff --git a/modules/theme/Styling.qml b/modules/theme/Styling.qml
old mode 100644
new mode 100755
index 0a768f97..068838ac
--- a/modules/theme/Styling.qml
+++ b/modules/theme/Styling.qml
@@ -3,6 +3,7 @@ import QtQuick
import qs.config
QtObject {
+ id: root
readonly property string defaultFont: Config.defaultFont
function radius(offset) {
@@ -17,28 +18,34 @@ QtObject {
return Math.max(Config.theme.monoFontSize + offset, 8);
}
+ // Pre-built "transparent" variant to avoid allocating a new object on every call
+ property var _transparentConfig: null
+
function getStyledRectConfig(variant) {
switch (variant) {
case "transparent":
- // Internal variant: uses bg config but with opacity, border and radius forced to 0
- const bgConfig = Config.theme.srBg;
- return {
- gradient: bgConfig.gradient,
- gradientType: bgConfig.gradientType,
- gradientAngle: bgConfig.gradientAngle,
- gradientCenterX: bgConfig.gradientCenterX,
- gradientCenterY: bgConfig.gradientCenterY,
- halftoneDotMin: bgConfig.halftoneDotMin,
- halftoneDotMax: bgConfig.halftoneDotMax,
- halftoneStart: bgConfig.halftoneStart,
- halftoneEnd: bgConfig.halftoneEnd,
- halftoneDotColor: bgConfig.halftoneDotColor,
- halftoneBackgroundColor: bgConfig.halftoneBackgroundColor,
- itemColor: bgConfig.itemColor,
- opacity: 0,
- border: [bgConfig.border[0], 0],
- radius: 0
- };
+ // Lazy-init the transparent config (needs bgConfig which may change on theme reload)
+ var bgConfig = Config.theme.srBg;
+ if (!root._transparentConfig) {
+ root._transparentConfig = {
+ gradient: bgConfig.gradient,
+ gradientType: bgConfig.gradientType,
+ gradientAngle: bgConfig.gradientAngle,
+ gradientCenterX: bgConfig.gradientCenterX,
+ gradientCenterY: bgConfig.gradientCenterY,
+ halftoneDotMin: bgConfig.halftoneDotMin,
+ halftoneDotMax: bgConfig.halftoneDotMax,
+ halftoneStart: bgConfig.halftoneStart,
+ halftoneEnd: bgConfig.halftoneEnd,
+ halftoneDotColor: bgConfig.halftoneDotColor,
+ halftoneBackgroundColor: bgConfig.halftoneBackgroundColor,
+ itemColor: bgConfig.itemColor,
+ opacity: 0,
+ border: [bgConfig.border[0], 0],
+ radius: 0
+ };
+ }
+ return root._transparentConfig;
case "bg":
return Config.theme.srBg;
case "popup":
diff --git a/modules/tools/MirrorWindow.qml b/modules/tools/MirrorWindow.qml
old mode 100644
new mode 100755
diff --git a/modules/tools/ScreenrecordTool.qml b/modules/tools/ScreenrecordTool.qml
old mode 100644
new mode 100755
diff --git a/modules/tools/ScreenshotOverlay.qml b/modules/tools/ScreenshotOverlay.qml
old mode 100644
new mode 100755
diff --git a/modules/tools/ScreenshotTool.qml b/modules/tools/ScreenshotTool.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/config/AiPanel.qml b/modules/widgets/config/AiPanel.qml
old mode 100644
new mode 100755
index d7d4ee86..d0d136c3
--- a/modules/widgets/config/AiPanel.qml
+++ b/modules/widgets/config/AiPanel.qml
@@ -39,7 +39,7 @@ Item {
// Providers
Repeater {
- model: ["gemini", "openai", "anthropic", "mistral", "groq", "ollama", "minimax"]
+ model: ["gemini", "openai", "anthropic", "mistral", "groq", "ollama", "minimax", "deepseek"]
delegate: StyledRect {
required property string modelData
Layout.fillWidth: true
diff --git a/modules/widgets/config/SettingsWindow.qml b/modules/widgets/config/SettingsWindow.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/AGENTS.md b/modules/widgets/dashboard/AGENTS.md
old mode 100644
new mode 100755
index 4acf4b38..6048ee8d
--- a/modules/widgets/dashboard/AGENTS.md
+++ b/modules/widgets/dashboard/AGENTS.md
@@ -1,7 +1,7 @@
# DASHBOARD KNOWLEDGE BASE
## OVERVIEW
-Central interactive hub of Ambxst. Tabbed interface with LRU-based lazy-loading for widgets, system controls, media, AI tools, clipboard, notes, and tmux management. Opened via the Notch overlay.
+Central interactive hub of NothingLess. Tabbed interface with LRU-based lazy-loading for widgets, system controls, media, AI tools, clipboard, notes, and tmux management. Opened via the Notch overlay.
## STRUCTURE
- **Root**: `Dashboard.qml` β Orchestrates LRU logic, tab layout, and open/close animations.
diff --git a/modules/widgets/dashboard/Dashboard.qml b/modules/widgets/dashboard/Dashboard.qml
old mode 100644
new mode 100755
index 0c23fc72..d02bfb87
--- a/modules/widgets/dashboard/Dashboard.qml
+++ b/modules/widgets/dashboard/Dashboard.qml
@@ -391,6 +391,7 @@ NotchAnimationBehavior {
// Generic Tab Loader Component
component TabLoader : Loader {
anchors.fill: parent
+ asynchronous: true
// Load based on LRU strategy or if currently active
active: root.shouldTabBeLoaded(index) || root.state.currentTab === index
diff --git a/modules/widgets/dashboard/DashboardView.qml b/modules/widgets/dashboard/DashboardView.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/LauncherButton.qml b/modules/widgets/dashboard/LauncherButton.qml
old mode 100644
new mode 100755
index 9515fbe2..5af93269
--- a/modules/widgets/dashboard/LauncherButton.qml
+++ b/modules/widgets/dashboard/LauncherButton.qml
@@ -5,7 +5,7 @@ import qs.config
import qs.modules.components
ToggleButton {
- buttonIcon: Config.bar.launcherIcon || Qt.resolvedUrl("../../../assets/ambxst/ambxst-icon.svg").toString().replace("file://", "")
+ buttonIcon: Config.bar.launcherIcon || Qt.resolvedUrl("../../../assets/nothingless/nothingless-icon.svg").toString().replace("file://", "")
iconTint: Config.bar.launcherIconTint
iconFullTint: Config.bar.launcherIconFullTint
iconSize: Config.bar.launcherIconSize
diff --git a/modules/widgets/dashboard/clipboard/ClipboardTab.qml b/modules/widgets/dashboard/clipboard/ClipboardTab.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/clipboard/clipboard_utils.js b/modules/widgets/dashboard/clipboard/clipboard_utils.js
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/controls/AudioDeviceItem.qml b/modules/widgets/dashboard/controls/AudioDeviceItem.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/controls/AudioMixerPanel.qml b/modules/widgets/dashboard/controls/AudioMixerPanel.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/controls/AudioVolumeEntry.qml b/modules/widgets/dashboard/controls/AudioVolumeEntry.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/controls/BindsPanel.qml b/modules/widgets/dashboard/controls/BindsPanel.qml
old mode 100644
new mode 100755
index 6655f205..90c110f6
--- a/modules/widgets/dashboard/controls/BindsPanel.qml
+++ b/modules/widgets/dashboard/controls/BindsPanel.qml
@@ -17,7 +17,7 @@ Item {
readonly property real sideMargin: (width - contentWidth) / 2
// Current category being viewed
- property string currentCategory: "ambxst"
+ property string currentCategory: "nothingless"
// Process for unbinding keybinds
Process {
@@ -55,7 +55,7 @@ Item {
property bool editMode: false
property int editingIndex: -1
property var editingBind: null
- property bool isEditingAmbxst: false
+ property bool isEditingNothingless: false
property bool isCreatingNew: false
// Edit form state - new format with keys[] and actions[]
@@ -237,14 +237,14 @@ Item {
}
}
- function openEditDialog(bind, index, isAmbxst) {
+ function openEditDialog(bind, index, isNothingless) {
root.editingIndex = index;
root.editingBind = bind;
- root.isEditingAmbxst = isAmbxst;
+ root.isEditingNothingless = isNothingless;
// Initialize edit form state
- if (isAmbxst) {
- // Ambxst binds still use old format (single key)
+ if (isNothingless) {
+ // NothingLess binds still use old format (single key)
const bindData = bind.bind;
root.editName = "";
root.editKeys = [
@@ -327,20 +327,20 @@ Item {
}
function saveEdit() {
- if (root.isEditingAmbxst) {
- // Save ambxst bind (still uses old format internally)
+ if (root.isEditingNothingless) {
+ // Save nothingless bind (still uses old format internally)
const path = root.editingBind.path.split(".");
- // path = ["ambxst", "section"?, "bindName"]
+ // path = ["nothingless", "section"?, "bindName"]
const adapter = Config.keybindsLoader.adapter;
- if (adapter && adapter.ambxst) {
+ if (adapter && adapter.nothingless) {
let bindObj = null;
if (path.length === 2) {
- // Top level: ambxst.bindName
- bindObj = adapter.ambxst[path[1]];
+ // Top level: nothingless.bindName
+ bindObj = adapter.nothingless[path[1]];
} else if (path.length === 3) {
- // Nested: ambxst.system.bindName
- bindObj = adapter.ambxst[path[1]][path[2]];
+ // Nested: nothingless.system.bindName
+ bindObj = adapter.nothingless[path[1]][path[2]];
}
if (bindObj) {
@@ -395,8 +395,8 @@ Item {
readonly property var categories: [
{
- id: "ambxst",
- label: "Ambxst",
+ id: "nothingless",
+ label: "NothingLess",
icon: Icons.widgets
},
{
@@ -431,38 +431,43 @@ Item {
return mods ? mods + " + " + bind.key : bind.key;
}
- // Get ambxst binds as a flat list
- function getAmbxstBinds() {
+ // Get nothingless binds as a flat list
+ function getNothinglessBinds() {
const adapter = Config.keybindsLoader.adapter;
- if (!adapter || !adapter.ambxst)
+ if (!adapter || !adapter.nothingless)
return [];
const binds = [];
- const ambxst = adapter.ambxst;
+ const nothingless = adapter.nothingless;
- // Core Ambxst binds (Launcher, Dashboard, etc.)
+ // Core NothingLess binds (Launcher, Dashboard, etc.)
const coreKeys = ["launcher", "dashboard", "assistant", "clipboard", "emoji", "notes", "tmux", "wallpapers"];
for (const key of coreKeys) {
- if (ambxst[key]) {
+ if (nothingless[key]) {
binds.push({
- category: "Ambxst",
+ category: "NothingLess",
name: key.charAt(0).toUpperCase() + key.slice(1),
- path: "ambxst." + key,
- bind: ambxst[key]
+ path: "nothingless." + key,
+ bind: nothingless[key]
});
}
}
// System binds
- if (ambxst.system) {
- const systemKeys = ["overview", "powermenu", "config", "lockscreen", "tools", "screenshot", "screenrecord", "lens", "reload", "quit"];
+ if (nothingless.system) {
+ const systemKeys = ["overview", "powermenu", "config", "lockscreen", "tools", "screenshot", "screenrecord", "lens", "reload", "quit", "toggle-metrics"];
for (const key of systemKeys) {
- if (ambxst.system[key]) {
+ let bindObj = nothingless.system[key];
+ // Fallback for keys not exposed by JsonAdapter (e.g., hyphenated names)
+ if (!bindObj && adapter.defaultNothinglessBinds && adapter.defaultNothinglessBinds.system) {
+ bindObj = adapter.defaultNothinglessBinds.system[key];
+ }
+ if (bindObj) {
binds.push({
category: "System",
name: key.charAt(0).toUpperCase() + key.slice(1),
- path: "ambxst.system." + key,
- bind: ambxst.system[key]
+ path: "nothingless.system." + key,
+ bind: bindObj
});
}
}
@@ -707,10 +712,10 @@ Item {
x: root.sideMargin
spacing: 4
- // Ambxst binds view
+ // NothingLess binds view
Repeater {
- id: ambxstRepeater
- model: root.currentCategory === "ambxst" ? root.getAmbxstBinds() : []
+ id: nothinglessRepeater
+ model: root.currentCategory === "nothingless" ? root.getNothinglessBinds() : []
delegate: BindItem {
required property var modelData
@@ -721,7 +726,7 @@ Item {
keybindText: root.formatKeybind(modelData.bind)
dispatcher: KeybindActions.describeAction(modelData.bind.action || modelData.bind)
argument: ""
- isAmbxst: true
+ isNothingless: true
onEditRequested: {
root.openEditDialog(modelData, index, true);
@@ -768,7 +773,7 @@ Item {
dispatcher: firstDispatcher
argument: firstArgument
isEnabled: modelData.enabled !== false
- isAmbxst: false
+ isNothingless: false
layouts: getUniqueLayouts()
onToggleEnabled: {
@@ -798,8 +803,8 @@ Item {
Text {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 20
- visible: (root.currentCategory === "ambxst" && ambxstRepeater.count === 0) || (root.currentCategory === "custom" && customRepeater.count === 0)
- text: root.currentCategory === "ambxst" ? "No Ambxst binds configured" : "No custom binds configured"
+ visible: (root.currentCategory === "nothingless" && nothinglessRepeater.count === 0) || (root.currentCategory === "custom" && customRepeater.count === 0)
+ text: root.currentCategory === "nothingless" ? "No NothingLess binds configured" : "No custom binds configured"
font.family: Config.theme.font
font.pixelSize: Styling.fontSize(0)
color: Colors.overSurfaceVariant
@@ -911,7 +916,7 @@ Item {
// Delete button (only for existing custom binds)
StyledRect {
id: deleteButton
- visible: !root.isEditingAmbxst && !root.isCreatingNew
+ visible: !root.isEditingNothingless && !root.isCreatingNew
variant: deleteButtonArea.containsMouse ? "focus" : "common"
Layout.preferredWidth: 36
Layout.preferredHeight: 36
@@ -939,10 +944,10 @@ Item {
}
}
- // Reset button (only for Ambxst binds)
+ // Reset button (only for NothingLess binds)
StyledRect {
id: resetButton
- visible: root.isEditingAmbxst
+ visible: root.isEditingNothingless
variant: resetButtonArea.pressed ? "primary" : (resetButtonArea.containsMouse ? "focus" : "common")
Layout.preferredWidth: resetButtonContent.width + 24
Layout.preferredHeight: 36
@@ -977,14 +982,14 @@ Item {
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
- if (root.isEditingAmbxst && root.editingBind) {
+ if (root.isEditingNothingless && root.editingBind) {
const path = root.editingBind.path.split(".");
- // path = ["ambxst", "dashboard"|"system", "bindName"]
+ // path = ["nothingless", "dashboard"|"system", "bindName"]
const section = path[1];
const bindName = path[2];
// Use the new helper in Config.qml to get the default values
- const defaultBind = Config.keybindsLoader.adapter.getAmbxstDefault(section, bindName);
+ const defaultBind = Config.keybindsLoader.adapter.getNothinglessDefault(section, bindName);
if (defaultBind) {
root.editKeys = [{
@@ -1062,7 +1067,7 @@ Item {
ColumnLayout {
Layout.fillWidth: true
spacing: 8
- visible: !root.isEditingAmbxst
+ visible: !root.isEditingNothingless
Text {
text: "Name (optional)"
@@ -1105,9 +1110,9 @@ Item {
}
}
- // Bind name/info (for ambxst binds only)
+ // Bind name/info (for nothingless binds only)
Text {
- visible: root.isEditingAmbxst && root.editingBind !== null
+ visible: root.isEditingNothingless && root.editingBind !== null
text: root.editingBind ? (root.editingBind.name || "") : ""
font.family: Config.theme.font
font.pixelSize: Styling.fontSize(1)
@@ -1179,7 +1184,7 @@ Item {
// Remove key button
StyledRect {
id: removeKeyBtn
- visible: root.editKeys.length > 1 && !root.isEditingAmbxst
+ visible: root.editKeys.length > 1 && !root.isEditingNothingless
variant: removeKeyBtnArea.containsMouse ? "focus" : "common"
Layout.preferredWidth: 28
Layout.preferredHeight: 28
@@ -1272,7 +1277,7 @@ Item {
// Add key button
StyledRect {
id: addKeyBtn
- visible: !root.isEditingAmbxst
+ visible: !root.isEditingNothingless
variant: addKeyBtnArea.containsMouse ? "primaryfocus" : "primary"
Layout.preferredWidth: 28
Layout.preferredHeight: 28
@@ -1383,12 +1388,12 @@ Item {
}
// =====================
- // ACTIONS SECTION (custom binds & flags for ambxst)
+ // ACTIONS SECTION (custom binds & flags for nothingless)
// =====================
ColumnLayout {
Layout.fillWidth: true
spacing: 8
- // visible: !root.isEditingAmbxst - Removed to allow editing flags for Ambxst binds
+ // visible: !root.isEditingNothingless - Removed to allow editing flags for NothingLess binds
// Actions section header with pager controls
RowLayout {
@@ -1406,7 +1411,7 @@ Item {
// Page indicator
Text {
- visible: root.editActions.length > 1 && !root.isEditingAmbxst
+ visible: root.editActions.length > 1 && !root.isEditingNothingless
text: (root.currentActionPage + 1) + " / " + root.editActions.length
font.family: Config.theme.font
font.pixelSize: Styling.fontSize(-1)
@@ -1416,7 +1421,7 @@ Item {
// Remove action button
StyledRect {
id: removeActionBtn
- visible: root.editActions.length > 1 && !root.isEditingAmbxst
+ visible: root.editActions.length > 1 && !root.isEditingNothingless
variant: removeActionBtnArea.containsMouse ? "focus" : "common"
Layout.preferredWidth: 28
Layout.preferredHeight: 28
@@ -1447,7 +1452,7 @@ Item {
// Previous action button
StyledRect {
id: prevActionBtn
- visible: root.editActions.length > 1 && !root.isEditingAmbxst
+ visible: root.editActions.length > 1 && !root.isEditingNothingless
variant: prevActionBtnArea.containsMouse ? "focus" : "common"
Layout.preferredWidth: 28
Layout.preferredHeight: 28
@@ -1478,7 +1483,7 @@ Item {
// Next action button
StyledRect {
id: nextActionBtn
- visible: root.editActions.length > 1 && !root.isEditingAmbxst
+ visible: root.editActions.length > 1 && !root.isEditingNothingless
variant: nextActionBtnArea.containsMouse ? "focus" : "common"
Layout.preferredWidth: 28
Layout.preferredHeight: 28
@@ -1509,7 +1514,7 @@ Item {
// Add action button
StyledRect {
id: addActionBtn
- visible: !root.isEditingAmbxst
+ visible: !root.isEditingNothingless
variant: addActionBtnArea.containsMouse ? "primaryfocus" : "primary"
Layout.preferredWidth: 28
Layout.preferredHeight: 28
@@ -1855,7 +1860,7 @@ Item {
property string dispatcher: ""
property string argument: ""
property bool isEnabled: true
- property bool isAmbxst: true
+ property bool isNothingless: true
property bool isHovered: false
property var layouts: [] // Layouts this bind is restricted to (empty = all layouts)
@@ -1890,7 +1895,7 @@ Item {
// Checkbox for custom binds (styled like OLED Mode)
Item {
id: checkboxItem
- visible: !bindItem.isAmbxst
+ visible: !bindItem.isNothingless
Layout.preferredWidth: 32
Layout.preferredHeight: 32
@@ -1971,7 +1976,7 @@ Item {
// Layout indicator
Row {
- visible: !bindItem.isAmbxst
+ visible: !bindItem.isNothingless
spacing: 4
Layout.alignment: Qt.AlignVCenter
@@ -2048,7 +2053,7 @@ Item {
// Checkbox MouseArea needs to be on top
MouseArea {
id: checkboxClickArea
- visible: !bindItem.isAmbxst
+ visible: !bindItem.isNothingless
x: 12
y: (parent.height - 32) / 2
width: 32
diff --git a/modules/widgets/dashboard/controls/BluetoothDeviceItem.qml b/modules/widgets/dashboard/controls/BluetoothDeviceItem.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/controls/BluetoothPanel.qml b/modules/widgets/dashboard/controls/BluetoothPanel.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/controls/ColorButton.qml b/modules/widgets/dashboard/controls/ColorButton.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/controls/ColorPickerPopup.qml b/modules/widgets/dashboard/controls/ColorPickerPopup.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/controls/ColorPickerView.qml b/modules/widgets/dashboard/controls/ColorPickerView.qml
old mode 100644
new mode 100755
index 28025d00..944e5787
--- a/modules/widgets/dashboard/controls/ColorPickerView.qml
+++ b/modules/widgets/dashboard/controls/ColorPickerView.qml
@@ -221,6 +221,8 @@ Item {
// Color grid (SCROLLABLE)
GridView {
id: colorGrid
+
+
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
diff --git a/modules/widgets/dashboard/controls/ColorSelector.qml b/modules/widgets/dashboard/controls/ColorSelector.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/controls/CompositorPanel.qml b/modules/widgets/dashboard/controls/CompositorPanel.qml
old mode 100644
new mode 100755
index dfa858be..f2278f0a
--- a/modules/widgets/dashboard/controls/CompositorPanel.qml
+++ b/modules/widgets/dashboard/controls/CompositorPanel.qml
@@ -319,6 +319,58 @@ Item {
}
}
+ // Inline component for text input rows
+ component TextInputRow: RowLayout {
+ id: textInputRowRoot
+ property string label: ""
+ property string text: ""
+ property string placeholder: ""
+ signal textEdited(string newText)
+
+ Layout.fillWidth: true
+ spacing: 8
+
+ Text {
+ text: textInputRowRoot.label
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(0)
+ color: Colors.overBackground
+ Layout.fillWidth: true
+ }
+
+ StyledRect {
+ variant: "common"
+ Layout.preferredWidth: 120
+ Layout.preferredHeight: 32
+ radius: Styling.radius(-2)
+
+ TextInput {
+ id: textInput
+ anchors.fill: parent
+ anchors.margins: 8
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(0)
+ color: Colors.overBackground
+ selectByMouse: true
+ clip: true
+ verticalAlignment: TextInput.AlignVCenter
+ horizontalAlignment: TextInput.AlignHCenter
+
+ readonly property string configText: textInputRowRoot.text
+ onConfigTextChanged: {
+ if (!activeFocus && text !== configText) {
+ text = configText;
+ }
+ }
+ Component.onCompleted: text = configText
+
+ onEditingFinished: {
+ textInputRowRoot.textEdited(text);
+ }
+ }
+ }
+ }
+
// Inline component for Border Gradients (Multi-color list)
component BorderGradientRow: ColumnLayout {
id: gradientRow
@@ -610,38 +662,21 @@ Item {
CompositorTabButton {
label: "AxctlService"
image: "../../../../assets/compositors/hyprland.svg"
- isSelected: stackLayout.currentIndex === 0
- onClicked: stackLayout.currentIndex = 0
- }
-
- CompositorTabButton {
- label: "Coming Soon"
- icon: Icons.clock
- isSelected: stackLayout.currentIndex === 1
- onClicked: stackLayout.currentIndex = 1
+ isSelected: true
}
}
}
- // Stack for content
+ // Content
Item {
Layout.fillWidth: true
- Layout.preferredHeight: stackLayout.height
+ Layout.preferredHeight: compositorPage.implicitHeight
- StackLayout {
- id: stackLayout
+ ColumnLayout {
+ id: compositorPage
width: root.contentWidth
anchors.horizontalCenter: parent.horizontalCenter
- height: currentIndex === 0 ? compositorPage.implicitHeight : placeholderPage.implicitHeight
- currentIndex: 0
-
- // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- // COMPOSITOR TAB
- // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- ColumnLayout {
- id: compositorPage
- Layout.fillWidth: true
- spacing: 16
+ spacing: 16
// Menu Section
ColumnLayout {
@@ -665,6 +700,38 @@ Item {
text: "Blur"
sectionId: "blur"
}
+ SectionButton {
+ text: "Opacity && Dim"
+ sectionId: "opacity"
+ }
+ SectionButton {
+ text: "Snap"
+ sectionId: "snap"
+ }
+ SectionButton {
+ text: "Input"
+ sectionId: "input"
+ }
+ SectionButton {
+ text: "Cursor"
+ sectionId: "cursor"
+ }
+ SectionButton {
+ text: "Monitors"
+ sectionId: "monitors"
+ }
+ SectionButton {
+ text: "Gestures"
+ sectionId: "gestures"
+ }
+ SectionButton {
+ text: "Layouts"
+ sectionId: "layouts"
+ }
+ SectionButton {
+ text: "Advanced"
+ sectionId: "advanced"
+ }
}
// General Section
@@ -1112,110 +1179,1042 @@ Item {
}
}
- // Bottom Padding
- Item {
- Layout.fillWidth: true
- Layout.preferredHeight: 16
- }
- }
-
- // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- // COMING SOON TAB
- // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- Item {
- id: placeholderPage
- Layout.fillWidth: true
- implicitHeight: 300
-
+ // Opacity & Dim Section
ColumnLayout {
- anchors.centerIn: parent
- spacing: 16
+ visible: root.currentSection === "opacity"
+ Layout.fillWidth: true
+ spacing: 8
Text {
- text: Icons.clock
- font.family: Icons.font
- font.pixelSize: 64
- color: Colors.surfaceVariant
- Layout.alignment: Qt.AlignHCenter
+ text: "Opacity && Dim"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-1)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.bottomMargin: -4
}
Text {
- text: "Coming Soon"
+ text: "Window Opacity"
font.family: Config.theme.font
- font.pixelSize: Styling.fontSize(2)
- font.bold: true
- color: Colors.overBackground
- Layout.alignment: Qt.AlignHCenter
+ font.pixelSize: Styling.fontSize(-2)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.topMargin: 4
+ }
+
+ DecimalInputRow {
+ label: "Active"
+ value: Config.compositor.activeOpacity ?? 1.0
+ minValue: 0.0
+ maxValue: 1.0
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.activeOpacity = newValue;
+ }
+ }
+
+ DecimalInputRow {
+ label: "Inactive"
+ value: Config.compositor.inactiveOpacity ?? 1.0
+ minValue: 0.0
+ maxValue: 1.0
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.inactiveOpacity = newValue;
+ }
+ }
+
+ DecimalInputRow {
+ label: "Fullscreen"
+ value: Config.compositor.fullscreenOpacity ?? 1.0
+ minValue: 0.0
+ maxValue: 1.0
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.fullscreenOpacity = newValue;
+ }
}
Text {
- text: "Support for more compositors\nis planned for future updates."
+ text: "Dim"
font.family: Config.theme.font
- font.pixelSize: Styling.fontSize(0)
+ font.pixelSize: Styling.fontSize(-2)
+ font.weight: Font.Medium
color: Colors.overSurfaceVariant
- horizontalAlignment: Text.AlignHCenter
- Layout.alignment: Qt.AlignHCenter
+ Layout.topMargin: 8
+ }
+
+ ToggleRow {
+ label: "Dim Inactive"
+ checked: Config.compositor.dimInactive ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.dimInactive = value;
+ }
+ }
+
+ DecimalInputRow {
+ label: "Dim Strength"
+ value: Config.compositor.dimStrength ?? 0.5
+ minValue: 0.0
+ maxValue: 1.0
+ enabled: Config.compositor.dimInactive
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.dimStrength = newValue;
+ }
+ }
+
+ DecimalInputRow {
+ label: "Dim Around"
+ value: Config.compositor.dimAround ?? 0.4
+ minValue: 0.0
+ maxValue: 1.0
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.dimAround = newValue;
+ }
+ }
+
+ DecimalInputRow {
+ label: "Dim Special"
+ value: Config.compositor.dimSpecial ?? 0.2
+ minValue: 0.0
+ maxValue: 1.0
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.dimSpecial = newValue;
+ }
}
}
- }
- }
- }
- }
- }
- // Color picker view (shown when colorPickerActive)
- Item {
- id: colorPickerContainer
- anchors.fill: parent
- clip: true
+ // Snap Section
+ ColumnLayout {
+ visible: root.currentSection === "snap"
+ Layout.fillWidth: true
+ spacing: 8
- // Horizontal slide + fade animation (enters from right)
- opacity: root.colorPickerActive ? 1 : 0
- transform: Translate {
- x: root.colorPickerActive ? 0 : 30
+ Text {
+ text: "Snap"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-1)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.bottomMargin: -4
+ }
- Behavior on x {
- enabled: Config.animDuration > 0
- NumberAnimation {
- duration: Config.animDuration / 2
- easing.type: Easing.OutQuart
- }
- }
- }
+ ToggleRow {
+ label: "Enabled"
+ checked: Config.compositor.snapEnabled ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.snapEnabled = value;
+ }
+ }
- Behavior on opacity {
- enabled: Config.animDuration > 0
- NumberAnimation {
- duration: Config.animDuration / 2
- easing.type: Easing.OutQuart
- }
- }
+ NumberInputRow {
+ label: "Window Gap"
+ value: Config.compositor.snapWindowGap ?? 10
+ minValue: 0
+ maxValue: 100
+ suffix: "px"
+ enabled: Config.compositor.snapEnabled
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.snapWindowGap = newValue;
+ }
+ }
- // Prevent interaction when hidden
- enabled: root.colorPickerActive
+ NumberInputRow {
+ label: "Monitor Gap"
+ value: Config.compositor.snapMonitorGap ?? 10
+ minValue: 0
+ maxValue: 100
+ suffix: "px"
+ enabled: Config.compositor.snapEnabled
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.snapMonitorGap = newValue;
+ }
+ }
- // Block interaction with elements behind when active
- MouseArea {
- anchors.fill: parent
- enabled: root.colorPickerActive
- hoverEnabled: true
- acceptedButtons: Qt.AllButtons
- onPressed: event => event.accepted = true
- onReleased: event => event.accepted = true
- onWheel: event => event.accepted = true
- }
+ ToggleRow {
+ label: "Border Overlap"
+ checked: Config.compositor.snapBorderOverlap ?? false
+ enabled: Config.compositor.snapEnabled
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.snapBorderOverlap = value;
+ }
+ }
- ColorPickerView {
- id: colorPickerContent
- anchors.fill: parent
- anchors.leftMargin: root.sideMargin
- anchors.rightMargin: root.sideMargin
- colorNames: root.colorPickerColorNames
- currentColor: root.colorPickerCurrentColor
- dialogTitle: root.colorPickerDialogTitle
+ ToggleRow {
+ label: "Respect Gaps"
+ checked: Config.compositor.snapRespectGaps ?? false
+ enabled: Config.compositor.snapEnabled
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.snapRespectGaps = value;
+ }
+ }
+ }
+
+ // Input Section
+ ColumnLayout {
+ visible: root.currentSection === "input"
+ Layout.fillWidth: true
+ spacing: 8
+
+ Text {
+ text: "Input"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-1)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.bottomMargin: -4
+ }
+
+ Text {
+ text: "Keyboard"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-2)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.topMargin: 4
+ }
+
+ TextInputRow {
+ label: "Layout"
+ text: Config.compositor.kbLayout ?? "us"
+ placeholder: "us"
+ onTextEdited: newText => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.kbLayout = newText;
+ }
+ }
+
+ TextInputRow {
+ label: "Variant"
+ text: Config.compositor.kbVariant ?? ""
+ placeholder: "e.g. dvorak"
+ onTextEdited: newText => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.kbVariant = newText;
+ }
+ }
+
+ TextInputRow {
+ label: "Options"
+ text: Config.compositor.kbOptions ?? ""
+ placeholder: "e.g. caps:escape"
+ onTextEdited: newText => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.kbOptions = newText;
+ }
+ }
+
+ ToggleRow {
+ label: "Numlock by Default"
+ checked: Config.compositor.numlockByDefault ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.numlockByDefault = value;
+ }
+ }
+
+ NumberInputRow {
+ label: "Repeat Rate"
+ value: Config.compositor.repeatRate ?? 25
+ minValue: 0
+ maxValue: 300
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.repeatRate = newValue;
+ }
+ }
+
+ NumberInputRow {
+ label: "Repeat Delay"
+ value: Config.compositor.repeatDelay ?? 600
+ minValue: 0
+ maxValue: 2000
+ suffix: "ms"
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.repeatDelay = newValue;
+ }
+ }
+
+ Text {
+ text: "Mouse"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-2)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.topMargin: 8
+ }
+
+ DecimalInputRow {
+ label: "Sensitivity"
+ value: Config.compositor.mouseSensitivity ?? 0.0
+ minValue: -1.0
+ maxValue: 1.0
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.mouseSensitivity = newValue;
+ }
+ }
+
+ ToggleRow {
+ label: "Natural Scroll"
+ checked: Config.compositor.mouseNaturalScroll ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.mouseNaturalScroll = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Left Handed"
+ checked: Config.compositor.mouseLeftHanded ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.mouseLeftHanded = value;
+ }
+ }
+
+ DecimalInputRow {
+ label: "Scroll Factor"
+ value: Config.compositor.mouseScrollFactor ?? 1.0
+ minValue: 0.1
+ maxValue: 10.0
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.mouseScrollFactor = newValue;
+ }
+ }
+
+ Text {
+ text: "Touchpad"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-2)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.topMargin: 8
+ }
+
+ ToggleRow {
+ label: "Disable While Typing"
+ checked: Config.compositor.touchpadDisableWhileTyping ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.touchpadDisableWhileTyping = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Natural Scroll"
+ checked: Config.compositor.touchpadNaturalScroll ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.touchpadNaturalScroll = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Tap to Click"
+ checked: Config.compositor.touchpadTapToClick ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.touchpadTapToClick = value;
+ }
+ }
+
+ DecimalInputRow {
+ label: "Scroll Factor"
+ value: Config.compositor.touchpadScrollFactor ?? 1.0
+ minValue: 0.1
+ maxValue: 10.0
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.touchpadScrollFactor = newValue;
+ }
+ }
+ }
+
+ // Cursor Section
+ ColumnLayout {
+ visible: root.currentSection === "cursor"
+ Layout.fillWidth: true
+ spacing: 8
+
+ Text {
+ text: "Cursor"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-1)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.bottomMargin: -4
+ }
+
+ ToggleRow {
+ label: "Enable Hyprcursor"
+ checked: Config.compositor.enableHyprcursor ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.enableHyprcursor = value;
+ }
+ }
+
+ ToggleRow {
+ label: "No Hardware Cursors"
+ checked: Config.compositor.noHardwareCursors ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.noHardwareCursors = value;
+ }
+ }
+
+ ToggleRow {
+ label: "No Warps"
+ checked: Config.compositor.noWarps ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.noWarps = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Persistent Warps"
+ checked: Config.compositor.persistentWarps ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.persistentWarps = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Warp on Workspace Change"
+ checked: Config.compositor.warpOnChangeWorkspace ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.warpOnChangeWorkspace = value;
+ }
+ }
+
+ DecimalInputRow {
+ label: "Zoom Factor"
+ value: Config.compositor.cursorZoomFactor ?? 1.0
+ minValue: 0.1
+ maxValue: 10.0
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.cursorZoomFactor = newValue;
+ }
+ }
+
+ NumberInputRow {
+ label: "Inactive Timeout"
+ value: Config.compositor.cursorInactiveTimeout ?? 0
+ minValue: 0
+ maxValue: 60
+ suffix: "s"
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.cursorInactiveTimeout = newValue;
+ }
+ }
+
+ ToggleRow {
+ label: "Hide on Key Press"
+ checked: Config.compositor.cursorHideOnKeyPress ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.cursorHideOnKeyPress = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Hide on Touch"
+ checked: Config.compositor.cursorHideOnTouch ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.cursorHideOnTouch = value;
+ }
+ }
+ }
+
+ // Gestures Section
+ ColumnLayout {
+ visible: root.currentSection === "gestures"
+ Layout.fillWidth: true
+ spacing: 8
+
+ Text {
+ text: "Gestures"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-1)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.bottomMargin: -4
+ }
+
+ ToggleRow {
+ label: "Create New Workspace"
+ checked: Config.compositor.workspaceSwipeCreateNew ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.workspaceSwipeCreateNew = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Swipe Forever"
+ checked: Config.compositor.workspaceSwipeForever ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.workspaceSwipeForever = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Direction Lock"
+ checked: Config.compositor.workspaceSwipeDirectionLock ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.workspaceSwipeDirectionLock = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Use Relative Workspaces"
+ checked: Config.compositor.workspaceSwipeUseR ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.workspaceSwipeUseR = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Invert Direction"
+ checked: Config.compositor.workspaceSwipeInvert ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.workspaceSwipeInvert = value;
+ }
+ }
+
+ DecimalInputRow {
+ label: "Cancel Ratio"
+ value: Config.compositor.workspaceSwipeCancelRatio ?? 0.5
+ minValue: 0.0
+ maxValue: 1.0
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.workspaceSwipeCancelRatio = newValue;
+ }
+ }
+
+ NumberInputRow {
+ label: "Min Speed to Force"
+ value: Config.compositor.workspaceSwipeMinSpeedToForce ?? 30
+ minValue: 0
+ maxValue: 500
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.workspaceSwipeMinSpeedToForce = newValue;
+ }
+ }
+
+ NumberInputRow {
+ label: "Swipe Distance"
+ value: Config.compositor.workspaceSwipeDistance ?? 300
+ minValue: 0
+ maxValue: 1000
+ suffix: "px"
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.workspaceSwipeDistance = newValue;
+ }
+ }
+ }
+
+ // Layouts Section
+ ColumnLayout {
+ visible: root.currentSection === "layouts"
+ Layout.fillWidth: true
+ spacing: 8
+
+ Text {
+ text: "Layouts"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-1)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.bottomMargin: -4
+ }
+
+ Text {
+ text: "Dwindle"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-2)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.topMargin: 4
+ }
+
+ ToggleRow {
+ label: "Preserve Split"
+ checked: Config.compositor.dwindlePreserveSplit ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.dwindlePreserveSplit = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Smart Split"
+ checked: Config.compositor.dwindleSmartSplit ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.dwindleSmartSplit = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Smart Resizing"
+ checked: Config.compositor.dwindleSmartResizing ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.dwindleSmartResizing = value;
+ }
+ }
+
+ DecimalInputRow {
+ label: "Split Ratio"
+ value: Config.compositor.dwindleDefaultSplitRatio ?? 1.0
+ minValue: 0.1
+ maxValue: 5.0
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.dwindleDefaultSplitRatio = newValue;
+ }
+ }
+
+ DecimalInputRow {
+ label: "Special Scale"
+ value: Config.compositor.dwindleSpecialScaleFactor ?? 0.8
+ minValue: 0.1
+ maxValue: 1.0
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.dwindleSpecialScaleFactor = newValue;
+ }
+ }
+
+ Text {
+ text: "Master"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-2)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.topMargin: 8
+ }
+
+ DecimalInputRow {
+ label: "Master Factor"
+ value: Config.compositor.masterMfact ?? 0.55
+ minValue: 0.05
+ maxValue: 0.95
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.masterMfact = newValue;
+ }
+ }
+
+ ToggleRow {
+ label: "Smart Resizing"
+ checked: Config.compositor.masterSmartResizing ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.masterSmartResizing = value;
+ }
+ }
+
+ DecimalInputRow {
+ label: "Special Scale"
+ value: Config.compositor.masterSpecialScaleFactor ?? 0.8
+ minValue: 0.1
+ maxValue: 1.0
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.masterSpecialScaleFactor = newValue;
+ }
+ }
+
+ Text {
+ text: "Scrolling"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-2)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.topMargin: 8
+ }
+
+ DecimalInputRow {
+ label: "Column Width"
+ value: Config.compositor.scrollingColumnWidth ?? 0.3
+ minValue: 0.05
+ maxValue: 1.0
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.scrollingColumnWidth = newValue;
+ }
+ }
+
+ ToggleRow {
+ label: "Follow Focus"
+ checked: Config.compositor.scrollingFollowFocus ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.scrollingFollowFocus = value;
+ }
+ }
+
+ DecimalInputRow {
+ label: "Min Visible"
+ value: Config.compositor.scrollingFollowMinVisible ?? 0.1
+ minValue: 0.0
+ maxValue: 1.0
+ enabled: Config.compositor.scrollingFollowFocus
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.scrollingFollowMinVisible = newValue;
+ }
+ }
+ }
+
+ // Advanced Section
+ ColumnLayout {
+ visible: root.currentSection === "advanced"
+ Layout.fillWidth: true
+ spacing: 8
+
+ Text {
+ text: "Advanced"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-1)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.bottomMargin: -4
+ }
+
+ Text {
+ text: "General"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-2)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.topMargin: 4
+ }
+
+ ToggleRow {
+ label: "Allow Tearing"
+ checked: Config.compositor.allowTearing ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.allowTearing = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Animations"
+ checked: Config.compositor.animationsEnabled ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.animationsEnabled = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Animate Manual Resizes"
+ checked: Config.compositor.animateManualResizes ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.animateManualResizes = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Animate Mouse Dragging"
+ checked: Config.compositor.animateMouseWindowdragging ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.animateMouseWindowdragging = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Focus on Activate"
+ checked: Config.compositor.focusOnActivate ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.focusOnActivate = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Resize on Border"
+ checked: Config.compositor.resizeOnBorder ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.resizeOnBorder = value;
+ }
+ }
+
+ NumberInputRow {
+ label: "Border Grab Area"
+ value: Config.compositor.extendBorderGrabArea ?? 15
+ minValue: 0
+ maxValue: 50
+ suffix: "px"
+ onValueEdited: newValue => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.extendBorderGrabArea = newValue;
+ }
+ }
+
+ Text {
+ text: "XWayland"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-2)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.topMargin: 8
+ }
+
+ ToggleRow {
+ label: "XWayland Enabled"
+ checked: Config.compositor.xwaylandEnabled ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.xwaylandEnabled = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Force Zero Scaling"
+ checked: Config.compositor.xwaylandForceZeroScaling ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.xwaylandForceZeroScaling = value;
+ }
+ }
+
+ Text {
+ text: "Startup"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-2)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.topMargin: 8
+ }
+
+ ToggleRow {
+ label: "Disable Logo"
+ checked: Config.compositor.disableHyprlandLogo ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.disableHyprlandLogo = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Disable Splash"
+ checked: Config.compositor.disableSplashRendering ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.disableSplashRendering = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Disable Update News"
+ checked: Config.compositor.noUpdateNews ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.noUpdateNews = value;
+ }
+ }
+ }
+
+ // =====================
+ // MONITORS SECTION
+ // =====================
+ ColumnLayout {
+ visible: root.currentSection === "monitors"
+ Layout.fillWidth: true
+ spacing: 16
+
+ Text {
+ text: "Monitors"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-1)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.bottomMargin: -4
+ }
+
+ // ββ Global monitor settings ββ
+ Text {
+ text: "Global Settings"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-2)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.topMargin: 4
+ }
+
+ ToggleRow {
+ label: "VFR (Variable Frame Rate)"
+ checked: Config.compositor.vfr ?? true
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.vfr = value;
+ }
+ }
+
+ Text {
+ text: "DPMS (Power Management)"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-2)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.topMargin: 8
+ }
+
+ ToggleRow {
+ label: "Wake on Mouse Move"
+ checked: Config.compositor.mouseMoveEnablesDpms ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.mouseMoveEnablesDpms = value;
+ }
+ }
+
+ ToggleRow {
+ label: "Wake on Key Press"
+ checked: Config.compositor.keyPressEnablesDpms ?? false
+ onToggled: value => {
+ GlobalStates.markCompositorChanged();
+ Config.compositor.keyPressEnablesDpms = value;
+ }
+ }
+
+ // ββ Visual arrangement with drag & drop ββ
+ Text {
+ text: "Layout"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-2)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.topMargin: 8
+ }
+
+ MonitorArrangementView {
+ id: arrangementView
+ Layout.fillWidth: true
+ }
+
+ // ββ Per-monitor cards (always available via Quickshell.screens) ββ
+ Text {
+ text: "Per-Monitor Configuration"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-2)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.topMargin: 8
+ }
+
+ Repeater {
+ id: monitorRepeater
+ model: Quickshell.screens
+
+ delegate: MonitorCard {
+ required property int index
+ required property var modelData
+ monitorIndex: index
+ screen: modelData
+ Layout.fillWidth: true
+ }
+ }
+ }
+
+ // Bottom Padding
+ Item {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 16
+ }
+ }
+ }
+ }
+ }
+
+ // Color picker view (shown when colorPickerActive)
+ Item {
+ id: colorPickerContainer
+ anchors.fill: parent
+ clip: true
+
+ // Horizontal slide + fade animation (enters from right)
+ opacity: root.colorPickerActive ? 1 : 0
+ transform: Translate {
+ x: root.colorPickerActive ? 0 : 30
+
+ Behavior on x {
+ enabled: Config.animDuration > 0
+ NumberAnimation {
+ duration: Config.animDuration / 2
+ easing.type: Easing.OutQuart
+ }
+ }
+ }
+
+ Behavior on opacity {
+ enabled: Config.animDuration > 0
+ NumberAnimation {
+ duration: Config.animDuration / 2
+ easing.type: Easing.OutQuart
+ }
+ }
+
+ // Prevent interaction when hidden
+ enabled: root.colorPickerActive
+
+ // Block interaction with elements behind when active
+ MouseArea {
+ anchors.fill: parent
+ enabled: root.colorPickerActive
+ hoverEnabled: true
+ acceptedButtons: Qt.AllButtons
+ onPressed: event => event.accepted = true
+ onReleased: event => event.accepted = true
+ onWheel: event => event.accepted = true
+ }
+
+ ColorPickerView {
+ id: colorPickerContent
+ anchors.fill: parent
+ anchors.leftMargin: root.sideMargin
+ anchors.rightMargin: root.sideMargin
+ colorNames: root.colorPickerColorNames
+ currentColor: root.colorPickerCurrentColor
+ dialogTitle: root.colorPickerDialogTitle
+
+ onColorSelected: color => root.handleColorSelected(color)
+ onClosed: root.closeColorPicker()
+ }
+ }
+}
- onColorSelected: color => root.handleColorSelected(color)
- onClosed: root.closeColorPicker()
- }
- }
-}
diff --git a/modules/widgets/dashboard/controls/EasyEffectsPanel.qml b/modules/widgets/dashboard/controls/EasyEffectsPanel.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/controls/GradientStopsEditor.qml b/modules/widgets/dashboard/controls/GradientStopsEditor.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/controls/MonitorArrangementView.qml b/modules/widgets/dashboard/controls/MonitorArrangementView.qml
new file mode 100644
index 00000000..3d0424f9
--- /dev/null
+++ b/modules/widgets/dashboard/controls/MonitorArrangementView.qml
@@ -0,0 +1,590 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Io
+import qs.modules.theme
+import qs.modules.components
+import qs.modules.services
+import qs.config
+
+/**
+ * MonitorArrangementView β nwg-displays style canvas
+ *
+ * Renders monitors at their REAL coordinates, scaled to fit the canvas.
+ * Drag & drop updates actual x,y positions. Snap-to-edge Γ la nwg-displays.
+ */
+StyledRect {
+ id: root
+
+ variant: "pane"
+ Layout.fillWidth: true
+ Layout.preferredHeight: canvasColumn.implicitHeight + 16
+ radius: Styling.radius(0)
+ enableShadow: true
+
+ property var monitors: []
+ property bool hasChanges: false
+ property var dragIdx: -1
+ property var dragNewX: 0
+ property var dragNewY: 0
+
+ signal positionChanged(int monitorIndex, int newX, int newY)
+ signal monitorSelected(int monitorIndex)
+
+ // ββ Reactively build monitor list from Quickshell.screens + AxctlService ββ
+ function refreshMonitors() {
+ var list = [];
+ var screens = Quickshell.screens;
+ if (!screens || screens.length === 0) return;
+
+ var axMons = AxctlService.monitors.values || [];
+
+ for (var i = 0; i < screens.length; i++) {
+ var s = screens[i];
+ var axctl = null;
+ for (var j = 0; j < axMons.length; j++) {
+ if (axMons[j].name === s.name) { axctl = axMons[j]; break; }
+ }
+
+ // Prefer AxctlService for position/geometry (real compositor state),
+ // fall back to Quickshell.screen properties
+ var w = axctl ? (axctl.width || s.width || 1920) : (s.width || 1920);
+ var h = axctl ? (axctl.height || s.height || 1080) : (s.height || 1080);
+ var x = axctl ? (axctl.x || s.x || 0) : (s.x || 0);
+ var y = axctl ? (axctl.y || s.y || 0) : (s.y || 0);
+ var sc = axctl ? (axctl.scale || 1.0) : 1.0;
+ var rr = axctl ? (axctl.refreshRate || 60) : 60;
+ var tf = axctl ? (axctl.transform || 0) : 0;
+
+ list.push({
+ name: s.name || ("Monitor-" + (i + 1)),
+ width: w, height: h,
+ x: x, y: y,
+ scale: sc,
+ refreshRate: rr,
+ transform: tf,
+ focused: (AxctlService.focusedMonitor && AxctlService.focusedMonitor.name === s.name)
+ });
+ }
+ root.monitors = list;
+ }
+
+ Component.onCompleted: {
+ refreshMonitors();
+ recalcBounds();
+ }
+
+ onPositionChanged: syncDebounceTimer.restart()
+
+ Timer {
+ id: syncDebounceTimer
+ interval: 1500
+ repeat: false
+ onTriggered: root.saveToConfig()
+ }
+
+ Connections {
+ target: AxctlService
+ function onMonitorsChanged() {
+ root.refreshMonitors();
+ root.recalcBounds();
+ }
+ }
+
+ // Recalculate bounds whenever the monitors array changes
+ onMonitorsChanged: {
+ root.recalcBounds();
+ }
+
+ // ββ View transform ββ
+ // Bounds of all monitors in real coordinates.
+ property var viewBounds_: ({ minX: -100, minY: -100, maxX: 100, maxY: 100, spanW: 200, spanH: 200 })
+
+ function recalcBounds() {
+ var mons = root.monitors;
+ if (!mons || mons.length === 0) return;
+
+ var minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
+ for (var i = 0; i < mons.length; i++) {
+ var m = mons[i];
+ // Use logical dimensions (physical / scale) so the canvas fits correctly
+ var scale = m.scale || 1.0;
+ var w = (m.width || 1920) / scale;
+ var h = (m.height || 1080) / scale;
+ var x = m.x || 0, y = m.y || 0;
+ if (x < minX) minX = x;
+ if (y < minY) minY = y;
+ if (x + w > maxX) maxX = x + w;
+ if (y + h > maxY) maxY = y + h;
+ }
+
+ // If all overlaps (all at 0,0), space them out for visibility
+ var margin = 100;
+ if (minX === maxX && minY === maxY && mons.length > 1) {
+ // Use sequential layout for preview
+ var cx = 0;
+ for (var i = 0; i < mons.length; i++) {
+ mons[i]._previewX = cx;
+ mons[i]._previewY = 0;
+ cx += (mons[i].width || 1920) + 20;
+ }
+ minX = 0;
+ minY = 0;
+ maxX = cx;
+ maxY = 1080;
+ }
+
+ root.viewBounds_ = {
+ minX: minX - margin,
+ minY: minY - margin,
+ maxX: maxX + margin,
+ maxY: maxY + margin,
+ spanW: Math.max((maxX + margin) - (minX - margin), 1),
+ spanH: Math.max((maxY + margin) - (minY - margin), 1)
+ };
+ }
+
+
+ // Canvas scale factor
+ property real viewScale: 0.1
+
+ function recalcScale() {
+ var cw = canvasArea.width;
+ var ch = canvasArea.height;
+ if (cw <= 0 || ch <= 0) return;
+ var vb = root.viewBounds_;
+ root.viewScale = Math.min(
+ (cw - 20) / vb.spanW,
+ (ch - 20) / vb.spanH
+ );
+ }
+
+ function realToCanvasX(realX) {
+ return (realX - root.viewBounds_.minX) * root.viewScale + 10;
+ }
+
+ function realToCanvasY(realY) {
+ return (realY - root.viewBounds_.minY) * root.viewScale + 10;
+ }
+
+ function canvasToRealX(cvX) {
+ return Math.round((cvX - 10) / root.viewScale + root.viewBounds_.minX);
+ }
+
+ function canvasToRealY(cvY) {
+ return Math.round((cvY - 10) / root.viewScale + root.viewBounds_.minY);
+ }
+
+ // ββ Auto-align left-to-right ββ
+ function autoAlignMonitors() {
+ var mons = root.monitors;
+ if (!mons || mons.length === 0) return;
+ var currentX = 0;
+ for (var i = 0; i < mons.length; i++) {
+ var m = mons[i];
+ // dispatch happens via MonitorsWriter.sync()
+ currentX += m.width;
+ }
+ recalcScale();
+ refreshTimer.start();
+ }
+
+ Timer {
+ id: refreshTimer
+ interval: 600
+ onTriggered: {
+ root.refreshMonitors();
+ root.recalcBounds();
+ }
+ }
+
+ // ββ Persist to disk ββ
+ function saveToConfig() {
+ var data = [];
+ var mons = root.monitors;
+ if (!mons || mons.length === 0) return;
+ for (var i = 0; i < mons.length; i++) {
+ var m = mons[i];
+ data.push({
+ name: m.name, width: m.width, height: m.height,
+ x: m.x, y: m.y, scale: m.scale,
+ refreshRate: m.refreshRate, transform: m.transform || 0,
+ enabled: true, bitdepth: 10
+ });
+ }
+ MonitorsWriter.syncWithData(data);
+ root.hasChanges = false;
+ }
+
+ // ββ Layout ββ
+ ColumnLayout {
+ id: canvasColumn
+ anchors.fill: parent
+ anchors.margins: 12
+ spacing: 8
+
+ // Header row
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 8
+
+ Text {
+ text: Icons.layout + " Monitor Layout"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(0)
+ font.bold: true
+ color: Colors.overBackground
+ Layout.fillWidth: true
+ }
+
+ Button {
+ id: alignBtn
+ flat: true; hoverEnabled: true
+ Layout.preferredHeight: 28
+ implicitWidth: alignLabel.implicitWidth + 20
+
+ background: StyledRect {
+ variant: alignBtn.hovered ? "focus" : "common"
+ radius: Styling.radius(-4)
+ }
+ contentItem: Text {
+ id: alignLabel
+ text: Icons.arrowsOutCardinal + " Auto-Arrange"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-2)
+ color: Colors.overBackground
+ anchors.centerIn: parent
+ }
+ onClicked: root.autoAlignMonitors()
+ }
+
+ Button {
+ id: saveBtn
+ flat: true; hoverEnabled: true
+ Layout.preferredHeight: 28
+ implicitWidth: saveLabel.implicitWidth + 20
+
+ background: StyledRect {
+ variant: saveBtn.hovered ? "primary" : "common"
+ radius: Styling.radius(-4)
+ }
+ contentItem: Text {
+ id: saveLabel
+ text: Icons.disk + " Save to Config"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-2)
+ color: saveBtn.hovered ? Styling.srItem("primary") : Colors.overBackground
+ anchors.centerIn: parent
+ }
+ onClicked: root.saveToConfig()
+ }
+ }
+
+ // Canvas
+ Item {
+ id: canvasArea
+ Layout.fillWidth: true
+ Layout.preferredHeight: 220
+ clip: true
+ onWidthChanged: recalcScale()
+ onHeightChanged: recalcScale()
+
+ // Background
+ StyledRect {
+ anchors.fill: parent
+ variant: "internalbg"
+ radius: Styling.radius(-2)
+ }
+
+ // Scroll container β shifts content so both monitors are visible
+ Item {
+ id: scrollBox
+ width: Math.max(parent.width, root.viewBounds_.spanW * root.viewScale + 20)
+ height: Math.max(parent.height, root.viewBounds_.spanH * root.viewScale + 20)
+
+ // Subtle grid at 500px intervals
+ Repeater {
+ model: Math.floor(root.viewBounds_.spanW / 500) + 2
+ Rectangle {
+ x: root.realToCanvasX(root.viewBounds_.minX + index * 500)
+ y: 0; width: 1; height: scrollBox.height
+ color: Qt.rgba(Colors.outlineVariant.r, Colors.outlineVariant.g, Colors.outlineVariant.b, 0.06)
+ }
+ }
+ Repeater {
+ model: Math.floor(root.viewBounds_.spanH / 500) + 2
+ Rectangle {
+ x: 0
+ y: root.realToCanvasY(root.viewBounds_.minY + index * 500)
+ width: scrollBox.width; height: 1
+ color: Qt.rgba(Colors.outlineVariant.r, Colors.outlineVariant.g, Colors.outlineVariant.b, 0.06)
+ }
+ }
+
+ // Origin marker (0,0)
+ StyledRect {
+ x: root.realToCanvasX(0) - 5
+ y: root.realToCanvasY(0) - 5
+ width: 10; height: 10
+ radius: Styling.radius(-10)
+ variant: "primary"
+ opacity: 0.6
+ }
+ // Origin axes
+ Rectangle { x: root.realToCanvasX(0) - 1; y: 0; width: 2; height: scrollBox.height; color: Qt.rgba(Colors.outline.r, Colors.outline.g, Colors.outline.b, 0.04); }
+ Rectangle { x: 0; y: root.realToCanvasY(0) - 1; width: scrollBox.width; height: 2; color: Qt.rgba(Colors.outline.r, Colors.outline.g, Colors.outline.b, 0.04); }
+
+ // Monitor items
+ Repeater {
+ model: root.monitors
+
+ delegate: Item {
+ id: monItem
+ required property int index
+ required property var modelData
+
+ // Compute position: use drag preview when dragging, real coords otherwise
+ readonly property real realX: root.dragIdx === index ? root.dragNewX : modelData.x
+ readonly property real realY: root.dragIdx === index ? root.dragNewY : modelData.y
+
+ x: root.realToCanvasX(realX)
+ y: root.realToCanvasY(realY)
+ // Show at logical size (physical / scale) like nwg-displays
+ readonly property real logicalW: modelData.width / (modelData.scale || 1.0)
+ readonly property real logicalH: modelData.height / (modelData.scale || 1.0)
+ width: Math.max(50, logicalW * root.viewScale)
+ height: Math.max(35, logicalH * root.viewScale)
+
+ // Body
+ StyledRect {
+ anchors.fill: parent
+ variant: modelData.focused ? "primary" : "common"
+ radius: Styling.radius(-2)
+ enableShadow: true
+ border.width: modelData.focused ? 1.5 : 1
+ border.color: modelData.focused
+ ? Styling.srItem("primary")
+ : Colors.outlineVariant
+ }
+
+ // Label
+ Column {
+ anchors.centerIn: parent; spacing: 1
+ Text {
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: modelData.name
+ font.family: Config.theme.font
+ font.pixelSize: Math.max(8, Math.min(11, Styling.fontSize(-3)))
+ font.bold: true
+ color: modelData.focused ? Styling.srItem("primary") : Colors.overBackground
+ }
+ Text {
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: realX + "," + realY
+ font.family: Config.theme.font
+ font.pixelSize: Math.max(7, Math.min(10, Styling.fontSize(-4)))
+ color: Colors.outline
+ } Text {
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: modelData.width + "Γ" + modelData.height
+ font.family: Config.theme.font
+ font.pixelSize: Math.max(7, Math.min(10, Styling.fontSize(-4)))
+ color: Colors.outline
+ }
+ Text {
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: "@" + modelData.scale.toFixed(2) + "x (" + Math.round(logicalW) + "Γ" + Math.round(logicalH) + " logical)"
+ font.pixelSize: Math.max(7, Math.min(10, Styling.fontSize(-4)))
+ color: Colors.outlineVariant
+ }
+ }
+
+ // Drag
+ MouseArea {
+ id: dragArea
+ anchors.fill: parent
+ cursorShape: Qt.SizeAllCursor
+ hoverEnabled: true
+
+ property real pressCX: 0
+ property real pressCY: 0
+ property real startRealX: 0
+ property real startRealY: 0
+ property bool dragging: false
+
+ onPressed: mouse => {
+ monItem.z = 100
+ dragging = true
+ pressCX = mouse.x + monItem.x
+ pressCY = mouse.y + monItem.y
+ startRealX = modelData.x
+ startRealY = modelData.y
+ root.dragIdx = index
+ root.dragNewX = modelData.x
+ root.dragNewY = modelData.y
+ root.monitorSelected(index)
+ }
+
+ onPositionChanged: mouse => {
+ if (!dragging) return
+
+ // Canvas-space delta
+ var dCX = (mouse.x + monItem.x) - pressCX
+ var dCY = (mouse.y + monItem.y) - pressCY
+
+ // Real-space delta
+ var dRX = dCX / root.viewScale
+ var dRY = dCY / root.viewScale
+
+ var newX = Math.round((startRealX + dRX) / 10) * 10
+ var newY = Math.round((startRealY + dRY) / 10) * 10
+
+ // Snap to other monitors
+ var mw = logicalW
+ var mh = logicalH
+ var snapPx = 50
+
+ for (var k = 0; k < root.monitors.length; k++) {
+ if (k === index) continue
+ var o = root.monitors[k]
+ if (!o) continue
+ var ox = o.x, oy = o.y
+ var ow = (o.width || 1920) / (o.scale || 1.0)
+ var oh = (o.height || 1080) / (o.scale || 1.0)
+
+ if (Math.abs(newX - (ox + ow)) < snapPx) newX = ox + ow
+ if (Math.abs((newX + mw) - ox) < snapPx) newX = ox - mw
+ if (Math.abs(newY - (oy + oh)) < snapPx) newY = oy + oh
+ if (Math.abs((newY + mh) - oy) < snapPx) newY = oy - mh
+ if (Math.abs(newX - ox) < snapPx) newX = ox
+ if (Math.abs(newY - oy) < snapPx) newY = oy
+ }
+
+ root.dragNewX = newX
+ root.dragNewY = newY
+ }
+
+ onReleased: {
+ if (!dragging) return
+ dragging = false
+ monItem.z = 1
+
+ var rx = root.dragNewX
+ var ry = root.dragNewY
+ var mw = logicalW
+ var mh = logicalH
+
+ // Stronger snap on release
+ var snapPx = 80
+ for (var k = 0; k < root.monitors.length; k++) {
+ if (k === index) continue
+ var o = root.monitors[k]
+ if (!o) continue
+ var ox = o.x, oy = o.y
+ var ow = (o.width || 1920) / (o.scale || 1.0)
+ var oh = (o.height || 1080) / (o.scale || 1.0)
+
+ if (Math.abs(rx - (ox + ow)) < snapPx) rx = ox + ow
+ if (Math.abs((rx + mw) - ox) < snapPx) rx = ox - mw
+ if (Math.abs(ry - (oy + oh)) < snapPx) ry = oy + oh
+ if (Math.abs((ry + mh) - oy) < snapPx) ry = oy - mh
+ if (Math.abs(rx - ox) < snapPx) rx = ox
+ if (Math.abs(ry - oy) < snapPx) ry = oy
+ }
+
+ // Prevent overlap
+ for (var j = 0; j < root.monitors.length; j++) {
+ if (j === index) continue
+ var o2 = root.monitors[j]
+ if (!o2) continue
+ var o2w = (o2.width || 1920) / (o2.scale || 1.0)
+ var o2h = (o2.height || 1080) / (o2.scale || 1.0)
+ if (rx < o2.x + o2w && rx + mw > o2.x && ry < o2.y + o2h && ry + mh > o2.y) {
+ var dL = rx + mw - o2.x, dR = o2.x + o2w - rx
+ var dU = ry + mh - o2.y, dD = o2.y + o2h - ry
+ var minD = Math.min(dL, dR, dU, dD)
+ if (minD === dL) rx = o2.x - mw
+ else if (minD === dR) rx = o2.x + o2w
+ else if (minD === dU) ry = o2.y - mh
+ else ry = o2.y + o2h
+ }
+ }
+
+ rx = Math.round(rx / 10) * 10
+ ry = Math.round(ry / 10) * 10
+
+ // Update model with new array (forces Repeater refresh)
+ var newMons = [];
+ for (var mi = 0; mi < root.monitors.length; mi++) {
+ var src = root.monitors[mi];
+ newMons.push(mi === index ? {
+ name: src.name, width: src.width, height: src.height,
+ x: rx, y: ry, scale: src.scale,
+ refreshRate: src.refreshRate, transform: src.transform || 0,
+ focused: src.focused
+ } : src);
+ }
+ root.monitors = newMons;
+ root.dragIdx = -1
+ root.hasChanges = true
+ root.positionChanged(index, rx, ry)
+ AxctlService.dispatch("monitor " + modelData.name + ",preferred," + rx + "x" + ry + ",auto")
+ }
+ }
+
+ // Hover glow
+ StyledRect {
+ anchors.fill: parent; anchors.margins: -3
+ variant: "common"
+ radius: Styling.radius(-1)
+ opacity: dragArea.containsMouse ? 0.3 : 0.0
+ Behavior on opacity { NumberAnimation { duration: 150 } }
+ z: -1
+ }
+ }
+ }
+ }
+ }
+
+ // Info bar β clean status line
+ StyledRect {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 24
+ variant: "internalbg"
+ radius: Styling.radius(-4)
+
+ RowLayout {
+ anchors.fill: parent
+ anchors.leftMargin: 10; anchors.rightMargin: 10
+ spacing: 8
+
+ Text {
+ Layout.fillWidth: true
+ text: {
+ if (root.monitors.length === 0) return "No monitors detected";
+ var parts = [];
+ for (var i = 0; i < root.monitors.length; i++) {
+ var m = root.monitors[i];
+ parts.push(m.name + " @ " + m.x + "," + m.y);
+ }
+ return (root.hasChanges ? Icons.shieldCheck + " " : Icons.handGrab + " ") + parts.join(" Β· ");
+ }
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-3)
+ color: Colors.outline
+ elide: Text.ElideRight
+ }
+
+ Text {
+ text: Icons.glassPlus + " 1:" + root.viewScale.toFixed(2)
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-4)
+ color: Colors.outlineVariant
+ }
+ }
+ }
+ }
+}
diff --git a/modules/widgets/dashboard/controls/MonitorCard.qml b/modules/widgets/dashboard/controls/MonitorCard.qml
new file mode 100644
index 00000000..80f40a3b
--- /dev/null
+++ b/modules/widgets/dashboard/controls/MonitorCard.qml
@@ -0,0 +1,478 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Io
+import qs.modules.theme
+import qs.modules.components
+import qs.modules.services
+import qs.modules.globals
+import qs.config
+
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+// MonitorCard β Per-monitor settings with polished visuals
+// Primary source: Quickshell.screens (always available)
+// Enriched with: AxctlService data + hyprctl monitors -j
+// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+StyledRect {
+ id: root
+
+ required property int monitorIndex
+ required property var screen
+
+ property var axctlData: null
+ property var detailedInfo: null
+ property var availableModes: []
+ property var validScales: []
+ property int currentModeIndex: 0
+ property bool isFetchingModes: false
+ property string displayName: ""
+ property int displayWidth: 0
+ property int displayHeight: 0
+ property int displayX: 0
+ property int displayY: 0
+ property real displayScale: 1.0
+ property real displayRefreshRate: 60
+ property bool isCollapsed: false
+
+ variant: "pane"
+ Layout.fillWidth: true
+ Layout.preferredHeight: cardLayout.implicitHeight + 20
+ radius: Styling.radius(0)
+ enableShadow: true
+
+ Component.onCompleted: {
+ refreshBasicData();
+ updateAxctlMatch();
+ fetchDetailedInfo();
+ }
+
+ function refreshBasicData() {
+ if (!root.screen) return;
+ root.displayName = root.screen.name || ("Monitor " + (root.monitorIndex + 1));
+ root.displayWidth = root.screen.width || 0;
+ root.displayHeight = root.screen.height || 0;
+ root.displayX = root.screen.x || 0;
+ root.displayY = root.screen.y || 0;
+ }
+
+ function updateAxctlMatch() {
+ if (!root.screen || !root.screen.name) return;
+ var monitors = AxctlService.monitors.values;
+ if (!monitors || monitors.length === 0) return;
+ for (var i = 0; i < monitors.length; i++) {
+ if (monitors[i].name === root.screen.name) {
+ root.axctlData = monitors[i];
+ root.displayRefreshRate = monitors[i].refreshRate || 60;
+ root.displayScale = monitors[i].scale || 1.0;
+ return;
+ }
+ }
+ }
+
+ function fetchDetailedInfo() {
+ if (isFetchingModes || !root.displayName) return;
+ isFetchingModes = true;
+ modeFetcherHyprctl.running = true;
+ }
+
+ property Process modeFetcherHyprctl: Process {
+ command: ["hyprctl", "monitors", "-j"]
+ stdout: StdioCollector {}
+ running: false
+ onExited: exitCode => {
+ if (exitCode === 0) {
+ try {
+ parseMonitorList(JSON.parse(modeFetcherHyprctl.stdout.text));
+ return;
+ } catch (e) { console.warn("MonitorCard: hyprctl parse failed:", e); }
+ }
+ modeFetcherAxctl.running = true;
+ }
+ }
+
+ property Process modeFetcherAxctl: Process {
+ command: ["axctl", "monitor", "list"]
+ stdout: StdioCollector {}
+ running: false
+ onExited: exitCode => {
+ root.isFetchingModes = false;
+ if (exitCode === 0) {
+ try { parseMonitorList(JSON.parse(modeFetcherAxctl.stdout.text)); }
+ catch (e) { console.warn("MonitorCard: axctl parse failed:", e); setFallbackModes(); }
+ } else { setFallbackModes(); }
+ }
+ }
+
+ function parseMonitorList(allMonitors) {
+ root.isFetchingModes = false;
+ if (!allMonitors || !Array.isArray(allMonitors)) { setFallbackModes(); return; }
+ var found = null;
+ for (var i = 0; i < allMonitors.length; i++) {
+ if (allMonitors[i].name === root.displayName) { found = allMonitors[i]; break; }
+ }
+ if (!found) { setFallbackModes(); return; }
+ root.detailedInfo = found;
+ if (found.width) root.displayWidth = found.width;
+ if (found.height) root.displayHeight = found.height;
+ if (found.x !== undefined) root.displayX = found.x;
+ if (found.y !== undefined) root.displayY = found.y;
+ root.displayScale = found.scale || root.displayScale;
+ root.displayRefreshRate = found.refreshRate || found.refresh_rate || root.displayRefreshRate;
+ var modes = found.availableModes || found.available_modes || [];
+ if (modes.length === 0 && found.width && found.height) {
+ modes = [found.width + "x" + found.height + "@" + root.displayRefreshRate.toFixed(2) + "Hz"];
+ }
+ root.availableModes = modes;
+ root.currentModeIndex = 0;
+ for (var j = 0; j < modes.length; j++) {
+ var modeStr = (modes[j] + "").replace("Hz", "").replace("hz", "").trim();
+ if (modeStr.indexOf(found.width + "x" + found.height) === 0) {
+ root.currentModeIndex = j;
+ if (modeStr.indexOf(Math.round(root.displayRefreshRate).toString()) !== -1) break;
+ }
+ }
+ root.validScales = computeScales(root.displayWidth, root.displayHeight);
+ }
+
+ function setFallbackModes() {
+ root.isFetchingModes = false;
+ if (root.displayWidth > 0 && root.displayHeight > 0) {
+ root.availableModes = [root.displayWidth + "x" + root.displayHeight + "@" + root.displayRefreshRate.toFixed(2) + "Hz"];
+ root.currentModeIndex = 0;
+ root.validScales = computeScales(root.displayWidth, root.displayHeight);
+ }
+ }
+
+ function computeScales(w, h) {
+ var scales = [];
+ if (w <= 0 || h <= 0) { return [1.0, 1.25, 1.5, 1.75, 2.0]; }
+ var base = Math.max(1.0, Math.min(w / 640, h / 480));
+ for (var step = 0; step <= 120; step++) {
+ var s = base + step / 120.0;
+ if (s > 10.0) break;
+ scales.push(s);
+ }
+ if (scales.length === 0) scales = [1.0];
+ return scales;
+ }
+
+ function findScaleIndex(targetScale) {
+ if (!root.validScales || root.validScales.length === 0) return 0;
+ var bestIdx = 0, bestDiff = Infinity;
+ for (var i = 0; i < root.validScales.length; i++) {
+ var diff = Math.abs(root.validScales[i] - targetScale);
+ if (diff < bestDiff) { bestDiff = diff; bestIdx = i; }
+ }
+ return bestIdx;
+ }
+
+ function applyMonitorSetting(key, value) {
+ var monName = root.displayName;
+ if (!monName) return;
+ GlobalStates.markCompositorChanged();
+ var cmd = "";
+ if (key === "resolution") cmd = "monitor " + monName + "," + value + ",auto,auto";
+ else if (key === "position") cmd = "monitor " + monName + ",preferred," + value.x + "x" + value.y + ",auto";
+ else if (key === "scale") cmd = "monitor " + monName + ",preferred,auto," + value;
+ else if (key === "transform") cmd = "monitor " + monName + ",preferred,auto,auto,transform," + value;
+ else if (key === "vrr") cmd = "monitor " + monName + ",preferred,auto,auto,vrr," + value;
+ else if (key === "disabled") cmd = "monitor " + monName + "," + (value ? "disable" : "preferred,auto,auto");
+ if (cmd) {
+ AxctlService.dispatch(cmd);
+ // Persist to disk (debounced)
+ monitorSyncDebounce.restart();
+ }
+ }
+
+ // ββββββββββββββββββββββββββββββββββββββββββ
+ // UI β Clean, modern design
+ // ββββββββββββββββββββββββββββββββββββββββββ
+ ColumnLayout {
+ id: cardLayout
+ anchors.fill: parent
+ anchors.margins: 14
+ spacing: 10
+
+ // ββ Header row ββ
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 10
+
+ Rectangle {
+ width: 10; height: 10; radius: 5
+ color: (AxctlService.focusedMonitor && AxctlService.focusedMonitor.name === root.displayName)
+ ? Styling.srItem("primary") : Colors.outline
+ Layout.alignment: Qt.AlignVCenter
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ spacing: 1
+
+ Text {
+ text: root.displayName
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(1)
+ font.bold: true
+ color: Colors.overBackground
+ }
+
+ Text {
+ text: {
+ var p = [];
+ if (root.detailedInfo && root.detailedInfo.make) p.push(root.detailedInfo.make);
+ if (root.detailedInfo && root.detailedInfo.model) p.push(root.detailedInfo.model);
+ if (root.displayWidth > 0) p.push(root.displayWidth + "Γ" + root.displayHeight + " @ " + Math.round(root.displayRefreshRate) + "Hz");
+ return p.join(" Β· ");
+ }
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-2)
+ color: Colors.outline
+ elide: Text.ElideRight
+ }
+ }
+
+ Button {
+ flat: true
+ Layout.preferredWidth: 32; Layout.preferredHeight: 32
+ Layout.alignment: Qt.AlignVCenter
+
+ contentItem: Text {
+ text: root.isCollapsed ? Icons.caretDown : Icons.caretUp
+ font.family: Icons.font; font.pixelSize: 16
+ color: Colors.outline
+ anchors.centerIn: parent
+ }
+
+ background: StyledRect {
+ variant: "common"
+ radius: Styling.radius(-6)
+ }
+ onClicked: root.isCollapsed = !root.isCollapsed
+ }
+ }
+
+ // ββ Quick info chips (always visible) ββ
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 6
+
+ StyledRect {
+ variant: "internalbg"
+ Layout.preferredHeight: 22
+ radius: Styling.radius(-6)
+ implicitWidth: posChipRow.implicitWidth + 12
+ RowLayout {
+ id: posChipRow; anchors.centerIn: parent; spacing: 3
+ Text { text: Icons.arrowsOutCardinal; font.family: Icons.font; font.pixelSize: 10; color: Colors.outline }
+ Text { text: root.displayX + ", " + root.displayY; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-3); color: Colors.outline }
+ }
+ }
+
+ StyledRect {
+ variant: "internalbg"
+ Layout.preferredHeight: 22
+ radius: Styling.radius(-6)
+ implicitWidth: scaleChipRow.implicitWidth + 12
+ RowLayout {
+ id: scaleChipRow; anchors.centerIn: parent; spacing: 3
+ Text { text: Icons.arrowsOut; font.family: Icons.font; font.pixelSize: 10; color: Colors.outline }
+ Text { text: root.displayScale.toFixed(2) + "x"; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-3); color: Colors.outline }
+ }
+ }
+
+ StyledRect {
+ variant: "internalbg"
+ Layout.preferredHeight: 22
+ radius: Styling.radius(-6)
+ implicitWidth: rrChipRow.implicitWidth + 12
+ RowLayout {
+ id: rrChipRow; anchors.centerIn: parent; spacing: 3
+ Text { text: Icons.arrowCounterClockwise; font.family: Icons.font; font.pixelSize: 10; color: Colors.outline }
+ Text { text: Math.round(root.displayRefreshRate) + "Hz"; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-3); color: Colors.outline }
+ }
+ }
+
+ Item { Layout.fillWidth: true }
+
+ Switch {
+ id: enabledSwitch; checked: true; Layout.alignment: Qt.AlignVCenter
+ onToggled: root.applyMonitorSetting("disabled", !checked)
+ indicator: Rectangle {
+ implicitWidth: 36; implicitHeight: 20; radius: 10
+ color: enabledSwitch.checked ? Styling.srItem("primary") : Qt.rgba(Colors.outline.r, Colors.outline.g, Colors.outline.b, 0.3)
+ border.color: enabledSwitch.checked ? Styling.srItem("primary") : Colors.outline; border.width: 1
+ Rectangle {
+ x: enabledSwitch.checked ? parent.width - width - 3 : 3
+ y: (parent.height - height) / 2
+ width: 14; height: 14; radius: 7
+ color: enabledSwitch.checked ? "#ffffff" : Colors.outline
+ Behavior on x { enabled: Config.animDuration > 0; NumberAnimation { duration: Config.animDuration / 2; easing.type: Easing.OutCubic } }
+ }
+ Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: Config.animDuration / 2 } }
+ }
+ }
+ }
+
+ // ββ Expandable settings ββ
+ Item {
+ Layout.fillWidth: true
+ Layout.preferredHeight: root.isCollapsed ? 0 : settingsColumn.implicitHeight
+ clip: true
+ visible: !root.isCollapsed
+ Behavior on Layout.preferredHeight {
+ enabled: Config.animDuration > 0
+ NumberAnimation { duration: Config.animDuration / 2; easing.type: Easing.OutCubic }
+ }
+
+ ColumnLayout {
+ id: settingsColumn; width: parent.width; spacing: 6
+
+ SettingsRow {
+ icon: Icons.layout; label: "Resolution"; Layout.fillWidth: true
+ ComboBox {
+ id: modeCombo
+ model: root.availableModes.length > 0 ? root.availableModes.map(function(m) { return (m+"").replace("Hz"," Hz"); }) : [root.displayWidth + "Γ" + root.displayHeight + " " + Math.round(root.displayRefreshRate) + " Hz"]
+ currentIndex: root.currentModeIndex; Layout.preferredWidth: 190
+ background: Rectangle {
+ color: modeCombo.hovered ? Colors.surfaceContainerHigh : Colors.surfaceContainer
+ radius: Styling.radius(-2)
+ border.color: Colors.outlineVariant; border.width: 1
+ }
+ contentItem: Text { text: modeCombo.displayText; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-1); color: Colors.overBackground; verticalAlignment: Text.AlignVCenter; leftPadding: 10; elide: Text.ElideRight }
+ indicator: Text { text: Icons.caretDown; font.family: Icons.font; font.pixelSize: 14; color: Colors.overBackground; anchors.verticalCenter: parent.verticalCenter; anchors.right: parent.right; anchors.rightMargin: 8 }
+ onActivated: { if (root.availableModes.length > 0 && index < root.availableModes.length) root.applyMonitorSetting("resolution", root.availableModes[index]); }
+ }
+ }
+
+ SettingsRow {
+ icon: Icons.arrowsOut; label: "Scale"; Layout.fillWidth: true
+ RowLayout {
+ spacing: 4
+ TextField {
+ id: scaleInput
+ text: root.displayScale.toFixed(2)
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-1)
+ color: Colors.overBackground
+ Layout.preferredWidth: 70
+ horizontalAlignment: Text.AlignRight
+ validator: DoubleValidator { bottom: 0.25; top: 10.0; decimals: 2 }
+ background: Rectangle {
+ color: scaleInput.hovered ? Colors.surfaceContainerHigh : Colors.surfaceContainer
+ radius: Styling.radius(-2)
+ border.color: Colors.outlineVariant; border.width: 1
+ }
+ onEditingFinished: {
+ var val = parseFloat(text);
+ if (!isNaN(val) && val >= 0.25 && val <= 10.0) {
+ root.applyMonitorSetting("scale", val);
+ } else {
+ text = root.displayScale.toFixed(2);
+ }
+ }
+ }
+ Text {
+ text: "Γ"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-1)
+ color: Colors.outline
+ }
+ }
+ }
+
+ SettingsRow {
+ icon: Icons.arrowsOutCardinal; label: "Position"; Layout.fillWidth: true
+ RowLayout {
+ spacing: 4
+ Text { text: "X"; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2); color: Colors.outline }
+ SpinBox {
+ id: posX; from: -10000; to: 10000; stepSize: 10; value: root.displayX; editable: true; Layout.preferredWidth: 80
+ background: Rectangle { color: Colors.surfaceContainer; border.color: Colors.outlineVariant; border.width: 1; radius: Styling.radius(-2) }
+ contentItem: TextInput { text: posX.value; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-1); color: Colors.overBackground; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter }
+ onValueModified: root.applyMonitorSetting("position", { x: posX.value, y: posY.value })
+ }
+ Text { text: "Y"; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2); color: Colors.outline }
+ SpinBox {
+ id: posY; from: -10000; to: 10000; stepSize: 10; value: root.displayY; editable: true; Layout.preferredWidth: 80
+ background: Rectangle { color: Colors.surfaceContainer; border.color: Colors.outlineVariant; border.width: 1; radius: Styling.radius(-2) }
+ contentItem: TextInput { text: posY.value; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-1); color: Colors.overBackground; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter }
+ onValueModified: root.applyMonitorSetting("position", { x: posX.value, y: posY.value })
+ }
+ }
+ }
+
+ SettingsRow {
+ icon: Icons.arrowCounterClockwise; label: "Rotation"; Layout.fillWidth: true
+ ComboBox {
+ id: transformCombo
+ model: ["0Β° Normal", "90Β°", "180Β°", "270Β°", "90Β° Flip", "270Β° Flip"]
+ currentIndex: root.detailedInfo ? Math.min(root.detailedInfo.transform || 0, 5) : 0; Layout.preferredWidth: 140
+ background: Rectangle {
+ color: transformCombo.hovered ? Colors.surfaceContainerHigh : Colors.surfaceContainer
+ radius: Styling.radius(-2)
+ border.color: Colors.outlineVariant; border.width: 1
+ }
+ contentItem: Text { text: transformCombo.displayText; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-1); color: Colors.overBackground; verticalAlignment: Text.AlignVCenter; leftPadding: 10 }
+ indicator: Text { text: Icons.caretDown; font.family: Icons.font; font.pixelSize: 14; color: Colors.overBackground; anchors.verticalCenter: parent.verticalCenter; anchors.right: parent.right; anchors.rightMargin: 8 }
+ onActivated: root.applyMonitorSetting("transform", index)
+ }
+ }
+
+ SettingsRow {
+ icon: Icons.waveform; label: "VRR"; Layout.fillWidth: true
+ ComboBox {
+ id: vrrCombo
+ model: ["Global Default", "Disabled", "Enabled", "Fullscreen", "Fullscreen+Gaming"]
+ currentIndex: 0; Layout.preferredWidth: 160
+ background: Rectangle {
+ color: vrrCombo.hovered ? Colors.surfaceContainerHigh : Colors.surfaceContainer
+ radius: Styling.radius(-2)
+ border.color: Colors.outlineVariant; border.width: 1
+ }
+ contentItem: Text { text: vrrCombo.displayText; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-1); color: Colors.overBackground; verticalAlignment: Text.AlignVCenter; leftPadding: 10 }
+ indicator: Text { text: Icons.caretDown; font.family: Icons.font; font.pixelSize: 14; color: Colors.overBackground; anchors.verticalCenter: parent.verticalCenter; anchors.right: parent.right; anchors.rightMargin: 8 }
+ onActivated: { var v = [null, "0", "1", "2", "3"]; root.applyMonitorSetting("vrr", v[index]); }
+ }
+ }
+ }
+ }
+ }
+
+ // ββ Inline: SettingsRow component ββ
+ component SettingsRow: RowLayout {
+ property string icon: ""; property string label: ""
+ spacing: 8
+ Text { text: icon; font.family: Icons.font; font.pixelSize: 14; color: Colors.outline; Layout.preferredWidth: 18 }
+ Text { text: label; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-1); color: Colors.overBackground; Layout.preferredWidth: 80 }
+ }
+
+ // Debounced monitor config sync (after settings change)
+ Timer {
+ id: monitorSyncDebounce
+ interval: 2500
+ repeat: false
+ onTriggered: MonitorsWriter.sync()
+ }
+
+ // ββ Connections ββ
+ Connections {
+ target: AxctlService
+ function onMonitorsChanged() {
+ root.updateAxctlMatch();
+ // Sync to disk after Axctl reports the change
+ monitorSyncDebounce.restart();
+ }
+ }
+
+ onDetailedInfoChanged: {
+ if (detailedInfo) {
+ if (detailedInfo.x !== undefined) posX.value = detailedInfo.x;
+ if (detailedInfo.y !== undefined) posY.value = detailedInfo.y;
+ if (detailedInfo.transform !== undefined) transformCombo.currentIndex = Math.min(detailedInfo.transform, 5);
+ }
+ }
+}
diff --git a/modules/widgets/dashboard/controls/MonitorsPanel.qml b/modules/widgets/dashboard/controls/MonitorsPanel.qml
new file mode 100644
index 00000000..857c8d56
--- /dev/null
+++ b/modules/widgets/dashboard/controls/MonitorsPanel.qml
@@ -0,0 +1,75 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import qs.modules.theme
+import qs.modules.components
+import qs.modules.services
+import qs.config
+
+Item {
+ id: root
+ implicitHeight: layout.implicitHeight
+ Layout.fillWidth: true
+
+ ColumnLayout {
+ id: layout
+ anchors.left: parent.left
+ anchors.right: parent.right
+ spacing: 14
+
+ // ββ Visual arrangement with drag & drop ββ
+ MonitorArrangementView {
+ id: arrangementView
+ Layout.fillWidth: true
+ }
+
+ // ββ Section title ββ
+ Text {
+ text: "Per-Monitor Settings"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-1)
+ font.weight: Font.Medium
+ color: Colors.outline
+ Layout.topMargin: 4
+ }
+
+ // ββ Individual monitor cards ββ
+ Repeater {
+ model: Quickshell.screens
+
+ delegate: MonitorCard {
+ required property int index
+ required property var modelData
+ monitorIndex: index
+ screen: modelData
+ Layout.fillWidth: true
+ }
+ }
+
+ // ββ Actions ββ
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 8
+
+ Button {
+ text: "Reset Positions"
+ Layout.fillWidth: true
+ onClicked: {
+ for (var j = 0; j < Quickshell.screens.length; j++) {
+ var s = Quickshell.screens[j];
+ AxctlService.dispatch("monitor " + s.name + ",preferred,0x0,auto");
+ }
+ }
+ }
+
+ Button {
+ text: "Detect Displays"
+ Layout.fillWidth: true
+ onClicked: {
+ AxctlService.dispatch("monitor all,preferred,auto,auto");
+ }
+ }
+ }
+ }
+}
diff --git a/modules/widgets/dashboard/controls/PanelTitlebar.qml b/modules/widgets/dashboard/controls/PanelTitlebar.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/controls/SettingsCrawler.js b/modules/widgets/dashboard/controls/SettingsCrawler.js
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/controls/SettingsIndex.qml b/modules/widgets/dashboard/controls/SettingsIndex.qml
old mode 100644
new mode 100755
index 6a606409..1788f55d
--- a/modules/widgets/dashboard/controls/SettingsIndex.qml
+++ b/modules/widgets/dashboard/controls/SettingsIndex.qml
@@ -11,7 +11,7 @@ QtObject {
// it will try to guess what users would want to search, not the feature name only
// Main Sections:
- // 0: Network, 1: Bluetooth, 2: Mixer, 3: Effects, 4: Theme, 5: Binds, 6: System, 7: Compositor, 8: Ambxst
+ // 0: Network, 1: Bluetooth, 2: Mixer, 3: Effects, 4: Theme, 5: Binds, 6: System, 7: Compositor, 8: Shell
property var dynamicItems: []
@@ -61,25 +61,25 @@ QtObject {
// --- Binds ---
{ label: "Key Bindings", keywords: "shortcuts keyboard hotkeys", section: 5, subSection: "", subLabel: "", icon: Icons.keyboard, isIcon: true },
- // Binds > Ambxst
- { label: "Launcher Keybind", keywords: "app launcher menu shortcut", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.rocket, isIcon: true },
- { label: "Dashboard Keybind", keywords: "widgets dashboard shortcut", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.squaresFour, isIcon: true },
- { label: "Clipboard Keybind", keywords: "copy paste shortcut super v", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.clipboard, isIcon: true },
- { label: "Emoji Keybind", keywords: "picker shortcut super period", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.keyboard, isIcon: true },
- { label: "Tmux Keybind", keywords: "terminal shortcut", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.keyboard, isIcon: true },
- { label: "Wallpapers Keybind", keywords: "background shortcut super comma", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.keyboard, isIcon: true },
- { label: "Assistant Keybind", keywords: "ai help shortcut", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.keyboard, isIcon: true },
- { label: "Notes Keybind", keywords: "note shortcut super n", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.keyboard, isIcon: true },
- { label: "Overview Keybind", keywords: "workspace shortcut super tab", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.keyboard, isIcon: true },
- { label: "Powermenu Keybind", keywords: "logout shutdown shortcut super escape", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.power, isIcon: true },
- { label: "Settings Keybind", keywords: "config preferences shortcut", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.gear, isIcon: true },
- { label: "Lockscreen Keybind", keywords: "lock security shortcut", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.lock, isIcon: true },
- { label: "Tools Keybind", keywords: "utilities tools shortcut", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.wrench, isIcon: true },
- { label: "Screenshot Keybind", keywords: "capture screen shortcut print", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.camera, isIcon: true },
- { label: "Screenrecord Keybind", keywords: "record video shortcut", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.videoCamera, isIcon: true },
- { label: "Lens Keybind", keywords: "magnifier zoom shortcut", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.magnifyingGlass, isIcon: true },
- { label: "Reload Keybind", keywords: "refresh restart shortcut", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.arrowCounterClockwise, isIcon: true },
- { label: "Quit Keybind", keywords: "exit close shortcut", section: 5, subSection: "", subLabel: "Binds > Ambxst", icon: Icons.signOut, isIcon: true },
+ // Binds > NothingLess
+ { label: "Launcher Keybind", keywords: "app launcher menu shortcut", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.rocket, isIcon: true },
+ { label: "Dashboard Keybind", keywords: "widgets dashboard shortcut", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.squaresFour, isIcon: true },
+ { label: "Clipboard Keybind", keywords: "copy paste shortcut super v", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.clipboard, isIcon: true },
+ { label: "Emoji Keybind", keywords: "picker shortcut super period", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.keyboard, isIcon: true },
+ { label: "Tmux Keybind", keywords: "terminal shortcut", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.keyboard, isIcon: true },
+ { label: "Wallpapers Keybind", keywords: "background shortcut super comma", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.keyboard, isIcon: true },
+ { label: "Assistant Keybind", keywords: "ai help shortcut", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.keyboard, isIcon: true },
+ { label: "Notes Keybind", keywords: "note shortcut super n", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.keyboard, isIcon: true },
+ { label: "Overview Keybind", keywords: "workspace shortcut super tab", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.keyboard, isIcon: true },
+ { label: "Powermenu Keybind", keywords: "logout shutdown shortcut super escape", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.power, isIcon: true },
+ { label: "Settings Keybind", keywords: "config preferences shortcut", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.gear, isIcon: true },
+ { label: "Lockscreen Keybind", keywords: "lock security shortcut", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.lock, isIcon: true },
+ { label: "Tools Keybind", keywords: "utilities tools shortcut", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.wrench, isIcon: true },
+ { label: "Screenshot Keybind", keywords: "capture screen shortcut print", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.camera, isIcon: true },
+ { label: "Screenrecord Keybind", keywords: "record video shortcut", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.videoCamera, isIcon: true },
+ { label: "Lens Keybind", keywords: "magnifier zoom shortcut", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.magnifyingGlass, isIcon: true },
+ { label: "Reload Keybind", keywords: "refresh restart shortcut", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.arrowCounterClockwise, isIcon: true },
+ { label: "Quit Keybind", keywords: "exit close shortcut", section: 5, subSection: "", subLabel: "Binds > NothingLess", icon: Icons.signOut, isIcon: true },
// --- System ---
{ label: "System", keywords: "hardware info resources cpu ram", section: 6, subSection: "", subLabel: "System", icon: Icons.circuitry, isIcon: true },
@@ -99,14 +99,14 @@ QtObject {
// System > Performance
{ label: "Blur Transition", keywords: "animation speed performance effect", section: 6, subSection: "performance", subLabel: "System > Performance", icon: Icons.lightning, isIcon: true },
{ label: "Window Preview", keywords: "thumbnail overview alt-tab", section: 6, subSection: "performance", subLabel: "System > Performance", icon: Icons.windowsLogo, isIcon: true },
- { label: "Wavy Line", keywords: "animated wave effect performance", section: 6, subSection: "performance", subLabel: "System > Performance", icon: Icons.lightning, isIcon: true },
+
// System > Resources
{ label: "System Resources", keywords: "cpu ram memory usage monitor", section: 6, subSection: "resources", subLabel: "System > Resources", icon: Icons.circuitry, isIcon: true },
// System > Idle
{ label: "Idle Settings", keywords: "screen lock timeout sleep suspend", section: 6, subSection: "idle", subLabel: "System > Idle", icon: Icons.moon, isIcon: true },
- { label: "Lock Command", keywords: "ambxst lock screen idle", section: 6, subSection: "idle", subLabel: "System > Idle", icon: Icons.moon, isIcon: true },
+ { label: "Lock Command", keywords: "nothingless lock screen idle", section: 6, subSection: "idle", subLabel: "System > Idle", icon: Icons.moon, isIcon: true },
{ label: "Before Sleep", keywords: "loginctl lock-session idle", section: 6, subSection: "idle", subLabel: "System > Idle", icon: Icons.moon, isIcon: true },
{ label: "After Sleep", keywords: "screen on resume idle", section: 6, subSection: "idle", subLabel: "System > Idle", icon: Icons.moon, isIcon: true },
{ label: "Idle Listener", keywords: "timeout brightness screen off suspend", section: 6, subSection: "idle", subLabel: "System > Idle", icon: Icons.moon, isIcon: true },
@@ -144,70 +144,70 @@ QtObject {
{ label: "Blur Brightness", keywords: "light dark level", section: 7, subSection: "blur", subLabel: "Compositor > AxctlService", icon: Icons.drop, isIcon: true },
{ label: "Blur Vibrancy", keywords: "saturation color", section: 7, subSection: "blur", subLabel: "Compositor > AxctlService", icon: Icons.drop, isIcon: true },
- // --- Ambxst / Shell ---
- { label: "Ambxst", keywords: "about info credits version shell", section: 8, subSection: "", subLabel: "", icon: Qt.resolvedUrl("../../../../assets/ambxst/ambxst-icon.svg"), isIcon: false },
-
- // Ambxst > Bar
- { label: "Bar", keywords: "panel taskbar top bottom", section: 8, subSection: "bar", subLabel: "Ambxst > Bar", icon: Icons.layout, isIcon: true },
- { label: "Bar Position", keywords: "top bottom left right edge", section: 8, subSection: "bar", subLabel: "Ambxst > Bar", icon: Icons.layout, isIcon: true },
- { label: "Launcher Icon", keywords: "logo symbol path", section: 8, subSection: "bar", subLabel: "Ambxst > Bar", icon: Icons.layout, isIcon: true },
- { label: "Launcher Icon Tint", keywords: "color theme", section: 8, subSection: "bar", subLabel: "Ambxst > Bar", icon: Icons.palette, isIcon: true },
- { label: "Launcher Icon Full Tint", keywords: "monochrome color", section: 8, subSection: "bar", subLabel: "Ambxst > Bar", icon: Icons.palette, isIcon: true },
- { label: "Launcher Icon Size", keywords: "width height pixels", section: 8, subSection: "bar", subLabel: "Ambxst > Bar", icon: Icons.layout, isIcon: true },
- { label: "Pill Style", keywords: "squished roundness radius bar", section: 8, subSection: "bar", subLabel: "Ambxst > Bar", icon: Icons.layout, isIcon: true },
- { label: "Firefox Player", keywords: "browser media music", section: 8, subSection: "bar", subLabel: "Ambxst > Bar", icon: Icons.layout, isIcon: true },
- { label: "Bar Auto-hide", keywords: "autohide hide show reveal", section: 8, subSection: "bar", subLabel: "Ambxst > Bar", icon: Icons.layout, isIcon: true },
- { label: "Pinned on Startup", keywords: "show visible default", section: 8, subSection: "bar", subLabel: "Ambxst > Bar", icon: Icons.layout, isIcon: true },
- { label: "Hover to Reveal", keywords: "mouse show hide edge", section: 8, subSection: "bar", subLabel: "Ambxst > Bar", icon: Icons.layout, isIcon: true },
- { label: "Hover Region Height", keywords: "pixels trigger area", section: 8, subSection: "bar", subLabel: "Ambxst > Bar", icon: Icons.layout, isIcon: true },
- { label: "Show Pin Button", keywords: "toggle pin unpin", section: 8, subSection: "bar", subLabel: "Ambxst > Bar", icon: Icons.layout, isIcon: true },
- { label: "Available on Fullscreen", keywords: "overlay game video", section: 8, subSection: "bar", subLabel: "Ambxst > Bar", icon: Icons.layout, isIcon: true },
- { label: "Show Running Indicators", keywords: "dots active apps", section: 8, subSection: "bar", subLabel: "Ambxst > Bar", icon: Icons.layout, isIcon: true },
- { label: "Show Overview Button", keywords: "workspace switcher", section: 8, subSection: "bar", subLabel: "Ambxst > Bar", icon: Icons.layout, isIcon: true },
- { label: "Bar Screens", keywords: "monitor display eDP", section: 8, subSection: "bar", subLabel: "Ambxst > Bar", icon: Icons.layout, isIcon: true },
-
- // Ambxst > Notch
- { label: "Notch", keywords: "island dynamic island center", section: 8, subSection: "notch", subLabel: "Ambxst > Notch", icon: Icons.layout, isIcon: true },
-
- // Ambxst > Workspaces
- { label: "Workspaces", keywords: "virtual desktop spaces", section: 8, subSection: "workspaces", subLabel: "Ambxst > Workspaces", icon: Icons.squaresFour, isIcon: true },
- { label: "Workspaces Shown", keywords: "number count visible", section: 8, subSection: "workspaces", subLabel: "Ambxst > Workspaces", icon: Icons.squaresFour, isIcon: true },
- { label: "Show App Icons", keywords: "application thumbnail workspace", section: 8, subSection: "workspaces", subLabel: "Ambxst > Workspaces", icon: Icons.squaresFour, isIcon: true },
- { label: "Always Show Numbers", keywords: "workspace label index", section: 8, subSection: "workspaces", subLabel: "Ambxst > Workspaces", icon: Icons.squaresFour, isIcon: true },
- { label: "Show Numbers", keywords: "workspace label index", section: 8, subSection: "workspaces", subLabel: "Ambxst > Workspaces", icon: Icons.squaresFour, isIcon: true },
- { label: "Dynamic Workspaces", keywords: "auto add remove flexible", section: 8, subSection: "workspaces", subLabel: "Ambxst > Workspaces", icon: Icons.squaresFour, isIcon: true },
-
- // Ambxst > Overview
- { label: "Overview", keywords: "expose mission control windows", section: 8, subSection: "overview", subLabel: "Ambxst > Overview", icon: Icons.squaresFour, isIcon: true },
- { label: "Overview Rows", keywords: "grid layout vertical", section: 8, subSection: "overview", subLabel: "Ambxst > Overview", icon: Icons.squaresFour, isIcon: true },
- { label: "Overview Columns", keywords: "grid layout horizontal", section: 8, subSection: "overview", subLabel: "Ambxst > Overview", icon: Icons.squaresFour, isIcon: true },
- { label: "Overview Scale", keywords: "zoom size preview", section: 8, subSection: "overview", subLabel: "Ambxst > Overview", icon: Icons.squaresFour, isIcon: true },
- { label: "Overview Workspace Spacing", keywords: "gap margin distance", section: 8, subSection: "overview", subLabel: "Ambxst > Overview", icon: Icons.squaresFour, isIcon: true },
-
- // Ambxst > Dock
- { label: "Dock", keywords: "taskbar launcher apps favorites", section: 8, subSection: "dock", subLabel: "Ambxst > Dock", icon: Icons.layout, isIcon: true },
- { label: "Dock Enabled", keywords: "show hide toggle", section: 8, subSection: "dock", subLabel: "Ambxst > Dock", icon: Icons.layout, isIcon: true },
- { label: "Dock Mode", keywords: "default floating integrated style", section: 8, subSection: "dock", subLabel: "Ambxst > Dock", icon: Icons.layout, isIcon: true },
- { label: "Dock Position", keywords: "left bottom right edge", section: 8, subSection: "dock", subLabel: "Ambxst > Dock", icon: Icons.layout, isIcon: true },
- { label: "Dock Height", keywords: "size thickness pixels", section: 8, subSection: "dock", subLabel: "Ambxst > Dock", icon: Icons.layout, isIcon: true },
- { label: "Dock Icon Size", keywords: "width height pixels apps", section: 8, subSection: "dock", subLabel: "Ambxst > Dock", icon: Icons.layout, isIcon: true },
- { label: "Dock Spacing", keywords: "gap between icons", section: 8, subSection: "dock", subLabel: "Ambxst > Dock", icon: Icons.layout, isIcon: true },
- { label: "Dock Margin", keywords: "edge distance offset", section: 8, subSection: "dock", subLabel: "Ambxst > Dock", icon: Icons.layout, isIcon: true },
- { label: "Dock Hover Region Height", keywords: "trigger area pixels", section: 8, subSection: "dock", subLabel: "Ambxst > Dock", icon: Icons.layout, isIcon: true },
- { label: "Dock Pinned on Startup", keywords: "show visible default", section: 8, subSection: "dock", subLabel: "Ambxst > Dock", icon: Icons.layout, isIcon: true },
-
- // Ambxst > Lockscreen
- { label: "Lockscreen", keywords: "lock screen password login", section: 8, subSection: "lockscreen", subLabel: "Ambxst > Lockscreen", icon: Icons.lock, isIcon: true },
-
- // Ambxst > Desktop
- { label: "Desktop", keywords: "icons wallpaper home", section: 8, subSection: "desktop", subLabel: "Ambxst > Desktop", icon: Icons.layout, isIcon: true },
- { label: "Desktop Enabled", keywords: "show hide icons toggle", section: 8, subSection: "desktop", subLabel: "Ambxst > Desktop", icon: Icons.layout, isIcon: true },
- { label: "Desktop Icon Size", keywords: "width height pixels", section: 8, subSection: "desktop", subLabel: "Ambxst > Desktop", icon: Icons.layout, isIcon: true },
- { label: "Desktop Vertical Spacing", keywords: "gap margin", section: 8, subSection: "desktop", subLabel: "Ambxst > Desktop", icon: Icons.layout, isIcon: true },
- { label: "Desktop Text Color", keywords: "label font", section: 8, subSection: "desktop", subLabel: "Ambxst > Desktop", icon: Icons.palette, isIcon: true },
-
- // Ambxst > System
- { label: "Shell System", keywords: "config settings ambxst", section: 8, subSection: "system", subLabel: "Ambxst > System", icon: Icons.circuitry, isIcon: true }
+ // --- NothingLess / Shell ---
+ { label: "NothingLess", keywords: "about info credits version shell", section: 8, subSection: "", subLabel: "", icon: Qt.resolvedUrl("../../../../assets/nothingless/nothingless-icon.svg"), isIcon: false },
+
+ // NothingLess > Bar
+ { label: "Bar", keywords: "panel taskbar top bottom", section: 8, subSection: "bar", subLabel: "NothingLess > Bar", icon: Icons.layout, isIcon: true },
+ { label: "Bar Position", keywords: "top bottom left right edge", section: 8, subSection: "bar", subLabel: "NothingLess > Bar", icon: Icons.layout, isIcon: true },
+ { label: "Launcher Icon", keywords: "logo symbol path", section: 8, subSection: "bar", subLabel: "NothingLess > Bar", icon: Icons.layout, isIcon: true },
+ { label: "Launcher Icon Tint", keywords: "color theme", section: 8, subSection: "bar", subLabel: "NothingLess > Bar", icon: Icons.palette, isIcon: true },
+ { label: "Launcher Icon Full Tint", keywords: "monochrome color", section: 8, subSection: "bar", subLabel: "NothingLess > Bar", icon: Icons.palette, isIcon: true },
+ { label: "Launcher Icon Size", keywords: "width height pixels", section: 8, subSection: "bar", subLabel: "NothingLess > Bar", icon: Icons.layout, isIcon: true },
+ { label: "Pill Style", keywords: "squished roundness radius bar", section: 8, subSection: "bar", subLabel: "NothingLess > Bar", icon: Icons.layout, isIcon: true },
+ { label: "Firefox Player", keywords: "browser media music", section: 8, subSection: "bar", subLabel: "NothingLess > Bar", icon: Icons.layout, isIcon: true },
+ { label: "Bar Auto-hide", keywords: "autohide hide show reveal", section: 8, subSection: "bar", subLabel: "NothingLess > Bar", icon: Icons.layout, isIcon: true },
+ { label: "Pinned on Startup", keywords: "show visible default", section: 8, subSection: "bar", subLabel: "NothingLess > Bar", icon: Icons.layout, isIcon: true },
+ { label: "Hover to Reveal", keywords: "mouse show hide edge", section: 8, subSection: "bar", subLabel: "NothingLess > Bar", icon: Icons.layout, isIcon: true },
+ { label: "Hover Region Height", keywords: "pixels trigger area", section: 8, subSection: "bar", subLabel: "NothingLess > Bar", icon: Icons.layout, isIcon: true },
+ { label: "Show Pin Button", keywords: "toggle pin unpin", section: 8, subSection: "bar", subLabel: "NothingLess > Bar", icon: Icons.layout, isIcon: true },
+ { label: "Available on Fullscreen", keywords: "overlay game video", section: 8, subSection: "bar", subLabel: "NothingLess > Bar", icon: Icons.layout, isIcon: true },
+ { label: "Show Running Indicators", keywords: "dots active apps", section: 8, subSection: "bar", subLabel: "NothingLess > Bar", icon: Icons.layout, isIcon: true },
+ { label: "Show Overview Button", keywords: "workspace switcher", section: 8, subSection: "bar", subLabel: "NothingLess > Bar", icon: Icons.layout, isIcon: true },
+ { label: "Bar Screens", keywords: "monitor display eDP", section: 8, subSection: "bar", subLabel: "NothingLess > Bar", icon: Icons.layout, isIcon: true },
+
+ // NothingLess > Notch
+ { label: "Notch", keywords: "island dynamic island center", section: 8, subSection: "notch", subLabel: "NothingLess > Notch", icon: Icons.layout, isIcon: true },
+
+ // NothingLess > Workspaces
+ { label: "Workspaces", keywords: "virtual desktop spaces", section: 8, subSection: "workspaces", subLabel: "NothingLess > Workspaces", icon: Icons.squaresFour, isIcon: true },
+ { label: "Workspaces Shown", keywords: "number count visible", section: 8, subSection: "workspaces", subLabel: "NothingLess > Workspaces", icon: Icons.squaresFour, isIcon: true },
+ { label: "Show App Icons", keywords: "application thumbnail workspace", section: 8, subSection: "workspaces", subLabel: "NothingLess > Workspaces", icon: Icons.squaresFour, isIcon: true },
+ { label: "Always Show Numbers", keywords: "workspace label index", section: 8, subSection: "workspaces", subLabel: "NothingLess > Workspaces", icon: Icons.squaresFour, isIcon: true },
+ { label: "Show Numbers", keywords: "workspace label index", section: 8, subSection: "workspaces", subLabel: "NothingLess > Workspaces", icon: Icons.squaresFour, isIcon: true },
+ { label: "Dynamic Workspaces", keywords: "auto add remove flexible", section: 8, subSection: "workspaces", subLabel: "NothingLess > Workspaces", icon: Icons.squaresFour, isIcon: true },
+
+ // NothingLess > Overview
+ { label: "Overview", keywords: "expose mission control windows", section: 8, subSection: "overview", subLabel: "NothingLess > Overview", icon: Icons.squaresFour, isIcon: true },
+ { label: "Overview Rows", keywords: "grid layout vertical", section: 8, subSection: "overview", subLabel: "NothingLess > Overview", icon: Icons.squaresFour, isIcon: true },
+ { label: "Overview Columns", keywords: "grid layout horizontal", section: 8, subSection: "overview", subLabel: "NothingLess > Overview", icon: Icons.squaresFour, isIcon: true },
+ { label: "Overview Scale", keywords: "zoom size preview", section: 8, subSection: "overview", subLabel: "NothingLess > Overview", icon: Icons.squaresFour, isIcon: true },
+ { label: "Overview Workspace Spacing", keywords: "gap margin distance", section: 8, subSection: "overview", subLabel: "NothingLess > Overview", icon: Icons.squaresFour, isIcon: true },
+
+ // NothingLess > Dock
+ { label: "Dock", keywords: "taskbar launcher apps favorites", section: 8, subSection: "dock", subLabel: "NothingLess > Dock", icon: Icons.layout, isIcon: true },
+ { label: "Dock Enabled", keywords: "show hide toggle", section: 8, subSection: "dock", subLabel: "NothingLess > Dock", icon: Icons.layout, isIcon: true },
+ { label: "Dock Mode", keywords: "default floating integrated style", section: 8, subSection: "dock", subLabel: "NothingLess > Dock", icon: Icons.layout, isIcon: true },
+ { label: "Dock Position", keywords: "left bottom right edge", section: 8, subSection: "dock", subLabel: "NothingLess > Dock", icon: Icons.layout, isIcon: true },
+ { label: "Dock Height", keywords: "size thickness pixels", section: 8, subSection: "dock", subLabel: "NothingLess > Dock", icon: Icons.layout, isIcon: true },
+ { label: "Dock Icon Size", keywords: "width height pixels apps", section: 8, subSection: "dock", subLabel: "NothingLess > Dock", icon: Icons.layout, isIcon: true },
+ { label: "Dock Spacing", keywords: "gap between icons", section: 8, subSection: "dock", subLabel: "NothingLess > Dock", icon: Icons.layout, isIcon: true },
+ { label: "Dock Margin", keywords: "edge distance offset", section: 8, subSection: "dock", subLabel: "NothingLess > Dock", icon: Icons.layout, isIcon: true },
+ { label: "Dock Hover Region Height", keywords: "trigger area pixels", section: 8, subSection: "dock", subLabel: "NothingLess > Dock", icon: Icons.layout, isIcon: true },
+ { label: "Dock Pinned on Startup", keywords: "show visible default", section: 8, subSection: "dock", subLabel: "NothingLess > Dock", icon: Icons.layout, isIcon: true },
+
+ // NothingLess > Lockscreen
+ { label: "Lockscreen", keywords: "lock screen password login", section: 8, subSection: "lockscreen", subLabel: "NothingLess > Lockscreen", icon: Icons.lock, isIcon: true },
+
+ // NothingLess > Desktop
+ { label: "Desktop", keywords: "icons wallpaper home", section: 8, subSection: "desktop", subLabel: "NothingLess > Desktop", icon: Icons.layout, isIcon: true },
+ { label: "Desktop Enabled", keywords: "show hide icons toggle", section: 8, subSection: "desktop", subLabel: "NothingLess > Desktop", icon: Icons.layout, isIcon: true },
+ { label: "Desktop Icon Size", keywords: "width height pixels", section: 8, subSection: "desktop", subLabel: "NothingLess > Desktop", icon: Icons.layout, isIcon: true },
+ { label: "Desktop Vertical Spacing", keywords: "gap margin", section: 8, subSection: "desktop", subLabel: "NothingLess > Desktop", icon: Icons.layout, isIcon: true },
+ { label: "Desktop Text Color", keywords: "label font", section: 8, subSection: "desktop", subLabel: "NothingLess > Desktop", icon: Icons.palette, isIcon: true },
+
+ // NothingLess > System
+ { label: "Shell System", keywords: "config settings nothingless", section: 8, subSection: "system", subLabel: "NothingLess > System", icon: Icons.circuitry, isIcon: true }
]
property var items: staticItems.concat(dynamicItems)
diff --git a/modules/widgets/dashboard/controls/SettingsTab.qml b/modules/widgets/dashboard/controls/SettingsTab.qml
old mode 100644
new mode 100755
index fcaeef56..6d40c84f
--- a/modules/widgets/dashboard/controls/SettingsTab.qml
+++ b/modules/widgets/dashboard/controls/SettingsTab.qml
@@ -62,60 +62,67 @@ Rectangle {
id: searchIndex
}
- // Dynamic Settings Indexer
+ // βββ Dynamic Settings Indexer (deferred to avoid startup lag) βββ
Item {
id: settingsIndexer
- visible: false // Headless
+ visible: false
property int currentPanelIndex: 0
property var aggregatedItems: []
property bool isIndexing: false
- // Helper to load panels one by one
Loader {
id: indexerLoader
active: settingsIndexer.isIndexing
asynchronous: true
- source: settingsIndexer.isIndexing && settingsIndexer.currentPanelIndex < contentArea.panelComponents.length ? contentArea.panelComponents[settingsIndexer.currentPanelIndex].component : ""
+ source: settingsIndexer.isIndexing && settingsIndexer.currentPanelIndex < contentArea.panelComponents.length
+ ? contentArea.panelComponents[settingsIndexer.currentPanelIndex].component
+ : ""
onStatusChanged: {
if (status === Loader.Ready && item) {
- // Scrape
const sectionId = contentArea.panelComponents[settingsIndexer.currentPanelIndex].section;
const newItems = SettingsCrawler.crawl(item, sectionId);
settingsIndexer.aggregatedItems = settingsIndexer.aggregatedItems.concat(newItems);
-
- // Move to next
- settingsIndexer.currentPanelIndex++;
+ advanceTimer.start();
} else if (status === Loader.Error) {
console.warn("Failed to load panel for indexing:", source);
- settingsIndexer.currentPanelIndex++;
+ advanceTimer.start();
}
}
}
- onCurrentPanelIndexChanged: {
- if (currentPanelIndex >= contentArea.panelComponents.length) {
- // Done
- if (isIndexing) {
- isIndexing = false;
- searchIndex.addDynamicItems(aggregatedItems);
- }
+ // Timer breaks binding loop: source β statusChanged β currentPanelIndex β source
+ Timer {
+ id: advanceTimer
+ interval: 1
+ onTriggered: {
+ settingsIndexer.currentPanelIndex++;
}
}
- Component.onCompleted: {
- // Start indexing after a short delay to allow UI to settle
- indexingTimer.start();
+ onCurrentPanelIndexChanged: {
+ if (currentPanelIndex >= contentArea.panelComponents.length && isIndexing) {
+ isIndexing = false;
+ searchIndex.addDynamicItems(aggregatedItems);
+ }
}
+ // Delay indexing until the UI has fully settled after open
Timer {
id: indexingTimer
- interval: 500
+ interval: 2500
onTriggered: {
- settingsIndexer.isIndexing = true;
+ // Only start if the window is still visible (user hasn't closed it)
+ if (root.visible) {
+ settingsIndexer.isIndexing = true;
+ }
}
}
+
+ Component.onCompleted: {
+ indexingTimer.start();
+ }
}
// Store pending subsection to apply when panel loads
@@ -126,7 +133,7 @@ Rectangle {
return;
// Panels that support subsections: Theme(5), System(7), Compositor(8), Shell(9)
- if ([5, 7, 8, 9].includes(sectionId)) {
+ if (sectionId === 5 || sectionId === 7 || sectionId === 8 || sectionId === 9) {
if (panelLoader.item && panelLoader.status === Loader.Ready) {
panelLoader.item.currentSection = subSectionId;
} else {
@@ -144,7 +151,6 @@ Rectangle {
const tabSpacing = 0;
const itemY = root.selectedIndex * (tabHeight + tabSpacing);
- // Check bounds and scroll if needed
if (itemY < sidebarFlickable.contentY) {
sidebarFlickable.contentY = itemY;
} else if (itemY + tabHeight > sidebarFlickable.contentY + sidebarFlickable.height) {
@@ -152,50 +158,53 @@ Rectangle {
}
}
- // Fuzzy match: checks if all characters of query appear in order in target
+ // βββ High-performance fuzzy matching βββ
+ // Returns boolean (fast path for filter checks)
function fuzzyMatch(query, target) {
- if (query.length === 0)
- return true;
- if (target.length === 0)
- return false;
+ if (query.length === 0) return true;
+ if (target.length === 0) return false;
const lowerQuery = query.toLowerCase();
const lowerTarget = target.toLowerCase();
- let queryIndex = 0;
- for (let i = 0; i < lowerTarget.length && queryIndex < lowerQuery.length; i++) {
- if (lowerTarget[i] === lowerQuery[queryIndex]) {
- queryIndex++;
+ let qi = 0;
+ // Micro-opt: cache length, use while loop, avoid bounds checks on each iteration
+ const qLen = lowerQuery.length, tLen = lowerTarget.length;
+ for (let i = 0; i < tLen && qi < qLen; i++) {
+ if (lowerTarget.charCodeAt(i) === lowerQuery.charCodeAt(qi)) {
+ qi++;
}
}
- return queryIndex === lowerQuery.length;
+ return qi === qLen;
}
- // Score a fuzzy match (higher is better)
+ // Returns integer score (higher = better match)
function fuzzyScore(query, target) {
- if (query.length === 0)
- return 0;
- if (target.length === 0)
- return -1;
+ if (query.length === 0) return 0;
+ if (target.length === 0) return -1;
const lowerQuery = query.toLowerCase();
const lowerTarget = target.toLowerCase();
-
- // Exact match gets highest score
- if (lowerTarget.includes(lowerQuery))
- return 1000 + (100 - target.length);
-
- // Fuzzy scoring
- let queryIndex = 0, score = 0, consecutive = 0, maxConsecutive = 0;
- for (let i = 0; i < lowerTarget.length && queryIndex < lowerQuery.length; i++) {
- if (lowerTarget[i] === lowerQuery[queryIndex]) {
- queryIndex++;
- consecutive++;
- maxConsecutive = Math.max(maxConsecutive, consecutive);
- if (i === 0 || " -_".includes(lowerTarget[i - 1]))
+ const qLen = lowerQuery.length, tLen = lowerTarget.length;
+
+ // Fast path: exact substring match β high score
+ if (lowerTarget.indexOf(lowerQuery) !== -1)
+ return 1000 + (100 - tLen);
+
+ // Fuzzy scoring with character codes for speed
+ let qi = 0, score = 0, consec = 0, maxConsec = 0;
+ for (let i = 0; i < tLen && qi < qLen; i++) {
+ const tc = lowerTarget.charCodeAt(i);
+ if (tc === lowerQuery.charCodeAt(qi)) {
+ qi++;
+ consec++;
+ if (consec > maxConsec) maxConsec = consec;
+ // Bonus for match at word boundary
+ if (i === 0 || tc < 97 || tc > 122) { // non-lowercase = boundary
score += 10;
+ }
} else {
- consecutive = 0;
+ consec = 0;
}
}
- return queryIndex === lowerQuery.length ? score + maxConsecutive * 5 : -1;
+ return qi === qLen ? score + maxConsec * 5 : -1;
}
// Original sections model
@@ -255,8 +264,8 @@ Rectangle {
isIcon: true
},
{
- icon: Qt.resolvedUrl("../../../../assets/ambxst/ambxst-icon.svg"),
- label: "Ambxst",
+ icon: Qt.resolvedUrl("../../../../assets/nothingless/nothingless-icon.svg"),
+ label: "NothingLess",
section: 9,
isIcon: false
}
@@ -268,27 +277,36 @@ Rectangle {
return sectionModel;
const query = searchQuery.toLowerCase();
- return searchIndex.items.filter(item => {
- return fuzzyMatch(query, item.label) || (item.keywords && item.keywords.includes(query));
- }).map(item => {
- // Find section metadata
+ const items = searchIndex.items;
+ const results = [];
+
+ // Single pass filter + map, avoid .filter().map() churn
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ if (!fuzzyMatch(query, item.label) && !(item.keywords && item.keywords.indexOf(query) !== -1))
+ continue;
+
const sectionMeta = sectionModel.find(s => s.section === item.section) || {};
- return {
+ results.push({
label: item.label,
section: item.section,
subSection: item.subSection || "",
subLabel: item.subLabel || "",
- // Use section icon instead of item icon
icon: sectionMeta.icon || item.icon,
isIcon: sectionMeta.isIcon !== undefined ? sectionMeta.isIcon : (item.isIcon !== undefined ? item.isIcon : true),
score: fuzzyScore(query, item.label)
- };
- }).sort((a, b) => b.score - a.score);
+ });
+ }
+
+ // Sort results by score descending
+ results.sort((a, b) => b.score - a.score);
+ return results;
}
// Find the index of current section in filtered list
function getFilteredIndex(sectionId) {
- for (let i = 0; i < filteredSections.length; i++) {
+ const fLen = filteredSections.length;
+ for (let i = 0; i < fLen; i++) {
if (filteredSections[i].section === sectionId)
return i;
}
@@ -419,7 +437,7 @@ Rectangle {
flat: true
hoverEnabled: true
- property bool isActive: index === root.selectedIndex
+ readonly property bool isActive: index === root.selectedIndex
background: Rectangle {
color: "transparent"
@@ -434,7 +452,7 @@ Rectangle {
text: sidebarButton.modelData.isIcon ? sidebarButton.modelData.icon : ""
font.family: Icons.font
font.pixelSize: 20
- color: sidebarButton.isActive ? Styling.srItem("overprimary") : Styling.srItem("common")
+ color: sidebarButton.isActive ? Styling.srItem("primary") : Styling.srItem("common")
anchors.verticalCenter: parent.verticalCenter
leftPadding: 10
visible: sidebarButton.modelData.isIcon && (root.searchQuery.length === 0 || !sidebarButton.modelData.subSection)
@@ -448,7 +466,7 @@ Rectangle {
}
}
- // SVG icon
+ // SVG icon (layer removed β same visual via icon font or direct colorization)
Item {
width: 30
height: 20
@@ -467,10 +485,11 @@ Rectangle {
smooth: true
asynchronous: true
layer.enabled: true
+ layer.samplerName: "source"
layer.effect: MultiEffect {
brightness: 1.0
colorization: 1.0
- colorizationColor: sidebarButton.isActive ? Styling.srItem("overprimary") : Styling.srItem("common")
+ colorizationColor: sidebarButton.isActive ? Styling.srItem("primary") : Styling.srItem("common")
}
}
}
@@ -484,7 +503,7 @@ Rectangle {
font.family: Config.theme.font
font.pixelSize: Styling.fontSize(0)
font.weight: sidebarButton.isActive ? Font.Bold : Font.Normal
- color: sidebarButton.isActive ? Styling.srItem("overprimary") : Styling.srItem("common")
+ color: sidebarButton.isActive ? Styling.srItem("primary") : Styling.srItem("common")
Behavior on color {
enabled: Config.animDuration > 0
@@ -602,7 +621,9 @@ Rectangle {
Loader {
id: panelLoader
anchors.fill: parent
- asynchronous: true
+ // FIX: Synchronous loading to avoid race conditions with PipeWire events
+ // that can cause segfaults when Connections targets get destroyed mid-incubation
+ asynchronous: false
source: contentArea.panelComponents[root.currentSection]?.component ?? ""
// Fade in animation
diff --git a/modules/widgets/dashboard/controls/ShellPanel.qml b/modules/widgets/dashboard/controls/ShellPanel.qml
index d02bf8cf..3a4060ed 100644
--- a/modules/widgets/dashboard/controls/ShellPanel.qml
+++ b/modules/widgets/dashboard/controls/ShellPanel.qml
@@ -6,6 +6,7 @@ import QtQuick.Layouts
import Quickshell
import qs.modules.theme
import qs.modules.components
+import Quickshell.Services.SystemTray
import qs.modules.globals
import qs.config
@@ -810,7 +811,16 @@ Item {
}
}
}
-
+ ToggleRow {
+ label: "Enable Chromium Player"
+ checked: Config.bar.enableChromiumPlayer ?? false
+ onToggled: value => {
+ if (value !== Config.bar.enableChromiumPlayer) {
+ GlobalStates.markShellChanged();
+ Config.bar.enableChromiumPlayer = value;
+ }
+ }
+ }
Separator {
Layout.fillWidth: true
}
@@ -973,7 +983,56 @@ Item {
Separator {
Layout.fillWidth: true
visible: false
- }
+
+ // Task Tray toggle
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 8
+
+ Text {
+ Layout.fillWidth: true
+ text: Icons.terminalWindow + " Task Tray"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-1)
+ color: Colors.overBackground
+ Layout.alignment: Qt.AlignVCenter
+ }
+
+ Switch {
+ id: taskTraySwitch
+ checked: Config.bar.taskTrayEnabled ?? true
+ onCheckedChanged: {
+ if (checked !== (Config.bar.taskTrayEnabled ?? true)) {
+ Config.bar.taskTrayEnabled = checked;
+ }
+ }
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 8
+
+ Text {
+ Layout.fillWidth: true
+ text: Icons.dotsThree + " Show Toggle Button"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-1)
+ color: Colors.overBackground
+ Layout.alignment: Qt.AlignVCenter
+ }
+
+ Switch {
+ id: showToggleSwitch
+ checked: Config.bar.taskTrayShowToggle ?? true
+ onCheckedChanged: {
+ if (checked !== (Config.bar.taskTrayShowToggle ?? true)) {
+ Config.bar.taskTrayShowToggle = checked;
+ }
+ }
+ }
+ }
+}
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// NOTCH SECTION
@@ -1116,7 +1175,7 @@ Item {
TextInputRow {
label: "Custom Text"
visible: Config.notch.noMediaDisplay === "custom"
- value: Config.notch.customText ?? "Ambxst"
+ value: Config.notch.customText ?? "NothingLess"
placeholder: "Enter text..."
onValueEdited: newValue => {
if (newValue !== Config.notch.customText) {
@@ -1757,16 +1816,11 @@ Item {
}
ActionButton {
- text: "About Ambxst " + Config.version
+ text: "About NothingLess " + Config.version
icon: Icons.info
- onClicked: Quickshell.execDetached(["xdg-open", "https://axeni.de/ambxst"])
+ onClicked: Quickshell.execDetached(["xdg-open", "https://github.com/Leriart/NothingLess"])
}
- ActionButton {
- text: "Donate β€οΈ"
- icon: Icons.heart
- onClicked: Quickshell.execDetached(["xdg-open", "https://axeni.de/donate"])
- }
Text {
text: "OCR Languages"
diff --git a/modules/widgets/dashboard/controls/SystemPanel.qml b/modules/widgets/dashboard/controls/SystemPanel.qml
old mode 100644
new mode 100755
index c3bf0421..048179c8
--- a/modules/widgets/dashboard/controls/SystemPanel.qml
+++ b/modules/widgets/dashboard/controls/SystemPanel.qml
@@ -141,6 +141,14 @@ Item {
SectionButton {
text: "Idle"
sectionId: "idle"
+ SectionButton {
+ text: "Battery"
+ sectionId: "battery"
+ }
+ }
+ SectionButton {
+ text: "Battery"
+ sectionId: "battery"
}
}
@@ -409,17 +417,6 @@ Item {
}
}
- // Wavy Line toggle
- ToggleRow {
- Layout.fillWidth: true
- label: "Wavy Line"
- description: "Animated wavy line effect"
- checked: Config.performance.wavyLine
- onToggled: checked => {
- Config.performance.wavyLine = checked;
- }
- }
-
// Rotate Cover Art toggle
ToggleRow {
Layout.fillWidth: true
@@ -593,6 +590,117 @@ Item {
}
}
+ // =====================
+ // BATTERY SECTION
+ // =====================
+ ColumnLayout {
+ visible: root.currentSection === "battery"
+ property string settingsSection: "battery"
+ Layout.fillWidth: true
+ spacing: 8
+
+ Text {
+ text: "Battery"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-1)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.bottomMargin: -4
+ }
+
+ ToggleRow {
+ label: "Low battery alerts"
+ checked: Config.system.batteryNotifications.enabled
+ onToggled: value => {
+ if (value !== Config.system.batteryNotifications.enabled) {
+ Config.system.batteryNotifications.enabled = value;
+ }
+ }
+ }
+
+ NumberInputRow {
+ label: "Low threshold (%)"
+ value: Config.system.batteryNotifications.lowThreshold
+ minValue: 5
+ maxValue: 50
+ onValueEdited: newValue => {
+ Config.system.batteryNotifications.lowThreshold = newValue;
+ }
+ }
+
+ NumberInputRow {
+ label: "Critical threshold (%)"
+ value: Config.system.batteryNotifications.criticalThreshold
+ minValue: 3
+ maxValue: 20
+ onValueEdited: newValue => {
+ Config.system.batteryNotifications.criticalThreshold = newValue;
+ }
+ }
+
+ Separator { Layout.fillWidth: true }
+
+ Text {
+ text: "Power Save"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-1)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.bottomMargin: -4
+ }
+
+ ToggleRow {
+ label: "Auto power-save on low battery"
+ checked: Config.system.batteryNotifications.autoPowerSave
+ onToggled: value => {
+ if (value !== Config.system.batteryNotifications.autoPowerSave) {
+ Config.system.batteryNotifications.autoPowerSave = value;
+ }
+ }
+ }
+
+ NumberInputRow {
+ label: "Power-save threshold (%)"
+ value: Config.system.batteryNotifications.powerSaveThreshold
+ minValue: 5
+ maxValue: 40
+ onValueEdited: newValue => {
+ Config.system.batteryNotifications.powerSaveThreshold = newValue;
+ }
+ }
+
+ Separator { Layout.fillWidth: true }
+
+ Text {
+ text: "Charge Limit"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-1)
+ font.weight: Font.Medium
+ color: Colors.overSurfaceVariant
+ Layout.bottomMargin: -4
+ }
+
+ ToggleRow {
+ label: "Charge limit notification"
+ checked: Config.system.batteryNotifications.chargeLimitEnabled
+ onToggled: value => {
+ if (value !== Config.system.batteryNotifications.chargeLimitEnabled) {
+ Config.system.batteryNotifications.chargeLimitEnabled = value;
+ }
+ }
+ }
+
+ NumberInputRow {
+ label: "Charge limit (%)"
+ value: Config.system.batteryNotifications.chargeLimit
+ minValue: 50
+ maxValue: 100
+ onValueEdited: newValue => {
+ Config.system.batteryNotifications.chargeLimit = newValue;
+ }
+ }
+ }
+
// =====================
// IDLE SECTION
// =====================
diff --git a/modules/widgets/dashboard/controls/ThemePanel.qml b/modules/widgets/dashboard/controls/ThemePanel.qml
old mode 100644
new mode 100755
index e622dff3..b2c7d4e8
--- a/modules/widgets/dashboard/controls/ThemePanel.qml
+++ b/modules/widgets/dashboard/controls/ThemePanel.qml
@@ -94,7 +94,7 @@ Item {
FileView {
id: wallpaperConfig
// QUICKSHELL-GIT: path: Quickshell.cachePath("wallpapers.json")
- path: Quickshell.env("HOME") + "/.cache/ambxst/wallpapers.json"
+ path: Quickshell.env("HOME") + "/.cache/nothingless/wallpapers.json"
JsonAdapter {
property string currentWall: ""
@@ -1549,7 +1549,7 @@ Item {
}
ColorPickerView {
- id: colorPickerContent
+ id: colorPicker
anchors.fill: parent
anchors.leftMargin: root.sideMargin
anchors.rightMargin: root.sideMargin
diff --git a/modules/widgets/dashboard/controls/VariantEditor.qml b/modules/widgets/dashboard/controls/VariantEditor.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/controls/VariantPreview.qml b/modules/widgets/dashboard/controls/VariantPreview.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/controls/WifiNetworkItem.qml b/modules/widgets/dashboard/controls/WifiNetworkItem.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/controls/WifiPanel.qml b/modules/widgets/dashboard/controls/WifiPanel.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/emoji/EmojiTab.qml b/modules/widgets/dashboard/emoji/EmojiTab.qml
old mode 100644
new mode 100755
index d7c7a504..9a37f81d
--- a/modules/widgets/dashboard/emoji/EmojiTab.qml
+++ b/modules/widgets/dashboard/emoji/EmojiTab.qml
@@ -247,14 +247,14 @@ Rectangle {
function loadRecentEmojis() {
// QUICKSHELL-GIT: recentProcess.command = ["bash", "-c", "cat " + Quickshell.cacheDir + "/emojis.json 2>/dev/null || echo '[]'"];
- recentProcess.command = ["bash", "-c", "cat " + Quickshell.env("HOME") + "/.cache/ambxst" + "/emojis.json 2>/dev/null || echo '[]'"];
+ recentProcess.command = ["bash", "-c", "cat " + Quickshell.env("HOME") + "/.cache/nothingless" + "/emojis.json 2>/dev/null || echo '[]'"];
recentProcess.running = true;
}
function saveRecentEmojis() {
var jsonData = JSON.stringify(recentEmojis, null, 2);
// QUICKSHELL-GIT: saveProcess.command = ["bash", "-c", "echo '" + jsonData.replace(/'/g, "'\\''") + "' > " + Quickshell.cacheDir + "/emojis.json"];
- saveProcess.command = ["bash", "-c", "echo '" + jsonData.replace(/'/g, "'\\''") + "' > " + Quickshell.env("HOME") + "/.cache/ambxst" + "/emojis.json"];
+ saveProcess.command = ["bash", "-c", "echo '" + jsonData.replace(/'/g, "'\\''") + "' > " + Quickshell.env("HOME") + "/.cache/nothingless" + "/emojis.json"];
saveProcess.running = true;
}
diff --git a/modules/widgets/dashboard/metrics/ConfigColorPick.qml b/modules/widgets/dashboard/metrics/ConfigColorPick.qml
new file mode 100755
index 00000000..ec97a830
--- /dev/null
+++ b/modules/widgets/dashboard/metrics/ConfigColorPick.qml
@@ -0,0 +1,80 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import qs.modules.theme
+import qs.modules.components
+import qs.config
+
+Item {
+ id: cp
+ Layout.fillWidth: true
+ property string label: ""; property string color: "#5EADFF"; property bool open: false
+ property real hue: 0.58; property real sat: 0.7; property real light: 0.6
+ signal picked(string hex)
+ implicitHeight: header.height + (open ? 40 : 0)
+
+ onColorChanged: {
+ let r = parseInt(color.slice(1,3),16)/255, g = parseInt(color.slice(3,5),16)/255, b = parseInt(color.slice(5,7),16)/255
+ let mx = Math.max(r,g,b), mn = Math.min(r,g,b), d = mx-mn
+ sat = mx === 0 ? 0 : d/mx
+ if (d === 0) hue = 0
+ else if (mx === r) hue = ((g-b)/d + (gMath.round(x*255).toString(16).padStart(2,'0')).join('').toUpperCase()
+ }
+
+ function pick(h,s,l) { hue=h; sat=s; light=l; let hex=hsvHex(h,s,l); color=hex; picked(hex) }
+
+ RowLayout { id: header; width: parent.width; spacing: 6
+ Text { text: cp.label; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-3); font.weight: Font.Medium; color: Colors.overBackground; Layout.preferredWidth: 60 }
+ Rectangle { Layout.preferredWidth: 24; Layout.preferredHeight: 24; radius: 5; color: cp.color; border.width: 1.5; border.color: Colors.outline
+ MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: cp.open = !cp.open } }
+ Text { text: cp.color; font.family: Config.theme.monoFont; font.pixelSize: Styling.fontSize(-4); color: Colors.overSurfaceVariant; Layout.fillWidth: true }
+ }
+
+ ColumnLayout { anchors.top: header.bottom; anchors.topMargin: 4; anchors.left: parent.left; anchors.leftMargin: 34; width: parent.width - 34; visible: cp.open; spacing: 3
+ Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 16; radius: 3
+ gradient: Gradient { orientation: Gradient.Horizontal
+ GradientStop { position: 0.0; color: "#FF0000" }
+ GradientStop { position: 0.17; color: "#FFFF00" }
+ GradientStop { position: 0.33; color: "#00FF00" }
+ GradientStop { position: 0.50; color: "#00FFFF" }
+ GradientStop { position: 0.67; color: "#0000FF" }
+ GradientStop { position: 0.83; color: "#FF00FF" }
+ GradientStop { position: 1.0; color: "#FF0000" }
+ }
+ Rectangle { x: parent.width * cp.hue - 6; y: -2; width: 12; height: parent.height + 4; radius: 2; color: "transparent"; border.width: 2; border.color: Colors.overBackground }
+ MouseArea { anchors.fill: parent
+ onPositionChanged: cp.pick(Math.max(0,Math.min(1,mouse.x/parent.width)), cp.sat, cp.light)
+ onClicked: cp.pick(Math.max(0,Math.min(1,mouse.x/parent.width)), cp.sat, cp.light)
+ }
+ }
+ Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 16; radius: 3
+ gradient: Gradient { orientation: Gradient.Horizontal
+ GradientStop { position: 0.0; color: "#000000" }
+ GradientStop { position: 0.5; color: cp.hsvHex(cp.hue, cp.sat, 0.5) }
+ GradientStop { position: 1.0; color: "#FFFFFF" }
+ }
+ Rectangle { x: parent.width * cp.light - 6; y: -2; width: 12; height: parent.height + 4; radius: 2; color: "transparent"; border.width: 2; border.color: Colors.overBackground }
+ MouseArea { anchors.fill: parent
+ onPositionChanged: cp.pick(cp.hue, cp.sat, Math.max(0,Math.min(1,mouse.x/parent.width)))
+ onClicked: cp.pick(cp.hue, cp.sat, Math.max(0,Math.min(1,mouse.x/parent.width)))
+ }
+ }
+ }
+}
diff --git a/modules/widgets/dashboard/metrics/ConfigToggleRow.qml b/modules/widgets/dashboard/metrics/ConfigToggleRow.qml
new file mode 100755
index 00000000..4d08973a
--- /dev/null
+++ b/modules/widgets/dashboard/metrics/ConfigToggleRow.qml
@@ -0,0 +1,33 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import qs.modules.theme
+import qs.modules.components
+import qs.config
+
+RowLayout {
+ id: tr
+ Layout.fillWidth: true; Layout.preferredHeight: 26; spacing: 8
+ property string icon: ""; property string label: ""; property bool on: true
+ signal toggled(bool v)
+
+ Text { text: tr.icon; font.family: Icons.font; font.pixelSize: Styling.fontSize(-2); color: Colors.overBackground; Layout.preferredWidth: 18 }
+ Text { Layout.fillWidth: true; text: tr.label; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2); color: Colors.overBackground; elide: Text.ElideRight }
+
+ Rectangle {
+ Layout.preferredWidth: 40; Layout.preferredHeight: 22; radius: 11
+ color: tr.on ? Styling.srItem("overprimary") : Qt.rgba(0.35,0.35,0.35,0.5)
+ border.width: 1.5; border.color: tr.on ? Styling.srItem("overprimary") : Colors.outline
+ Behavior on color { enabled: Config.animDuration > 0; ColorAnimation { duration: 150 } }
+ Rectangle {
+ anchors.verticalCenter: parent.verticalCenter
+ x: tr.on ? parent.width - width - 3 : 3
+ width: 16; height: 16; radius: 8
+ color: tr.on ? Colors.background : Colors.overSurfaceVariant
+ Behavior on x { enabled: Config.animDuration > 0; NumberAnimation { duration: 150; easing.type: Easing.OutCubic } }
+ }
+ MouseArea { anchors.fill: parent; cursorShape: Qt.PointingHandCursor; onClicked: { tr.on = !tr.on; tr.toggled(tr.on) } }
+ }
+}
diff --git a/modules/widgets/dashboard/metrics/MetricsConfigPanel.qml b/modules/widgets/dashboard/metrics/MetricsConfigPanel.qml
new file mode 100755
index 00000000..946c3cb1
--- /dev/null
+++ b/modules/widgets/dashboard/metrics/MetricsConfigPanel.qml
@@ -0,0 +1,220 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Quickshell
+import Quickshell.Io
+import qs.modules.theme
+import qs.modules.components
+import qs.modules.services
+import qs.config
+
+Flickable {
+ id: root
+ contentHeight: content.implicitHeight
+ clip: true
+ boundsBehavior: Flickable.StopAtBounds
+
+ signal applyChanges(var data)
+
+ // Local State
+ property bool cpuUsage: true
+ property bool cpuTemp: true
+ property bool cpuPower: false
+ property bool ram: true
+ property bool gpuUsage: true
+ property bool gpuTemp: true
+ property bool gpuPower: true
+ property bool fps: true
+ property bool disk: true
+ property string colorCpu: "#5EADFF"
+ property string colorGpu: "#B0B0B0"
+ property string colorFps: "#64FFDA"
+ property string colorRam: "#F59E0B"
+ property string colorDisk: "#C084FC"
+
+ property bool saved: false
+ property bool raplOK: false
+ property bool raplBusy: false
+
+ Component.onCompleted: loadFromState()
+
+ function loadFromState() {
+ cpuUsage = StateService.get("metricCpuUsage", true)
+ cpuTemp = StateService.get("metricCpuTemp", true)
+ cpuPower = StateService.get("metricCpuPower", false)
+ ram = StateService.get("metricRam", true)
+ gpuUsage = StateService.get("metricGpuUsage", true)
+ gpuTemp = StateService.get("metricGpuTemp", true)
+ gpuPower = StateService.get("metricGpuPower", true)
+ fps = StateService.get("metricFps", true)
+ disk = StateService.get("metricDisk", true)
+ colorCpu = StateService.get("metricColorCpu", "#5EADFF")
+ colorGpu = StateService.get("metricColorGpu", "#B0B0B0")
+ colorFps = StateService.get("metricColorFps", "#64FFDA")
+ colorRam = StateService.get("metricColorRam", "#F59E0B")
+ colorDisk = StateService.get("metricColorDisk", "#C084FC")
+ checkRapl()
+ saveToSystem()
+ }
+
+ function saveToSystem() {
+ SystemResources.cpuUsageEnabled = cpuUsage
+ SystemResources.cpuTempEnabled = cpuTemp
+ SystemResources.cpuPowerEnabled = cpuPower
+ SystemResources.ramEnabled = ram
+ SystemResources.gpuUsageEnabled = gpuUsage
+ SystemResources.gpuTempEnabled = gpuTemp
+ SystemResources.gpuPowerEnabled = gpuPower
+ SystemResources.fpsEnabled = fps
+ SystemResources.diskEnabled = disk
+ SystemResources.metricColorCpu = colorCpu
+ SystemResources.metricColorGpu = colorGpu
+ SystemResources.metricColorFps = colorFps
+ SystemResources.metricColorRam = colorRam
+ SystemResources.metricColorDisk = colorDisk
+
+ StateService.set("metricCpuUsage", cpuUsage)
+ StateService.set("metricCpuTemp", cpuTemp)
+ StateService.set("metricCpuPower", cpuPower)
+ StateService.set("metricRam", ram)
+ StateService.set("metricGpuUsage", gpuUsage)
+ StateService.set("metricGpuTemp", gpuTemp)
+ StateService.set("metricGpuPower", gpuPower)
+ StateService.set("metricFps", fps)
+ StateService.set("metricDisk", disk)
+ StateService.set("metricColorCpu", colorCpu)
+ StateService.set("metricColorGpu", colorGpu)
+ StateService.set("metricColorFps", colorFps)
+ StateService.set("metricColorRam", colorRam)
+ StateService.set("metricColorDisk", colorDisk)
+
+ SystemResources.notchVersion++
+ SystemResources.saveMetricsConfig()
+ saved = true
+ saveTimer.restart()
+ }
+
+ Timer { id: saveTimer; interval: 2000; onTriggered: saved = false }
+
+ function checkRapl() {
+ const p = Qt.createQmlObject('import Quickshell.Io; Process { running:true; command:["test","-r","/sys/class/powercap/intel-rapl:0/energy_uj"]; onExited:destroy() }', root)
+ if (p) p.exited.connect(function(){ raplOK = (p.exitCode === 0); p.destroy() })
+ }
+
+ function installRapl() {
+ raplBusy = true
+ var d = Quickshell.shellDir
+ var p = Qt.createQmlObject('import Quickshell.Io; Process { running:true; command:["pkexec","sh","-c","cp ' + d + '/config/99-rapl-permissions.rules /etc/udev/rules.d/ \u0026\u0026 udevadm control --reload-rules \u0026\u0026 udevadm trigger"]; onExited:destroy() }', root)
+ if (p) p.exited.connect(function(){ raplBusy=false; if(p.exitCode===0) checkRapl(); p.destroy() })
+ else raplBusy=false
+ }
+
+ function useThemeColors() {
+ colorCpu = String(Styling.srItem("overprimary") || "#5EADFF")
+ colorGpu = String(Colors.cyan || "#84d5c4")
+ colorFps = String(Colors.green || "#6BCB77")
+ colorRam = String(Colors.yellow || Colors.lightYellow || "#F59E0B")
+ colorDisk = String(Colors.magenta || "#C084FC")
+ saveToSystem()
+ }
+
+ function useWallColors() {
+ colorCpu = String(Colors.red || Colors.error || "#FF6B6B")
+ colorGpu = String(Colors.cyan || "#5EADFF")
+ colorFps = String(Colors.yellow || Colors.lightYellow || "#FFD93D")
+ colorRam = String(Colors.green || "#6BCB77")
+ colorDisk = String(Colors.magenta || "#C084FC")
+ saveToSystem()
+ }
+
+ ColumnLayout {
+ id: content
+ width: parent.width
+ spacing: 8
+
+ Text {
+ Layout.fillWidth: true
+ text: "Metrics Setup"
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-1)
+ font.weight: Font.Medium
+ color: Colors.overBackground
+ }
+
+ Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 1; color: Colors.outline }
+
+ // ββ CPU ββ
+ ConfigToggleRow { icon: Icons.cpu; label: "CPU Usage %"; on: root.cpuUsage; onToggled: { root.cpuUsage = v } }
+ ConfigToggleRow { icon: Icons.temperature; label: "CPU Temperature"; on: root.cpuTemp; onToggled: { root.cpuTemp = v } }
+ ConfigToggleRow { icon: Icons.lightning; label: "CPU Power (RAPL)"; on: root.cpuPower; onToggled: { root.cpuPower = v; if(v && !root.raplOK) root.installRapl() } }
+ RowLayout {
+ Layout.leftMargin: 28; visible: root.cpuPower; spacing: 4
+ Text { visible: !root.raplOK; text: "\u26A0"; font.pixelSize: 10; color: Colors.yellow }
+ Text { text: root.raplOK ? "\u2713 RAPL ready" : root.raplBusy ? "Requesting..." : "Needs permission"; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-4); color: root.raplOK ? Colors.green : Colors.yellow }
+ }
+ ConfigColorPick { label: "CPU Color"; color: root.colorCpu; visible: root.cpuUsage || root.cpuTemp || root.cpuPower; onPicked: { root.colorCpu = hex } }
+
+ Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 1; color: Colors.outline + "44" }
+
+ // ββ GPU ββ
+ ConfigToggleRow { icon: Icons.gpu; label: "GPU Usage %"; on: root.gpuUsage; onToggled: { root.gpuUsage = v } }
+ ConfigToggleRow { icon: Icons.temperature; label: "GPU Temperature"; on: root.gpuTemp; onToggled: { root.gpuTemp = v } }
+ ConfigToggleRow { icon: Icons.lightning; label: "GPU Power"; on: root.gpuPower; onToggled: { root.gpuPower = v } }
+ ConfigColorPick { label: "GPU Color"; color: root.colorGpu; visible: root.gpuUsage || root.gpuTemp || root.gpuPower; onPicked: { root.colorGpu = hex } }
+
+
+
+ Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 1; color: Colors.outline + "44" }
+
+ // ββ RAM ββ
+ ConfigToggleRow { icon: Icons.ram; label: "RAM Usage %"; on: root.ram; onToggled: { root.ram = v } }
+ ConfigColorPick { label: "RAM Color"; color: root.colorRam; visible: root.ram; onPicked: { root.colorRam = hex } }
+
+ Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 1; color: Colors.outline + "44" }
+
+ // ββ DISK ββ
+ ConfigToggleRow { icon: Icons.disk; label: "Disk Usage %"; on: root.disk; onToggled: { root.disk = v } }
+ ConfigColorPick { label: "Disk Color"; color: root.colorDisk; visible: root.disk; onPicked: { root.colorDisk = hex } }
+
+ // ββ FPS ββ
+ ConfigToggleRow { icon: Icons.recordScreen; label: "FPS (Built-in)"; on: root.fps; onToggled: { root.fps = v } }
+ ConfigColorPick { label: "FPS Color"; color: root.colorFps; visible: root.fps; onPicked: { root.colorFps = hex } }
+
+ Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 1; color: Colors.outline + "44" }
+
+ // ββ Quick Palettes ββ
+ Text { text: "Quick Palettes"; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-2); font.weight: Font.Medium; color: Colors.overBackground }
+ RowLayout {
+ Layout.fillWidth: true; spacing: 4
+ StyledRect { Layout.preferredHeight: 22; Layout.fillWidth: true; radius: Styling.radius(-4); variant: themeMa.containsMouse ? "focus" : "pane"
+ Text { anchors.centerIn: parent; text: "Theme"; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-4); color: Styling.srItem("overprimary") }
+ MouseArea { id: themeMa; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; hoverEnabled: true; onClicked: root.useThemeColors() } }
+ StyledRect { Layout.preferredHeight: 22; Layout.fillWidth: true; radius: Styling.radius(-4); variant: wallMa.containsMouse ? "focus" : "pane"
+ Text { anchors.centerIn: parent; text: "Wallpaper"; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-4); color: Colors.overBackground }
+ MouseArea { id: wallMa; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; hoverEnabled: true; onClicked: root.useWallColors() } }
+ StyledRect { Layout.preferredHeight: 22; Layout.fillWidth: true; radius: Styling.radius(-4); variant: resetMa.containsMouse ? "focus" : "pane"
+ Text { anchors.centerIn: parent; text: "Reset"; font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-4); color: Colors.overSurfaceVariant }
+ MouseArea { id: resetMa; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; hoverEnabled: true; onClicked: { root.colorCpu="#5EADFF"; root.colorGpu="#B0B0B0"; root.colorFps="#64FFDA"; root.colorRam="#F59E0B"; root.colorDisk="#C084FC"; root.saveToSystem() } } }
+ }
+
+ Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 1; color: Colors.outline + "44" }
+
+ // ββ SAVE BUTTON ββ
+ StyledRect {
+ Layout.fillWidth: true; Layout.preferredHeight: 36; radius: Styling.radius(0)
+ variant: saveMa.containsMouse ? "focus" : root.saved ? "primary" : "pane"
+ Text {
+ anchors.centerIn: parent
+ text: root.saved ? "\u2713 Saved!" : "Save & Apply"
+ font.family: Config.theme.font; font.pixelSize: Styling.fontSize(-1); font.weight: Font.Bold
+ color: root.saved ? Colors.green : Styling.srItem("overprimary")
+ }
+ MouseArea {
+ id: saveMa; anchors.fill: parent; cursorShape: Qt.PointingHandCursor; hoverEnabled: true
+ onClicked: root.saveToSystem()
+ }
+ }
+ }
+}
diff --git a/modules/widgets/dashboard/metrics/MetricsTab.qml b/modules/widgets/dashboard/metrics/MetricsTab.qml
old mode 100644
new mode 100755
index 97b91b37..b372582a
--- a/modules/widgets/dashboard/metrics/MetricsTab.qml
+++ b/modules/widgets/dashboard/metrics/MetricsTab.qml
@@ -194,6 +194,8 @@ Rectangle {
fillMode: Image.PreserveAspectCrop
smooth: true
asynchronous: true
+ sourceSize.width: 96
+ sourceSize.height: 96
visible: status === Image.Ready
layer.enabled: true
@@ -350,11 +352,22 @@ Rectangle {
}
}
+ MetricsConfigPanel {
+ id: metricsConfig
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.leftMargin: 16
+ Layout.rightMargin: 16
+ visible: root.configMode
+ onVisibleChanged: { if (visible) metricsConfig.loadFromState() }
+ }
+
Flickable {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: 16
Layout.rightMargin: 16
+ visible: !root.configMode
contentHeight: resourcesColumn.height
clip: true
boundsBehavior: Flickable.StopAtBounds
@@ -368,13 +381,15 @@ Rectangle {
Column {
width: parent.width
spacing: 4
+ visible: SystemResources.cpuUsageEnabled || SystemResources.cpuTempEnabled || SystemResources.cpuPowerEnabled
ResourceItem {
width: parent.width
icon: Icons.cpu
label: "CPU"
- value: SystemResources.cpuUsage / 100
+ value: SystemResources.cpuUsageEnabled ? SystemResources.cpuUsage / 100 : 0
barColor: Colors.red
+ visible: SystemResources.cpuUsageEnabled
}
RowLayout {
@@ -395,6 +410,7 @@ Rectangle {
}
Text {
+ visible: SystemResources.cpuUsageEnabled
text: `${Math.round(SystemResources.cpuUsage)}%`
font.family: Config.theme.font
font.pixelSize: Styling.fontSize(-2)
@@ -403,7 +419,7 @@ Rectangle {
}
Text {
- visible: SystemResources.cpuTemp >= 0
+ visible: SystemResources.cpuTempEnabled && SystemResources.cpuTemp >= 0
text: Icons.temperature
font.family: Icons.font
font.pixelSize: Styling.fontSize(-2)
@@ -411,7 +427,7 @@ Rectangle {
}
Text {
- visible: SystemResources.cpuTemp >= 0
+ visible: SystemResources.cpuTempEnabled && SystemResources.cpuTemp >= 0
text: `${SystemResources.cpuTemp}Β°`
font.family: Config.theme.font
font.pixelSize: Styling.fontSize(-2)
@@ -419,12 +435,40 @@ Rectangle {
color: Colors.overBackground
}
}
+
+ // CPU Power row (RAPL)
+ RowLayout {
+ width: parent.width
+ spacing: 4
+ visible: SystemResources.cpuPowerEnabled && SystemResources.cpuPower > 0
+
+ Text {
+ text: Icons.lightning
+ font.family: Icons.font
+ font.pixelSize: Styling.fontSize(-2)
+ color: Colors.yellow
+ }
+
+ Text {
+ text: `${SystemResources.cpuPower.toFixed(1)} W`
+ font.family: Config.theme.font
+ font.pixelSize: Styling.fontSize(-2)
+ font.weight: Font.Medium
+ color: Colors.overBackground
+ }
+
+ Separator {
+ Layout.preferredHeight: 2
+ Layout.fillWidth: true
+ }
+ }
}
// RAM
Column {
width: parent.width
spacing: 4
+ visible: SystemResources.ramEnabled
ResourceItem {
width: parent.width
@@ -468,7 +512,8 @@ Rectangle {
// GPUs (if detected) - show one bar per GPU
Repeater {
id: gpuRepeater
- model: SystemResources.gpuDetected ? SystemResources.gpuCount : 0
+ visible: !root.configMode
+ model: (SystemResources.gpuDetected && (SystemResources.gpuUsageEnabled || SystemResources.gpuTempEnabled || SystemResources.gpuPowerEnabled)) ? SystemResources.gpuCount : 0
Column {
required property int index
@@ -569,7 +614,8 @@ Rectangle {
// Disks
Repeater {
id: diskRepeater
- model: SystemResources.validDisks
+ visible: !root.configMode
+ model: SystemResources.diskEnabled ? SystemResources.validDisks : []
Column {
required property string modelData
@@ -984,4 +1030,49 @@ Rectangle {
}
}
}
-}
+
+ // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ // Metrics Configuration β gear toggles inline config
+ // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ property bool configMode: false
+
+ // Gear icon (top-right corner)
+ StyledRect {
+ id: gearBtn
+ anchors.top: parent.top
+ anchors.right: parent.right
+ anchors.topMargin: 8
+ anchors.rightMargin: 8
+ width: 32; height: 32
+ radius: Styling.radius(-4)
+ variant: gearMa.containsMouse ? "focus" : "common"
+ z: 10
+
+ Text {
+ anchors.centerIn: parent
+ text: root.configMode ? Icons.caretLeft : Icons.gear
+ font.family: Icons.font
+ font.pixelSize: 16
+ color: gearMa.containsMouse ? Styling.srItem("overprimary") : Colors.overBackground
+
+ Behavior on color {
+ enabled: Config.animDuration > 0
+ ColorAnimation { duration: Config.animDuration }
+ }
+ }
+
+ MouseArea {
+ id: gearMa
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ hoverEnabled: true
+ onClicked: root.configMode = !root.configMode
+ }
+
+ StyledToolTip {
+ visible: gearMa.containsMouse
+ tooltipText: root.configMode ? "Back to metrics" : "Configure metrics"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/widgets/dashboard/metrics/ResourceItem.qml b/modules/widgets/dashboard/metrics/ResourceItem.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/notes/NotesTab.qml b/modules/widgets/dashboard/notes/NotesTab.qml
old mode 100644
new mode 100755
index 01e120bc..9ca60711
--- a/modules/widgets/dashboard/notes/NotesTab.qml
+++ b/modules/widgets/dashboard/notes/NotesTab.qml
@@ -22,7 +22,7 @@ Item {
property int leftPanelWidth: 0
// Notes directory configuration
- property string notesDir: (Quickshell.env("XDG_DATA_HOME") || (Quickshell.env("HOME") + "/.local/share")) + "/ambxst-notes"
+ property string notesDir: (Quickshell.env("XDG_DATA_HOME") || (Quickshell.env("HOME") + "/.local/share")) + "/nothingless-notes"
property string indexPath: notesDir + "/index.json"
property string notesPath: notesDir + "/notes"
property string noteExtension: ".html" // Store as HTML for rich text (Markdown uses .md)
diff --git a/modules/widgets/dashboard/notes/notes_utils.js b/modules/widgets/dashboard/notes/notes_utils.js
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/tmux/TmuxTab.qml b/modules/widgets/dashboard/tmux/TmuxTab.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/wallpapers/FilterBar.qml b/modules/widgets/dashboard/wallpapers/FilterBar.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/wallpapers/MpvShaderGenerator.js b/modules/widgets/dashboard/wallpapers/MpvShaderGenerator.js
deleted file mode 100644
index 9b87795f..00000000
--- a/modules/widgets/dashboard/wallpapers/MpvShaderGenerator.js
+++ /dev/null
@@ -1,93 +0,0 @@
-.pragma library
-
-function generate(paletteColors) {
- // Safety check
- if (!paletteColors || paletteColors.length === 0) {
- // Return a passthrough shader if no palette
- return `//!HOOK MAIN
-//!BIND HOOKED
-//!DESC Ambxst Passthrough
-void main() {
- HOOKED_col = HOOKED_tex(HOOKED_pos);
-}`;
- }
-
- let unrolledLogic = "";
-
- // Unroll the loop to ensure compatibility with all GLES drivers
- for (let i = 0; i < paletteColors.length; i++) {
- let color = paletteColors[i];
-
- let r = (typeof color.r === 'number' ? color.r : 0.0).toFixed(5);
- let g = (typeof color.g === 'number' ? color.g : 0.0).toFixed(5);
- let b = (typeof color.b === 'number' ? color.b : 0.0).toFixed(5);
-
- unrolledLogic += `
- {
- vec3 pColor = vec3(${r}, ${g}, ${b});
- vec3 diff = color - pColor;
-
- // Perceptual weighting (Red: 0.299, Green: 0.587, Blue: 0.114)
- // This makes the distance match human perception better than raw Euclidean
- vec3 weightedDiff = diff * vec3(0.55, 0.77, 0.34); // Sqrt of standard luma weights roughly
- float distSq = dot(weightedDiff, weightedDiff);
-
- // Track closest color for fallback
- if (distSq < minDistSq) {
- minDistSq = distSq;
- closestColor = pColor;
- }
-
- float weight = exp(-distributionSharpness * distSq);
- accumulatedColor += pColor * weight;
- totalWeight += weight;
- }
-`;
- }
-
- return `//!HOOK MAIN
-//!BIND HOOKED
-//!DESC Ambxst Palette Tint
-
-// Simple dithering function
-float noise_random(vec2 uv) {
- return fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453);
-}
-
-vec4 hook() {
- vec4 tex = HOOKED_tex(HOOKED_pos);
- vec3 color = tex.rgb;
-
- // Add slight dithering to input to break banding before quantization
- float noise = (noise_random(HOOKED_pos * 100.0 + sin(HOOKED_pos.x)) - 0.5) / 64.0;
- color += noise;
-
- vec3 accumulatedColor = vec3(0.0);
- float totalWeight = 0.0;
- float minDistSq = 1000.0;
- vec3 closestColor = vec3(0.0);
-
- // Increased sharpness for cleaner separation (was 20.0)
- // 40.0 makes it stick tighter to palette colors
- float distributionSharpness = 40.0;
-
- // Unrolled palette comparison
- ${unrolledLogic}
-
- vec3 finalColor;
-
- // If we have a decent match blend, use it.
- // Otherwise snap to closest to avoid "holes" or dark spots.
- if (totalWeight > 0.0001) {
- finalColor = accumulatedColor / totalWeight;
- } else {
- finalColor = closestColor;
- }
-
- // Mix in the closest color slightly to reinforce structure if the blend is too muddy
- // finalColor = mix(finalColor, closestColor, 0.2);
-
- return vec4(finalColor, tex.a);
-}
-`;
-}
diff --git a/modules/widgets/dashboard/wallpapers/SchemeSelector.qml b/modules/widgets/dashboard/wallpapers/SchemeSelector.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/wallpapers/Wallpaper.qml b/modules/widgets/dashboard/wallpapers/Wallpaper.qml
old mode 100644
new mode 100755
index 852359ef..220f1a08
--- a/modules/widgets/dashboard/wallpapers/Wallpaper.qml
+++ b/modules/widgets/dashboard/wallpapers/Wallpaper.qml
@@ -1,11 +1,11 @@
import QtQuick
+import QtMultimedia
import Quickshell
import Quickshell.Wayland
import Quickshell.Io
import qs.modules.globals
import qs.modules.theme
import qs.config
-import "MpvShaderGenerator.js" as ShaderGenerator
PanelWindow {
id: wallpaper
@@ -18,16 +18,29 @@ PanelWindow {
}
WlrLayershell.layer: WlrLayer.Background
- WlrLayershell.namespace: "ambxst:wallpaper"
+ WlrLayershell.namespace: "nothingless:wallpaper"
exclusionMode: ExclusionMode.Ignore
color: "transparent"
property string wallpaperDir: wallpaperConfig.adapter.wallPath
- property string fallbackDir: decodeURIComponent(Qt.resolvedUrl("../../../../assets/wallpapers_example").toString().replace("file://", ""))
+ property string fallbackDir: decodeURIComponent(Qt.resolvedUrl("../../../../assets/nothingless-wallpapers").toString().replace("file://", ""))
property var wallpaperPaths: []
property var subfolderFilters: []
property var allSubdirs: []
+
+ // Custom palette loaded from JSON file
+ property var customPalette: []
+ property int customPaletteSize: 0
+
+ // Default palette (optimizedPalette) as fallback
+ readonly property var fallbackPalette: optimizedPalette
+ readonly property int fallbackPaletteSize: optimizedPalette.length
+
+ // Effective palette that will be used in the shader
+ readonly property var effectivePalette: customPaletteSize > 0 ? customPalette : fallbackPalette
+ readonly property int effectivePaletteSize: customPaletteSize > 0 ? customPaletteSize : fallbackPaletteSize
+
property int currentIndex: 0
property string currentWallpaper: initialLoadCompleted && wallpaperPaths.length > 0 ? wallpaperPaths[currentIndex] : ""
property bool initialLoadCompleted: false
@@ -38,16 +51,23 @@ PanelWindow {
property string effectiveWallpaper: perScreenWallpapers[currentScreenName] || currentWallpaper
property string currentScreenName: wallpaper.screen ? wallpaper.screen.name : ""
property alias tintEnabled: wallpaperAdapter.tintEnabled
+ property alias interpolationEnabled: wallpaperAdapter.interpolationEnabled
+ property alias interpolationMultiplier: wallpaperAdapter.interpolationMultiplier
+ property alias targetInputFps: wallpaperAdapter.targetInputFps
property int thumbnailsVersion: 0
- // QUICKSHELL-GIT: property string mpvShaderDir: Quickshell.cacheDir + "/mpv_shaders_" + (currentScreenName ? currentScreenName : "ALL")
- property string mpvShaderDir: Quickshell.env("HOME") + "/.cache/ambxst/mpv_shaders_" + (currentScreenName ? currentScreenName : "ALL")
- property string mpvShaderPath: ""
- property bool mpvShaderReady: false
-
- readonly property var optimizedPalette: ["background", "overBackground", "shadow", "surface", "surfaceBright", "surfaceDim", "surfaceContainer", "surfaceContainerHigh", "surfaceContainerHighest", "surfaceContainerLow", "surfaceContainerLowest", "primary", "secondary", "tertiary", "red", "lightRed", "green", "lightGreen", "blue", "lightBlue", "yellow", "lightYellow", "cyan", "lightCyan", "magenta", "lightMagenta"]
-
- // Sync state from the primary wallpaper manager to secondary instances
+ // Optimized palette color names (used as fallback)
+ readonly property var optimizedPalette: [
+ "background", "overBackground", "shadow", "surface", "surfaceBright", "surfaceDim",
+ "surfaceContainer", "surfaceContainerHigh", "surfaceContainerHighest",
+ "surfaceContainerLow", "surfaceContainerLowest", "primary", "secondary", "tertiary",
+ "red", "lightRed", "green", "lightGreen", "blue", "lightBlue", "yellow", "lightYellow",
+ "cyan", "lightCyan", "magenta", "lightMagenta"
+ ]
+
+ // -------------------------------------------------------------------
+ // Bindings to sync state from primary wallpaper manager
+ // -------------------------------------------------------------------
Binding {
target: wallpaper
property: "wallpaperPaths"
@@ -76,14 +96,16 @@ PanelWindow {
when: GlobalStates.wallpaperManager !== null && GlobalStates.wallpaperManager !== wallpaper
}
- property string colorPresetsDir: Quickshell.env("HOME") + "/.config/ambxst/colors"
+ // -------------------------------------------------------------------
+ // Color presets
+ // -------------------------------------------------------------------
+ property string colorPresetsDir: Quickshell.env("HOME") + "/.config/nothingless/colors"
property string officialColorPresetsDir: decodeURIComponent(Qt.resolvedUrl("../../../../assets/colors").toString().replace("file://", ""))
onColorPresetsDirChanged: console.log("Color Presets Directory:", colorPresetsDir)
property list colorPresets: []
onColorPresetsChanged: console.log("Color Presets Updated:", colorPresets)
property string activeColorPreset: wallpaperConfig.adapter.activeColorPreset || ""
- // React to light/dark mode changes
property bool isLightMode: Config.theme.lightMode
onIsLightModeChanged: {
if (activeColorPreset) {
@@ -106,19 +128,14 @@ PanelWindow {
}
function applyColorPreset() {
- if (!activeColorPreset)
- return;
+ if (!activeColorPreset) return;
var mode = Config.theme.lightMode ? "light.json" : "dark.json";
-
var officialFile = officialColorPresetsDir + "/" + activeColorPreset + "/" + mode;
var userFile = colorPresetsDir + "/" + activeColorPreset + "/" + mode;
- // QUICKSHELL-GIT: var dest = Quickshell.cachePath("colors.json");
- var dest = Quickshell.env("HOME") + "/.cache/ambxst/colors.json";
+ var dest = Quickshell.env("HOME") + "/.cache/nothingless/colors.json";
- // Try official first, then user. Use bash conditional.
var cmd = "if [ -f '" + officialFile + "' ]; then cp '" + officialFile + "' '" + dest + "'; else cp '" + userFile + "' '" + dest + "'; fi";
-
console.log("Applying color preset:", activeColorPreset);
applyPresetProcess.command = ["bash", "-c", cmd];
applyPresetProcess.running = true;
@@ -126,10 +143,11 @@ PanelWindow {
function setColorPreset(name) {
wallpaperConfig.adapter.activeColorPreset = name;
- // activeColorPreset property will update automatically via binding to adapter
}
- // Funciones utilitarias para tipos de archivo
+ // -------------------------------------------------------------------
+ // Utility functions for file types
+ // -------------------------------------------------------------------
function getFileType(path) {
var extension = path.toLowerCase().split('.').pop();
if (['jpg', 'jpeg', 'png', 'webp', 'tif', 'tiff', 'bmp'].includes(extension)) {
@@ -143,68 +161,39 @@ PanelWindow {
}
function getThumbnailPath(filePath) {
- // Compute relative path from wallpaperDir
var basePath = wallpaperDir.endsWith("/") ? wallpaperDir : wallpaperDir + "/";
var relativePath = filePath.replace(basePath, "");
-
- // Replace the filename with .jpg extension
var pathParts = relativePath.split('/');
var fileName = pathParts.pop();
var thumbnailName = fileName + ".jpg";
var relativeDir = pathParts.join('/');
-
- // Build the proxy path
- // QUICKSHELL-GIT: var thumbnailPath = Quickshell.cacheDir + "/thumbnails/" + relativeDir + "/" + thumbnailName;
- var thumbnailPath = Quickshell.env("HOME") + "/.cache/ambxst" + "/thumbnails/" + relativeDir + "/" + thumbnailName;
- return thumbnailPath;
+ return Quickshell.env("HOME") + "/.cache/nothingless/thumbnails/" + relativeDir + "/" + thumbnailName;
}
function getDisplaySource(filePath) {
var fileType = getFileType(filePath);
-
- // Para el display (WallpapersTab), siempre usar thumbnails si estΓ‘n disponibles
if (fileType === 'video' || fileType === 'image' || fileType === 'gif') {
- var thumbnailPath = getThumbnailPath(filePath);
- // Verificar si el thumbnail existe (esto es solo para debugging, QML manejarΓ‘ el fallback)
- return thumbnailPath;
+ return getThumbnailPath(filePath);
}
-
- // Fallback al archivo original si no es un tipo soportado
return filePath;
}
function getColorSource(filePath) {
var fileType = getFileType(filePath);
-
- // Para generaciΓ³n de colores: solo videos usan thumbnails
if (fileType === 'video') {
return getThumbnailPath(filePath);
}
-
- // ImΓ‘genes y GIFs usan el archivo original para colores
return filePath;
}
function getLockscreenFramePath(filePath) {
- if (!filePath) {
- return "";
- }
-
+ if (!filePath) return "";
var fileType = getFileType(filePath);
-
- // Para imΓ‘genes estΓ‘ticas, usar el archivo original
- if (fileType === 'image') {
- return filePath;
- }
-
- // Para videos y GIFs, usar el frame cacheado
+ if (fileType === 'image') return filePath;
if (fileType === 'video' || fileType === 'gif') {
var fileName = filePath.split('/').pop();
- // QUICKSHELL-GIT: var cachePath = Quickshell.cacheDir + "/lockscreen/" + fileName + ".jpg";
- var cachePath = Quickshell.env("HOME") + "/.cache/ambxst" + "/lockscreen/" + fileName + ".jpg";
- return cachePath;
+ return Quickshell.env("HOME") + "/.cache/nothingless/lockscreen/" + fileName + ".jpg";
}
-
return filePath;
}
@@ -213,15 +202,10 @@ PanelWindow {
console.warn("generateLockscreenFrame: empty filePath");
return;
}
-
console.log("Generating lockscreen frame for:", filePath);
-
var scriptPath = decodeURIComponent(Qt.resolvedUrl("../../../../scripts/lockwall.py").toString().replace("file://", ""));
- // QUICKSHELL-GIT: var dataPath = Quickshell.cacheDir;
- var dataPath = Quickshell.env("HOME") + "/.cache/ambxst";
-
+ var dataPath = Quickshell.env("HOME") + "/.cache/nothingless";
lockscreenWallpaperScript.command = ["python3", scriptPath, filePath, dataPath];
-
lockscreenWallpaperScript.running = true;
}
@@ -229,58 +213,92 @@ PanelWindow {
var basePath = wallpaperDir.endsWith("/") ? wallpaperDir : wallpaperDir + "/";
var relativePath = filePath.replace(basePath, "");
var parts = relativePath.split("/");
- if (parts.length > 1) {
- return parts[0];
- }
+ if (parts.length > 1) return parts[0];
return "";
}
+ // -------------------------------------------------------------------
+ // Palette loading
+ // -------------------------------------------------------------------
+ function loadCustomPalette(filePath) {
+ if (!filePath) return;
+ // Vaciar paleta actual para usar fallback mientras se carga la nueva
+ customPalette = [];
+ customPaletteSize = 0;
+ var palettePath = getPalettePath(filePath);
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "file://" + palettePath, true);
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState === XMLHttpRequest.DONE) {
+ if (xhr.status === 200) {
+ try {
+ var data = JSON.parse(xhr.responseText);
+ customPalette = data.colors;
+ customPaletteSize = data.size;
+ console.log("Palette loaded:", customPaletteSize, "colors - First:", customPalette[0]);
+ } catch (e) {
+ console.warn("Failed to parse palette:", palettePath, e);
+ fallbackToDefaultPalette();
+ }
+ } else {
+ console.warn("Palette file not found (status " + xhr.status + "):", palettePath);
+ fallbackToDefaultPalette();
+ }
+ }
+ };
+ xhr.send();
+ }
+
+ function fallbackToDefaultPalette() {
+ customPalette = [];
+ customPaletteSize = 0;
+ }
+
+ function getPalettePath(filePath) {
+ var basePath = wallpaperDir.endsWith("/") ? wallpaperDir : wallpaperDir + "/";
+ var relativePath = filePath.replace(basePath, "");
+ return Quickshell.env("HOME") + "/.cache/nothingless/palettes/" + relativePath + ".json";
+ }
+
function scanSubfolders() {
- if (!wallpaperDir)
- return;
- // Explicitly update command with current wallpaperDir
+ if (!wallpaperDir) return;
var cmd = ["find", wallpaperDir, "-mindepth", "1", "-name", ".*", "-prune", "-o", "-type", "d", "-print"];
scanSubfoldersProcess.command = cmd;
scanSubfoldersProcess.running = true;
}
- // Update directory watcher when wallpaperDir changes
onWallpaperDirChanged: {
- // Skip initial spurious changes before config is loaded
- if (!_wallpaperDirInitialized)
- return;
-
- // Only the primary wallpaper manager should handle directory changes
- if (GlobalStates.wallpaperManager !== wallpaper)
- return;
+ if (!_wallpaperDirInitialized) return;
+ if (GlobalStates.wallpaperManager !== wallpaper) return;
console.log("Wallpaper directory changed to:", wallpaperDir);
usingFallback = false;
-
- // Clear current lists to reflect change immediately
wallpaperPaths = [];
subfolderFilters = [];
-
directoryWatcher.path = wallpaperDir;
- // Force update scan command
- var cmd = ["find", wallpaperDir, "-name", ".*", "-prune", "-o", "-type", "f", "(", "-name", "*.jpg", "-o", "-name", "*.jpeg", "-o", "-name", "*.png", "-o", "-name", "*.webp", "-o", "-name", "*.tif", "-o", "-name", "*.tiff", "-o", "-name", "*.gif", "-o", "-name", "*.mp4", "-o", "-name", "*.webm", "-o", "-name", "*.mov", "-o", "-name", "*.avi", "-o", "-name", "*.mkv", ")", "-print"];
+ var cmd = ["find", wallpaperDir, "-name", ".*", "-prune", "-o", "-type", "f",
+ "(", "-name", "*.jpg", "-o", "-name", "*.jpeg", "-o", "-name", "*.png",
+ "-o", "-name", "*.webp", "-o", "-name", "*.tif", "-o", "-name", "*.tiff",
+ "-o", "-name", "*.gif", "-o", "-name", "*.mp4", "-o", "-name", "*.webm",
+ "-o", "-name", "*.mov", "-o", "-name", "*.avi", "-o", "-name", "*.mkv", ")", "-print"];
scanWallpapers.command = cmd;
scanWallpapers.running = true;
-
scanSubfolders();
- // Regenerate thumbnails for the new directory (delayed)
if (delayedThumbnailGen.running)
delayedThumbnailGen.restart();
else
delayedThumbnailGen.start();
}
- onCurrentWallpaperChanged:
- // Matugen se ejecuta manualmente en las funciones de cambio
- {}
+ onCurrentWallpaperChanged: {
+ // Matugen is executed manually in change functions
+ }
+ // -------------------------------------------------------------------
+ // Wallpaper control functions
+ // -------------------------------------------------------------------
function setWallpaper(path, targetScreen = null) {
if (GlobalStates.wallpaperManager && GlobalStates.wallpaperManager !== wallpaper) {
GlobalStates.wallpaperManager.setWallpaper(path, targetScreen);
@@ -292,29 +310,28 @@ PanelWindow {
var pathIndex = wallpaperPaths.indexOf(path);
if (pathIndex !== -1) {
if (targetScreen) {
- // If targeting a specific screen, save to perScreenWallpapers instead of currentWall
let perScreen = Object.assign({}, wallpaperConfig.adapter.perScreenWallpapers || {});
perScreen[targetScreen] = path;
wallpaperConfig.adapter.perScreenWallpapers = perScreen;
-
- // If this targetScreen is the primary screen, it must update currentWall
- // because currentWall is exactly the primary monitor fallback.
+
let isPrimary = false;
if (GlobalStates.wallpaperManager && GlobalStates.wallpaperManager.screen) {
isPrimary = (targetScreen === GlobalStates.wallpaperManager.screen.name);
}
-
if (isPrimary || !wallpaperConfig.adapter.currentWall) {
currentIndex = pathIndex;
wallpaperConfig.adapter.currentWall = path;
currentWallpaper = path;
+ loadCustomPalette(path);
+ generateLockscreenFrame(path);
runMatugenForCurrentWallpaper();
}
} else {
- // Global fallback target
currentIndex = pathIndex;
wallpaperConfig.adapter.currentWall = path;
currentWallpaper = path;
+ loadCustomPalette(path);
+ generateLockscreenFrame(path);
runMatugenForCurrentWallpaper();
}
generateLockscreenFrame(path);
@@ -328,7 +345,6 @@ PanelWindow {
GlobalStates.wallpaperManager.clearPerScreenWallpaper(targetScreen);
return;
}
-
console.log("Clearing per-screen wallpaper for:", targetScreen);
let perScreen = Object.assign({}, wallpaperConfig.adapter.perScreenWallpapers || {});
if (perScreen[targetScreen]) {
@@ -342,9 +358,7 @@ PanelWindow {
GlobalStates.wallpaperManager.nextWallpaper();
return;
}
-
- if (wallpaperPaths.length === 0)
- return;
+ if (wallpaperPaths.length === 0) return;
initialLoadCompleted = true;
currentIndex = (currentIndex + 1) % wallpaperPaths.length;
currentWallpaper = wallpaperPaths[currentIndex];
@@ -358,9 +372,7 @@ PanelWindow {
GlobalStates.wallpaperManager.previousWallpaper();
return;
}
-
- if (wallpaperPaths.length === 0)
- return;
+ if (wallpaperPaths.length === 0) return;
initialLoadCompleted = true;
currentIndex = currentIndex === 0 ? wallpaperPaths.length - 1 : currentIndex - 1;
currentWallpaper = wallpaperPaths[currentIndex];
@@ -374,7 +386,6 @@ PanelWindow {
GlobalStates.wallpaperManager.setWallpaperByIndex(index);
return;
}
-
if (index >= 0 && index < wallpaperPaths.length) {
initialLoadCompleted = true;
currentIndex = index;
@@ -385,10 +396,8 @@ PanelWindow {
}
}
- // FunciΓ³n para re-ejecutar Matugen con el wallpaper actual
function setMatugenScheme(scheme) {
wallpaperConfig.adapter.matugenScheme = scheme;
-
if (wallpaperConfig.adapter.activeColorPreset) {
console.log("Switching to Matugen scheme, clearing preset");
wallpaperConfig.adapter.activeColorPreset = "";
@@ -397,295 +406,75 @@ PanelWindow {
}
}
- // property string mpvSocket: "/tmp/ambxst_mpv_socket"
- property string mpvSocket: "/tmp/ambxst_mpv_socket_" + (currentScreenName ? currentScreenName : "ALL")
-
function runMatugenForCurrentWallpaper() {
if (activeColorPreset) {
console.log("Skipping Matugen because color preset is active:", activeColorPreset);
return;
}
-
if (currentWallpaper && initialLoadCompleted) {
+ // No regenerar si el wallpaper Y el scheme no han cambiado
+ var lastWallpaper = wallpaperConfig.adapter.lastMatugenWallpaper || "";
+ var lastScheme = wallpaperConfig.adapter.lastMatugenScheme || "";
+ var currentScheme = wallpaperConfig.adapter.matugenScheme;
+ if (lastWallpaper === currentWallpaper && lastScheme === currentScheme) {
+ console.log("Skipping Matugen β wallpaper unchanged since last generation");
+ return;
+ }
+
console.log("Running Matugen for current wallpaper:", currentWallpaper);
-
var fileType = getFileType(currentWallpaper);
var matugenSource = getColorSource(currentWallpaper);
-
console.log("Using source for matugen:", matugenSource, "(type:", fileType + ")");
- // Stop existing processes if running to prioritize new request
- if (matugenProcessWithConfig.running) {
- matugenProcessWithConfig.running = false;
- }
- if (matugenProcessNormal.running) {
- matugenProcessNormal.running = false;
- }
+ if (matugenProcessWithConfig.running) matugenProcessWithConfig.running = false;
+ if (matugenProcessNormal.running) matugenProcessNormal.running = false;
- // Ejecutar matugen con configuraciΓ³n especΓfica
- var commandWithConfig = ["matugen", "image", matugenSource, "--source-color-index", "0", "-c", decodeURIComponent(Qt.resolvedUrl("../../../../assets/matugen/config.toml").toString().replace("file://", "")), "-t", wallpaperConfig.adapter.matugenScheme];
- if (Config.theme.lightMode) {
- commandWithConfig.push("-m", "light");
- }
+ var commandWithConfig = ["matugen", "image", matugenSource, "--source-color-index", "0",
+ "-c", decodeURIComponent(Qt.resolvedUrl("../../../../assets/matugen/config.toml").toString().replace("file://", "")),
+ "-t", wallpaperConfig.adapter.matugenScheme];
+ if (Config.theme.lightMode) commandWithConfig.push("-m", "light");
matugenProcessWithConfig.command = commandWithConfig;
matugenProcessWithConfig.running = true;
- // Ejecutar matugen normal en paralelo
- var commandNormal = ["matugen", "image", matugenSource, "--source-color-index", "0", "-t", wallpaperConfig.adapter.matugenScheme];
- if (Config.theme.lightMode) {
- commandNormal.push("-m", "light");
- }
+ var commandNormal = ["matugen", "image", matugenSource, "--source-color-index", "0",
+ "-t", wallpaperConfig.adapter.matugenScheme];
+ if (Config.theme.lightMode) commandNormal.push("-m", "light");
matugenProcessNormal.command = commandNormal;
matugenProcessNormal.running = true;
- }
- }
-
- function updateMpvRuntime(enable) {
- var cmdString;
- if (enable) {
- // Since we are using unique filenames, we can just set the new path.
- // MPV will handle the switch smoothly and won't use cached versions.
- var setCmd = JSON.stringify({
- "command": ["set_property", "glsl-shaders", mpvShaderPath]
- });
- cmdString = "echo '" + setCmd + "' | socat - " + mpvSocket;
- } else {
- // Clear shaders
- var jsonCmd = JSON.stringify({
- "command": ["set_property", "glsl-shaders", ""]
- });
- cmdString = "echo '" + jsonCmd + "' | socat - " + mpvSocket;
- }
-
- mpvIpcProcess.command = ["bash", "-c", cmdString];
- mpvIpcProcess.running = true;
- }
-
- function requestVideoSync() {
- if (GlobalStates.wallpaperManager !== wallpaper) {
- if (GlobalStates.wallpaperManager) {
- GlobalStates.wallpaperManager.requestVideoSync();
- }
- return;
- }
- videoSyncTimer.restart();
- }
-
- Timer {
- id: videoSyncTimer
- interval: 1200 // give mpvpaper processes time to spawn and initialize
- repeat: false
- onTriggered: {
- console.log("Broadcasting video sync to all mpvpaper sockets...");
- mpvSyncProcess.running = true;
- }
- }
-
- Process {
- id: mpvSyncProcess
- running: false
- command: ["bash", "-c", "for sock in /tmp/ambxst_mpv_socket_*; do echo '{ \"command\": [\"set_property\", \"time-pos\", 0] }' | socat - \"$sock\" 2>/dev/null; done"]
- onExited: code => {
- console.log("Video sync broadcast completed with code:", code);
- }
- }
-
- function updateMpvShader() {
- if (getFileType(effectiveWallpaper) !== "video") {
- return;
- }
- if (!wallpaperAdapter.tintEnabled) {
- updateMpvRuntime(false);
- return;
- }
-
- var colors = [];
- // Log the first color to see if it changed
- var firstColorRaw = Colors[optimizedPalette[0]];
- console.log("Generating MPV shader. First palette color (" + optimizedPalette[0] + "):", firstColorRaw);
-
- for (var i = 0; i < optimizedPalette.length; i++) {
- var rawColor = Colors[optimizedPalette[i]];
- if (rawColor) {
- var c = Qt.darker(rawColor, 1.0);
- if (c && !isNaN(c.r) && !isNaN(c.g) && !isNaN(c.b)) {
- colors.push({
- r: c.r,
- g: c.g,
- b: c.b
- });
- }
- }
- }
-
- if (colors.length === 0) {
- console.warn("MpvShaderGenerator: No valid colors found for palette! Aborting.");
- return;
- }
-
- var shaderContent = ShaderGenerator.generate(colors);
-
- // Generate a unique filename in a dedicated directory
- var timestamp = Date.now();
- var currentShaderPath = mpvShaderDir + "/tint_" + timestamp + ".glsl";
-
- // Store the current active path so updateMpvRuntime knows which one to use
- wallpaper.mpvShaderPath = currentShaderPath;
-
- var cmd = ["python3", "-c", "import sys, os, pathlib; " + "d = pathlib.Path(sys.argv[1]); " + "d.mkdir(parents=True, exist_ok=True); " + "[f.unlink() for f in d.iterdir() if f.is_file()]; " + "pathlib.Path(sys.argv[2]).write_text(sys.argv[3]); " + "print('Wrote shader to ' + sys.argv[2]); " + "legacy_dir = os.path.dirname(sys.argv[1]); " + "[pathlib.Path(legacy_dir, f).unlink(missing_ok=True) for f in ['mpv_tint_0.glsl', 'mpv_tint_1.glsl', 'mpv_tint.glsl']]", mpvShaderDir, currentShaderPath, shaderContent];
-
- mpvShaderWriter.command = cmd;
- mpvShaderWriter.running = true;
- }
-
- property int ipcRetryCount: 0
-
- Timer {
- id: ipcRetryTimer
- interval: 200
- repeat: false
- onTriggered: {
- // Retry the last command (which is currently set in mpvIpcProcess)
- mpvIpcProcess.running = true;
- }
- }
-
- Process {
- id: mpvIpcProcess
- running: false
- onExited: code => {
- if (code !== 0) {
- console.warn("MPV IPC failed (is mpvpaper running?) Code:", code);
- if (ipcRetryCount < 10) {
- ipcRetryCount++;
- console.log("Retrying IPC (" + ipcRetryCount + "/10)...");
- ipcRetryTimer.restart();
- }
- } else {
- ipcRetryCount = 0;
- }
- }
- }
-
- Process {
- id: mpvShaderWriter
- running: false
- command: []
-
- stdout: StdioCollector {
- onStreamFinished: {
- if (text.length > 0) {
- console.log("mpvShaderWriter stdout:", text);
- }
- }
- }
-
- stderr: StdioCollector {
- onStreamFinished: {
- if (text.length > 0) {
- console.warn("mpvShaderWriter stderr:", text);
- }
- }
- }
-
- onExited: code => {
- if (code === 0) {
- console.log("MPV tint shader generated at:", mpvShaderPath);
- mpvShaderReady = true;
- // Apply immediately via IPC
- updateMpvRuntime(true);
- } else {
- console.warn("Failed to generate MPV shader");
- }
- }
- }
-
- // Trigger update when colors change
- Timer {
- id: shaderUpdateDebounce
- interval: 500
- onTriggered: {
- console.log("Shader debounce triggered, updating MPV...");
- updateMpvShader();
- }
- }
-
- Connections {
- target: Colors
- // Watch for file reload (theme change)
- function onFileChanged() {
- console.log("Colors file changed, scheduling update...");
- shaderUpdateDebounce.restart();
- }
- // Watch for background change (OLED mode often affects this first/only)
- function onBackgroundChanged() {
- console.log("Colors background changed, scheduling update...");
- shaderUpdateDebounce.restart();
- }
- // Fallback
- function onPrimaryChanged() {
- console.log("Colors primary changed, scheduling update...");
- shaderUpdateDebounce.restart();
- }
- }
-
- Connections {
- target: Config
- function onOledModeChanged() {
- console.log("Config OLED mode changed, scheduling update...");
- shaderUpdateDebounce.restart();
- }
- }
-
- onTintEnabledChanged: {
- console.log("Tint enabled changed to", tintEnabled);
- updateMpvShader();
- }
-
- onEffectiveWallpaperChanged: {
- if (getFileType(effectiveWallpaper) === "video") {
- shaderUpdateDebounce.restart();
+
+ // Guardar el wallpaper y scheme actual para no regenerar al reiniciar
+ wallpaperConfig.adapter.lastMatugenWallpaper = currentWallpaper;
+ wallpaperConfig.adapter.lastMatugenScheme = wallpaperConfig.adapter.matugenScheme;
}
}
Component.onCompleted: {
- // Only the first Wallpaper instance should manage scanning
- // Other instances (for other screens) share the same data via GlobalStates
if (GlobalStates.wallpaperManager !== null) {
- // Another instance already registered, skip initialization
_wallpaperDirInitialized = true;
return;
}
-
GlobalStates.wallpaperManager = wallpaper;
- // Verificar si existe wallpapers.json, si no, crear con fallback
checkWallpapersJson.running = true;
-
- // Initial scans - do these once after config is loaded
scanColorPresets();
- // Start directory monitoring
presetsWatcher.reload();
officialPresetsWatcher.reload();
- // Load initial wallpaper config - this will trigger onWallPathChanged which does the actual scan
wallpaperConfig.reload();
- // Generate lockscreen frame for initial wallpaper after a short delay
Qt.callLater(function () {
if (currentWallpaper) {
generateLockscreenFrame(currentWallpaper);
- }
- // Force shader generation on startup if enabled
- if (tintEnabled) {
- updateMpvShader();
+ loadCustomPalette(currentWallpaper);
}
});
}
+ // -------------------------------------------------------------------
+ // Configuration file handling
+ // -------------------------------------------------------------------
FileView {
id: wallpaperConfig
- // QUICKSHELL-GIT: path: Quickshell.cachePath("wallpapers.json")
- path: Quickshell.env("HOME") + "/.cache/ambxst/wallpapers.json"
+ path: Quickshell.env("HOME") + "/.cache/nothingless/wallpapers.json"
watchChanges: true
onLoaded: {
@@ -697,11 +486,9 @@ PanelWindow {
onFileChanged: reload()
onAdapterUpdated: {
- // Ensure matugenScheme has a default value
if (!wallpaperConfig.adapter.matugenScheme) {
wallpaperConfig.adapter.matugenScheme = "scheme-tonal-spot";
}
- // Update the currentMatugenScheme property to trigger UI updates
currentMatugenScheme = Qt.binding(function () {
return wallpaperConfig.adapter.matugenScheme;
});
@@ -714,7 +501,12 @@ PanelWindow {
property string wallPath: ""
property string matugenScheme: "scheme-tonal-spot"
property string activeColorPreset: ""
+ property string lastMatugenWallpaper: ""
+ property string lastMatugenScheme: ""
property bool tintEnabled: false
+ property bool interpolationEnabled: false
+ property real targetInputFps: 24.0
+ property int interpolationMultiplier: 2
property var perScreenWallpapers: ({})
onActiveColorPresetChanged: {
@@ -724,17 +516,9 @@ PanelWindow {
}
onCurrentWallChanged: {
- // Skip during initial load - scanWallpapers handles this
- if (!wallpaper._wallpaperDirInitialized)
- return;
-
- // Siempre actualizar si es diferente al actual
+ if (!wallpaper._wallpaperDirInitialized) return;
if (currentWall && currentWall !== wallpaper.currentWallpaper) {
- // If paths are not loaded yet, wait for scanWallpapers to finish
- if (wallpaper.wallpaperPaths.length === 0) {
- return;
- }
-
+ if (wallpaper.wallpaperPaths.length === 0) return;
var pathIndex = wallpaper.wallpaperPaths.indexOf(currentWall);
if (pathIndex !== -1) {
wallpaper.currentIndex = pathIndex;
@@ -751,22 +535,19 @@ PanelWindow {
onWallPathChanged: {
if (wallPath) {
console.log("Config wallPath updated:", wallPath);
-
- // Initialize scanning on first valid wallPath load
if (!wallpaper._wallpaperDirInitialized && GlobalStates.wallpaperManager === wallpaper) {
wallpaper._wallpaperDirInitialized = true;
-
- // Set up directory watcher
directoryWatcher.path = wallPath;
directoryWatcher.reload();
- // Perform initial wallpaper scan
- var cmd = ["find", wallPath, "-name", ".*", "-prune", "-o", "-type", "f", "(", "-name", "*.jpg", "-o", "-name", "*.jpeg", "-o", "-name", "*.png", "-o", "-name", "*.webp", "-o", "-name", "*.tif", "-o", "-name", "*.tiff", "-o", "-name", "*.gif", "-o", "-name", "*.mp4", "-o", "-name", "*.webm", "-o", "-name", "*.mov", "-o", "-name", "*.avi", "-o", "-name", "*.mkv", ")", "-print"];
+ var cmd = ["find", wallPath, "-name", ".*", "-prune", "-o", "-type", "f",
+ "(", "-name", "*.jpg", "-o", "-name", "*.jpeg", "-o", "-name", "*.png",
+ "-o", "-name", "*.webp", "-o", "-name", "*.tif", "-o", "-name", "*.tiff",
+ "-o", "-name", "*.gif", "-o", "-name", "*.mp4", "-o", "-name", "*.webm",
+ "-o", "-name", "*.mov", "-o", "-name", "*.avi", "-o", "-name", "*.mkv", ")", "-print"];
scanWallpapers.command = cmd;
scanWallpapers.running = true;
wallpaper.scanSubfolders();
-
- // Start thumbnail generation
delayedThumbnailGen.start();
}
}
@@ -774,12 +555,13 @@ PanelWindow {
}
}
+ // -------------------------------------------------------------------
+ // External processes
+ // -------------------------------------------------------------------
Process {
id: checkWallpapersJson
running: false
- // QUICKSHELL-GIT: command: ["test", "-f", Quickshell.cachePath("wallpapers.json")]
- command: ["test", "-f", Quickshell.env("HOME") + "/.cache/ambxst/wallpapers.json"]
-
+ command: ["test", "-f", Quickshell.env("HOME") + "/.cache/nothingless/wallpapers.json"]
onExited: function (exitCode) {
if (exitCode !== 0) {
console.log("wallpapers.json does not exist, creating with fallbackDir");
@@ -794,77 +576,28 @@ PanelWindow {
id: matugenProcessWithConfig
running: false
command: []
-
- stdout: StdioCollector {
- onStreamFinished: {
- if (text.length > 0) {
- console.log("Matugen (with config) output:", text);
- }
- }
- }
-
- stderr: StdioCollector {
- onStreamFinished: {
- if (text.length > 0) {
- console.warn("Matugen (with config) error:", text);
- }
- }
- }
-
- onExited: {
- console.log("Matugen with config finished");
- }
+ stdout: StdioCollector { onStreamFinished: { if (text.length > 0) console.log("Matugen (with config) output:", text); } }
+ stderr: StdioCollector { onStreamFinished: { if (text.length > 0) console.warn("Matugen (with config) error:", text); } }
+ onExited: { console.log("Matugen with config finished"); }
}
Process {
id: matugenProcessNormal
running: false
command: []
-
- stdout: StdioCollector {
- onStreamFinished: {
- if (text.length > 0) {
- console.log("Matugen (normal) output:", text);
- }
- }
- }
-
- stderr: StdioCollector {
- onStreamFinished: {
- if (text.length > 0) {
- console.warn("Matugen (normal) error:", text);
- }
- }
- }
-
- onExited: {
- console.log("Matugen normal finished");
- }
+ stdout: StdioCollector { onStreamFinished: { if (text.length > 0) console.log("Matugen (normal) output:", text); } }
+ stderr: StdioCollector { onStreamFinished: { if (text.length > 0) console.warn("Matugen (normal) error:", text); } }
+ onExited: { console.log("Matugen normal finished"); }
}
- // Proceso para generar thumbnails de videos
Process {
id: thumbnailGeneratorScript
running: false
- // QUICKSHELL-GIT: command: ["python3", decodeURIComponent(Qt.resolvedUrl("../../../../scripts/thumbgen.py").toString().replace("file://", "")), Quickshell.cacheDir + "/wallpapers.json", Quickshell.cacheDir, fallbackDir]
- command: ["python3", decodeURIComponent(Qt.resolvedUrl("../../../../scripts/thumbgen.py").toString().replace("file://", "")), Quickshell.env("HOME") + "/.cache/ambxst" + "/wallpapers.json", Quickshell.env("HOME") + "/.cache/ambxst", fallbackDir]
-
- stdout: StdioCollector {
- onStreamFinished: {
- if (text.length > 0) {
- console.log("Thumbnail Generator:", text);
- }
- }
- }
-
- stderr: StdioCollector {
- onStreamFinished: {
- if (text.length > 0) {
- console.warn("Thumbnail Generator Error:", text);
- }
- }
- }
-
+ command: ["python3", decodeURIComponent(Qt.resolvedUrl("../../../../scripts/thumbgen.py").toString().replace("file://", "")),
+ Quickshell.env("HOME") + "/.cache/nothingless/wallpapers.json",
+ Quickshell.env("HOME") + "/.cache/nothingless", fallbackDir]
+ stdout: StdioCollector { onStreamFinished: { if (text.length > 0) console.log("Thumbnail Generator:", text); } }
+ stderr: StdioCollector { onStreamFinished: { if (text.length > 0) console.warn("Thumbnail Generator Error:", text); } }
onExited: function (exitCode) {
if (exitCode === 0) {
console.log("β
Video thumbnails generated successfully");
@@ -877,39 +610,20 @@ PanelWindow {
Timer {
id: delayedThumbnailGen
- interval: 2000 // Delay 2 seconds after change to not block
+ interval: 2000
repeat: false
onTriggered: thumbnailGeneratorScript.running = true
}
- // Proceso para generar frame de lockscreen con el script de Python
Process {
id: lockscreenWallpaperScript
running: false
command: []
-
- stdout: StdioCollector {
- onStreamFinished: {
- if (text.length > 0) {
- console.log("Lockscreen Wallpaper Generator:", text);
- }
- }
- }
-
- stderr: StdioCollector {
- onStreamFinished: {
- if (text.length > 0) {
- console.warn("Lockscreen Wallpaper Generator Error:", text);
- }
- }
- }
-
+ stdout: StdioCollector { onStreamFinished: { if (text.length > 0) console.log("Lockscreen Wallpaper Generator:", text); } }
+ stderr: StdioCollector { onStreamFinished: { if (text.length > 0) console.warn("Lockscreen Wallpaper Generator Error:", text); } }
onExited: function (exitCode) {
- if (exitCode === 0) {
- console.log("β
Lockscreen wallpaper ready");
- } else {
- console.warn("β οΈ Lockscreen wallpaper generation failed with code:", exitCode);
- }
+ if (exitCode === 0) console.log("β
Lockscreen wallpaper ready");
+ else console.warn("β οΈ Lockscreen wallpaper generation failed with code:", exitCode);
}
}
@@ -917,18 +631,12 @@ PanelWindow {
id: scanSubfoldersProcess
running: false
command: wallpaperDir ? ["find", wallpaperDir, "-mindepth", "1", "-name", ".*", "-prune", "-o", "-type", "d", "-print"] : []
-
stdout: StdioCollector {
onStreamFinished: {
console.log("scanSubfolders stdout:", text);
- var rawPaths = text.trim().split("\n").filter(function (f) {
- return f.length > 0;
- });
-
+ var rawPaths = text.trim().split("\n").filter(function (f) { return f.length > 0; });
allSubdirs = rawPaths;
-
var basePath = wallpaperDir.endsWith("/") ? wallpaperDir : wallpaperDir + "/";
-
var topLevelFolders = rawPaths.filter(function (path) {
var relative = path.replace(basePath, "");
return relative.indexOf("/") === -1;
@@ -937,58 +645,38 @@ PanelWindow {
}).filter(function (name) {
return name.length > 0 && !name.startsWith(".");
});
-
topLevelFolders.sort();
subfolderFilters = topLevelFolders;
- subfolderFiltersChanged(); // Emitir seΓ±al manualmente
console.log("Updated subfolderFilters:", subfolderFilters);
}
}
-
- stderr: StdioCollector {
- onStreamFinished: {
- if (text.length > 0) {
- console.warn("Error scanning subfolders:", text);
- }
- }
- }
-
+ stderr: StdioCollector { onStreamFinished: { if (text.length > 0) console.warn("Error scanning subfolders:", text); } }
onRunningChanged: {
- if (running) {
- console.log("Starting scanSubfolders for directory:", wallpaperDir);
- } else {
- console.log("Finished scanSubfolders");
- }
+ if (running) console.log("Starting scanSubfolders for directory:", wallpaperDir);
+ else console.log("Finished scanSubfolders");
}
}
- // Directory watcher using FileView to monitor the wallpaper directory
+ // -------------------------------------------------------------------
+ // Directory watchers
+ // -------------------------------------------------------------------
FileView {
id: directoryWatcher
path: wallpaperDir
watchChanges: true
printErrors: false
-
onFileChanged: {
- if (wallpaperDir === "")
- return;
+ if (wallpaperDir === "") return;
console.log("Wallpaper directory changed, rescanning...");
scanWallpapers.running = true;
scanSubfoldersProcess.running = true;
- // Regenerar thumbnails si hay nuevos videos (delayed)
- if (delayedThumbnailGen.running)
- delayedThumbnailGen.restart();
- else
- delayedThumbnailGen.start();
+ if (delayedThumbnailGen.running) delayedThumbnailGen.restart();
+ else delayedThumbnailGen.start();
}
-
- // Remove onLoadFailed to prevent premature fallback activation
}
- // Recursive directory watchers for subfolders
Instantiator {
model: allSubdirs
-
delegate: FileView {
path: modelData
watchChanges: true
@@ -997,36 +685,28 @@ PanelWindow {
console.log("Subdirectory content changed (" + path + "), rescanning...");
scanWallpapers.running = true;
scanSubfoldersProcess.running = true;
-
- // Regenerar thumbnails (delayed)
- if (delayedThumbnailGen.running)
- delayedThumbnailGen.restart();
- else
- delayedThumbnailGen.start();
+ if (delayedThumbnailGen.running) delayedThumbnailGen.restart();
+ else delayedThumbnailGen.start();
}
}
}
- // Directory watcher for user color presets
FileView {
id: presetsWatcher
path: colorPresetsDir
watchChanges: true
printErrors: false
-
onFileChanged: {
console.log("User color presets directory changed, rescanning...");
scanPresetsProcess.running = true;
}
}
- // Directory watcher for official color presets
FileView {
id: officialPresetsWatcher
path: officialColorPresetsDir
watchChanges: true
printErrors: false
-
onFileChanged: {
console.log("Official color presets directory changed, rescanning...");
scanPresetsProcess.running = true;
@@ -1036,41 +716,34 @@ PanelWindow {
Process {
id: scanWallpapers
running: false
- command: wallpaperDir ? ["find", wallpaperDir, "-name", ".*", "-prune", "-o", "-type", "f", "(", "-name", "*.jpg", "-o", "-name", "*.jpeg", "-o", "-name", "*.png", "-o", "-name", "*.webp", "-o", "-name", "*.tif", "-o", "-name", "*.tiff", "-o", "-name", "*.gif", "-o", "-name", "*.mp4", "-o", "-name", "*.webm", "-o", "-name", "*.mov", "-o", "-name", "*.avi", "-o", "-name", "*.mkv", ")", "-print"] : []
-
+ command: wallpaperDir ? ["find", wallpaperDir, "-name", ".*", "-prune", "-o", "-type", "f",
+ "(", "-name", "*.jpg", "-o", "-name", "*.jpeg", "-o", "-name", "*.png",
+ "-o", "-name", "*.webp", "-o", "-name", "*.tif", "-o", "-name", "*.tiff",
+ "-o", "-name", "*.gif", "-o", "-name", "*.mp4", "-o", "-name", "*.webm",
+ "-o", "-name", "*.mov", "-o", "-name", "*.avi", "-o", "-name", "*.mkv", ")", "-print"] : []
onRunningChanged: {
if (running && wallpaperDir === "") {
console.log("Blocking scanWallpapers because wallpaperDir is empty");
running = false;
}
}
-
stdout: StdioCollector {
onStreamFinished: {
- var files = text.trim().split("\n").filter(function (f) {
- return f.length > 0;
- });
+ var files = text.trim().split("\n").filter(function (f) { return f.length > 0; });
if (files.length === 0) {
console.log("No wallpapers found in main directory, using fallback");
usingFallback = true;
scanFallback.running = true;
} else {
usingFallback = false;
- // Only update if the list has actually changed
var newFiles = files.sort();
var listChanged = JSON.stringify(newFiles) !== JSON.stringify(wallpaperPaths);
if (listChanged) {
console.log("Wallpaper directory updated. Found", newFiles.length, "images");
wallpaperPaths = newFiles;
-
- // Always try to load the saved wallpaper when list changes
if (wallpaperPaths.length > 0) {
- // Trigger thumbnail generation if list changed
- if (delayedThumbnailGen.running)
- delayedThumbnailGen.restart();
- else
- delayedThumbnailGen.start();
-
+ if (delayedThumbnailGen.running) delayedThumbnailGen.restart();
+ else delayedThumbnailGen.start();
if (wallpaperConfig.adapter.currentWall) {
var savedIndex = wallpaperPaths.indexOf(wallpaperConfig.adapter.currentWall);
if (savedIndex !== -1) {
@@ -1083,25 +756,21 @@ PanelWindow {
} else {
currentIndex = 0;
}
-
if (!initialLoadCompleted) {
if (!wallpaperConfig.adapter.currentWall) {
wallpaperConfig.adapter.currentWall = wallpaperPaths[0];
}
initialLoadCompleted = true;
- // runMatugenForCurrentWallpaper() will be called by onCurrentWallChanged
}
}
}
}
}
}
-
stderr: StdioCollector {
onStreamFinished: {
if (text.length > 0) {
console.warn("Error scanning wallpaper directory:", text);
- // Only fallback if we don't already have wallpapers loaded AND we have a valid directory that failed
if (wallpaperPaths.length === 0 && wallpaperDir !== "") {
console.log("Directory scan failed for " + wallpaperDir + ", using fallback");
usingFallback = true;
@@ -1115,38 +784,30 @@ PanelWindow {
Process {
id: scanFallback
running: false
- command: ["find", fallbackDir, "-name", ".*", "-prune", "-o", "-type", "f", "(", "-name", "*.jpg", "-o", "-name", "*.jpeg", "-o", "-name", "*.png", "-o", "-name", "*.webp", "-o", "-name", "*.tif", "-o", "-name", "*.tiff", "-o", "-name", "*.gif", "-o", "-name", "*.mp4", "-o", "-name", "*.webm", "-o", "-name", "*.mov", "-o", "-name", "*.avi", "-o", "-name", "*.mkv", ")", "-print"]
-
+ command: ["find", fallbackDir, "-name", ".*", "-prune", "-o", "-type", "f",
+ "(", "-name", "*.jpg", "-o", "-name", "*.jpeg", "-o", "-name", "*.png",
+ "-o", "-name", "*.webp", "-o", "-name", "*.tif", "-o", "-name", "*.tiff",
+ "-o", "-name", "*.gif", "-o", "-name", "*.mp4", "-o", "-name", "*.webm",
+ "-o", "-name", "*.mov", "-o", "-name", "*.avi", "-o", "-name", "*.mkv", ")", "-print"]
stdout: StdioCollector {
onStreamFinished: {
- var files = text.trim().split("\n").filter(function (f) {
- return f.length > 0;
- });
+ var files = text.trim().split("\n").filter(function (f) { return f.length > 0; });
console.log("Using fallback wallpapers. Found", files.length, "images");
-
- // Only use fallback if we don't already have main wallpapers loaded
if (usingFallback) {
wallpaperPaths = files.sort();
-
- // Initialize fallback wallpaper selection
if (wallpaperPaths.length > 0) {
if (wallpaperConfig.adapter.currentWall) {
var savedIndex = wallpaperPaths.indexOf(wallpaperConfig.adapter.currentWall);
- if (savedIndex !== -1) {
- currentIndex = savedIndex;
- } else {
- currentIndex = 0;
- }
+ if (savedIndex !== -1) currentIndex = savedIndex;
+ else currentIndex = 0;
} else {
currentIndex = 0;
}
-
if (!initialLoadCompleted) {
if (!wallpaperConfig.adapter.currentWall) {
wallpaperConfig.adapter.currentWall = wallpaperPaths[0];
}
initialLoadCompleted = true;
- // runMatugenForCurrentWallpaper() will be called by onCurrentWallChanged
}
}
}
@@ -1157,9 +818,7 @@ PanelWindow {
Process {
id: scanPresetsProcess
running: false
- // Scan both directories. find will complain to stderr if one is missing but still output what it finds.
command: ["find", officialColorPresetsDir, colorPresetsDir, "-mindepth", "1", "-maxdepth", "1", "-type", "d"]
-
stdout: StdioCollector {
onStreamFinished: {
console.log("Scan Presets Output:", text);
@@ -1167,286 +826,573 @@ PanelWindow {
var uniqueNames = [];
for (var i = 0; i < rawLines.length; i++) {
var line = rawLines[i].trim();
- if (line.length === 0)
- continue;
+ if (line.length === 0) continue;
var name = line.split('/').pop();
- // Deduplicate
- if (uniqueNames.indexOf(name) === -1) {
- uniqueNames.push(name);
- }
+ if (uniqueNames.indexOf(name) === -1) uniqueNames.push(name);
}
uniqueNames.sort();
console.log("Found color presets:", uniqueNames);
colorPresets = uniqueNames;
}
}
-
- stderr: StdioCollector {
- onStreamFinished: {
- // Suppress common "No such file or directory" if one dir is missing
- // console.warn("Scan Presets Error:", text);
- }
- }
+ stderr: StdioCollector { onStreamFinished: { /* suppress errors */ } }
}
Process {
id: applyPresetProcess
running: false
command: []
-
onExited: code => {
- if (code === 0)
- console.log("Color preset applied successfully");
- else
- console.warn("Failed to apply color preset, code:", code);
+ if (code === 0) console.log("Color preset applied successfully");
+ else console.warn("Failed to apply color preset, code:", code);
}
}
- Rectangle {
- id: background
- anchors.fill: parent
- color: "black"
- focus: true
-
- Keys.onLeftPressed: {
- if (wallpaper.wallpaperPaths.length > 0) {
- wallpaper.previousWallpaper();
- }
- }
-
- Keys.onRightPressed: {
- if (wallpaper.wallpaperPaths.length > 0) {
- wallpaper.nextWallpaper();
- }
- }
-
- WallpaperImage {
- id: wallImage
- anchors.fill: parent
- source: wallpaper.effectiveWallpaper
- }
+ // -------------------------------------------------------------------
+ // Reusable shader effect for palette tinting
+ // -------------------------------------------------------------------
+ component PaletteShaderEffect: ShaderEffect {
+ id: effect
+ property var source: null
+ property var paletteTexture: null
+ property real paletteSize: 0
+ property real texWidth: 1
+ property real texHeight: 1
+
+ vertexShader: "palette.vert.qsb"
+ fragmentShader: "palette.frag.qsb"
}
- component WallpaperImage: Item {
- property string source
- property string previousSource
+ // -------------------------------------------------------------------
+ // Component for static images (jpg, png, webp, etc.)
+ // -------------------------------------------------------------------
+ Component {
+ id: staticImageComponent
+ Item {
+ id: staticImageRoot
+ anchors.fill: parent
+ property string sourceFile
+ property bool tint: wallpaper.tintEnabled
+
+ onSourceFileChanged: console.log("staticImageComponent: sourceFile =", sourceFile)
+ onTintChanged: console.log("staticImageComponent: tint =", tint)
+
+ // βββ Canvas-based palette texture (pre-baked once; no per-frame render-to-texture) βββ
+ Canvas {
+ id: paletteCanvas
+ width: wallpaper.effectivePaletteSize
+ height: 1
+ visible: false
+
+ onPaint: {
+ var ctx = getContext("2d");
+ if (!ctx) return;
+ ctx.clearRect(0, 0, width, height);
+ var pal = wallpaper.effectivePalette;
+ for (var i = 0; i < pal.length; i++) {
+ var c = pal[i];
+ if (typeof c === "string" && c.charAt(0) === '#') {
+ ctx.fillStyle = c;
+ } else {
+ ctx.fillStyle = Colors[c] || "#000000";
+ }
+ ctx.fillRect(i, 0, 1, 1);
+ }
+ }
- Process {
- id: killMpvpaperProcess
- running: false
- command: ["pkill", "-f", wallpaper.mpvSocket]
+ Component.onCompleted: requestPaint() // β‘ Trigger initial paint
- onExited: function (exitCode) {
- console.log("Killed mpvpaper processes on socket", wallpaper.mpvSocket, ", exit code:", exitCode);
+ Connections {
+ target: Colors
+ function onFileChanged() { Qt.callLater(paletteCanvas.requestPaint); }
+ }
+ Connections {
+ target: wallpaper
+ function onEffectivePaletteChanged() { paletteCanvas.requestPaint(); }
+ }
}
- }
- // Trigger animation when source changes
- onSourceChanged: {
- if (previousSource !== "" && source !== previousSource) {
- if (Config.animDuration > 0) {
- transitionAnimation.restart();
+ ShaderEffectSource {
+ id: paletteTextureSource
+ sourceItem: paletteCanvas
+ live: false // β‘ static texture β no per-frame re-capture
+ hideSource: true
+ visible: false
+ smooth: false
+ recursive: false
+
+ Connections {
+ target: paletteCanvas
+ function onPainted() { paletteTextureSource.scheduleUpdate(); }
}
}
- previousSource = source;
- // Kill mpvpaper if switching to a static image
- if (source) {
- var fileType = getFileType(source);
- if (fileType === 'image') {
- killMpvpaperProcess.running = true;
+ // Image with layer effect for tinting
+ Image {
+ id: rawImage
+ anchors.fill: parent
+ source: staticImageRoot.sourceFile ? "file://" + staticImageRoot.sourceFile : ""
+ fillMode: Image.PreserveAspectCrop
+ asynchronous: true
+ smooth: true
+ mipmap: true
+ visible: true
+
+ // Layer effect for palette tinting
+ layer.enabled: staticImageRoot.tint && wallpaper.effectivePaletteSize > 0
+ layer.effect: PaletteShaderEffect {
+ property var paletteTexture: paletteTextureSource
+ property int paletteSize: wallpaper.effectivePaletteSize
+ property real sharpness: 20.0
+ property real mixStrength: 1.0
+ texWidth: rawImage.width
+ texHeight: rawImage.height
+
+ vertexShader: "palette.vert.qsb"
+ fragmentShader: "palette.frag.qsb"
+ }
+
+ onStatusChanged: {
+ if (status === Image.Ready) {
+ console.log("rawImage ready");
+ }
}
}
}
-
- SequentialAnimation {
- id: transitionAnimation
-
- ParallelAnimation {
- NumberAnimation {
- target: wallImage
- property: "scale"
- to: 1.01
- duration: Config.animDuration
- easing.type: Easing.OutCubic
+ }
+ // -------------------------------------------------------------------
+ // Component for videos and GIFs with softwareβcontrolled input FPS
+ // -------------------------------------------------------------------
+ Component {
+ id: videoComponent
+ Item {
+ id: videoRoot
+ anchors.fill: parent
+ property string sourceFile
+ property bool tint: wallpaper.tintEnabled
+ property bool interpolate: wallpaper.interpolationEnabled
+ property int multiplier: wallpaper.interpolationMultiplier
+ property real targetInputFps: 24.0
+
+ // Frame control properties
+ property real originalFps: 30
+ property real effectiveInputFps: targetInputFps
+ property real captureIntervalMs: 1000 / effectiveInputFps
+ property real lastCaptureTime: 0
+ property real blendFactor: 0.0
+ property bool isOriginalFrame: true
+ property int frameCounter: 0
+
+ // FPS estimation
+ property real fpsOutput: 0
+ property int frameCountSinceLastSecond: 0
+ property real lastFpsUpdateTime: 0
+
+ // Debug overlay
+ property bool debugMode: false
+
+ onTintChanged: console.log("videoComponent: tint =", tint)
+ onInterpolateChanged: {
+ console.log("videoComponent: interpolate =", interpolate)
+ if (interpolate) {
+ captureTimer.restart()
+ frameAnimation.running = true
+ previousFrameSource.scheduleUpdate()
+ videoRoot.lastCaptureTime = Date.now()
+ } else {
+ captureTimer.stop()
+ frameAnimation.running = false
}
- NumberAnimation {
- target: wallImage
- property: "opacity"
- to: 0.5
- duration: Config.animDuration
- easing.type: Easing.OutCubic
+ }
+ onMultiplierChanged: {
+ // multiplier does not affect capture rate
+ }
+ onTargetInputFpsChanged: {
+ effectiveInputFps = Math.min(originalFps, targetInputFps)
+ captureIntervalMs = 1000 / effectiveInputFps
+ if (interpolate) {
+ captureTimer.restart()
+ videoRoot.lastCaptureTime = Date.now()
}
}
- ParallelAnimation {
- NumberAnimation {
- target: wallImage
- property: "scale"
- to: 1.0
- duration: Config.animDuration
- easing.type: Easing.OutCubic
+ // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ // Canvas-based palette texture (pre-baked once, no per-frame re-render)
+ // βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ Canvas {
+ id: paletteCanvas2
+ width: wallpaper.effectivePaletteSize
+ height: 1
+ visible: false
+
+ onPaint: {
+ var ctx = getContext("2d");
+ if (!ctx) return;
+ ctx.clearRect(0, 0, width, height);
+ var pal = wallpaper.effectivePalette;
+ for (var i = 0; i < pal.length; i++) {
+ var c = pal[i];
+ if (typeof c === "string" && c.charAt(0) === '#') {
+ ctx.fillStyle = c;
+ } else {
+ ctx.fillStyle = Colors[c] || "#000000";
+ }
+ ctx.fillRect(i, 0, 1, 1);
+ }
+ }
+
+ Component.onCompleted: requestPaint() // β‘ Trigger initial paint
+
+ Connections {
+ target: Colors
+ function onFileChanged() { Qt.callLater(paletteCanvas2.requestPaint); }
}
- NumberAnimation {
- target: wallImage
- property: "opacity"
- to: 1.0
- duration: Config.animDuration
- easing.type: Easing.OutCubic
+ Connections {
+ target: wallpaper
+ function onEffectivePaletteChanged() { paletteCanvas2.requestPaint(); }
}
}
- }
-
- Loader {
- anchors.fill: parent
- sourceComponent: {
- if (!parent.source)
- return null;
- var fileType = getFileType(parent.source);
- if (fileType === 'image') {
- return staticImageComponent;
- } else if (fileType === 'gif' || fileType === 'video') {
- return mpvpaperComponent;
+ ShaderEffectSource {
+ id: paletteTextureSource
+ sourceItem: paletteCanvas2
+ live: false // β‘ static texture β no per-frame re-capture
+ hideSource: true
+ visible: false
+ smooth: false
+ recursive: false
+
+ Connections {
+ target: paletteCanvas2
+ function onPainted() { paletteTextureSource.scheduleUpdate(); }
}
- return staticImageComponent; // fallback
}
- property string sourceFile: parent.source
- }
+ // -------------------------------------------------------------------
+ // Original video player (plays at normal speed)
+ // -------------------------------------------------------------------
+ Video {
+ id: videoPlayer
+ anchors.fill: parent
+ source: videoRoot.sourceFile ? "file://" + videoRoot.sourceFile : ""
+ loops: MediaPlayer.Infinite
+ autoPlay: true
+ muted: true
+ fillMode: VideoOutput.PreserveAspectCrop
+ visible: !videoRoot.interpolate || videoRoot.multiplier <= 1
+ // Siempre a velocidad normal; el control de FPS lo hace el Timer
+ playbackRate: 1.0
+
+ onMetaDataChanged: {
+ if (metaData.frameRate && metaData.frameRate > 0) {
+ videoRoot.originalFps = metaData.frameRate
+ videoRoot.effectiveInputFps = Math.min(videoRoot.originalFps, videoRoot.targetInputFps)
+ videoRoot.captureIntervalMs = 1000 / videoRoot.effectiveInputFps
+ console.log("videoComponent: detected FPS =", videoRoot.originalFps,
+ "effective input FPS =", videoRoot.effectiveInputFps)
+ }
+ }
- Component {
- id: staticImageComponent
- Item {
- id: staticImageRoot
- width: parent.width
- height: parent.height
- property string sourceFile: parent.sourceFile
- property bool tint: wallpaper.tintEnabled
-
- // Subset of colors for optimization (approx 25 colors vs 98)
- readonly property var optimizedPalette: ["background", "overBackground", "shadow", "surface", "surfaceBright", "surfaceDim", "surfaceContainer", "surfaceContainerHigh", "surfaceContainerHighest", "surfaceContainerLow", "surfaceContainerLowest", "primary", "secondary", "tertiary", "red", "lightRed", "green", "lightGreen", "blue", "lightBlue", "yellow", "lightYellow", "cyan", "lightCyan", "magenta", "lightMagenta"]
-
- // Palette generation for the shader
- Item {
- id: paletteSourceItem
- // Must be visible for ShaderEffectSource to capture it,
- // but we hide it visually by placing it behind or expecting ShaderEffectSource hideSource behavior.
- visible: true
- width: staticImageRoot.optimizedPalette.length
- height: 1
- opacity: 0 // Make invisible to eye but maintain presence for capture if needed (though hideSource usually handles this)
-
- Row {
- anchors.fill: parent
- Repeater {
- model: staticImageRoot.optimizedPalette
- Rectangle {
- width: 1
- height: 1
- color: Colors[modelData]
- }
- }
+ onPlaybackStateChanged: {
+ if (playbackState === MediaPlayer.PlayingState && videoRoot.interpolate) {
+ captureTimer.restart()
+ frameAnimation.running = true
+ previousFrameSource.scheduleUpdate()
+ videoRoot.lastCaptureTime = Date.now()
+ } else {
+ captureTimer.stop()
+ frameAnimation.running = false
}
}
+ }
- ShaderEffectSource {
- id: paletteTextureSource
- sourceItem: paletteSourceItem
- hideSource: true
- visible: false // The source object itself doesn't need to be visible in the scene graph
- smooth: false
- recursive: false
+ // Live capture of the current frame
+ ShaderEffectSource {
+ id: liveSource
+ // Only capture from videoPlayer when interpolation is ON.
+ // When off, sourceItem = null so QSGVideoNode renders directly to screen
+ // without being forced into texture-capture mode.
+ sourceItem: videoRoot.interpolate ? videoPlayer : null
+ live: videoRoot.interpolate
+ hideSource: true
+ smooth: true
+ visible: false
+ }
+
+ // Buffer for the previous frame (updated at target input FPS)
+ ShaderEffectSource {
+ id: previousFrameSource
+ sourceItem: videoRoot.interpolate ? videoPlayer : null
+ live: false
+ hideSource: true
+ smooth: true
+ visible: false
+ }
+
+ // -------------------------------------------------------------------
+ // Timer that captures frames at the desired input FPS
+ // -------------------------------------------------------------------
+ Timer {
+ id: captureTimer
+ interval: videoRoot.captureIntervalMs
+ repeat: true
+ running: false
+ onTriggered: {
+ if (!videoRoot.interpolate) return
+ previousFrameSource.scheduleUpdate()
+ videoRoot.lastCaptureTime = Date.now()
+ videoRoot.isOriginalFrame = true
+ console.log("Captured input frame at", videoRoot.lastCaptureTime)
}
+ }
- Image {
- mipmap: true
- id: rawImage
- anchors.fill: parent
- source: parent.sourceFile ? "file://" + parent.sourceFile : ""
- fillMode: Image.PreserveAspectCrop
- asynchronous: true
- smooth: true
- sourceSize.width: wallpaper.width
- sourceSize.height: wallpaper.height
- layer.enabled: parent.tint
- layer.effect: ShaderEffect {
- property var paletteTexture: paletteTextureSource
- property real paletteSize: staticImageRoot.optimizedPalette.length
- property real texWidth: rawImage.width
- property real texHeight: rawImage.height
-
- vertexShader: "palette.vert.qsb"
- fragmentShader: "palette.frag.qsb"
+ // -------------------------------------------------------------------
+ // FrameAnimation for continuous blendFactor updates (VSync synced)
+ // -------------------------------------------------------------------
+ FrameAnimation {
+ id: frameAnimation
+ running: false
+ onTriggered: {
+ if (!videoRoot.interpolate || videoRoot.multiplier <= 1) return
+ if (videoPlayer.playbackState !== MediaPlayer.PlayingState) return
+
+ var now = Date.now()
+ var elapsed = now - videoRoot.lastCaptureTime
+ var factor = elapsed / videoRoot.captureIntervalMs
+ videoRoot.blendFactor = Math.min(1.0, factor)
+ videoRoot.isOriginalFrame = (videoRoot.blendFactor < 0.01 || videoRoot.blendFactor > 0.99)
+
+ // Update FPS statistics
+ videoRoot.frameCountSinceLastSecond++
+ var fpsElapsed = now - videoRoot.lastFpsUpdateTime
+ if (fpsElapsed >= 1000) {
+ videoRoot.fpsOutput = videoRoot.frameCountSinceLastSecond * 1000 / fpsElapsed
+ videoRoot.frameCountSinceLastSecond = 0
+ videoRoot.lastFpsUpdateTime = now
}
+ videoRoot.frameCounter++
}
}
- }
- Component {
- id: mpvpaperComponent
- Item {
- property string sourceFile: parent.sourceFile
- property string scriptPath: decodeURIComponent(Qt.resolvedUrl("mpvpaper.sh").toString().replace("file://", ""))
-
- Timer {
- id: mpvpaperRestartTimer
- interval: 100
- onTriggered: {
- if (sourceFile) {
- console.log("Restarting mpvpaper for:", sourceFile);
- mpvpaperProcess.running = true;
- wallpaper.requestVideoSync();
- }
+ // -------------------------------------------------------------------
+ // Interpolation Shader Effect
+ // -------------------------------------------------------------------
+ ShaderEffect {
+ id: interpolationEffect
+ anchors.fill: parent
+ visible: videoRoot.interpolate && videoRoot.multiplier > 1
+ property var currentFrame: liveSource
+ property var previousFrame: previousFrameSource
+ property real blendFactor: videoRoot.blendFactor
+ property vector2d iResolution: Qt.vector2d(width, height)
+ property int blockSize: 12
+ property int searchRadius: 3
+ property real motionThreshold: 0.05
+ property bool debugMode: videoRoot.debugMode
+ property bool isOriginalFrame: videoRoot.isOriginalFrame
+ property int frameCounter: videoRoot.frameCounter
+
+ vertexShader: "interpol.vert.qsb"
+ fragmentShader: "interpol.frag.qsb"
+
+ onStatusChanged: {
+ if (status === ShaderEffect.Error) {
+ console.warn("β Interpolation shader error - falling back to direct video")
+ videoRoot.interpolate = false
+ } else if (status === ShaderEffect.Ready) {
+ console.log("β
Interpolation shader ready")
}
}
+ }
+
+ // -------------------------------------------------------------------
+ // Tint layer applied over everything
+ // -------------------------------------------------------------------
+ layer.enabled: videoRoot.tint && wallpaper.effectivePaletteSize > 0
+ layer.smooth: true
+ layer.effect: ShaderEffect {
+ property var paletteTexture: paletteTextureSource
+ property int paletteSize: wallpaper.effectivePaletteSize
+ property real sharpness: 20.0
+ property real mixStrength: 1.0
+ property real texWidth: videoRoot.width
+ property real texHeight: videoRoot.height
+
+ vertexShader: "palette.vert.qsb"
+ fragmentShader: "palette.frag.qsb"
+ }
- onSourceFileChanged: {
- if (sourceFile) {
- console.log("Source file changed to:", sourceFile);
- mpvpaperProcess.running = false;
- mpvpaperRestartTimer.restart();
+ // -------------------------------------------------------------------
+ // Debug overlay
+ // -------------------------------------------------------------------
+ Rectangle {
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.margins: 8
+ color: "#80000000"
+ radius: 4
+ visible: videoRoot.debugMode
+ width: debugColumn.implicitWidth + 16
+ height: debugColumn.implicitHeight + 8
+
+ Column {
+ id: debugColumn
+ anchors.centerIn: parent
+ spacing: 2
+
+ Text {
+ text: "Input FPS: " + videoRoot.effectiveInputFps.toFixed(1) + " (orig: " + videoRoot.originalFps.toFixed(1) + ")"
+ color: "white"
+ font.pixelSize: 12
+ }
+ Text {
+ text: "Multiplier: x" + videoRoot.multiplier
+ color: "white"
+ font.pixelSize: 12
+ }
+ Text {
+ text: "Target Output FPS: " + (videoRoot.effectiveInputFps * videoRoot.multiplier).toFixed(1)
+ color: "#aaffaa"
+ font.pixelSize: 12
+ }
+ Text {
+ text: "Actual Output FPS: " + videoRoot.fpsOutput.toFixed(1)
+ color: "#ffaa00"
+ font.pixelSize: 12
+ }
+ Text {
+ text: "Frame: " + (videoRoot.isOriginalFrame ? "ORIGINAL" : "INTERPOLATED")
+ color: videoRoot.isOriginalFrame ? "#aaaaff" : "#aaffaa"
+ font.pixelSize: 12
+ }
+ Text {
+ text: "Blend: " + videoRoot.blendFactor.toFixed(2)
+ color: "white"
+ font.pixelSize: 12
+ }
+ Text {
+ text: "Count: " + videoRoot.frameCounter
+ color: "white"
+ font.pixelSize: 12
}
}
+ }
- Component.onCompleted: {
- if (sourceFile) {
- console.log("Initial mpvpaper run for:", sourceFile);
- mpvpaperProcess.running = true;
- wallpaper.requestVideoSync();
- }
+ // -------------------------------------------------------------------
+ // Source synchronization
+ // -------------------------------------------------------------------
+ onSourceFileChanged: {
+ console.log("videoComponent: sourceFile =", sourceFile)
+ if (sourceFile) {
+ videoPlayer.source = "file://" + sourceFile
+ } else {
+ videoPlayer.source = ""
+ }
+ previousFrameSource.scheduleUpdate()
+ videoRoot.lastCaptureTime = Date.now()
+ videoRoot.lastFpsUpdateTime = Date.now()
+ videoRoot.frameCountSinceLastSecond = 0
+ videoRoot.fpsOutput = 0
+ }
+
+ Component.onCompleted: {
+ if (sourceFile) {
+ videoPlayer.source = "file://" + sourceFile
}
+ previousFrameSource.scheduleUpdate()
+ videoRoot.lastCaptureTime = Date.now()
+ videoRoot.lastFpsUpdateTime = Date.now()
+ }
+ }
+ }
+ // -------------------------------------------------------------------
+ // Main wallpaper display area
+ // -------------------------------------------------------------------
+ Rectangle {
+ id: background
+ anchors.fill: parent
+ color: "black"
+ focus: true
- Component.onDestruction:
- // mpvpaper script handles killing previous instances
- {}
+ Keys.onLeftPressed: {
+ if (wallpaper.wallpaperPaths.length > 0) wallpaper.previousWallpaper();
+ }
- Process {
- id: mpvpaperProcess
- running: false
- command: sourceFile && wallpaper.currentScreenName ? ["bash", scriptPath, sourceFile, (wallpaper.tintEnabled ? wallpaper.mpvShaderPath : ""), wallpaper.currentScreenName] : []
+ Keys.onRightPressed: {
+ if (wallpaper.wallpaperPaths.length > 0) wallpaper.nextWallpaper();
+ }
- stdout: StdioCollector {
- onStreamFinished: {
- if (text.length > 0) {
- console.log("mpvpaper output:", text);
- }
- }
- }
+ // Container that handles source changes, transitions, and palette loading
+ Item {
+ id: wallImageContainer
+ anchors.fill: parent
+ property string source: wallpaper.effectiveWallpaper
+ property string previousSource: ""
- stderr: StdioCollector {
- onStreamFinished: {
- if (text.length > 0) {
- console.warn("mpvpaper error:", text);
- }
- }
+ onSourceChanged: {
+ console.log("wallImageContainer source changed to:", source);
+ if (source) wallpaper.loadCustomPalette(source);
+ // Animation will be triggered after loader finishes loading
+ }
+
+ SequentialAnimation {
+ id: transitionAnimation
+ ParallelAnimation {
+ NumberAnimation { target: wallImageContainer; property: "scale"; to: 1.01; duration: Config.animDuration; easing.type: Easing.OutCubic }
+ NumberAnimation { target: wallImageContainer; property: "opacity"; to: 0.5; duration: Config.animDuration; easing.type: Easing.OutCubic }
+ }
+ ParallelAnimation {
+ NumberAnimation { target: wallImageContainer; property: "scale"; to: 1.0; duration: Config.animDuration; easing.type: Easing.OutCubic }
+ NumberAnimation { target: wallImageContainer; property: "opacity"; to: 1.0; duration: Config.animDuration; easing.type: Easing.OutCubic }
+ }
+ }
+
+ Loader {
+ id: wallImageLoader
+ anchors.fill: parent
+ asynchronous: true
+ sourceComponent: {
+ if (!wallImageContainer.source) return null;
+ var fileType = wallpaper.getFileType(wallImageContainer.source);
+ console.log("Loader: fileType =", fileType, "source =", wallImageContainer.source);
+ if (fileType === 'image') return staticImageComponent;
+ else if (fileType === 'gif' || fileType === 'video') return videoComponent;
+ return staticImageComponent;
+ }
+
+ onLoaded: {
+ console.log("Loader: item loaded, assigning sourceFile =", wallImageContainer.source);
+ if (item) {
+ item.sourceFile = wallImageContainer.source;
+ }
+ // Trigger animation after new content is loaded
+ if (wallImageContainer.previousSource !== "" &&
+ wallImageContainer.source !== wallImageContainer.previousSource &&
+ Config.animDuration > 0) {
+ transitionAnimation.restart();
}
+ wallImageContainer.previousSource = wallImageContainer.source;
+ }
+
+ // Bind sourceFile directly to wallImageContainer.source
+ Binding {
+ target: wallImageLoader.item
+ property: "sourceFile"
+ value: wallImageContainer.source
+ when: wallImageLoader.item !== null
+ }
+ }
- onExited: function (exitCode) {
- console.log("mpvpaper process exited with code:", exitCode);
+ // Fallback in case Binding doesn't trigger
+ Connections {
+ target: wallImageContainer
+ function onSourceChanged() {
+ if (wallImageLoader.item) {
+ console.log("Connections: updating sourceFile to", wallImageContainer.source);
+ wallImageLoader.item.sourceFile = wallImageContainer.source;
}
}
}
}
}
-}
+}
\ No newline at end of file
diff --git a/modules/widgets/dashboard/wallpapers/WallpapersTab.qml b/modules/widgets/dashboard/wallpapers/WallpapersTab.qml
old mode 100644
new mode 100755
index e35d928f..9d923113
--- a/modules/widgets/dashboard/wallpapers/WallpapersTab.qml
+++ b/modules/widgets/dashboard/wallpapers/WallpapersTab.qml
@@ -73,6 +73,13 @@ FocusScope {
tintCheckbox.forceActiveFocus();
}
},
+ {
+ id: "interpolationCheckbox",
+ focusFunc: function () {
+ interpolationCheckboxContainer.keyboardNavigationActive = true;
+ interpolationCheckbox.forceActiveFocus();
+ }
+ },
{
id: "schemeSelector",
focusFunc: function () {
@@ -108,8 +115,13 @@ FocusScope {
// FunciΓ³n para enfocar los filtros
function focusFilters() {
- currentFocusIndex = 2;
- focusableElements[2].focusFunc();
+ for (var i = 0; i < focusableElements.length; i++) {
+ if (focusableElements[i].id === "filters") {
+ currentFocusIndex = i;
+ focusableElements[i].focusFunc();
+ break;
+ }
+ }
}
// FunciΓ³n para navegar hacia adelante (Tab)
@@ -768,6 +780,219 @@ FocusScope {
}
}
}
+
+ // Spacer
+ // Item { Layout.fillWidth: true }
+
+ // Motion Interpolation Toggle + Multiplier
+ Item {
+ id: interpolationCheckboxContainer
+ Layout.preferredWidth: 180
+ Layout.preferredHeight: 48
+
+ property bool keyboardNavigationActive: false
+
+ StyledRect {
+ variant: interpolationCheckboxContainer.keyboardNavigationActive && interpolationCheckbox.activeFocus ? "focus" : "pane"
+ anchors.fill: parent
+ radius: Styling.radius(4)
+ opacity: 1.0
+
+ RowLayout {
+ anchors.fill: parent
+ anchors.margins: 4
+ spacing: 4
+
+ // Label area (clickable)
+ Rectangle {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ color: Colors.background
+ radius: Styling.radius(0)
+
+ RowLayout {
+ anchors.fill: parent
+ spacing: 6
+
+ Text {
+ text: (typeof Icons !== 'undefined' && Icons.movie !== undefined) ? Icons.movie : ""
+ font.family: (typeof Icons !== 'undefined' && Icons.font !== undefined) ? Icons.font : Config.theme.font
+ font.pixelSize: 20
+ color: interpolationCheckbox.checked ? Styling.srItem("primary") : Colors.overSurfaceVariant
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Text {
+ Layout.fillWidth: true
+ text: "Motion"
+ color: Colors.overSurface
+ font.family: Config.theme.font
+ font.pixelSize: Config.theme.fontSize
+ font.weight: Font.Medium
+ verticalAlignment: Text.AlignVCenter
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ if (GlobalStates.wallpaperManager) {
+ GlobalStates.wallpaperManager.interpolationEnabled = !GlobalStates.wallpaperManager.interpolationEnabled;
+ }
+ }
+ }
+ }
+
+ // Checkbox (same style as OLED/Tint)
+ Item {
+ id: interpolationCheckbox
+ Layout.preferredWidth: 40
+ Layout.preferredHeight: 40
+
+ property bool checked: GlobalStates.wallpaperManager ? GlobalStates.wallpaperManager.interpolationEnabled : false
+
+ onActiveFocusChanged: {
+ if (!activeFocus) {
+ interpolationCheckboxContainer.keyboardNavigationActive = false;
+ }
+ }
+
+ Keys.onPressed: event => {
+ if (event.key === Qt.Key_Tab) {
+ interpolationCheckboxContainer.keyboardNavigationActive = false;
+ if (event.modifiers & Qt.ShiftModifier) {
+ wallpapersTabRoot.focusPreviousElement();
+ } else {
+ wallpapersTabRoot.focusNextElement();
+ }
+ event.accepted = true;
+ } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_Space) {
+ if (GlobalStates.wallpaperManager) {
+ GlobalStates.wallpaperManager.interpolationEnabled = !GlobalStates.wallpaperManager.interpolationEnabled;
+ }
+ event.accepted = true;
+ } else if (event.key === Qt.Key_Escape) {
+ interpolationCheckboxContainer.keyboardNavigationActive = false;
+ focusSearch();
+ event.accepted = true;
+ }
+ }
+
+ Item {
+ anchors.fill: parent
+
+ Rectangle {
+ anchors.fill: parent
+ radius: Styling.radius(0)
+ color: Colors.background
+ visible: !interpolationCheckbox.checked
+ }
+
+ StyledRect {
+ variant: "primary"
+ anchors.fill: parent
+ radius: Styling.radius(0)
+ visible: interpolationCheckbox.checked
+ opacity: interpolationCheckbox.checked ? 1.0 : 0.0
+
+ Behavior on opacity {
+ enabled: Config.animDuration > 0
+ NumberAnimation {
+ duration: Config.animDuration / 2
+ easing.type: Easing.OutQuart
+ }
+ }
+
+ Text {
+ anchors.centerIn: parent
+ text: (typeof Icons !== 'undefined' && Icons.accept !== undefined) ? Icons.accept : "β"
+ color: Styling.srItem("primary")
+ font.family: (typeof Icons !== 'undefined' && Icons.font !== undefined) ? Icons.font : Config.theme.font
+ font.pixelSize: 20
+ scale: interpolationCheckbox.checked ? 1.0 : 0.0
+
+ Behavior on scale {
+ enabled: Config.animDuration > 0
+ NumberAnimation {
+ duration: Config.animDuration / 2
+ easing.type: Easing.OutBack
+ easing.overshoot: 1.5
+ }
+ }
+ }
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ if (GlobalStates.wallpaperManager) {
+ GlobalStates.wallpaperManager.interpolationEnabled = !GlobalStates.wallpaperManager.interpolationEnabled;
+ }
+ }
+ }
+ }
+
+ // Multiplier selector (visible when checked)
+ ComboBox {
+ id: multiplierCombo
+ Layout.preferredWidth: 70
+ Layout.preferredHeight: 40
+ visible: interpolationCheckbox.checked
+
+ model: ["x2", "x3", "x4", "x5"]
+ currentIndex: {
+ var mult = GlobalStates.wallpaperManager ? GlobalStates.wallpaperManager.interpolationMultiplier : 2
+ return Math.max(0, Math.min(3, mult - 2))
+ }
+ // CORRECCIΓN: parΓ‘metro explΓcito en funciΓ³n
+ onActivated: function(index) {
+ if (GlobalStates.wallpaperManager) {
+ GlobalStates.wallpaperManager.interpolationMultiplier = index + 2
+ }
+ }
+
+ background: Rectangle {
+ color: Colors.background
+ radius: Styling.radius(0)
+ }
+
+ contentItem: Text {
+ text: parent.displayText
+ color: Colors.overSurface
+ font: Config.theme.font
+ verticalAlignment: Text.AlignVCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ indicator: Text {
+ x: parent.width - width - 8
+ y: parent.height / 2 - height / 2
+ text: (typeof Icons !== 'undefined' && Icons.chevronDown !== undefined) ? Icons.chevronDown : "βΌ"
+ font.family: (typeof Icons !== 'undefined' && Icons.font !== undefined) ? Icons.font : Config.theme.font
+ font.pixelSize: 16
+ color: Colors.overSurfaceVariant
+ }
+
+ Keys.onPressed: event => {
+ if (event.key === Qt.Key_Tab) {
+ if (event.modifiers & Qt.ShiftModifier) {
+ wallpapersTabRoot.focusPreviousElement();
+ } else {
+ wallpapersTabRoot.focusNextElement();
+ }
+ event.accepted = true;
+ } else if (event.key === Qt.Key_Escape) {
+ focusSearch();
+ event.accepted = true;
+ }
+ }
+ }
+ }
+ }
+ }
// Spacer
// Item { Layout.fillWidth: true }
diff --git a/modules/widgets/dashboard/wallpapers/interpol.frag b/modules/widgets/dashboard/wallpapers/interpol.frag
new file mode 100755
index 00000000..ce9cba2a
--- /dev/null
+++ b/modules/widgets/dashboard/wallpapers/interpol.frag
@@ -0,0 +1,172 @@
+#version 440
+
+#ifdef GL_ES
+precision highp float;
+precision mediump int;
+#endif
+
+layout(location = 0) in vec2 qt_TexCoord0;
+layout(location = 0) out vec4 fragColor;
+
+layout(binding = 1) uniform sampler2D currentFrame;
+layout(binding = 2) uniform sampler2D previousFrame;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 qt_Matrix;
+ float qt_Opacity;
+ float blendFactor;
+ vec2 iResolution;
+ int blockSize;
+ int searchRadius;
+ float motionThreshold;
+ int debugMode;
+ int isOriginalFrame;
+ int frameCounter;
+} ubuf;
+
+// -------------------------------------------------------------------
+// Ultraβfast approximate exp() β Refined Quakeβstyle polynomial
+// -------------------------------------------------------------------
+float fast_exp(float x) {
+ x = clamp(x, -10.0, 10.0);
+ float x2 = x * x;
+ float x3 = x2 * x;
+ float x4 = x2 * x2;
+ return 1.0 + x + 0.5 * x2 + 0.16666666 * x3 + 0.04166666 * x4;
+}
+
+// -------------------------------------------------------------------
+// Utility: clamp integer coordinate
+// -------------------------------------------------------------------
+ivec2 clampCoord(ivec2 coord, ivec2 minBound, ivec2 maxBound) {
+ return ivec2(clamp(coord.x, minBound.x, maxBound.x),
+ clamp(coord.y, minBound.y, maxBound.y));
+}
+
+// -------------------------------------------------------------------
+// Sample a pixel safely
+// -------------------------------------------------------------------
+vec3 samplePixel(sampler2D tex, ivec2 coord) {
+ ivec2 res = ivec2(ubuf.iResolution);
+ ivec2 clamped = clampCoord(coord, ivec2(0, 0), res - 1);
+ return texelFetch(tex, clamped, 0).rgb;
+}
+
+// -------------------------------------------------------------------
+// Optimized SAD using texelFetch (with manual unrolling for speed)
+// -------------------------------------------------------------------
+float blockSADFast(ivec2 centerCurr, ivec2 centerPrev, int bSize) {
+ float sad = 0.0;
+ int h = bSize / 2;
+ ivec2 res = ivec2(ubuf.iResolution);
+ ivec2 minBound = ivec2(h, h);
+ ivec2 maxBound = res - h - 1;
+
+ for (int y = -h; y < h; ++y) {
+ for (int x = -h; x < h; ++x) {
+ ivec2 offset = ivec2(x, y);
+ ivec2 coord_curr = clampCoord(centerCurr + offset, minBound, maxBound);
+ ivec2 coord_prev = clampCoord(centerPrev + offset, minBound, maxBound);
+ vec3 c = texelFetch(currentFrame, coord_curr, 0).rgb;
+ vec3 p = texelFetch(previousFrame, coord_prev, 0).rgb;
+ sad += dot(abs(c - p), vec3(0.299, 0.587, 0.114));
+ }
+ }
+ return sad / float(bSize * bSize);
+}
+
+void main() {
+ vec2 uv = qt_TexCoord0;
+ ivec2 res = ivec2(ubuf.iResolution);
+ ivec2 texelCoord = ivec2(uv * vec2(res));
+
+ int bSize = ubuf.blockSize;
+ int h = bSize / 2;
+ ivec2 minBound = ivec2(h, h);
+ ivec2 maxBound = res - h - 1;
+ ivec2 safeCoord = clampCoord(texelCoord, minBound, maxBound);
+
+ ivec2 blockIdx = safeCoord / bSize;
+ ivec2 blockCenter = blockIdx * bSize + h;
+
+ vec3 curr = samplePixel(currentFrame, safeCoord);
+ vec3 prev = samplePixel(previousFrame, safeCoord);
+
+ vec2 motion = vec2(0.0);
+ float bestCost = 1e10;
+ bool motionValid = false;
+
+ // ---- Pyramidβbased motion search (only at block centers) ----
+ if (all(equal(safeCoord, blockCenter))) {
+ // Fast check: if block difference is low, skip expensive search
+ float coarseDiff = blockSADFast(blockCenter, blockCenter, bSize);
+ if (coarseDiff > ubuf.motionThreshold) {
+ int sr = ubuf.searchRadius;
+ // Coarse search at 1/4 resolution for efficiency
+ vec2 coarseTexel = 4.0 / vec2(res);
+ vec2 coarseUV = uv * 0.25;
+ for (int dy = -sr; dy <= sr; ++dy) {
+ for (int dx = -sr; dx <= sr; ++dx) {
+ vec2 offset = vec2(float(dx), float(dy)) * coarseTexel;
+ vec3 c = textureLod(currentFrame, coarseUV, 2.0).rgb;
+ vec3 p = textureLod(previousFrame, coarseUV + offset, 2.0).rgb;
+ float cost = dot(abs(c - p), vec3(0.299, 0.587, 0.114));
+ if (cost < bestCost) {
+ bestCost = cost;
+ motion = offset * 4.0;
+ }
+ }
+ }
+ // Fine refinement at full resolution (only if coarse search found something)
+ if (bestCost < 1e9) {
+ ivec2 coarseMotion = ivec2(motion * vec2(res));
+ for (int dy = -2; dy <= 2; ++dy) {
+ for (int dx = -2; dx <= 2; ++dx) {
+ ivec2 offset = coarseMotion + ivec2(dx, dy);
+ ivec2 blockCenterPrev = blockCenter + offset;
+ if (any(lessThan(blockCenterPrev, minBound)) || any(greaterThan(blockCenterPrev, maxBound)))
+ continue;
+ float sad = blockSADFast(blockCenter, blockCenterPrev, bSize);
+ if (sad < bestCost) {
+ bestCost = sad;
+ motion = vec2(offset) / vec2(res);
+ }
+ }
+ }
+ motionValid = (bestCost < ubuf.motionThreshold * 2.0);
+ }
+ }
+ }
+
+ // ---- Warping & hole filling ----
+ vec2 texelSize = 1.0 / vec2(res);
+ vec2 motionUV = motion;
+ vec2 halfTexel = texelSize * 0.5;
+
+ vec2 warpedUV = uv - motionUV * ubuf.blendFactor;
+ warpedUV = clamp(warpedUV, halfTexel, 1.0 - halfTexel);
+ vec3 warpedPrev = texture(previousFrame, warpedUV).rgb;
+
+ vec2 warpedCurrUV = uv + motionUV * (1.0 - ubuf.blendFactor);
+ warpedCurrUV = clamp(warpedCurrUV, halfTexel, 1.0 - halfTexel);
+ vec3 warpedCurr = texture(currentFrame, warpedCurrUV).rgb;
+
+ vec3 blended = mix(prev, curr, ubuf.blendFactor);
+ vec3 finalColor;
+
+ if (motionValid) {
+ vec3 centerWarpedPrev = texture(previousFrame, warpedUV).rgb;
+ float holeWeight = clamp(dot(abs(curr - centerWarpedPrev), vec3(0.299, 0.587, 0.114)) / 0.3, 0.0, 1.0);
+ vec3 motionCompensated = mix(warpedPrev, warpedCurr, holeWeight);
+ float confidence = 1.0 - clamp(bestCost / (ubuf.motionThreshold * 3.0), 0.0, 1.0);
+ finalColor = mix(blended, motionCompensated, confidence * 0.9);
+ } else {
+ finalColor = blended;
+ }
+
+ if (ubuf.debugMode != 0 && ubuf.isOriginalFrame == 0) {
+ finalColor *= vec3(1.0, 1.2, 1.0);
+ }
+
+ fragColor = vec4(finalColor, 1.0) * ubuf.qt_Opacity;
+}
\ No newline at end of file
diff --git a/modules/widgets/dashboard/wallpapers/interpol.frag.qsb b/modules/widgets/dashboard/wallpapers/interpol.frag.qsb
new file mode 100755
index 00000000..46c7fb26
Binary files /dev/null and b/modules/widgets/dashboard/wallpapers/interpol.frag.qsb differ
diff --git a/modules/widgets/dashboard/wallpapers/interpol.vert b/modules/widgets/dashboard/wallpapers/interpol.vert
new file mode 100755
index 00000000..ae795deb
--- /dev/null
+++ b/modules/widgets/dashboard/wallpapers/interpol.vert
@@ -0,0 +1,22 @@
+#version 440
+layout(location = 0) in vec4 qt_Vertex;
+layout(location = 1) in vec2 qt_MultiTexCoord0;
+layout(location = 0) out vec2 qt_TexCoord0;
+
+layout(std140, binding = 0) uniform buf {
+ mat4 qt_Matrix;
+ float qt_Opacity;
+ float blendFactor;
+ vec2 iResolution;
+ int blockSize;
+ int searchRadius;
+ float motionThreshold;
+ int debugMode;
+ int isOriginalFrame;
+ int frameCounter;
+} ubuf;
+
+void main() {
+ qt_TexCoord0 = qt_MultiTexCoord0;
+ gl_Position = ubuf.qt_Matrix * qt_Vertex;
+}
\ No newline at end of file
diff --git a/modules/widgets/dashboard/wallpapers/interpol.vert.qsb b/modules/widgets/dashboard/wallpapers/interpol.vert.qsb
new file mode 100755
index 00000000..1c67b18e
Binary files /dev/null and b/modules/widgets/dashboard/wallpapers/interpol.vert.qsb differ
diff --git a/modules/widgets/dashboard/wallpapers/mpvpaper.sh b/modules/widgets/dashboard/wallpapers/mpvpaper.sh
deleted file mode 100755
index edcb0dd4..00000000
--- a/modules/widgets/dashboard/wallpapers/mpvpaper.sh
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/env bash
-
-if [ -z "$1" ]; then
- echo "Use: $0 /path/to/wallpaper [shader_path] [monitor_target]"
- exit 1
-fi
-
-WALLPAPER="$1"
-SHADER="$2"
-MONITOR="${3:-ALL}"
-
-# When a specific monitor is targeted, we don't kill all mpvpaper instances,
-# just the one for that monitor if possible. However mpvpaper doesn't
-# natively support killing by monitor easily via pkill.
-# For now, we'll kill by checking the command line args if MONITOR != ALL.
-# We must avoid killing this script itself, so we filter by the exact executable name.
-if [ "$MONITOR" = "ALL" ]; then
- pkill -x "mpvpaper" 2>/dev/null
-else
- pgrep -x mpvpaper | while read -r pid; do
- if ps -p "$pid" -o args= | grep -q "$MONITOR"; then
- kill "$pid" 2>/dev/null
- fi
- done
-fi
-SOCKET="/tmp/ambxst_mpv_socket_${MONITOR}"
-
-MPV_OPTS="no-audio loop hwdec=auto scale=bilinear interpolation=no video-sync=display-resample panscan=1.0 video-scale-x=1.0 video-scale-y=1.0 load-scripts=no input-ipc-server=$SOCKET"
-
-# Si el shader no estΓ‘ vacΓo y el archivo existe, agregarlo a MPV_OPTS
-if [ -n "$SHADER" ] && [ -f "$SHADER" ]; then
- MPV_OPTS="$MPV_OPTS glsl-shaders=$SHADER"
-fi
-
-nohup mpvpaper -o "$MPV_OPTS" "$MONITOR" "$WALLPAPER" >/tmp/mpvpaper.log 2>&1 &
diff --git a/modules/widgets/dashboard/wallpapers/palette.frag b/modules/widgets/dashboard/wallpapers/palette.frag
old mode 100644
new mode 100755
index eabcd2eb..a468a8fa
--- a/modules/widgets/dashboard/wallpapers/palette.frag
+++ b/modules/widgets/dashboard/wallpapers/palette.frag
@@ -1,4 +1,10 @@
#version 440
+
+#ifdef GL_ES
+precision highp float;
+precision mediump int;
+#endif
+
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
@@ -8,7 +14,9 @@ layout(binding = 2) uniform sampler2D paletteTexture;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
- float paletteSize;
+ int paletteSize;
+ float sharpness;
+ float mixStrength;
float texWidth;
float texHeight;
} ubuf;
@@ -16,39 +24,41 @@ layout(std140, binding = 0) uniform buf {
void main() {
vec4 tex = texture(source, qt_TexCoord0);
vec3 color = tex.rgb;
-
- vec3 accumulatedColor = vec3(0.0);
- float totalWeight = 0.0;
- int size = int(ubuf.paletteSize);
+ if (tex.a < 0.001) {
+ fragColor = vec4(0.0);
+ return;
+ }
+
+ int size = ubuf.paletteSize;
+ if (size <= 0 || ubuf.mixStrength <= 0.0) {
+ fragColor = tex * ubuf.qt_Opacity;
+ return;
+ }
+
+ mediump vec3 accum = vec3(0.0);
+ mediump float sumW = 0.0;
- // "Sharpness" factor.
- // Higher value = colors stick closer to the palette (more posterized).
- // Lower value = colors blend more (more washed out/grey).
- // 15.0 - 20.0 is a good sweet spot for keeping identity while allowing gradients.
- float distributionSharpness = 20.0;
-
- for (int i = 0; i < 128; i++) {
+ const float invLn2 = 1.44269504;
+ float sharpness = ubuf.sharpness;
+
+ // Loop bound = 32 (max palette size is 26, gives margin)
+ // Previously 128 β the GLSL compiler would unroll ALL 128 iterations
+ // wasting GPU cycles on break checks. 32 fits in 1-2 warp/wavefront.
+ for (int i = 0; i < 32; ++i) {
if (i >= size) break;
- float u = (float(i) + 0.5) / ubuf.paletteSize;
- vec3 pColor = texture(paletteTexture, vec2(u, 0.5)).rgb;
-
+ vec3 pColor = texelFetch(paletteTexture, ivec2(i, 0), 0).rgb;
vec3 diff = color - pColor;
- // Euclidean squared distance
- float distSq = dot(diff, diff);
+ mediump float distSq = dot(diff, diff);
+ mediump float w = exp2(-sharpness * distSq * invLn2);
- // Gaussian Weighting function: e^(-k * d^2)
- // This creates a smooth bell curve of influence around each palette color.
- float weight = exp(-distributionSharpness * distSq);
-
- accumulatedColor += pColor * weight;
- totalWeight += weight;
+ accum += pColor * w;
+ sumW += w;
}
-
- // Normalize
- vec3 finalColor = accumulatedColor / (totalWeight + 0.00001); // Avoid div by zero
-
- // Pre-multiply alpha for proper blending in Qt Quick
- fragColor = vec4(finalColor * tex.a, tex.a) * ubuf.qt_Opacity;
-}
+
+ vec3 finalColor = accum / (sumW + 1e-5);
+ vec3 mixed = mix(color, finalColor, ubuf.mixStrength);
+
+ fragColor = vec4(mixed * tex.a, tex.a) * ubuf.qt_Opacity;
+}
\ No newline at end of file
diff --git a/modules/widgets/dashboard/wallpapers/palette.frag.qsb b/modules/widgets/dashboard/wallpapers/palette.frag.qsb
old mode 100644
new mode 100755
index 1b3548ee..0ec1c0a5
Binary files a/modules/widgets/dashboard/wallpapers/palette.frag.qsb and b/modules/widgets/dashboard/wallpapers/palette.frag.qsb differ
diff --git a/modules/widgets/dashboard/wallpapers/palette.vert b/modules/widgets/dashboard/wallpapers/palette.vert
old mode 100644
new mode 100755
index fbecaa9d..266347ef
--- a/modules/widgets/dashboard/wallpapers/palette.vert
+++ b/modules/widgets/dashboard/wallpapers/palette.vert
@@ -6,7 +6,9 @@ layout(location = 0) out vec2 qt_TexCoord0;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
- float paletteSize;
+ int paletteSize;
+ float sharpness;
+ float mixStrength;
float texWidth;
float texHeight;
} ubuf;
@@ -14,4 +16,4 @@ layout(std140, binding = 0) uniform buf {
void main() {
qt_TexCoord0 = qt_MultiTexCoord0;
gl_Position = ubuf.qt_Matrix * qt_Vertex;
-}
+}
\ No newline at end of file
diff --git a/modules/widgets/dashboard/wallpapers/palette.vert.qsb b/modules/widgets/dashboard/wallpapers/palette.vert.qsb
old mode 100644
new mode 100755
index a1bd0d0b..dde97c2f
Binary files a/modules/widgets/dashboard/wallpapers/palette.vert.qsb and b/modules/widgets/dashboard/wallpapers/palette.vert.qsb differ
diff --git a/modules/widgets/dashboard/widgets/ControlButton.qml b/modules/widgets/dashboard/widgets/ControlButton.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/widgets/FullPlayer.qml b/modules/widgets/dashboard/widgets/FullPlayer.qml
old mode 100644
new mode 100755
index 5c701ba5..8ed33a13
--- a/modules/widgets/dashboard/widgets/FullPlayer.qml
+++ b/modules/widgets/dashboard/widgets/FullPlayer.qml
@@ -203,7 +203,7 @@ StyledRect {
accentColor: Colors.primary
trackColor: Colors.outline
lineWidth: 6
- wavy: Config.performance.wavyLine
+ wavy: false
waveAmplitude: player.isPlaying ? 3 : 0
waveFrequency: 24
handleSpacing: 20
@@ -249,25 +249,7 @@ StyledRect {
fillMode: Image.PreserveAspectCrop
asynchronous: true
- // Placeholder (with WavyLine)
- Rectangle {
- anchors.fill: parent
- color: Colors.surface
- visible: !player.hasArtwork && player.wallpaperPath === ""
-
- Loader {
- active: parent.visible && Config.performance.wavyLine
- anchors.centerIn: parent
- width: parent.width * 0.6
- height: 20
- sourceComponent: WavyLine {
- anchors.fill: parent
- color: Colors.primary
- frequency: 2
- amplitudeMultiplier: 2
- }
- }
- }
+
}
}
diff --git a/modules/widgets/dashboard/widgets/LockPlayer.qml b/modules/widgets/dashboard/widgets/LockPlayer.qml
old mode 100644
new mode 100755
index cc1d673e..4aedc5c2
--- a/modules/widgets/dashboard/widgets/LockPlayer.qml
+++ b/modules/widgets/dashboard/widgets/LockPlayer.qml
@@ -111,24 +111,8 @@ StyledRect {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: 24
- sourceComponent: CarouselProgress {
- anchors.fill: parent
- frequency: 4
- color: Colors.surfaceBright
- amplitudeMultiplier: 4
- lineWidth: 2
- fullLength: width
- opacity: 1.0
- animationsEnabled: true
- active: true
-
- Behavior on color {
- enabled: Config.animDuration > 0
- ColorAnimation {
- duration: Config.animDuration
- easing.type: Easing.OutQuart
- }
- }
+ sourceComponent: Rectangle {
+ color: Qt.rgba(Colors.surfaceBright.r, Colors.surfaceBright.g, Colors.surfaceBright.b, 0.4)
}
}
}
diff --git a/modules/widgets/dashboard/widgets/NotificationHistory.qml b/modules/widgets/dashboard/widgets/NotificationHistory.qml
old mode 100644
new mode 100755
index 38797936..ea4fdd33
--- a/modules/widgets/dashboard/widgets/NotificationHistory.qml
+++ b/modules/widgets/dashboard/widgets/NotificationHistory.qml
@@ -210,10 +210,10 @@ Item {
Image {
mipmap: true
- source: Qt.resolvedUrl("../../../../assets/ambxst/ambxst-logo.svg")
- opacity: 0.25
- sourceSize.width: 64
- sourceSize.height: 64
+ source: Qt.resolvedUrl("../../../../assets/nothingless/nothingless-logo.svg")
+ opacity: 0.35
+ sourceSize.width: 160
+ sourceSize.height: 160
fillMode: Image.PreserveAspectFit
anchors.horizontalCenter: parent.horizontalCenter
layer.enabled: true
diff --git a/modules/widgets/dashboard/widgets/QuickControls.qml b/modules/widgets/dashboard/widgets/QuickControls.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/widgets/WeatherWidget.qml b/modules/widgets/dashboard/widgets/WeatherWidget.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/widgets/WidgetsTab.qml b/modules/widgets/dashboard/widgets/WidgetsTab.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/widgets/calendar/Calendar.qml b/modules/widgets/dashboard/widgets/calendar/Calendar.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/widgets/calendar/CalendarDayButton.qml b/modules/widgets/dashboard/widgets/calendar/CalendarDayButton.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/dashboard/widgets/calendar/layout.js b/modules/widgets/dashboard/widgets/calendar/layout.js
old mode 100644
new mode 100755
diff --git a/modules/widgets/defaultview/CompactPlayer.qml b/modules/widgets/defaultview/CompactPlayer.qml
old mode 100644
new mode 100755
index 265b87e8..67da0680
--- a/modules/widgets/defaultview/CompactPlayer.qml
+++ b/modules/widgets/defaultview/CompactPlayer.qml
@@ -10,6 +10,7 @@ import qs.modules.theme
import qs.modules.bar.workspaces
import qs.modules.services
import qs.modules.components
+import qs.modules.globals
import qs.config
Item {
@@ -76,7 +77,7 @@ Item {
const displayType = Config.notch.noMediaDisplay ?? "userHost";
if (displayType === "userHost") return userHostText;
if (displayType === "compositor") return "AxctlService";
- return Config.notch.customText ?? "Ambxst";
+ return Config.notch.customText ?? "NothingLess";
}
readonly property string displayedTitle: {
diff --git a/modules/widgets/defaultview/DefaultView.qml b/modules/widgets/defaultview/DefaultView.qml
old mode 100644
new mode 100755
index c75a7ad2..48016a7b
--- a/modules/widgets/defaultview/DefaultView.qml
+++ b/modules/widgets/defaultview/DefaultView.qml
@@ -65,8 +65,96 @@ Item {
}
}
+ // Metrics mode
+ readonly property bool metricsActive: Config.notch && Config.notch.showMetrics === true
+
+ // Dynamic notch metrics model (ordered by notchMetricsOrder from StateService)
+ property ListModel notchMetrics: ListModel {}
+ property var notchOrder: []
+
+ function rebuildNotchMetrics() {
+ notchMetrics.clear()
+ var order = StateService.get("notchMetricsOrder", ["cpu","gpu","fps","ram","disk"])
+ notchOrder = order
+ var items = {}
+
+ // Unified CPU: temp + usage % + power
+ items.cpu = {
+ id: "cpu", label: "CPU",
+ visible: SystemResources.cpuUsageEnabled || SystemResources.cpuTempEnabled || SystemResources.cpuPowerEnabled,
+ labelColor: SystemResources.metricColorCpu,
+ valueText: (SystemResources.metricsAvailable && SystemResources.cpuTempEnabled && SystemResources.cpuTemp > 0) ? SystemResources.cpuTemp.toString() : (SystemResources.metricsAvailable && SystemResources.cpuUsageEnabled) ? Math.round(SystemResources.cpuUsage).toString() : "--",
+ valueUnit: (SystemResources.metricsAvailable && SystemResources.cpuTempEnabled && SystemResources.cpuTemp > 0) ? "Β°C" : (SystemResources.metricsAvailable && SystemResources.cpuUsageEnabled) ? "%" : "",
+ subValue: (SystemResources.metricsAvailable && SystemResources.cpuPowerEnabled && SystemResources.cpuPower > 0) ? SystemResources.cpuPower.toFixed(0) : "",
+ subUnit: (SystemResources.metricsAvailable && SystemResources.cpuPowerEnabled && SystemResources.cpuPower > 0) ? "W" : ""
+ }
+ // Unified GPU: temp + usage % + power
+ items.gpu = {
+ id: "gpu", label: "GPU",
+ visible: SystemResources.gpuUsageEnabled || SystemResources.gpuTempEnabled || SystemResources.gpuPowerEnabled,
+ labelColor: SystemResources.metricColorGpu,
+ valueText: (SystemResources.metricsAvailable && SystemResources.gpuTempEnabled && SystemResources.gpuTemp > 0) ? SystemResources.gpuTemp.toString() : (SystemResources.metricsAvailable && SystemResources.gpuUsageEnabled && SystemResources.gpuUsages.length > 0) ? Math.round(SystemResources.gpuUsages[0]).toString() : "--",
+ valueUnit: (SystemResources.metricsAvailable && SystemResources.gpuTempEnabled && SystemResources.gpuTemp > 0) ? "Β°C" : (SystemResources.metricsAvailable && SystemResources.gpuUsageEnabled) ? "%" : "",
+ subValue: (SystemResources.metricsAvailable && SystemResources.gpuPowerEnabled && SystemResources.gpuPower > 0) ? SystemResources.gpuPower.toFixed(0) : "",
+ subUnit: (SystemResources.metricsAvailable && SystemResources.gpuPowerEnabled && SystemResources.gpuPower > 0) ? "W" : ""
+ }
+ // FPS
+ items.fps = {
+ id: "fps", label: "FPS", visible: SystemResources.fpsEnabled,
+ labelColor: SystemResources.metricColorFps,
+ valueText: (SystemResources.metricsAvailable && SystemResources.fpsEnabled && SystemResources.fps > 0) ? Math.round(SystemResources.fps).toString() : "--",
+ valueUnit: "", subValue: "", subUnit: ""
+ }
+ // RAM
+ items.ram = {
+ id: "ram", label: "RAM", visible: SystemResources.ramEnabled,
+ labelColor: SystemResources.metricColorRam,
+ valueText: (SystemResources.metricsAvailable && SystemResources.ramEnabled && SystemResources.ramUsage > 0) ? Math.round(SystemResources.ramUsage).toString() : "--",
+ valueUnit: (SystemResources.metricsAvailable && SystemResources.ramEnabled && SystemResources.ramUsage > 0) ? "%" : "",
+ subValue: "", subUnit: ""
+ }
+ // Disk
+ items.disk = {
+ id: "disk", label: "DSK", visible: SystemResources.diskEnabled,
+ labelColor: SystemResources.metricColorDisk,
+ valueText: (SystemResources.metricsAvailable && SystemResources.diskEnabled && SystemResources.validDisks.length > 0 && SystemResources.diskUsage[SystemResources.validDisks[0]]) ? Math.round(SystemResources.diskUsage[SystemResources.validDisks[0]]).toString() : "--",
+ valueUnit: (SystemResources.metricsAvailable && SystemResources.diskEnabled && SystemResources.validDisks.length > 0) ? "%" : "",
+ subValue: "", subUnit: ""
+ }
+
+ for (var i = 0; i < order.length; i++) {
+ var it = items[order[i]]
+ if (it) notchMetrics.append(it)
+ }
+ }
+
+ // Rebuild when any toggle or color changes
+ Connections {
+ target: SystemResources
+ function onCpuUsageEnabledChanged() { rebuildNotchMetrics() }
+ function onCpuTempEnabledChanged() { rebuildNotchMetrics() }
+ function onCpuPowerEnabledChanged() { rebuildNotchMetrics() }
+ function onRamEnabledChanged() { rebuildNotchMetrics() }
+ function onGpuUsageEnabledChanged() { rebuildNotchMetrics() }
+ function onGpuTempEnabledChanged() { rebuildNotchMetrics() }
+ function onGpuPowerEnabledChanged() { rebuildNotchMetrics() }
+ function onFpsEnabledChanged() { rebuildNotchMetrics() }
+ function onDiskEnabledChanged() { rebuildNotchMetrics() }
+ function onMetricColorCpuChanged() { rebuildNotchMetrics() }
+ function onMetricColorGpuChanged() { rebuildNotchMetrics() }
+ function onMetricColorFpsChanged() { rebuildNotchMetrics() }
+ function onMetricColorRamChanged() { rebuildNotchMetrics() }
+ function onMetricColorDiskChanged() { rebuildNotchMetrics() }
+ function onNotchVersionChanged() { rebuildNotchMetrics() }
+ }
+
+ Component.onCompleted: rebuildNotchMetrics()
+ readonly property real metricsRowWidth: (metricsActive && metricsModeRow.visible) ? metricsModeRow.implicitWidth : 0
+
// Computed dimensions
- readonly property real mainRowContentWidth: 200 + userInfo.width + separator1.width + separator2.width + notifIndicator.width + (mainRow.spacing * 4) + mainRowMargin
+ readonly property real mainRowContentWidth: metricsActive
+ ? Math.max(metricsRowWidth + mainRowMargin, 200)
+ : (200 + userInfo.width + separator1.width + separator2.width + notifIndicator.width + (mainRow.spacing * 4) + mainRowMargin)
readonly property real mainRowHeight: Config.showBackground ? (Config.notchTheme === "island" ? 36 : 44) : (Config.notchTheme === "island" ? 36 : 40)
readonly property real notificationMinWidth: expandedState ? 420 : 320
readonly property real notificationContainerHeight: notificationView.implicitHeight + notificationPaddingTop + notificationPaddingBottom
@@ -129,9 +217,75 @@ Item {
Item {
anchors.fill: parent
- // mainRow container
+ // Metrics mode content (replaces mainRow when showMetrics is active)
+ Row {
+ id: metricsModeRow
+ visible: metricsActive
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.verticalCenter: parent.verticalCenter
+ height: mainRowHeight
+ spacing: 14
+ leftPadding: 6
+ rightPadding: 6
+ z: 3
+
+ MetricsGroup {
+ visible: SystemResources.cpuUsageEnabled || SystemResources.cpuTempEnabled || SystemResources.cpuPowerEnabled
+ label: "CPU"
+ labelColor: SystemResources.metricColorCpu
+ valueText: (SystemResources.metricsAvailable && SystemResources.cpuTempEnabled && SystemResources.cpuTemp > 0) ? (SystemResources.cpuTemp.toString() + (SystemResources.metricsAvailable && SystemResources.cpuUsageEnabled ? "Β° " + Math.round(SystemResources.cpuUsage).toString() + "%" : "Β°C")) : (SystemResources.metricsAvailable && SystemResources.cpuUsageEnabled) ? Math.round(SystemResources.cpuUsage).toString() + "%" : "--"
+ valueUnit: ""
+ subValue: (SystemResources.metricsAvailable && SystemResources.cpuPowerEnabled && SystemResources.cpuPower > 0) ? SystemResources.cpuPower.toFixed(0) : ""
+ subUnit: (SystemResources.metricsAvailable && SystemResources.cpuPowerEnabled && SystemResources.cpuPower > 0) ? "W" : ""
+ }
+
+ MetricsGroup {
+ visible: SystemResources.gpuUsageEnabled || SystemResources.gpuTempEnabled || SystemResources.gpuPowerEnabled
+ label: "GPU"
+ labelColor: SystemResources.metricColorGpu
+ valueText: (SystemResources.metricsAvailable && SystemResources.gpuTempEnabled && SystemResources.gpuTemp > 0) ? (SystemResources.gpuTemp.toString() + (SystemResources.metricsAvailable && SystemResources.gpuUsageEnabled && SystemResources.gpuUsages.length > 0 ? "Β° " + Math.round(SystemResources.gpuUsages[0]).toString() + "%" : "Β°C")) : (SystemResources.metricsAvailable && SystemResources.gpuUsageEnabled && SystemResources.gpuUsages.length > 0) ? Math.round(SystemResources.gpuUsages[0]).toString() + "%" : "--"
+ valueUnit: ""
+ subValue: (SystemResources.metricsAvailable && SystemResources.gpuPowerEnabled && SystemResources.gpuPower > 0) ? SystemResources.gpuPower.toFixed(0) : ""
+ subUnit: (SystemResources.metricsAvailable && SystemResources.gpuPowerEnabled && SystemResources.gpuPower > 0) ? "W" : ""
+ }
+
+
+
+ MetricsGroup {
+ visible: SystemResources.ramEnabled
+ label: "RAM"
+ labelColor: SystemResources.metricColorRam
+ valueText: (SystemResources.metricsAvailable && SystemResources.ramEnabled && SystemResources.ramUsage > 0) ? Math.round(SystemResources.ramUsage).toString() + "%" : "--"
+ valueUnit: ""
+ subValue: ""
+ subUnit: ""
+ }
+
+ MetricsGroup {
+ visible: SystemResources.diskEnabled
+ label: "DSK"
+ labelColor: SystemResources.metricColorDisk
+ valueText: (SystemResources.metricsAvailable && SystemResources.diskEnabled && SystemResources.validDisks.length > 0 && SystemResources.diskUsage[SystemResources.validDisks[0]]) ? Math.round(SystemResources.diskUsage[SystemResources.validDisks[0]]).toString() + "%" : "--"
+ valueUnit: ""
+ subValue: ""
+ subUnit: ""
+ }
+
+ MetricsGroup {
+ visible: SystemResources.fpsEnabled
+ label: "FPS"
+ labelColor: SystemResources.metricColorFps
+ valueText: (SystemResources.metricsAvailable && SystemResources.fpsEnabled && SystemResources.fps > 0) ? Math.round(SystemResources.fps).toString() : "--"
+ valueUnit: ""
+ subValue: ""
+ subUnit: ""
+ }
+ }
+
+ // mainRow container (hidden when metrics mode is active)
Row {
id: mainRow
+ visible: !metricsActive
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: isBottom ? undefined : parent.top
anchors.bottom: isBottom ? parent.bottom : undefined
diff --git a/modules/widgets/defaultview/MetricsGroup.qml b/modules/widgets/defaultview/MetricsGroup.qml
new file mode 100755
index 00000000..dda21459
--- /dev/null
+++ b/modules/widgets/defaultview/MetricsGroup.qml
@@ -0,0 +1,81 @@
+import QtQuick
+
+/**
+ * Individual metrics group in the notch metrics overlay.
+ * Shows a colored dot, label, value with small unit, optional sub value/unit.
+ * e.g. "CPU 51Β°C 40W" β label=CPU, valueText=51, valueUnit=Β°C, subValue=40, subUnit=W
+ */
+Item {
+ id: root
+
+ required property string label
+ required property color labelColor
+ property string valueText: ""
+ property string valueUnit: ""
+ property string subValue: ""
+ property string subUnit: ""
+
+ implicitHeight: parent ? parent.height : 32
+ implicitWidth: innerRow.implicitWidth
+
+ Row {
+ id: innerRow
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: parent.left
+ spacing: 5
+
+ // Label
+ Text {
+ text: root.label
+ color: root.labelColor
+ font.pixelSize: 11
+ font.weight: Font.Bold
+ font.family: "sans-serif"
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ // Value number (large)
+ Text {
+ text: root.valueText
+ color: "#FFFFFF"
+ font.pixelSize: 12
+ font.weight: Font.DemiBold
+ font.family: "sans-serif"
+ anchors.verticalCenter: parent.verticalCenter
+ visible: root.valueText !== ""
+ }
+
+ // Value unit (small, e.g. Β°C)
+ Text {
+ text: root.valueUnit
+ color: "#CCFFFFFF"
+ font.pixelSize: 8
+ font.weight: Font.Normal
+ font.family: "sans-serif"
+ anchors.verticalCenter: parent.verticalCenter
+ visible: root.valueUnit !== ""
+ }
+
+ // Sub value (large, e.g. watts)
+ Text {
+ text: root.subValue
+ color: "#FFFFFF"
+ font.pixelSize: 12
+ font.weight: Font.DemiBold
+ font.family: "sans-serif"
+ anchors.verticalCenter: parent.verticalCenter
+ visible: root.subValue !== ""
+ }
+
+ // Sub unit (small, e.g. W)
+ Text {
+ text: root.subUnit
+ color: "#CCFFFFFF"
+ font.pixelSize: 8
+ font.weight: Font.Normal
+ font.family: "sans-serif"
+ anchors.verticalCenter: parent.verticalCenter
+ visible: root.subUnit !== ""
+ }
+ }
+}
diff --git a/modules/widgets/defaultview/NotificationIndicator.qml b/modules/widgets/defaultview/NotificationIndicator.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/defaultview/UserInfo.qml b/modules/widgets/defaultview/UserInfo.qml
old mode 100644
new mode 100755
index 1cfad227..032da7f9
--- a/modules/widgets/defaultview/UserInfo.qml
+++ b/modules/widgets/defaultview/UserInfo.qml
@@ -64,6 +64,8 @@ Item {
anchors.fill: parent
source: `file://${Quickshell.env("HOME")}/.face.icon`
fillMode: Image.PreserveAspectCrop
+ sourceSize.width: 24
+ sourceSize.height: 24
}
}
}
diff --git a/modules/widgets/launcher/AGENTS.md b/modules/widgets/launcher/AGENTS.md
old mode 100644
new mode 100755
diff --git a/modules/widgets/launcher/LauncherView.qml b/modules/widgets/launcher/LauncherView.qml
old mode 100644
new mode 100755
index 9af6e933..ea507adb
--- a/modules/widgets/launcher/LauncherView.qml
+++ b/modules/widgets/launcher/LauncherView.qml
@@ -227,9 +227,12 @@ Rectangle {
function executeApp(appId) {
let app = appsById[appId];
- if (app && app.execute) {
- app.execute();
- // Record usage for sorting priority
+ if (app) {
+ // Delegate to AppSearch.launchApp for consistency with DockAppButton.
+ // app.execute() (Quickshell DesktopEntry) does not handle Terminal=true
+ // entries: TUI apps (btop, htop, nvim, ranger, etc.) launched from the
+ // drawer fail silently because the Exec is run without a terminal wrapper.
+ AppSearch.launchApp(app);
UsageTracker.recordUsage(appId);
}
}
diff --git a/modules/widgets/launcher/qmldir b/modules/widgets/launcher/qmldir
old mode 100644
new mode 100755
diff --git a/modules/widgets/overview/AGENTS.md b/modules/widgets/overview/AGENTS.md
old mode 100644
new mode 100755
diff --git a/modules/widgets/overview/Overview.qml b/modules/widgets/overview/Overview.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/overview/OverviewButton.qml b/modules/widgets/overview/OverviewButton.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/overview/OverviewPopup.qml b/modules/widgets/overview/OverviewPopup.qml
old mode 100644
new mode 100755
index 6ad80557..62c319a4
--- a/modules/widgets/overview/OverviewPopup.qml
+++ b/modules/widgets/overview/OverviewPopup.qml
@@ -24,7 +24,7 @@ PanelWindow {
color: "transparent"
WlrLayershell.layer: WlrLayer.Overlay
- WlrLayershell.namespace: "ambxst:overview"
+ WlrLayershell.namespace: "nothingless:overview"
WlrLayershell.keyboardFocus: overviewOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
// Get this screen's visibility state
diff --git a/modules/widgets/overview/OverviewView.qml b/modules/widgets/overview/OverviewView.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/overview/OverviewWindow.qml b/modules/widgets/overview/OverviewWindow.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/overview/ScrollingOverview.qml b/modules/widgets/overview/ScrollingOverview.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/overview/ScrollingWorkspace.qml b/modules/widgets/overview/ScrollingWorkspace.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/powermenu/PowerButton.qml b/modules/widgets/powermenu/PowerButton.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/powermenu/PowerMenu.qml b/modules/widgets/powermenu/PowerMenu.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/powermenu/PowerMenuView.qml b/modules/widgets/powermenu/PowerMenuView.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/presets/PresetsButton.qml b/modules/widgets/presets/PresetsButton.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/presets/PresetsPopup.qml b/modules/widgets/presets/PresetsPopup.qml
old mode 100644
new mode 100755
index 5afcc42b..a99f7386
--- a/modules/widgets/presets/PresetsPopup.qml
+++ b/modules/widgets/presets/PresetsPopup.qml
@@ -24,7 +24,7 @@ PanelWindow {
color: "transparent"
WlrLayershell.layer: WlrLayer.Overlay
- WlrLayershell.namespace: "ambxst:presets"
+ WlrLayershell.namespace: "nothingless:presets"
WlrLayershell.keyboardFocus: presetsOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
// Get this screen's visibility state
diff --git a/modules/widgets/presets/PresetsTab.qml b/modules/widgets/presets/PresetsTab.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/tools/ToolsMenu.qml b/modules/widgets/tools/ToolsMenu.qml
old mode 100644
new mode 100755
diff --git a/modules/widgets/tools/ToolsMenuView.qml b/modules/widgets/tools/ToolsMenuView.qml
old mode 100644
new mode 100755
diff --git a/nix/lib.nix b/nix/lib.nix
old mode 100644
new mode 100755
index 814cf618..e9131757
--- a/nix/lib.nix
+++ b/nix/lib.nix
@@ -1,4 +1,4 @@
-# Utility functions for Ambxst flake
+# Utility functions for NothingLess flake
{ nixpkgs }:
let
diff --git a/nix/modules/default.nix b/nix/modules/default.nix
old mode 100644
new mode 100755
index d1837278..020ca413
--- a/nix/modules/default.nix
+++ b/nix/modules/default.nix
@@ -1,21 +1,21 @@
-# NixOS module for Ambxst
+# NixOS module for NothingLess
{ config, lib, pkgs, ... }:
let
- cfg = config.programs.ambxst;
+ cfg = config.programs.nothingless;
in {
- options.programs.ambxst = {
- enable = lib.mkEnableOption "Ambxst shell";
+ options.programs.nothingless = {
+ enable = lib.mkEnableOption "NothingLess shell";
package = lib.mkOption {
type = lib.types.package;
- description = "The Ambxst package to use";
+ description = "The NothingLess package to use";
};
fonts.enable = lib.mkOption {
type = lib.types.bool;
default = true;
- description = "Whether to install Ambxst fonts (including Phosphor Icons)";
+ description = "Whether to install NothingLess fonts (including Phosphor Icons)";
};
};
diff --git a/nix/packages/apps.nix b/nix/packages/apps.nix
old mode 100644
new mode 100755
diff --git a/nix/packages/core.nix b/nix/packages/core.nix
old mode 100644
new mode 100755
diff --git a/nix/packages/default.nix b/nix/packages/default.nix
old mode 100644
new mode 100755
index 6d620894..5db2d115
--- a/nix/packages/default.nix
+++ b/nix/packages/default.nix
@@ -1,10 +1,23 @@
-# Main Ambxst package
-{ pkgs, lib, self, system, axctl }:
+# Main NothingLess package
+{
+ pkgs,
+ lib,
+ self,
+ system,
+ axctl,
+ version,
+}:
let
- quickshellPkg = pkgs.quickshell;
axctlPkg = axctl.packages.${system}.default;
-
+ quickshellPkg = pkgs.quickshell.overrideAttrs (old: {
+ buildInputs = (old.buildInputs or [ ]) ++ [
+ pkgs.kdePackages.kirigami
+ pkgs.kdePackages.kirigami-addons
+ pkgs.kdePackages.qqc2-desktop-style
+ pkgs.kdePackages.syntax-highlighting
+ ];
+ });
# Import sub-packages
ttf-phosphor-icons = import ./phosphor-icons.nix { inherit pkgs; };
@@ -17,32 +30,27 @@ let
tesseractPkgs = import ./tesseract.nix { inherit pkgs; };
# Combine all packages (NixOS-specific deps handled by the module)
- baseEnv = corePkgs
- ++ [ axctlPkg ]
- ++ toolsPkgs
- ++ mediaPkgs
- ++ appsPkgs
- ++ fontsPkgs
- ++ tesseractPkgs;
+ baseEnv =
+ corePkgs ++ [ axctlPkg ] ++ toolsPkgs ++ mediaPkgs ++ appsPkgs ++ fontsPkgs ++ tesseractPkgs;
- envAmbxst = pkgs.buildEnv {
- name = "Ambxst-env";
+ envNothingLess = pkgs.buildEnv {
+ name = "NothingLess-env";
paths = baseEnv;
};
# Create fontconfig configuration to find bundled fonts
- fontconfigConf = pkgs.writeTextDir "etc/fonts/conf.d/99-ambxst-fonts.conf" ''
+ fontconfigConf = pkgs.writeTextDir "etc/fonts/conf.d/99-nothingless-fonts.conf" ''
- ${envAmbxst}/share/fonts
+ ${envNothingLess}/share/fonts
'';
# Copy shell sources to the Nix store
shellSrc = pkgs.stdenv.mkDerivation {
- pname = "ambxst-shell";
- version = lib.removeSuffix "\n" (builtins.readFile ../../version);
+ pname = "nothingless-shell";
+ inherit version;
src = lib.cleanSource self;
dontBuild = true;
installPhase = ''
@@ -51,12 +59,12 @@ let
'';
};
- launcher = pkgs.writeShellScriptBin "ambxst" ''
- export AMBXST_QS="${quickshellPkg}/bin/qs"
- export PATH="${envAmbxst}/bin:$PATH"
+ launcher = pkgs.writeShellScriptBin "nothingless" ''
+ export NOTHINGLESS_QS="${quickshellPkg}/bin/qs"
+ export PATH="${envNothingLess}/bin:$PATH"
- # Set QML2_IMPORT_PATH to include modules from envAmbxst (like syntax-highlighting)
- export QML2_IMPORT_PATH="${envAmbxst}/lib/qt-6/qml:$QML2_IMPORT_PATH"
+ # Set QML2_IMPORT_PATH to include modules from envNothingLess (like syntax-highlighting)
+ export QML2_IMPORT_PATH="${envNothingLess}/lib/qt-6/qml:$QML2_IMPORT_PATH"
export QML_IMPORT_PATH="$QML2_IMPORT_PATH"
# Make bundled fonts available to fontconfig
@@ -66,8 +74,12 @@ let
exec ${shellSrc}/cli.sh "$@"
'';
-in pkgs.buildEnv {
- name = "Ambxst";
- paths = [ envAmbxst launcher ];
- meta.mainProgram = "ambxst";
+in
+pkgs.buildEnv {
+ name = "NothingLess-${version}";
+ paths = [
+ envNothingLess
+ launcher
+ ];
+ meta.mainProgram = "nothingless";
}
diff --git a/nix/packages/fonts.nix b/nix/packages/fonts.nix
old mode 100644
new mode 100755
diff --git a/nix/packages/media.nix b/nix/packages/media.nix
old mode 100644
new mode 100755
diff --git a/nix/packages/phosphor-icons.nix b/nix/packages/phosphor-icons.nix
old mode 100644
new mode 100755
diff --git a/nix/packages/tesseract.nix b/nix/packages/tesseract.nix
old mode 100644
new mode 100755
diff --git a/nix/packages/tools.nix b/nix/packages/tools.nix
old mode 100644
new mode 100755
diff --git a/scripts/AGENTS.md b/scripts/AGENTS.md
old mode 100644
new mode 100755
diff --git a/scripts/ambfps-launcher.sh b/scripts/ambfps-launcher.sh
new file mode 100755
index 00000000..f2c9c4da
--- /dev/null
+++ b/scripts/ambfps-launcher.sh
@@ -0,0 +1,73 @@
+#!/usr/bin/env bash
+# nothingless-fps β Launch any program with built-in FPS monitoring
+#
+# Sets LD_PRELOAD=libambfps.so so the game's frame presents are
+# intercepted and FPS is written to /dev/shm/nothingless_fps.
+# NothingLess's fps_monitor.py reads that file and shows FPS in the notch.
+#
+# The library only activates when nothingless-fps=1 is in the environment,
+# which this script also sets automatically.
+#
+# Usage:
+# nothingless-fps ./my-game
+# nothingless-fps steam steam://rungameid/730
+# nothingless-fps %command% (Steam launch options)
+#
+# Env vars:
+# nothingless-fps=1 Set automatically by this script
+# AMBXST_FPS_LIB Override library path (backward compat)
+# NOTHINGLESS_FPS_LIB Override library path
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+# ββ Locate libambfps.so ββββββββββββββββββββββββββββββββββββββββββ
+# Search order: env override > next to script > standard install paths
+# Check NOTHINGLESS_FPS_LIB first, then fall back to AMBXST_FPS_LIB
+if [ -n "${NOTHINGLESS_FPS_LIB:-}" ] && [ -f "$NOTHINGLESS_FPS_LIB" ]; then
+ AMBFPS_LIB="$NOTHINGLESS_FPS_LIB"
+elif [ -n "${AMBXST_FPS_LIB:-}" ] && [ -f "$AMBXST_FPS_LIB" ]; then
+ AMBFPS_LIB="$AMBXST_FPS_LIB"
+elif [ -f "$SCRIPT_DIR/libambfps.so" ]; then
+ AMBFPS_LIB="$SCRIPT_DIR/libambfps.so"
+elif [ -f "$HOME/.local/lib/libambfps.so" ]; then
+ AMBFPS_LIB="$HOME/.local/lib/libambfps.so"
+elif [ -f "/usr/local/lib/libambfps.so" ]; then
+ AMBFPS_LIB="/usr/local/lib/libambfps.so"
+elif libambfps="$(command -v libambfps.so 2>/dev/null)"; then
+ AMBFPS_LIB="$libambfps"
+else
+ echo "nothingless-fps: libambfps.so not found." >&2
+ echo " Compile: gcc -shared -fPIC -O2 -o libambfps.so fps_preload.c -lm -ldl" >&2
+ echo " Install: cp libambfps.so ~/.local/lib/" >&2
+ echo " Or run: nothingless install" >&2
+ exit 1
+fi
+
+if [ $# -eq 0 ]; then
+ echo "Usage: nothingless-fps [args...]" >&2
+ echo "" >&2
+ echo " Launch a program with built-in FPS monitoring." >&2
+ echo " FPS will appear in the NothingLess notch (enable metrics view)." >&2
+ echo "" >&2
+ echo "Examples:" >&2
+ echo " nothingless-fps ./my-game" >&2
+ echo " nothingless-fps steam steam://rungameid/730" >&2
+ echo " nothingless-fps vkcube" >&2
+ exit 1
+fi
+
+# ββ Activate FPS interception ββββββββββββββββββββββββββββββββββββ
+# NOTHINGLESS_FPS is the underscore variant (POSIX shell compatible).
+# libambfps.so checks both NOTHINGLESS_FPS, AMBXST_FPS and nothingless-fps env vars.
+NOTHINGLESS_FPS=1
+AMBXST_FPS=1
+LD_PRELOAD="$AMBFPS_LIB${LD_PRELOAD:+:$LD_PRELOAD}"
+export NOTHINGLESS_FPS AMBXST_FPS LD_PRELOAD
+
+# ββ Ensure /dev/shm is writable ββββββββββββββββββββββββββββββββββ
+mkdir -p /dev/shm 2>/dev/null || true
+
+# ββ Launch the game ββββββββββββββββββββββββββββββββββββββββββββββ
+exec "$@"
diff --git a/scripts/bluetooth_helper.py b/scripts/bluetooth_helper.py
new file mode 100755
index 00000000..ff79ce2b
--- /dev/null
+++ b/scripts/bluetooth_helper.py
@@ -0,0 +1,209 @@
+#!/usr/bin/env python3
+"""
+Bluetooth helper for NothingLess.
+Uses PTY-based bluetoothctl for reliable device discovery.
+
+Commands: power on|off|status / scan find [secs] / devices / info / connect / disconnect / pair / trust / remove
+"""
+
+import sys, json, subprocess, os, pty, time, re, select, threading
+
+
+def run_btctl(*args, timeout=10):
+ cmd = ["bluetoothctl", "--"] + list(args)
+ try:
+ r = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
+ return r.stdout.strip(), r.stderr.strip(), r.returncode
+ except subprocess.TimeoutExpired:
+ return "", "timeout", 124
+ except FileNotFoundError:
+ return "", "bluetoothctl not found", 127
+
+
+def parse_devices(text):
+ devs = []
+ for line in text.split("\n"):
+ line = line.strip()
+ if line.startswith("Device "):
+ p = line.split(" ", 2)
+ if len(p) >= 3:
+ devs.append({"address": p[1], "name": p[2], "alias": p[2],
+ "paired": True, "connected": False, "trusted": False,
+ "icon": "bluetooth", "rssi": 0})
+ return devs
+
+
+def parse_info(text, addr):
+ info = {"address": addr, "name": "Unknown", "alias": "Unknown",
+ "paired": False, "connected": False, "trusted": False,
+ "icon": "bluetooth", "rssi": 0}
+ for line in text.split("\n"):
+ line = line.strip()
+ if line.startswith("Name: "): info["name"] = line[6:]
+ elif line.startswith("Alias: "): info["alias"] = line[7:]
+ elif line.startswith("Paired: "): info["paired"] = "yes" in line
+ elif line.startswith("Connected: "): info["connected"] = "yes" in line
+ elif line.startswith("Trusted: "): info["trusted"] = "yes" in line
+ elif line.startswith("Icon: "): info["icon"] = line[6:]
+ return info
+
+
+def cmd_power(action):
+ if action == "status":
+ out, _, _ = run_btctl("show")
+ print(json.dumps({"powered": "Powered: yes" in out}))
+ elif action in ("on", "off"):
+ run_btctl("power", action)
+ out, _, _ = run_btctl("show")
+ print(json.dumps({"powered": "Powered: yes" in out}))
+
+
+def cmd_devices():
+ out, _, _ = run_btctl("devices")
+ devs = parse_devices(out)
+ out2, _, _ = run_btctl("devices", "Connected")
+ for line in out2.split("\n"):
+ if line.strip().startswith("Device "):
+ parts = line.strip().split(" ", 2)
+ if len(parts) >= 2:
+ for d in devs:
+ if d["address"] == parts[1]:
+ d["connected"] = True
+ print(json.dumps(devs))
+
+
+def cmd_info(addr):
+ out, _, _ = run_btctl("info", addr)
+ print(json.dumps(parse_info(out, addr)))
+
+
+def cmd_connect(addr):
+ out, err, rc = run_btctl("connect", addr)
+ print(json.dumps({"connected": rc == 0, "error": err or None}))
+
+
+def cmd_disconnect(addr):
+ out, err, rc = run_btctl("disconnect", addr)
+ print(json.dumps({"connected": False, "error": err or None}))
+
+
+def cmd_pair(addr):
+ out, err, rc = run_btctl("pair", addr)
+ print(json.dumps({"paired": rc == 0, "error": err or None}))
+
+
+def cmd_trust(addr):
+ out, err, rc = run_btctl("trust", addr)
+ print(json.dumps({"trusted": rc == 0, "error": err or None}))
+
+
+def cmd_remove(addr):
+ out, err, rc = run_btctl("remove", addr)
+ print(json.dumps({"removed": rc == 0, "error": err or None}))
+
+
+# ββ PTY-based scan: spawns bluetoothctl with pseudo-terminal ββ
+def cmd_scan_find(duration=8):
+ try: duration = int(duration)
+ except: duration = 8
+ duration = max(3, min(duration, 30))
+
+ discovered = {}
+ new_re = re.compile(r"\[NEW\]\s+Device\s+([0-9A-Fa-f:]{17})\s+(.*)")
+
+ try:
+ master_fd, slave_fd = pty.openpty()
+ proc = subprocess.Popen(
+ ["bluetoothctl"],
+ stdin=slave_fd, stdout=slave_fd, stderr=slave_fd,
+ close_fds=True, preexec_fn=os.setsid
+ )
+ os.close(slave_fd)
+
+ time.sleep(0.3)
+ os.write(master_fd, b"power on\n")
+ time.sleep(0.2)
+ os.write(master_fd, b"scan on\n")
+
+ deadline = time.time() + duration
+ buf = b""
+ while time.time() < deadline:
+ r, _, _ = select.select([master_fd], [], [], 0.5)
+ if r:
+ try:
+ data = os.read(master_fd, 4096)
+ if not data: break
+ buf += data
+ lines = buf.split(b"\n")
+ buf = lines.pop()
+ for line in lines:
+ m = new_re.match(line.decode(errors="replace").strip())
+ if m:
+ discovered[m.group(1).upper()] = {
+ "address": m.group(1).upper(),
+ "name": m.group(2).strip()
+ }
+ except OSError: break
+
+ os.write(master_fd, b"scan off\nquit\n")
+ time.sleep(0.5)
+ try: os.close(master_fd)
+ except: pass
+ try: proc.wait(timeout=3)
+ except: proc.kill()
+ except Exception as e:
+ print(json.dumps({"error": str(e), "devices": []}))
+ return
+
+ # Merge known devices
+ out, _, _ = run_btctl("devices")
+ for line in out.split("\n"):
+ if line.strip().startswith("Device "):
+ parts = line.strip().split(" ", 2)
+ if len(parts) >= 3 and parts[1] not in discovered:
+ discovered[parts[1]] = {"address": parts[1], "name": parts[2],
+ "paired": True}
+ # Mark connected
+ out2, _, _ = run_btctl("devices", "Connected")
+ for line in out2.split("\n"):
+ if line.strip().startswith("Device "):
+ parts = line.strip().split(" ", 2)
+ if len(parts) >= 2 and parts[1] in discovered:
+ discovered[parts[1]]["connected"] = True
+
+ print(json.dumps(list(discovered.values())))
+
+
+def cmd_scan(action="find"):
+ if action == "on":
+ run_btctl("scan", "on")
+ print(json.dumps({"scanning": True}))
+ elif action == "off":
+ run_btctl("scan", "off")
+ print(json.dumps({"scanning": False}))
+ else:
+ cmd_scan_find(action)
+
+
+def main():
+ if len(sys.argv) < 2:
+ print("Usage: bluetooth_helper.py [args...]", file=sys.stderr)
+ sys.exit(1)
+ cmd, args = sys.argv[1], sys.argv[2:]
+ try:
+ {"power": lambda: cmd_power(args[0] if args else "status"),
+ "scan": lambda: cmd_scan(args[0] if args else "find"),
+ "devices": cmd_devices, "info": lambda: cmd_info(args[0] if args else ""),
+ "connect": lambda: cmd_connect(args[0] if args else ""),
+ "disconnect": lambda: cmd_disconnect(args[0] if args else ""),
+ "pair": lambda: cmd_pair(args[0] if args else ""),
+ "trust": lambda: cmd_trust(args[0] if args else ""),
+ "remove": lambda: cmd_remove(args[0] if args else ""),
+ }.get(cmd, lambda: print(json.dumps({"error": f"Unknown: {cmd}"})))()
+ except Exception as e:
+ print(json.dumps({"error": str(e)}))
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/desktop_thumbgen.py b/scripts/desktop_thumbgen.py
old mode 100644
new mode 100755
diff --git a/scripts/fps_monitor.py b/scripts/fps_monitor.py
new file mode 100755
index 00000000..9d5d49dc
--- /dev/null
+++ b/scripts/fps_monitor.py
@@ -0,0 +1,391 @@
+#!/usr/bin/env python3
+"""
+Metrics overlay provider for NothingLess.
+Pure Python 3, no external dependencies.
+
+Monitors:
+ - CPU temp via sysfs hwmon
+ - CPU power via RAPL energy counters
+ - GPU temp/power/usage via sysfs or nvidia-smi
+ - FPS via built-in libambfps.so (LD_PRELOAD) - primary
+ - FPS via MangoHud CSV, gsr-fps, lsfgvk - optional fallbacks
+
+Usage:
+ ./fps_monitor.py # continuous output
+"""
+import os
+import json
+import sys
+import time
+import struct
+import re
+
+# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+# Hardware monitoring helpers
+# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+def _read_sysfs(path, default=None):
+ try:
+ with open(path, 'r') as f:
+ return f.read().strip()
+ except (FileNotFoundError, PermissionError, OSError):
+ return default
+
+def _get_cpu_temp():
+ hwmon_base = '/sys/class/hwmon'
+ if not os.path.isdir(hwmon_base):
+ return -1
+ for hwmon in sorted(os.listdir(hwmon_base)):
+ path = os.path.join(hwmon_base, hwmon)
+ try:
+ name = _read_sysfs(os.path.join(path, 'name'))
+ if name in ('coretemp', 'k10temp', 'zenpower', 'cpu_thermal',
+ 'x86_pkg_temp', 'amd_energy'):
+ for item in sorted(os.listdir(path)):
+ if item.endswith('_input') and item.startswith('temp'):
+ raw = _read_sysfs(os.path.join(path, item))
+ if raw:
+ val = int(raw)
+ if 10000 < val < 120000:
+ return val // 1000
+ except OSError:
+ continue
+ return -1
+
+# ββ CPU power from RAPL (rate of change, not cumulative) ββ
+_rapl_cache = {'uj': None, 'time': 0.0}
+_rapl_last_watts = 0.0
+
+def _get_cpu_power():
+ """Read CPU package power from RAPL.
+ Calculates power from the rate of energy change over time.
+ Returns last valid reading between samples to avoid flicker.
+ Needs udev rule: see config/99-rapl-permissions.rules
+ """
+ global _rapl_cache, _rapl_last_watts
+ try:
+ p = '/sys/class/powercap/intel-rapl:0/energy_uj'
+ if not os.path.exists(p):
+ return 0.0
+ with open(p) as f:
+ v = f.read().strip()
+ if not v or not v.isdigit():
+ return 0.0
+
+ uj_now = int(v)
+ t_now = time.monotonic()
+
+ prev_uj = _rapl_cache['uj']
+ prev_t = _rapl_cache['time']
+
+ if prev_uj is not None:
+ dt = t_now - prev_t
+ if dt >= 0.8:
+ diff = uj_now - prev_uj
+ if diff > 0:
+ watts = (diff / 1_000_000.0) / dt
+ if 0 < watts < 500:
+ _rapl_last_watts = round(watts, 1)
+ _rapl_cache['uj'] = uj_now
+ _rapl_cache['time'] = t_now
+
+ if prev_uj is None:
+ _rapl_cache['uj'] = uj_now
+ _rapl_cache['time'] = t_now
+
+ return _rapl_last_watts
+ except (OSError, ValueError, PermissionError):
+ return 0.0
+
+def _get_gpu_stats():
+ usages, temps, powers = [], [], []
+ if os.path.exists('/proc/driver/nvidia/gpus'):
+ try:
+ out = os.popen(
+ 'nvidia-smi --query-gpu=utilization.gpu,temperature.gpu,power.draw'
+ ' --format=csv,noheader,nounits 2>/dev/null'
+ ).read().strip()
+ if out:
+ parts = out.split(',')
+ if len(parts) >= 3:
+ usages.append(float(parts[0]))
+ temps.append(int(parts[1]))
+ powers.append(float(parts[2]))
+ elif len(parts) >= 2:
+ usages.append(float(parts[0]))
+ temps.append(int(parts[1]))
+ powers.append(0.0)
+ except (ValueError, OSError):
+ pass
+ if usages:
+ return usages, temps, powers
+
+ drm_base = '/sys/class/drm'
+ if os.path.exists(drm_base):
+ for card in sorted(os.listdir(drm_base)):
+ if not card.startswith('card') or '-' in card:
+ continue
+ vendor = _read_sysfs(f'{drm_base}/{card}/device/vendor')
+ if vendor == '0x1002':
+ usage = 0.0
+ gpu_busy = _read_sysfs(f'{drm_base}/{card}/device/gpu_busy_percent')
+ if gpu_busy:
+ try: usage = float(gpu_busy)
+ except ValueError: pass
+ temp = -1
+ hwmon_base = f'{drm_base}/{card}/device/hwmon'
+ if os.path.isdir(hwmon_base):
+ dirs = os.listdir(hwmon_base)
+ if dirs:
+ t = _read_sysfs(f'{hwmon_base}/{dirs[0]}/temp1_input')
+ if t:
+ try: temp = int(t) // 1000
+ except ValueError: pass
+ power = 0.0
+ if os.path.isdir(hwmon_base):
+ dirs = os.listdir(hwmon_base)
+ if dirs:
+ for pname in ('power1_average', 'power2_average'):
+ p = _read_sysfs(f'{hwmon_base}/{dirs[0]}/{pname}')
+ if p:
+ try: power = int(p) / 1000000.0; break
+ except ValueError: pass
+ usages.append(usage); temps.append(temp); powers.append(power)
+ break
+ if not usages:
+ usages.append(0.0); temps.append(-1); powers.append(0.0)
+ return usages, temps, powers
+
+# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+# FPS - Primary: Built-in libambfps.so via LD_PRELOAD (shm)
+# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+# This is the recommended way: nothingless-fps ./game sets LD_PRELOAD
+# and the library writes actual app FPS to /dev/shm/nothingless_fps.
+# No external tools needed - ships with NothingLess.
+
+SHM_FPS_FILE = '/dev/shm/nothingless_fps'
+
+def _get_fps_shm():
+ """Primary FPS source: built-in libambfps.so LD_PRELOAD library.
+
+ Reads from /dev/shm/nothingless_fps which contains:
+ fps=
+ pid=
+ frames=
+ source=nothingless-preload
+
+ Checks /proc/ to detect if the game is still running.
+ Returns None if no data or process is gone.
+ """
+ if not os.path.exists(SHM_FPS_FILE):
+ return None
+ try:
+ age = time.time() - os.path.getmtime(SHM_FPS_FILE)
+ if age > 5:
+ return None # Stale: game probably exited
+
+ with open(SHM_FPS_FILE, 'r') as f:
+ data = {}
+ for line in f:
+ if '=' in line:
+ k, v = line.strip().split('=', 1)
+ data[k] = v
+
+ fps_val = data.get('fps', '0.0')
+ fps = float(fps_val)
+
+ # Verify the process is still alive
+ pid_str = data.get('pid', '')
+ if pid_str:
+ try:
+ pid = int(pid_str)
+ if not os.path.isdir(f'/proc/{pid}'):
+ return None # Process exited
+ except ValueError:
+ pass
+
+ if fps > 0:
+ return fps
+ return 0.0
+ except (OSError, ValueError, IndexError):
+ return None
+
+# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+# FPS - Optional fallbacks
+# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+# These require external tools but are kept as optional alternatives.
+
+LSFGVK_FILE = '/dev/shm/lsfgvk-fps'
+
+def _get_fps_lsfgvk():
+ """Optional: Read post-LSFG FPS from modified lsfg-vk layer.
+ Only used as fallback when libambfps.so is not active.
+ """
+ if not os.path.exists(LSFGVK_FILE):
+ return None
+ try:
+ age = time.time() - os.path.getmtime(LSFGVK_FILE)
+ if age > 3:
+ return None
+ with open(LSFGVK_FILE, 'r') as f:
+ val = f.read().strip()
+ if val:
+ fps = float(val)
+ if fps > 0:
+ return fps
+ return 0.0
+ except (OSError, ValueError):
+ return None
+
+# ββ gpu-screen-recorder fallback ββββββββββββββββββββββββββββββββββ
+GSR_FILE = '/dev/shm/gsr-fps-stats'
+_gsr_fps_re = re.compile(rb'update fps: (\d+)')
+
+def _get_fps_gsr():
+ """Optional fallback: Read FPS from gpu-screen-recorder stats.
+ Requires gpu-screen-recorder running separately.
+ """
+ if not os.path.exists(GSR_FILE):
+ return None
+ try:
+ age = time.time() - os.path.getmtime(GSR_FILE)
+ if age > 10:
+ return None
+ with open(GSR_FILE, 'rb') as f:
+ f.seek(0, 2)
+ size = f.tell()
+ chunk_size = min(size, 4096)
+ f.seek(-chunk_size, 2)
+ data = f.read()
+ for line in reversed(data.split(b'\n')):
+ m = _gsr_fps_re.search(line)
+ if m:
+ fps_val = float(m.group(1))
+ if fps_val > 0:
+ return fps_val
+ return 0.0
+ return None
+ except (OSError, ValueError):
+ return None
+
+# ββ MangoHud fallback βββββββββββββββββββββββββββββββββββββββββββββ
+MANGOHUD_LOG_DIR = '/dev/shm/mangohud'
+
+def _get_fps_mangohud():
+ """Optional fallback: Read FPS from MangoHud CSV log."""
+ if not os.path.isdir(MANGOHUD_LOG_DIR):
+ return None
+ try:
+ csv_files = [f for f in os.listdir(MANGOHUD_LOG_DIR)
+ if f.endswith('.csv')]
+ if not csv_files:
+ return None
+ latest = max(csv_files, key=lambda f:
+ os.path.getmtime(os.path.join(MANGOHUD_LOG_DIR, f)))
+ csv_path = os.path.join(MANGOHUD_LOG_DIR, latest)
+ age = time.time() - os.path.getmtime(csv_path)
+ if age > 10:
+ return None
+ with open(csv_path, 'r') as f:
+ lines = f.readlines()
+ if not lines:
+ return None
+ for line in reversed(lines):
+ line = line.strip()
+ if not line:
+ continue
+ parts = line.split(',')
+ if len(parts) >= 1:
+ try:
+ fps_val = float(parts[0])
+ if fps_val > 0:
+ return fps_val
+ return 0.0
+ except (ValueError, IndexError):
+ continue
+ return None
+ except (OSError, ValueError, IndexError, PermissionError):
+ return None
+
+# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+# FPS resolution: try sources in order of preference
+# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+def _get_fps():
+ """Resolve FPS from available sources.
+
+ Priority:
+ 1. Built-in libambfps.so (via LD_PRELOAD) - PRIMARY
+ 2. Modified LSFG-VK (optional, if available)
+ 3. gpu-screen-recorder (optional fallback)
+ 4. MangoHud CSV (optional fallback)
+ """
+ # 1. PRIMARY: Built-in nothingless preload library
+ fps = _get_fps_shm()
+ if fps is not None:
+ return fps, True
+
+ # 2. Modified LSFG-VK (optional external)
+ fps = _get_fps_lsfgvk()
+ if fps is not None:
+ return fps, True
+
+ # 3. gpu-screen-recorder (optional external)
+ fps = _get_fps_gsr()
+ if fps is not None:
+ return fps, True
+
+ # 4. MangoHud CSV (optional external)
+ fps = _get_fps_mangohud()
+ if fps is not None:
+ return fps, True
+
+ return None, False
+
+# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+# Main loop
+# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+def main():
+ fps_samples = []
+ output_interval = 0.3 # Update notch every 300ms
+
+ try:
+ while True:
+ tick_start = time.monotonic()
+ cpu_temp = _get_cpu_temp()
+ cpu_power = _get_cpu_power()
+ gpu_usages, gpu_temps, gpu_powers = _get_gpu_stats()
+
+ fps_val, fps_active = _get_fps()
+
+ result = {
+ 'cpu_temp': cpu_temp,
+ 'cpu_power': cpu_power,
+ 'gpu_usage': round(gpu_usages[0], 1) if gpu_usages else 0.0,
+ 'gpu_temp': gpu_temps[0] if gpu_temps else -1,
+ 'gpu_power': round(gpu_powers[0], 1) if gpu_powers else 0.0,
+ }
+
+ if fps_val is not None and fps_val > 0:
+ # FPS from libambfps.so is already smoothed (EMA).
+ # FPS from other sources may be raw - cap and average.
+ capped = min(fps_val, 500.0)
+ fps_samples.append(capped)
+ if len(fps_samples) > 10:
+ fps_samples.pop(0)
+ avg_fps = sum(fps_samples) / len(fps_samples)
+ result['fps'] = round(avg_fps, 1)
+ result['fps_active'] = True
+ else:
+ result['fps'] = 0.0
+ result['fps_active'] = fps_active
+
+ print(json.dumps(result), flush=True)
+ elapsed = time.monotonic() - tick_start
+ time.sleep(max(0.01, output_interval - elapsed))
+ except KeyboardInterrupt:
+ pass
+
+if __name__ == '__main__':
+ main()
diff --git a/scripts/fps_preload.c b/scripts/fps_preload.c
new file mode 100755
index 00000000..0106e416
--- /dev/null
+++ b/scripts/fps_preload.c
@@ -0,0 +1,182 @@
+/**
+ * fps_preload.c β Built-in FPS counter for NothingLess
+ * Funciona como capa Vulkan+VK_LAYER con encadenamiento correcto.
+ * TambiΓ©n hooks OpenGL/EGL via LD_PRELOAD.
+ *
+ * Compile: gcc -shared -fPIC -O2 -o libambfps.so fps_preload.c -lm -ldl
+ */
+#define _GNU_SOURCE
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define EXPORT __attribute__((visibility("default")))
+
+static int enabled = 0;
+static void check_env(void) {
+ const char *v = getenv("nothingless-fps");
+ if (!v || strcmp(v, "0") == 0) v = getenv("NOTHINGLESS_FPS");
+ if (!v || strcmp(v, "0") == 0) v = getenv("ENABLE_VK_LAYER_nothingless_fps");
+ if (v && v[0] && strcmp(v, "0") != 0) enabled = 1;
+}
+
+/* ββ FPS tracking ββββββββββββββββββββββββββββββββββββββββββββββββ */
+#define MAX_SAMPLES 32
+static double fps_samples[MAX_SAMPLES];
+static int sample_count = 0, sample_idx = 0;
+static uint64_t last_present_ns = 0, frame_count = 0;
+static double fps_smoothed = 0.0;
+static int smooth_init = 0;
+
+static uint64_t get_ns(void) {
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
+}
+
+static void write_fps(double fps) {
+ FILE *f = fopen("/dev/shm/nothingless_fps", "we");
+ if (f) {
+ fprintf(f, "fps=%.1f\npid=%d\nframes=%lu\nsource=nothingless-preload\n",
+ fps, getpid(), (unsigned long)frame_count);
+ fclose(f);
+ }
+}
+
+static void record_present(void) {
+ if (!enabled) return;
+ uint64_t now = get_ns();
+ if (last_present_ns > 0) {
+ uint64_t dt = now - last_present_ns;
+ if (dt > 500000ULL) {
+ double fps = 1000000000.0 / (double)dt;
+ if (fps > 0.0 && fps < 2000.0) {
+ fps_samples[sample_idx] = fps;
+ sample_idx = (sample_idx + 1) % MAX_SAMPLES;
+ if (sample_count < MAX_SAMPLES) sample_count++;
+ if (!smooth_init) { fps_smoothed = fps; smooth_init = 1; }
+ else { fps_smoothed = fps_smoothed * (1.0 - 0.08) + fps * 0.08; }
+ frame_count++;
+ if (frame_count % 8 == 0) {
+ int n = sample_count < MAX_SAMPLES ? sample_count : MAX_SAMPLES;
+ double sum = 0.0;
+ for (int i = 0; i < n; i++) sum += fps_samples[i];
+ double blended = fps_smoothed * 0.7 + (sum / n) * 0.3;
+ write_fps(blended);
+ }
+ }
+ }
+ }
+ last_present_ns = now;
+}
+
+/* ββ Vulkan layer: intercept vkQueuePresentKHR βββββββββββββββββββ */
+/* These functions are called by the Vulkan loader AS a layer */
+
+/* Store pointers to the NEXT layer's functions */
+static void *next_gipa = NULL;
+static void *next_gdpa = NULL;
+static void *next_qpresent = NULL;
+
+/* Forward decls */
+EXPORT void *vkGetDeviceProcAddr(void *device, const char *pName);
+EXPORT int vkQueuePresentKHR(void *queue, void *pPresentInfo);
+
+EXPORT void *vkGetInstanceProcAddr(void *instance, const char *pName) {
+ if (!enabled) goto fallback;
+ if (!pName) goto fallback;
+ if (strcmp(pName, "vkGetInstanceProcAddr") == 0) return (void*)vkGetInstanceProcAddr;
+ if (strcmp(pName, "vkGetDeviceProcAddr") == 0) return (void*)vkGetDeviceProcAddr;
+ if (strcmp(pName, "vkQueuePresentKHR") == 0) return (void*)vkQueuePresentKHR;
+fallback:
+ if (!next_gipa) {
+ void *h = dlopen("libvulkan.so.1", RTLD_LAZY | RTLD_NOLOAD);
+ if (!h) h = dlopen("libvulkan.so", RTLD_LAZY | RTLD_NOLOAD);
+ if (h) next_gipa = dlsym(h, "vkGetInstanceProcAddr");
+ }
+ if (next_gipa) {
+ typedef void *(*PFN)(void*, const char*);
+ return ((PFN)next_gipa)(instance, pName);
+ }
+ return NULL;
+}
+
+EXPORT void *vkGetDeviceProcAddr(void *device, const char *pName) {
+ if (enabled && pName) {
+ if (strcmp(pName, "vkQueuePresentKHR") == 0) return (void*)vkQueuePresentKHR;
+ }
+ if (!next_gdpa) {
+ void *h = dlopen("libvulkan.so.1", RTLD_LAZY | RTLD_NOLOAD);
+ if (!h) h = dlopen("libvulkan.so", RTLD_LAZY | RTLD_NOLOAD);
+ if (h) next_gdpa = dlsym(h, "vkGetDeviceProcAddr");
+ }
+ if (next_gdpa) {
+ typedef void *(*PFN)(void*, const char*);
+ return ((PFN)next_gdpa)(device, pName);
+ }
+ return NULL;
+}
+
+EXPORT int vkQueuePresentKHR(void *queue, void *pPresentInfo) {
+ record_present();
+ if (!next_qpresent) {
+ /* Get the next layer's vkQueuePresentKHR via the chain */
+ if (next_gdpa) {
+ typedef void *(*PFN)(void*, const char*);
+ next_qpresent = ((PFN)next_gdpa)(NULL, "vkQueuePresentKHR");
+ }
+ if (!next_qpresent) {
+ void *h = dlopen("libvulkan.so.1", RTLD_LAZY | RTLD_NOLOAD);
+ if (!h) h = dlopen("libvulkan.so", RTLD_LAZY | RTLD_NOLOAD);
+ if (h) next_qpresent = dlsym(h, "vkQueuePresentKHR");
+ }
+ }
+ if (next_qpresent) {
+ typedef int (*PFN)(void*, void*);
+ return ((PFN)next_qpresent)(queue, pPresentInfo);
+ }
+ return 0;
+}
+
+/* ββ OpenGL hooks (LD_PRELOAD only) ββββββββββββββββββββββββββββββ */
+static void *resolve(const char *lib, const char *sym) {
+ void *h = dlopen(lib, RTLD_LAZY | RTLD_NOLOAD);
+ if (!h) h = dlopen(lib, RTLD_LAZY);
+ if (!h) return NULL;
+ void *p = dlsym(h, sym);
+ dlclose(h);
+ return p;
+}
+
+EXPORT int eglSwapBuffers(void *display, void *surface) {
+ record_present();
+ static int (*real)(void*, void*) = NULL;
+ if (!real) real = resolve("libEGL.so.1", "eglSwapBuffers");
+ if (!real) real = resolve("libEGL.so", "eglSwapBuffers");
+ return real ? real(display, surface) : 0;
+}
+
+EXPORT void glXSwapBuffers(void *display, uint64_t drawable) {
+ record_present();
+ static void (*real)(void*, uint64_t) = NULL;
+ if (!real) real = resolve("libGL.so.1", "glXSwapBuffers");
+ if (!real) real = resolve("libGL.so", "glXSwapBuffers");
+ if (real) real(display, drawable);
+}
+
+/* ββ Constructor βββββββββββββββββββββββββββββββββββββββββββββββββ */
+static void __attribute__((constructor)) init(void) {
+ check_env();
+ if (enabled) {
+ write_fps(0.0);
+ }
+}
+
+static void __attribute__((destructor)) fini(void) {
+ if (enabled) remove("/dev/shm/nothingless_fps");
+}
diff --git a/scripts/gsr-fps.sh b/scripts/gsr-fps.sh
new file mode 100755
index 00000000..ee0b9701
--- /dev/null
+++ b/scripts/gsr-fps.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+# gsr-fps.sh β Lanza juego con monitoreo de FPS post-LSFG
+# Integrado en NothingLess. Usar: nothingless fps
+#
+# Escribe FPS a /dev/shm/gsr-fps-stats para que fps_monitor.py lo lea
+# y los muestre en el notch.
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+GSR_FILE="/dev/shm/gsr-fps-stats"
+GSR_PID=""
+
+trap "rm -f $GSR_FILE; kill $GSR_PID 2>/dev/null; rm -f /tmp/nothingless-gsr-fps.mp4" EXIT INT TERM
+
+# Iniciar gpu-screen-recorder en modo verbose
+# -w screen: captura pantalla completa
+# -v yes: verbose mode (genera "update fps: N" a stderr)
+# stderr al archivo para fps_monitor.py
+gpu-screen-recorder \
+ -w screen \
+ -f 999 \
+ -s 1920x1080 \
+ -c mkv \
+ -o /tmp/nothingless-gsr-fps.mp4 \
+ -v yes \
+ -df no \
+ 2>"$GSR_FILE" &
+GSR_PID=$!
+
+# Ejecutar el juego (todos los argumentos)
+"$@"
+GAME_EXIT=$?
+
+# Cleanup
+kill $GSR_PID 2>/dev/null
+wait $GSR_PID 2>/dev/null
+rm -f "$GSR_FILE" /tmp/nothingless-gsr-fps.mp4
+exit $GAME_EXIT
diff --git a/scripts/keystore.py b/scripts/keystore.py
old mode 100644
new mode 100755
index d84d110e..f75769c4
--- a/scripts/keystore.py
+++ b/scripts/keystore.py
@@ -11,7 +11,7 @@ def get_machine_id():
with open("/etc/machine-id", "r") as f:
return f.read().strip().encode("utf-8")
except Exception:
- return b"ambxst-fallback-salt-82741"
+ return b"nothingless-fallback-salt-82741"
def xor_crypt(data, key):
diff --git a/scripts/libMangoHud.so b/scripts/libMangoHud.so
new file mode 100755
index 00000000..44d03f3e
Binary files /dev/null and b/scripts/libMangoHud.so differ
diff --git a/scripts/libMangoHud_opengl.so b/scripts/libMangoHud_opengl.so
new file mode 100755
index 00000000..a85a894e
Binary files /dev/null and b/scripts/libMangoHud_opengl.so differ
diff --git a/scripts/libMangoHud_shim.so b/scripts/libMangoHud_shim.so
new file mode 100755
index 00000000..a0bff8e4
Binary files /dev/null and b/scripts/libMangoHud_shim.so differ
diff --git a/scripts/libambfps.so b/scripts/libambfps.so
new file mode 100755
index 00000000..a1335d66
Binary files /dev/null and b/scripts/libambfps.so differ
diff --git a/scripts/lockwall.py b/scripts/lockwall.py
index bb308c9a..c82c8516 100755
--- a/scripts/lockwall.py
+++ b/scripts/lockwall.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""
-Lockscreen Wallpaper Frame Extractor for Ambxst
+Lockscreen Wallpaper Frame Extractor for NothingLess
Extracts first frame from video/GIF wallpapers for lockscreen background.
Only processes video and GIF files - skips regular images.
"""
@@ -102,7 +102,7 @@ def extract_first_frame(self) -> Tuple[bool, str]:
def run(self) -> int:
"""Main execution function."""
- print("π Ambxst Lockscreen Wallpaper Generator")
+ print("π NothingLess Lockscreen Wallpaper Generator")
print("=" * 40)
# Validate wallpaper
diff --git a/scripts/loginlock.sh b/scripts/loginlock.sh
index 3f27504f..18fb884b 100755
--- a/scripts/loginlock.sh
+++ b/scripts/loginlock.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
-LOCKFILE="/tmp/ambxst_loginlock.lock"
+LOCKFILE="/tmp/nothingless_loginlock.lock"
if [ -e "$LOCKFILE" ]; then
PID=$(cat "$LOCKFILE")
if kill -0 "$PID" 2>/dev/null; then
@@ -9,13 +9,13 @@ if [ -e "$LOCKFILE" ]; then
fi
echo $$ >"$LOCKFILE"
-CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/ambxst/config/system.json"
+CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/nothingless/config/system.json"
get_lock_cmd() {
if [ -f "$CONFIG_FILE" ]; then
- jq -r '.idle.general.lock_cmd // "ambxst lock"' "$CONFIG_FILE"
+ jq -r '.idle.general.lock_cmd // "nothingless lock"' "$CONFIG_FILE"
else
- echo "ambxst lock"
+ echo "nothingless lock"
fi
}
diff --git a/scripts/mangohud-patch/build-mangohud.sh b/scripts/mangohud-patch/build-mangohud.sh
new file mode 100755
index 00000000..31648719
--- /dev/null
+++ b/scripts/mangohud-patch/build-mangohud.sh
@@ -0,0 +1,49 @@
+#!/usr/bin/env bash
+# Build MangoHud modificado con output SHM para NothingLess
+set -euo pipefail
+
+MANGOHUD_VERSION="v0.8.3"
+MANGOHUD_DIR="/tmp/mangohud-build"
+INSTALL_DIR="$HOME/.local/lib"
+
+echo "=== Building MangoHud $MANGOHUD_VERSION with NothingLess FPS output ==="
+
+# Clone MangoHud source
+if [ ! -d "$MANGOHUD_DIR/MangoHud" ]; then
+ mkdir -p "$MANGOHUD_DIR"
+ git clone --depth 1 --branch "$MANGOHUD_VERSION" https://github.com/flightlessmango/MangoHud.git "$MANGOHUD_DIR/MangoHud"
+fi
+
+# Apply the FPS output patch
+cd "$MANGOHUD_DIR/MangoHud"
+if ! grep -q "nothingless_fps" src/overlay.cpp 2>/dev/null; then
+ echo "Applying FPS output patch..."
+ # Find the line where fps is calculated
+ LINE=$(grep -n "sw_stats.fps = " src/overlay.cpp | head -1 | cut -d: -f1)
+ if [ -n "$LINE" ]; then
+ sed -i "${LINE}a\\
+ // Write FPS to /dev/shm/nothingless_fps for NothingLess notch\\
+ FILE *nfps = fopen(\"/dev/shm/nothingless_fps\", \"w\");\\
+ if (nfps) {\
+ fprintf(nfps, \"fps=%.1f\\npid=%d\\nframes=%lu\\nsource=mangohud\\n\",\\
+ sw_stats.fps, getpid(), (unsigned long)sw_stats.n_frames_since_update);\\
+ fclose(nfps);\\
+ }" src/overlay.cpp
+ echo "Patch applied."
+ fi
+fi
+
+# Build
+echo "Building..."
+pip3 install --user mako --break-system-packages 2>/dev/null || true
+meson setup build --buildtype=release
+ninja -C build
+
+# Install
+echo "Installing to $INSTALL_DIR..."
+cp build/src/libMangoHud.so "$INSTALL_DIR/"
+cp build/src/libMangoHud_shim.so "$INSTALL_DIR/"
+cp build/src/libMangoHud_opengl.so "$INSTALL_DIR/"
+cp build/src/mangohud "$INSTALL_DIR/../bin/mangohud-nothingless" 2>/dev/null || true
+
+echo "β Done. MangoHud modificado instalado en $INSTALL_DIR"
diff --git a/scripts/monitors_writer.py b/scripts/monitors_writer.py
new file mode 100755
index 00000000..d9e8cdb5
--- /dev/null
+++ b/scripts/monitors_writer.py
@@ -0,0 +1,372 @@
+#!/usr/bin/env python3
+"""
+MonitorsWriter - Persist monitor layout to Hyprland config files
+Invoked by NothingLess's MonitorsWriter QML service
+
+Usage:
+ monitors_writer.py sync [--json ] [--conf ] [--lua ]
+ monitors_writer.py write [--data ]
+
+Reads monitor state from either:
+ - JSON file (--json), or
+ - JSON string (--data), or
+ - `hyprctl monitors -j` (no args)
+
+Writes:
+ - monitors.conf (Hyprland .conf format)
+ - monitors.lua (Hyprland V2 Lua format)
+ - Creates backups
+ - Applies changes live via hyprctl dispatch
+"""
+
+import json
+import os
+import shutil
+import subprocess
+import sys
+import time
+from datetime import datetime
+from pathlib import Path
+
+
+def get_config_dir():
+ """Get Hyprland config dir"""
+ xdg_config = os.environ.get("XDG_CONFIG_HOME",
+ os.path.expanduser("~/.config"))
+ return os.path.join(xdg_config, "hypr")
+
+
+def get_monitors_data(json_path=None, json_data=None):
+ """Get monitor data from JSON file, string, or hyprctl"""
+ if json_path and os.path.isfile(json_path):
+ with open(json_path, "r") as f:
+ return json.load(f)
+ elif json_data:
+ return json.loads(json_data)
+ else:
+ result = subprocess.run(
+ ["hyprctl", "monitors", "-j"],
+ capture_output=True, text=True, timeout=5
+ )
+ if result.returncode == 0 and result.stdout.strip():
+ return json.loads(result.stdout)
+ # Fall back to axctl
+ result = subprocess.run(
+ ["axctl", "monitor", "list"],
+ capture_output=True, text=True, timeout=5
+ )
+ if result.returncode == 0 and result.stdout.strip():
+ return json.loads(result.stdout)
+ return []
+
+
+def normalize_monitors(monitors_data):
+ """
+ Normalize monitor data from various sources into a standard format.
+ Handles hyprctl, axctl, and custom data formats.
+ """
+ normalized = []
+
+ for m in monitors_data:
+ meta = m.get("metadata", {})
+ entry = {
+ "name": m.get("name", ""),
+ "width": m.get("width", 0),
+ "height": m.get("height", 0),
+ "x": m.get("x", meta.get("x", 0)),
+ "y": m.get("y", meta.get("y", 0)),
+ "scale": m.get("scale", 1.0),
+ "refreshRate": m.get("refreshRate", m.get("refresh_rate", 60)),
+ "transform": m.get("transform", meta.get("transform", 0)),
+ "enabled": m.get("enabled", True),
+ "bitdepth": m.get("bitdepth", m.get("bit_depth", 0)),
+ "mirror": m.get("mirror", ""),
+ "vrr": m.get("vrr", 0),
+ "description": m.get("description", ""),
+ }
+ normalized.append(entry)
+
+ return normalized
+
+
+def generate_conf_lines(monitors):
+ """Generate Hyprland .conf format lines"""
+ transforms = ["normal", "90", "180", "270",
+ "flipped", "flipped-90", "flipped-180", "flipped-270"]
+
+ lines = [
+ "# Generated by NothingLess on {}. Do not edit manually.".format(
+ datetime.now().strftime("%Y-%m-%d at %H:%M:%S")),
+ "",
+ ]
+
+ for m in monitors:
+ name = m["name"]
+ if not m["enabled"]:
+ lines.append(f"monitor={name},disable")
+ continue
+
+ mode = f"{m['width']}x{m['height']}@{m['refreshRate']:.2f}Hz"
+ pos = f"{m['x']}x{m['y']}"
+
+ line = f"monitor={name},{mode},{pos},{m['scale']}"
+
+ if m.get("bitdepth", 0) >= 10:
+ line += ",bitdepth,10"
+
+ if m.get("mirror"):
+ line += f",mirror,{m['mirror']}"
+
+ lines.append(line)
+
+ if m.get("transform", 0) != 0:
+ lines.append(f"monitor={name},transform,{m['transform']}")
+
+ if m.get("vrr", 0):
+ lines.append(f"monitor={name},vrr,{m['vrr']}")
+
+ return lines
+
+
+def generate_lua_lines(monitors):
+ """Generate Hyprland V2 Lua format lines"""
+ lines = [
+ "-- Generated by NothingLess on {}. Do not edit manually.".format(
+ datetime.now().strftime("%Y-%m-%d at %H:%M:%S")),
+ "",
+ ]
+
+ for m in monitors:
+ name = m["name"]
+
+ if not m["enabled"]:
+ lines.append(f"hl.monitor({{\n output = \"{name}\",\n disabled = true\n}})")
+ lines.append("")
+ continue
+
+ mode = f"{m['width']}x{m['height']}@{m['refreshRate']:.2f}Hz"
+ pos = f"{m['x']}x{m['y']}"
+
+ lua_lines = [f"hl.monitor({{"]
+ lua_lines.append(f' output = "{name}",')
+ lua_lines.append(f' mode = "{mode}",')
+ lua_lines.append(f' position = "{pos}",')
+ lua_lines.append(f' scale = {m["scale"]}')
+
+ if m.get("bitdepth", 0) >= 10:
+ lua_lines.append(f" bitdepth = 10")
+
+ if m.get("mirror"):
+ lua_lines.append(f' mirror = "{m["mirror"]}"')
+
+ if m.get("transform", 0) != 0:
+ lua_lines.append(f" transform = {m['transform']}")
+
+ if m.get("vrr", 0):
+ lua_lines.append(f" vrr = {m['vrr']}")
+
+ lua_lines.append("})")
+ lua_lines.append("")
+
+ lines.extend(lua_lines)
+
+ return lines
+
+
+def apply_live(monitors):
+ """Apply monitor changes via hyprctl reload (reads monitors.conf)"""
+ try:
+ subprocess.run(["hyprctl", "reload"], capture_output=True, timeout=5)
+ print("[OK] Applied via hyprctl reload")
+ except Exception as e:
+ print(f"[WARN] hyprctl reload failed: {e}", file=sys.stderr)
+ for m in monitors:
+ name = m.get("name", "")
+ if not name or not m.get("enabled", True):
+ continue
+ mode = f"{m['width']}x{m['height']}@{m['refreshRate']:.2f}Hz"
+ pos = f"{m['x']}x{m['y']}"
+ try:
+ subprocess.run(
+ ["hyprctl", "dispatch", f"monitor {name},{mode},{pos},{m['scale']}"],
+ capture_output=True, timeout=3
+ )
+ except Exception as e:
+ print(f"[WARN] Failed to apply {name}: {e}", file=sys.stderr)
+
+def write_conf(lines, path):
+ """Write .conf file with backup"""
+ path = os.path.expanduser(path)
+ backup = path + ".bak"
+
+ if os.path.isfile(path):
+ shutil.copy2(path, backup)
+
+ with open(path, "w") as f:
+ f.write("\n".join(lines) + "\n")
+
+ print(f"[OK] Wrote {path} ({len(lines)} lines)")
+
+
+def write_lua(lines, path):
+ """Write .lua file with backup"""
+ path = os.path.expanduser(path)
+ backup = path + ".bak"
+
+ if os.path.isfile(path):
+ shutil.copy2(path, backup)
+
+ with open(path, "w") as f:
+ f.write("\n".join(lines) + "\n")
+
+ print(f"[OK] Wrote {path} ({len(lines)} lines)")
+
+
+def _inject_monitors_conf(lines, path):
+ """Inject monitor= lines into NothingLess hyprland.conf between markers"""
+ start_mark = "# === NOTHINGLESS MONITORS ===\n"
+ end_mark = "# === END MONITORS ===\n"
+ monitor_lines = [l for l in lines if l.startswith("monitor=")]
+ block = start_mark + "\n".join(monitor_lines) + "\n" + end_mark
+
+ with open(path) as f:
+ content = f.read()
+
+ # Remove old block if exists
+ if start_mark in content:
+ before = content.split(start_mark)[0]
+ after = content.split(end_mark)[1] if end_mark in content else ""
+ content = before + after
+
+ # Append new block
+ content = content.rstrip() + "\n\n" + block
+ with open(path, "w") as f:
+ f.write(content)
+ print(f"[OK] Injected {len(monitor_lines)} monitors into {path}")
+
+
+def _inject_monitors_lua(lines, path):
+ """Inject hl.monitor() lines into NothingLess hyprland.lua between markers"""
+ start_mark = "-- === NOTHINGLESS MONITORS ===\n"
+ end_mark = "-- === END MONITORS ===\n"
+ lua_lines = [l for l in lines if l.startswith("hl.monitor")]
+ block = start_mark + "\n".join(lua_lines) + "\n" + end_mark
+
+ with open(path) as f:
+ content = f.read()
+
+ if start_mark in content:
+ before = content.split(start_mark)[0]
+ after = content.split(end_mark)[1] if end_mark in content else ""
+ content = before + after
+
+ content = content.rstrip() + "\n\n" + block
+ with open(path, "w") as f:
+ f.write(content)
+ print(f"[OK] Injected monitors into {path}")
+
+
+def cmd_sync(args):
+ """Read monitors and write configs"""
+ config_dir = get_config_dir()
+ conf_path = args.conf or os.path.join(config_dir, "monitors.conf")
+ lua_path = args.lua or os.path.join(config_dir, "monitors.lua")
+
+ monitors_data = get_monitors_data(json_path=args.json, json_data=args.data)
+ monitors = normalize_monitors(monitors_data)
+
+ if not monitors:
+ print("[WARN] No monitors detected", file=sys.stderr)
+ return 1
+
+ print(f"[INFO] Syncing {len(monitors)} monitors...")
+
+ conf_lines = generate_conf_lines(monitors)
+ lua_lines = generate_lua_lines(monitors)
+
+ os.makedirs(os.path.dirname(os.path.expanduser(conf_path)), exist_ok=True)
+ os.makedirs(os.path.dirname(os.path.expanduser(lua_path)), exist_ok=True)
+
+ write_conf(conf_lines, conf_path)
+ write_lua(lua_lines, lua_path)
+
+ # Also inject into NothingLess hyprland.conf (sourced by hyprland.conf)
+ nl_dir = os.path.expanduser("~/.local/share/nothingless")
+ nl_conf = os.path.join(nl_dir, "hyprland.conf")
+ nl_lua = os.path.join(nl_dir, "hyprland.lua")
+ if os.path.isfile(nl_conf):
+ _inject_monitors_conf(conf_lines, nl_conf)
+ if os.path.isfile(nl_lua):
+ _inject_monitors_lua(lua_lines, nl_lua)
+
+ if not args.no_apply:
+ print("[INFO] Applying live...")
+ apply_live(monitors)
+
+ print("[OK] Monitor sync complete")
+ return 0
+
+
+def cmd_write(args):
+ """Write specific data to config files (from QML)"""
+ monitors_data = get_monitors_data(json_data=args.data)
+ monitors = normalize_monitors(monitors_data)
+
+ if not monitors:
+ print("[WARN] No monitor data provided", file=sys.stderr)
+ return 1
+
+ conf_path = os.path.expanduser(args.conf_path)
+ lua_path = os.path.expanduser(args.lua_path)
+
+ conf_lines = generate_conf_lines(monitors)
+ lua_lines = generate_lua_lines(monitors)
+
+ os.makedirs(os.path.dirname(conf_path), exist_ok=True)
+ os.makedirs(os.path.dirname(lua_path), exist_ok=True)
+
+ write_conf(conf_lines, conf_path)
+ write_lua(lua_lines, lua_path)
+
+ apply_live(monitors)
+
+ print("[OK] Write complete")
+ return 0
+
+
+def main():
+ import argparse
+
+ parser = argparse.ArgumentParser(
+ description="NothingLess MonitorsWriter - Persist monitor config"
+ )
+
+ subparsers = parser.add_subparsers(dest="command")
+
+ # sync command
+ sync_parser = subparsers.add_parser("sync", help="Sync monitors from hyprctl")
+ sync_parser.add_argument("--json", help="JSON file with monitor data")
+ sync_parser.add_argument("--conf", help="Path to monitors.conf (default: ~/.config/hypr/monitors.conf)")
+ sync_parser.add_argument("--lua", help="Path to monitors.lua (default: ~/.config/hypr/monitors.lua)")
+ sync_parser.add_argument("--data", help="JSON string with monitor data")
+ sync_parser.add_argument("--no-apply", action="store_true", help="Skip live application")
+
+ # write command
+ write_parser = subparsers.add_parser("write", help="Write monitor data to files")
+ write_parser.add_argument("conf_path", help="Path to monitors.conf")
+ write_parser.add_argument("lua_path", help="Path to monitors.lua")
+ write_parser.add_argument("--data", required=True, help="JSON string with monitor data")
+
+ args = parser.parse_args()
+
+ if args.command == "sync":
+ return cmd_sync(args)
+ elif args.command == "write":
+ return cmd_write(args)
+ else:
+ parser.print_help()
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/scripts/nothing-fps b/scripts/nothing-fps
new file mode 100755
index 00000000..0550bfbf
--- /dev/null
+++ b/scripts/nothing-fps
@@ -0,0 +1,66 @@
+#!/usr/bin/env bash
+# nothing-fps β Launch any program with FPS monitoring
+#
+# Incluye MangoHud modificado (con output SHM). Auto-instala
+# las librerΓas si no estΓ‘n en ~/.local/lib/.
+# FPS escrito a /dev/shm/nothingless_fps para el notch.
+#
+# Usage:
+# nothing-fps %command% Steam launch options / normal
+# nothing-fps --quiet %command% Hidden overlay, only sends data to notch
+# nothing-fps --visible %command% Show overlay
+
+SHM_FILE="/dev/shm/nothingless_fps"
+cleanup() { rm -f "$SHM_FILE" 2>/dev/null; }
+trap cleanup EXIT
+
+# ββ Locate MangoHud libraries ββ
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)"
+LHOME="$HOME/.local/lib"
+LREPO=""
+
+# Search order: (1) junto al script, (2) repo scripts/, (3) ~/.local/lib/
+for dir in "$SCRIPT_DIR" "$HOME/.local/src/nothingless/scripts" "$HOME/.local/src/Test-NothingLess/scripts"; do
+ if [ -f "$dir/libMangoHud_shim.so" ]; then
+ LREPO="$dir"
+ break
+ fi
+done
+
+# If found in repo but not in home, copy
+if [ -n "$LREPO" ]; then
+ mkdir -p "$LHOME"
+ for lib in libMangoHud.so libMangoHud_shim.so libMangoHud_opengl.so; do
+ [ -f "$LREPO/$lib" ] && cp -n "$LREPO/$lib" "$LHOME/$lib" 2>/dev/null || true
+ done
+fi
+
+if [ ! -f "$LHOME/libMangoHud_shim.so" ]; then
+ echo "nothing-fps: error β libMangoHud_shim.so no encontrado." >&2
+ echo "ReinstalΓ‘ NothingLess o copiΓ‘ los archivos a ~/.local/lib/" >&2
+ exec "$@"
+fi
+
+# ββ Environment ββ
+export LD_LIBRARY_PATH="${LHOME}:${LD_LIBRARY_PATH:-}"
+export LD_PRELOAD="${LHOME}/libMangoHud_shim.so${LD_PRELOAD:+:$LD_PRELOAD}"
+# ββ Parse flags ββ
+SHOW_OVERLAY=false
+ARGS=()
+for arg in "$@"; do
+ case "$arg" in
+ --quiet|--hidden) ;;
+ --visible) SHOW_OVERLAY=true ;;
+ *) ARGS+=("$arg") ;;
+ esac
+done
+
+# ββ MangoHud config ββ
+if [ "$SHOW_OVERLAY" = true ]; then
+ export MANGOHUD_CONFIG="fps_only,font_size=14,background_alpha=0.2,position=top-right,offset_x=8,offset_y=8"
+else
+ export MANGOHUD_CONFIG="fps_only,no_display,font_size=14"
+fi
+export MANGOHUD=1
+
+exec env 'nothingless-fps=1' "${ARGS[@]}"
diff --git a/scripts/sleep_monitor.sh b/scripts/sleep_monitor.sh
index 8d3171d7..71f7236d 100755
--- a/scripts/sleep_monitor.sh
+++ b/scripts/sleep_monitor.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
-LOCKFILE="/tmp/ambxst_sleep_monitor.lock"
+LOCKFILE="/tmp/nothingless_sleep_monitor.lock"
if [ -e "$LOCKFILE" ]; then
PID=$(cat "$LOCKFILE")
if kill -0 "$PID" 2>/dev/null; then
@@ -10,7 +10,7 @@ fi
echo $$ >"$LOCKFILE"
# Sleep Monitor - Executes commands before and after sleep
-CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/ambxst/config/system.json"
+CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/nothingless/config/system.json"
get_cmd() {
local type=$1
@@ -18,13 +18,13 @@ get_cmd() {
if [ "$type" == "before" ]; then
jq -r '.idle.general.before_sleep_cmd // "loginctl lock-session"' "$CONFIG_FILE"
else
- jq -r '.idle.general.after_sleep_cmd // "ambxst screen on"' "$CONFIG_FILE"
+ jq -r '.idle.general.after_sleep_cmd // "nothingless screen on"' "$CONFIG_FILE"
fi
else
if [ "$type" == "before" ]; then
echo "loginctl lock-session"
else
- echo "ambxst screen on"
+ echo "nothingless screen on"
fi
fi
}
diff --git a/scripts/system_monitor.py b/scripts/system_monitor.py
index 5b24c8b3..39e90009 100755
--- a/scripts/system_monitor.py
+++ b/scripts/system_monitor.py
@@ -15,6 +15,7 @@ def __init__(self, disks=[]):
self.cpu_model = self._detect_cpu_model()
self.gpu_info = self._detect_gpus()
self.disk_types = self._detect_disk_types(disks)
+ self._cpu_temp_path = None # cached hwmon path for CPU temp
def _detect_cpu_model(self):
try:
@@ -97,27 +98,32 @@ def _detect_gpus(self):
def _detect_disk_types(self, disks):
types = {}
+ # Build mount lookup map in one pass over /proc/mounts
+ mount_dev = {}
+ try:
+ with open("/proc/mounts", "r") as f:
+ for line in f:
+ parts = line.split()
+ if len(parts) >= 2 and parts[1] in disks:
+ mount_dev[parts[1]] = parts[0]
+ except:
+ pass
+
+ # Precompile regex for device base name extraction
+ import re as _re
+ _dev_re = _re.compile(r"p?\d*$")
+
for mount in disks:
types[mount] = "unknown"
- try:
- with open("/proc/mounts", "r") as f:
- for line in f:
- parts = line.split()
- if parts[1] == mount:
- dev = parts[0]
- if dev.startswith("/dev/"):
- base = re.sub(
- r"p?[0-9]*$", "", dev.replace("/dev/", "")
- )
- rota_path = f"/sys/block/{base}/queue/rotational"
- if os.path.exists(rota_path):
- with open(rota_path, "r") as f2:
- types[mount] = (
- "hdd" if f2.read().strip() == "1" else "ssd"
- )
- break
- except:
- pass
+ dev = mount_dev.get(mount)
+ if dev and dev.startswith("/dev/"):
+ base = _dev_re.sub("", dev[5:]) # dev.replace("/dev/", "")
+ rota_path = f"/sys/block/{base}/queue/rotational"
+ try:
+ with open(rota_path, "r") as f:
+ types[mount] = "hdd" if f.read().strip() == "1" else "ssd"
+ except:
+ pass
return types
def get_cpu(self):
@@ -142,8 +148,22 @@ def get_cpu(self):
return 0.0
def get_cpu_temp(self):
+ # Use cached hwmon path (found once, reused)
+ if self._cpu_temp_path is not None:
+ try:
+ for item in os.listdir(self._cpu_temp_path):
+ if item.endswith("_input") and item.startswith("temp"):
+ with open(os.path.join(self._cpu_temp_path, item), "r") as f:
+ val = int(f.read().strip())
+ if 10000 < val < 120000:
+ return val // 1000
+ except:
+ self._cpu_temp_path = None # invalidate cache on error
+ return -1
+
base = "/sys/class/hwmon"
if not os.path.exists(base):
+ self._cpu_temp_path = None
return -1
for hwmon in os.listdir(base):
path = os.path.join(base, hwmon)
@@ -158,6 +178,7 @@ def get_cpu_temp(self):
"x86_pkg_temp",
"amd_energy",
]:
+ self._cpu_temp_path = path # cache for subsequent calls
for item in os.listdir(path):
if item.endswith("_input") and item.startswith("temp"):
with open(os.path.join(path, item), "r") as f:
@@ -166,6 +187,7 @@ def get_cpu_temp(self):
return val // 1000
except:
continue
+ self._cpu_temp_path = None
return -1
def get_mem(self):
@@ -202,6 +224,34 @@ def get_disk_usage(self, disks):
usage_map[mount] = 0.0
return usage_map
+ def get_fps(self):
+ """Read FPS from SHM file, returns 0 if file is stale (>5s old)."""
+ import os
+ try:
+ mtime = os.path.getmtime("/dev/shm/nothingless_fps")
+ if time.time() - mtime > 5:
+ return 0.0
+ with open("/dev/shm/nothingless_fps") as f:
+ for line in f:
+ line = line.strip()
+ if not line or line.startswith("#") or line.startswith("time"):
+ continue
+ if line.startswith("fps="):
+ val = float(line.split("=", 1)[1])
+ return max(0.0, val)
+ parts = line.split(",")
+ if len(parts) >= 2:
+ try:
+ val = float(parts[1].strip())
+ if val > 0:
+ return val
+ except:
+ pass
+ except:
+ pass
+ return 0.0
+
+
def get_gpu_stats(self):
usages = []
temps = []
@@ -301,6 +351,7 @@ def get_gpu_stats(self):
ram_usage, ram_total, ram_used, ram_avail = monitor.get_mem()
disk_usage = monitor.get_disk_usage(disks)
gpu_usages, gpu_temps = monitor.get_gpu_stats()
+ fps = monitor.get_fps()
print(
json.dumps(
@@ -319,6 +370,7 @@ def get_gpu_stats(self):
"usages": gpu_usages,
"temps": gpu_temps,
},
+ "fps": fps,
}
),
flush=True,
diff --git a/scripts/thumbgen.py b/scripts/thumbgen.py
index 5064f991..408ff234 100755
--- a/scripts/thumbgen.py
+++ b/scripts/thumbgen.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""
-Thumbnail Generator for Ambxst Wallpaper System
+Thumbnail Generator for NothingLess Wallpaper System
Generates thumbnails for video files, images, and GIFs using FFmpeg and ImageMagick with multithreading.
"""
@@ -353,7 +353,7 @@ def process_files(self, max_workers: int = 4) -> None:
def run(self) -> int:
"""Main execution function."""
- print("πΌοΈ Ambxst Thumbnail Generator")
+ print("πΌοΈ NothingLess Thumbnail Generator")
print("=" * 40)
# Load configuration
diff --git a/scripts/toggle-metrics.sh b/scripts/toggle-metrics.sh
new file mode 100755
index 00000000..680a6728
--- /dev/null
+++ b/scripts/toggle-metrics.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+# Toggle notch metrics overlay
+# IPC call handled by GlobalShortcuts - this triggers the QML handler
+qs ipc --pid "$(pidof nothingless)" call nothingless run toggle-metrics 2>/dev/null || true
diff --git a/scripts/weather.sh b/scripts/weather.sh
index 0d20185e..83a6b26b 100755
--- a/scripts/weather.sh
+++ b/scripts/weather.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-# Weather fetching script for Ambxst
+# Weather fetching script for NothingLess
# Usage: weather.sh [location]
# If no location is provided, uses GeoIP to determine location
# Output: JSON with weather data or error
diff --git a/scripts/wf-record.sh b/scripts/wf-record.sh
index 1da78cc6..ce4cb8d6 100755
--- a/scripts/wf-record.sh
+++ b/scripts/wf-record.sh
@@ -68,7 +68,7 @@ trap cleanup EXIT
if [ "$AUDIO_OUTPUT" = true ] && [ "$AUDIO_INPUT" = true ]; then
# Both: Create temporary mixed sink
- SINK_NAME="ambxst_record_sink_$$"
+ SINK_NAME="nothingless_record_sink_$$"
# Load null sink
MOD_SINK=$(pactl load-module module-null-sink media.class=Audio/Sink sink_name=$SINK_NAME channel_map=stereo)
diff --git a/shell.qml b/shell.qml
old mode 100644
new mode 100755
index 44a22dd5..be403e99
--- a/shell.qml
+++ b/shell.qml
@@ -1,10 +1,11 @@
//@ pragma UseQApplication
-//@ pragma ShellId ambxst
-//@ pragma DataDir $BASE/ambxst
-//@ pragma StateDir $BASE/ambxst
+//@ pragma ShellId nothingless
+//@ pragma DataDir $BASE/nothingless
+//@ pragma StateDir $BASE/nothingless
import QtQuick
import Quickshell
+import Quickshell.Io
import Quickshell.Wayland
import qs.modules.bar
import qs.modules.bar.workspaces
@@ -177,6 +178,10 @@ ShellRoot {
id: compositorConfig
}
+ CompositorKeybinds {
+ id: compositorKeybinds
+ }
+
// Screenshot tool
Variants {
model: Quickshell.screens
@@ -287,6 +292,7 @@ ShellRoot {
let _ = CaffeineService.inhibit;
_ = IdleService.lockCmd; // Force init
_ = GlobalShortcuts.appId; // Force init (IPC pipe listener)
+ _ = BatteryAlertService.enabled; // Force init (battery notifications)
});
}
}
@@ -300,4 +306,68 @@ ShellRoot {
_ = GameModeService.toggled;
}
}
+
+ // --- Boot Splash (NOTHING animation with chroma key) ---
+ Loader {
+ id: bootSplash
+ active: true
+ sourceComponent: Component {
+ Variants {
+ model: Quickshell.screens
+ PanelWindow {
+ required property var modelData
+ screen: modelData
+ anchors { top: true; left: true; right: true; bottom: true }
+ color: "#000000"
+ WlrLayershell.layer: WlrLayer.Overlay
+ WlrLayershell.namespace: "nothingless:splash-overlay"
+ exclusionMode: ExclusionMode.Ignore
+
+ Rectangle {
+ id: splashBg
+ anchors.fill: parent
+ color: "#000000"
+
+ AnimatedImage {
+ id: splashAnim
+ anchors.centerIn: parent
+ width: Math.min(parent.width, parent.height) * 0.6
+ height: width
+ source: "assets/nothingless/NOTHING_splash.webp"
+ fillMode: Image.PreserveAspectFit
+ playing: true
+ currentFrame: 0
+ }
+
+ // Fade out and destroy after animation
+ opacity: splashVisible ? 1.0 : 0.0
+ Behavior on opacity { NumberAnimation { duration: 800 } }
+
+ property bool splashVisible: true
+ property bool splashEnded: false
+
+ Timer {
+ interval: 4500
+ running: true
+ onTriggered: {
+ splashBg.splashVisible = false
+ }
+ }
+
+ Timer {
+ interval: 5300
+ running: true
+ onTriggered: {
+ splashAnim.playing = false
+ splashAnim.source = ""
+ bootSplash.active = false
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // toggle-metrics bind is in the sourced config (cli.sh) and managed by the config system
}
diff --git a/version b/version
old mode 100644
new mode 100755
index 65087b4f..e25d8d9f
--- a/version
+++ b/version
@@ -1 +1 @@
-1.1.4
+1.1.5