Compare commits

..

28 Commits

Author SHA1 Message Date
Uyanide 311a89c0e6 update 2026-05-05 09:22:05 +02:00
Uyanide 874311fcb0 update 2026-05-04 23:02:34 +02:00
Uyanide e648c1f1a6 stop building the wheels! 2026-05-04 23:02:18 +02:00
Uyanide f789df0f02 update 2026-05-03 12:37:05 +02:00
Uyanide 417d48f0f5 update 2026-05-03 00:11:07 +02:00
Uyanide a60f923e59 update 2026-04-29 00:16:38 +02:00
Uyanide fff6a726d8 yazi: update keybinds 2026-04-27 18:47:20 +02:00
Uyanide ac91f71a92 niri: disable xray for floating windows 2026-04-25 22:50:47 +02:00
Uyanide 5fefee5e2c qs: add caps event handler for niri ipc 2026-04-25 22:38:21 +02:00
Uyanide 6f90fe5d28 gtk settings 2026-04-25 21:58:04 +02:00
Uyanide b043039705 ya pkg upgrade 2026-04-25 21:58:04 +02:00
Uyanide 9453784fb8 regard ghostty shaders as submodule 2026-04-25 21:52:52 +02:00
Uyanide 410021ae9e update 2026-04-25 21:52:52 +02:00
Uyanide 6a70cb85a3 update 2026-04-25 21:52:52 +02:00
Uyanide 24b90c6721 update 2026-04-25 21:52:52 +02:00
Uyanide 26e7b292d7 add ghostty shaders 2026-04-25 21:52:52 +02:00
Uyanide 6c1bc55687 Refactor README structure and content
Reorganize sections and update details for clarity.
2026-04-25 21:52:51 +02:00
Uyanide de839f7faf update 2026-04-25 20:41:43 +02:00
Uyanide 12d3fdcaa5 update 2026-04-25 20:41:31 +02:00
Uyanide 88c522e9d1 update 2026-04-25 12:19:51 +02:00
Uyanide e3c273516e update
Co-authored-by: Copilot <copilot@github.com>
2026-04-24 21:41:21 +02:00
Uyanide c09865abbc qt: fix media notify 2026-04-21 22:26:19 +02:00
Uyanide 8e2029a768 fix: quickshell-kill 2026-04-21 13:43:54 +02:00
Uyanide 1d9a7cfecf update 2026-04-21 10:48:42 +02:00
Uyanide 73c5a74d0f isCapslockOn ? "CAPS LOCK ON" : "caps lock off" 2026-04-20 19:02:20 +02:00
Uyanide 294788a314 led_monitor -> led-monitor 2026-04-20 18:59:18 +02:00
Uyanide 454262a803 qs: add capslock monitor 2026-04-20 18:55:59 +02:00
Uyanide 266ea92f4a qs: add TempNotification 2026-04-20 17:49:13 +02:00
71 changed files with 2037 additions and 1218 deletions
+3
View File
@@ -4,3 +4,6 @@
[submodule "assets/yazi-catppuccin"] [submodule "assets/yazi-catppuccin"]
path = assets/yazi-catppuccin path = assets/yazi-catppuccin
url = https://github.com/catppuccin/yazi.git url = https://github.com/catppuccin/yazi.git
[submodule "config/ghostty/.config/ghostty/shaders"]
path = config/ghostty/.config/ghostty/shaders
url = https://github.com/0xhckr/ghostty-shaders.git
+42 -22
View File
@@ -3,16 +3,6 @@
## How it looks like... ## How it looks like...
<details> <details>
<summary>Hyprland & Waybar & Eww</summary>
<figure>
<img src="https://github.com/Uyanide/backgrounds/blob/master/screenshots/desktop.jpg?raw=true"/>
</figure>
</details>
<details>
<summary>Niri & Quickshell</summary> <summary>Niri & Quickshell</summary>
https://github.com/user-attachments/assets/2550607a-48ea-4662-98ba-d26722b26b1b https://github.com/user-attachments/assets/2550607a-48ea-4662-98ba-d26722b26b1b
@@ -47,10 +37,10 @@ https://github.com/user-attachments/assets/2550607a-48ea-4662-98ba-d26722b26b1b
- Bar: ~~Waybar~~ | **Quickshell** - Bar: ~~Waybar~~ | **Quickshell**
- Shell: (bash & fish) | **Zsh** - Shell: (bash & fish) | **Zsh**
- Prompt: Oh My Posh | **Starship** - Prompt: Oh My Posh | **Starship**
- Terminal: **Kitty** & (WezTerm | **Ghostty**) - Terminal: **Kitty** | WezTerm | Ghostty
- Power Menu: **Wlogout** & Quickshell - Power Menu: **Wlogout** & Quickshell
- Colorscheme: **Catppuccin Mocha** - Colorscheme: **Catppuccin Mocha**
- App Launcher: **Rofi** | ~~Fuzzel~~ - App Launcher: ~~Rofi~~ | ~~Fuzzel~~ | **vicinae**
- Desktop Widgets: ~~Eww~~ | **Quickshell** - Desktop Widgets: ~~Eww~~ | **Quickshell**
- Wallpaper Daemon: ~~Awww~~ | **Quickshell** - Wallpaper Daemon: ~~Awww~~ | **Quickshell**
- Notification Daemon: ~~Mako~~ | **Quickshell** - Notification Daemon: ~~Mako~~ | **Quickshell**
@@ -66,22 +56,12 @@ Not based on, but heavily depends on many modules from (an old version of) [noct
This setup is currently only adapted for Niri. This setup is currently only adapted for Niri.
## Eww
- `main`, main dashboard, modified from [syndrizzle/hotfiles](https://github.com/syndrizzle/hotfiles/tree/bspwm) but without notification center.
- `lyrics`, scrolling lyrics player, depends on [a small utility](https://github.com/Uyanide/Spotify_Lyrics) from myself <small>(which also happens to be my frist Golang program :D)</small>.
- `lyrics-single`, similar to `lyrics`, but only with a single line and can be easily embeded into the status bar.
## Wallpaper & Colortheme ## Wallpaper & Colortheme
- [WallReel](https://github.com/Uyanide/WallReel): an Image Carousel implemented with QtQuick to browse and set wallpapers from. - [WallReel](https://github.com/Uyanide/WallReel): an Image Carousel implemented with QtQuick to browse and set wallpapers from.
- [change-colortheme](./config/scripts/.local/scripts/change-colortheme): script that extract colors from the current wallpaper and generate a catppuccin color scheme accordingly. - [change-colortheme](./config/scripts/.local/scripts/change-colortheme): script that extract colors from the current wallpaper and generate a catppuccin color scheme accordingly.
- [backgrounds](https://github.com/Uyanide/backgrounds) collection for personal use (mostly waifus). - [backgrounds](https://github.com/Uyanide/backgrounds) collection for personal use (mostly waifus).
## Rofi
Based on [codeopshq/dotfiles](https://github.com/codeopshq/dotfiles), also serves as the clipboard history browser and emoji picker.
## Grub theme ## Grub theme
Based on [vinceliuice/Elegant-grub2-themes](https://github.com/vinceliuice/Elegant-grub2-themes) with an [illustration from 紺屋](https://www.pixiv.net/artworks/119683453). Based on [vinceliuice/Elegant-grub2-themes](https://github.com/vinceliuice/Elegant-grub2-themes) with an [illustration from 紺屋](https://www.pixiv.net/artworks/119683453).
@@ -89,3 +69,43 @@ Based on [vinceliuice/Elegant-grub2-themes](https://github.com/vinceliuice/Elega
## Fonts ## Fonts
See [fontconfig.md](https://github.com/Uyanide/dotfiles/blob/main/memo/fontconfig.md). See [fontconfig.md](https://github.com/Uyanide/dotfiles/blob/main/memo/fontconfig.md).
---
<details>
<summary>Previous setup</summary>
## How it looks like...
<figure>
<img src="https://github.com/Uyanide/backgrounds/blob/master/screenshots/desktop.jpg?raw=true"/>
</figure>
## Hyprland & friends
Based on an old version of [end-4/dots-hyprland](https://github.com/end-4/dots-hyprland) but without ags, quickshell, eww and tons of other stuff.
## Eww
- `main`, main dashboard, modified from [syndrizzle/hotfiles](https://github.com/syndrizzle/hotfiles/tree/bspwm) but without notification center.
- `lyrics`, scrolling lyrics player, depends on [a small utility](https://github.com/Uyanide/Spotify_Lyrics) from myself <small>(which also happens to be my frist Golang program :D)</small>.
- `lyrics-single`, similar to `lyrics`, but only with a single line and can be easily embeded into the status bar.
## Swww
The wallpaper will be automatically blurred when there is a window in focus, which is implemented in the [wallpaper-daemon](https://github.com/Uyanide/dotfiles/blob/main/.scripts/wallpaper-daemon) script.
This feature is only enabled in Niri. Swww also manages wallpapers of the Hyprland setup, yet only in the regular way.
## Wallpaper & Colortheme
The most suitable primary color (or so-called flavor) will be chosen from the [Catppuccin Mocha](https://catppuccin.com/palette/) palette and applied to various apps automatically after changing wallpaper. And also:
- [wallpaper-chooser](https://github.com/Uyanide/Wallpaper_Chooser) to select wallpaper, which implements an Image Carousel with Qt Widgets.
- [backgrounds collection](https://github.com/Uyanide/backgrounds) for personal use.
## Rofi
Based on [codeopshq/dotfiles](https://github.com/codeopshq/dotfiles), also serves as the clipboard history browser and emoji picker.
</details>
-23
View File
@@ -66,12 +66,6 @@ PKGS = {
"niri": NIRI_PKGS, "niri": NIRI_PKGS,
} }
SESSION_NAME = {
"hyprland": "Hyprland",
"niri": "niri",
"default": "default",
}
PKGS_PATH = Path(__file__).resolve().parent.resolve() / "config" PKGS_PATH = Path(__file__).resolve().parent.resolve() / "config"
DEST_PATH = Path.home().expanduser() DEST_PATH = Path.home().expanduser()
@@ -107,12 +101,6 @@ def unstow(pkg: str):
) )
def switch(session: str):
subprocess.run(
[str(Path("~/.local/scripts/config-switch").expanduser()), session], check=True
)
def main(): def main():
if not check_deps(): if not check_deps():
exit(1) exit(1)
@@ -147,17 +135,6 @@ def main():
if is_unstow: if is_unstow:
return # No need to switch session if we're just unstowing return # No need to switch session if we're just unstowing
if args.package in SESSION_NAME:
session = SESSION_NAME[args.package]
else:
session = SESSION_NAME["default"]
try:
switch(session)
_log("INFO", f"Switched to session profile '{session}'.")
except subprocess.CalledProcessError as e:
_log("ERROR", f"Failed to switch session profile '{session}': {e}")
if __name__ == "__main__": if __name__ == "__main__":
main() main()
@@ -1 +0,0 @@
shaders
@@ -1,27 +0,0 @@
theme = Catppuccin Mocha
background-opacity = 0.75
background-blur = true
window-padding-x = 10
window-padding-y = 10
keybind = ctrl+shift+r=reload_config
keybind = ctrl+shift+h=write_screen_file:copy
keybind = ctrl+shift+j=text:ghostty-capture\n
keybind = ctrl+enter=unbind
command = exec /bin/zsh
confirm-close-surface = false
font-family = monospace
font-size = 12
cursor-style = bar
adjust-cursor-thickness = 3
custom-shader = cursor-shaders/cursor-smear.glsl
quit-after-last-window-closed = false
@@ -1 +0,0 @@
shaders
@@ -1,120 +0,0 @@
// https://github.com/KroneCorylus/ghostty-shader-playground/blob/main/shaders/cursor_smear.glsl
float getSdfRectangle(in vec2 p, in vec2 xy, in vec2 b)
{
vec2 d = abs(p - xy) - b;
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
}
// Based on Inigo Quilez's 2D distance functions article: https://iquilezles.org/articles/distfunctions2d/
// Potencially optimized by eliminating conditionals and loops to enhance performance and reduce branching
float seg(in vec2 p, in vec2 a, in vec2 b, inout float s, float d) {
vec2 e = b - a;
vec2 w = p - a;
vec2 proj = a + e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);
float segd = dot(p - proj, p - proj);
d = min(d, segd);
float c0 = step(0.0, p.y - a.y);
float c1 = 1.0 - step(0.0, p.y - b.y);
float c2 = 1.0 - step(0.0, e.x * w.y - e.y * w.x);
float allCond = c0 * c1 * c2;
float noneCond = (1.0 - c0) * (1.0 - c1) * (1.0 - c2);
float flip = mix(1.0, -1.0, step(0.5, allCond + noneCond));
s *= flip;
return d;
}
float getSdfParallelogram(in vec2 p, in vec2 v0, in vec2 v1, in vec2 v2, in vec2 v3) {
float s = 1.0;
float d = dot(p - v0, p - v0);
d = seg(p, v0, v3, s, d);
d = seg(p, v1, v0, s, d);
d = seg(p, v2, v1, s, d);
d = seg(p, v3, v2, s, d);
return s * sqrt(d);
}
vec2 normalize(vec2 value, float isPosition) {
return (value * 2.0 - (iResolution.xy * isPosition)) / iResolution.y;
}
float antialising(float distance) {
return 1. - smoothstep(0., normalize(vec2(2., 2.), 0.).x, distance);
}
float determineStartVertexFactor(vec2 a, vec2 b) {
// Conditions using step
float condition1 = step(b.x, a.x) * step(a.y, b.y); // a.x < b.x && a.y > b.y
float condition2 = step(a.x, b.x) * step(b.y, a.y); // a.x > b.x && a.y < b.y
// If neither condition is met, return 1 (else case)
return 1.0 - max(condition1, condition2);
}
vec2 getRectangleCenter(vec4 rectangle) {
return vec2(rectangle.x + (rectangle.z / 2.), rectangle.y - (rectangle.w / 2.));
}
float ease(float x) {
return pow(1.0 - x, 3.0);
}
vec4 saturate(vec4 color, float factor) {
float gray = dot(color, vec4(0.299, 0.587, 0.114, 0.)); // luminance
return mix(vec4(gray), color, factor);
}
vec4 TRAIL_COLOR = iCurrentCursorColor;
const float OPACITY = 0.6;
const float DURATION = 0.3; //IN SECONDS
void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
#if !defined(WEB)
fragColor = texture(iChannel0, fragCoord.xy / iResolution.xy);
#endif
// Normalization for fragCoord to a space of -1 to 1;
vec2 vu = normalize(fragCoord, 1.);
vec2 offsetFactor = vec2(-.5, 0.5);
// Normalization for cursor position and size;
// cursor xy has the postion in a space of -1 to 1;
// zw has the width and height
vec4 currentCursor = vec4(normalize(iCurrentCursor.xy, 1.), normalize(iCurrentCursor.zw, 0.));
vec4 previousCursor = vec4(normalize(iPreviousCursor.xy, 1.), normalize(iPreviousCursor.zw, 0.));
// When drawing a parellelogram between cursors for the trail i need to determine where to start at the top-left or top-right vertex of the cursor
float vertexFactor = determineStartVertexFactor(currentCursor.xy, previousCursor.xy);
float invertedVertexFactor = 1.0 - vertexFactor;
// Set every vertex of my parellogram
vec2 v0 = vec2(currentCursor.x + currentCursor.z * vertexFactor, currentCursor.y - currentCursor.w);
vec2 v1 = vec2(currentCursor.x + currentCursor.z * invertedVertexFactor, currentCursor.y);
vec2 v2 = vec2(previousCursor.x + currentCursor.z * invertedVertexFactor, previousCursor.y);
vec2 v3 = vec2(previousCursor.x + currentCursor.z * vertexFactor, previousCursor.y - previousCursor.w);
float sdfCurrentCursor = getSdfRectangle(vu, currentCursor.xy - (currentCursor.zw * offsetFactor), currentCursor.zw * 0.5);
float sdfTrail = getSdfParallelogram(vu, v0, v1, v2, v3);
float progress = clamp((iTime - iTimeCursorChange) / DURATION, 0.0, 1.0);
float easedProgress = ease(progress);
// Distance between cursors determine the total length of the parallelogram;
vec2 centerCC = getRectangleCenter(currentCursor);
vec2 centerCP = getRectangleCenter(previousCursor);
float lineLength = distance(centerCC, centerCP);
vec4 newColor = vec4(fragColor);
vec4 trail = TRAIL_COLOR;
trail = saturate(trail, 2.5);
// Draw trail
newColor = mix(newColor, trail, antialising(sdfTrail));
// Draw current cursor
newColor = mix(newColor, trail, antialising(sdfCurrentCursor));
newColor = mix(newColor, fragColor, step(sdfCurrentCursor, 0.));
// newColor = mix(fragColor, newColor, OPACITY);
fragColor = mix(fragColor, newColor, step(sdfCurrentCursor, easedProgress * lineLength));
}
@@ -1,6 +1,6 @@
theme = Catppuccin Mocha theme = Catppuccin Mocha
background-opacity = 0.95 background-opacity = 0.9
background-blur = true background-blur = true
window-padding-x = 10 window-padding-x = 10
@@ -1,48 +0,0 @@
allow_remote_control yes
listen_on unix:/tmp/kitty
shell_integration enabled
# kitty-scrollback.nvim Kitten alias
action_alias kitty_scrollback_nvim kitten $HOME/.local/share/nvim/lazy/kitty-scrollback.nvim/python/kitty_scrollback_nvim.py
# Browse scrollback buffer in nvim
map kitty_mod+h kitty_scrollback_nvim
# Browse output of the last shell command in nvim
map kitty_mod+g kitty_scrollback_nvim --config ksb_builtin_last_cmd_output
# Show clicked command output in nvim
mouse_map ctrl+shift+right press ungrabbed combine : mouse_select_command_output : kitty_scrollback_nvim --config ksb_builtin_last_visited_cmd_output
# disable the stupid notification
confirm_os_window_close 0
# set shell to zsh
shell /bin/zsh
# hide_window_decorations yes
window_padding_width 10
background_opacity 0.75
background_blur 16
font_family monospace
font_size 12
tab_bar_style powerline
tab_powerline_style round
tab_title_template {title}{' :{}:'.format(num_windows) if num_windows > 1 else ''}
map ctrl+up previous_window
map ctrl+down next_window
cursor_trail 1
cursor_shape beam
include Catppuccin-Mocha.conf
map ctrl+plus change_font_size all +1
map ctrl+kp_add change_font_size all +1
map ctrl+minus change_font_size all -1
map ctrl+kp_subtract change_font_size all -1
map ctrl+0 change_font_size all 0
map ctrl+kp_0 change_font_size all 0
@@ -1,80 +0,0 @@
# vim:ft=kitty
## name: Catppuccin-Mocha
## author: Pocco81 (https://github.com/Pocco81)
## license: MIT
## upstream: https://github.com/catppuccin/kitty/blob/main/mocha.conf
## blurb: Soothing pastel theme for the high-spirited!
# The basic colors
foreground #CDD6F4
background #1E1E2E
selection_foreground #1E1E2E
selection_background #F5E0DC
# Cursor colors
cursor #F5E0DC
cursor_text_color #1E1E2E
# URL underline color when hovering with mouse
url_color #F5E0DC
# Kitty window border colors
active_border_color #B4BEFE
inactive_border_color #6C7086
bell_border_color #F9E2AF
# OS Window titlebar colors
wayland_titlebar_color system
macos_titlebar_color system
# Tab bar colors
active_tab_foreground #11111B
active_tab_background #CBA6F7
inactive_tab_foreground #CDD6F4
inactive_tab_background #181825
tab_bar_background #11111B
# Colors for marks (marked text in the terminal)
mark1_foreground #1E1E2E
mark1_background #B4BEFE
mark2_foreground #1E1E2E
mark2_background #CBA6F7
mark3_foreground #1E1E2E
mark3_background #74C7EC
# The 16 terminal colors
# black
color0 #45475A
color8 #585B70
# red
color1 #F38BA8
color9 #F38BA8
# green
color2 #A6E3A1
color10 #A6E3A1
# yellow
color3 #F9E2AF
color11 #F9E2AF
# blue
color4 #89B4FA
color12 #89B4FA
# magenta
color5 #F5C2E7
color13 #F5C2E7
# cyan
color6 #94E2D5
color14 #94E2D5
# white
color7 #BAC2DE
color15 #A6ADC8
+7
View File
@@ -0,0 +1,7 @@
include kitty.conf
remember_window_size false
initial_window_width 960
initial_window_height 720
window_padding_width 5
@@ -3,13 +3,13 @@ listen_on unix:/tmp/kitty
shell_integration enabled shell_integration enabled
# kitty-scrollback.nvim Kitten alias # kitty-scrollback.nvim Kitten alias
action_alias kitty_scrollback_nvim kitten $HOME/.local/share/nvim/lazy/kitty-scrollback.nvim/python/kitty_scrollback_nvim.py # action_alias kitty_scrollback_nvim kitten $HOME/.local/share/nvim/lazy/kitty-scrollback.nvim/python/kitty_scrollback_nvim.py
# Browse scrollback buffer in nvim # Browse scrollback buffer in nvim
map kitty_mod+h kitty_scrollback_nvim # map kitty_mod+h kitty_scrollback_nvim
# Browse output of the last shell command in nvim # Browse output of the last shell command in nvim
map kitty_mod+g kitty_scrollback_nvim --config ksb_builtin_last_cmd_output # map kitty_mod+g kitty_scrollback_nvim --config ksb_builtin_last_cmd_output
# Show clicked command output in nvim # Show clicked command output in nvim
mouse_map ctrl+shift+right press ungrabbed combine : mouse_select_command_output : kitty_scrollback_nvim --config ksb_builtin_last_visited_cmd_output # mouse_map ctrl+shift+right press ungrabbed combine : mouse_select_command_output : kitty_scrollback_nvim --config ksb_builtin_last_visited_cmd_output
# disable the stupid notification # disable the stupid notification
confirm_os_window_close 0 confirm_os_window_close 0
@@ -20,7 +20,7 @@ shell /bin/zsh
# hide_window_decorations yes # hide_window_decorations yes
window_padding_width 10 window_padding_width 10
background_opacity 0.95 background_opacity 0.9
background_blur 16 background_blur 16
font_family monospace font_family monospace
+12 -11
View File
@@ -14,9 +14,9 @@ binds {
Mod+E repeat=false { spawn "dolphin" "--new-window"; } Mod+E repeat=false { spawn "dolphin" "--new-window"; }
Mod+Shift+E repeat=false { spawn "nautilus" "--new-window"; } Mod+Shift+E repeat=false { spawn "nautilus" "--new-window"; }
Mod+W repeat=false { spawn-sh "zen || zen-browser"; } Mod+W repeat=false { spawn-sh "zen || zen-browser"; }
Mod+B repeat=false { spawn-sh "pkill -x -n btop || ghostty +new-window -e btop"; } Mod+B repeat=false { spawn-sh "pkill -x -n btop || kitty-floating -e btop"; }
Mod+Shift+T repeat=false { spawn "ghostty" "+new-window"; } Mod+Shift+T repeat=false { spawn "kitty-floating"; }
Mod+Shift+Return repeat=false { spawn "ghostty" "+new-window"; } Mod+Shift+Return repeat=false { spawn "kitty-floating"; }
Mod+T repeat=false { spawn "kitty"; } Mod+T repeat=false { spawn "kitty"; }
Mod+Return repeat=false { spawn "kitty"; } Mod+Return repeat=false { spawn "kitty"; }
Mod+Shift+W repeat=false { spawn "wallreel"; } Mod+Shift+W repeat=false { spawn "wallreel"; }
@@ -45,9 +45,9 @@ binds {
Mod+D repeat=false { spawn "vicinae" "vicinae://launch/system/run?toggle=true"; } Mod+D repeat=false { spawn "vicinae" "vicinae://launch/system/run?toggle=true"; }
// Actions // Actions
Print repeat=false { screenshot-screen; } Print repeat=false { screenshot-screen show-pointer=false; }
Mod+Shift+S repeat=false { screenshot; } Mod+Shift+S repeat=false { screenshot show-pointer=false; }
Mod+Ctrl+Shift+S repeat=false { screenshot-window; } Mod+Ctrl+Shift+S repeat=false { screenshot-window show-pointer=false; }
Mod+Shift+C repeat=false { spawn "hyprpicker" "-a"; } Mod+Shift+C repeat=false { spawn "hyprpicker" "-a"; }
// Media // Media
@@ -65,10 +65,11 @@ binds {
XF86MonBrightnessDown allow-when-locked=true { spawn "qs" "ipc" "call" "brightness" "down"; } XF86MonBrightnessDown allow-when-locked=true { spawn "qs" "ipc" "call" "brightness" "down"; }
// Window management // Window management
Mod+Tab repeat=false { toggle-overview; } Mod+Tab repeat=false { toggle-overview; }
Mod+Q repeat=false { close-window; } Mod+Q repeat=false { close-window; }
Alt+F4 repeat=false { close-window; } // can't imagine this does not come as default Mod+Shift+Q repeat=false { spawn "niri-force-kill-window"; }
Alt+F4 repeat=false { close-window; } // can't imagine this does not come as default
Mod+Left { focus-column-left; } Mod+Left { focus-column-left; }
Mod+Down { focus-window-or-workspace-down; } Mod+Down { focus-window-or-workspace-down; }
@@ -161,8 +162,8 @@ binds {
Mod+Escape allow-inhibiting=false repeat=false { toggle-keyboard-shortcuts-inhibit; } Mod+Escape allow-inhibiting=false repeat=false { toggle-keyboard-shortcuts-inhibit; }
// Session // Session
Mod+Shift+Q allow-inhibiting=false repeat=false { quit; } Mod+K allow-inhibiting=false repeat=false { quit; }
Mod+Shift+P allow-inhibiting=false repeat=false { spawn-sh "hyprlock & niri msg action power-off-monitors"; } Mod+Shift+P allow-inhibiting=false repeat=false { spawn-sh "loginctl lock-session; niri msg action power-off-monitors"; }
Mod+L allow-inhibiting=false repeat=false { spawn "loginctl" "lock-session"; } Mod+L allow-inhibiting=false repeat=false { spawn "loginctl" "lock-session"; }
} }
@@ -1,6 +1,3 @@
// Switch configs
spawn-at-startup "config-switch" "niri"
// Core // Core
spawn-at-startup "nm-applet" spawn-at-startup "nm-applet"
spawn-at-startup "gnome-keyring-daemon" "--start" "--components=secrets" spawn-at-startup "gnome-keyring-daemon" "--start" "--components=secrets"
+9 -1
View File
@@ -20,6 +20,7 @@ window-rule {
window-rule { window-rule {
match app-id="org.wezfurlong.wezterm" match app-id="org.wezfurlong.wezterm"
match app-id="com.mitchellh.ghostty" match app-id="com.mitchellh.ghostty"
match app-id="kitty-floating"
open-floating true open-floating true
default-column-width { proportion 0.5; } default-column-width { proportion 0.5; }
} }
@@ -61,9 +62,16 @@ window-rule {
open-floating true open-floating true
} }
// QQ
window-rule {
match app-id="QQ" title="资料卡"
match app-id="QQ" title="天气"
open-focused false
}
// Block from recording // Block from recording
window-rule { window-rule {
match app-id="thunderbird" match app-id="org.mozilla.Thunderbird"
block-out-from "screen-capture" block-out-from "screen-capture"
} }
+20 -4
View File
@@ -82,10 +82,20 @@ layer-rule {
} }
cursor { layer-rule {
xcursor-theme "Bibata-Modern-Ice" match layer="top"
xcursor-size 24 match layer="overlay"
hide-when-typing
background-effect {
xray false
}
}
window-rule {
match is-floating=true
background-effect {
xray false
}
} }
// I love rounded corners // I love rounded corners
@@ -94,6 +104,12 @@ window-rule {
clip-to-geometry true clip-to-geometry true
} }
cursor {
xcursor-theme "Bibata-Modern-Ice"
xcursor-size 24
hide-when-typing
}
recent-windows { recent-windows {
highlight { highlight {
active-color "#89b4fa" active-color "#89b4fa"
@@ -11,15 +11,23 @@ Item {
property color fillColor: Colors.mRed property color fillColor: Colors.mRed
property color _actualColor: Colors.mRed property color _actualColor: Colors.mRed
property bool _expand: mouseArea.containsMouse property bool _expand: mouseArea.containsMouse
property string displayText: Niri.castOutputs.length > 0 ? Niri.castOutputs.join(", ") : "Casting"
visible: RecordService.isRecording visible: Niri.isCasting
implicitHeight: Math.max(symbolIcon.implicitHeight, textLabel.implicitHeight) implicitHeight: Math.max(symbolIcon.implicitHeight, textLabel.implicitHeight)
implicitWidth: height + expander.implicitWidth implicitWidth: height + expander.implicitWidth
Connections {
target: Niri
onCastOutputsListChanged: {
root.displayText = Niri.castOutputs.length > 0 ? Niri.castOutputs.join(", ") : "Casting";
}
}
SequentialAnimation { SequentialAnimation {
id: blinkAnimation id: blinkAnimation
running: RecordService.isRecording running: root.visible
loops: Animation.Infinite loops: Animation.Infinite
ColorAnimation { ColorAnimation {
@@ -70,7 +78,7 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 5 anchors.leftMargin: 5
text: RecordService.recordingDisplay || "Recording" text: root.displayText
color: root.fillColor color: root.fillColor
} }
@@ -92,12 +100,6 @@ Item {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: (mouse) => {
if (mouse.button === Qt.LeftButton)
RecordService.startOrStop();
}
} }
Behavior on _actualColor { Behavior on _actualColor {
@@ -1,28 +1,102 @@
import QtQuick import QtQuick
import QtQuick.Layouts
import Quickshell.Io import Quickshell.Io
import qs.Components
import qs.Constants import qs.Constants
import qs.Services import qs.Services
Text { Item {
text: TimeService.time + " | " + TimeService.dateString id: root
font.pointSize: Style.fontSizeM
font.family: Fonts.primary
color: Colors.mPrimary
MouseArea { readonly property int switchDistance: Style.barHeight
anchors.fill: parent readonly property int animationDuration: Style.animationNormal
acceptedButtons: Qt.LeftButton
cursorShape: Qt.PointingHandCursor implicitWidth: Math.max(timeLayer.implicitWidth, notiLayer.implicitWidth)
onClicked: { implicitHeight: Math.max(timeLayer.implicitHeight, notiLayer.implicitHeight)
action.running = !action.running; clip: true
UText {
id: timeLayer
readonly property real restY: (root.height - implicitHeight) / 2
anchors.horizontalCenter: parent.horizontalCenter
y: TempNotificationService.active ? restY + root.switchDistance : restY
opacity: TempNotificationService.active ? 0 : 1
text: TimeService.time + " | " + TimeService.dateString
color: Colors.mPrimary
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
cursorShape: Qt.PointingHandCursor
onClicked: {
action.running = !action.running;
}
} }
Process {
id: action
running: false
command: ["vicinae", "toggle"]
}
Behavior on y {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.OutCubic
}
}
Behavior on opacity {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.OutCubic
}
}
} }
Process { RowLayout {
id: action id: notiLayer
readonly property real restY: (root.height - implicitHeight) / 2
anchors.horizontalCenter: parent.horizontalCenter
y: TempNotificationService.active ? restY : restY - root.switchDistance
opacity: TempNotificationService.active ? 1 : 0
spacing: Style.marginXS
UIcon {
visible: TempNotificationService.iconName !== ""
iconName: TempNotificationService.iconName
color: Colors.mPrimary
}
UText {
text: TempNotificationService.message
color: Colors.mPrimary
}
Behavior on y {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.OutCubic
}
}
Behavior on opacity {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.OutCubic
}
}
running: false
command: ["vicinae", "toggle"]
} }
} }
@@ -18,7 +18,7 @@ Singleton {
id: process id: process
running: false running: false
command: ["ghostty", "+new-window", "-e", "btop"] command: ["kitty-floating", "-e", "btop"]
} }
} }
@@ -633,4 +633,42 @@ Singleton {
} }
Pipewire.preferredDefaultAudioSource = newSource; Pipewire.preferredDefaultAudioSource = newSource;
} }
onVolumeChanged: {
if (root.consumeOutputOSDSuppression()) {
return;
}
if (root.muted) {
TempNotificationService.showWithIcon("volume-mute", "Muted");
return;
}
TempNotificationService.showWithIcon(root.getOutputIcon(), Math.round(root.volume * 100) + "%");
}
onInputVolumeChanged: {
if (root.consumeInputOSDSuppression()) {
return;
}
if (root.inputMuted) {
TempNotificationService.showWithIcon("volume-mute", "Muted");
return;
}
TempNotificationService.showWithIcon(root.getInputIcon(), Math.round(root.inputVolume * 100) + "%");
}
onMutedChanged: {
if (root.muted) {
TempNotificationService.showWithIcon("volume-mute", "Muted");
} else {
TempNotificationService.showWithIcon(root.getOutputIcon(), Math.round(root.volume * 100) + "%");
}
}
onInputMutedChanged: {
if (root.inputMuted) {
TempNotificationService.showWithIcon("microphone-mute", "Input Muted");
} else {
TempNotificationService.showWithIcon("microphone", "Input Unmuted");
}
}
} }
@@ -1,6 +1,7 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import qs.Services
import qs.Utils import qs.Utils
pragma Singleton pragma Singleton
@@ -161,6 +162,9 @@ Singleton {
Component.onDestruction: { Component.onDestruction: {
stopInhibition(); stopInhibition();
} }
onIsInhibitedChanged: {
TempNotificationService.showWithIcon("mug-filled", isInhibited ? "Inhibition active: " + reason : "Inhibition stopped");
}
// Process for maintaining the inhibition (subprocess fallback only) // Process for maintaining the inhibition (subprocess fallback only)
Process { Process {
@@ -0,0 +1,39 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Services
import qs.Utils
pragma Singleton
Singleton {
id: root
property bool isCapslockOn: false
onIsCapslockOnChanged: {
if (root.isCapslockOn) {
TempNotificationService.showWithIcon("letter-case-toggle", "CAPS LOCK ON");
} else {
TempNotificationService.showWithIcon("letter-case", "caps lock off");
}
}
Process {
id: capslockMonitorProcess
running: true
command: ["led-monitor", "-l", "capslock"]
stdout: SplitParser {
splitMarker: "\n"
onRead: (line) => {
if (line.trim() === "1")
root.isCapslockOn = true;
else if (line.trim() === "0")
root.isCapslockOn = false;
}
}
}
}
@@ -153,4 +153,16 @@ Item {
target: "notes" target: "notes"
} }
IpcHandler {
function showMsg(message: string, duration: int) {
TempNotificationService.show(message, duration);
}
function showWithIcon(iconName: string, message: string, duration: int) {
TempNotificationService.showWithIcon(iconName, message, duration);
}
target: "tempNotification"
}
} }
@@ -11,7 +11,7 @@ Singleton {
property bool dirsLoaded: false property bool dirsLoaded: false
property bool initialized: dirsLoaded && ImageCacheService.initialized && ShellState.isLoaded && SettingsService.isLoaded property bool initialized: dirsLoaded && ImageCacheService.initialized && ShellState.isLoaded && SettingsService.isLoaded
Component.onCompleted: { function mkdirs() {
let mkdirs = ""; let mkdirs = "";
for (const dir of [Paths.cacheDir, Paths.configDir, Paths.recordingDir, Paths.notesDir]) { for (const dir of [Paths.cacheDir, Paths.configDir, Paths.recordingDir, Paths.notesDir]) {
mkdirs += `mkdir -p "${dir}" && `; mkdirs += `mkdir -p "${dir}" && `;
@@ -22,6 +22,11 @@ Singleton {
process.running = true; process.running = true;
} }
Component.onCompleted: {
ImageCacheService.init();
root.mkdirs();
}
Process { Process {
id: process id: process
@@ -193,7 +193,10 @@ Singleton {
} }
} }
onInternalPositionChanged: updateIndex() onInternalPositionChanged: updateIndex()
onLyricsOffsetChanged: updateIndex() onLyricsOffsetChanged: () => {
TempNotificationService.showWithIcon("hourglass-empty", root.lyricsOffset + "ms");
updateIndex()
}
Connections { Connections {
function onCurrentPlayerChanged() { function onCurrentPlayerChanged() {
@@ -271,6 +271,10 @@ Singleton {
} }
} }
function notifyTrackChange() {
TempNotificationService.showWithIcon("music", (trackArtist ? trackArtist + " - " : "") + trackTitle, 3000);
}
Component.onCompleted: { Component.onCompleted: {
updateCurrentPlayer(); updateCurrentPlayer();
} }
@@ -280,6 +284,8 @@ Singleton {
currentPosition = 0; currentPosition = 0;
} }
onTrackTitleChanged: Qt.callLater(notifyTrackChange)
onTrackArtistChanged: Qt.callLater(notifyTrackChange)
// Update progress bar every second while playing // Update progress bar every second while playing
Timer { Timer {
@@ -35,11 +35,16 @@ Singleton {
}) })
property var workspaceCache: ({ property var workspaceCache: ({
}) })
property var castCache: ({
})
property var castOutputs: []
property bool isCasting: false
signal workspaceChanged() signal workspaceChanged()
signal activeWindowChanged() signal activeWindowChanged()
signal windowListChanged() signal windowListChanged()
signal outputsChanged() signal outputsChanged()
signal castOutputsListChanged()
function initialize() { function initialize() {
niriEventStream.connected = true; niriEventStream.connected = true;
@@ -434,6 +439,79 @@ Singleton {
} }
} }
function _syncCasts() {
isCasting = Object.keys(castCache).length > 0;
castOutputs = [];
for (const castId in castCache) {
const cast = castCache[castId];
if (cast.output) {
if (!castOutputs.includes(cast.output))
castOutputs.push(cast.output);
}
}
castOutputsListChanged();
}
function _handleCastsChanged(eventData) {
try {
const casts = eventData.casts || [];
castCache = {
};
castOutputs = [];
for (const cast of casts) {
const castData = {
"id": cast.stream_id,
"stream_id": cast.stream_id,
"session_id": cast.session_id,
"kind": cast.kind,
"output": cast.target?.Output?.name,
"pid": cast.pid
};
castCache[castData.id] = castData;
}
_syncCasts();
} catch (e) {
Logger.e("NiriService", "Error handling CastsChanged:", e);
}
}
function _handleCastStopped(eventData) {
try {
const castId = eventData.stream_id;
delete castCache[castId];
_syncCasts();
} catch (e) {
Logger.e("NiriService", "Error handling CastStopped:", e);
}
}
function _handleCastStartedOrChanged(eventData) {
try {
const cast = eventData.cast;
if (!cast)
return ;
if (cast.is_active === true) {
// If the cast is active, we can treat it as a new or updated cast
const castData = {
"id": cast.stream_id,
"stream_id": cast.stream_id,
"session_id": cast.session_id,
"kind": cast.kind,
"output": cast.target?.Output?.name,
"pid": cast.pid
};
castCache[castData.id] = castData;
} else {
// If the cast is not active, we should remove it from the cache
const castId = cast.stream_id;
delete castCache[castId];
}
_syncCasts();
} catch (e) {
Logger.e("NiriService", "Error handling CastStartedOrChanged:", e);
}
}
function switchToWorkspace(workspace) { function switchToWorkspace(workspace) {
try { try {
Quickshell.execDetached(["niri", "msg", "action", "focus-workspace", workspace.idx.toString()]); Quickshell.execDetached(["niri", "msg", "action", "focus-workspace", workspace.idx.toString()]);
@@ -578,6 +656,12 @@ Singleton {
_queryDisplayScales(); _queryDisplayScales();
else if (event.ScreenshotCaptured) else if (event.ScreenshotCaptured)
_handleScreenshotCaptured(event.ScreenshotCaptured); _handleScreenshotCaptured(event.ScreenshotCaptured);
else if (event.CastsChanged)
_handleCastsChanged(event.CastsChanged);
else if (event.CastStopped)
_handleCastStopped(event.CastStopped);
else if (event.CastStartedOrChanged)
_handleCastStartedOrChanged(event.CastStartedOrChanged);
} catch (e) { } catch (e) {
Logger.e("NiriService", "Error parsing event stream:", e, data); Logger.e("NiriService", "Error parsing event stream:", e, data);
} }
@@ -39,7 +39,7 @@ Singleton {
function openNote(path) { function openNote(path) {
recentNotePath = path; recentNotePath = path;
Quickshell.execDetached(["ghostty", "+new-window", "-e", "helix", path]); Quickshell.execDetached(["kitty-floating", "-e", "helix", path]);
} }
function openRecent() { function openRecent() {
@@ -46,8 +46,10 @@ Singleton {
if (!sunsetProcess.running) { if (!sunsetProcess.running) {
temperature = 0; temperature = 0;
Logger.i("Sunset", "Stopped sunset process"); Logger.i("Sunset", "Stopped sunset process");
TempNotificationService.showWithIcon("sunset-2-filled", "Sunset disabled");
} else { } else {
Logger.i("Sunset", "Started sunset process"); Logger.i("Sunset", "Started sunset process");
TempNotificationService.showWithIcon("sunset-2-filled", "Sunset enabled");
} }
} }
@@ -0,0 +1,38 @@
pragma Singleton
import QtQuick
import Quickshell
Singleton {
id: root
readonly property int defaultDuration: 2000
property bool active: false
property string message: ""
property string iconName: ""
function show(message: string, duration: int) {
root._showInternal("", message, duration);
}
function showWithIcon(iconName: string, message: string, duration: int) {
root._showInternal(iconName, message, duration);
}
function _showInternal(iconName: string, message: string, duration: int) {
root.iconName = iconName;
root.message = message;
root.active = true;
resetTimer.interval = duration > 0 ? duration : root.defaultDuration;
resetTimer.restart();
}
Timer {
id: resetTimer
repeat: false
onTriggered: root.active = false
}
}
@@ -10,10 +10,6 @@ import qs.Services
ShellRoot { ShellRoot {
id: root id: root
Component.onCompleted: {
ImageCacheService.init();
}
Loader { Loader {
id: loader id: loader
@@ -24,6 +20,7 @@ ShellRoot {
SunsetService; SunsetService;
NotesService; NotesService;
WallpaperCycle; WallpaperCycle;
CapslockService;
} }
IPCService { IPCService {
@@ -40,13 +40,9 @@ CONFIG_DIR = Path("~/.config").expanduser()
# An application may have multiple scripts (e.g. due to config-switch) # An application may have multiple scripts (e.g. due to config-switch)
SCRIPTS = { SCRIPTS = {
"eww": [CONFIG_DIR / "eww" / "apply-color"],
"fastfetch": [CONFIG_DIR / "fastfetch" / "apply-color"], "fastfetch": [CONFIG_DIR / "fastfetch" / "apply-color"],
"fuzzel": [CONFIG_DIR / "fuzzel" / "apply-color"],
"hypr": [CONFIG_DIR / "hypr" / "apply-color"],
"kvantum": [CONFIG_DIR / "Kvantum" / "apply-color"], "kvantum": [CONFIG_DIR / "Kvantum" / "apply-color"],
"nwg-look": [CONFIG_DIR / "nwg-look" / "apply-color"], "nwg-look": [CONFIG_DIR / "nwg-look" / "apply-color"],
"mako": [CONFIG_DIR / "mako" / "apply-color"],
"niri": [CONFIG_DIR / "niri" / "apply-color"], "niri": [CONFIG_DIR / "niri" / "apply-color"],
"oh-my-posh": [ "oh-my-posh": [
CONFIG_DIR / "fish" / "apply-color-omp" CONFIG_DIR / "fish" / "apply-color-omp"
@@ -55,12 +51,7 @@ SCRIPTS = {
CONFIG_DIR / "fish" / "apply-color-starship" CONFIG_DIR / "fish" / "apply-color-starship"
], # borrowing fish's directory ], # borrowing fish's directory
"quickshell": [CONFIG_DIR / "quickshell" / "apply-color"], "quickshell": [CONFIG_DIR / "quickshell" / "apply-color"],
"rofi": [CONFIG_DIR / "rofi" / "apply-color"], "wlogout": [CONFIG_DIR / "wlogout" / "apply-color"],
"waybar": [CONFIG_DIR / "waybar" / "apply-color"],
"wlogout": [
CONFIG_DIR / ".alt" / "wlogout-default" / "apply-color",
CONFIG_DIR / ".alt" / "wlogout-niri" / "apply-color",
],
"yazi": [CONFIG_DIR / "yazi" / "apply-color"], "yazi": [CONFIG_DIR / "yazi" / "apply-color"],
} }
# or simply `find [-L] <CONFIG_DIR> -type f -name 'apply-color*'` to get all available scripts, # or simply `find [-L] <CONFIG_DIR> -type f -name 'apply-color*'` to get all available scripts,
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
kitty --app-id kitty-floating --config "$HOME/.config/kitty/floating.conf" "$@"
+175
View File
@@ -0,0 +1,175 @@
#!/usr/bin/env python3
import sys
import glob
import argparse
import select
import os
import struct
EVENT_FORMAT = '@llHHi'
EVENT_SIZE = struct.calcsize(EVENT_FORMAT)
EV_LED = 0x11
LED_CODES = {
'numlock': 0x00,
'capslock': 0x01,
'scrolllock': 0x02
}
def get_led_state(led_type: str) -> int:
pattern = f"/sys/class/leds/*::{led_type}/brightness"
paths = glob.glob(pattern)
if not paths:
return 0
for path in paths:
try:
with open(path, 'r') as f:
if int(f.read().strip()) > 0:
return 1
except (IOError, ValueError, OSError):
continue
return 0
def has_led_capability(event_path: str) -> bool:
try:
basename = os.path.basename(event_path)
cap_path = f"/sys/class/input/{basename}/device/capabilities/led"
if os.path.exists(cap_path):
with open(cap_path, 'r') as f:
return f.read().strip() != "0"
except OSError:
pass
return False
class DeviceMonitor:
def __init__(self, target_led: str):
self.target_led = target_led
self.target_led_code = LED_CODES[target_led]
self.poller = select.poll()
self.active_fds = {}
self.last_state = get_led_state(self.target_led)
self.emit_state(self.last_state)
def emit_state(self, state: int) -> None:
try:
sys.stdout.write(f"{state}\n")
sys.stdout.flush()
except BrokenPipeError:
devnull = os.open(os.devnull, os.O_WRONLY)
os.dup2(devnull, sys.stdout.fileno())
sys.exit(0)
def scan_devices(self) -> None:
current_fds = set(self.active_fds.keys())
paths = glob.glob('/dev/input/event*')
found_fds = set()
for path in paths:
if not has_led_capability(path):
continue
already_open = False
for fd, opened_path in self.active_fds.items():
if opened_path == path:
found_fds.add(fd)
already_open = True
break
if not already_open:
try:
fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK)
self.poller.register(fd, select.POLLIN)
self.active_fds[fd] = path
found_fds.add(fd)
except PermissionError:
sys.stderr.write(f"Error: Permission denied when opening {path}.\n")
sys.exit(1)
except OSError:
continue
for fd in current_fds - found_fds:
self.remove_device(fd)
def remove_device(self, fd: int) -> None:
if fd in self.active_fds:
try:
self.poller.unregister(fd)
os.close(fd)
del self.active_fds[fd]
except Exception:
pass
def handle_device_events(self, events: list) -> None:
fds_to_remove = []
for fd, event in events:
if event & select.POLLIN:
try:
while True:
data = os.read(fd, 4096)
if not data:
fds_to_remove.append(fd)
break
events_count = len(data) // EVENT_SIZE
for i in range(events_count):
chunk = data[i * EVENT_SIZE : (i + 1) * EVENT_SIZE]
_, _, ev_type, ev_code, ev_value = struct.unpack(EVENT_FORMAT, chunk)
if ev_type == EV_LED and ev_code == self.target_led_code:
if ev_value != self.last_state:
self.emit_state(ev_value)
self.last_state = ev_value
except BlockingIOError:
pass
except OSError:
fds_to_remove.append(fd)
for fd in fds_to_remove:
self.remove_device(fd)
def run(self) -> None:
self.scan_devices()
try:
while True:
events = self.poller.poll(1000)
if not events:
self.scan_devices()
current_state = get_led_state(self.target_led)
if current_state != self.last_state:
self.emit_state(current_state)
self.last_state = current_state
else:
self.handle_device_events(events)
except KeyboardInterrupt:
sys.exit(0)
finally:
for fd in list(self.active_fds.keys()):
try:
os.close(fd)
except Exception:
pass
def main():
parser = argparse.ArgumentParser(description="Zero-polling keyboard LED monitor.")
parser.add_argument(
'-l', '--led',
type=str,
default='capslock',
choices=['capslock', 'numlock', 'scrolllock'],
help="Target LED to monitor"
)
args = parser.parse_args()
monitor = DeviceMonitor(args.led)
monitor.run()
if __name__ == "__main__":
main()
+182
View File
@@ -0,0 +1,182 @@
#!/usr/bin/env bash
# https://github.com/SHORiN-KiWATA/shorin-contrib
#
# niri-force-kill-window
# 通过鼠标点击强制结束 (SIGKILL) 任意窗口。
# 完美支持 Wayland 原生与 XWayland 代理窗口的独立精准击杀。
# 具备多语言自适应 (i18n) 与智能依赖提示功能。
#
# 实现原理:
# 1. 通过 niri msg pick-window 点选窗口
# 2. 利用窗口的 PID通过进程名称判断它是 xwayland 还是 wayland
# 3. 获取真实的底层 PIDWayland 直接取用XWayland 利用 xprop 获取焦点窗口的真实 PID
# 4. 向上追溯该真实 PID找到所属应用的根进程 (App Root),并利用防火墙逻辑避开系统进程
# 5. 从根进程向下遍历收集所有子孙进程,利用 kill -9 执行“九头蛇绞杀”,防止多进程软件死灰复燃
# ==========================================
# 0. 多语言 (Locale) 自适应支持
# ==========================================
if [[ "${LANG}" == zh_* ]]; then
STR_ERR_DEP_TITLE="依赖缺失"
STR_ERR_DEP_MSG="缺少必需命令: %s\n请尝试安装包: %s"
STR_ERR_INFO_TITLE="获取信息失败"
STR_ERR_INFO_MSG="无法获取窗口或进程信息。"
STR_ERR_KILL_TITLE="结束失败"
STR_ERR_KILL_MSG="无法提取目标真实 PID。"
STR_SUCC_TITLE="强制杀死 (%s)"
STR_SUCC_MSG="应用: %s\n连根拔起: 成功摧毁 %s 个相关进程。"
STR_UNKNOWN_APP="未知应用"
else
STR_ERR_DEP_TITLE="Missing Dependency"
STR_ERR_DEP_MSG="Command not found: %s\nPlease install package: %s"
STR_ERR_INFO_TITLE="Info Error"
STR_ERR_INFO_MSG="Could not determine window or process information."
STR_ERR_KILL_TITLE="Kill Failed"
STR_ERR_KILL_MSG="Could not extract target real PID."
STR_SUCC_TITLE="Headshot! (%s)"
STR_SUCC_MSG="App: %s\nHydra Kill: %s processes terminated."
STR_UNKNOWN_APP="Unknown App"
fi
# ==========================================
# 1. 环境与依赖检查
# ==========================================
check_dependency() {
local cmd="$1"
local pkg="$2"
if ! command -v "$cmd" &> /dev/null; then
local msg
printf -v msg "$STR_ERR_DEP_MSG" "$cmd" "$pkg"
# 如果 notify-send 存在,则发送桌面通知;否则输出到终端标准错误
if command -v notify-send &> /dev/null; then
notify-send "$STR_ERR_DEP_TITLE" "$msg" -u critical -i dialog-error
else
echo -e "[${STR_ERR_DEP_TITLE}]\n${msg}" >&2
fi
exit 1
fi
}
# 检查三大核心依赖并提示对应软件包
check_dependency "niri" "niri"
check_dependency "notify-send" "libnotify"
check_dependency "xprop" "xorg-xprop"
# ==========================================
# 2. 辅助函数:发送通知与音效 (非阻塞)
# ==========================================
notify_and_play() {
local title="$1"
local msg="$2"
notify-send "$title" "$msg" -a "Window Killer" -i application-exit
# pw-play 不是强依赖,如果有则播放音效,放入后台运行防止阻塞
if command -v pw-play &> /dev/null; then
pw-play /usr/share/sounds/freedesktop/stereo/dialog-error.oga >/dev/null 2>&1 &
fi
}
# ==========================================
# 3. 抓取目标窗口信息
# ==========================================
# 执行命令并捕获退出码。如果用户按 Esc 取消,返回非零退出码,静默退出
if ! output=$(niri msg pick-window 2>/dev/null); then
exit 0
fi
# 如果没有任何输出,直接退出
if [[ -z "$output" ]]; then
exit 0
fi
# 提取 Niri 视角下的 PID 和 App ID
pid=$(grep -oP 'PID:\s*\K\d+' <<< "$output")
app_id=$(grep -oP 'App ID:\s*"\K[^"]+' <<< "$output")
app_name="${app_id:-$STR_UNKNOWN_APP}"
# 如果正则没有提取到 PID未命中合法窗口静默退出
if [[ -z "$pid" ]]; then
exit 0
fi
# 如果确实抓到了 PID但此时进程已不存在发出异常通知
if [[ ! -f "/proc/$pid/comm" ]]; then
notify-send "$STR_ERR_INFO_TITLE" "$STR_ERR_INFO_MSG" -a "Window Killer" -i dialog-error
exit 1
fi
# ==========================================
# 4. 判定协议类型并获取真实 PID
# ==========================================
process_name=$(cat "/proc/$pid/comm")
process_name_lower="${process_name,,}"
if [[ "$process_name_lower" == *"xwayland"* ]]; then
proto_str="XWayland"
# 给内核与 X11 服务端预留 50 毫秒的时间传递和同步焦点
sleep 0.05
# 顺着焦点,询问 X11 当前活动的窗口 ID
active_wid=$(xprop -root -notype _NET_ACTIVE_WINDOW 2>/dev/null | grep -o '0x[0-9a-fA-F]\+')
# 顺藤摸瓜:提取这个 X11 窗口绑定的真实底层 Linux PID
real_pid=$(xprop -id "$active_wid" -notype _NET_WM_PID 2>/dev/null | grep -oP '\d+')
else
proto_str="Wayland"
real_pid="$pid"
fi
if [[ -z "$real_pid" ]]; then
notify-send "$STR_ERR_KILL_TITLE" "$STR_ERR_KILL_MSG" -a "Window Killer" -i dialog-error
exit 1
fi
# ==========================================
# 5. 九头蛇绞杀逻辑 (Hydra Kill)
# ==========================================
# 向上追溯,寻找进程家族的老祖宗 (App Root)
app_root=$real_pid
current=$real_pid
while true; do
ppid=$(ps -o ppid= -p "$current" 2>/dev/null | tr -d ' ')
# 如果找不到父进程,或者父进程是系统最高层 PID 1停止追溯
if [[ -z "$ppid" || "$ppid" == "1" ]]; then
break
fi
pname=$(ps -o comm= -p "$ppid" 2>/dev/null)
# 【核心防火墙】:遇到桌面环境、终端、系统核心服务,立刻停止溯源!
if [[ "$pname" =~ ^(systemd|niri|bash|zsh|fish|tmux|screen|xwayland.*|sshd|login|init|sway|hyprland)$ ]]; then
break
fi
app_root=$ppid
current=$ppid
done
# 向下递归,收集家族所有子孙 PID
get_descendants() {
local p=$1
echo "$p"
# pgrep -P 获取直接子进程
for c in $(pgrep -P "$p" 2>/dev/null); do
get_descendants "$c"
done
}
family_pids=$(get_descendants "$app_root")
pid_count=$(echo "$family_pids" | wc -w)
# 执行联合绞杀:把所有收集到的 PID 一次性全部强制终止
kill -9 $family_pids 2>/dev/null
# ==========================================
# 6. 发送战果通知与音效
# ==========================================
printf -v final_title "$STR_SUCC_TITLE" "$proto_str"
printf -v final_msg "$STR_SUCC_MSG" "$app_name" "$pid_count"
notify_and_play "$final_title" "$final_msg"
@@ -10,8 +10,7 @@ pids=$(pgrep -x quickshell)
children=() children=()
for pid in $pids; do for pid in $pids; do
# children=$(pgrep -P "$pid" 2>/dev/null) mapfile -t -O "${#children[@]}" children < <(pgrep -P "$pid" 2>/dev/null)
children+=($!)
kill "$pid" || true kill "$pid" || true
done done
@@ -46,4 +46,5 @@ fi
if (( $+commands[bat] )); then if (( $+commands[bat] )); then
export BAT_THEME="Catppuccin Mocha" export BAT_THEME="Catppuccin Mocha"
export BAT_STYLE="default,-numbers"
fi fi
+9 -6
View File
@@ -54,12 +54,12 @@ if (( $+commands[fzf] )); then
if (( $+commands[yay] )); then if (( $+commands[yay] )); then
# fyq: fuzzy yay local query # fyq: fuzzy yay local query
alias fyq="yay -Qq | fzf --preview 'yay -Qi {}'" alias fyq="yay -Qq | fzf --preview 'yay -Qi {}' --preview-window='right,70%,wrap'"
# fyi: fuzzy yay install # fyi: fuzzy yay install
fyi() { fyi() {
local pkg local pkg
pkg=$(yay -Sl | awk '{print $1"/"$2}' | fzf -m --preview 'yay -Si {}' --header "[install package]" "$@") pkg=$(yay -Sl | awk '{print $1"/"$2}' | fzf -m --preview 'yay -Si {}' --preview-window='right,70%,wrap' --header "[install package]" "$@")
# yay supports "repo/package" format # yay supports "repo/package" format
[[ -n "$pkg" ]] && yay -S ${=pkg} [[ -n "$pkg" ]] && yay -S ${=pkg}
} }
@@ -67,7 +67,7 @@ if (( $+commands[fzf] )); then
# fyr: fuzzy yay remove # fyr: fuzzy yay remove
fyr() { fyr() {
local pkg local pkg
pkg=$(yay -Qq | fzf -m --preview 'yay -Qi {}' --header "[remove package]" "$@") pkg=$(yay -Qq | fzf -m --preview 'yay -Qi {}' --preview-window='right,70%,wrap' --header "[remove package]" "$@")
[[ -n "$pkg" ]] && yay -Rn ${=pkg} [[ -n "$pkg" ]] && yay -Rn ${=pkg}
} }
fi fi
@@ -180,17 +180,20 @@ if (( $+commands[git] )); then
print -r -- "$repo" print -r -- "$repo"
} }
gc() { gcl() {
local repo local repo
repo=$(uy_git_repo_from_clipboard) || return 1 repo=$(uy_git_repo_from_clipboard) || return 1
git clone "$repo" git clone "$repo"
} }
pingo() { pingo() {
cd "$HOME/Repositories/Uni" || return 1 builtin cd "$HOME/Repositories/Uni" || return 1
local repo local repo
repo=$(uy_git_repo_from_clipboard) || return 1 repo=$(uy_git_repo_from_clipboard) || return 1
local dir_name="${repo:t:r}" local dir_name="${repo:t:r}"
local course="${${dir_name%%[^[:lower:]]*}:u}"
mkdir -p "$course"
builtin cd "$course" || return 1
if [[ ! -d "$dir_name" ]]; then if [[ ! -d "$dir_name" ]]; then
git clone "$repo" || return 1 git clone "$repo" || return 1
fi fi
@@ -201,7 +204,7 @@ if (( $+commands[git] )); then
disown disown
else else
echo "Opening method missing or invalid" echo "Opening method missing or invalid"
cd "$dir_name" builtin cd "$dir_name"
fi fi
} }
fi fi
+1
View File
@@ -45,6 +45,7 @@ fi
[[ -f "$HOME/.cargo/env" ]] && source "$HOME/.cargo/env" [[ -f "$HOME/.cargo/env" ]] && source "$HOME/.cargo/env"
(( $+commands[opam] )) && eval "$(opam env)" (( $+commands[opam] )) && eval "$(opam env)"
prepend_path "$HOME/.cargo/bin"
prepend_path "$HOME/go/bin" prepend_path "$HOME/go/bin"
prepend_path "$HOME/.local/bin" prepend_path "$HOME/.local/bin"
prepend_path "$HOME/.local/scripts" prepend_path "$HOME/.local/scripts"
@@ -1,24 +0,0 @@
#!/bin/bash
[ -f "$HOME/.local/snippets/apply-color-helper" ] || {
echo "Missing helper script: $HOME/.local/snippets/apply-color-helper"
exit 1
}
. "$HOME/.local/snippets/apply-color-helper"
for file in "$path"/icons/*.svg; do
[ -f "$file" ] || continue
sed -i -E "s/(fill=\"#)([0-9A-Fa-f]{6})(\")/\1${colorHex}\3/" "$file" || {
log_error "Failed to edit ${file}"
exit 1
}
done
file="$path"/style.css
sed -i -E "s/(border-color:\s*#)([0-9A-Fa-f]{6})(;)/\1${colorHex}\3/" "$file" || {
log_error "Failed to edit ${file}"
exit 1
}
log_success "wlogout"
@@ -1,36 +0,0 @@
{
"label": "lock",
"action": "loginctl lock-session",
"text": "Lock",
"keybind": "l"
}
{
"label": "hibernate",
"action": "systemctl hibernate",
"text": "Hibernate",
"keybind": "h"
}
{
"label": "logout",
"action": "hyprctl dispatch exit",
"text": "Logout",
"keybind": "e"
}
{
"label": "shutdown",
"action": "systemctl poweroff",
"text": "Shutdown",
"keybind": "s"
}
{
"label": "suspend",
"action": "sleep 0.1 && systemctl suspend",
"text": "Suspend",
"keybind": "u"
}
{
"label": "reboot",
"action": "systemctl reboot",
"text": "Reboot",
"keybind": "r"
}
@@ -1,120 +0,0 @@
* {
background-image: none;
font-size: 24px;
font-family: 'Sour Gummy Light';
}
window {
background-color: rgba(30, 30, 46, 0.5);
}
button {
color: #cdd6f4;
border-radius: 0;
outline-style: none;
background-color: alpha(#1e1e2e, 0.8);
border: none;
border-width: 0px;
border-radius: 0px;
border-color: #89b4fa;
box-shadow: none;
text-shadow: none;
text-decoration-color: #cdd6f4;
background-repeat: no-repeat;
background-position: center;
background-size: 20%;
animation: gradient_f 20s ease-in infinite;
}
button:focus,
button:active,
button:hover {
background-size: 20%;
background-color: alpha(#1e1e2e, 0.7);
animation: gradient_f 20s ease-in infinite;
transition: all 0.3s cubic-bezier(0.55, 0, 0.28, 1.682);
}
#lock {
background-image: image(url('./icons/lock.svg'));
border-radius: 32px 0 0 0;
}
#logout {
background-image: image(url('./icons/logout.svg'));
}
#suspend {
background-image: image(url('./icons/suspend.svg'));
border-radius: 0 32px 0 0;
}
#hibernate {
background-image: image(url('./icons/hibernate.svg'));
border-radius: 0 0 0 32px;
}
#shutdown {
background-image: image(url('./icons/shutdown.svg'));
}
#reboot {
background-image: image(url('./icons/reboot.svg'));
border-radius: 0 0 32px 0;
}
#lock:hover {
border-radius: 30px 0 0 0;
margin: -30px 0 0 -30px;
}
#logout:hover {
margin: -30px 0 0 0;
}
#suspend:hover {
border-radius: 0 30px 0 0;
margin: -30px -30px 0 0;
}
#hibernate:hover {
border-radius: 0 0 0 30px;
margin: 0 0 -30 -30px;
}
#shutdown:hover {
margin: 0 0 -30px 0;
}
#reboot:hover {
border-radius: 0 0 30px 0;
margin: 0 -30px -30px 0;
}
#lock:focus {
border-radius: 60px 0 0 0;
margin: -60px 0 0 -60px;
}
#logout:focus {
margin: -60px 0 0 0;
}
#suspend:focus {
border-radius: 0 60px 0 0;
margin: -60px -60px 0 0;
}
#hibernate:focus {
border-radius: 0 0 0 60px;
margin: 0 0 -60 -60px;
}
#shutdown:focus {
margin: 0 0 -60px 0;
}
#reboot:focus {
border-radius: 0 0 60px 0;
margin: 0 -60px -60px 0;
}
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg fill="#89b4fa" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
<metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>
<g><g><path d="M500,10C229.4,10,10,229.4,10,500s219.4,490,490,490s490-219.4,490-490S770.6,10,500,10z M500,885.1c-212.7,0-385.1-172.4-385.1-385.1S287.3,114.9,500,114.9S885.1,287.3,885.1,500S712.7,885.1,500,885.1z M576.5,308.7v382.4c0,42.2-34.2,76.5-76.5,76.5c-42.3,0-76.5-34.2-76.5-76.5V308.7c0-42.2,34.2-76.5,76.5-76.5C542.2,232.3,576.5,266.5,576.5,308.7z"/></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g></g>
</svg>

Before

Width:  |  Height:  |  Size: 969 B

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg fill="#89b4fa" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
<metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>
<g><g><path d="M321.8,455.5h356.4V321.8c0-49.2-17.4-91.2-52.2-126c-34.8-34.8-76.8-52.2-126-52.2c-49.2,0-91.2,17.4-126,52.2c-34.8,34.8-52.2,76.8-52.2,126L321.8,455.5L321.8,455.5z M900.9,522.3v400.9c0,18.6-6.5,34.3-19.5,47.3c-13,13-28.8,19.5-47.3,19.5H165.9c-18.6,0-34.3-6.5-47.3-19.5s-19.5-28.8-19.5-47.3V522.3c0-18.6,6.5-34.3,19.5-47.3c13-13,28.8-19.5,47.3-19.5h22.3V321.8c0-85.4,30.6-158.7,91.9-219.9C341.3,40.6,414.6,10,500,10c85.4,0,158.7,30.6,219.9,91.9c61.3,61.3,91.9,134.6,91.9,219.9v133.6h22.3c18.6,0,34.3,6.5,47.3,19.5C894.4,487.9,900.9,503.7,900.9,522.3L900.9,522.3z"/></g></g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg fill="#89b4fa" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
<metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>
<g><path d="M622.5,990H50.8C26.3,990,10,973.7,10,949.2V50.8C10,26.3,26.3,10,50.8,10h571.7c24.5,0,40.8,16.3,40.8,40.8v285.8c0,24.5-16.3,40.8-40.8,40.8s-40.8-16.3-40.8-40.8v-245h-490v816.7h490v-245c0-24.5,16.3-40.8,40.8-40.8s40.8,16.3,40.8,40.8v285.8C663.3,973.7,647,990,622.5,990z"/><path d="M949.2,540.8H336.7c-24.5,0-40.8-16.3-40.8-40.8c0-24.5,16.3-40.8,40.8-40.8h612.5c24.5,0,40.8,16.3,40.8,40.8C990,524.5,973.7,540.8,949.2,540.8z"/><path d="M949.2,540.8c-12.3,0-20.4-4.1-28.6-12.3L757.3,365.3c-16.3-16.3-16.3-40.8,0-57.2c16.3-16.3,40.8-16.3,57.2,0l163.3,163.3c16.3,16.3,16.3,40.8,0,57.2C969.6,536.8,961.4,540.8,949.2,540.8z"/><path d="M785.8,704.2c-12.3,0-20.4-4.1-28.6-12.3c-16.3-16.3-16.3-40.8,0-57.2l163.3-163.3c16.3-16.3,40.8-16.3,57.2,0c16.3,16.3,16.3,40.8,0,57.2L814.4,691.9C806.3,700.1,798.1,704.2,785.8,704.2z"/></g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg fill="#89b4fa" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
<metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>
<g><path d="M134.6,285.6C64.9,420.7,60.1,590,137.1,723.4L42,668.5l-32,55.4c93.1,52.1,133.6,75.9,184,106.2c28.5-51.5,52.8-94.4,107.4-186.1L246,612l-53.4,92.5C65.4,502.7,167.2,200.3,398.8,126.2C638,29.3,929,223.5,931.5,481.5c19.6,236.7-208.9,443.6-439.3,416.2l-29.5,51c277.7,54.4,556.5-201.7,524.7-483.1C976.1,170.8,637.1-41.2,367.1,77.5C262.8,114.2,183.1,191.5,134.6,285.6z"/></g>
</svg>

Before

Width:  |  Height:  |  Size: 877 B

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg fill="#89b4fa" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
<metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>
<g><path d="M764,152.1c30.9,22,58.3,46.8,82.4,74.6c24,27.8,44.6,57.8,61.8,90.1c17.2,32.3,30.2,66.4,39.1,102.4c8.9,36,13.4,72.6,13.4,109.6c0,63.8-12.2,123.7-36.5,179.6c-24.4,55.9-57.3,104.7-98.8,146.2c-41.5,41.5-90.2,74.5-146.2,98.8C623.2,977.8,563.3,990,499.5,990c-63.1,0-122.7-12.2-178.6-36.5c-55.9-24.4-104.8-57.3-146.7-98.8c-41.9-41.5-74.8-90.2-98.8-146.2c-24-55.9-36-115.8-36-179.6c0-36.4,4.3-72.1,12.9-107.1c8.6-35,20.8-68.3,36.5-99.9c15.8-31.6,35.3-61.1,58.7-88.5c23.3-27.5,49.4-52.2,78.2-74.1c15.1-11,31.4-15.1,48.9-12.4c17.5,2.7,31.7,11.3,42.7,25.7c11,14.4,15.1,30.5,12.4,48.4c-2.7,17.8-11.3,32.3-25.7,43.2c-43.2,31.6-76.4,70.3-99.3,116.3c-23,46-34.5,95.4-34.5,148.2c0,45.3,8.6,88,25.7,128.2c17.2,40.1,40.7,75.1,70.5,105c29.9,29.9,64.9,53.5,105,71c40.1,17.5,82.9,26.3,128.2,26.3c45.3,0,88-8.7,128.2-26.3c40.1-17.5,75.1-41.2,105-71s53.5-64.9,71-105c17.5-40.1,26.3-82.9,26.3-128.2c0-53.5-12.4-104.1-37.1-151.8c-24.7-47.7-59.4-87-104-117.9c-15.1-10.3-24.2-24.4-27.3-42.2c-3.1-17.8,0.5-34.3,10.8-49.4c10.3-14.4,24.4-23.2,42.2-26.2C732.5,138.2,748.9,141.8,764,152.1L764,152.1z M499.5,531.9c-17.8,0-33.1-6.3-45.8-19c-12.7-12.7-19-28-19-45.8V75.9c0-17.8,6.3-33.3,19-46.3c12.7-13,28-19.6,45.8-19.6c18.5,0,34.1,6.5,46.8,19.6c12.7,13,19,28.5,19,46.3v391.2c0,17.8-6.3,33.1-19,45.8C533.6,525.6,518,531.9,499.5,531.9L499.5,531.9z"/></g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Svg Vector Icons : http://www.onlinewebfonts.com/icon -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg fill="#89b4fa" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
<metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>
<g><path d="M500,990c-66.1,0-130.3-13-190.7-38.5c-58.4-24.7-110.8-60-155.7-105s-80.3-97.4-105-155.7C23,630.3,10,566.1,10,500c0-66.1,13-130.3,38.5-190.7c24.7-58.4,60-110.8,105-155.7c45-45,97.4-80.3,155.7-105C369.7,23,433.9,10,500,10c66.1,0,130.3,13,190.7,38.5c58.4,24.7,110.8,60,155.7,105c45,45,80.3,97.4,105,155.7C977,369.7,990,433.9,990,500c0,66.1-13,130.3-38.5,190.7c-24.7,58.4-60,110.8-105,155.7s-97.4,80.3-155.7,105C630.3,977,566.1,990,500,990z M500,79.6c-112.3,0-217.9,43.7-297.3,123.1C123.3,282.1,79.6,387.7,79.6,500s43.7,217.9,123.1,297.3c79.4,79.4,185,123.1,297.3,123.1c112.3,0,217.9-43.7,297.3-123.1c79.4-79.4,123.1-185,123.1-297.3s-43.7-217.9-123.1-297.3C717.9,123.3,612.3,79.6,500,79.6z"/><path d="M322.5,290.6h108v412h-108V290.6z"/><path d="M561.6,290.6h107.9v412H561.6V290.6z"/></g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

Before

Width:  |  Height:  |  Size: 969 B

After

Width:  |  Height:  |  Size: 969 B

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before

Width:  |  Height:  |  Size: 877 B

After

Width:  |  Height:  |  Size: 877 B

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -1,6 +1,6 @@
{ {
"label": "lock", "label": "lock",
"action": "hyprlock &", "action": "loginctl lock-session",
"text": "Lock", "text": "Lock",
"keybind": "l" "keybind": "l"
} }
@@ -24,7 +24,7 @@
} }
{ {
"label": "suspend", "label": "suspend",
"action": "sleep 0.1 && systemctl suspend", "action": "systemctl suspend",
"text": "Suspend", "text": "Suspend",
"keybind": "u" "keybind": "u"
} }
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -1,11 +1,11 @@
[[plugin.deps]] [[plugin.deps]]
use = "yazi-rs/plugins:git" use = "yazi-rs/plugins:git"
rev = "442d908" rev = "ac82af3"
hash = "6849444b7c2df08eace83f3f86fb55a3" hash = "6849444b7c2df08eace83f3f86fb55a3"
[[plugin.deps]] [[plugin.deps]]
use = "yazi-rs/plugins:smart-enter" use = "yazi-rs/plugins:smart-enter"
rev = "442d908" rev = "ac82af3"
hash = "187cc58ba7ac3befd49c342129e6f1b6" hash = "187cc58ba7ac3befd49c342129e6f1b6"
[[plugin.deps]] [[plugin.deps]]
@@ -20,8 +20,8 @@ hash = "771af2becc575a3f43d0542de823969d"
[[plugin.deps]] [[plugin.deps]]
use = "llanosrocas/yaziline" use = "llanosrocas/yaziline"
rev = "d9cc2cb" rev = "cc0314c"
hash = "b6073aadf2f9a1d5389a6d389f33f69c" hash = "b937c5c8e2d9fa314d4532489176814e"
[[plugin.deps]] [[plugin.deps]]
use = "Rolv-Apneseth/starship" use = "Rolv-Apneseth/starship"
@@ -30,17 +30,15 @@ local function setup(_, options)
color = options.color or nil, color = options.color or nil,
secondary_color = options.secondary_color or nil, secondary_color = options.secondary_color or nil,
default_files_color = options.default_files_color default_files_color = options.default_files_color
or th.which.separator_style:fg() or th.which.separator_style:fg()
or "darkgray", or "darkgray",
selected_files_color = options.selected_files_color selected_files_color = options.selected_files_color
or th.mgr.count_selected:bg() or th.mgr.count_selected:bg()
or "white", or "white",
yanked_files_color = options.selected_files_color yanked_files_color = options.yanked_files_color
or th.mgr.count_copied:bg() or th.mgr.count_copied:bg()
or "green", or "green",
cut_files_color = options.cut_files_color cut_files_color = options.cut_files_color or th.mgr.count_cut:bg() or "red",
or th.mgr.count_cut:bg()
or "red",
} }
local current_separator_style = config.separator_styles local current_separator_style = config.separator_styles
@@ -55,10 +53,10 @@ local function setup(_, options)
local style = self:style() local style = self:style()
return ui.Line({ return ui.Line({
ui.Span(current_separator_style.separator_head) ui.Span(current_separator_style.separator_head)
:fg(config.color or style.main:bg()), :fg(config.color or style.main:bg()),
ui.Span(" " .. mode .. " ") ui.Span(" " .. mode .. " ")
:fg(th.which.mask:bg()) :fg(th.which.mask:bg())
:bg(config.color or style.main:bg()), :bg(config.color or style.main:bg()),
}) })
end end
@@ -67,9 +65,14 @@ local function setup(_, options)
local size = h and (h:size() or h.cha.len) or 0 local size = h and (h:size() or h.cha.len) or 0
local style = self:style() local style = self:style()
return ui.Span(current_separator_style.separator_close .. " " .. ya.readable_size(size) .. " ") return ui.Span(
:fg(config.color or style.main:bg()) current_separator_style.separator_close
:bg(config.secondary_color or th.which.separator_style:fg()) .. " "
.. ya.readable_size(size)
.. " "
)
:fg(config.color or style.main:bg())
:bg(config.secondary_color or th.which.separator_style:fg())
end end
function Status:utf8_sub(str, start_char, end_char) function Status:utf8_sub(str, start_char, end_char)
@@ -90,8 +93,8 @@ local function setup(_, options)
if utf8.len(base_name) > max_length then if utf8.len(base_name) > max_length then
base_name = self:utf8_sub(base_name, 1, config.filename_truncate_length) base_name = self:utf8_sub(base_name, 1, config.filename_truncate_length)
.. config.filename_truncate_separator .. config.filename_truncate_separator
.. self:utf8_sub(base_name, -config.filename_truncate_length) .. self:utf8_sub(base_name, -config.filename_truncate_length)
end end
return base_name .. extension return base_name .. extension
@@ -103,19 +106,18 @@ local function setup(_, options)
if not h then if not h then
return ui.Line({ return ui.Line({
ui.Span(current_separator_style.separator_close .. " ") ui.Span(current_separator_style.separator_close .. " ")
:fg(config.secondary_color or th.which.separator_style:fg()), :fg(config.secondary_color or th.which.separator_style:fg()),
ui.Span("Empty dir") ui.Span("Empty dir"):fg(config.color or style.main:bg()),
:fg(config.color or style.main:bg()),
}) })
end end
local truncated_name = self:truncate_name(h.name, config.filename_max_length) local truncated_name =
self:truncate_name(h.name, config.filename_max_length)
return ui.Line({ return ui.Line({
ui.Span(current_separator_style.separator_close .. " ") ui.Span(current_separator_style.separator_close .. " ")
:fg(config.secondary_color or th.which.separator_style:fg()), :fg(config.secondary_color or th.which.separator_style:fg()),
ui.Span(truncated_name) ui.Span(truncated_name):fg(config.color or style.main:bg()),
:fg(config.color or style.main:bg()),
}) })
end end
@@ -124,28 +126,22 @@ local function setup(_, options)
local files_selected = #cx.active.selected local files_selected = #cx.active.selected
local files_cut = cx.yanked.is_cut local files_cut = cx.yanked.is_cut
local selected_fg = files_selected > 0 local selected_fg = files_selected > 0 and config.selected_files_color
and config.selected_files_color or config.default_files_color
or config.default_files_color
local yanked_fg = files_yanked > 0 local yanked_fg = files_yanked > 0
and and (files_cut and config.cut_files_color or config.yanked_files_color)
(files_cut or config.default_files_color
and config.cut_files_color
or config.yanked_files_color
)
or config.default_files_color
local yanked_text = files_yanked > 0 local yanked_text = files_yanked > 0
and config.yank_symbol .. " " .. files_yanked and config.yank_symbol .. " " .. files_yanked
or config.yank_symbol .. " 0" or config.yank_symbol .. " 0"
return ui.Line({ return ui.Line({
ui.Span(" " .. current_separator_style.separator_close_thin .. " ") ui.Span(" " .. current_separator_style.separator_close_thin .. " ")
:fg(th.which.separator_style:fg()), :fg(th.which.separator_style:fg()),
ui.Span(config.select_symbol .. " " .. files_selected .. " ") ui.Span(config.select_symbol .. " " .. files_selected .. " ")
:fg(selected_fg), :fg(selected_fg),
ui.Span(yanked_text .. " ") ui.Span(yanked_text .. " "):fg(yanked_fg),
:fg(yanked_fg),
}) })
end end
@@ -159,8 +155,12 @@ local function setup(_, options)
local cha = hovered.cha local cha = hovered.cha
local time = (cha.mtime or 0) // 1 local time = (cha.mtime or 0) // 1
return ui.Span(os.date("%Y-%m-%d %H:%M", time) .. " " .. current_separator_style.separator_open_thin .. " ") return ui.Span(
:fg(th.which.separator_style:fg()) os.date("%Y-%m-%d %H:%M", time)
.. " "
.. current_separator_style.separator_open_thin
.. " "
):fg(th.which.separator_style:fg())
end end
function Status:percent() function Status:percent()
@@ -182,13 +182,13 @@ local function setup(_, options)
local style = self:style() local style = self:style()
return ui.Line({ return ui.Line({
ui.Span(" " .. current_separator_style.separator_open) ui.Span(" " .. current_separator_style.separator_open)
:fg(config.secondary_color or th.which.separator_style:fg()), :fg(config.secondary_color or th.which.separator_style:fg()),
ui.Span(percent) ui.Span(percent)
:fg(config.color or style.main:bg()) :fg(config.color or style.main:bg())
:bg(config.secondary_color or th.which.separator_style:fg()), :bg(config.secondary_color or th.which.separator_style:fg()),
ui.Span(current_separator_style.separator_open) ui.Span(current_separator_style.separator_open)
:fg(config.color or style.main:bg()) :fg(config.color or style.main:bg())
:bg(config.secondary_color or th.which.separator_style:fg()), :bg(config.secondary_color or th.which.separator_style:fg()),
}) })
end end
@@ -198,10 +198,13 @@ local function setup(_, options)
local style = self:style() local style = self:style()
return ui.Line({ return ui.Line({
ui.Span(string.format(" %2d/%-2d ", math.min(cursor + 1, length), length)) ui.Span(
:fg(th.which.mask:bg()) string.format(" %2d/%-2d ", math.min(cursor + 1, length), length)
:bg(config.color or style.main:bg()), )
ui.Span(current_separator_style.separator_tail):fg(config.color or style.main:bg()), :fg(th.which.mask:bg())
:bg(config.color or style.main:bg()),
ui.Span(current_separator_style.separator_tail)
:fg(config.color or style.main:bg()),
}) })
end end
+43
View File
@@ -0,0 +1,43 @@
> 嗯, 用户想问的似乎是如何使用
>
> - `qemu-user-static`
> - `qemu-user-static-binfmt`
>
> 通过
>
> ```bash
> sudo systemd-nspawn -M {{ rootfs-name }}
> ```
>
> 运行从
>
> ```bash
> wget https://os.archlinuxarm.org/os/ArchLinuxARM-aarch64-latest.tar.gz
> ```
>
> 下载且通过
>
> ```bash
> sudo mkdir -p /var/lib/machines/{{ rootfs-name }} && \
> sudo bsdtar -xpf ArchLinuxARM-aarch64-latest.tar.gz -C /var/lib/machines/{{ rootfs-name }}
> ```
>
> 解压的 aarch64 架构的 archlinux rootfs. 但要不要提醒用户应该取消注释 `cat /etc/pacman.conf` 中的
>
> ```conf
> [options]
> #DisableSandboxFilesystem
> #DisableSandboxSyscalls
> ```
>
> 以禁用在 systemd-nspawn 中默认不可用的下载沙盒, 并且应该避免运行需要 setuid 的程序 (如 sudo), 因为 `/proc/sys/fs/binfmt_misc/qemu-aarch64` 中的 `flags` 缺失 `C` (Credentials) 标志, 导致可执行文件的 `s` 权限位被忽视? 不过如果真的需要 setuid 的话, 也可以通过
>
> ```bash
> sudo cp /usr/lib/binfmt.d/qemu-aarch64-static.conf /etc/binfmt.d/
> ```
>
> 复制一份并在末尾加上 C, 要不要一并告诉用户?
>
> 算了, 反正用户什么也不懂, 即使问题解决也只会原地消失, 一句感谢也不说.
_Baked for 27s._
+76
View File
@@ -0,0 +1,76 @@
## 问题
### VapourSynth 破坏性更新
> Tue May 5 09:08:30 CEST 2026
- `extra/vapoursynth` 包更新至 75 版本后 (此问题其实是 R74 引入的, 但他们似乎跳过了 R74 直接在近几天更新至 R75), vsscript 库文件符号链接关系如下:
- `/usr/lib/libvapoursynth-script.so` -> `python3.14/site-packages/vapoursynth/libvsscript.so`
- `/usr/lib/libvapoursynth-script.so.0` -> `python3.14/site-packages/vapoursynth/libvsscript.so`
这导致 ldconfig 根据 SONAME 字段建立的缓存条目变化, 例如从 `libvapoursynth-script.so.0 (libc,x86-64) => /usr/lib/libvapoursynth-script.so.0` 变为 `libvsscript.so (libc6,x86-64) => /usr/lib/libvsscript.so`, 可能会使其他未能重新链接的程序找不到对应库.
但该问题影响范围比较有限, 因为动态库并不完全根据 ldconfig 缓存条目加载 —— 如果文件名完全匹配也能正确加载, 毕竟 `/usr/lib/libvapoursynth-script.so.0` 仍然存在.
- 同时, 插件目录也从 `/usr/lib/vapoursynth/` 改为 `/usr/lib/python3.14/site-packages/vapoursynth/plugins/`, 很多包未能及时更新插件安装路径, 导致 vapoursynth 插件丢失.
extra 仓库中的 vapoursynth-plugin-\* 包均已改为类似如下动态查找插件安装路径的方式:
```bash
install -Dm 755 libxxx.so -t "${pkgdir}"/$(python -c 'import vapoursynth;print(vapoursynth.get_plugin_dir())')/
```
所以不会出问题. 主要影响范围是打包者或上游硬编码插件安装路径为 `/usr/lib/vapoursynth` 的包, 其中包括少数 extra 仓库的包如 `extra/ffms2` 和众多 AUR 包.
- vapoursynth 不再在编译器 link libpython, 而必须在加载 libvsscript.so 时选择 python. 选哪个 python 由 `$HOME/.config/vapoursynth/vapoursynth.toml` 维护的映射关系决定, 格式类似:
```toml
"/usr/lib/python3.14/site-packages/vapoursynth/libvsscript.so" = ["/usr/bin/python","/usr/lib/libpython3.14.so.1.0"]
```
可以运行 `vapoursynth config` 自动更新.
然而, 上文提到过新的 libvsscript.so 的 ldconfig 条目是 `libvsscript.so (libc6,x86-64) => /usr/lib/libvsscript.so`, 而 `vapoursynth config` 只会在 `vapoursynth.toml` 写入 `/usr/lib/python3.14/site-packages/vapoursynth/libvsscript.so` 的条目. 此时, 如果加载 libvsscript.so, 会从 ldconfig 拿到 `/usr/lib/libvsscript.so`,而 vapoursynth 在拿到此路径后不解 symlink, 查 `vapoursynth.toml` 时和任何条目都对不上, 导致加载失败. 报错类似:
```
Python executable and library path couldn't be determined despite automatic configuration. Run `vapoursynth config` to set it for this Python installation and then try again.
```
临时修复方式为在 `$HOME/.config/vapoursynth/vapoursynth.toml` 里手动加上针对 `/usr/lib/libvsscript.so` 符号链接的条目:
```toml
"/usr/lib/libvsscript.so" = ["/usr/bin/python","/usr/lib/libpython3.14.so.1.0"]
```
如果运行 vapoursynth 此次更新前构建并链接的程序, 可能同样需要为 `/usr/lib/libvapoursynth-script.so.0` 准备对应条目:
```toml
"/usr/lib/libvapoursynth-script.so.0" = ["/usr/bin/python","/usr/lib/libpython3.14.so.1.0"]
```
影响范围为所有动态链接 vapoursynth 的二进制文件.
### CUDA 13.2 不兼容 GCC 16.1
> Mon May 4 15:39:17 CEST 2026
nvcc 和 gcc 兼容问题的历史重演. 随 GCC 16.1 发布, 以前用 GCC 15.2 能够编译的 CUDA 包会报一堆错误, 影响范围非常大.
解决方法为装 gcc15 等旧版本工具链, 并修改编译流程使 nvcc 使用指定编译器. 例如对于部分 Makefile:
```makefile
CUDA_CCBIN ?=
cudaccbin = $(if $(CUDA_CCBIN),-ccbin $(CUDA_CCBIN),)
...
nvcc $(cudaccbin) ...
```
但这样会带来一个问题: `gcc15` 是 AUR 包, 需要编译. 而这并不会是一个很愉快的过程. 除去超大的仓库体积, 超长时间的编译测试和超高的资源占用外, 过程中还可能会因为各种问题失败, 修复问题后可能还需要从头再来, 成本过高.
但好在 cachyos 有打包 `gcc14` 二进制包, 因此再降一个版本即可绕过编译过程. 但 AUR 上的众多打包者应该还是会选择 `gcc15`, 因此在有仓库打包 `gcc15` 二进制包或 NVIDIA 更新 CUDA 支持 GCC 16 之前, 几乎所有涉及 CUDA 的包仍然需要手动修改 PKGBUILD 构建.
另外还有几个可选方案:
- 可以用 clang++ 代替 nvcc, 但支持程度似乎有限 (`clang++: warning: CUDA version is newer than the latest partially supported version 12.9 [-Wunknown-cuda-version]`), 且对于部分已经在使用 nvcc 构建的项目可能需要较大幅度地修改编译参数和流程, 实用价值不高.
-75
View File
@@ -1,75 +0,0 @@
things I have installed:
full KDE Plasma 6 setup
which can provide:
SDDM theme # Breeze is enough
kcalc/kalc # calculator(s), what's the difference?
kcolorchooser # or hyprpicker
pipewire & friends
...
# hypr*
hyprland
hypridle
hyprlock
hyprshot
hyprpicker
plugin hyprexpo # workspaces overview
plugin hyprorganize # https://github.com/Uyanide/hyprplug
# xdg-desktop-portal*
xdg-desktop-portal
xdg-desktop-portal-hyprland # not working with my Intel iGPU, but fine with NVIDIA dGPU
xdg-desktop-portal-gtk # for file picker
xdg-desktop-portal-gnome # why not?
# terminal emulator(s)
kitty # normal terminal
ghostty # floating terminal, for btop for example
# under surface
swww # wallpaper daemon
mako # notification daemon
gnome-keyring # --password-store=gnome-libsecret
wl-clipboard
cliphist # clipboard history
slurp # region selector
wf-recorder # screen recorder
brightnessctl
playerctl
pamixer
zoxide # better cd
eza # better ls
bat # better cat
# GUI
waybar
eww
wlogout
rofi(-wayland)
mpv
network-manager-applet # nm-applet
blueman # bluetooth GUI & applet
pwvucontrol
gnome-text-editor # or kwrite, notepad replacement
btop # system monitor
activate-linux # :/
polkit-gnome # polkit authentication agent
gradia # screenshots editor
# fonts & themes
maplemono-nf-cn / ttf-maplemono-nf-cn-unhinted (archlinuxcn)
Sour Gummy
ttf-meslo-nerd
ttf-jetbrains-mono-nerd
spicetify # spotify tweaks
spicetify-maketplace # spotify themes
nwg-look # theme of GTK apps
catppuccin-gtk-theme-mocha # theme of GTK apps
kvantum
# utils
bc
jq
python-colorthief
python-watchdog
-7
View File
@@ -1,7 +0,0 @@
things I have installed:
in addition to everything in `./hyprland-ricing.txt`:
xwayland-satellite
wlsunset
wezterm # replaces ghostty
+590
View File
@@ -0,0 +1,590 @@
set -o pipefail
awk '
/^<!-- update-full-list:start$/ { grab=1; next }
/^update-full-list:end -->$/ { exit }
grab
' "$0" \
| bash -s -- "$0"
exit $?
<!-- update-full-list:start
set -euo pipefail
script_path="$(readlink -f -- "$1")"
tmp_file="$(mktemp -- "${script_path}.XXXXXX")"
trap 'rm -f -- "$tmp_file"' EXIT
pkgs="$(yay -Qeq | LC_ALL=C sort)"
awk -v pkgs="$pkgs" '
$0 == "## Full list" {
print
in_header = 1
next
}
in_header && $0 == "```" {
print
print pkgs
in_header = 0
in_block = 1
next
}
in_header { print; next }
in_block {
if ($0 == "```") {
print
in_block = 0
}
next
}
{ print }
END {
if (in_header || in_block) {
print "ERROR: Full list section is malformed (missing closing ```)" > "/dev/stderr"
exit 1
}
}
' "$script_path" > "$tmp_file"
mv -- "$tmp_file" "$script_path"
echo "Updated Full list in: $script_path"
update-full-list:end -->
> [!NOTE]
> The gibberish above is **NOT** meant to be copy-pasted into the terminal. It is a script that updates the [Full list](#full-list) section below, and should be run as:
>
> ```bash
> bash /path/to/dotfiles/memo/packages.md
> ```
## Notes
| | |
| --------- | ---------------------------- |
| alass | Subtitle sync; used in mpv |
| axel | CLI download accelerator |
| figlet | Draw large letters |
| foliate | GTK eBook reader |
| gearlever | AppImage manager |
| gping | Ping with better looking TUI |
| jp2a | JPEG to ASCII |
| nethogs | Network top |
| picard | Music tagger (MusicBrainz) |
| toilet | Better FIGlet |
| wev | Debug wayland events |
| yad | Fork of zenity |
| zenity | Display dialog boxes via cli |
## Some useful commands
Show packages sorted by size, with a preview of their info:
```bash
expac -H M '%m\t%n' \
| sort -hr \
| fzf --delimiter='\t' --with-nth=1,2 --multi \
--preview 'yay -Qi {2}' \
--preview-window='right,70%,wrap'
```
Update the list below:
```bash
bash /path/to/dotfiles/memo/packages.md
```
## Full list
```
7zip
aarch64-linux-gnu-gcc
alacritty
alass
arch-install-scripts
archiso
archlinux-contrib
archlinuxcn-keyring
ark
av1an
awww
axel
azure-cli
base
base-devel
bash-completion
bat
bc
bind
blueman
bluez-tools
bluez-utils
bootconfig
bpf
bridge-utils
brightnessctl
bsd-games
btop
btrfs-assistant
btrfs-progs
busybox
cachyos-keyring
cachyos-mirrorlist
cachyos-rate-mirrors
cachyos-v3-mirrorlist
cachyos-v4-mirrorlist
catppuccin-gtk-theme-mocha
cava
cbonsai
chafa
chaotic-keyring
chaotic-mirrorlist
chromium
chwd
claude-code
cloc
cmake
cmatrix-git
composer
compsize
corectrl
cowfortune
cpu-x
cpupower
cuda
curl
cython
deno
devtools
digital
dnsmasq
docker
docker-compose
dolphin
dotnet-sdk
doxygen
drm-info
dwarfs
ed
efibootmgr
elisa
ethtool
euphonica-git
eww
expac
extra-cmake-modules
eza
fastfetch
fcitx5
fcitx5-chinese-addons
fcitx5-configtool
fcitx5-gtk
fcitx5-pinyin-moegirl
fcitx5-pinyin-zhwiki
fcitx5-qt
fd
fdkaac
ffmpeg-full
ffms2
ffnvcodec-headers
ffvship
figlet
filelight
fish
fisher
flatpak
flutter-bin
fnm
foliate
font-manager
fontforge
foot
frei0r-plugins
fuzzel
fzf
gamemode
gcc14
gdb
gdu
gearlever
geoclue
geoipupdate
ghostty
gifski
gimp
git
git-filter-repo
git-sizer
github-cli
glaze
gnome-keyring
gnome-text-editor
go
gping
gradia
gradle
grim
grub
grub-btrfs
gst-plugins-bad
gucharmap
gvfs-smb
gwenview
handbrake
hashclash-cuda-git
helix
hmcl
htop
hwinfo
hyperfine
hyperv
hypridle
hyprlock
hyprpicker
hyprpolkitagent
hyprsunset
hyprutils
imagemagick
inetutils
intel-gpu-tools
intel-media-sdk
intel-speed-select
intel-ucode
iperf3
jdk-openjdk
jdk17-openjdk
jdk21-graalvm-ee-bin
jetbrains-toolbox
jp2a
jujutsu
kalk
kate
kcalendarcore
kcolorchooser
kcontacts
kcpuid
kdav
kdenlive
kdiskmark
kdoctools
kid3
kitty
kmscon
konsole
kpeople
kplotting
krdc
ktexttemplate
kvantum
lazygit
lib32-nvidia-utils
lib32-opencl-nvidia
lib32-vulkan-icd-loader
lib32-vulkan-intel
libc++
libdbusmenu-lxqt
libguestfs
libreoffice-still-zh-cn
libspng
libva-intel-driver
libva-nvidia-driver
libva-utils
libvips
libvirt
lightdm
linux-cachyos
linux-cachyos-headers
linux-firmware
linux-lts
linux-lts-headers
linuxqq
llmfit-bin
localsend
lolcat
lua-socket
luarocks
lutris
lzip
magiskboot-bin
man-db
man-pages
mangohud
matugen
meson
mkvtoolnix-cli
modprobed-db
moonlight-qt
moreutils
mpc
mpd
mpd-mpris
mpv-full
mpv-mpris
msedit
namcap
nasm
nautilus
nautilus-share
nethogs
network-manager-applet
networkmanager
networkmanager-openvpn
nfs-utils
niri
nmap
nordvpn-bin
noto-fonts-cjk
nvidia-container-toolkit
nvidia-open-dkms
nvidia-prime
nvidia-settings
nvidia-utils
nvme-cli
nvtop
nwg-look
oavif
obs-studio
obsidian
okular
opam
openbsd-netcat
opencl-headers
opencl-nvidia
openlist-bin
openssh
os-prober
pacman-contrib
pacman-utils
pamixer
pandoc-bin
papirus-icon-theme
perf
perl-file-homedir
perl-image-exiftool
perl-yaml-tiny
php
picard
pipes.c
pipewire-alsa
plasma-meta
polkit-gnome
power-profiles-daemon
protonplus
pwvucontrol
pyside6
python-adblock
python-aiohttp
python-argcomplete
python-chardet
python-colorthief
python-darkdetect
python-fonttools
python-lxml
python-opencv-cuda
python-pygments
python-pyqt6
python-pytest
python-pytz
python-virtualenv
python-watchdog
python-yaml
qbittorrent-enhanced
qdiskinfo
qemu-full
qemu-user-static
qemu-user-static-binfmt
qt5-graphicaleffects
qt5-quickcontrols
qt5-quickcontrols2
qt5-wayland
qt6-3d
qt6-datavis3d
qt6-doc
qt6-examples
qt6-graphs
qt6-grpc
qt6-httpserver
qt6-languageserver
qt6-lottie
qt6-networkauth
qt6-quick3dphysics
qt6-quickeffectmaker
qt6-remoteobjects
qt6-scxml
qt6-serialbus
qt6ct
qtcreator
qtrvsim
quickshell-git
qutebrowser
rclone
reflector
resources
riscv64-linux-gnu-binutils
riscv64-linux-gnu-gcc
rsync
ruff
rust
rustdesk
scrcpy
sd
seahorse
shellcheck-bin
sl
slurp
snapper
solaar
spicetify-cli
spicetify-marketplace-bin
spike
spotify
squashfs-tools-ng
sshfs
starship
steam
stow
sudo
sunshine
sushi
sysbench
systemc
tailscale
tcpdump
telegram-desktop
terminus-font
texlive-basic
texlive-bibtexextra
texlive-binextra
texlive-context
texlive-fontsextra
texlive-fontsrecommended
texlive-fontutils
texlive-formatsextra
texlive-games
texlive-humanities
texlive-latex
texlive-latexextra
texlive-latexrecommended
texlive-luatex
texlive-mathscience
texlive-metapost
texlive-music
texlive-pictures
texlive-plaingeneric
texlive-pstricks
texlive-publishers
texlive-xetex
thunderbird
tigervnc
tk
tmon
tmux
toilet
tombi
trash-cli
tree
ttf-comic-shanns-nerd
ttf-jetbrains-mono-nerd
ttf-lxgw-wenkai
ttf-lxgw-wenkai-tc
ttf-maplemono-nf-cn
ttf-meslo-nerd
ttf-noto-sans-cjk-vf
ttf-sarasa-gothic
ttf-symbola
tty-clock
turbostat
unarchiver
unrar
usbip
uv
valgrind
vapoursynth-plugin-vship-cuda-git
ventoy-bin
vesktop-bin
vicinae
vim
virt-install
virt-manager
visual-studio-code-bin
vk-hdr-layer-kwin6-git
vlc
vulkan-extra-layers
vulkan-extra-tools
vulkan-gfxstream
vulkan-headers
vulkan-intel
vulkan-mesa-layers
vulkan-swrast
vvenc
wallreel
waybar
waydroid
waydroid-helper
waypaper
wev
wezterm
wf-recorder
wget
whisper.cpp-model-large-v3-turbo
wine
winetricks
wireshark-qt
wl-clipboard
wl-mirror
wlogout
wlsunset
wqy-bitmapfont
wqy-microhei
wqy-zenhei
x86_energy_perf_policy
xclip
xdg-desktop-portal-gnome
xdg-desktop-portal-gtk
xone-dkms
xorg-bdftopcf
xorg-font-util
xorg-iceauth
xorg-mkfontscale
xorg-server-devel
xorg-server-xephyr
xorg-server-xnest
xorg-server-xvfb
xorg-sessreg
xorg-smproxy
xorg-x11perf
xorg-xbacklight
xorg-xcmsdb
xorg-xcursorgen
xorg-xdriinfo
xorg-xev
xorg-xgamma
xorg-xhost
xorg-xinit
xorg-xinput
xorg-xkbevd
xorg-xkbutils
xorg-xkill
xorg-xlsatoms
xorg-xlsclients
xorg-xpr
xorg-xrefresh
xorg-xsetroot
xorg-xvinfo
xorg-xwininfo
xpadneo-dkms
xwayland-satellite
yad
yay
yay-debug
yazi
yt-dlp
zellij
zen-browser-bin
zenity
zig
zoxide
zram-generator
zsh
zsh-antidote
```