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 @@

-Ambxst Logo -
-
-An Axtremely customizable shell. + NothingLess +

+ A high-performance, deeply customizable Wayland shell built with Quickshell. +

+ Forked from Ambxst β€” less is more.

-

- - GitHub stars - - - Ko-Fi +

+ + repo - - Discord + + fork

--- -

Camera with Flash Screenshots

+## Screenshots + +

+ NothingLess Settings +    + NothingLess Gaming +

+ +--- + +## 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). --- -

Package 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 | -

Sparkles 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
+ +
+
+
+ NOTHINGLESS +
+
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