125 Commits

Author SHA1 Message Date
81b051c95d Removal: Drop QT_QUICK_CONTROLS_STYLE (hyprland-qt-support) env
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 09:24:49 +01:00
e563ec22d2 Feature: Shorter bar labels on narrow screens
Bar.qml switches ActiveWindow and MediaPlayer to tighter truncation
limits when the bar is narrower than compactBarWidth (1600px), so on
the laptop's eDP-1 they don't crowd the centre clock/weather group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 09:24:49 +01:00
7d9905f73b Fix: Make system/install.sh report what it did and stop adding blank lines
Prints whether options.conf and the pacman.conf Include were installed or
already in place, then the options pacman ends up using. Dropping an old
Include now also drops the blank line written after it, so re-runs leave
pacman.conf untouched instead of adding a blank line and rewriting it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:57:35 +01:00
7f11abf50f Feature: Pac-Man progress bars, version table and 10 parallel downloads in pacman
Turns on ILoveCandy and VerbosePkgLists and raises ParallelDownloads to 10
in system/pacman/options.conf. install.sh now puts the Include at the end of
[options] instead of straight after the header, so these override
pacman.conf's own defaults (its ParallelDownloads = 5 would otherwise win),
and moves an existing Include there when re-run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:53:36 +01:00
37c03fa03c Feature: Make Ghostty the default terminal
Super+Return, $TERMINAL (rofi-sensible-terminal and scripts) and Thunar's
"Open Terminal Here" (exo helper + helpers.rc) now open Ghostty; foot stays
installed as the fallback. Also replaces Ghostty's deprecated
gtk-tabs-location = hidden with window-show-tab-bar = never.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:50:20 +01:00
20873c1ca6 Merge pull request 'Fix: Known issues from the dotfiles documentation pass' (#8) from thomas/fixes/known_issues into main
Reviewed-on: #8
2026-10-22 08:29:48 +01:00
6e1ea8ffc4 Removal: Delete pre-Lua Hyprland config (hyprland.conf, UserConfigs, colours, animations)
hyprland.lua and its modules replaced all of these in the Lua migration;
nothing loads or reads them any more.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:19:12 +01:00
3eb8f6027b Fix: Toggle touchpad via hyprctl eval and detect the device automatically
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:18:39 +01:00
9e7d6284eb Fix: Update README program list and link Outline docs, fix fastfetch v2 logo
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:17:25 +01:00
0e9dbf0abc Fix: Stop Refresh.sh relaunching Waybar, drop dead special:nyx rule
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:17:25 +01:00
aefa68f24c Fix: Bootstrap oh-my-zsh, its plugins and the bar's Cascadia font on new installs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:17:25 +01:00
02adb17b4e Fix: Install hyprlock's SF Pro fonts through stow so fontconfig finds them
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:17:19 +01:00
353d77c4fb Fix: Only set NVIDIA env vars when the NVIDIA driver is loaded, drop duplicate Qt theme var
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:17:19 +01:00
3931597aca Fix: Use Bibata-Modern-Classic everywhere and add it to packages
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:17:13 +01:00
206bf15bf4 Fix: Style clipboard menu with the launcher theme
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:17:13 +01:00
818971b29c Fix: Rewrite animation picker to switch Lua presets
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:17:13 +01:00
f861c7c31c Fix: Autostart Plexamp from plexamp-bin's real binary
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:17:13 +01:00
6a8c8c92d8 Fix: Set zoom via hyprctl eval and launch installed HyprMod from keybinds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:17:13 +01:00
e23ce6647f Fix: Use Lua dispatch syntax for log out and DPMS, repair swaync power/lock buttons
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:17:13 +01:00
5c6dad3e91 Feature: Run system updater in Ghostty with floating window rule
Open the update terminal in Ghostty, offering to install it via pkexec
when missing and falling back to foot if declined. Add the
system-update-terminal window rule the script already referred to, so
the window floats centred at 1000x650 instead of tiling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:23:24 +01:00
5827f18d05 Feature: Add Ghostty config ported from foot
Same font, padding, colours, cursor and Ctrl+C/Ctrl+Shift+C bindings as
foot.ini, so the two can be tried side by side. Ctrl+C copies only with
a selection and interrupts otherwise; TERM falls back to xterm-256color
only over SSH. foot stays the default $term.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:23:24 +01:00
561312c234 Feature: Show public IP in network panel
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:21:09 +01:00
6e567dd1e1 Feature: Open Steam games on headless output while Sunshine streams
Disabled-by-default window rule that sends steam_app windows fullscreen
to workspace 9 (HEADLESS-1), toggled by sunshine-games.sh, so old games
like KOTOR start at the stream's resolution.
2026-10-22 07:11:14 +01:00
9af1beeaae Feature: Add now-playing media player to bar
Show the current MPRIS track after the workspaces, with a per-source
brand icon (YouTube, Spotify, Plexamp, Twitch...) that greys out while
paused. Clicking it opens a panel with album art, a seek bar, transport
controls and shuffle/repeat.

Also cap the window title and track text by character count rather
than pixel width.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:11:14 +01:00
16a0d12b8f Feature: Add compatibility for kotor 2026-10-22 06:41:45 +01:00
e8113f4366 Fix: Adding all/most hosts 2026-10-22 06:40:05 +01:00
ea11fe0eb6 Feature: Make home UniFi VPN row a collapsible card
Put the Replace config option behind an expand chevron, like Mullvad's
options, and frame the card while it's expanded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:07:01 +01:00
19e7d3f673 Feature: Add config import button for home UniFi VPN
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:45:12 +01:00
bf86ffe258 Feature: Add gavin homelab host to SSH config
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:41:26 +01:00
8d73d2a37e Feature: Add home UniFi WireGuard tunnel to VPN panel
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 20:40:23 +01:00
54b91a72e2 Feature: Add new-install package review list
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:24:30 +01:00
788d4939f0 Fix: Drop plain Ctrl+V paste binding in foot
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:23:41 +01:00
9a038b74c3 Fix: Restart portals after env import so screen sharing works
xdg-desktop-portal-hyprland could start before Hyprland imported
WAYLAND_DISPLAY/XDG_CURRENT_DESKTOP, leaving no ScreenCast backend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:23:41 +01:00
954dedf7cd Fix: Scale bar volume scroll by distance for touchpads
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 03:40:27 +01:00
90d0e2bed0 Feature: Show Plexamp icon in media notifications
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 03:37:01 +01:00
14f72af3f2 Fix: Fall back to grim/slurp for screenshots without hyprshot
Super+Print and Super+Shift+Print use hyprshot when installed and
grim/slurp otherwise. Add Super+Shift+S (area) and Super+F6 (full
screen) for keyboards without a Print key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 03:31:57 +01:00
836848dedd Fix: Bind workspace number keys by name in Lua config
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 02:26:10 +01:00
fbaffc0816 Fix: Use Lua dispatch syntax for bar workspace buttons
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 02:26:10 +01:00
ccae379490 Feature: Ctrl+C/Ctrl+V copy and paste in foot
Ctrl+C and Ctrl+V now copy and paste like in other apps. The interrupt
signal (^C) moves to Ctrl+Shift+C.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 06:33:49 +01:00
7f0fad5922 Fix: Run Plex under XWayland to stop a blank window
hypr env sets QT_QPA_PLATFORM="wayland;xcb", and under Wayland Plex's
bundled QtWebEngine never loads the web client, so the window stays
blank. Both the PATH wrapper and the .desktop entry now force xcb, as
upstream does, and set XCURSOR_SIZE=24 so the X11 cursor isn't huge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 06:33:12 +01:00
b40d4a9fa4 Fix: Point Sunshine tray link at new tailnet domain
The tailnet's MagicDNS suffix changed from twhomelab.local to
twhomelab.tailnet, so the Sunshine web UI link now opens
xol.twhomelab.tailnet:47990.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 06:33:12 +01:00
19d6936aa6 Feature: Enable hypridle without locking during Sunshine streams
Sunshine's global_prep_cmd now runs hypr/scripts/SunshineIdle.sh, which
holds a systemd idle inhibitor for the length of a stream (hypridle
respects it) and unlocks hyprlock if the session was already locked.
With that in place hypridle starts at login again.

Sunshine's config (sunshine.conf, apps.json, prep scripts) moves into
the dotfiles; credentials, logs and state stay local and are gitignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:14:44 +01:00
4a57e9902f Feature: Bootstrap script for new installs
bootstrap.sh installs everything listed in system/packages.txt (pacman,
AUR via yay, flatpak, official install scripts), auto-detects CPU
microcode and GPU drivers, enables services, links dotfiles with stow
and runs system/install.sh. Optional --svt-av1 installs
svt-av1-essential-git and rebuilds ffmpeg against it via
system/rebuild-ffmpeg.sh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:10:27 +01:00
1201e6a4cc Feature: Sync dotfiles on system update
update-system.sh now runs sync-dotfiles.sh first, which pulls the repo
(fast-forward only) and re-stows it so both machines pick up config changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:34:07 +01:00
626c7dbcc1 Feature: Move volume OSD to bottom-center and enlarge slightly
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:28:55 +01:00
35374800bf Fix: Volume keys use wpctl instead of missing pamixer
Quickshell's VolumeOsd now shows changes, so drop notify-send popups.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:28:55 +01:00
7cb7f10ff6 Fix: Only create Sunshine headless output on desktop (no battery)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:10:21 +01:00
88484f8495 Remove: Drop orbit, network click opens nm-connection-editor
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:10:21 +01:00
1babf2e334 Feature: Shared pacman options installed via system/install.sh
Enables Color through an Include snippet in /etc/pacman.d so pacman
options stay in sync between machines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:10:21 +01:00
cc78daea77 Merge branch 'main' of ssh://gitea.thomasleewilliams.co.uk:2222/thomas/dotfiles 2026-10-11 16:52:33 +01:00
0415804b67 applying changes 2026-10-11 16:50:54 +01:00
75aa6b945b UI: Remove reduced opacity on unfocused windows
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:46:06 +01:00
8b65136048 Feature: Add ForwardAgent for passing SSH Keys 2026-10-11 02:05:54 +01:00
4bd3d327c6 Feature: Seafile starts on login and is pinned in the tray
Launches seafile-applet from startup.lua and adds it to PinnedTray between Bitwarden and Sunshine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 10:49:04 +01:00
786a3d191a Fix: Typo in Hyprland startup.lua stopped startup apps launching
The typo caused a Lua error on login, so the rest of the startup handler never ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 10:38:24 +01:00
ad5efc9597 Feature: cl alias now runs fastfetch after clear
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:53:56 +01:00
850da708c2 UI: Update icon no longer highlights when updates are available
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:53:56 +01:00
de39c40460 Feature: Added Videos bookmark
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:53:56 +01:00
72db6ca784 Fix: Disabled VRR to stop flickering on Acer monitor
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:53:56 +01:00
84828e58bc Feature: Use Bitwarden SSH agent for GUI apps
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:53:56 +01:00
26b7bf4ecc Feature: Enabled NVIDIA environment variables
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:53:56 +01:00
5b58c27e80 Fix: Removed Comments 2026-09-06 02:06:00 +01:00
82be653c04 UI: Corrected Sizing 2026-09-06 01:02:45 +01:00
d395a155af Feature: Added Weather widget 2026-09-06 01:00:25 +01:00
243c90a809 Features: Added Update functionality 2026-09-06 00:35:20 +01:00
38df0301f2 Feature: Icon does not appear unless installed 2026-09-05 23:10:26 +01:00
714fadc997 Fix: Corrected opened URL and Added assets for Sunshine 2026-09-05 23:03:02 +01:00
99e2225b51 UI: Made Bitwarden correct size 2026-09-05 22:58:20 +01:00
4aaa1d2d98 UI: Fixes Sizes 2026-09-05 22:16:10 +01:00
14b7e748d4 Feature: Add pinned tray for Bitwarden and Sunshine
Replaces the disabled generic system tray with a curated PinnedTray
showing just Bitwarden and Sunshine. Each shows its live tray icon
(and forwards clicks to it) while running; when not running it shows
a dimmed static icon and launches the app on click instead.

Right-clicking Sunshine specifically opens its web UI in the browser
rather than forwarding to secondaryActivate(), since that's how it's
actually configured/used.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 22:10:21 +01:00
a0b9ea29fe Feature: Added options for mullvad vpn 2026-09-05 21:56:42 +01:00
007843db6e Fix: Not to render on display with HEADLESS 2026-09-05 21:55:57 +01:00
764652d826 Feature: Added new window for steam big picture 2026-09-05 21:55:16 +01:00
8dedfe30fa Merge pull request 'Removal: Stopped Waybar from starting' (#7) from maintinance/remove_waybar into main
Reviewed-on: #7

granted
2026-08-27 15:02:44 +01:00
bc54a4b5b7 Removal: Stopped Waybar from starting 2026-08-27 15:01:45 +01:00
d306270dc8 Merge pull request 'features/adding_quickshell' (#6) from features/adding_quickshell into main
Reviewed-on: #6

tested on home laptop
tested in home PC

will need to make install scripts for process

approved
2026-08-27 14:55:29 +01:00
03b043d9d3 Merge branch 'features/adding_quickshell' of ssh://gitea.thomasleewilliams.co.uk:2222/thomas/dotfiles into features/adding_quickshell 2026-08-27 14:51:01 +01:00
1ff92f3b79 Merge pull request 'features/monitor_options' (#5) from features/monitor_options into features/adding_quickshell
Reviewed-on: #5
2026-08-27 14:46:07 +01:00
e5809efc6a UI: Moved Updates to bluetooth devices inside bluetooth box 2026-08-27 14:17:03 +01:00
c64cf5ff12 Fix: Restored "time to charged" 2026-08-27 14:11:27 +01:00
d6f79685f0 Feature: Extra functionablity to bluetooth actions 2026-08-27 14:07:52 +01:00
9e532023bd Fix: Corrected battery behavior 2026-08-27 14:06:21 +01:00
c2c6675f4b Feature: Added battery percentage/view 2026-08-27 13:18:46 +01:00
fcce5f304b UI: Fixed hover colours 2026-08-27 13:02:34 +01:00
4b0b5c72ba UI: Fixed padding error 2026-08-27 13:02:02 +01:00
3f4e3514a2 Fix: Icons now map correctly to device 2026-08-27 11:45:14 +01:00
622afb2100 UI: fixed selectors & added spacing 2026-08-27 11:28:57 +01:00
3ae5c57303 Feature: Added Volume Sink 2026-08-27 11:21:41 +01:00
ef06a96757 UI: new header & current connected interface 2026-08-27 11:13:09 +01:00
03ea182c8c Feature: Added wifi scanning indicator 2026-08-27 10:53:56 +01:00
ba8a565cda Feature: added scanner indicator 2026-08-27 10:45:50 +01:00
c8a9c30fc5 Fix: hover button fix 2026-08-27 10:45:36 +01:00
185b752393 Fix: removed previous border 2026-08-27 10:23:13 +01:00
8515f3f5ce Fix: Added borders to selectors 2026-08-27 10:22:51 +01:00
86921f7aab Feature: new selectors 2026-08-27 09:39:32 +01:00
cede2a1690 Feature: Password input 2026-08-27 09:18:57 +01:00
00e4368abe Fix: corrected icons 2026-08-27 09:18:42 +01:00
00d7dcb411 Fix: changed icon for power 2026-08-27 08:50:29 +01:00
4bdb590899 Feature: added connected indicator to VPN 2026-08-27 08:48:08 +01:00
44e73f5a9d Fix: corrected padding for bar icon 2026-08-27 08:17:37 +01:00
3184c61267 Fix: increased icon size 2026-08-27 08:16:51 +01:00
fb79f0a37e Fix: corrected wifi sizing 2026-08-27 08:12:00 +01:00
7a1073646a Fix: changed sizing of logo to be consistant 2026-08-27 07:49:36 +01:00
90d068a878 Fix: fixed tailscale logo sizing to match others 2026-08-27 07:48:27 +01:00
57e5c0c7b3 Fixes: removed extra padding on icon 2026-08-27 07:42:37 +01:00
b7a92c9a59 Changes: replaced icons with material icons 2026-08-27 07:40:53 +01:00
d3856ada0d utility: checking all dependencies 2026-08-27 00:38:50 +01:00
3601f52600 Merge pull request 'feature: float bitwarden extension window' (#4) from features/floating_bitwarden_extension into features/adding_quickshell
fine by me
2026-08-26 21:09:06 +01:00
98f9a77b23 feature: float bitwarden extension window 2026-08-26 21:05:03 +01:00
bc242ae956 updated to add quickshell 2026-08-26 20:21:08 +01:00
4237b2ed9e Merge pull request 'migration: lua' (#2) from features/hypr_lua_config into features/adding_quickshell
Reviewed-on: #2

pulling into changes
2026-08-26 20:17:28 +01:00
7b0cf9934b migration: lua 2026-08-26 20:14:43 +01:00
9df5731ee1 changed volume location and widget colours 2026-08-25 09:35:22 +01:00
3806935316 implimented multiple VPN connectors 2026-08-25 09:34:55 +01:00
516f8eed82 removed tray 2026-08-25 09:34:19 +01:00
369179c057 added vpn exclusitivity to allow functions to coexist 2026-08-25 09:34:04 +01:00
322804577f added vpn exclusitivity to allow functions to coexist 2026-08-25 09:33:45 +01:00
0eee14858b basic tailscale widget 2026-08-25 07:25:38 +01:00
461f4c3fff added tailscale widget 2026-08-25 07:25:27 +01:00
2f9dfda410 added initial bluetooth widget 2026-08-24 15:12:27 +01:00
e35ceb1450 added bluetooth widget 2026-08-24 15:10:55 +01:00
3d0a18866f implimented network widget 2026-08-24 15:07:18 +01:00
6c415d6fbb added starting funtionality 2026-08-24 15:06:58 +01:00
c4b4a3bd26 added quickshell config 2026-08-24 10:03:59 +01:00
866ee0bb9b Merge pull request 'features/updating_fonts' (#1) from features/updating_fonts into main
Reviewed-on: #1
2026-08-21 09:22:44 +01:00
140 changed files with 10115 additions and 2849 deletions

View File

@@ -39,13 +39,29 @@ mkdir Projects
git clone ssh://git@gitea.thomasleewilliams.co.uk:2222/thomas/dotfiles.git
```
Once that is complete, run this command:
On a fresh install, run the bootstrap script. It installs drivers (auto-detected),
every package in `system/packages.txt`, yay, flatpaks, then links the dotfiles and
system config below:
```console
~/Projects/dotfiles/bootstrap.sh # asks about the optional SVT-AV1 + ffmpeg rebuild
~/Projects/dotfiles/bootstrap.sh --svt-av1 # or --no-svt-av1 to skip the question
```
To only link the dotfiles, run this command:
```console
stow -d ~/Projects/dotfiles/ -t ~/ dotfiles
```
This will sync wallpapers/config files to where they are usually located
System-level config (e.g. pacman options) lives in `system/` and is installed with sudo:
```console
~/Projects/dotfiles/system/install.sh
```
Full documentation (every config, keybinding and script) lives in the Outline wiki's
**Dotfiles** collection: https://outline.twhomelab.co.uk/collection/dotfiles-frMIfJi2Hf
## 📦 Programs
List of programs and tools I use.
@@ -54,12 +70,12 @@ List of programs and tools I use.
| Component | Program |
|-------------------|------------|
|🪟 Windows Manager | [hyprland](https://github.com/hyprwm/Hyprland) |
|🖥️ Terminal | [foot](https://codeberg.org/dnkl/foot) |
|🐚 Shell | [zsh](https://github.com/ohmyzsh/ohmyzsh/wiki/Installing-ZSH) / [theme](https://github.com/ohmyzsh/ohmyzsh/blob/master/themes/refined.zsh-theme) |
|🖥️ Terminal | [ghostty](https://ghostty.org/) / [foot](https://codeberg.org/dnkl/foot) (fallback) |
|🐚 Shell | [zsh](https://github.com/ohmyzsh/ohmyzsh/wiki/Installing-ZSH) + oh-my-zsh / [oh-my-posh](https://ohmyposh.dev/) prompt (`.config/ohmyposh/zen.toml`) |
|📁 File Manager | [thunar](https://wiki.archlinux.org/title/Thunar) |
|📝 Editor | [vs code](https://github.com/microsoft/vscode) / [neovim (changing to soon)](https://github.com/neovim/neovim) |
|🌐 Browser | [zen](https://github.com/zen-browser/desktop) / [firefox](https://github.com/topics/firefox-browser) / [startpage](https://blog-bucket-personal.s3.eu-west-2.amazonaws.com/Screenshot_02-May_08-44-23_6614.png) |
|📊 Bar | [waybar](https://github.com/Alexays/Waybar) |
|📊 Bar | [quickshell](https://quickshell.org/) (waybar retired, config kept) |
|🚀 Launcher | [rofi](https://github.com/davatorium/rofi) |
|🔒 Lockscreen | [hyprlock](https://github.com/hyprwm/hyprlock) |
|🚪 Login Menu | [sddm](https://github.com/sddm/sddm) |

189
bootstrap.sh Executable file
View File

@@ -0,0 +1,189 @@
#!/usr/bin/env bash
# Sets up a fresh Arch install: drivers, packages, dotfiles and system config.
# Safe to re-run; anything already installed is skipped.
#
# Usage: ./bootstrap.sh [--svt-av1 | --no-svt-av1]
# --svt-av1 also install svt-av1-essential-git and rebuild ffmpeg against it
# --no-svt-av1 skip that without asking
# (neither) ask
set -euo pipefail
repo="$(cd "$(dirname "$0")" && pwd)"
list="$repo/system/packages.txt"
svt_av1=ask
for arg in "$@"; do
case "$arg" in
--svt-av1) svt_av1=yes ;;
--no-svt-av1) svt_av1=no ;;
*) echo "Unknown option: $arg" >&2; exit 1 ;;
esac
done
if (( EUID == 0 )); then
echo "Run this as your normal user, not root (it uses sudo where needed)." >&2
exit 1
fi
step() { printf '\n\033[1;34m==> %s\033[0m\n' "$*"; }
# Prints the packages under a [section] of packages.txt, skipping comments
packages() {
awk -v want="[$1]" '
/^\[/ { section = $1; next }
section == want { sub(/#.*/, ""); if (NF) print $1 }
' "$list"
}
# --- System config -----------------------------------------------------------
step "System config (pacman options)"
"$repo/system/install.sh"
step "Enabling multilib (needed for Steam and 32-bit drivers)"
if ! grep -q '^\[multilib\]' /etc/pacman.conf; then
sudo sed -i '/^#\[multilib\]/,/^#Include/ s/^#//' /etc/pacman.conf
fi
sudo pacman -Syu --needed --noconfirm pciutils git base-devel
# --- Hardware ----------------------------------------------------------------
step "Detecting CPU and GPU"
hw=()
case "$(grep -m1 '^vendor_id' /proc/cpuinfo | awk '{print $3}')" in
AuthenticAMD) hw+=(amd-ucode); echo "CPU: AMD" ;;
GenuineIntel) hw+=(intel-ucode); echo "CPU: Intel" ;;
*) echo "CPU: unknown vendor, skipping microcode" ;;
esac
# Vendor column of every display controller, e.g. "NVIDIA Corporation"
gpus="$(lspci -mm | awk -F'"' '$2 ~ /VGA|3D|Display/ { print $4 }')"
if grep -q NVIDIA <<<"$gpus"; then
echo "GPU: NVIDIA"
# nvidia-open only ships modules for the stock `linux` kernel; any other
# kernel needs the DKMS build. Supports Turing (GTX 16xx / RTX 20xx) and newer.
if pacman -Qq linux-lts linux-zen linux-hardened >/dev/null 2>&1; then
hw+=(nvidia-open-dkms linux-headers)
else
hw+=(nvidia-open)
fi
hw+=(nvidia-utils lib32-nvidia-utils libva-nvidia-driver)
fi
if grep -q 'Advanced Micro Devices' <<<"$gpus"; then
echo "GPU: AMD"
hw+=(mesa lib32-mesa vulkan-radeon lib32-vulkan-radeon)
fi
if grep -q 'Intel Corporation' <<<"$gpus"; then
echo "GPU: Intel"
hw+=(mesa lib32-mesa vulkan-intel lib32-vulkan-intel intel-media-driver)
fi
# Installed before the main list so Steam's 32-bit Vulkan dependency is
# already satisfied by the right driver
if (( ${#hw[@]} )); then
sudo pacman -S --needed --noconfirm "${hw[@]}"
fi
# --- Packages ----------------------------------------------------------------
step "Official repo packages"
mapfile -t pkgs < <(packages pacman)
sudo pacman -S --needed --noconfirm "${pkgs[@]}"
step "yay (AUR helper)"
if ! command -v yay >/dev/null; then
build="$(mktemp -d)"
git clone https://aur.archlinux.org/yay-bin.git "$build/yay-bin"
(cd "$build/yay-bin" && makepkg -si --noconfirm)
rm -rf "$build"
else
echo "already installed"
fi
step "AUR packages"
mapfile -t pkgs < <(packages aur)
yay -S --needed --noconfirm "${pkgs[@]}"
step "Flatpak apps"
sudo flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
mapfile -t pkgs < <(packages flatpak)
sudo flatpak install -y --noninteractive flathub "${pkgs[@]}"
step "Apps from official install scripts"
mkdir -p "$HOME/.local/bin"
for app in $(packages installer); do
if command -v "$app" >/dev/null || [[ -x "$HOME/.local/bin/$app" ]]; then
echo "$app: already installed"
continue
fi
case "$app" in
claude) curl -fsSL https://claude.ai/install.sh | bash ;;
oh-my-posh) curl -fsSL https://ohmyposh.dev/install.sh | bash -s -- -d "$HOME/.local/bin" ;;
*) echo "$app: no install recipe in bootstrap.sh, skipping" >&2 ;;
esac
done
step "oh-my-zsh and plugins"
# .zshrc sources ~/.oh-my-zsh/oh-my-zsh.sh and loads two custom plugins from it.
# A plain clone is all oh-my-zsh needs; its installer would also write a template
# ~/.zshrc, which then conflicts with the stowed one.
if [[ ! -d "$HOME/.oh-my-zsh" ]]; then
git clone --depth 1 https://github.com/ohmyzsh/ohmyzsh.git "$HOME/.oh-my-zsh"
else
echo "oh-my-zsh: already installed"
fi
zsh_custom="$HOME/.oh-my-zsh/custom/plugins"
for plugin in zsh-autosuggestions zsh-syntax-highlighting; do
if [[ -d "$zsh_custom/$plugin" ]]; then
echo "$plugin: already installed"
else
git clone --depth 1 "https://github.com/zsh-users/$plugin" "$zsh_custom/$plugin"
fi
done
# --- Optional: SVT-AV1 essential + ffmpeg rebuild ----------------------------
if [[ $svt_av1 == ask ]]; then
read -rp $'\nInstall svt-av1-essential-git and rebuild ffmpeg against it? (slow) [y/N] ' reply
[[ $reply == [yY]* ]] && svt_av1=yes || svt_av1=no
fi
if [[ $svt_av1 == yes ]]; then
step "svt-av1-essential-git"
# Interactive on purpose: pacman asks to replace the conflicting svt-av1,
# and --noconfirm would answer no
yay -S --needed svt-av1-essential-git
step "Rebuilding ffmpeg against svt-av1-essential-git"
"$repo/system/rebuild-ffmpeg.sh"
fi
# --- Services ----------------------------------------------------------------
step "Enabling services"
# Not started now: sddm would take over the screen mid-script. They come up on reboot.
mapfile -t units < <(packages services)
sudo systemctl enable "${units[@]}"
# --- Dotfiles ----------------------------------------------------------------
step "Linking dotfiles with stow"
# Create real parent dirs first, otherwise stow symlinks the whole directory
# (e.g. ~/.config) into the repo and every app's files end up in git
mkdir -p "$HOME/.config" "$HOME/.local/bin" "$HOME/.local/share/fonts" "$HOME/Pictures"
# Sunshine keeps credentials and logs beside its config; those must stay out of git
mkdir -p "$HOME/.config/sunshine"
mkdir -p -m 700 "$HOME/.ssh"
if ! stow -d "$repo" -t "$HOME" --restow dotfiles; then
echo "stow hit conflicts (listed above). Move those files aside, then run:" >&2
echo " stow -d \"$repo\" -t \"\$HOME\" dotfiles" >&2
fi
if [[ "$(getent passwd "$USER" | cut -d: -f7)" != */zsh ]]; then
step "Setting zsh as login shell"
chsh -s /usr/bin/zsh
fi
# Picks up the stowed fonts in ~/.local/share/fonts (SF Pro Display for hyprlock)
fc-cache -f "$HOME/.local/share/fonts" >/dev/null
step "Done — reboot to load drivers and start Hyprland"

View File

@@ -0,0 +1 @@
SSH_AUTH_SOCK=${HOME}/.bitwarden-ssh-agent.sock

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json",
"logo": {
//"source": "~/.config/fastfetch/nixos.png",
//"source": "~/.config/fastfetch/arch.png",
//"type": "kitty-direct",
"height": 15,
"width": 30,

View File

@@ -47,3 +47,11 @@ regular4=53BDFA # blue
regular5=FAE994 # magenta
regular6=99faf2 # cyan
regular7=C7C7C7 # white
[key-bindings]
clipboard-copy=Control+c XF86Copy
clipboard-paste=Control+Shift+v XF86Paste
[text-bindings]
# Ctrl+C now copies, so send the interrupt signal (^C) from Ctrl+Shift+C instead
\x03=Control+Shift+c

View File

@@ -0,0 +1,67 @@
# Ported from foot/foot.ini — same look and bindings, so the two can be
# tried side by side. Ghostty is now the default terminal ($term in
# hypr/input/keybinds.lua). Check edits with `ghostty +validate-config`.
# --- Font / window ---
font-family = JetBrainsMono Nerd Font
font-size = 9
# foot: pad=12x12 center
window-padding-x = 12
window-padding-y = 12
window-padding-balance = true
# foot: initial-window-size-pixels=1000x520. Ghostty sizes in cells, not
# pixels — this is roughly the same at this font size. Only matters for
# floating windows; tiled ones fill their slot either way.
window-width = 135
window-height = 31
# No title bar or tab strip — Hyprland draws the borders. (window-show-tab-bar
# replaces the deprecated gtk-tabs-location = hidden.)
window-decoration = none
window-show-tab-bar = never
# --- Terminal type ---
# foot forced TERM=xterm-256color everywhere so SSH'd hosts wouldn't lack
# terminfo. Ghostty keeps its own richer TERM locally and only falls back
# to xterm-256color (or installs its terminfo) when you ssh somewhere.
shell-integration-features = ssh-env,ssh-terminfo
# --- Scrollback / mouse ---
# foot kept 5000 lines; Ghostty's limit is in bytes and its 10MB default
# is already well beyond that, so it's left alone.
mouse-scroll-multiplier = 2
mouse-hide-while-typing = false
# URLs: Ctrl+click opens them with xdg-open by default, like foot's url-mode.
# --- Cursor ---
cursor-style = bar
cursor-style-blink = true
adjust-cursor-thickness = 1
# --- Colours ---
background-opacity = 0.80
background = #0a0e14
foreground = #fffbf6
selection-foreground = #b3b1ad
selection-background = #b3b1ad
palette = 0=#01060e
palette = 1=#eb4129
palette = 2=#abe047
palette = 3=#f9af4f
palette = 4=#53bdfa
palette = 5=#fae994
palette = 6=#99faf2
palette = 7=#c7c7c7
# --- Keybindings ---
# foot: Ctrl+C copies, Ctrl+Shift+C sends the interrupt. "performable:"
# makes Ctrl+C copy only when there's a selection — with nothing selected
# it falls through and interrupts as normal. Ctrl+Shift+C still sends ^C
# too, for muscle memory.
keybind = performable:ctrl+c=copy_to_clipboard
keybind = ctrl+shift+c=text:\x03
# Ctrl+Shift+V paste is Ghostty's default already.

View File

@@ -3,5 +3,6 @@ file:///home/thomas/Documents
file:///home/thomas/Downloads
file:///home/thomas/Pictures
file:///home/thomas/Music
file:///home/thomas/Videos
smb://192.168.1.84/media/
smb://192.168.1.85/music/

View File

@@ -2,7 +2,7 @@
gtk-theme-name=HyprFlux-Compact
gtk-icon-theme-name=Papirus-Dark
gtk-font-name=Adwaita Sans 11
gtk-cursor-theme-name=Future-black Cursors
gtk-cursor-theme-name=Bibata-Modern-Classic
gtk-cursor-theme-size=24
gtk-toolbar-style=GTK_TOOLBAR_ICONS
gtk-toolbar-icon-size=GTK_ICON_SIZE_LARGE_TOOLBAR

View File

@@ -2,6 +2,6 @@
gtk-theme-name=HyprFlux-Compact
gtk-icon-theme-name=Papirus-Dark
gtk-font-name=Adwaita Sans 11
gtk-cursor-theme-name=Future-black Cursors
gtk-cursor-theme-name=Bibata-Modern-Classic
gtk-cursor-theme-size=24
gtk-application-prefer-dark-theme=1

View File

@@ -1,6 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# HyprFlux Dots
Settings have been massivily modified as to work in a more simple mannor
I will update the Hyprland-Dots wiki for guidance. Make sure to check out changelogs as well

View File

@@ -1,89 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# Environment variables. See https://wiki.hyprland.org/Configuring/Environment-variables/
# Set your defaults editor through ENV in ~/.config/hypr/UserConfigs/01-UserDefaults.conf
# environment-variables
# Toolkit Backend Variables
env = GDK_BACKEND,wayland,x11,*
env = QT_QPA_PLATFORM,wayland;xcb
env = CLUTTER_BACKEND,wayland
#Run SDL2 applications on Wayland.
#Remove or set to x11 if games that provide older versions of SDL cause compatibility issues
#env = SDL_VIDEODRIVER,wayland
# xdg Specifications
env = XDG_CURRENT_DESKTOP,Hyprland
env = XDG_SESSION_DESKTOP,Hyprland
env = XDG_SESSION_TYPE,wayland
# QT Variables
env = QT_AUTO_SCREEN_SCALE_FACTOR,1
env = QT_WAYLAND_DISABLE_WINDOWDECORATION,1
env = QT_QPA_PLATFORMTHEME,qt5ct
env = QT_QPA_PLATFORMTHEME,qt6ct
# hyprland-qt-support
env = QT_QUICK_CONTROLS_STYLE,org.hyprland.style
# xwayland apps scale fix (useful if you are use monitor scaling).
# Set same value if you use scaling in Monitors.conf
# 1 is 100% 1.5 is 150%
# see https://wiki.hyprland.org/Configuring/XWayland/
env = GDK_SCALE,1
env = QT_SCALE_FACTOR,1
# Bibata-Modern-Ice-Cursor
# NOTE! You must have the hyprcursor version to activate this.
# https://wiki.hyprland.org/Hypr-Ecosystem/hyprcursor/
env = HYPRCURSOR_THEME,Bibata-Modern-Classic
env = HYPRCURSOR_SIZE,24
# firefox
env = MOZ_ENABLE_WAYLAND,1
# electron >28 apps (may help) ##
# https://www.electronjs.org/docs/latest/api/environment-variables
env = ELECTRON_OZONE_PLATFORM_HINT,auto # auto selects Wayland if possible, X11 otherwise
# NVIDIA
# This is from Hyprland Wiki. Below will be activated nvidia gpu detected
# See hyprland wiki https://wiki.hyprland.org/Nvidia/#environment-variables
#env = LIBVA_DRIVER_NAME,nvidia
#env = __GLX_VENDOR_LIBRARY_NAME,nvidia
#env = NVD_BACKEND,direct
#env = GSK_RENDERER,ngl
# additional ENV's for nvidia. Caution, activate with care
#env = GBM_BACKEND,nvidia-drm
#env = __GL_GSYNC_ALLOWED,1 #adaptive Vsync
#env = __NV_PRIME_RENDER_OFFLOAD,1
#env = __VK_LAYER_NV_optimus,NVIDIA_only
#env = WLR_DRM_NO_ATOMIC,1
# FOR VM and POSSIBLY NVIDIA
# LIBGL_ALWAYS_SOFTWARE software mesa rendering
#env = LIBGL_ALWAYS_SOFTWARE,1 # Warning. May cause hyprland to crash
#env = WLR_RENDERER_ALLOW_SOFTWARE,1
# nvidia firefox (for hardware acceleration on FF)?
# check this post https://github.com/elFarto/nvidia-vaapi-driver#configuration
#env = MOZ_DISABLE_RDD_SANDBOX,1
#env = EGL_PLATFORM,wayland
#### Aquamarine Environment Variables #### ( Hyprland > 0.45 )
# https://wiki.hyprland.org/Configuring/Environment-variables/#aquamarine-environment-variables----ref-httpsgithubcomhyprwmaquamarineblobmaindocsenvmd---
# env = AQ_TRACE,1 # Enables more verbose logging.
# env = AQ_DRM_DEVICES,/dev/dri/card1:/dev/dri/card0 # Set an explicit list of DRM devices (GPUs) to use. It’s a colon-separated list of paths, with the first being the primary. E.g. /dev/dri/card1:/dev/dri/card0
# env = AQ_MGPU_NO_EXPLICIT,1 # Disables explicit syncing on mgpu buffers
# env = AQ_NO_MODIFIERS,1 # Disables modifiers for DRM buffers
#### Hyprland Environment Variables ####
# https://wiki.hyprland.org/Configuring/Environment-variables/#hyprland-environment-variables
# env = HYPRLAND_TRACE,1 # Enables more verbose logging.
# env = HYPRLAND_NO_RT,1 # Disables realtime priority setting by Hyprland.
# env = HYPRLAND_NO_SD_NOTIFY,1 # If systemd, disables the 'sd_notify' calls.
# env = HYPRLAND_NO_SD_VARS,1 # Disables management of variables in systemd and dbus activation environments.

View File

@@ -1,209 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# This is where you put your own keybinds. Be Mindful to check as well ~/.config/hypr/configs/Keybinds.conf to avoid conflict
# if you think I should replace the Pre-defined Keybinds in ~/.config/hypr/configs/Keybinds.conf , submit an issue or let me know in DC and present me a valid reason as to why, such as conflicting with global shortcuts, etc etc
# See https://wiki.hyprland.org/Configuring/Keywords/ for more settings and variables
# See also Laptops.conf for laptops keybinds
# /* ---- ✴️ Variables ✴️ ---- */ #
$mainMod = SUPER
$scriptsDir = $HOME/.config/hypr/scripts
$UserScripts = $HOME/.config/hypr/UserScripts
# settings for User defaults apps - set your default terminal and file manager on this file
$edit=${EDITOR:-nvim}
# These two are for UserKeybinds.conf & Waybar Modules
$term = foot # Terminal
$files = thunar # File Manager
# Default Search Engine for ROFI Search (SUPER S)
$Search_Engine = "https://www.google.com/search?q={}"
# common shortcuts
bind = $mainMod, SPACE, exec, .config/rofi/launchers/type-4/launcher.sh # desktop overview (if installed)
bind = $mainMod, Q, killactive, # close active (not kill)
bind = $mainMod SHIFT, Q, exec, $scriptsDir/KillActiveProcess.sh # Kill active process
bind = $mainMod, Return, exec, $term #terminal
bind = $mainMod, F, exec, $files #file manager
bind = $mainMod, B, exec, zen-browser # Launch Zen Browser New Everytime
bind = $mainMod, V, exec, $scriptsDir/ClipManager.sh # Clipboard Manager
bind = $mainMod, C, exec, code --ozone-platform=x11 # Visual Studio Code
bind = $mainMod, O, exec, obsidian --ozone-platform=x11 # Obsidian
bind = $mainMod, X, exec, vesktop # Vesktop(Discord)
bind = $mainMod, M, exec, fdm # Free Downlaod Manager
bind = $mainMod, W, exec, waypaper # Wayland Wallpaper Setter
bind = $mainMod, L, exec, hyprlock # Screen Locker
bind = $mainMod, DELETE, exec, ~/.config/rofi/powermenu/type-2/powermenu.sh # Power Menu
bind = $mainMod, S, exec, cd /home/thomas/hyprmod && uv run hyprmod # Hyprmod - A Hyprland settings manager (if installed)
bind = $mainMod SHIFT, R, exec, $scriptsDir/Refresh.sh # Refresh waybar, swaync, rofirrr
bind = $mainMod, N, exec, swaync-client -t -sw # swayNC notification panel
# FEATURES / EXTRAS
bind = $mainMod SHIFT, F, fullscreen # whole full screen
bind = $mainMod SHIFT, SPACE, togglefloating, #Float Mode
# Desktop zooming or magnifier
bind = $mainMod ALT, mouse_down, exec, hyprctl keyword cursor:zoom_factor "$(hyprctl getoption cursor:zoom_factor | awk 'NR==1 {factor = $2; if (factor < 1) {factor = 1}; print factor * 2.0}')"
bind = $mainMod ALT, mouse_up, exec, hyprctl keyword cursor:zoom_factor "$(hyprctl getoption cursor:zoom_factor | awk 'NR==1 {factor = $2; if (factor < 1) {factor = 1}; print factor / 2.0}')"
## NOTES for ja (Hyprland version 0.39 (Ubuntu 24.04))
#bind = $mainMod ALT, mouse_down, exec, hyprctl keyword misc:cursor_zoom_factor "$(hyprctl getoption misc:cursor_zoom_factor | awk 'NR==1 {factor = $2; if (factor < 1) {factor = 1}; print factor * 2.0}')"
#bind = $mainMod ALT, mouse_up, exec, hyprctl keyword misc:cursor_zoom_factor "$(hyprctl getoption misc:cursor_zoom_factor | awk 'NR==1 {factor = $2; if (factor < 1) {factor = 1}; print factor / 2.0}')"
# Waybar / Bar related
bind = $mainMod CTRL ALT, B, exec, pkill -SIGUSR1 waybar # Toggle hide/show waybar
# FEATURES / EXTRAS (UserScripts)
bind = $mainMod SHIFT, A, exec, $scriptsDir/Animations.sh #hyprland animations menu
bind = $mainMod SHIFT, Z, exec, $UserScripts/ZshChangeTheme.sh # Change oh-my-zsh theme
#######################################################################################################################################
#Default Keybinds#
#######################################################################################################################################
bind = CTRL ALT, Delete, exec, hyprctl dispatch exit 0 # exit Hyprland
# Master Layout
bind = $mainMod CTRL, D, layoutmsg, removemaster
bind = $mainMod, I, layoutmsg, addmaster
bind = $mainMod, J, layoutmsg, cyclenext
bind = $mainMod, K, layoutmsg, cycleprev
bind = $mainMod CTRL, Return, layoutmsg, swapwithmaster
# Dwindle Layout
bind = $mainMod, P, pseudo, # dwindle
# Special Keys / Hot Keys
bindel = , xf86audioraisevolume, exec, $scriptsDir/Volume.sh --inc # volume up
bindel = , xf86audiolowervolume, exec, $scriptsDir/Volume.sh --dec # volume down
bindl = , xf86AudioMicMute, exec, $scriptsDir/Volume.sh --toggle-mic # mic mute
bindl = , xf86audiomute, exec, $scriptsDir/Volume.sh --toggle # mute
bindl = , xf86Sleep, exec, systemctl suspend # sleep button
bindl = , xf86Rfkill, exec, $scriptsDir/AirplaneMode.sh # Airplane mode
# media controls using keyboards
bindl = , xf86AudioPlayPause, exec, $scriptsDir/MediaCtrl.sh --pause
bindl = , xf86AudioPause, exec, $scriptsDir/MediaCtrl.sh --pause
bindl = , xf86AudioPlay, exec, $scriptsDir/MediaCtrl.sh --pause
bindl = , xf86AudioNext, exec, $scriptsDir/MediaCtrl.sh --nxt
bindl = , xf86AudioPrev, exec, $scriptsDir/MediaCtrl.sh --prv
bindl = , xf86audiostop, exec, $scriptsDir/MediaCtrl.sh --stop
# Resize windows
binde = $mainMod SHIFT, left, resizeactive,-50 0
binde = $mainMod SHIFT, right, resizeactive,50 0
binde = $mainMod SHIFT, up, resizeactive,0 -50
binde = $mainMod SHIFT, down, resizeactive,0 50
# Move windows
bind = $mainMod CTRL, left, movewindow, l
bind = $mainMod CTRL, right, movewindow, r
bind = $mainMod CTRL, up, movewindow, u
bind = $mainMod CTRL, down, movewindow, d
# Swap windows
bind = $mainMod ALT, left, swapwindow, l
bind = $mainMod ALT, right, swapwindow, r
bind = $mainMod ALT, up, swapwindow, u
bind = $mainMod ALT, down, swapwindow, d
# Move focus with mainMod + arrow keys
bind = $mainMod, left, movefocus, l
bind = $mainMod, right, movefocus, r
bind = $mainMod, up, movefocus, u
bind = $mainMod, down, movefocus, d
# Workspaces related
bind = $mainMod, tab, workspace, m+1
bind = $mainMod SHIFT, tab, workspace, m-1
# Special workspace
# bind = $mainMod SHIFT, U, movetoworkspace, special
# bind = $mainMod, U, togglespecialworkspace,
bind = $mainMod, U, togglespecialworkspace, nyx
bind = $mainMod SHIFT, U, movetoworkspace, special:nyx
# The following mappings use the key codes to better support various keyboard layouts
# 1 is code:10, 2 is code 11, etc
# Switch workspaces with mainMod + [0-9]
bind = $mainMod, code:10, workspace, 1 # NOTE: code:10 = key 1
bind = $mainMod, code:11, workspace, 2 # NOTE: code:11 = key 2
bind = $mainMod, code:12, workspace, 3 # NOTE: code:12 = key 3
bind = $mainMod, code:13, workspace, 4 # NOTE: code:13 = key 4
bind = $mainMod, code:14, workspace, 5 # NOTE: code:14 = key 5
bind = $mainMod, code:15, workspace, 6 # NOTE: code:15 = key 6
bind = $mainMod, code:16, workspace, 7 # NOTE: code:16 = key 7
bind = $mainMod, code:17, workspace, 8 # NOTE: code:17 = key 8
bind = $mainMod, code:18, workspace, 9 # NOTE: code:18 = key 9
bind = $mainMod, code:19, workspace, 10 # NOTE: code:19 = key 0
# Move active window and follow to workspace mainMod + SHIFT [0-9]
bind = $mainMod SHIFT, code:10, movetoworkspace, 1 # NOTE: code:10 = key 1
bind = $mainMod SHIFT, code:11, movetoworkspace, 2 # NOTE: code:11 = key 2
bind = $mainMod SHIFT, code:12, movetoworkspace, 3 # NOTE: code:12 = key 3
bind = $mainMod SHIFT, code:13, movetoworkspace, 4 # NOTE: code:13 = key 4
bind = $mainMod SHIFT, code:14, movetoworkspace, 5 # NOTE: code:14 = key 5
bind = $mainMod SHIFT, code:15, movetoworkspace, 6 # NOTE: code:15 = key 6
bind = $mainMod SHIFT, code:16, movetoworkspace, 7 # NOTE: code:16 = key 7
bind = $mainMod SHIFT, code:17, movetoworkspace, 8 # NOTE: code:17 = key 8
bind = $mainMod SHIFT, code:18, movetoworkspace, 9 # NOTE: code:18 = key 9
bind = $mainMod SHIFT, code:19, movetoworkspace, 10 # NOTE: code:19 = key 0
bind = $mainMod SHIFT, bracketleft, movetoworkspace, -1 # brackets [
bind = $mainMod SHIFT, bracketright, movetoworkspace, +1 # brackets ]
# Move active window to a workspace silently mainMod + CTRL [0-9]
bind = $mainMod CTRL, code:10, movetoworkspacesilent, 1 # NOTE: code:10 = key 1
bind = $mainMod CTRL, code:11, movetoworkspacesilent, 2 # NOTE: code:11 = key 2
bind = $mainMod CTRL, code:12, movetoworkspacesilent, 3 # NOTE: code:12 = key 3
bind = $mainMod CTRL, code:13, movetoworkspacesilent, 4 # NOTE: code:13 = key 4
bind = $mainMod CTRL, code:14, movetoworkspacesilent, 5 # NOTE: code:14 = key 5
bind = $mainMod CTRL, code:15, movetoworkspacesilent, 6 # NOTE: code:15 = key 6
bind = $mainMod CTRL, code:16, movetoworkspacesilent, 7 # NOTE: code:16 = key 7
bind = $mainMod CTRL, code:17, movetoworkspacesilent, 8 # NOTE: code:17 = key 8
bind = $mainMod CTRL, code:18, movetoworkspacesilent, 9 # NOTE: code:18 = key 9
bind = $mainMod CTRL, code:19, movetoworkspacesilent, 10 # NOTE: code:19 = key 0
bind = $mainMod CTRL, bracketleft, movetoworkspacesilent, -1 # brackets [
bind = $mainMod CTRL, bracketright, movetoworkspacesilent, +1 # brackets ]
# Scroll through existing workspaces with mainMod + scroll
bind = $mainMod, mouse_down, workspace, e+1
bind = $mainMod, mouse_up, workspace, e-1
bind = $mainMod, period, workspace, e+1
bind = $mainMod, comma, workspace, e-1
# Move/resize windows with mainMod + LMB/RMB and dragging
bindm = $mainMod, mouse:272, movewindow # NOTE: mouse:272 = left click
bindm = $mainMod, mouse:273, resizewindow # NOTE: mouse:272 = right click
#########################################################################################################
# Laptop specific keybinds
#########################################################################################################
# for disabling Touchpad. hyprctl devices to get device name.
$Touchpad_Device=asue1209:00-04f3:319f-touchpad
binde = , xf86KbdBrightnessDown, exec, $scriptsDir/BrightnessKbd.sh --dec # decrease keyboard brightness
binde = , xf86KbdBrightnessUp, exec, $scriptsDir/BrightnessKbd.sh --inc # increase keyboard brightness
bind = , xf86Launch1, exec, rog-control-center # ASUS Armory crate button
bind = , xf86Launch3, exec, asusctl led-mode -n # FN+F4 Switch keyboard RGB profile
bind = , xf86Launch4, exec, asusctl profile -n # FN+F5 change of fan profiles (Quite, Balance, Performance)
binde = , xf86MonBrightnessDown, exec, $scriptsDir/Brightness.sh --dec # decrease monitor brightness
binde = , xf86MonBrightnessUp, exec, $scriptsDir/Brightness.sh --inc # increase monitor brightness
bind = , xf86TouchpadToggle, exec, $scriptsDir/TouchPad.sh # disable touchpad
# Screenshot keybindings using F6 (no PrinSrc button)
#bind = $mainMod, F6, exec, $scriptsDir/ScreenShot.sh --now # screenshot
#bind = $mainMod SHIFT, F6, exec, $scriptsDir/ScreenShot.sh --area # screenshot (area)
#bind = $mainMod CTRL, F6, exec, $scriptsDir/ScreenShot.sh --in5 # # screenshot (5 secs delay)
#bind = $mainMod ALT, F6, exec, $scriptsDir/ScreenShot.sh --in10 # screenshot (10 secs delay)
#bind = ALT, F6, exec, $scriptsDir/ScreenShot.sh --active # screenshot (active window only)
$TOUCHPAD_ENABLED = true
device {
name = $Touchpad_Device
enabled = $TOUCHPAD_ENABLED
}

View File

@@ -1,4 +0,0 @@
# NOTE, THIS FILE IS BEING USED by disabling Laptop display monitor behaviour when closing lid.
# See notes on Laptops.conf
#monitor = eDP-1, preferred, auto, 1

View File

@@ -1,31 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# See https://wiki.hyprland.org/Configuring/Keywords/ for more variable settings
# These configs are mostly for laptops. This is addemdum to Keybinds.conf
$mainMod = SUPER
$scriptsDir = $HOME/.config/hypr/scripts
$UserConfigs = $HOME/.config/hypr/UserConfigs
# Below are useful when you are connecting your laptop in external display
# Suggest you edit below for your laptop display
# From WIKI This is to disable laptop monitor when lid is closed.
# consult https://wiki.hyprland.org/hyprland-wiki/pages/Configuring/Binds/#switches
#bindl = , switch:off:Lid Switch,exec,hyprctl keyword monitor "eDP-1, preferred, auto, 1"
#bindl = , switch:on:Lid Switch,exec,hyprctl keyword monitor "eDP-1, disable"
# WARNING! Using this method has some caveats!! USE THIS PART WITH SOME CAUTION!
# CONS of doing this, is that you need to set up your wallpaper (SUPER W) and choose wallpaper.
# CAVEATS! Sometimes the Main Laptop Monitor DOES NOT have display that it needs to re-connect your external monitor
# One work around is to ensure that before shutting down laptop, MAKE SURE your laptop lid is OPEN!!
# Make sure to comment (put # on the both the bindl = , switch ......) above
# NOTE: Display for laptop are being generated into LaptopDisplay.conf
# This part is to be use if you do not want your main laptop monitor to wake up during say wallpaper change etc
#bindl = , switch:off:Lid Switch,exec,echo "monitor = eDP-1, preferred, auto, 1" > $UserConfigs/LaptopDisplay.conf
#bindl = , switch:on:Lid Switch,exec,echo "monitor = eDP-1, disable" > $UserConfigs/LaptopDisplay.conf
# for laptop-lid action (to erase the last entry)
#exec-once = echo "monitor = eDP-1, preferred, auto, 1" > $HOME/.config/hypr/UserConfigs/LaptopDisplay.conf
#

View File

@@ -1,43 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# Commands and Apps to be executed at launch
$scriptsDir = $HOME/.config/hypr/scripts
# wallpaper stuff
exec-once = awww-daemon --format xrgb
#exec-once = mpvpaper '*' -o "load-scripts=no no-audio --loop" $livewallpaper
# Startup
exec-once = dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
exec-once = systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
# Polkit (Polkit Gnome / KDE)
exec-once = $scriptsDir/Polkit.sh
# starup apps
exec-once = nm-applet --indicator
exec-once = swaync
#exec-once = ags
#exec-once = blueman-applet
#exec-once = rog-control-center
exec-once = waybar
exec-once = qs # quickshell AGS Desktop Overview alternative
#clipboard manager
exec-once = wl-paste --type text --watch cliphist store
exec-once = wl-paste --type image --watch cliphist store
# Starting hypridle to start hyprlock
exec-once = hypridle
exec-once = hyprctl dispatch workspace 3
exec-once = /usr/bin/Plexamp.AppImage &&
exec-once = sleep 3 && vesktop
exec-once = hyprctl dispatch workspace 1
exec-once = bitwarden-desktop --ozone-platform-hint=auto --silent
# Here are list of features available but disabled by default
# exec-once = awww-daemon --format xrgb && awww img $HOME/Pictures/wallpapers/mecha-nostalgia.png # persistent wallpaper
# xdg-desktop-portal-hyprland (should be auto starting. However, you can force to start)
#exec-once = $scriptsDir/PortalHyprland.sh

View File

@@ -1,26 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
animations {
enabled = yes
bezier = wind, 0.05, 0.9, 0.1, 1.05
bezier = winIn, 0.1, 1.1, 0.1, 1.1
bezier = winOut, 0.3, -0.3, 0, 1
bezier = liner, 1, 1, 1, 1
bezier = overshot, 0.05, 0.9, 0.1, 1.05
bezier = smoothOut, 0.5, 0, 0.99, 0.99
bezier = smoothIn, 0.5, -0.5, 0.68, 1.5
animation = windows, 1, 6, wind, slide
animation = windowsIn, 1, 5, winIn, slide
animation = windowsOut, 1, 3, smoothOut, slide
animation = windowsMove, 1, 5, wind, slide
animation = border, 1, 1, liner
animation = borderangle, 1, 180, liner, loop #used by rainbow borders and rotating colors
animation = fade, 1, 3, smoothOut
animation = workspaces, 1, 5, overshot
# animations for -git or version >0.42.0
animation = workspacesIn, 1, 5, winIn, slide
animation = workspacesOut, 1, 5, winOut, slide
}

View File

@@ -1,58 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# Decoration Settings
# Hyprland Wiki Links
# Animation - https://wiki.hyprland.org/Configuring/Animations/
# Decoration - https://wiki.hyprland.org/Configuring/Variables/#decoration
# HyprFlux static color palette
source = $HOME/.config/hypr/colours/hyprflux-colors.conf
general {
border_size = 0
gaps_in = 2
gaps_out = 4
# col.active_border = $color12
# col.inactive_border = $color10
}
decoration {
rounding = 10
active_opacity = 1.0
inactive_opacity = 0.9
fullscreen_opacity = 1.0
dim_inactive = true
dim_strength = 0.1
dim_special = 0.8
shadow {
enabled = false
range = 3
render_power = 1
color = $color12
color_inactive = $color10
}
blur {
enabled = true
size = 6
passes = 2
ignore_opacity = true
new_optimizations = true
special = true
popups = true
}
}
group {
col.border_active = $color15
groupbar {
col.active = $color0
}
}

View File

@@ -1,116 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# User Settings
# This is where you put your own settings as this will not be touched during update
# if the upgrade.sh is used.
# refer to Hyprland wiki for more info https://wiki.hyprland.org/Configuring/Variables/
# NOTE: some settings are in ~/.config/hypr/UserConfigs/UserDecorAnimations.conf
dwindle {
preserve_split = true
#smart_split = true
special_scale_factor = 1
}
master {
new_status = master
new_on_top = 1
mfact = 0.5
}
general {
resize_on_border = true
layout = dwindle
}
input {
kb_layout = gb
kb_variant =
kb_model =
kb_options =
kb_rules =
repeat_rate = 50
repeat_delay = 300
sensitivity = 0.6 #mouse sensitivity
#accel_profile = # flat or adaptive or blank or EMPTY means libinput’s default mode
numlock_by_default = true
left_handed = false
follow_mouse = 1
float_switch_override_focus = false
touchpad {
disable_while_typing = true
natural_scroll = true
clickfinger_behavior = false
middle_button_emulation = false
tap-to-click = true
drag_lock = false
}
# below for devices with touchdevice ie. touchscreen
touchdevice {
enabled = true
}
# below is for table see link above for proper variables
tablet {
transform = 0
left_handed = 0
}
}
gestures {
# workspace_swipe = true
# workspace_swipe_fingers = 3
workspace_swipe_distance = 500
workspace_swipe_invert = true
workspace_swipe_min_speed_to_force = 30
workspace_swipe_cancel_ratio = 0.5
workspace_swipe_create_new = true
workspace_swipe_forever = true
#workspace_swipe_use_r = true #uncomment if wanted a forever create a new workspace with swipe right
}
misc {
disable_hyprland_logo = true
disable_splash_rendering = true
vrr = 2
mouse_move_enables_dpms = true
enable_swallow = off
swallow_regex = ^(kitty)$
focus_on_activate = false
initial_workspace_tracking = 0
middle_click_paste = false
enable_anr_dialog = true # Application not Responding (ANR)
anr_missed_pings = 15 # ANR Threshold default 1 is too low
}
#opengl {
# nvidia_anti_flicker = true
#}
binds {
workspace_back_and_forth = true
allow_workspace_cycles = true
pass_mouse_when_bound = false
}
#Could help when scaling and not pixelating
xwayland {
enabled = true
force_zero_scaling = true
}
render {
direct_scanout = 0
}
cursor {
sync_gsettings_theme = true
no_hardware_cursors = 2 # change to 1 if want to disable
enable_hyprcursor = true
warp_on_change_workspace = 2
no_warps = true
}

View File

@@ -1,744 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# For window rules and layerrules
# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more
# NOTES: This is only for Hyprland > 0.48
# note for ja: This should NOT be implemented on Debian and Ubuntu
# windowrule - tags - add apps under appropriate tag to use the same settings
# browser tags
windowrule {
name = windowrule-1
tag = +browser
match:class = ^([Ff]irefox|org.mozilla.firefox|[Ff]irefox-esr|[Ff]irefox-bin)$
}
windowrule {
name = windowrule-2
tag = +browser
match:class = ^([Gg]oogle-chrome(-beta|-dev|-unstable)?)$
}
windowrule {
name = windowrule-3
tag = +browser
match:class = ^(chrome-.+-Default)$ # Chrome PWAs
}
# windowrule = tag +browser, class:^([Cc]hromium)$
windowrule {
name = windowrule-4
tag = +browser
match:class = ^([Mm]icrosoft-edge(-stable|-beta|-dev|-unstable))$
}
windowrule {
name = windowrule-5
tag = +browser
match:class = ^(Brave-browser(-beta|-dev|-unstable)?)$
}
windowrule {
name = windowrule-6
tag = +browser
match:class = ^([Tt]horium-browser|[Cc]achy-browser)$
}
windowrule {
name = windowrule-7
tag = +browser
match:class = ^(zen-alpha|zen)$
}
# notif tags
windowrule {
name = windowrule-8
tag = +notif
match:class = ^(swaync-control-center|swaync-notification-window|swaync-client|class)$
}
# HyprFlux settings tag
windowrule {
name = windowrule-9
tag = +HyprFlux_Cheat
match:title = ^(HyprFlux Quick Cheat Sheet)$
}
windowrule {
name = windowrule-10
tag = +HyprFlux_Settings
match:title = ^(HyprFlux Settings)$
}
windowrule {
name = windowrule-11
tag = +HyprFlux-Settings
match:class = ^(nwg-displays|nwg-look)$
}
# terminal tags
windowrule {
name = windowrule-12
tag = +terminal
match:class = ^(Alacritty|kitty|kitty-dropterm)$
}
# email tags
windowrule {
name = windowrule-13
tag = +email
match:class = ^([Tt]hunderbird|org.gnome.Evolution)$
}
windowrule {
name = windowrule-14
tag = +email
match:class = ^(eu.betterbird.Betterbird)$
}
# project tags
windowrule {
name = windowrule-15
tag = +projects
match:class = ^(codium|codium-url-handler|VSCodium)$
}
windowrule {
name = windowrule-16
tag = +projects
match:class = ^(VSCode|code-url-handler)$
}
windowrule {
name = windowrule-17
tag = +projects
match:class = ^(jetbrains-.+)$ # JetBrains IDEs
}
# screenshare tags
windowrule {
name = windowrule-18
tag = +screenshare
match:class = ^(com.obsproject.Studio)$
}
# IM tags
# windowrule = tag +im, class:^([Dd]iscord|[Ww]ebCord|[Vv]esktop)$
windowrule {
name = windowrule-19
tag = +im
center = on
float = on
size = (monitor_w*0.6) (monitor_h*0.7)
match:class = ^([Ff]erdium)$
}
windowrule {
name = windowrule-20
tag = +im
match:class = ^([Ww]hatsapp-for-linux)$
}
windowrule {
name = windowrule-21
tag = +im
match:class = ^(ZapZap|com.rtosta.zapzap)$
}
windowrule {
name = windowrule-22
tag = +im
match:class = ^(org.telegram.desktop|io.github.tdesktop_x64.TDesktop)$
}
windowrule {
name = windowrule-23
tag = +im
match:class = ^(teams-for-linux)$
}
windowrule {
name = windowrule-24
tag = +im
match:class = ^(im.riot.Riot|Element)$ # Element Matrix client
}
# game tags
windowrule {
name = windowrule-25
tag = +games
match:class = ^(gamescope)$
}
windowrule {
name = windowrule-26
tag = +games
match:class = ^(steam_app_\d+)$
}
# gamestore tags
windowrule {
name = windowrule-27
tag = +gamestore
match:class = ^([Ss]team)$
}
windowrule {
name = windowrule-28
tag = +gamestore
match:title = ^([Ll]utris)$
}
windowrule {
name = windowrule-29
tag = +gamestore
match:class = ^(com.heroicgameslauncher.hgl)$
}
# file-manager tags
windowrule {
name = windowrule-30
tag = +file-manager
match:class = ^([Tt]hunar|org.gnome.Nautilus|[Pp]cmanfm-qt)$
}
windowrule {
name = windowrule-31
tag = +file-manager
match:class = ^(app.drey.Warp)$
}
# wallpaper tags
windowrule {
name = windowrule-32
tag = +wallpaper
match:class = ^([Ww]aytrogen)$
}
# multimedia tags
windowrule {
name = windowrule-33
tag = +multimedia
match:class = ^([Aa]udacious)$
}
# multimedia-video tags
windowrule {
name = windowrule-34
tag = +multimedia_video
match:class = ^([Mm]pv|vlc)$
}
# settings tags
windowrule {
name = windowrule-35
tag = +settings
center = on
match:title = ^(ROG Control)$
}
windowrule {
name = windowrule-36
tag = +settings
match:class = ^(wihotspot(-gui)?)$ # wifi hotspot
}
windowrule {
name = windowrule-37
tag = +settings
match:class = ^([Bb]aobab|org.gnome.[Bb]aobab)$ # Disk usage analyzer
}
windowrule {
name = windowrule-38
tag = +settings
match:class = ^(gnome-disks|wihotspot(-gui)?)$
}
windowrule {
name = windowrule-39
tag = +settings
match:title = (Kvantum Manager)
}
windowrule {
name = windowrule-40
tag = +settings
match:class = ^(file-roller|org.gnome.FileRoller)$ # archive manager
}
windowrule {
name = windowrule-41
tag = +settings
match:class = ^(nm-applet|nm-connection-editor|blueman-manager)$
}
windowrule {
name = windowrule-42
tag = +settings
center = on
match:class = ^(pavucontrol|org.pulseaudio.pavucontrol|com.saivert.pwvucontrol)$
}
windowrule {
name = windowrule-43
tag = +settings
match:class = ^(qt5ct|qt6ct|[Yy]ad)$
}
windowrule {
name = windowrule-44
tag = +settings
match:class = (xdg-desktop-portal-gtk)
}
windowrule {
name = windowrule-45
tag = +settings
match:class = ^(org.kde.polkit-kde-authentication-agent-1)$
}
windowrule {
name = windowrule-46
tag = +settings
match:class = ^([Rr]ofi)$
}
# Custom rule
windowrule {
name = windowrule-47
tile = on
match:class = ^([Cc]hromium)$
}
windowrule {
name = windowrule-48
size = (monitor_w*0.25) (monitor_h*0.55)
float = on
match:class = ^(com.network.manager)$
}
windowrule {
name = windowrule-49
workspace = special:nyx
match:class = ^([Vv]esktop|[Dd]iscord|[Ss]potify)$
}
# viewer tags
windowrule {
name = windowrule-50
tag = +viewer
match:class = ^(gnome-system-monitor|org.gnome.SystemMonitor|io.missioncenter.MissionCenter)$ # system monitor
}
windowrule {
name = windowrule-51
tag = +viewer
match:class = ^(evince)$ # document viewer
}
windowrule {
name = windowrule-52
tag = +viewer
match:class = ^(eog|org.gnome.Loupe)$ # image viewer
}
# Some special override rules
windowrule {
name = windowrule-53
no_blur = on
opacity = 1.0
match:tag = multimedia_video*
}
# POSITION
# windowrule = center,floating:1 # warning, it cause even the menu to float and center.
windowrule {
name = windowrule-54
center = on
float = on
size = (monitor_w*0.65) (monitor_h*0.9)
match:tag = HyprFlux_Cheat*
}
windowrule {
name = windowrule-55
center = on
float = on
match:class = ([Tt]hunar)
match:title = negative:(.*[Tt]hunar.*)
}
windowrule {
name = windowrule-56
center = on
float = on
match:tag = HyprFlux-Settings*
}
windowrule {
name = windowrule-57
center = on
match:title = ^(Keybindings)$
}
windowrule {
name = windowrule-58
center = on
size = (monitor_w*0.6) (monitor_h*0.7)
match:class = ^([Ww]hatsapp-for-linux|ZapZap|com.rtosta.zapzap)$
}
windowrule {
name = windowrule-59
move = ((monitor_w*0.61)) ((monitor_h*0.07))
float = on
size = (monitor_w*0.38) (monitor_h*0.38)
pin = on
keep_aspect_ratio = on
match:title = ^(Picture-in-Picture)$
}
#windowrule = move 72% 7%,title:^(Firefox)$
# windowrule to avoid idle for fullscreen apps
#windowrule = idleinhibit fullscreen, class:^(*)$
#windowrule = idleinhibit fullscreen, title:^(*)$
windowrule {
name = windowrule-60
idle_inhibit = fullscreen
match:fullscreen = 1
}
# windowrule move to workspace
windowrule {
name = windowrule-61
workspace = 1
match:tag = projects*
}
windowrule {
name = windowrule-62
workspace = 1
match:tag = email*
}
windowrule {
name = windowrule-63
workspace = 2
match:tag = browser*
}
windowrule {
name = windowrule-64
workspace = 3
match:class = ^([Tt]hunar)$
}
windowrule {
name = windowrule-65
workspace = 4
match:tag = im*
}
windowrule {
name = windowrule-66
workspace = 5
match:tag = gamestore*
}
windowrule {
name = windowrule-67
workspace = 8
no_blur = on
fullscreen = on
match:tag = games*
}
windowrule {
name = windowrule-68
workspace = 1
match:class = ^(kitty)$
match:title = ^(tmuxifier)$
}
windowrule {
name = windowrule-69
workspace = 9
match:class = ^(virt-viewer)$
}
windowrule {
name = windowrule-70
workspace = 10
match:class = ^([Oo]bsidian)$
}
# windowrule move to workspace (silent)
windowrule {
name = windowrule-71
workspace = 4 silent
match:tag = screenshare*
}
windowrule {
name = windowrule-72
workspace = 9 silent
match:class = ^(virt-manager)$
}
windowrule {
name = windowrule-73
workspace = 9 silent
match:class = ^(.virt-manager-wrapped)$
}
windowrule {
name = windowrule-74
workspace = 9 silent
match:tag = multimedia*
}
# FLOAT
windowrule {
name = windowrule-75
float = on
opacity = 0.9 0.7
size = (monitor_w*0.7) (monitor_h*0.7)
match:tag = wallpaper*
}
windowrule {
name = windowrule-76
float = on
opacity = 0.8 0.7
size = (monitor_w*0.7) (monitor_h*0.7)
match:tag = settings*
}
windowrule {
name = windowrule-77
float = on
match:tag = viewer*
}
windowrule {
name = windowrule-78
float = on
match:class = ([Zz]oom|onedriver|onedriver-launcher)$
}
windowrule {
name = windowrule-79
float = on
match:class = (org.gnome.Calculator)
match:title = (Calculator)
}
windowrule {
name = windowrule-80
float = on
match:class = ^(mpv|com.github.rafostar.Clapper)$
}
windowrule {
name = windowrule-81
float = on
match:class = ^([Qq]alculate-gtk)$
}
#windowrule = float, class:^([Ww]hatsapp-for-linux|ZapZap|com.rtosta.zapzap)$
#windowrule = float, title:^(Firefox)$
windowrule {
name = windowrule-82
float = on
match:class = ^(fdm|freedownloadmanager)$
}
windowrule {
name = windowrule-83
float = on
match:class = ^(proton-authenticator)$
}
windowrule {
name = windowrule-84
float = on
match:class = ^([Ww]indscribe)$
}
# windowrule - ######### float popups and dialogue #######
windowrule {
name = windowrule-85
float = on
center = on
match:title = ^(Authentication Required)$
}
windowrule {
name = windowrule-86
float = on
match:class = (codium|codium-url-handler|VSCodium)
match:title = negative:(.*codium.*|.*VSCodium.*)
}
windowrule {
name = windowrule-87
float = on
match:class = ^(com.heroicgameslauncher.hgl)$
match:title = negative:(Heroic Games Launcher)
}
windowrule {
name = windowrule-88
float = on
match:class = ^([Ss]team)$
match:title = negative:^([Ss]team)$
}
windowrule {
name = windowrule-89
float = on
size = (monitor_w*0.7) (monitor_h*0.6)
center = on
match:title = ^(Add Folder to Workspace)$
}
windowrule {
name = windowrule-90
float = on
size = (monitor_w*0.7) (monitor_h*0.6)
center = on
match:title = ^(Save As)$
}
windowrule {
name = windowrule-91
float = on
size = (monitor_w*0.7) (monitor_h*0.6)
match:initial_title = (Open Files)
}
windowrule {
name = windowrule-92
float = on
center = on
size = (monitor_w*0.16) (monitor_h*0.12)
match:title = ^(SDDM Background)$ #HyprFlux YAD for setting SDDM background
}
# END of float popups and dialogue #######
# OPACITY
# windowrule = opacity 0.9 0.7, tag:browser*
# windowrule = opacity 0.9 0.8, tag:projects*
# windowrule = opacity 0.94 0.86, tag:im*
# windowrule = opacity 0.94 0.86, tag:multimedia*
# windowrule = opacity 0.9 0.8, tag:file-manager*
windowrule {
name = windowrule-93
opacity = 0.8 0.7
match:tag = terminal*
}
# windowrule = opacity 0.82 0.75, tag:viewer*
windowrule {
name = windowrule-94
opacity = 0.8 0.7
match:class = ^(gedit|org.gnome.TextEditor|mousepad)$
}
windowrule {
name = windowrule-95
opacity = 0.9 0.8
match:class = ^(deluge)$
}
windowrule {
name = windowrule-96
opacity = 0.9 0.8
match:class = ^(seahorse)$ # gnome-keyring gui
}
# windowrule = opacity 0.95 0.75, title:^(Picture-in-Picture)$
# SIZE
#windowrule = size 25% 25%, title:^(Firefox)$
# PINNING
#windowrule = pin,title:^(Firefox)$
# windowrule - extras
# BLUR & FULLSCREEN
#windowrule = bordercolor rgb(EE4B55) rgb(880808), fullscreen:1
#windowrule = bordercolor rgb(282737) rgb(1E1D2D), floating:1
#windowrule = opacity 0.8 0.8, pinned:1
# LAYER RULES
layerrule {
name = layerrule-1
blur = on
ignore_alpha = 0
match:namespace = rofi
}
layerrule {
name = layerrule-2
blur = on
ignore_alpha = 0
match:namespace = notifications
}
layerrule {
name = layerrule-3
blur = on
ignore_alpha = 0.5
match:namespace = quickshell:overview
}
#layerrule = ignorealpha 0.5, tag:notif*
#layerrule = ignorezero, class:^([Rr]ofi)$
#layerrule = blur, class:^([Rr]ofi)$
#layerrule = unset,class:^([Rr]ofi)$
#layerrule = ignorezero, <rofi>
#layerrule = ignorezero, overview
#layerrule = blur, overview

View File

@@ -1,666 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# For window rules and layerrules
# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more
# NOTES: This is only for Hyprland > 0.48
# note for ja: This should NOT be implemented on Debian and Ubuntu
# windowrule - tags - add apps under appropriate tag to use the same settings
# browser tags
windowrule {
name = windowrule-1
tag = +browser
match:class = ^([Ff]irefox|org.mozilla.firefox|[Ff]irefox-esr|[Ff]irefox-bin)$
}
windowrule {
name = rustdesk
tag = +rustdesk
match:class = ^(rustdesk)$, xwayland
}
windowrule {
name = windowrule-2
tag = +browser
match:class = ^([Gg]oogle-chrome(-beta|-dev|-unstable)?)$
}
windowrule {
name = windowrule-3
tag = +browser
match:class = ^(chrome-.+-Default)$ # Chrome PWAs
}
# windowrule = tag +browser, class:^([Cc]hromium)$
windowrule {
name = windowrule-4
tag = +browser
match:class = ^([Mm]icrosoft-edge(-stable|-beta|-dev|-unstable))$
}
windowrule {
name = windowrule-5
tag = +browser
match:class = ^(Brave-browser(-beta|-dev|-unstable)?)$
}
windowrule {
name = windowrule-6
tag = +browser
match:class = ^([Tt]horium-browser|[Cc]achy-browser)$
}
windowrule {
name = windowrule-7
tag = +browser
match:class = ^(zen-alpha|zen)$
}
# notif tags
windowrule {
name = windowrule-8
tag = +notif
match:class = ^(swaync-control-center|swaync-notification-window|swaync-client|class)$
}
# HyprFlux settings tag
windowrule {
name = windowrule-9
tag = +HyprFlux_Cheat
match:title = ^(HyprFlux Quick Cheat Sheet)$
}
windowrule {
name = windowrule-10
tag = +HyprFlux_Settings
match:title = ^(HyprFlux Settings)$
}
windowrule {
name = windowrule-11
tag = +HyprFlux-Settings
match:class = ^(nwg-displays|nwg-look)$
}
# terminal tags
windowrule {
name = windowrule-12
tag = +terminal
match:class = ^(Alacritty|kitty|kitty-dropterm)$
}
# email tags
windowrule {
name = windowrule-13
tag = +email
match:class = ^([Tt]hunderbird|org.gnome.Evolution)$
}
windowrule {
name = windowrule-14
tag = +email
match:class = ^(eu.betterbird.Betterbird)$
}
# project tags
windowrule {
name = windowrule-15
tag = +projects
match:class = ^(codium|codium-url-handler|VSCodium)$
}
windowrule {
name = windowrule-16
tag = +projects
match:class = ^(VSCode|code-url-handler)$
}
windowrule {
name = windowrule-17
tag = +projects
match:class = ^(jetbrains-.+)$ # JetBrains IDEs
}
# screenshare tags
windowrule {
name = windowrule-18
tag = +screenshare
match:class = ^(com.obsproject.Studio)$
}
# IM tags
# windowrule = tag +im, class:^([Dd]iscord|[Ww]ebCord|[Vv]esktop)$
windowrule {
name = windowrule-19
tag = +im
center = on
float = on
size = (monitor_w*0.6) (monitor_h*0.7)
match:class = ^([Ff]erdium)$
}
windowrule {
name = windowrule-20
tag = +im
match:class = ^([Ww]hatsapp-for-linux)$
}
windowrule {
name = windowrule-21
tag = +im
match:class = ^(ZapZap|com.rtosta.zapzap)$
}
windowrule {
name = windowrule-22
tag = +im
match:class = ^(org.telegram.desktop|io.github.tdesktop_x64.TDesktop)$
}
windowrule {
name = windowrule-23
tag = +im
match:class = ^(teams-for-linux)$
}
windowrule {
name = windowrule-24
tag = +im
match:class = ^(im.riot.Riot|Element)$ # Element Matrix client
}
# game tags
windowrule {
name = windowrule-25
tag = +games
match:class = ^(gamescope)$
}
windowrule {
name = windowrule-26
tag = +games
match:class = ^(steam_app_\d+)$
}
# gamestore tags
windowrule {
name = windowrule-27
tag = +gamestore
match:class = ^([Ss]team)$
}
windowrule {
name = windowrule-28
tag = +gamestore
match:title = ^([Ll]utris)$
}
windowrule {
name = windowrule-29
tag = +gamestore
match:class = ^(com.heroicgameslauncher.hgl)$
}
# file-manager tags
windowrule {
name = windowrule-30
tag = +file-manager
match:class = ^([Tt]hunar|org.gnome.Nautilus|[Pp]cmanfm-qt)$
}
windowrule {
name = windowrule-31
tag = +file-manager
match:class = ^(app.drey.Warp)$
}
# wallpaper tags
windowrule {
name = windowrule-32
tag = +wallpaper
match:class = ^([Ww]aytrogen)$
}
# multimedia tags
windowrule {
name = windowrule-33
tag = +multimedia
match:class = ^([Aa]udacious)$
}
# multimedia-video tags
windowrule {
name = windowrule-34
tag = +multimedia_video
match:class = ^([Mm]pv|vlc)$
}
# settings tags
windowrule {
name = windowrule-35
tag = +settings
center = on
match:title = ^(ROG Control)$
}
windowrule {
name = windowrule-36
tag = +settings
match:class = ^(wihotspot(-gui)?)$ # wifi hotspot
}
windowrule {
name = windowrule-37
tag = +settings
match:class = ^([Bb]aobab|org.gnome.[Bb]aobab)$ # Disk usage analyzer
}
windowrule {
name = windowrule-38
tag = +settings
match:class = ^(gnome-disks|wihotspot(-gui)?)$
}
windowrule {
name = windowrule-39
tag = +settings
match:title = (Kvantum Manager)
}
windowrule {
name = windowrule-40
tag = +settings
match:class = ^(file-roller|org.gnome.FileRoller)$ # archive manager
}
windowrule {
name = windowrule-41
tag = +settings
match:class = ^(nm-applet|nm-connection-editor|blueman-manager)$
}
windowrule {
name = windowrule-42
tag = +settings
center = on
match:class = ^(pavucontrol|org.pulseaudio.pavucontrol|com.saivert.pwvucontrol)$
}
windowrule {
name = windowrule-43
tag = +settings
match:class = ^(qt5ct|qt6ct|[Yy]ad)$
}
windowrule {
name = windowrule-44
tag = +settings
match:class = (xdg-desktop-portal-gtk)
}
windowrule {
name = windowrule-45
tag = +settings
match:class = ^(org.kde.polkit-kde-authentication-agent-1)$
}
windowrule {
name = windowrule-46
tag = +settings
match:class = ^([Rr]ofi)$
}
# Custom rule
windowrule {
name = windowrule-47
tile = on
match:class = ^([Cc]hromium)$
}
windowrule {
name = windowrule-48
size = (monitor_w*0.25) (monitor_h*0.55)
float = on
match:class = ^(com.network.manager)$
}
windowrule {
name = windowrule-49
workspace = special:nyx
match:class = ^([Vv]esktop|[Dd]iscord|[Ss]potify)$
}
windowrule {
name = windowrule-255
size = (monitor_w*0.75) (monitor_h*0.80)
float = on
match:class = ^([Bb]itwarden)$
}
# viewer tags
windowrule {
name = windowrule-50
tag = +viewer
match:class = ^(gnome-system-monitor|org.gnome.SystemMonitor|io.missioncenter.MissionCenter)$ # system monitor
}
windowrule {
name = windowrule-51
tag = +viewer
match:class = ^(evince)$ # document viewer
}
windowrule {
name = windowrule-52
tag = +viewer
match:class = ^(eog|org.gnome.Loupe)$ # image viewer
}
# Some special override rules
windowrule {
name = windowrule-53
no_blur = on
opacity = 1.0
match:tag = multimedia_video*
}
# POSITION
# windowrule = center,floating:1 # warning, it cause even the menu to float and center.
windowrule {
name = windowrule-54
center = on
float = on
size = (monitor_w*0.65) (monitor_h*0.9)
match:tag = HyprFlux_Cheat*
}
windowrule {
name = windowrule-55
center = on
float = on
match:class = ([Tt]hunar)
match:title = negative:(.*[Tt]hunar.*)
}
windowrule {
name = windowrule-56
center = on
float = on
match:tag = HyprFlux-Settings*
}
windowrule {
name = windowrule-57
center = on
match:title = ^(Keybindings)$
}
windowrule {
name = windowrule-58
center = on
size = (monitor_w*0.6) (monitor_h*0.7)
match:class = ^([Ww]hatsapp-for-linux|ZapZap|com.rtosta.zapzap)$
}
windowrule {
name = windowrule-59
move = ((monitor_w*0.61)) ((monitor_h*0.07))
float = on
size = (monitor_w*0.38) (monitor_h*0.38)
pin = on
keep_aspect_ratio = on
match:title = ^(Picture-in-Picture)$
}
#windowrule = move 72% 7%,title:^(Firefox)$
# windowrule to avoid idle for fullscreen apps
#windowrule = idleinhibit fullscreen, class:^(*)$
#windowrule = idleinhibit fullscreen, title:^(*)$
windowrule {
name = windowrule-60
idle_inhibit = fullscreen
match:fullscreen = 1
}
# Discord to workspace 3
windowrule = match:class ^([Vv]esktop|[Dd]iscord|[Ss]potify|[Pp]lexamp)$, workspace 3
windowrule = match:class ^([Pp]lexamp), size 20% 20%
# FLOAT
windowrule {
name = windowrule-75
float = on
opacity = 0.9 0.7
size = (monitor_w*0.7) (monitor_h*0.7)
match:tag = wallpaper*
}
windowrule {
name = windowrule-76
float = on
opacity = 0.8 0.7
size = (monitor_w*0.7) (monitor_h*0.7)
match:tag = settings*
}
windowrule {
name = windowrule-77
float = on
match:tag = viewer*
}
windowrule {
name = windowrule-78
float = on
match:class = ([Zz]oom|onedriver|onedriver-launcher)$
}
windowrule {
name = windowrule-79
float = on
match:class = (org.gnome.Calculator)
match:title = (Calculator)
}
windowrule {
name = windowrule-80
float = on
match:class = ^(mpv|com.github.rafostar.Clapper)$
}
windowrule {
name = windowrule-81
float = on
match:class = ^([Qq]alculate-gtk)$
}
#windowrule = float, class:^([Ww]hatsapp-for-linux|ZapZap|com.rtosta.zapzap)$
#windowrule = float, title:^(Firefox)$
windowrule {
name = windowrule-82
float = on
match:class = ^(fdm|freedownloadmanager)$
}
windowrule {
name = windowrule-83
float = on
match:class = ^(proton-authenticator)$
}
windowrule {
name = windowrule-84
float = on
match:class = ^([Ww]indscribe)$
}
# windowrule - ######### float popups and dialogue #######
windowrule {
name = windowrule-85
float = on
center = on
match:title = ^(Authentication Required)$
}
windowrule {
name = windowrule-86
float = on
match:class = (codium|codium-url-handler|VSCodium)
match:title = negative:(.*codium.*|.*VSCodium.*)
}
windowrule {
name = windowrule-87
float = on
match:class = ^(com.heroicgameslauncher.hgl)$
match:title = negative:(Heroic Games Launcher)
}
windowrule {
name = windowrule-88
float = on
match:class = ^([Ss]team)$
match:title = negative:^([Ss]team)$
}
windowrule {
name = windowrule-89
float = on
size = (monitor_w*0.7) (monitor_h*0.6)
center = on
match:title = ^(Add Folder to Workspace)$
}
windowrule {
name = windowrule-90
float = on
size = (monitor_w*0.7) (monitor_h*0.6)
center = on
match:title = ^(Save As)$
}
windowrule {
name = windowrule-91
float = on
size = (monitor_w*0.7) (monitor_h*0.6)
match:initial_title = (Open Files)
}
windowrule {
name = windowrule-92
float = on
center = on
size = (monitor_w*0.16) (monitor_h*0.12)
match:title = ^(SDDM Background)$ #HyprFlux YAD for setting SDDM background
}
# END of float popups and dialogue #######
# OPACITY
# windowrule = opacity 0.9 0.7, tag:browser*
# windowrule = opacity 0.9 0.8, tag:projects*
# windowrule = opacity 0.94 0.86, tag:im*
# windowrule = opacity 0.94 0.86, tag:multimedia*
# windowrule = opacity 0.9 0.8, tag:file-manager*
windowrule {
name = windowrule-93
opacity = 0.8 0.7
match:tag = terminal*
}
# windowrule = opacity 0.82 0.75, tag:viewer*
windowrule {
name = windowrule-94
opacity = 0.8 0.7
match:class = ^(gedit|org.gnome.TextEditor|mousepad)$
}
windowrule {
name = windowrule-95
opacity = 0.9 0.8
match:class = ^(deluge)$
}
windowrule {
name = windowrule-96
opacity = 0.9 0.8
match:class = ^(seahorse)$ # gnome-keyring gui
}
# windowrule = opacity 0.95 0.75, title:^(Picture-in-Picture)$
# SIZE
#windowrule = size 25% 25%, title:^(Firefox)$
# PINNING
#windowrule = pin,title:^(Firefox)$
# windowrule - extras
# BLUR & FULLSCREEN
#windowrule = bordercolor rgb(EE4B55) rgb(880808), fullscreen:1
#windowrule = bordercolor rgb(282737) rgb(1E1D2D), floating:1
#windowrule = opacity 0.8 0.8, pinned:1
# LAYER RULES
layerrule {
name = layerrule-1
blur = on
ignore_alpha = 0
match:namespace = rofi
}
layerrule {
name = layerrule-2
blur = on
ignore_alpha = 0
match:namespace = notifications
}
layerrule {
name = layerrule-3
blur = on
ignore_alpha = 0.5
match:namespace = quickshell:overview
}
#layerrule = ignorealpha 0.5, tag:notif*
#layerrule = ignorezero, class:^([Rr]ofi)$
#layerrule = blur, class:^([Rr]ofi)$
#layerrule = unset,class:^([Rr]ofi)$
#layerrule = ignorezero, <rofi>
#layerrule = ignorezero, overview
#layerrule = blur, overview

View File

@@ -1,12 +0,0 @@
# Generated with hyprdynamicmonitors freeze.
# This is a template that you can edit, it is just a starter that pulled your current monitor setup,
# adjust as needed.
# You can use templates here etc, see https://github.com/fiffeek/hyprdynamicmonitors/blob/main/examples/basic/hyprconfigs/template.go.tmpl.
# Monitors are given arbitrary tags (the "monitor" prefix and the ID coming from hyprland).
# If you are using tui to edit, leave this at the end of your file (the last monitor config applies)
# and leave the markers.
# <<<<< TUI AUTO START
monitor=desc:LG Electronics LG ULTRAWIDE 0x0002E68A,2560x1080@60.00000,1920x-740,1.00000000,transform,3,vrr,0
monitor=desc:HP Inc. HP 524pf VNG447017D,1920x1080@100.00000,-1920x0,1.00000000,transform,0,vrr,0
monitor=desc:Acer Technologies VG240Y S 0x126191DE,1920x1080@165.00301,0x0,1.00000000,transform,0,vrr,1,bitdepth,10,cm,wide
# <<<<< TUI AUTO END

View File

@@ -1,30 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# NOTE: This will be overwritten by NWG-Displays
# once you use and click apply.
#
# *********************************************************** #
# You can set workspace rules to achieve workspace-specific behaviors.
# For instance, you can define a workspace where all windows are drawn without borders or gaps.
# https://wiki.hyprland.org/Configuring/Workspace-Rules/
# Assigning workspace to a certain monitor. Below are just examples
workspace = 1, monitor:DP-2, default=true
workspace = 2, monitor:DP-1, default=true
workspace = 3, monitor:HDMI-A-1, default=true
# workspace = 4, monitor:eDP-1
# workspace = 5, monitor:DP-2
# workspace = 6, monitor:DP-2
# workspace = 7, monitor:DP-2
# workspace = 8, monitor:DP-2
# example rules (from wiki)
# workspace = 3, rounding:false, decorate:false
# workspace = name:coding, rounding:false, decorate:false, gapsin:0, gapsout:0, border:false, decorate:false, monitor:DP-1
# workspace = 8,bordersize:8
# workspace = name:Hello, monitor:DP-1, default:true
# workspace = name:gaming, monitor:desc:Chimei Innolux Corporation 0x150C, default:true
# workspace = 5, on-created-empty:[float] firefox
# workspace = special:scratchpad, on-created-empty:foot

View File

@@ -1,3 +1,3 @@
# Place your new scripts here.
# If you need to edit a script from main script (~/.config/hypr/scripts), copy it on this directory, and edit.
# Make sure to update as well the keybinds in ~/.config/hypr/UserConfigs directory if any script is linked to it
# Make sure to update as well the keybinds in ~/.config/hypr/input/keybinds.lua if any script is linked to it

View File

@@ -1,25 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
animations {
enabled = yes
bezier = wind, 0.05, 0.9, 0.1, 1.05
bezier = winIn, 0.1, 1.1, 0.1, 1.1
bezier = winOut, 0.3, -0.3, 0, 1
bezier = liner, 1, 1, 1, 1
bezier = overshot, 0.05, 0.9, 0.1, 1.05
bezier = smoothOut, 0.5, 0, 0.99, 0.99
bezier = smoothIn, 0.5, -0.5, 0.68, 1.5
animation = windows, 1, 6, wind, slide
animation = windowsIn, 1, 5, winIn, slide
animation = windowsOut, 1, 3, smoothOut, slide
animation = windowsMove, 1, 5, wind, slide
animation = border, 1, 1, liner
animation = borderangle, 1, 180, liner, loop #used by rainbow borders and rotating colors
animation = fade, 1, 3, smoothOut
animation = workspaces, 1, 5, overshot
# animations for -git or version >0.42.0
animation = workspacesIn, 1, 5, winIn, slide
animation = workspacesOut, 1, 5, winOut, slide
}

View File

@@ -1,26 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# old animations
animations {
enabled = yes
bezier = myBezier, 0.05, 0.9, 0.1, 1.05
bezier = linear, 0.0, 0.0, 1.0, 1.0
bezier = wind, 0.05, 0.9, 0.1, 1.05
bezier = winIn, 0.1, 1.1, 0.1, 1.1
bezier = winOut, 0.3, -0.3, 0, 1
bezier = slow, 0, 0.85, 0.3, 1
bezier = overshot, 0.7, 0.6, 0.1, 1.1
bezier = bounce, 1.1, 1.6, 0.1, 0.85
bezier = sligshot, 1, -1, 0.15, 1.25
bezier = nice, 0, 6.9, 0.5, -4.20
animation = windowsIn, 1, 5, slow, popin
animation = windowsOut, 1, 5, winOut, popin
animation = windowsMove, 1, 5, wind, slide
animation = border, 1, 10, linear
animation = borderangle, 1, 180, linear, loop #used by rainbow borders and rotating colors
animation = fade, 1, 5, overshot
animation = workspaces, 1, 5, wind
animation = windows, 1, 5, bounce, popin
}

View File

@@ -1,4 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
animations {
enabled = no
}

View File

@@ -1,39 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# name "End-4"
# credit https://github.com/end-4/dots-hyprland
animations {
enabled = true
# Animation curves
bezier = linear, 0, 0, 1, 1
bezier = md3_standard, 0.2, 0, 0, 1
bezier = md3_decel, 0.05, 0.7, 0.1, 1
bezier = md3_accel, 0.3, 0, 0.8, 0.15
bezier = overshot, 0.05, 0.9, 0.1, 1.1
bezier = crazyshot, 0.1, 1.5, 0.76, 0.92
bezier = hyprnostretch, 0.05, 0.9, 0.1, 1.0
bezier = menu_decel, 0.1, 1, 0, 1
bezier = menu_accel, 0.38, 0.04, 1, 0.07
bezier = easeInOutCirc, 0.85, 0, 0.15, 1
bezier = easeOutCirc, 0, 0.55, 0.45, 1
bezier = easeOutExpo, 0.16, 1, 0.3, 1
bezier = softAcDecel, 0.26, 0.26, 0.15, 1
bezier = md2, 0.4, 0, 0.2, 1 # use with .2s duration
# Animation configs
animation = windows, 1, 3, md3_decel, popin 60%
animation = windowsIn, 1, 3, md3_decel, popin 60%
animation = windowsOut, 1, 3, md3_accel, popin 60%
animation = border, 1, 10, default
animation = fade, 1, 3, md3_decel
# animation = layers, 1, 2, md3_decel, slide
animation = layersIn, 1, 3, menu_decel, slide
animation = layersOut, 1, 1.6, menu_accel
animation = fadeLayersIn, 1, 2, menu_decel
animation = fadeLayersOut, 1, 4.5, menu_accel
animation = workspaces, 1, 7, menu_decel, slide
# animation = workspaces, 1, 2.5, softAcDecel, slide
# animation = workspaces, 1, 7, menu_decel, slidefade 15%
# animation = specialWorkspace, 1, 3, md3_decel, slidefadevert 15%
animation = specialWorkspace, 1, 3, md3_decel, slidevert
}

View File

@@ -1,33 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# name "Vertical"
# credit https://github.com/prasanthrangan/hyprdots
animations {
enabled = yes
bezier = fluent_decel, 0, 0.2, 0.4, 1
bezier = easeOutCirc, 0, 0.55, 0.45, 1
bezier = easeOutCubic, 0.33, 1, 0.68, 1
bezier = easeinoutsine, 0.37, 0, 0.63, 1
# Windows
animation = windowsIn, 1, 1.5, easeinoutsine, popin 60% # window open
animation = windowsOut, 1, 1.5, easeOutCubic, popin 60% # window close.
animation = windowsMove, 1, 1.5, easeinoutsine, slide # everything in between, moving, dragging, resizing.
# Fading
animation = fade, 1, 2.5, fluent_decel
animation = fadeLayersIn, 0
animation = border, 0
# Layers
animation = layers, 1, 1.5, easeinoutsine, popin
# Workspaces
#animation = workspaces, 1, 3, fluent_decel, slidefade 30% # styles: slide, slidevert, fade, slidefade, slidefadevert
animation = workspaces, 1, 3, fluent_decel, slidefadevert 30% # styles: slide, slidevert, fade, slidefade, slidefadevert
animation = specialWorkspace, 1, 2, fluent_decel, slidefade 10%
}

View File

@@ -1,20 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# name "Default"
# credit https://github.com/prasanthrangan/hyprdots
animations {
enabled = yes
bezier = wind, 0.05, 0.9, 0.1, 1.05
bezier = winIn, 0.1, 1.1, 0.1, 1.1
bezier = winOut, 0.3, -0.3, 0, 1
bezier = liner, 1, 1, 1, 1
animation = windows, 1, 6, wind, slide
animation = windowsIn, 1, 6, winIn, slide
animation = windowsOut, 1, 5, winOut, slide
animation = windowsMove, 1, 5, wind, slide
animation = border, 1, 1, liner
animation = borderangle, 1, 30, liner, once
animation = fade, 1, 10, default
animation = workspaces, 1, 5, wind
animation = specialWorkspace, 1, 5, wind, slidevert
}

View File

@@ -1,26 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# name "Minimal-1"
# credit https://github.com/prasanthrangan/hyprdots-
animations {
enabled = true
# █▄▄ █▀▀ ▀█ █ █▀▀ █▀█   █▀▀ █░█ █▀█ █░█ █▀▀
# █▄█ ██▄ █▄ █ ██▄ █▀▄   █▄▄ █▄█ █▀▄ ▀▄▀ ██▄
bezier = wind, 0.05, 0.9, 0.1, 1.05
bezier = winIn, 0.1, 1.1, 0.1, 1.1
bezier = winOut, 0.3, -0.3, 0, 1
bezier = liner, 1, 1, 1, 1
#▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
#█▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
animation = windows, 1, 6, wind, slide
animation = windowsIn, 1, 6, winIn, slide
animation = windowsOut, 1, 5, winOut, slide
animation = windowsMove, 1, 5, wind, slide
animation = border, 1, 1, liner
animation = borderangle, 1, 30, liner, once
animation = fade, 1, 10, default
animation = workspaces, 1, 5, wind
}

View File

@@ -1,15 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# name "Minimal-2"
# credit https://github.com/prasanthrangan/hyprdots
animations {
enabled = yes
bezier = quart, 0.25, 1, 0.5, 1
animation = windows, 1, 6, quart, slide
animation = border, 1, 6, quart
animation = borderangle, 1, 6, quart
animation = fade, 1, 6, quart
animation = workspaces, 1, 6, quart
}

View File

@@ -1,40 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# name "Optimized"
# credit https://github.com/prasanthrangan/hyprdots
animations {
enabled = true
bezier = wind, 0.05, 0.85, 0.03, 0.97
bezier = winIn, 0.07, 0.88, 0.04, 0.99
bezier = winOut, 0.20, -0.15, 0, 1
bezier = liner, 1, 1, 1, 1
bezier = md3_standard, 0.12, 0, 0, 1
bezier = md3_decel, 0.05, 0.80, 0.10, 0.97
bezier = md3_accel, 0.20, 0, 0.80, 0.08
bezier = overshot, 0.05, 0.85, 0.07, 1.04
bezier = crazyshot, 0.1, 1.22, 0.68, 0.98
bezier = hyprnostretch, 0.05, 0.82, 0.03, 0.94
bezier = menu_decel, 0.05, 0.82, 0, 1
bezier = menu_accel, 0.20, 0, 0.82, 0.10
bezier = easeInOutCirc, 0.75, 0, 0.15, 1
bezier = easeOutCirc, 0, 0.48, 0.38, 1
bezier = easeOutExpo, 0.10, 0.94, 0.23, 0.98
bezier = softAcDecel, 0.20, 0.20, 0.15, 1
bezier = md2, 0.30, 0, 0.15, 1
bezier = OutBack, 0.28, 1.40, 0.58, 1
bezier = easeInOutCirc, 0.78, 0, 0.15, 1
animation = border, 1, 1.6, liner
animation = borderangle, 1, 82, liner, once
animation = windowsIn, 1, 3.2, winIn, slide
animation = windowsOut, 1, 2.8, easeOutCirc
animation = windowsMove, 1, 3.0, wind, slide
animation = fade, 1, 1.8, md3_decel
animation = layersIn, 1, 1.8, menu_decel, slide
animation = layersOut, 1, 1.5, menu_accel
animation = fadeLayersIn, 1, 1.6, menu_decel
animation = fadeLayersOut, 1, 1.8, menu_accel
animation = workspaces, 1, 4.0, menu_decel, slide
animation = specialWorkspace, 1, 2.3, md3_decel, slidefadevert 15%
}

View File

@@ -1,14 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# name "Classic"
# credit https://github.com/mylinuxforwork/dotfiles
animations {
enabled = true
bezier = myBezier, 0.05, 0.9, 0.1, 1.05
animation = windows, 1, 7, myBezier
animation = windowsOut, 1, 7, default, popin 80%
animation = border, 1, 10, default
animation = borderangle, 1, 8, default
animation = fade, 1, 7, default
animation = workspaces, 1, 6, default
}

View File

@@ -1,19 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# name "Dynamic"
# credit https://github.com/mylinuxforwork/dotfiles
animations {
enabled = true
bezier = wind, 0.05, 0.9, 0.1, 1.05
bezier = winIn, 0.1, 1.1, 0.1, 1.1
bezier = winOut, 0.3, -0.3, 0, 1
bezier = liner, 1, 1, 1, 1
animation = windows, 1, 6, wind, slide
animation = windowsIn, 1, 6, winIn, slide
animation = windowsOut, 1, 5, winOut, slide
animation = windowsMove, 1, 5, wind, slide
animation = border, 1, 1, liner
animation = borderangle, 1, 30, liner, loop
animation = fade, 1, 10, default
animation = workspaces, 1, 5, wind
}

View File

@@ -1,23 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# name "Fast"
# credit https://github.com/mylinuxforwork/dotfiles
animations {
enabled = true
bezier = linear, 0, 0, 1, 1
bezier = md3_standard, 0.2, 0, 0, 1
bezier = md3_decel, 0.05, 0.7, 0.1, 1
bezier = md3_accel, 0.3, 0, 0.8, 0.15
bezier = overshot, 0.05, 0.9, 0.1, 1.1
bezier = crazyshot, 0.1, 1.5, 0.76, 0.92
bezier = hyprnostretch, 0.05, 0.9, 0.1, 1.0
bezier = fluent_decel, 0.1, 1, 0, 1
bezier = easeInOutCirc, 0.85, 0, 0.15, 1
bezier = easeOutCirc, 0, 0.55, 0.45, 1
bezier = easeOutExpo, 0.16, 1, 0.3, 1
animation = windows, 1, 3, md3_decel, popin 60%
animation = border, 1, 10, default
animation = fade, 1, 2.5, md3_decel
animation = workspaces, 1, 3.5, easeOutExpo, slide
animation = specialWorkspace, 1, 3, md3_decel, slidevert
}

View File

@@ -1,19 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# name "High"
# credit https://github.com/mylinuxforwork/dotfiles
animations {
enabled = true
bezier = wind, 0.05, 0.9, 0.1, 1.05
bezier = winIn, 0.1, 1.1, 0.1, 1.1
bezier = winOut, 0.3, -0.3, 0, 1
bezier = liner, 1, 1, 1, 1
animation = windows, 1, 6, wind, slide
animation = windowsIn, 1, 6, winIn, slide
animation = windowsOut, 1, 5, winOut, slide
animation = windowsMove, 1, 5, wind, slide
animation = border, 1, 1, liner
animation = borderangle, 1, 30, liner, once
animation = fade, 1, 10, default
animation = workspaces, 1, 5, wind
}

View File

@@ -1,19 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# name "Moving"
# credit https://github.com/mylinuxforwork/dotfiles
animations {
enabled = true
bezier = overshot, 0.05, 0.9, 0.1, 1.05
bezier = smoothOut, 0.5, 0, 0.99, 0.99
bezier = smoothIn, 0.5, -0.5, 0.68, 1.5
animation = windows, 1, 5, overshot, slide
animation = windowsOut, 1, 3, smoothOut
animation = windowsIn, 1, 3, smoothOut
animation = windowsMove, 1, 4, smoothIn, slide
animation = border, 1, 5, default
animation = fade, 1, 5, smoothIn
animation = fadeDim, 1, 5, smoothIn
animation = workspaces, 1, 6, default
}

View File

@@ -1,14 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# name "Standard"
# credit https://github.com/mylinuxforwork/dotfiles
animations {
enabled = true
bezier = myBezier, 0.05, 0.9, 0.1, 1.05
animation = windows, 1, 7, myBezier
animation = windowsOut, 1, 7, default, popin 80%
animation = border, 1, 10, default
animation = borderangle, 1, 8, default
animation = fade, 1, 7, default
animation = workspaces, 1, 6, default
}

View File

@@ -1,54 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# name "Me-1"
# credit https://github.com/mahaveergurjar
animations {
enabled = true
# Animation curves
bezier = wind, 0.05, 0.9, 0.1, 1.05
bezier = winIn, 0.1, 1.1, 0.1, 1.1
bezier = winOut, 0.3, -0.3, 0, 1
bezier = liner, 1, 1, 1, 1
# bezier = linear, 0, 0, 1, 1
bezier = md3_standard, 0.2, 0, 0, 1
bezier = md3_decel, 0.05, 0.7, 0.1, 1
bezier = md3_accel, 0.3, 0, 0.8, 0.15
bezier = overshot, 0.05, 0.9, 0.1, 1.1
bezier = crazyshot, 0.1, 1.5, 0.76, 0.92
bezier = hyprnostretch, 0.05, 0.9, 0.1, 1.0
bezier = menu_decel, 0.1, 1, 0, 1
bezier = menu_accel, 0.38, 0.04, 1, 0.07
bezier = easeInOutCirc, 0.85, 0, 0.15, 1
bezier = easeOutCirc, 0, 0.55, 0.45, 1
bezier = easeOutExpo, 0.16, 1, 0.3, 1
bezier = softAcDecel, 0.26, 0.26, 0.15, 1
bezier = md2, 0.4, 0, 0.2, 1 # use with .2s duration
# Animation configs
# animation = windows, 1, 3, md3_decel, popin 60%
# animation = windowsIn, 1, 3, md3_decel, popin 60%
# animation = windowsOut, 1, 3, md3_accel, popin 60%
# animation = windows, 1, 6, wind, slide
animation = border, 1, 1, liner
animation = borderangle, 1, 30, liner, once
animation = windows, 1, 6, wind, slide
animation = windowsIn, 1, 6, winIn, slide
animation = windowsOut, 1, 5, winOut, slide
animation = windowsMove, 1, 5, wind, slide
# animation = border, 1, 10, default
animation = fade, 1, 3, md3_decel
# animation = layers, 1, 2, md3_decel, slide
animation = layersIn, 1, 3, menu_decel, slide
animation = layersOut, 1, 1.6, menu_accel
animation = fadeLayersIn, 1, 2, menu_decel
animation = fadeLayersOut, 1, 4.5, menu_accel
animation = workspaces, 1, 7, menu_decel, slide
animation = workspaces, 1, 5, wind
# animation = workspaces, 1, 2.5, softAcDecel, slide
# animation = workspaces, 1, 7, menu_decel, slidefade 15%
animation = specialWorkspace, 1, 3, md3_decel, slidefadevert 15%
animation = specialWorkspace, 1, 3, md3_decel, slidevert
}

View File

@@ -1,29 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# name "Me-2"
# credit https://github.com/mahaveergurjar
animations {
enabled = yes
bezier = wind, 0.05, 0.9, 0.1, 1.05
bezier = winIn, 0.1, 1.1, 0.1, 1.1
bezier = winOut, 0.3, -0.3, 0, 1
bezier = liner, 1, 1, 1, 1
bezier = overshot, 0.05, 0.9, 0.1, 1.05
bezier = smoothOut, 0.5, 0, 0.99, 0.99
bezier = smoothIn, 0.5, -0.5, 0.68, 1.5
animation = windows, 1, 6, wind, slide
animation = windowsIn, 1, 5, winIn, slide
animation = windowsOut, 1, 3, smoothOut, slide
animation = windowsMove, 1, 5, wind, slide
animation = border, 1, 1, liner
animation = borderangle, 1, 180, liner, loop #used by rainbow borders and rotating colors
animation = fade, 1, 3, smoothOut
animation = workspaces, 1, 5, overshot
# animations for -git or version >0.42.0
animation = workspacesIn, 1, 5, winIn, slide
animation = workspacesOut, 1, 5, winOut, slide
}

View File

@@ -0,0 +1,6 @@
-- Active animation preset.
-- The presets live in ../presets/anim-*.lua. Super+Shift+A (scripts/Animations.sh)
-- picks one and rewrites the require() line below; edit it by hand to switch too,
-- e.g. require("presets.anim-ml4w-classic"). Keep it to this single line.
require("presets.anim-hyprflux")

View File

@@ -6,7 +6,7 @@
-- Animation - https://wiki.hyprland.org/Configuring/Animations/
-- Decoration - https://wiki.hyprland.org/Configuring/Variables/#decoration
local colors = require("colors")
local colors = require("appearance.colors")
hl.config({
general = {
@@ -24,7 +24,7 @@ hl.config({
rounding = 10,
active_opacity = 1.0,
inactive_opacity = 0.9,
inactive_opacity = 1.0,
fullscreen_opacity = 1.0,
dim_inactive = true,

View File

@@ -1,21 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# HyprFlux static color palette
$background = rgb(010102)
$foreground = rgb(FDF8FE)
$color0 = rgb(313131)
$color1 = rgb(09050C)
$color2 = rgb(221647)
$color3 = rgb(2C1A40)
$color4 = rgb(5E3887)
$color5 = rgb(7344A6)
$color6 = rgb(BAB0BD)
$color7 = rgb(F3ECF5)
$color8 = rgb(AAA5AC)
$color9 = rgb(0B0711)
$color10 = rgb(2D1D5F)
$color11 = rgb(3B2355)
$color12 = rgb(7D4AB4)
$color13 = rgb(9A5BDD)
$color14 = rgb(F8EAFC)
$color15 = rgb(F3ECF5)

View File

@@ -19,11 +19,7 @@ hl.env("XDG_SESSION_TYPE", "wayland")
-- QT Variables
hl.env("QT_AUTO_SCREEN_SCALE_FACTOR", "1")
hl.env("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1")
hl.env("QT_QPA_PLATFORMTHEME", "qt5ct")
hl.env("QT_QPA_PLATFORMTHEME", "qt6ct")
-- hyprland-qt-support
hl.env("QT_QUICK_CONTROLS_STYLE", "org.hyprland.style")
hl.env("QT_QPA_PLATFORMTHEME", "qt6ct") -- qt6ct also styles Qt5 apps that read it; qt5ct.conf is kept in sync
-- xwayland apps scale fix (useful if you use monitor scaling).
-- Set same value if you use scaling in monitors.lua
@@ -32,11 +28,17 @@ hl.env("QT_QUICK_CONTROLS_STYLE", "org.hyprland.style")
hl.env("GDK_SCALE", "1")
hl.env("QT_SCALE_FACTOR", "1")
-- Bibata-Modern-Ice-Cursor
-- NOTE! You must have the hyprcursor version to activate this.
-- Cursor: Bibata-Modern-Classic (AUR bibata-cursor-theme-bin, XCursor format).
-- Same theme in gtk-*/settings.ini, the gsettings call in hyprland.lua and initial-boot.sh.
-- There's no hyprcursor build installed, so Hyprland falls back to XCURSOR_THEME.
-- https://wiki.hyprland.org/Hypr-Ecosystem/hyprcursor/
hl.env("HYPRCURSOR_THEME", "Bibata-Modern-Classic")
hl.env("HYPRCURSOR_SIZE", "24")
hl.env("XCURSOR_THEME", "Bibata-Modern-Classic")
hl.env("XCURSOR_SIZE", "24")
-- Default terminal for anything that asks $TERMINAL (rofi-sensible-terminal, scripts)
hl.env("TERMINAL", "ghostty")
-- firefox
hl.env("MOZ_ENABLE_WAYLAND", "1")
@@ -46,13 +48,17 @@ hl.env("MOZ_ENABLE_WAYLAND", "1")
hl.env("ELECTRON_OZONE_PLATFORM_HINT", "auto") -- auto selects Wayland if possible, X11 otherwise
-- NVIDIA
-- This is from the Hyprland wiki. Uncomment below if an nvidia GPU is detected.
-- See hyprland wiki https://wiki.hyprland.org/Nvidia/#environment-variables
-- hl.env("LIBVA_DRIVER_NAME", "nvidia")
-- hl.env("__GLX_VENDOR_LIBRARY_NAME", "nvidia")
-- hl.env("NVD_BACKEND", "direct")
-- hl.env("GSK_RENDERER", "ngl")
-- Only when the NVIDIA kernel driver is loaded, so the same config works on the
-- laptop (forcing LIBVA_DRIVER_NAME=nvidia there breaks VA-API video decoding).
local nvidia = io.open("/proc/driver/nvidia/version")
if nvidia then
nvidia:close()
hl.env("LIBVA_DRIVER_NAME", "nvidia")
hl.env("__GLX_VENDOR_LIBRARY_NAME", "nvidia")
hl.env("NVD_BACKEND", "direct")
hl.env("GSK_RENDERER", "ngl")
end
-- additional ENV's for nvidia. Caution, activate with care
-- hl.env("GBM_BACKEND", "nvidia-drm")
@@ -85,3 +91,6 @@ hl.env("ELECTRON_OZONE_PLATFORM_HINT", "auto") -- auto selects Wayland if possib
-- hl.env("HYPRLAND_NO_RT", "1") -- Disables realtime priority setting by Hyprland.
-- hl.env("HYPRLAND_NO_SD_NOTIFY", "1") -- If systemd, disables the 'sd_notify' calls.
-- hl.env("HYPRLAND_NO_SD_VARS", "1") -- Disables management of variables in systemd and dbus activation environments.
-- Bitwarden SSH agent (so GUI apps like VS Code see it, not just shells)
hl.env("SSH_AUTH_SOCK", os.getenv("HOME") .. "/.bitwarden-ssh-agent.sock")

View File

@@ -0,0 +1,36 @@
-- Generated by HyprMod
-- Ported from hyprland-gui.conf
--
-- HyprMod isn't currently installed on this machine, so this is a straight syntax port of
-- whatever it last wrote. If you reinstall HyprMod, prefer letting its own "Migrate to Lua…"
-- wizard take over managing this file instead of hand-editing it going forward.
-- vrr forced off globally: the per-monitor vrr = 0 flag below does NOT override this when
-- misc.vrr is 1 (tested live on Hyprland 0.56.2 — DP-2 stayed vrr:true until this global
-- was changed). DP-2 (Acer VG240Y S) flickers under VRR when idle in fullscreen (mouse
-- stationary -> no repaint damage -> frame delivery stalls near the panel's VRR floor).
-- The other two monitors don't negotiate VRR at all, so this has no effect on them.
hl.config({
general = {
allow_tearing = true,
snap = {
enabled = false,
},
},
misc = {
vrr = 0,
},
})
-- Monitors
hl.monitor({ output = "DP-2", mode = "1920x1080@165.00Hz", position = "0x0", scale = 1, bitdepth = 10, cm = "wide", vrr = 0 })
-- Keybinds
-- (unbind = SUPER SHIFT, Print / unbind = SUPER, up from the source .conf have no Lua equivalent needed —
-- we just don't define a conflicting bind elsewhere; SUPER + up -> movefocus u below is otherwise identical
-- to the one already in keybinds.lua, kept here too for parity with the original file)
hl.bind("SUPER + Print", hl.dsp.exec_cmd([[sh -c 'if command -v hyprshot >/dev/null; then hyprshot -o "$HOME/Pictures/Screenshots-Linux" -m region; else grim -g "$(slurp)" "$HOME/Pictures/Screenshots-Linux/$(date +%Y-%m-%d_%H-%M-%S).png" && notify-send "Screenshot saved"; fi']])) -- drag an area (hyprshot if installed, else grim/slurp)
hl.bind("SUPER + SHIFT + S", hl.dsp.exec_cmd([[sh -c 'grim -g "$(slurp)" "$HOME/Pictures/Screenshots-Linux/$(date +%Y-%m-%d_%H-%M-%S).png" && notify-send "Screenshot saved"']])) -- region; Surface keyboard has no Print key
hl.bind("SUPER + F6", hl.dsp.exec_cmd([[sh -c 'grim "$HOME/Pictures/Screenshots-Linux/$(date +%Y-%m-%d_%H-%M-%S).png" && notify-send "Screenshot saved"']])) -- full screen
hl.bind("SUPER + up", hl.dsp.focus({ direction = "u" }))
hl.bind("SUPER + SHIFT + Print", hl.dsp.exec_cmd([[sh -c 'if command -v hyprshot >/dev/null; then hyprshot -o "$HOME/Pictures/Screenshots-Linux" -m window; else grim "$HOME/Pictures/Screenshots-Linux/$(date +%Y-%m-%d_%H-%M-%S).png" && notify-send "Screenshot saved"; fi']])) -- window (hyprshot) or full screen (fallback)

View File

@@ -13,6 +13,11 @@ hl.on("hyprland.start", function()
-- Startup
hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
hl.exec_cmd("systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
-- restart portals once the env is imported, otherwise screen sharing breaks
hl.exec_cmd("sleep 1 && systemctl --user restart xdg-desktop-portal-hyprland && systemctl --user restart xdg-desktop-portal")
-- Sunshine (virtual monitor only on desktop; skipped on laptops, detected by a system battery)
hl.exec_cmd("sleep 1 && ! ls -d /sys/class/power_supply/BAT* >/dev/null 2>&1 && hyprctl output create headless")
-- Polkit (Polkit Gnome / KDE)
hl.exec_cmd(scriptsDir .. "/Polkit.sh")
@@ -23,7 +28,7 @@ hl.on("hyprland.start", function()
-- hl.exec_cmd("ags")
-- hl.exec_cmd("blueman-applet")
-- hl.exec_cmd("rog-control-center")
hl.exec_cmd("waybar")
-- hl.exec_cmd("waybar")
hl.exec_cmd("qs") -- quickshell AGS Desktop Overview alternative
-- clipboard manager
@@ -31,15 +36,22 @@ hl.on("hyprland.start", function()
hl.exec_cmd("wl-paste --type image --watch cliphist store")
-- Starting hypridle to start hyprlock
-- Sunshine streams hold an idle inhibitor (scripts/SunshineIdle.sh) so this never locks mid-stream
hl.exec_cmd("hypridle")
-- Sunshine for streaming media
hl.exec_cmd("sunshine")
-- Float + center the Bitwarden extension pop-out (window rules can't catch it — see the script)
hl.exec_cmd(scriptsDir .. "/FloatBitwardenPopup.sh")
hl.dispatch(hl.dsp.focus({ workspace = 3 }))
-- NOTE: the trailing "&&" here is a stray leftover from the original .conf (dangling, does nothing) — ported as-is
hl.exec_cmd("/usr/bin/Plexamp.AppImage &&")
hl.exec_cmd("plexamp") -- plexamp-bin (AUR)
hl.exec_cmd("sleep 3 && vesktop")
hl.dispatch(hl.dsp.focus({ workspace = 1 }))
hl.exec_cmd("bitwarden-desktop --ozone-platform-hint=auto --silent")
hl.exec_cmd("seafile-applet")
-- Here are a list of features available but disabled by default
-- hl.exec_cmd("awww-daemon --format xrgb && awww img " .. os.getenv("HOME") .. "/Pictures/wallpapers/mecha-nostalgia.png") -- persistent wallpaper

View File

@@ -8,7 +8,7 @@ general {
lock_cmd = pidof hyprlock || hyprlock # runs hyprlock if it is not already running (this is always run when "loginctl lock-session" is called)
# unlock_cmd = killall hyprlock # kills hyprlock when unlocking (this is always run when "loginctl unlock-session" is called)
before_sleep_cmd = loginctl lock-session # ensures that the session is locked before going to sleep
after_sleep_cmd = hyprctl dispatch dpms on # turn of screen after sleep (not strictly necessary, but just in case)
after_sleep_cmd = hyprctl dispatch 'hl.dsp.dpms({action = "on"})' # turn of screen after sleep (not strictly necessary, but just in case)
ignore_dbus_inhibit = false # whether to ignore dbus-sent idle-inhibit requests (used by e.g. firefox or steam)
}
@@ -16,8 +16,8 @@ general {
# (disabled by default)
# listener {
# timeout = 30 # 30 seconds
# on-timeout = pidof hyprlock && hyprctl dispatch dpms off # turns off the screen if hyprlock is active
# on-resume = pidof hyprlock && hyprctl dispatch dpms on # command to run when activity is detected after timeout has fired.
# on-timeout = pidof hyprlock && hyprctl dispatch 'hl.dsp.dpms({action = "off"})' # turns off the screen if hyprlock is active
# on-resume = pidof hyprlock && hyprctl dispatch 'hl.dsp.dpms({action = "on"})' # command to run when activity is detected after timeout has fired.
# }
# Warn
@@ -38,8 +38,8 @@ listener {
# (disabled by default)
# listener {
# timeout = 630 # 10.5 min
# on-timeout = hyprctl dispatch dpms off # command to run when timeout has passed
# on-resume = hyprctl dispatch dpms on # command to run when activity is detected after timeout has fired.
# on-timeout = hyprctl dispatch 'hl.dsp.dpms({action = "off"})' # command to run when timeout has passed
# on-resume = hyprctl dispatch 'hl.dsp.dpms({action = "on"})' # command to run when activity is detected after timeout has fired.
# }
# Suspend # disabled by default

View File

@@ -1,15 +0,0 @@
# Generated by HyprMod
general:allow_tearing = 1
general:snap:enabled = 0
misc:vrr = 1
# Monitors
monitor = DP-2, 1920x1080@165.00Hz, 0x0, 1, bitdepth, 10, cm, wide
# Keybinds
unbind = SUPER SHIFT, Print
unbind = SUPER, up
bind = SUPER, Print, exec, hyprshot -o $HOME/Pictures/Screenshots-Linux -m region
bind = SUPER, up, movefocus, u
bind = SUPER SHIFT, Print, exec, hyprshot -o $HOME/Pictures/Screenshots-Linux -m window

View File

@@ -1,34 +0,0 @@
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# always refer to Hyprland wiki
# https://wiki.hyprland.org/
# Initial boot script enable to apply initial wallpapers, theming, new settings etc.
# suggest not to change this or delete this including deleting referrence file in ~/.config/hypr/.initial_startup_done
# as long as the referrence file is present, this initial-boot.sh will not execute
exec-once = $HOME/.config/hypr/initial-boot.sh
# ## This is where you want to start tinkering
$UserConfigs = $HOME/.config/hypr/UserConfigs # User Configs directory path
source= $UserConfigs/Startup_Apps.conf # put your start-up packages on this file
source= $UserConfigs/ENVariables.conf # Environment variables to load
source= $UserConfigs/WindowRules.conf # all about Hyprland Window Rules and Layer Rules
source= $UserConfigs/UserDecorations.conf # Decorations config file
source= $UserConfigs/UserAnimations.conf # Animation config file
source= $UserConfigs/Keybinds.conf # Put your own keybinds here
source= $UserConfigs/UserSettings.conf # Main Hyprland Settings.
# nwg-displays
source= $UserConfigs/monitors.conf
source= $UserConfigs/workspaces.conf
exec = gsettings set org.gnome.desktop.interface cursor-theme Future-black Cursors
# HyprMod managed settings
source = $HOME/.config/hypr/hyprland-gui.conf

View File

@@ -2,9 +2,13 @@
-- Ported from hyprland.conf — see https://wiki.hyprland.org/
--
-- This mirrors the old source= chain 1:1, just as require()'d Lua modules instead
-- of hyprlang includes. See each file for its origin. The animations/ preset library
-- of hyprlang includes. See each file for its origin. The presets/ preset library
-- (anim-*.lua) is also ported but intentionally not required from here — only the
-- currently-active style (animations.lua) is wired in, exactly like before.
-- currently-active style (appearance/animations.lua) is wired in, exactly like before.
--
-- Modules live in subfolders sorted by concern (core/, appearance/, layout/, input/,
-- presets/) — require() paths below use dotted module names, which Lua resolves to
-- the matching subfolder (e.g. require("core.env") -> core/env.lua).
-- Initial boot script: applies initial wallpapers, theming, new settings etc.
-- suggest not changing/deleting this, including the reference file at ~/.config/hypr/.initial_startup_done
@@ -14,25 +18,25 @@ hl.on("hyprland.start", function()
end)
-- ## This is where you want to start tinkering
require("startup") -- put your start-up packages in startup.lua
require("env") -- Environment variables to load
require("windowrules") -- all about Hyprland Window Rules and Layer Rules
require("decorations") -- Decorations config
require("animations") -- Animation config
require("keybinds") -- Put your own keybinds here
require("settings") -- Main Hyprland Settings
require("core.startup") -- put your start-up packages in core/startup.lua
require("core.env") -- Environment variables to load
require("layout.windowrules") -- all about Hyprland Window Rules and Layer Rules
require("appearance.decorations") -- Decorations config
require("appearance.animations") -- Animation config
require("input.keybinds") -- Put your own keybinds here
require("core.settings") -- Main Hyprland Settings
-- nwg-displays / hyprdynamicmonitors
require("monitors")
require("workspaces")
require("layout.monitors")
require("layout.workspaces")
-- NOTE: the original hyprland.conf used a plain `exec` (not `exec-once`) here, so it
-- re-ran on every `hyprctl reload`, not just at startup. hl.on("hyprland.start", ...)
-- only fires once at actual compositor start — close enough for a one-shot gsettings
-- call, but worth knowing if you relied on the reload-time re-apply.
hl.on("hyprland.start", function()
hl.exec_cmd("gsettings set org.gnome.desktop.interface cursor-theme Future-black Cursors")
hl.exec_cmd("gsettings set org.gnome.desktop.interface cursor-theme Bibata-Modern-Classic && gsettings set org.gnome.desktop.interface cursor-size 24")
end)
-- HyprMod managed settings
require("hyprmod")
require("core.hyprmod")

View File

@@ -113,7 +113,7 @@ label {
text = cmd[update:1000] echo "$(~/.config/hypr/hyprlock/Scripts/songdetail.sh)"
color = rgba(255, 255, 255, 0.6)
font_size = 18
font_family = JetBrains Mono Nerd, SF Pro Display Bold
font_family = JetBrainsMono Nerd Font, SF Pro Display Bold
position = 0, 90
halign = center
valign = bottom
@@ -221,7 +221,7 @@ label {
text = cmd[update:1000] echo "$(~/.config/hypr/hyprlock/Scripts/songdetail.sh)"
color = rgba(255, 255, 255, 0.6)
font_size = 28
font_family = JetBrains Mono Nerd, SF Pro Display Bold
font_family = JetBrainsMono Nerd Font, SF Pro Display Bold
position = 0, 90
halign = center
valign = bottom

View File

@@ -1,29 +0,0 @@
-- Generated by HyprMod
-- Ported from hyprland-gui.conf
--
-- HyprMod isn't currently installed on this machine, so this is a straight syntax port of
-- whatever it last wrote. If you reinstall HyprMod, prefer letting its own "Migrate to Lua…"
-- wizard take over managing this file instead of hand-editing it going forward.
hl.config({
general = {
allow_tearing = true,
snap = {
enabled = false,
},
},
misc = {
vrr = 1,
},
})
-- Monitors
hl.monitor({ output = "DP-2", mode = "1920x1080@165.00Hz", position = "0x0", scale = 1, bitdepth = 10, cm = "wide" })
-- Keybinds
-- (unbind = SUPER SHIFT, Print / unbind = SUPER, up from the source .conf have no Lua equivalent needed —
-- we just don't define a conflicting bind elsewhere; SUPER + up -> movefocus u below is otherwise identical
-- to the one already in keybinds.lua, kept here too for parity with the original file)
hl.bind("SUPER + Print", hl.dsp.exec_cmd("hyprshot -o " .. os.getenv("HOME") .. "/Pictures/Screenshots-Linux -m region"))
hl.bind("SUPER + up", hl.dsp.focus({ direction = "u" }))
hl.bind("SUPER + SHIFT + Print", hl.dsp.exec_cmd("hyprshot -o " .. os.getenv("HOME") .. "/Pictures/Screenshots-Linux -m window"))

View File

@@ -13,7 +13,7 @@ kvantum_theme="catppuccin-mocha-blue"
color_scheme="prefer-dark"
gtk_theme="Material-DeepOcean-BL"
icon_theme="Papirus-Dark"
cursor_theme="Moga-Cursor"
cursor_theme="Bibata-Modern-Classic"
# Check if a marker file exists.
if [ ! -f "$HOME/.config/hypr/.initial_startup_done" ]; then

View File

@@ -10,7 +10,7 @@ local userScripts = os.getenv("HOME") .. "/.config/hypr/UserScripts"
local edit = os.getenv("EDITOR") or "nvim"
-- These two are for UserKeybinds.conf & Waybar Modules
local term = "foot" -- Terminal
local term = "ghostty" -- Terminal (foot is still installed as a fallback)
local files = "thunar" -- File Manager
-- Default Search Engine for ROFI Search (SUPER S)
@@ -31,7 +31,7 @@ hl.bind(mainMod .. " + M", hl.dsp.exec_cmd("fdm")) -- Free Download Manager
hl.bind(mainMod .. " + W", hl.dsp.exec_cmd("waypaper")) -- Wayland Wallpaper Setter
hl.bind(mainMod .. " + L", hl.dsp.exec_cmd("hyprlock")) -- Screen Locker
hl.bind(mainMod .. " + DELETE", hl.dsp.exec_cmd("~/.config/rofi/powermenu/type-2/powermenu.sh")) -- Power Menu
hl.bind(mainMod .. " + S", hl.dsp.exec_cmd("cd /home/thomas/hyprmod && uv run hyprmod")) -- Hyprmod - A Hyprland settings manager (if installed)
hl.bind(mainMod .. " + S", hl.dsp.exec_cmd("hyprmod")) -- Hyprmod - A Hyprland settings manager (AUR: hyprmod)
hl.bind(mainMod .. " + SHIFT + R", hl.dsp.exec_cmd(scriptsDir .. "/Refresh.sh")) -- Refresh waybar, swaync, rofirrr
hl.bind(mainMod .. " + N", hl.dsp.exec_cmd("swaync-client -t -sw")) -- swayNC notification panel
@@ -40,8 +40,9 @@ hl.bind(mainMod .. " + SHIFT + F", hl.dsp.window.fullscreen({ mode = "fullscreen
hl.bind(mainMod .. " + SHIFT + SPACE", hl.dsp.window.float({ action = "toggle" })) -- Float Mode
-- Desktop zooming or magnifier
hl.bind(mainMod .. " + ALT + mouse_down", hl.dsp.exec_cmd([[hyprctl keyword cursor:zoom_factor "$(hyprctl getoption cursor:zoom_factor | awk 'NR==1 {factor = $2; if (factor < 1) {factor = 1}; print factor * 2.0}')"]]))
hl.bind(mainMod .. " + ALT + mouse_up", hl.dsp.exec_cmd([[hyprctl keyword cursor:zoom_factor "$(hyprctl getoption cursor:zoom_factor | awk 'NR==1 {factor = $2; if (factor < 1) {factor = 1}; print factor / 2.0}')"]]))
-- (`hyprctl keyword` is rejected by the Lua config — "keyword can't work with non-legacy parsers" — so set it via eval)
hl.bind(mainMod .. " + ALT + mouse_down", hl.dsp.exec_cmd([[hyprctl eval "hl.config({ cursor = { zoom_factor = $(hyprctl getoption cursor:zoom_factor | awk 'NR==1 {factor = $2; if (factor < 1) {factor = 1}; print factor * 2.0}') } })"]]))
hl.bind(mainMod .. " + ALT + mouse_up", hl.dsp.exec_cmd([[hyprctl eval "hl.config({ cursor = { zoom_factor = $(hyprctl getoption cursor:zoom_factor | awk 'NR==1 {factor = $2; if (factor < 1) {factor = 1}; print factor / 2.0}') } })"]]))
-- Waybar / Bar related
hl.bind(mainMod .. " + CTRL + ALT + B", hl.dsp.exec_cmd("pkill -SIGUSR1 waybar")) -- Toggle hide/show waybar
@@ -116,23 +117,22 @@ hl.bind(mainMod .. " + SHIFT + tab", hl.dsp.focus({ workspace = "m-1" }))
hl.bind(mainMod .. " + U", hl.dsp.workspace.toggle_special("nyx"))
hl.bind(mainMod .. " + SHIFT + U", hl.dsp.window.move({ workspace = "special:nyx" }))
-- The following mappings use key codes to better support various keyboard layouts
-- 1 is code:10, 2 is code:11, etc
-- Number-row keys (the Lua bind parser does not accept the code:NN keycode syntax)
-- Switch workspaces with mainMod + [0-9]
for i, code in ipairs({ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }) do
hl.bind(mainMod .. " + code:" .. code, hl.dsp.focus({ workspace = i })) -- code:10 = key 1 ... code:19 = key 0
for i, key in ipairs({ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }) do
hl.bind(mainMod .. " + " .. key, hl.dsp.focus({ workspace = i }))
end
-- Move active window and follow to workspace mainMod + SHIFT + [0-9]
for i, code in ipairs({ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }) do
hl.bind(mainMod .. " + SHIFT + code:" .. code, hl.dsp.window.move({ workspace = i, follow = true }))
for i, key in ipairs({ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }) do
hl.bind(mainMod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = i, follow = true }))
end
hl.bind(mainMod .. " + SHIFT + bracketleft", hl.dsp.window.move({ workspace = -1, follow = true })) -- brackets [
hl.bind(mainMod .. " + SHIFT + bracketright", hl.dsp.window.move({ workspace = 1, follow = true })) -- brackets ]
-- Move active window to a workspace silently mainMod + CTRL + [0-9]
for i, code in ipairs({ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }) do
hl.bind(mainMod .. " + CTRL + code:" .. code, hl.dsp.window.move({ workspace = i, follow = false }))
for i, key in ipairs({ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }) do
hl.bind(mainMod .. " + CTRL + " .. key, hl.dsp.window.move({ workspace = i, follow = false }))
end
hl.bind(mainMod .. " + CTRL + bracketleft", hl.dsp.window.move({ workspace = -1, follow = false })) -- brackets [
hl.bind(mainMod .. " + CTRL + bracketright", hl.dsp.window.move({ workspace = 1, follow = false })) -- brackets ]
@@ -151,9 +151,6 @@ hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true }) --
-- Laptop specific keybinds
--------------------------------------------------------------------------------------
-- for disabling Touchpad. hyprctl devices to get device name.
local touchpadDevice = "asue1209:00-04f3:319f-touchpad"
hl.bind("XF86KbdBrightnessDown", hl.dsp.exec_cmd(scriptsDir .. "/BrightnessKbd.sh --dec"), { repeating = true }) -- decrease keyboard brightness
hl.bind("XF86KbdBrightnessUp", hl.dsp.exec_cmd(scriptsDir .. "/BrightnessKbd.sh --inc"), { repeating = true }) -- increase keyboard brightness
hl.bind("XF86Launch1", hl.dsp.exec_cmd("rog-control-center")) -- ASUS Armory crate button
@@ -161,7 +158,7 @@ hl.bind("XF86Launch3", hl.dsp.exec_cmd("asusctl led-mode -n")) -- FN+F4 Switch k
hl.bind("XF86Launch4", hl.dsp.exec_cmd("asusctl profile -n")) -- FN+F5 change of fan profiles (Quiet, Balance, Performance)
hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd(scriptsDir .. "/Brightness.sh --dec"), { repeating = true }) -- decrease monitor brightness
hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd(scriptsDir .. "/Brightness.sh --inc"), { repeating = true }) -- increase monitor brightness
hl.bind("XF86TouchpadToggle", hl.dsp.exec_cmd(scriptsDir .. "/TouchPad.sh")) -- disable touchpad
hl.bind("XF86TouchpadToggle", hl.dsp.exec_cmd(scriptsDir .. "/TouchPad.sh")) -- toggle touchpad (finds the device itself)
-- Screenshot keybindings using F6 (no PrintSrc button)
-- hl.bind(mainMod .. " + F6", hl.dsp.exec_cmd(scriptsDir .. "/ScreenShot.sh --now")) -- screenshot
@@ -169,8 +166,3 @@ hl.bind("XF86TouchpadToggle", hl.dsp.exec_cmd(scriptsDir .. "/TouchPad.sh")) --
-- hl.bind(mainMod .. " + CTRL + F6", hl.dsp.exec_cmd(scriptsDir .. "/ScreenShot.sh --in5")) -- screenshot (5 secs delay)
-- hl.bind(mainMod .. " + ALT + F6", hl.dsp.exec_cmd(scriptsDir .. "/ScreenShot.sh --in10")) -- screenshot (10 secs delay)
-- hl.bind("ALT + F6", hl.dsp.exec_cmd(scriptsDir .. "/ScreenShot.sh --active")) -- screenshot (active window only)
hl.device({
name = touchpadDevice,
enabled = true,
})

View File

@@ -10,5 +10,6 @@
-- <<<<< TUI AUTO START
hl.monitor({ output = "desc:LG Electronics LG ULTRAWIDE 0x0002E68A", mode = "2560x1080@60.00000", position = "1920x-740", scale = 1.00000000, transform = 3, vrr = 0 })
hl.monitor({ output = "desc:HP Inc. HP 524pf VNG447017D", mode = "1920x1080@100.00000", position = "-1920x0", scale = 1.00000000, transform = 0, vrr = 0 })
hl.monitor({ output = "desc:Acer Technologies VG240Y S 0x126191DE", mode = "1920x1080@165.00301", position = "0x0", scale = 1.00000000, transform = 0, vrr = 1, bitdepth = 10, cm = "wide" })
hl.monitor({ output = "desc:Acer Technologies VG240Y S 0x126191DE", mode = "1920x1080@165.00301", position = "0x0", scale = 1.00000000, transform = 0, vrr = 0, bitdepth = 10, cm = "wide" })
hl.monitor({ output = "HEADLESS-1", mode = "1728x1080@60.00000", position = "4480x0", scale = 1.00000000, transform = 0, vrr = 0 })
-- <<<<< TUI AUTO END

View File

@@ -57,6 +57,11 @@ hl.window_rule({ name = "windowrule-24", tag = "+im", match = { class = "^(im.ri
-- game tags
hl.window_rule({ name = "windowrule-25", tag = "+games", match = { class = "^(gamescope)$" } })
hl.window_rule({ name = "windowrule-26", tag = "+games", match = { class = "^(steam_app_\\d+)$" } })
-- While Sunshine is streaming, open Steam games fullscreen on the headless stream output
-- (workspace 9 -> HEADLESS-1). Old games like KOTOR fix their render size/scaling at launch
-- and never adapt to a later resize, so they must start there. Off by default (local play
-- unaffected); toggled by ~/.config/sunshine/scripts/sunshine-games.sh via the global.
SunshineGamesRule = hl.window_rule({ name = "sunshine-games", enabled = false, workspace = "9", fullscreen = true, match = { class = "^(steam_app_\\d+)$" } })
-- gamestore tags
hl.window_rule({ name = "windowrule-27", tag = "+gamestore", match = { class = "^([Ss]team)$" } })
@@ -93,8 +98,13 @@ hl.window_rule({ name = "windowrule-46", tag = "+settings", match = { class = "^
-- Custom rule
hl.window_rule({ name = "windowrule-47", tile = true, match = { class = "^([Cc]hromium)$" } })
hl.window_rule({ name = "windowrule-48", size = "monitor_w*0.25 monitor_h*0.55", float = true, match = { class = "^(com.network.manager)$" } })
hl.window_rule({ name = "windowrule-49", workspace = "special:nyx", match = { class = "^([Vv]esktop|[Dd]iscord|[Ss]potify)$" } })
hl.window_rule({ name = "windowrule-255", size = "monitor_w*0.75 monitor_h*0.80", float = true, match = { class = "^([Bb]itwarden)$" } })
-- System updater terminal (quickshell/scripts/update-system.sh) — float + center it instead of
-- tiling into the current workspace. Matches the title the script launches it with, so it works
-- for Ghostty and the foot fallback alike; initial_title so a later rename can't dodge it.
-- Fixed pixel size rather than monitor_w/h fractions: HDMI-A-1 is rotated portrait (1080 wide),
-- where a fraction of the width made it too narrow for package output; 1000x650 fits every screen.
hl.window_rule({ name = "system-update-terminal", float = true, center = true, size = "1000 650", match = { initial_title = "^(update)$" } })
-- viewer tags
hl.window_rule({ name = "windowrule-50", tag = "+viewer", match = { class = "^(gnome-system-monitor|org.gnome.SystemMonitor|io.missioncenter.MissionCenter)$" } }) -- system monitor
@@ -128,17 +138,14 @@ hl.window_rule({
-- hl.window_rule({ idle_inhibit = "fullscreen", match = { title = "^(*)$" } })
hl.window_rule({ name = "windowrule-60", idle_inhibit = "fullscreen", match = { fullscreen = true } })
-- Discord to workspace 3
-- NOTE: these two used the old single-line windowrule syntax in the source .conf (match packed
-- oddly with the rule); windowrule-49 above already sends the same apps to special:nyx, so this
-- looks like a pre-existing conflict in the original config rather than something introduced here —
-- ported literally, last-defined-wins as before.
-- Discord / Spotify / Plexamp to workspace 3 (the portrait LG). An older rule also sent
-- Vesktop/Discord/Spotify to special:nyx; this one always won, so that rule was removed.
hl.window_rule({ workspace = "3", match = { class = "^([Vv]esktop|[Dd]iscord|[Ss]potify|[Pp]lexamp)$" } })
hl.window_rule({ size = "20% 20%", match = { class = "^([Pp]lexamp)" } })
-- FLOAT
hl.window_rule({ name = "windowrule-75", float = true, opacity = "0.9 0.7", size = "monitor_w*0.7 monitor_h*0.7", match = { tag = "wallpaper*" } })
hl.window_rule({ name = "windowrule-76", float = true, opacity = "0.8 0.7", size = "monitor_w*0.7 monitor_h*0.7", match = { tag = "settings*" } })
hl.window_rule({ name = "windowrule-75", float = true, opacity = "0.9 0.9", size = "monitor_w*0.7 monitor_h*0.7", match = { tag = "wallpaper*" } })
hl.window_rule({ name = "windowrule-76", float = true, opacity = "0.8 0.8", size = "monitor_w*0.7 monitor_h*0.7", match = { tag = "settings*" } })
hl.window_rule({ name = "windowrule-77", float = true, match = { tag = "viewer*" } })
hl.window_rule({ name = "windowrule-78", float = true, match = { class = "([Zz]oom|onedriver|onedriver-launcher)$" } })
hl.window_rule({ name = "windowrule-79", float = true, match = { class = "(org.gnome.Calculator)", title = "(Calculator)" } })
@@ -162,17 +169,17 @@ hl.window_rule({ name = "windowrule-92", float = true, center = true, size = "mo
-- END of float popups and dialogue #######
-- OPACITY
-- hl.window_rule({ opacity = "0.9 0.7", match = { tag = "browser*" } })
-- hl.window_rule({ opacity = "0.9 0.8", match = { tag = "projects*" } })
-- hl.window_rule({ opacity = "0.94 0.86", match = { tag = "im*" } })
-- hl.window_rule({ opacity = "0.94 0.86", match = { tag = "multimedia*" } })
-- hl.window_rule({ opacity = "0.9 0.8", match = { tag = "file-manager*" } })
hl.window_rule({ name = "windowrule-93", opacity = "0.8 0.7", match = { tag = "terminal*" } })
-- hl.window_rule({ opacity = "0.82 0.75", match = { tag = "viewer*" } })
hl.window_rule({ name = "windowrule-94", opacity = "0.8 0.7", match = { class = "^(gedit|org.gnome.TextEditor|mousepad)$" } })
hl.window_rule({ name = "windowrule-95", opacity = "0.9 0.8", match = { class = "^(deluge)$" } })
hl.window_rule({ name = "windowrule-96", opacity = "0.9 0.8", match = { class = "^(seahorse)$" } }) -- gnome-keyring gui
-- hl.window_rule({ opacity = "0.95 0.75", match = { title = "^(Picture-in-Picture)$" } })
-- hl.window_rule({ opacity = "0.9 0.9", match = { tag = "browser*" } })
-- hl.window_rule({ opacity = "0.9 0.9", match = { tag = "projects*" } })
-- hl.window_rule({ opacity = "0.94 0.94", match = { tag = "im*" } })
-- hl.window_rule({ opacity = "0.94 0.94", match = { tag = "multimedia*" } })
-- hl.window_rule({ opacity = "0.9 0.9", match = { tag = "file-manager*" } })
hl.window_rule({ name = "windowrule-93", opacity = "0.8 0.8", match = { tag = "terminal*" } })
-- hl.window_rule({ opacity = "0.82 0.82", match = { tag = "viewer*" } })
hl.window_rule({ name = "windowrule-94", opacity = "0.8 0.8", match = { class = "^(gedit|org.gnome.TextEditor|mousepad)$" } })
hl.window_rule({ name = "windowrule-95", opacity = "0.9 0.9", match = { class = "^(deluge)$" } })
hl.window_rule({ name = "windowrule-96", opacity = "0.9 0.9", match = { class = "^(seahorse)$" } }) -- gnome-keyring gui
-- hl.window_rule({ opacity = "0.95 0.95", match = { title = "^(Picture-in-Picture)$" } })
-- SIZE
-- hl.window_rule({ size = "25% 25%", match = { title = "^(Firefox)$" } })

View File

@@ -13,6 +13,7 @@
hl.workspace_rule({ workspace = 1, monitor = "DP-2", default = true })
hl.workspace_rule({ workspace = 2, monitor = "DP-1", default = true })
hl.workspace_rule({ workspace = 3, monitor = "HDMI-A-1", default = true })
hl.workspace_rule({ workspace = 9, monitor = "HEADLESS-1", default = true })
-- hl.workspace_rule({ workspace = 4, monitor = "eDP-1" })
-- hl.workspace_rule({ workspace = 5, monitor = "DP-2" })
-- hl.workspace_rule({ workspace = 6, monitor = "DP-2" })

View File

@@ -1,9 +1,7 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- Ported from UserConfigs/UserAnimations.conf (the active preset)
--
-- The 14 alternative presets from animations/*.conf were also ported, flat, as
-- anim-*.lua (see the plan) — none of them are wired in. To switch styles, replace
-- the body of this file with `require("anim-<preset>")`, e.g. require("anim-ml4w-classic").
-- name "HyprFlux (default)"
-- The animation set that shipped as appearance/animations.lua (ported from
-- UserConfigs/UserAnimations.conf). Selected by default; Super+Shift+A switches.
hl.config({
animations = {

View File

@@ -1,31 +1,38 @@
#!/bin/bash
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# For applying Animations from different users
# Animation preset picker (Super+Shift+A).
# Lists ~/.config/hypr/presets/anim-*.lua by their original HyprFlux preset name
# (the "Ported from animations/<name>.conf" line, else `-- name "..."`), then
# points appearance/animations.lua's require() at the chosen one and reloads.
iDIR="$HOME/.config/swaync/images"
presets_dir="$HOME/.config/hypr/presets"
active_file="$HOME/.config/hypr/appearance/animations.lua"
rofi_theme="$HOME/.config/rofi/launchers/type-4/style-4.rasi"
# Check if rofi is already running
if pidof rofi >/dev/null; then
pkill rofi
fi
# Variables
iDIR="$HOME/.config/swaync/images"
SCRIPTSDIR="$HOME/.config/hypr/scripts"
animations_dir="$HOME/.config/hypr/animations"
UserConfigs="$HOME/.config/hypr/UserConfigs"
rofi_theme="$HOME/.config/rofi/config-Animations.rasi"
msg='❗NOTE:❗ This will copy animations into UserAnimations.conf'
# list of animation files, sorted alphabetically with numbers first
animations_list=$(find -L "$animations_dir" -maxdepth 1 -type f | sed 's/.*\///' | sed 's/\.conf$//' | sort -V)
# "Display name<TAB>module" per preset, sorted by name
list=$(for f in "$presets_dir"/anim-*.lua; do
module=$(basename "$f" .lua)
name=$(sed -n 's/^-- Ported from animations\/\(.*\)\.conf.*/\1/p' "$f" | head -n1)
[[ -z "$name" ]] && name=$(sed -n 's/^-- name "\(.*\)"$/\1/p' "$f" | head -n1)
printf '%s\t%s\n' "${name:-$module}" "$module"
done | sort -f)
# Rofi Menu
chosen_file=$(echo "$animations_list" | rofi -i -dmenu -config $rofi_theme -mesg "$msg")
current=$(sed -n 's/^require("presets\.\(.*\)")$/\1/p' "$active_file")
current_name=$(awk -F'\t' -v m="$current" '$2 == m { print $1 }' <<<"$list")
# Check if a file was selected
if [[ -n "$chosen_file" ]]; then
full_path="$animations_dir/$chosen_file.conf"
cp "$full_path" "$UserConfigs/UserAnimations.conf"
notify-send -u low -i "$iDIR/ja.png" "$chosen_file" "Hyprland Animation Loaded"
fi
chosen=$(cut -f1 <<<"$list" | rofi -i -dmenu -p "Animations" \
-theme "$rofi_theme" \
-theme-str 'mainbox { children: [ "inputbar", "message", "listview" ]; }' \
-mesg "Current: ${current_name:-$current}")
[[ -z "$chosen" ]] && exit 0
sleep 1
"$SCRIPTSDIR/RefreshNoWaybar.sh"
module=$(awk -F'\t' -v n="$chosen" '$1 == n { print $2; exit }' <<<"$list")
[[ -z "$module" ]] && exit 1
sed -i "s/^require(\"presets\.[^\"]*\")$/require(\"presets.$module\")/" "$active_file"
hyprctl reload >/dev/null
notify-send -u low -i "$iDIR/note.png" "$chosen" "Hyprland animation loaded"

View File

@@ -3,7 +3,7 @@
# Clipboard Manager. This script uses cliphist, rofi, and wl-copy.
# Variables
rofi_theme="$HOME/.config/rofi/config-clipboard.rasi"
rofi_theme="$HOME/.config/rofi/launchers/type-4/style-4.rasi"
msg='👀 **note** CTRL DEL = cliphist del (entry) or ALT DEL - cliphist wipe (all)'
# Actions:
# CTRL Del to delete an entry
@@ -19,7 +19,9 @@ while true; do
rofi -i -dmenu \
-kb-custom-1 "Control-Delete" \
-kb-custom-2 "Alt-Delete" \
-config $rofi_theme < <(cliphist list) \
-theme "$rofi_theme" \
-theme-str 'mainbox { children: [ "inputbar", "message", "listview" ]; }' \
< <(cliphist list) \
-mesg "$msg"
)

View File

@@ -0,0 +1,73 @@
#!/bin/bash
# Float + resize (to 781x702) + center the Bitwarden browser-extension pop-out window.
#
# Why this exists instead of a window rule: Hyprland's `float`/`center`/`size`
# rules are "static" — evaluated exactly once, at the instant a window is
# created — and this popup opens with the generic title "Zen Browser" (same
# initialTitle/initialClass as the main browser window), only updating to
# "Extension: (Bitwarden Password Manager) - Bitwarden..." a moment after
# it's already mapped. By then the static rule has already missed its one
# chance and there's no field available at creation time to tell the popup
# apart from a normal browser window. See layout/windowrules.lua and
# https://github.com/hyprwm/Hyprland/issues/7402 for the same problem class.
#
# No socat/ncat is installed, so instead of an event-driven IPC listener this
# just polls `hyprctl clients` at a short interval with jq (already
# installed).
#
# Dispatch syntax note: Hyprland 0.55+'s Lua config changed `hyprctl dispatch`
# to take a literal Lua expression calling hl.dsp.* (e.g.
# `hyprctl dispatch 'hl.dsp.window.center()'`), not the old
# "centerwindow"-style dispatcher strings. Verified live against the real
# API in /usr/share/hypr/stubs/hl.meta.lua before wiring this in.
#
# Why it re-asserts for a few seconds instead of firing once: right after the
# popup leaves the tiled layout, Hyprland briefly re-tiles the sibling window
# to fill the freed space, and during that reflow this popup was observed
# reporting `floating: false` again for one poll tick — snapping it back to
# its old tiled geometry. Re-asserting float+center for a short settle window
# after first detection rides that out. After the settle window it's left
# alone, so a manual re-tile/move by you later isn't fought.
TITLE_PATTERN='Extension: \(Bitwarden Password Manager\)'
POLL_INTERVAL=0.3
SETTLE_TICKS=6 # ~2s of re-assertion after first detection, at POLL_INTERVAL
TARGET_WIDTH=781
TARGET_HEIGHT=702
declare -A ticks_left # address -> remaining re-assert ticks
while true; do
sleep "$POLL_INTERVAL"
# Forget any tracked window that's no longer open.
open_addresses=$(hyprctl clients -j | jq -r '.[].address')
for addr in "${!ticks_left[@]}"; do
grep -qF "$addr" <<<"$open_addresses" || unset 'ticks_left[$addr]'
done
# Process substitution (not a pipe) so the while loop runs in this shell
# and its writes to ticks_left actually persist across iterations.
while IFS= read -r win; do
title=$(jq -r '.title' <<<"$win")
[[ "$title" =~ $TITLE_PATTERN ]] || continue
address=$(jq -r '.address' <<<"$win")
floating=$(jq -r '.floating' <<<"$win")
# First time we've seen this window: start its settle window.
[[ -v ticks_left[$address] ]] || ticks_left[$address]=$SETTLE_TICKS
# Already floating and past its settle window: leave it alone.
[[ "$floating" == "true" && ${ticks_left[$address]} -le 0 ]] && continue
hyprctl dispatch "hl.dsp.focus({ window = \"address:$address\" })" >/dev/null
hyprctl dispatch 'hl.dsp.window.float({ action = "enable" })' >/dev/null
# resize({x=W,y=H}) sets the absolute size (not a delta) — verified live.
# Must happen before center(), since resizing shifts what "centered" means.
hyprctl dispatch "hl.dsp.window.resize({ x = $TARGET_WIDTH, y = $TARGET_HEIGHT })" >/dev/null
hyprctl dispatch 'hl.dsp.window.center()' >/dev/null
ticks_left[$address]=$((ticks_left[$address] - 1))
done < <(hyprctl clients -j | jq -c '.[] | {address, title, floating}')
done

View File

@@ -4,6 +4,11 @@
music_icon="$HOME/.config/swaync/icons/music.png"
# Use the app's own icon for known players
case "$(playerctl metadata --format '{{playerName}}' 2>/dev/null)" in
Plexamp) music_icon="com.plexamp.Plexamp" ;;
esac
# Play the next track
play_next() {
playerctl next

View File

@@ -1,6 +1,6 @@
#!/bin/bash
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# Scripts for refreshing waybar, rofi, swaync
# Scripts for refreshing quickshell, rofi, swaync (Waybar is retired — not restarted here)
SCRIPTSDIR=$HOME/.config/hypr/scripts
UserScripts=$HOME/.config/hypr/UserScripts
@@ -15,31 +15,24 @@ file_exists() {
}
# Kill already running processes
_ps=(waybar rofi swaync ags)
_ps=(rofi swaync ags)
for _prs in "${_ps[@]}"; do
if pidof "${_prs}" >/dev/null; then
pkill "${_prs}"
fi
done
# ensure waybar fully reloads
killall -SIGUSR2 waybar
# quit ags & relaunch ags
#ags -q && ags &
# quit quickshell & relaunch quickshell
pkill qs && qs &
pkill -x qs; qs &
# some process to kill
for pid in $(pidof waybar rofi swaync ags swaybg); do
for pid in $(pidof rofi swaync ags swaybg); do
kill -SIGUSR1 "$pid"
done
#Restart waybar
sleep 1
waybar &
# relaunch swaync
sleep 0.5
swaync > /dev/null 2>&1 &

View File

@@ -0,0 +1,29 @@
#!/bin/bash
# Keeps hypridle from locking the screen while Sunshine is streaming.
# Hooked up as Sunshine's global_prep_cmd, so it runs for every app:
# start -> hold a systemd idle inhibitor (hypridle respects these) and
# unlock hyprlock if the session was already locked
# stop -> release the inhibitor so normal idle locking resumes
pidfile="${XDG_RUNTIME_DIR:-/tmp}/sunshine-idle-inhibit.pid"
case "$1" in
start)
if ! { [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; }; then
setsid systemd-inhibit --what=idle --who=Sunshine --why="Streaming" \
sleep infinity >/dev/null 2>&1 &
echo $! > "$pidfile"
fi
# SIGUSR1 tells hyprlock to unlock
pkill -USR1 -x hyprlock
;;
stop)
[ -f "$pidfile" ] && kill "$(cat "$pidfile")" 2>/dev/null
rm -f "$pidfile"
;;
*)
echo "Usage: $0 start|stop" >&2
exit 1
;;
esac
exit 0

View File

@@ -1,32 +1,30 @@
#!/bin/bash
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# For disabling touchpad.
# Edit the Touchpad_Device on ~/.config/hypr/UserConfigs/Laptops.conf according to your system
# use hyprctl devices to get your system touchpad device name
# source https://github.com/hyprwm/Hyprland/discussions/4283?sort=new#discussioncomment-8648109
# Toggles the laptop touchpad (XF86TouchpadToggle).
# Finds the touchpad by name in `hyprctl devices`, so it works on any laptop
# without hard-coding the device. The Lua config rejects `hyprctl keyword`,
# so the device is switched with hl.device() through `hyprctl eval`.
notif="$HOME/.config/swaync/images/ja.png"
notif="$HOME/.config/swaync/images/note.png"
STATUS_FILE="$XDG_RUNTIME_DIR/touchpad.status"
export STATUS_FILE="$XDG_RUNTIME_DIR/touchpad.status"
enable_touchpad() {
printf "true" >"$STATUS_FILE"
notify-send -u low -i $notif " Enabling" " touchpad"
hyprctl keyword '$TOUCHPAD_ENABLED' "true" -r
}
disable_touchpad() {
printf "false" >"$STATUS_FILE"
notify-send -u low -i $notif " Disabling" " touchpad"
hyprctl keyword '$TOUCHPAD_ENABLED' "false" -r
}
if ! [ -f "$STATUS_FILE" ]; then
enable_touchpad
else
if [ $(cat "$STATUS_FILE") = "true" ]; then
disable_touchpad
elif [ $(cat "$STATUS_FILE") = "false" ]; then
enable_touchpad
fi
device=$(hyprctl devices -j | jq -r '[.mice[].name | select(test("touchpad"; "i"))][0] // empty')
if [ -z "$device" ]; then
notify-send -u low -i "$notif" "Touchpad" "No touchpad found"
exit 1
fi
set_touchpad() {
printf '%s' "$1" >"$STATUS_FILE"
hyprctl eval "hl.device({ name = \"$device\", enabled = $1 })" >/dev/null
if [ "$1" = true ]; then
notify-send -u low -i "$notif" " Enabling" " touchpad"
else
notify-send -u low -i "$notif" " Disabling" " touchpad"
fi
}
if [ "$(cat "$STATUS_FILE" 2>/dev/null)" = "false" ]; then
set_touchpad true
else
set_touchpad false
fi

View File

@@ -1,143 +1,37 @@
#!/bin/bash
# HyprFlux — https://github.com/ahmad9059/HyprFlux
# Scripts for volume controls for audio and mic
# Volume controls for audio and mic via wpctl (PipeWire).
# Quickshell's VolumeOsd watches PipeWire directly, so no notifications here.
iDIR="$HOME/.config/swaync/icons"
sDIR="$HOME/.config/hypr/scripts"
SINK="@DEFAULT_AUDIO_SINK@"
SOURCE="@DEFAULT_AUDIO_SOURCE@"
STEP="5%"
# Get Volume
# Print volume as an integer percent, or "Muted"
get_volume() {
volume=$(pamixer --get-volume)
if [[ "$volume" -eq "0" ]]; then
local out
out=$(wpctl get-volume "$1")
if [[ "$out" == *MUTED* ]]; then
echo "Muted"
else
echo "$volume %"
awk '{ printf "%d %%\n", $2 * 100 + 0.5 }' <<< "$out"
fi
}
# Get icons
get_icon() {
current=$(get_volume)
if [[ "$current" == "Muted" ]]; then
echo "$iDIR/volume-mute.png"
elif [[ "${current%\%}" -le 30 ]]; then
echo "$iDIR/volume-low.png"
elif [[ "${current%\%}" -le 60 ]]; then
echo "$iDIR/volume-mid.png"
else
echo "$iDIR/volume-high.png"
fi
is_muted() {
[[ "$(wpctl get-volume "$1")" == *MUTED* ]]
}
# Notify
notify_user() {
if [[ "$(get_volume)" == "Muted" ]]; then
notify-send -e -h string:x-canonical-private-synchronous:volume_notif -h boolean:SWAYNC_BYPASS_DND:true -u low -i "$(get_icon)" " Volume:" " Muted"
else
notify-send -e -h int:value:"$(get_volume | sed 's/%//')" -h string:x-canonical-private-synchronous:volume_notif -h boolean:SWAYNC_BYPASS_DND:true -u low -i "$(get_icon)" " Volume Level:" " $(get_volume)" &&
"$sDIR/Sounds.sh" --volume
fi
}
# Raising/lowering while muted unmutes first, matching the old behaviour
inc() { is_muted "$1" && wpctl set-mute "$1" 0 || wpctl set-volume -l 1.0 "$1" "$STEP+"; }
dec() { is_muted "$1" && wpctl set-mute "$1" 0 || wpctl set-volume "$1" "$STEP-"; }
# Increase Volume
inc_volume() {
if [ "$(pamixer --get-mute)" == "true" ]; then
toggle_mute
else
pamixer -i 5 --allow-boost --set-limit 150 && notify_user
fi
}
# Decrease Volume
dec_volume() {
if [ "$(pamixer --get-mute)" == "true" ]; then
toggle_mute
else
pamixer -d 5 && notify_user
fi
}
# Toggle Mute
toggle_mute() {
if [ "$(pamixer --get-mute)" == "false" ]; then
pamixer -m && notify-send -e -u low -h boolean:SWAYNC_BYPASS_DND:true -i "$iDIR/volume-mute.png" " Mute"
elif [ "$(pamixer --get-mute)" == "true" ]; then
pamixer -u && notify-send -e -u low -h boolean:SWAYNC_BYPASS_DND:true -i "$(get_icon)" " Volume:" " Switched ON"
fi
}
# Toggle Mic
toggle_mic() {
if [ "$(pamixer --default-source --get-mute)" == "false" ]; then
pamixer --default-source -m && notify-send -e -u low -h boolean:SWAYNC_BYPASS_DND:true -i "$iDIR/microphone-mute.png" " Microphone:" " Switched OFF"
elif [ "$(pamixer --default-source --get-mute)" == "true" ]; then
pamixer -u --default-source u && notify-send -e -u low -h boolean:SWAYNC_BYPASS_DND:true -i "$iDIR/microphone.png" " Microphone:" " Switched ON"
fi
}
# Get Mic Icon
get_mic_icon() {
current=$(pamixer --default-source --get-volume)
if [[ "$current" -eq "0" ]]; then
echo "$iDIR/microphone-mute.png"
else
echo "$iDIR/microphone.png"
fi
}
# Get Microphone Volume
get_mic_volume() {
volume=$(pamixer --default-source --get-volume)
if [[ "$volume" -eq "0" ]]; then
echo "Muted"
else
echo "$volume %"
fi
}
# Notify for Microphone
notify_mic_user() {
volume=$(get_mic_volume)
icon=$(get_mic_icon)
notify-send -e -h int:value:"$volume" -h "string:x-canonical-private-synchronous:volume_notif" -h boolean:SWAYNC_BYPASS_DND:true -u low -i "$icon" " Mic Level:" " $volume"
}
# Increase MIC Volume
inc_mic_volume() {
if [ "$(pamixer --default-source --get-mute)" == "true" ]; then
toggle_mic
else
pamixer --default-source -i 5 && notify_mic_user
fi
}
# Decrease MIC Volume
dec_mic_volume() {
if [ "$(pamixer --default-source --get-mute)" == "true" ]; then
toggle-mic
else
pamixer --default-source -d 5 && notify_mic_user
fi
}
# Execute accordingly
if [[ "$1" == "--get" ]]; then
get_volume
elif [[ "$1" == "--inc" ]]; then
inc_volume
elif [[ "$1" == "--dec" ]]; then
dec_volume
elif [[ "$1" == "--toggle" ]]; then
toggle_mute
elif [[ "$1" == "--toggle-mic" ]]; then
toggle_mic
elif [[ "$1" == "--get-icon" ]]; then
get_icon
elif [[ "$1" == "--get-mic-icon" ]]; then
get_mic_icon
elif [[ "$1" == "--mic-inc" ]]; then
inc_mic_volume
elif [[ "$1" == "--mic-dec" ]]; then
dec_mic_volume
else
get_volume
fi
case "$1" in
--inc) inc "$SINK" ;;
--dec) dec "$SINK" ;;
--toggle) wpctl set-mute "$SINK" toggle ;;
--toggle-mic) wpctl set-mute "$SOURCE" toggle ;;
--mic-inc) inc "$SOURCE" ;;
--mic-dec) dec "$SOURCE" ;;
--get-mic) get_volume "$SOURCE" ;;
*) get_volume "$SINK" ;;
esac

View File

@@ -1,5 +0,0 @@
accent_primary = "#007acc"
accent_secondary = "#06b6d4"
background = "#1e1e1e"
foreground = "#d4d4d8"
opacity = 0.91

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Sunshine_Logo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" width="256px" height="256px" viewBox="10 6 236 245" style="enable-background:new 0 0 256 256;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FDD107;}
.st1{fill:#F89A1C;}
.st2{fill:#EF3E23;}
.st3{fill:#F26222;}
</style>
<path class="st0" d="M118.7688675,20.7120476c0,0-63.8333359,26-74.3333359,83.8333282s37.1666718,91.5,86.3333282,75.3333282
s70.3333435-51,81.8333435-86.9999924c0,0-9.3333435,100.4999924-96.1666718,115.4999924s-118.1666641-50-82.1666641-119.8333282
C44.2688675,67.0453796,80.5188675,29.6287155,118.7688675,20.7120476z"/>
<path class="st1" d="M118.7688675,20.7120476c0,0-41.125,3.6666679-83.25,61.0416679s-28.125,139.125,34.25,149.375
s115.8749924-44.875,133.5-82.375s15.1666718-61.4583282,9.75-77.8749924c0,0,0.6666718,36.4166641-13.3333282,59.6666641
s-29.75,46.3333282-65.0833282,62.1666718s-74.1666718,13.75-95.4166718-19.25s-5.9166641-76.0833359-0.2916641-85.3333359
S72.3938675,33.7953796,118.7688675,20.7120476z"/>
<path class="st2" d="M73.0188675,39.6287155c0,0,38.125-28.125,76.8749924-28.125s63,28.25,68.5,52.25s6,54.125-11.5,87.6249924
s-37.375,56-79.1249924,76.125s-84.625,2.75-84.625,2.75s25.9769745,25.8750153,71.0509872,16.5
c45.0740051-9.375,82.2406769-40.875,98.4073486-69.5s28.7916565-57.3749924,27.6666565-92.2499924s-23.75-54.5-31.25-60.25
s-23.1875-17.8125-58.1875-16.5625S86.4563675,29.8162155,73.0188675,39.6287155z"/>
<path class="st3" d="M73.0188675,39.6287155c0,0,35-32.8125,82.4374924-32.8125s69.1875,24.8125,78.875,44.6875
s21.8125,70-12.1875,122.9999924s-74.625,67.375-93.625,71.625s-42.4311447,4.269165-59.1114044-1.2299957
c0,0,35.1947479,8.3966675,66.7780762-7.4366608s51.6666718-32.1666718,74.0833282-68.8333435
s25.9166718-72.7499924,22.1666718-93.9166565s-12.1666718-42.4166718-36.5-56.3333359s-56.7291718-10.531251-74.4791641-4.531251
S91.9876175,26.4099655,73.0188675,39.6287155z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,53 @@
pragma Singleton
import QtQuick
QtObject {
readonly property string fontFamily: "CaskaydiaCove Nerd Font Mono"
readonly property string iconFontFamily: "Material Symbols Outlined"
// Same glyph set, softer/rounded terminals — used where a module wants
// a visually distinct icon style from the rest of the bar (currently
// just BluetoothIndicator.qml) without switching every icon over.
readonly property string iconFontFamilyRounded: "Material Symbols Rounded"
// Brand logos (YouTube, Spotify, Plex...), which Material Symbols doesn't
// have — used by MediaPlayer.qml's per-source icon. The "Propo" build
// draws Nerd Font icons at full size; the Mono one squeezes them into a
// single character cell, which renders them noticeably smaller.
readonly property string brandIconFontFamily: "CaskaydiaCove Nerd Font Propo"
// Material Symbols is a variable font. Left unset, glyphs fall back to the
// font's default optical size (24px), which is tuned for larger rendering
// and makes some icons (e.g. "lock") look heavier/bigger than others when
// drawn small — pin "opsz" near the actual render size to keep icons
// visually consistent. See Appearance.iconSize for the matching pixelSize.
readonly property var iconFontAxes: ({ "FILL": 0, "wght": 400, "GRAD": 0, "opsz": 20 })
readonly property int iconSize: 16
readonly property int fontSize: 13
readonly property int fontSizeSmall: 11
readonly property int radius: 14
readonly property int radiusSmall: 8
readonly property int barHeight: 34
readonly property int spacing: 10
readonly property int animationDuration: 150
// Max characters for the bar's free-text labels before they're cut off
// with "…" (ActiveWindow.qml and MediaPlayer.qml).
readonly property int activeWindowMaxChars: 50
readonly property int mediaMaxChars: 40
// Tighter limits for narrow screens (the laptop's eDP-1 is 1440 logical
// px wide), where the full-length labels nearly run into the centre
// clock/weather group. Bar.qml switches when it's narrower than
// compactBarWidth.
readonly property int compactBarWidth: 1600
readonly property int activeWindowMaxCharsCompact: 32
readonly property int mediaMaxCharsCompact: 28
// Counts code points rather than UTF-16 units so emoji / some CJK
// aren't split in half at the cut.
function truncate(str, maxChars) {
const chars = Array.from(str || "")
return chars.length > maxChars ? chars.slice(0, maxChars - 1).join("").replace(/\s+$/, "") + "…" : chars.join("")
}
}

View File

@@ -0,0 +1,62 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
// Wallpaper-driven palette, sourced from pywal's ~/.cache/wal/colors.json.
// Run `wal -i /path/to/wallpaper.png` to (re)generate it — this file watches
// the cache and updates live, no shell restart needed. Until that's been run
// once, everything falls back to a hand-picked dark palette below.
QtObject {
id: root
readonly property bool active: _parsed !== null
property var _parsed: null
// --- Fallback palette (used until pywal has run) ---
readonly property color fallbackBg: "#1a1b26"
readonly property color fallbackFg: "#c0caf5"
readonly property var fallbackPalette: [
"#15161e", "#f7768e", "#9ece6a", "#e0af68",
"#7aa2f7", "#bb9af7", "#7dcfff", "#a9b1d6",
"#414868", "#f7768e", "#9ece6a", "#e0af68",
"#7aa2f7", "#bb9af7", "#7dcfff", "#c0caf5"
]
function paletteColor(i) {
if (_parsed && _parsed.colors)
return _parsed.colors["color" + i] || fallbackPalette[i]
return fallbackPalette[i]
}
readonly property color background: _parsed && _parsed.special ? _parsed.special.background : fallbackBg
readonly property color foreground: _parsed && _parsed.special ? _parsed.special.foreground : fallbackFg
readonly property color primary: paletteColor(4)
// Named primaryText (not "onPrimary") because QML treats any "onXxx"
// identifier as a signal handler slot, not a regular property.
readonly property color primaryText: background
readonly property color urgent: paletteColor(1)
// paletteColor(8) alone renders quite dark/low-contrast for text (it's
// meant as a muted *background* tone in most wal palettes) — lighten it
// so muted labels stay legible against the bar's dark background.
readonly property color muted: Qt.lighter(paletteColor(8), 2.2)
readonly property color surface: Qt.lighter(background, 1.35)
// QtObject has no default property, so the FileView is attached via a
// property assignment rather than as a plain child element.
readonly property FileView _walFile: FileView {
path: Quickshell.env("HOME") + "/.cache/wal/colors.json"
watchChanges: true
onLoaded: {
try {
root._parsed = JSON.parse(text())
} catch (e) {
console.warn("Colors: failed to parse pywal cache:", e)
root._parsed = null
}
}
onLoadFailed: (error) => {
root._parsed = null
}
}
}

View File

@@ -0,0 +1,72 @@
pragma Singleton
import QtQuick
import Quickshell.Io
// Shared state for SystemUpdate.qml, the bar's package-update indicator.
// Bar.qml is instantiated once per screen (see shell.qml's Variants), so
// without this being a singleton, plugging in extra monitors would mean
// running `checkupdates` that many times in parallel on every poll — which
// isn't just wasteful, it's actively unreliable: concurrent invocations
// race over checkupdates' own sync directory and can intermittently report
// 0 pending updates. One shared Process here means exactly one `checkupdates`
// run at a time, and every bar's icon reflects the same result.
QtObject {
id: root
// checkupdates syncs a throwaway copy of the pacman db, so it's not
// free to run often — matches the old waybar custom/updater module's
// ~21.7 minute poll interval (see .config/waybar/config).
readonly property int refreshIntervalMs: 1300 * 1000
property int updateCount: 0
property bool checking: false
property bool updating: false
function refresh() {
if (_checkProc.running) return
root.checking = true
_checkProc.running = true
}
// scriptPath: absolute path to update-system.sh (resolved by the caller
// via Quickshell.shellPath, which isn't available as a plain import
// here without pulling in the whole Quickshell singleton for one call).
function runUpdate(scriptPath) {
if (_updateProc.running) return
root.updating = true
_updateProc.command = [scriptPath]
_updateProc.running = true
}
// QtObject has no default property, so these are attached via property
// assignment rather than as plain child elements (same as Colors.qml's
// FileView).
readonly property Process _checkProc: Process {
command: ["sh", "-c", "checkupdates 2>/dev/null | wc -l"]
stdout: StdioCollector {
id: checkCollector
onStreamFinished: {
root.updateCount = parseInt(checkCollector.text.trim(), 10) || 0
root.checking = false
}
}
}
// Runs update-system.sh, which opens its own terminal and blocks until
// that terminal is closed — so this only exits once the user's actually
// done updating, which is exactly when a re-check matters.
readonly property Process _updateProc: Process {
onExited: {
root.updating = false
root.refresh()
}
}
readonly property Timer _timer: Timer {
interval: root.refreshIntervalMs
running: true
repeat: true
triggeredOnStart: true
onTriggered: root.refresh()
}
}

View File

@@ -0,0 +1,17 @@
pragma Singleton
import QtQuick
// Tailscale, Mullvad, ProtonVPN, and the home UniFi WireGuard tunnel all
// fight over the default route/DNS if more than one is active at once (see
// VpnIndicator.qml's header comment) — only one should ever actually be
// connected. This singleton is the shared signal between the two widget
// files: VpnIndicator.qml writes each tunnel's live connection state here
// as it polls them; TailscaleIndicator.qml reads it to back off (`tailscale
// down`) rather than stay up alongside one, and to refuse turning Tailscale
// back on while one is still connected.
QtObject {
property bool mullvadConnected: false
property bool protonConnected: false
property bool homeWgConnected: false
readonly property bool commercialVpnActive: mullvadConnected || protonConnected || homeWgConnected
}

View File

@@ -0,0 +1,242 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
// Live weather for the bar's Weather.qml icon. A singleton for the same
// reason as SystemUpdates.qml: Bar.qml is instantiated once per monitor
// (shell.qml's Variants), so without this being shared, every extra screen
// would mean another set of network requests running in parallel.
//
// Location is auto-detected from the public IP (via ipwho.is, no API key)
// and cached to disk (~/.cache/quickshell/weather-location.json) so it's
// only ever looked up once, not on every quickshell restart — IP-geolocation
// free tiers rate-limit fast, and a desktop's location barely ever changes
// anyway. Only the weather itself re-polls on a timer. Conditions come from
// Open-Meteo (api.open-meteo.com) — also free and keyless, and it does its
// own °C/km-h conversion server-side via query params.
//
// Loosely modeled on omarchy's weather panel
// (github.com/omacom/omarchy, quattro branch,
// shell/plugins/panels/weather/Model.js) for the WMO weather-code
// grouping, but the icons are this bar's own Material Symbols glyphs
// rather than pulling in a separate icon font just for this.
QtObject {
id: root
// Location barely ever changes for a desktop machine, so it's fetched
// once (and cached); the weather itself is worth re-checking
// periodically.
readonly property int refreshIntervalMs: 15 * 60 * 1000
property bool locating: true
property bool loading: false
property bool hasLocation: false
property string locationName: ""
property real latitude: 0
property real longitude: 0
function saveLocation() {
_locationCache.setText(JSON.stringify({
latitude: root.latitude,
longitude: root.longitude,
name: root.locationName
}))
}
// Called from Weather.qml's search box once you pick a result — always
// wins over whatever IP-based guess was cached before, and persists so
// it sticks across restarts.
function selectLocation(name, lat, lon) {
root.latitude = lat
root.longitude = lon
root.locationName = name
root.hasLocation = true
root.locating = false
root.saveLocation()
root.refresh()
}
property bool searching: false
property var searchResults: []
function search(query) {
const q = String(query || "").trim()
if (q.length < 2) {
root.searchResults = []
return
}
root.searching = true
_searchProc.command = ["curl", "-s", "--max-time", "6",
"https://geocoding-api.open-meteo.com/v1/search?count=5&language=en&format=json&name=" + encodeURIComponent(q)]
_searchProc.running = true
}
property real tempC: 0
property real feelsLikeC: 0
property int humidity: 0
property real windKmh: 0
property int weatherCode: 0
property bool isDay: true
// [{date, code, maxC, minC}] — tomorrow onward; today is already
// covered by the current-conditions fields above.
property var forecast: []
function locate() {
if (_locateProc.running) return
root.locating = true
_locateProc.running = true
}
function refresh() {
if (!root.hasLocation || _weatherProc.running) return
root.loading = true
_weatherProc.command = ["curl", "-s", "--max-time", "8",
"https://api.open-meteo.com/v1/forecast?latitude=" + root.latitude
+ "&longitude=" + root.longitude
+ "&current=temperature_2m,apparent_temperature,relative_humidity_2m,weather_code,is_day,wind_speed_10m"
+ "&daily=weather_code,temperature_2m_max,temperature_2m_min"
+ "&temperature_unit=celsius&wind_speed_unit=kmh&timezone=auto&forecast_days=4"]
_weatherProc.running = true
}
// Material Symbols Rounded glyph for a WMO weather code (see
// https://open-meteo.com/en/docs#weathervariables), grouped the same
// way omarchy's Model.js groups them into its own icon set.
function iconFor(code, day) {
if (code === 0) return day ? "sunny" : "clear_night"
if (code === 1 || code === 2) return day ? "partly_cloudy_day" : "partly_cloudy_night"
if (code === 3) return "cloud"
if (code === 45 || code === 48) return "foggy"
if ([51, 53, 55, 56, 57, 61, 63, 65, 66, 67, 80, 81, 82].indexOf(code) !== -1) return "rainy"
if ([71, 73, 75, 77, 85, 86].indexOf(code) !== -1) return "weather_snowy"
if (code === 95 || code === 96 || code === 99) return "thunderstorm"
return "cloud"
}
// QtObject has no default property, so these are attached via property
// assignment rather than as plain child elements (same as Colors.qml's
// FileView and SystemUpdates.qml's Process/Timer).
// Read once at startup; onLoaded/onLoadFailed (below) decide whether a
// network lookup is still needed.
readonly property FileView _locationCache: FileView {
path: Quickshell.env("HOME") + "/.cache/quickshell/weather-location.json"
onLoaded: {
try {
const data = JSON.parse(text())
const lat = parseFloat(data.latitude)
const lon = parseFloat(data.longitude)
if (!isNaN(lat) && !isNaN(lon)) {
root.latitude = lat
root.longitude = lon
root.locationName = data.name || ""
root.hasLocation = true
root.locating = false
root.refresh()
return
}
} catch (e) {
// fall through to a fresh lookup below
}
root.locate()
}
onLoadFailed: root.locate()
}
readonly property Process _locateProc: Process {
command: ["curl", "-s", "--max-time", "6", "https://ipwho.is/"]
stdout: StdioCollector {
id: locateCollector
onStreamFinished: {
try {
const data = JSON.parse(locateCollector.text)
const lat = parseFloat(data.latitude)
const lon = parseFloat(data.longitude)
root.hasLocation = data.success !== false && !isNaN(lat) && !isNaN(lon)
if (root.hasLocation) {
root.latitude = lat
root.longitude = lon
root.locationName = data.city || data.region || data.country || ""
root.saveLocation()
}
} catch (e) {
console.warn("Weather: locate parse failed:", e)
root.hasLocation = false
}
root.locating = false
if (root.hasLocation) root.refresh()
}
}
}
readonly property Process _weatherProc: Process {
stdout: StdioCollector {
id: weatherCollector
onStreamFinished: {
try {
const data = JSON.parse(weatherCollector.text)
const current = data.current || {}
root.tempC = current.temperature_2m
root.feelsLikeC = current.apparent_temperature
root.humidity = current.relative_humidity_2m
root.windKmh = current.wind_speed_10m
root.weatherCode = current.weather_code
root.isDay = current.is_day === 1
const daily = data.daily || {}
const times = daily.time || []
const days = []
// Start at 1 — index 0 is today, already shown as the
// current conditions above; this is "what's coming".
for (let i = 1; i < times.length && days.length < 3; i++) {
days.push({
date: times[i],
code: (daily.weather_code || [])[i],
maxC: (daily.temperature_2m_max || [])[i],
minC: (daily.temperature_2m_min || [])[i]
})
}
root.forecast = days
} catch (e) {
console.warn("Weather: failed to parse Open-Meteo response:", e)
}
root.loading = false
}
}
}
readonly property Process _searchProc: Process {
stdout: StdioCollector {
id: searchCollector
onStreamFinished: {
try {
const data = JSON.parse(searchCollector.text)
const results = data.results || []
root.searchResults = results.filter(function(r) {
return r && r.name && r.latitude !== undefined && r.longitude !== undefined
}).map(function(r) {
return {
name: r.name,
description: [r.admin1, r.country].filter(function(p) { return !!p }).join(", "),
latitude: r.latitude,
longitude: r.longitude
}
})
} catch (e) {
root.searchResults = []
}
root.searching = false
}
}
}
readonly property Timer _timer: Timer {
interval: root.refreshIntervalMs
running: true
repeat: true
onTriggered: root.refresh()
}
}

View File

@@ -0,0 +1,6 @@
module config
singleton Colors 1.0 Colors.qml
singleton Appearance 1.0 Appearance.qml
singleton VpnExclusivity 1.0 VpnExclusivity.qml
singleton SystemUpdates 1.0 SystemUpdates.qml
singleton WeatherData 1.0 WeatherData.qml

View File

@@ -0,0 +1,12 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Hyprland
import "../../config"
Text {
property bool compact: false
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
text: Hyprland.activeToplevel ? Appearance.truncate(Hyprland.activeToplevel.title, compact ? Appearance.activeWindowMaxCharsCompact : Appearance.activeWindowMaxChars) : ""
}

View File

@@ -0,0 +1,67 @@
import Quickshell
import Quickshell.Wayland
import QtQuick
import QtQuick.Layouts
import "../../config"
PanelWindow {
id: bar
anchors {
top: true
left: true
right: true
}
implicitHeight: Appearance.barHeight
exclusiveZone: implicitHeight
color: "transparent"
// Narrow screen (laptop): shorten the left-side free-text labels so they
// don't crowd the centre group.
readonly property bool compact: width < Appearance.compactBarWidth
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "quickshell:bar"
Rectangle {
anchors.fill: parent
color: Colors.background
}
RowLayout {
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
leftMargin: Appearance.spacing
}
spacing: Appearance.spacing
Workspaces { screen: bar.screen }
MediaPlayer { barWindow: bar; compact: bar.compact }
ActiveWindow { compact: bar.compact }
}
RowLayout {
anchors.centerIn: parent
spacing: Appearance.spacing
Weather { barWindow: bar }
Clock {}
SystemUpdate { barWindow: bar }
}
RowLayout {
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
rightMargin: Appearance.spacing
}
spacing: Appearance.spacing
PinnedTray {}
TailscaleIndicator { barWindow: bar }
VpnIndicator { barWindow: bar }
NetworkIndicator { barWindow: bar }
BluetoothIndicator { barWindow: bar }
Volume { barWindow: bar }
BatteryIndicator { barWindow: bar }
PowerButton { barWindow: bar }
}
}

View File

@@ -0,0 +1,605 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
import Quickshell.Services.UPower
import "../../config"
// Battery + power-profile panel, ported from omarchy's power panel
// (github.com/basecamp/omarchy shell/plugins/panels/power/{Model.js,Panel.qml})
// onto this project's own conventions: Quickshell's native UPower/PowerProfiles
// bindings instead of omarchy's `omarchy-battery-status` /
// `omarchy-powerprofiles-list` / `omarchy-powerprofiles-set` helper scripts
// (which don't exist outside omarchy), and Material Symbols glyphs instead of
// Nerd Font ones. The charge-threshold ("Holding"/limited-charge) concept from
// the original is hardware-specific (vendor charge-limit sysfs knobs) and has
// no equivalent in Quickshell's UPower binding, so it's dropped rather than
// guessed at; "Battery health" (computed from sysfs energy_full/design) is shown in its
// place, using data UPower actually reports.
Item {
id: root
readonly property var device: UPower.displayDevice
// isLaptopBattery (type === Battery && powerSupply === true) is the
// Quickshell-recommended check for "is this actually a battery" — more
// precise than isPresent, which the docs note is only meaningful once
// you already know the device is of Battery type.
readonly property bool batteryPresent: !!(root.device && root.device.ready && root.device.isLaptopBattery)
// Hidden entirely on a desktop with no battery — RowLayout skips
// invisible children for both sizing and spacing, so this just vanishes
// from the bar rather than leaving a gap.
visible: root.batteryPresent
implicitWidth: root.batteryPresent ? label.implicitWidth : 0
implicitHeight: Appearance.barHeight
// The bar's own PanelWindow — passed in from Bar.qml so this popup can
// anchor to the bar itself rather than to this icon, keeping it a fixed
// distance from the bar and the screen edge regardless of where this
// icon sits in the row.
required property var barWindow
readonly property bool onBattery: UPower.onBattery
// Named deviceState, not "state" — Item already has a built-in `state`
// property (the property-based state-machine one) that this would
// otherwise collide with.
readonly property int deviceState: root.batteryPresent ? root.device.state : UPowerDeviceState.Unknown
readonly property bool fullyCharged: root.deviceState === UPowerDeviceState.FullyCharged
readonly property bool charging: root.batteryPresent && !root.onBattery && !root.fullyCharged
// device.percentage is a 0..1 fraction despite the name (same convention
// Volume.qml relies on for Pipewire's audio.volume).
readonly property real fraction: root.batteryPresent ? Math.max(0, Math.min(1, root.device.percentage)) : 0
readonly property bool low: root.batteryPresent && root.fraction <= 0.15 && !root.charging
// Quickshell's UPower binding has no charge-cycle property (see the
// file-header comment on why the old charge-until threshold was
// dropped) — it comes straight from sysfs instead.
//
// device.nativePath turned out to be empty on real hardware (tested
// live), so rather than build a path from it, a shell glob finds
// whichever BAT* directory actually exists — same shell-out-to-a-CLI
// convention TailscaleIndicator.qml uses for its own live stats.
// This barely changes during a session, so one read at startup is
// enough; no polling timer.
property int cycleCount: -1
Process {
id: cycleCountProc
command: ["sh", "-c", "cat \"$(ls -d /sys/class/power_supply/BAT* 2>/dev/null | head -n1)/cycle_count\" 2>/dev/null"]
running: root.batteryPresent
stdout: StdioCollector {
id: cycleCountCollector
onStreamFinished: {
const n = parseInt(cycleCountCollector.text.trim(), 10)
root.cycleCount = (isNaN(n) || n <= 0) ? -1 : n
}
}
}
// device.healthPercentage/healthSupported come from upowerd over D-Bus,
// and upowerd has been observed reporting energy-full as a flat 0 (a
// live daemon glitch, `upower -i` shows it too) while sysfs still has
// the real numbers underneath — same class of unreliability that put
// cycle_count on sysfs above instead of trusting upowerd's own
// charge-cycles field. Health is computed the same way UPower computes
// it (current full-charge capacity over the battery's original design
// capacity), just read directly so a daemon hiccup doesn't blank it.
property real energyFullWh: -1
property real energyFullDesignWh: -1
readonly property real healthPercent: (root.energyFullWh > 0 && root.energyFullDesignWh > 0)
? Math.min(100, (root.energyFullWh / root.energyFullDesignWh) * 100) : -1
Process {
id: energyFullProc
command: ["sh", "-c", "cat \"$(ls -d /sys/class/power_supply/BAT* 2>/dev/null | head -n1)/energy_full\" 2>/dev/null"]
running: root.batteryPresent
stdout: StdioCollector {
id: energyFullCollector
onStreamFinished: {
const n = parseFloat(energyFullCollector.text.trim())
root.energyFullWh = (isNaN(n) || n <= 0) ? -1 : (n / 1000000)
}
}
}
Process {
id: energyFullDesignProc
command: ["sh", "-c", "cat \"$(ls -d /sys/class/power_supply/BAT* 2>/dev/null | head -n1)/energy_full_design\" 2>/dev/null"]
running: root.batteryPresent
stdout: StdioCollector {
id: energyFullDesignCollector
onStreamFinished: {
const n = parseFloat(energyFullDesignCollector.text.trim())
root.energyFullDesignWh = (isNaN(n) || n <= 0) ? -1 : (n / 1000000)
}
}
}
// device.timeToFull/timeToEmpty are computed by Quickshell from
// energy/energyCapacity/changeRate — when upowerd's EnergyFull glitches
// to 0 (the same daemon flake HEALTH routes around above), that formula
// divides by a bogus capacity and comes out negative rather than a
// real estimate (confirmed live: timeToFull sat at -3751 while `upower
// -i` itself still reported a normal 26.7 minutes). This recomputes
// the same estimate straight from sysfs as a fallback, only used when
// Quickshell's own value isn't positive. Unlike cycle_count/design
// capacity above, energy_now and power_now change continuously while
// (dis)charging, so this polls on a timer instead of reading once.
property real sysfsEnergyNowWh: -1
property real sysfsPowerNowW: -1
readonly property real fallbackTimeToFullSeconds: (root.sysfsPowerNowW > 0 && root.energyFullWh > 0 && root.sysfsEnergyNowWh >= 0)
? Math.max(0, (root.energyFullWh - root.sysfsEnergyNowWh) / root.sysfsPowerNowW * 3600) : -1
readonly property real fallbackTimeToEmptySeconds: (root.sysfsPowerNowW > 0 && root.sysfsEnergyNowWh > 0)
? (root.sysfsEnergyNowWh / root.sysfsPowerNowW * 3600) : -1
// Prefers upowerd's own live estimate when it's sane; only falls back
// to the sysfs-computed one when upowerd's has gone zero/negative.
readonly property real timeRemainingSeconds: {
const primary = root.onBattery ? root.device.timeToEmpty : root.device.timeToFull
if (primary > 0) return primary
return root.onBattery ? root.fallbackTimeToEmptySeconds : root.fallbackTimeToFullSeconds
}
Timer {
id: sysfsRateTimer
interval: 15000
repeat: true
running: root.batteryPresent
triggeredOnStart: true
onTriggered: {
if (!sysfsEnergyNowProc.running) sysfsEnergyNowProc.running = true
if (!sysfsPowerNowProc.running) sysfsPowerNowProc.running = true
}
}
Process {
id: sysfsEnergyNowProc
command: ["sh", "-c", "cat \"$(ls -d /sys/class/power_supply/BAT* 2>/dev/null | head -n1)/energy_now\" 2>/dev/null"]
stdout: StdioCollector {
id: sysfsEnergyNowCollector
onStreamFinished: {
const n = parseFloat(sysfsEnergyNowCollector.text.trim())
root.sysfsEnergyNowWh = (isNaN(n) || n < 0) ? -1 : (n / 1000000)
}
}
}
Process {
id: sysfsPowerNowProc
command: ["sh", "-c", "cat \"$(ls -d /sys/class/power_supply/BAT* 2>/dev/null | head -n1)/power_now\" 2>/dev/null"]
stdout: StdioCollector {
id: sysfsPowerNowCollector
onStreamFinished: {
const n = parseFloat(sysfsPowerNowCollector.text.trim())
root.sysfsPowerNowW = (isNaN(n) || n <= 0) ? -1 : (n / 1000000)
}
}
}
// Raw UPower state string (Charging/Discharging/PendingCharge/…) — more
// granular than modeLabel below, which collapses those down to the
// three phrases the hero text and STATE row show.
readonly property string rawStateLabel: root.batteryPresent
? UPowerDeviceState.toString(root.deviceState) : "—"
// Material Symbols' charging glyphs aren't an evenly-spaced "_bar"
// scale like the non-charging ones below — they're fixed at these
// seven breakpoints (confirmed against the installed font's ligature
// table), so this picks the highest one at or below the current
// percentage rather than rounding to an index.
function chargingIcon(fraction) {
const percent = fraction * 100
const thresholds = [20, 30, 50, 60, 80, 90, 100]
const icons = [
"battery_charging_20", "battery_charging_30", "battery_charging_50", "battery_charging_60",
"battery_charging_80", "battery_charging_90", "battery_charging_full"
]
for (let i = thresholds.length - 1; i >= 0; i--) {
if (percent >= thresholds[i]) return icons[i]
}
// Below 20% there's no lower charging glyph — it's still the
// truthful floor of what's available while current is flowing in.
return icons[0]
}
function batteryIcon() {
if (!root.batteryPresent) return "battery_unknown"
if (root.fullyCharged) return "battery_full"
if (root.charging) return root.chargingIcon(root.fraction)
if (root.fraction <= 0.1) return "battery_alert"
const bars = ["battery_0_bar", "battery_1_bar", "battery_2_bar", "battery_3_bar", "battery_4_bar", "battery_5_bar", "battery_full"]
return bars[Math.max(0, Math.min(bars.length - 1, Math.round(root.fraction * (bars.length - 1))))]
}
function formatDuration(seconds) {
const s = Math.max(0, Math.round(seconds || 0))
if (s <= 0) return "—"
const h = Math.floor(s / 3600)
const m = Math.round((s % 3600) / 60)
return h > 0 ? (h + "h " + m + "m") : (m + "m")
}
readonly property string modeLabel: {
if (!root.batteryPresent) return ""
if (root.fullyCharged) return "Fully charged"
return root.onBattery ? "On battery" : "Charging"
}
// Info grid rows — same shape as NetworkIndicator.qml's statRows/
// statCells: a flat label/value list, flattened again into individual
// grid cells (label, value, label, value, …) so a 4-column GridLayout
// lays them out as two label/value pairs per row.
readonly property var statRows: [
{
label: "HEALTH",
value: root.healthPercent > 0 ? Math.round(root.healthPercent) + "%" : "—"
},
{
label: root.onBattery ? "TIME LEFT" : "TIME TO FULL",
value: root.batteryPresent ? root.formatDuration(root.timeRemainingSeconds) : "—"
},
{ label: "STATUS", value: root.rawStateLabel },
{ label: "CYCLES", value: root.cycleCount > 0 ? String(root.cycleCount) : "—" }
]
readonly property var statCells: {
const cells = []
for (let i = 0; i < root.statRows.length; i++) {
cells.push({ text: root.statRows[i].label, isLabel: true })
cells.push({ text: root.statRows[i].value, isLabel: false })
}
return cells
}
// Cute rotating status phrases while current is actually flowing —
// ported as-is from omarchy's power panel, which pairs the same phrase
// sets with the same charging/on-battery states.
readonly property var chargingPhrases: [
"Pumping power", "Injecting electrons", "Pouring juice", "Amassing watts",
"Hoarding joules", "Sucking volts", "Topping reserves", "Soaking amps", "Inhaling kilowatts"
]
readonly property var onBatteryPhrases: [
"Slurping power", "Spending joules", "Draining watts", "Burning electrons",
"Sipping juice", "Spending coulombs", "Bleeding amps", "Guzzling volts", "Munching reserves"
]
readonly property var activePhrases: root.fullyCharged ? []
: (root.charging ? root.chargingPhrases : (root.onBattery ? root.onBatteryPhrases : []))
readonly property bool rotatingPhrases: root.activePhrases.length > 0
property int phraseIndex: 0
readonly property string heroStatusText: root.rotatingPhrases
? root.activePhrases[root.phraseIndex % root.activePhrases.length]
: root.modeLabel
// Only rotates while the panel is open and actually charging/discharging.
Timer {
id: phraseTimer
interval: 2800
running: panel.visible && root.rotatingPhrases
repeat: true
triggeredOnStart: false
onTriggered: phraseSwap.restart()
}
SequentialAnimation {
id: phraseSwap
PropertyAnimation { target: heroStatus; property: "opacity"; to: 0.0; duration: 180; easing.type: Easing.OutQuad }
ScriptAction {
script: {
const n = root.activePhrases.length
if (n > 0) root.phraseIndex = (root.phraseIndex + 1) % n
}
}
PropertyAnimation { target: heroStatus; property: "opacity"; to: 1.0; duration: 260; easing.type: Easing.InQuad }
}
// If we leave a rotating state mid-swap (e.g. unplugged mid-fade),
// snap back to full opacity so the new state reads immediately.
Connections {
target: root
function onRotatingPhrasesChanged() {
if (!root.rotatingPhrases) {
phraseSwap.stop()
heroStatus.opacity = 1.0
}
}
}
// --- Power profile list ------------------------------------------------
// power-profiles-daemon only ever exposes these three fixed profiles;
// Performance is conditional on hasPerformanceProfile (e.g. absent on
// battery-only/thermally-limited hardware) — same gating omarchy's own
// Model.js applies via its profile-list script.
readonly property var profileEntries: {
const entries = [
{ value: PowerProfile.PowerSaver, label: "Saver", icon: "eco" },
{ value: PowerProfile.Balanced, label: "Balanced", icon: "balance" }
]
if (PowerProfiles.hasPerformanceProfile)
entries.push({ value: PowerProfile.Performance, label: "Performance", icon: "bolt" })
return entries
}
// --- Automatic power profile --------------------------------------
// Battery-state-driven, on top of the manual picker above: below 20%
// → Power Saver (protect what's left), plugged in → Performance,
// unplugged and healthy → Balanced. Low battery wins even if a weak
// charger is still plugged in — running low is the more urgent signal.
// Performance only gets requested when the daemon actually reports it
// as available, falling back to Balanced instead of erroring on
// hardware without a performance profile (e.g. the ERROR this exact
// widget hit before power-profiles-daemon was installed).
//
// This only ever writes when the *target* changes (crossing the 20%
// line, plugging/unplugging) rather than on every poll, so a manual
// pick from the buttons above still sticks until the next real
// transition instead of being fought immediately.
readonly property int desiredProfile: {
if (root.fraction <= 0.20) return PowerProfile.PowerSaver
if (!root.onBattery) return PowerProfiles.hasPerformanceProfile ? PowerProfile.Performance : PowerProfile.Balanced
return PowerProfile.Balanced
}
function applyDesiredProfile() {
if (!root.batteryPresent) return
if (PowerProfiles.profile !== root.desiredProfile) PowerProfiles.profile = root.desiredProfile
}
onDesiredProfileChanged: root.applyDesiredProfile()
Component.onCompleted: Qt.callLater(root.applyDesiredProfile)
Text {
id: label
anchors.centerIn: parent
font.family: Appearance.iconFontFamilyRounded
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 16
color: root.low ? Colors.urgent : Colors.foreground
text: root.batteryIcon()
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
panel.visible = !panel.visible
grab.active = panel.visible
}
}
// --- Reusable pieces -----------------------------------------------
component ProfileButton: Rectangle {
id: profBtn
required property string label
required property string icon
required property bool active
signal activate()
width: parent ? parent.width : 0
radius: 0
implicitHeight: 44
color: mouse.containsMouse ? Colors.primary
: (profBtn.active ? Qt.rgba(Colors.primary.r, Colors.primary.g, Colors.primary.b, 0.18) : "transparent")
// Hover-only outline, offset outside the button rather than drawn on
// its own edge — same treatment as this project's other selectable
// rows (e.g. Volume.qml's DeviceRow).
Rectangle {
anchors.fill: parent
anchors.margins: -3
radius: 0
color: "transparent"
border.color: Colors.foreground
border.width: mouse.containsMouse ? 1 : 0
}
Column {
anchors.centerIn: parent
spacing: 2
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: profBtn.icon
font.family: Appearance.iconFontFamilyRounded
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 18
color: mouse.containsMouse ? Colors.primaryText : Colors.foreground
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: profBtn.label
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
font.bold: profBtn.active
color: mouse.containsMouse ? Colors.primaryText : Colors.foreground
}
}
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: profBtn.activate()
}
}
// --- Panel -----------------------------------------------------------
PopupWindow {
id: panel
visible: false
color: "transparent"
// Wider than before (244) to give the 2-column info grid's
// label/value pairs enough room per half without eliding —
// same reasoning as NetworkIndicator.qml's own wider panel.
readonly property int contentWidth: 280
readonly property int contentPadding: 16
implicitWidth: contentWidth + contentPadding * 2
implicitHeight: mainColumn.implicitHeight + contentPadding * 2
anchor {
window: root.barWindow
// Unlike anchor.item, anchor.window doesn't default rect to the
// window's own bounds — set it explicitly so edges/gravity below
// resolve against the full bar (and thus the true screen edges)
// instead of a near-zero rect at the window's origin.
rect.x: 0
rect.y: 0
rect.width: root.barWindow.width
rect.height: root.barWindow.height
edges: Edges.Bottom | Edges.Right
gravity: Edges.Bottom | Edges.Left
margins.bottom: -4
margins.right: 0
}
HyprlandFocusGrab {
id: grab
windows: [panel]
onCleared: panel.visible = false
}
Rectangle {
anchors.fill: parent
radius: 0
color: Colors.surface
border.color: Colors.muted
border.width: 3
Column {
id: mainColumn
anchors.fill: parent
anchors.margins: panel.contentPadding
spacing: 10
// Hero: icon · title/status · percentage
RowLayout {
width: parent.width
spacing: 10
Text {
text: root.batteryIcon()
font.family: Appearance.iconFontFamilyRounded
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 30
color: root.low ? Colors.urgent : Colors.foreground
}
Column {
Layout.fillWidth: true
spacing: 2
Text {
width: parent.width
text: "Battery"
elide: Text.ElideRight
font.family: Appearance.fontFamily
font.pixelSize: 18
font.bold: true
color: Colors.foreground
}
Text {
id: heroStatus
width: parent.width
text: root.heroStatusText
elide: Text.ElideRight
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
}
Text {
text: Math.round(root.fraction * 100) + "%"
font.family: Appearance.fontFamily
font.pixelSize: 20
font.bold: true
color: Colors.foreground
}
}
// Charge level bar
Item {
width: parent.width
height: 4
Rectangle {
anchors.fill: parent
radius: 0
color: Colors.muted
opacity: 0.35
}
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
radius: 0
color: root.low ? Colors.urgent : Colors.primary
width: Math.max(height, parent.width * root.fraction)
Behavior on width { NumberAnimation { duration: 320; easing.type: Easing.OutCubic } }
}
}
// Stats — a 2-column, 2-row info grid, same layout
// NetworkIndicator.qml uses for its own live stats: a
// 4-column GridLayout fed a flattened label/value/label/value
// list, so each row holds two label/value pairs side by side
// with the values right-aligned against their own half.
GridLayout {
width: parent.width
columns: 4
columnSpacing: 12
rowSpacing: 5
Repeater {
model: root.statCells
Text {
required property var modelData
Layout.fillWidth: !modelData.isLabel
horizontalAlignment: modelData.isLabel ? Text.AlignLeft : Text.AlignRight
text: modelData.text
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: modelData.isLabel ? Colors.muted : Colors.foreground
}
}
}
// Power profile picker
Column {
width: parent.width
spacing: 6
Text {
text: "POWER PROFILE"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
Row {
id: profileRow
width: parent.width
spacing: 6
readonly property real cellWidth: root.profileEntries.length > 0
? (width - spacing * (root.profileEntries.length - 1)) / root.profileEntries.length
: 0
Repeater {
model: root.profileEntries
ProfileButton {
required property var modelData
width: profileRow.cellWidth
label: modelData.label
icon: modelData.icon
active: PowerProfiles.profile === modelData.value
onActivate: PowerProfiles.profile = modelData.value
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,795 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import Quickshell.Bluetooth
import "../../config"
Item {
id: root
// Independent left/right breathing room around the glyph. Material
// Symbols icons aren't monospaced — each one's font-reported advance
// width (label.implicitWidth) bakes in its own side bearings, so the
// same "+4, centered" math that every other bar icon uses can still
// look tighter/looser icon-to-icon. Tune these two by eye instead of
// the icon's own bounding box to even it out against its neighbours.
property real iconLeftPadding: 0
property real iconRightPadding: 0
implicitWidth: root.iconLeftPadding + label.implicitWidth + root.iconRightPadding
implicitHeight: Appearance.barHeight
// The bar's own PanelWindow — passed in from Bar.qml so this popup can
// anchor to the bar itself rather than to this icon, keeping it a fixed
// distance from the bar and the screen edge regardless of where this
// icon sits in the row.
required property var barWindow
readonly property var adapter: Bluetooth.defaultAdapter
// Scan only while the panel is open — same convention as the WiFi
// scannerEnabled binding in NetworkIndicator.qml.
Binding {
target: root.adapter
property: "discovering"
value: panel.visible && root.adapter.enabled
when: root.adapter !== null
}
// --- Device row helpers ----------------------------------------------
// Rows are plain objects rather than the live BluetoothDevice QObject —
// BlueZ churn (pairing timeouts, a device dropping out of range mid-scan)
// can destroy the object while a delegate still holds it, the same
// crash risk noted for WiFi networks in NetworkIndicator.qml. The actual
// object is re-resolved by address only when an action needs it, via
// deviceForAddress().
function deviceLabel(d) {
return (d.deviceName || d.name || d.address || "").trim()
}
// BlueZ's Icon property is a freedesktop icon-theme name (verified
// against a real paired device on this machine: "audio-headset") — map
// the common categories to a Material Symbols glyph, falling back to a
// generic "devices_other" for anything unrecognized.
function deviceIcon(icon) {
const i = icon || ""
if (i.indexOf("headset") !== -1 || i.indexOf("headphone") !== -1) return "headphones"
if (i.indexOf("mouse") !== -1) return "mouse"
if (i.indexOf("keyboard") !== -1) return "keyboard"
if (i.indexOf("gaming") !== -1 || i.indexOf("joystick") !== -1) return "sports_esports"
if (i.indexOf("tablet") !== -1) return "tablet"
if (i.indexOf("camera") !== -1) return "videocam"
if (i.indexOf("printer") !== -1) return "print"
if (i.indexOf("watch") !== -1) return "watch"
if (i.indexOf("tv") !== -1) return "tv"
if (i.indexOf("computer") !== -1) return "computer"
if (i.indexOf("card") !== -1) return "speaker"
return "devices_other"
}
function deviceRow(d) {
if (!d) return null
const label = root.deviceLabel(d)
if (!label) return null
return {
address: d.address || "",
label: label,
icon: root.deviceIcon(d.icon),
connected: !!d.connected,
paired: !!d.paired,
bonded: !!d.bonded,
trusted: !!d.trusted,
pairing: !!d.pairing,
state: d.state,
batteryAvailable: !!d.batteryAvailable,
battery: d.battery || 0
}
}
readonly property var deviceRows: root.adapter
? root.adapter.devices.values.map(root.deviceRow).filter(function(r) { return r !== null })
: []
readonly property var connectedRows: root.deviceRows.filter(function(r) { return r.connected })
readonly property var knownRows: root.deviceRows.filter(function(r) {
return !r.connected && (r.paired || r.bonded || r.trusted)
})
readonly property var discoveredRows: root.deviceRows.filter(function(r) {
return !r.connected && !r.paired && !r.bonded && !r.trusted
})
function deviceForAddress(address) {
if (!root.adapter) return null
const devs = root.adapter.devices.values
for (let i = 0; i < devs.length; i++) {
if (devs[i].address === address) return devs[i]
}
return null
}
// --- Action feedback ---------------------------------------------------
// BlueZ operations here are fire-and-forget from QML's side — connect()/
// disconnect()/pair()/forget() report failure only via a C++-side log
// line (see quickshell's bluetooth/device.cpp), nothing reaches QML. So
// "did it work" has to be inferred from the device's own properties
// instead of a success/failure callback. Shown as a line right under the
// specific row it's about (DeviceRow.statusText below) rather than a
// panel-wide banner — actionStatusAddress is how a row knows a message
// is meant for it; since it's address-keyed rather than tied to a
// particular DeviceRow instance, the message still finds its row even
// when the device jumps sections (Paired → Connected on a successful
// connect, say), because that's a new DeviceRow for the same address.
// - busyStatus is purely reactive — it just reads pairing/state off
// whichever row is currently mid-operation, however that operation
// was triggered. It's not shown directly (each row already derives
// the same "Connecting…"/"Disconnecting…" off its own dev.state);
// it only exists here to tell resolvePendingAction when to fire.
// - pendingAction+the busyStatus watcher below is how a *specific*
// click gets its own outcome message: it snapshots what was asked
// for, waits for busyStatus to clear, then checks whether the
// expected end state (connected/paired) actually landed.
readonly property string busyStatus: {
for (const r of root.deviceRows) {
if (r.pairing) return "pairing:" + r.address
if (r.state === BluetoothDeviceState.Connecting) return "connecting:" + r.address
if (r.state === BluetoothDeviceState.Disconnecting) return "disconnecting:" + r.address
}
return ""
}
property var pendingAction: null
property string actionStatus: ""
property string actionStatusAddress: ""
// Checks whether pendingAction's expected end state actually landed and
// reports it, then clears pendingAction. Called both when busyStatus
// clears normally and from pendingTimeoutTimer below — an operation
// that resolves inside a single binding re-evaluation (a device already
// in range reconnecting near-instantly, say) can flip busyStatus from ""
// straight back to "" without ever visibly changing, which would
// otherwise leave pendingAction stuck forever and misattribute whatever
// busyStatus transition happens to come along next.
function resolvePendingAction() {
if (!root.pendingAction) return
const p = root.pendingAction
root.pendingAction = null
const d = root.deviceForAddress(p.address)
root.actionStatusAddress = p.address
if (p.kind === "disconnect") {
root.actionStatus = (d && !d.connected) ? "Disconnected" : "Couldn't disconnect"
} else if (p.kind === "pair") {
root.actionStatus = (d && d.paired) ? "Paired" : "Couldn't pair"
} else {
root.actionStatus = (d && d.connected) ? "Connected" : "Couldn't connect"
}
actionStatusTimer.restart()
}
onBusyStatusChanged: if (root.busyStatus === "") root.resolvePendingAction()
Timer {
id: actionStatusTimer
interval: 3500
repeat: false
onTriggered: root.actionStatus = ""
}
// Backstop for the "resolved before busyStatus ever visibly changed"
// case above, and a genuine timeout for an operation that's just stuck
// (device out of range, etc.) — either way, restarted whenever
// pendingAction is (re)set, so it always reflects the latest click.
Timer {
id: pendingTimeoutTimer
interval: 8000
repeat: false
onTriggered: root.resolvePendingAction()
}
function connectDevice(row) {
const d = root.deviceForAddress(row.address)
if (!d) return
// Already-paired devices just reconnect; a fresh discovery result
// has to pair first (BlueZ's own agent handles the SSP/passkey
// handshake — there's no PIN-entry surface exposed by this API).
if (row.paired || row.bonded || row.trusted) {
root.pendingAction = { address: row.address, kind: "connect" }
pendingTimeoutTimer.restart()
d.connect()
} else {
root.pendingAction = { address: row.address, kind: "pair" }
pendingTimeoutTimer.restart()
d.pair()
}
}
function disconnectDevice(row) {
const d = root.deviceForAddress(row.address)
if (!d) return
root.pendingAction = { address: row.address, kind: "disconnect" }
pendingTimeoutTimer.restart()
d.disconnect()
}
function forgetDevice(row) {
const d = root.deviceForAddress(row.address)
if (!d) return
if (d.connected) d.disconnect()
d.forget()
// No feedback line here — forgetting removes this row from every
// list immediately, so there's nothing left to show a message
// under. The row vanishing is the confirmation.
}
function setTrusted(row, trusted) {
const d = root.deviceForAddress(row.address)
if (!d) return
d.trusted = trusted
root.actionStatusAddress = row.address
root.actionStatus = trusted ? "Trusting" : "No longer trusting"
actionStatusTimer.restart()
}
readonly property bool anyConnected: root.connectedRows.length > 0
// Caps how tall the Paired/Available list gets before it scrolls
// instead of continuing to grow the popup — same convention as
// NetworkIndicator.qml's Known/Other Networks list.
readonly property int maxDeviceListHeight: 260
Text {
id: label
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: root.iconLeftPadding
font.family: Appearance.iconFontFamilyRounded
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 16
color: (root.adapter && root.adapter.enabled) ? Colors.foreground : Colors.muted
text: {
if (!root.adapter || !root.adapter.enabled) return "bluetooth_disabled"
return root.anyConnected ? "bluetooth_connected" : "bluetooth"
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
panel.visible = !panel.visible
grab.active = panel.visible
}
}
// --- Reusable pieces ---------------------------------------------------
component ToggleSwitch: Item {
id: toggleRoot
property bool checked: false
signal toggled()
implicitWidth: 34
implicitHeight: 18
Rectangle {
anchors.fill: parent
radius: 0
color: toggleRoot.checked ? Colors.primary : Colors.muted
opacity: toggleRoot.checked ? 1 : 0.35
}
// Hover-only outline, offset outside the switch itself rather than
// drawn on its edge — the switch lags behind a click until the
// backend confirms it (e.g. the adapter's enabled state), so this
// is just a "you can click this" cue, not click feedback.
Rectangle {
anchors.fill: parent
anchors.margins: -4
radius: 0
color: "transparent"
border.color: Colors.foreground
border.width: toggleMouse.containsMouse ? 1 : 0
}
Rectangle {
width: parent.height - 4
height: parent.height - 4
radius: 0
color: Colors.background
anchors.verticalCenter: parent.verticalCenter
x: toggleRoot.checked ? parent.width - width - 2 : 2
Behavior on x { NumberAnimation { duration: Appearance.animationDuration } }
}
MouseArea {
id: toggleMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: toggleRoot.toggled()
}
}
// One device row. `showForget` gates the whole trailing action cluster —
// trust toggle, disconnect, forget — for discovered-but-unpaired devices,
// which have none of those to act on yet (a plain click there pairs
// instead, via activate()). For a paired/connected row, the row body
// itself still connects/disconnects (activate()) same as before; the
// actions below are the explicit, discoverable versions of that plus
// the two this row previously had no way to reach at all.
component DeviceRow: Rectangle {
id: devRow
required property var dev
property bool showForget: true
signal activate()
width: parent ? parent.width : 0
radius: 0
implicitHeight: rowContent.implicitHeight + 12
// Which action icon (if any) the pointer is currently over — drives
// both that icon's own hover color below and rowMouse's click
// dispatch. Derived from rowMouse.mouseX/containsMouse rather than a
// MouseArea on each icon: those icons live inside rowContent, which
// sits underneath rowMouse in stacking order, so a nested MouseArea
// there would just be shadowed by it (same reason
// NetworkIndicator.qml's NetworkRow dispatches by position too).
// Checked right-to-left since that's the order the icons are laid
// out in, so an earlier icon's "from here to the row's right edge"
// zone doesn't swallow the ones after it.
readonly property string hoveredAction: {
if (!rowMouse.containsMouse) return ""
if (devRow.showForget && forgetIcon.visible
&& rowMouse.mouseX >= devRow.mapFromItem(forgetIcon, 0, 0).x - 8) return "forget"
if (devRow.showForget && trustIcon.visible
&& rowMouse.mouseX >= devRow.mapFromItem(trustIcon, 0, 0).x - 8) return "trust"
if (disconnectIcon.visible
&& rowMouse.mouseX >= devRow.mapFromItem(disconnectIcon, 0, 0).x - 8) return "disconnect"
return ""
}
// Hover checked first (same ordering as Volume.qml's DeviceRow):
// hovering always shows a solid Colors.primary fill, even for an
// already-connected row, so the primaryText used below never has to
// sit on top of the dimmer translucent tint instead.
color: rowMouse.containsMouse ? Colors.primary
: (devRow.dev.connected ? Qt.rgba(Colors.primary.r, Colors.primary.g, Colors.primary.b, 0.18) : "transparent")
// Hover-only outline, offset outside the row rather than drawn on
// its own edge — same treatment as NetworkIndicator.qml's NetworkRow.
// The lists this feeds are inset by 3px (see deviceListColumn and
// the Connected section below) so this has room without being
// cropped by deviceListFlick's clip.
Rectangle {
anchors.fill: parent
anchors.margins: -3
radius: 0
color: "transparent"
border.color: Colors.foreground
border.width: rowMouse.containsMouse ? 1 : 0
}
RowLayout {
id: rowContent
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: 6
spacing: 8
Text {
Layout.preferredWidth: 20
Layout.minimumWidth: 20
Layout.maximumWidth: 20
horizontalAlignment: Text.AlignLeft
text: devRow.dev.icon
font.family: Appearance.iconFontFamilyRounded
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 14
color: rowMouse.containsMouse ? Colors.primaryText : Colors.foreground
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
Text {
Layout.fillWidth: true
text: devRow.dev.label
elide: Text.ElideRight
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSize
font.bold: devRow.dev.connected
color: rowMouse.containsMouse ? Colors.primaryText : Colors.foreground
}
Text {
readonly property string statusText: {
if (devRow.dev.pairing) return "Pairing…"
if (devRow.dev.state === BluetoothDeviceState.Connecting) return "Connecting…"
if (devRow.dev.state === BluetoothDeviceState.Disconnecting) return "Disconnecting…"
// The outcome of the last action taken on this
// specific device (connect/disconnect/pair/trust) —
// see the "Action feedback" comment above
// root.busyStatus. Matched by address rather than by
// this being "the same row" that was clicked, since
// a successful connect/disconnect moves the device
// to a different section (a new DeviceRow entirely).
if (root.actionStatusAddress === devRow.dev.address && root.actionStatus !== "")
return root.actionStatus
if (devRow.dev.connected && devRow.dev.batteryAvailable)
return Math.round(devRow.dev.battery * 100) + "%"
if (devRow.dev.connected) return "Connected"
return ""
}
visible: statusText !== ""
text: statusText
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: rowMouse.containsMouse ? Colors.primaryText : Colors.muted
}
}
// Each action below is a fixed-size slot with its own
// hover-halo outline — offset outside the slot rather than drawn
// on its own edge, same treatment as devRow's own outline above
// and every other hoverable box in this file (ToggleSwitch,
// DeviceRow itself, etc.) — instead of just the color swap the
// row's own icons get.
// Disconnect without forgetting — same effect as clicking the
// row body on a Connected row (activate() below calls
// disconnectDevice too), just given its own explicit, always-
// visible icon instead of relying on that being guessed.
Item {
id: disconnectIcon
visible: devRow.showForget && devRow.dev.connected
Layout.preferredWidth: 20
Layout.preferredHeight: 20
Rectangle {
anchors.fill: parent
anchors.margins: -3
radius: 0
color: "transparent"
border.color: Colors.foreground
border.width: devRow.hoveredAction === "disconnect" ? 1 : 0
}
Text {
anchors.centerIn: parent
text: "link_off"
font.family: Appearance.iconFontFamilyRounded
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 14
// Stays primaryText (dark) rather than switching to
// Colors.primary while targeted — devRow's own fill is
// already Colors.primary on hover, so that would've put
// a primary-colored glyph on a primary-colored
// background. The outline Rectangle above is what shows
// which slot is targeted.
color: rowMouse.containsMouse ? Colors.primaryText : Colors.muted
}
}
// Trust toggle — full brightness when trusted, dimmed when not,
// same "state via opacity" convention PowerButton.qml uses for
// its disabled entries.
Item {
id: trustIcon
visible: devRow.showForget
Layout.preferredWidth: 20
Layout.preferredHeight: 20
Rectangle {
anchors.fill: parent
anchors.margins: -3
radius: 0
color: "transparent"
border.color: Colors.foreground
border.width: devRow.hoveredAction === "trust" ? 1 : 0
}
Text {
anchors.centerIn: parent
text: "verified_user"
opacity: devRow.dev.trusted ? 1 : 0.4
font.family: Appearance.iconFontFamilyRounded
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 14
color: rowMouse.containsMouse ? Colors.primaryText : Colors.muted
}
}
Item {
id: forgetIcon
visible: devRow.showForget
Layout.preferredWidth: 20
Layout.preferredHeight: 20
Rectangle {
anchors.fill: parent
anchors.margins: -3
radius: 0
color: "transparent"
border.color: Colors.foreground
border.width: devRow.hoveredAction === "forget" ? 1 : 0
}
Text {
anchors.centerIn: parent
text: "close"
font.family: Appearance.iconFontFamilyRounded
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 14
color: rowMouse.containsMouse ? Colors.primaryText : Colors.muted
}
}
}
MouseArea {
id: rowMouse
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
height: rowContent.height + 12
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
// Dispatches on devRow.hoveredAction rather than its own
// MouseArea per icon — see that property's comment above.
onClicked: {
switch (devRow.hoveredAction) {
case "forget": root.forgetDevice(devRow.dev); break
case "trust": root.setTrusted(devRow.dev, !devRow.dev.trusted); break
case "disconnect": root.disconnectDevice(devRow.dev); break
default: devRow.activate()
}
}
}
}
// --- Panel -------------------------------------------------------------
PopupWindow {
id: panel
visible: false
color: "transparent"
// Wider than the other panels' usual 244 — each device row now ends
// in a row of three 20px action slots (disconnect/trust/forget)
// instead of a single close icon, and the name column needs room to
// not immediately start eliding against that.
readonly property int contentWidth: 300
readonly property int contentPadding: 16
implicitWidth: contentWidth + contentPadding * 2
implicitHeight: mainColumn.implicitHeight + contentPadding * 2
// Connected/paired/discovered devices come and go while the panel
// is open — animate the resize instead of snapping, same as the
// network panel's WiFi list.
Behavior on implicitHeight {
NumberAnimation { duration: Appearance.animationDuration; easing.type: Easing.OutCubic }
}
anchor {
window: root.barWindow
rect.x: 0
rect.y: 0
rect.width: root.barWindow.width
rect.height: root.barWindow.height
edges: Edges.Bottom | Edges.Right
gravity: Edges.Bottom | Edges.Left
margins.bottom: -4
margins.right: 0
}
HyprlandFocusGrab {
id: grab
windows: [panel]
onCleared: panel.visible = false
}
Rectangle {
anchors.fill: parent
radius: 0
color: Colors.surface
border.color: Colors.muted
border.width: 3
Column {
id: mainColumn
anchors.fill: parent
anchors.margins: panel.contentPadding
spacing: 10
// Header
RowLayout {
width: parent.width
spacing: 8
Text {
text: label.text
font.family: Appearance.iconFontFamilyRounded
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 21
color: Colors.foreground
}
Text {
Layout.fillWidth: true
text: "Bluetooth"
font.family: Appearance.fontFamily
font.pixelSize: 18
font.bold: true
color: Colors.foreground
}
// Spins continuously while root.adapter.discovering is
// true — a persistent "still searching" cue independent
// of the list itself, unlike the "Scanning for devices…"
// text below (which only shows while the device list is
// still empty).
Text {
id: scanSpinner
visible: root.adapter !== null && root.adapter.discovering
text: "progress_activity"
font.family: Appearance.iconFontFamilyRounded
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 16
color: Colors.muted
RotationAnimation on rotation {
running: scanSpinner.visible
loops: Animation.Infinite
from: 0
to: 360
duration: 900
}
}
ToggleSwitch {
visible: root.adapter !== null
checked: root.adapter !== null && root.adapter.enabled
onToggled: root.adapter.enabled = !root.adapter.enabled
}
}
Text {
width: parent.width
visible: root.adapter === null
text: "No Bluetooth adapter"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
Text {
width: parent.width
visible: root.adapter !== null && !root.adapter.enabled
text: "Turn Bluetooth on to see devices"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
// Connected — always shown in full above the scrollable
// list, mirroring the network panel's layout.
Column {
width: parent.width
spacing: 4
visible: root.connectedRows.length > 0
Text {
text: "Connected"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
Column {
// Inset 3px on each side to match deviceListColumn
// below, so DeviceRow's hover halo has room here too.
x: 3
width: parent.width - 6
spacing: 2
Repeater {
model: root.connectedRows
DeviceRow {
required property var modelData
dev: modelData
showForget: true
onActivate: root.disconnectDevice(dev)
}
}
}
}
// Paired — always shown in full, same as Connected above and
// for the same reason: these are one click away from
// reconnecting (root.connectDevice reconnects rather than
// re-pairing whenever a row is already paired/bonded/trusted),
// so they shouldn't be buried in the capped-height scroll area
// below or hidden behind an active scan turning up other
// nearby devices.
Column {
width: parent.width
spacing: 4
visible: root.knownRows.length > 0
Text {
text: "Paired Devices"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
Column {
// Inset 3px on each side to match deviceListColumn
// below, so DeviceRow's hover halo has room here too.
x: 3
width: parent.width - 6
spacing: 2
Repeater {
model: root.knownRows
DeviceRow {
required property var modelData
dev: modelData
showForget: true
onActivate: root.connectDevice(dev)
}
}
}
}
Text {
width: parent.width
visible: root.adapter !== null && root.adapter.enabled && root.deviceRows.length === 0
text: (root.adapter && root.adapter.discovering) ? "Scanning for devices…" : "No devices found"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
// Available — freshly discovered, unpaired devices only.
// Capped height, scrolls once there are more than fit rather
// than growing the popup indefinitely.
Flickable {
id: deviceListFlick
width: parent.width
height: Math.min(deviceListColumn.implicitHeight, root.maxDeviceListHeight)
contentWidth: width
contentHeight: deviceListColumn.implicitHeight
clip: true
boundsBehavior: Flickable.StopAtBounds
visible: deviceListColumn.implicitHeight > 0
Column {
id: deviceListColumn
// Inset 3px on every side so DeviceRow's hover halo
// has room without being cropped by deviceListFlick's
// clip. Left/right needs the manual x/width shift
// (children bind width: parent.width, which ignores
// Column's own padding); top/bottom just needs
// padding, since that's along Column's own layout
// axis and Flickable's contentHeight tracks it.
x: 3
width: deviceListFlick.width - 6
topPadding: 3
bottomPadding: 3
spacing: 6
Column {
width: parent.width
spacing: 4
visible: root.adapter !== null && root.adapter.discovering && root.discoveredRows.length > 0
Text {
text: "Available Devices"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
Column {
width: parent.width
spacing: 2
Repeater {
model: root.discoveredRows
DeviceRow {
required property var modelData
dev: modelData
showForget: false
onActivate: root.connectDevice(dev)
}
}
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,18 @@
import QtQuick
import "../../config"
Text {
id: root
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSize
font.bold: true
color: Colors.foreground
text: Qt.formatDateTime(new Date(), "ddd d MMM hh:mm")
Timer {
interval: 1000
running: true
repeat: true
onTriggered: root.text = Qt.formatDateTime(new Date(), "ddd d MMM hh:mm")
}
}

Some files were not shown because too many files have changed in this diff Show More