mirror of
https://github.com/anotherhadi/nixy.git
synced 2026-10-05 11:18:24 +02:00
@@ -3,8 +3,19 @@
|
||||
pkgs,
|
||||
scripts,
|
||||
...
|
||||
}: {
|
||||
home.packages = [pkgs.wl-clipboard];
|
||||
}: let
|
||||
tofiMenu = "${pkgs.tofi}/bin/tofi --horizontal false --anchor center --width 700 --height 500 --margin-top 0 --margin-left 0 --margin-right 0 --num-results 10";
|
||||
in {
|
||||
scripts.clipboard-menu = pkgs.writeShellScriptBin "clipboard-menu" ''
|
||||
list=$(${pkgs.cliphist}/bin/cliphist list)
|
||||
selected=$(printf '%s\n' "$list" | cut -f2- | ${tofiMenu} --prompt-text "Clipboard: ")
|
||||
[ -n "$selected" ] && printf '%s\n' "$list" \
|
||||
| awk -F'\t' -v s="$selected" '$2 == s {print; exit}' \
|
||||
| ${pkgs.cliphist}/bin/cliphist decode \
|
||||
| ${pkgs.wl-clipboard}/bin/wl-copy
|
||||
'';
|
||||
|
||||
home.packages = [pkgs.wl-clipboard scripts.clipboard-menu];
|
||||
|
||||
services.cliphist = {
|
||||
enable = true;
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
# hypridle handles idle management: lock the screen, turn the display off, and
|
||||
# suspend after periods of inactivity. The `caffeine-toggle` script pauses it.
|
||||
{pkgs, ...}: {
|
||||
{
|
||||
pkgs,
|
||||
scripts,
|
||||
...
|
||||
}: {
|
||||
scripts.caffeine-toggle = pkgs.writeShellScriptBin "caffeine-toggle" ''
|
||||
# Pause hypridle (stay awake) or resume it.
|
||||
if systemctl --user is-active --quiet hypridle; then
|
||||
systemctl --user stop hypridle
|
||||
OSD_TEXT=" Keep Awake On"
|
||||
else
|
||||
systemctl --user start hypridle
|
||||
OSD_TEXT=" Keep Awake Off"
|
||||
fi
|
||||
${scripts.waybar-osd}/bin/waybar-osd "$OSD_TEXT"
|
||||
'';
|
||||
|
||||
home.packages = [scripts.caffeine-toggle];
|
||||
|
||||
services.hypridle = {
|
||||
enable = true;
|
||||
settings = {
|
||||
@@ -17,7 +35,7 @@
|
||||
on-timeout = "loginctl lock-session";
|
||||
}
|
||||
{
|
||||
timeout = 360; # 6 min → screen off
|
||||
timeout = 720; # 12 min → screen off
|
||||
on-timeout = "${pkgs.hyprland}/bin/hyprctl dispatch dpms off";
|
||||
on-resume = "${pkgs.hyprland}/bin/hyprctl dispatch dpms on";
|
||||
}
|
||||
@@ -28,4 +46,16 @@
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
xdg.desktopEntries = {
|
||||
caffeine-toggle = {
|
||||
name = "Keep Awake";
|
||||
exec = "${scripts.caffeine-toggle}/bin/caffeine-toggle";
|
||||
icon = "my-caffeine-on-symbolic";
|
||||
comment = "Pause or resume idle locking and suspend";
|
||||
categories = ["System"];
|
||||
terminal = false;
|
||||
settings.Keywords = "caffeine;idle;awake;inhibit;suspend;";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,63 +46,6 @@ in {
|
||||
|
||||
bind =
|
||||
[
|
||||
# Applications
|
||||
(
|
||||
"$shiftMod, A, exec, "
|
||||
+ lib.getExe (mkMenu [
|
||||
{
|
||||
key = "a";
|
||||
desc = "Proton Authenticator";
|
||||
cmd = "env WEBKIT_DISABLE_COMPOSITING_MODE=1 ${pkgs.proton-authenticator}/bin/proton-authenticator";
|
||||
}
|
||||
{
|
||||
key = "p";
|
||||
desc = "Proton Pass";
|
||||
cmd = "${pkgs.proton-pass}/bin/proton-pass";
|
||||
}
|
||||
{
|
||||
key = "v";
|
||||
desc = "Proton VPN";
|
||||
cmd = "${pkgs.proton-vpn}/bin/protonvpn-app";
|
||||
}
|
||||
{
|
||||
key = "c";
|
||||
desc = "Proton Calendar";
|
||||
cmd = "${config.programs.helium.package}/bin/helium 'https://calendar.proton.me/'";
|
||||
}
|
||||
{
|
||||
key = "m";
|
||||
desc = "Proton Mail";
|
||||
cmd = "${config.programs.helium.package}/bin/helium 'https://mail.proton.me/'";
|
||||
}
|
||||
{
|
||||
key = "o";
|
||||
desc = "Obsidian";
|
||||
cmd = "${pkgs.obsidian}/bin/obsidian";
|
||||
}
|
||||
{
|
||||
key = "s";
|
||||
desc = "Signal";
|
||||
cmd = "${pkgs.signal-desktop}/bin/signal-desktop";
|
||||
}
|
||||
{
|
||||
key = "t";
|
||||
desc = "TickTick";
|
||||
cmd = "${pkgs.ticktick}/bin/ticktick";
|
||||
}
|
||||
{
|
||||
key = "b";
|
||||
desc = "Helium";
|
||||
cmd = "${config.programs.helium.package}/bin/helium";
|
||||
}
|
||||
{
|
||||
key = "i";
|
||||
desc = "Helium (Incognito)";
|
||||
cmd = "${config.programs.helium.package}/bin/helium --incognito";
|
||||
}
|
||||
])
|
||||
)
|
||||
|
||||
"$mod,B, exec, uwsm app -- ${config.programs.helium.package}/bin/helium" # Browser
|
||||
|
||||
# Power
|
||||
@@ -112,22 +55,22 @@ in {
|
||||
{
|
||||
key = "l";
|
||||
desc = "Lock";
|
||||
cmd = "${pkgs.hyprlock}/bin/hyprlock";
|
||||
cmd = config.xdg.desktopEntries.lock.exec;
|
||||
}
|
||||
{
|
||||
key = "s";
|
||||
desc = "Suspend";
|
||||
cmd = "systemctl suspend";
|
||||
cmd = config.xdg.desktopEntries.suspend.exec;
|
||||
}
|
||||
{
|
||||
key = "r";
|
||||
desc = "Reboot";
|
||||
cmd = "systemctl reboot";
|
||||
cmd = config.xdg.desktopEntries.reboot.exec;
|
||||
}
|
||||
{
|
||||
key = "p";
|
||||
desc = "Power Off";
|
||||
cmd = "systemctl poweroff";
|
||||
cmd = config.xdg.desktopEntries.shutdown.exec;
|
||||
}
|
||||
])
|
||||
)
|
||||
@@ -159,8 +102,8 @@ in {
|
||||
"$shiftMod, S, movetoworkspace, special:scratch" # Move to scratch workspace
|
||||
|
||||
# Utilities
|
||||
", Print, exec, ${pkgs.hyprshot}/bin/hyprshot -m region" # Capture region
|
||||
"$shiftMod, Print, exec, ${pkgs.hyprshot}/bin/hyprshot -m output" # Capture screen
|
||||
", Print, exec, ${config.xdg.desktopEntries.screenshot-region.exec}" # Capture region
|
||||
"$shiftMod, Print, exec, ${config.xdg.desktopEntries.screenshot-screen.exec}" # Capture screen
|
||||
]
|
||||
++ (builtins.concatLists (
|
||||
builtins.genList (
|
||||
|
||||
@@ -21,6 +21,7 @@ in {
|
||||
./bindings.nix
|
||||
./polkitagent.nix
|
||||
./hyprpaper.nix
|
||||
./scripts.nix
|
||||
];
|
||||
|
||||
home.packages =
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Compositor-level scripts: focus mode (temporarily flattens Hyprland's
|
||||
# animations/gaps/decoration) and night shift (hyprsunset).
|
||||
{
|
||||
pkgs,
|
||||
scripts,
|
||||
...
|
||||
}: {
|
||||
scripts.focus-toggle = pkgs.writeShellScriptBin "focus-toggle" ''
|
||||
if test -f "$XDG_RUNTIME_DIR/hypr-focus-mode"; then
|
||||
rm "$XDG_RUNTIME_DIR/hypr-focus-mode"
|
||||
OSD_TEXT=" Focus Off"
|
||||
${scripts.waybar-osd}/bin/waybar-osd "$OSD_TEXT"
|
||||
${pkgs.hyprland}/bin/hyprctl reload
|
||||
${pkgs.hyprland}/bin/hyprctl dispatch exec waybar
|
||||
else
|
||||
touch "$XDG_RUNTIME_DIR/hypr-focus-mode"
|
||||
OSD_TEXT=" Focus On"
|
||||
${scripts.waybar-osd}/bin/waybar-osd "$OSD_TEXT"
|
||||
${pkgs.procps}/bin/pkill waybar || true
|
||||
${pkgs.hyprland}/bin/hyprctl keyword animations:enabled false
|
||||
${pkgs.hyprland}/bin/hyprctl keyword general:gaps_in 0
|
||||
${pkgs.hyprland}/bin/hyprctl keyword general:gaps_out 0
|
||||
${pkgs.hyprland}/bin/hyprctl keyword decoration:active_opacity 1
|
||||
${pkgs.hyprland}/bin/hyprctl keyword decoration:inactive_opacity 1
|
||||
${pkgs.hyprland}/bin/hyprctl keyword decoration:rounding 0
|
||||
fi
|
||||
'';
|
||||
|
||||
scripts.nightshift-toggle = pkgs.writeShellScriptBin "nightshift-toggle" ''
|
||||
if ${pkgs.procps}/bin/pidof "hyprsunset" > /dev/null; then
|
||||
pkill hyprsunset
|
||||
OSD_TEXT=" Night Shift Off"
|
||||
else
|
||||
${pkgs.hyprsunset}/bin/hyprsunset -t 4500 &
|
||||
OSD_TEXT=" Night Shift On"
|
||||
fi
|
||||
${scripts.waybar-osd}/bin/waybar-osd "$OSD_TEXT"
|
||||
'';
|
||||
|
||||
home.packages = [scripts.focus-toggle scripts.nightshift-toggle];
|
||||
|
||||
xdg.desktopEntries = {
|
||||
focus-toggle = {
|
||||
name = "Focus Mode";
|
||||
exec = "${scripts.focus-toggle}/bin/focus-toggle";
|
||||
icon = "do-not-disturb-symbolic";
|
||||
comment = "Toggle focus mode";
|
||||
categories = ["System"];
|
||||
terminal = false;
|
||||
};
|
||||
|
||||
nightshift-toggle = {
|
||||
name = "Night Shift";
|
||||
exec = "${scripts.nightshift-toggle}/bin/nightshift-toggle";
|
||||
icon = "night-light-symbolic";
|
||||
comment = "Toggle night shift";
|
||||
categories = ["System"];
|
||||
terminal = false;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -63,6 +63,15 @@
|
||||
in {
|
||||
stylix.targets.hyprlock.enable = false;
|
||||
|
||||
xdg.desktopEntries.lock = {
|
||||
name = "Lock";
|
||||
exec = "${pkgs.hyprlock}/bin/hyprlock";
|
||||
icon = "system-lock-screen-symbolic";
|
||||
comment = "Lock the screen";
|
||||
categories = ["System"];
|
||||
terminal = false;
|
||||
};
|
||||
|
||||
programs.hyprlock = {
|
||||
enable = true;
|
||||
settings = {
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
text = ["nvim-ghostty.desktop"];
|
||||
code = ["nvim-ghostty.desktop"];
|
||||
image = ["imv-dir.desktop"];
|
||||
audio = ["vlc.desktop"];
|
||||
audio = ["kew.desktop"];
|
||||
video = ["vlc.desktop"];
|
||||
directory = ["elio.desktop"];
|
||||
office = ["onlyoffice-desktopeditors.desktop"];
|
||||
pdf = ["onlyoffice-desktopeditors.desktop"];
|
||||
pdf = ["helium.desktop"];
|
||||
archive = ["elio.desktop"];
|
||||
browser = ["helium.desktop"];
|
||||
};
|
||||
@@ -53,21 +53,24 @@
|
||||
"image/bmp"
|
||||
"image/gif"
|
||||
"image/jpeg"
|
||||
"image/jpg"
|
||||
"image/png"
|
||||
"image/svg+xml"
|
||||
"image/tiff"
|
||||
"image/vnd.microsoft.icon"
|
||||
"image/webp"
|
||||
"image/heif"
|
||||
"image/avif"
|
||||
];
|
||||
audio = [
|
||||
"audio/aac"
|
||||
"audio/mpeg"
|
||||
"audio/ogg"
|
||||
"audio/opus"
|
||||
"audio/wav"
|
||||
"audio/vnd.wave"
|
||||
"audio/webm"
|
||||
"audio/x-matroska"
|
||||
"audio/flac"
|
||||
"audio/mp4"
|
||||
];
|
||||
video = [
|
||||
"video/mp2t"
|
||||
@@ -78,6 +81,7 @@
|
||||
"video/x-flv"
|
||||
"video/x-matroska"
|
||||
"video/x-msvideo"
|
||||
"video/quicktime"
|
||||
];
|
||||
directory = ["inode/directory"];
|
||||
office = [
|
||||
@@ -104,6 +108,8 @@
|
||||
"application/x-xz"
|
||||
"application/x-zstd"
|
||||
"application/x-compressed-tar"
|
||||
"application/x-bzip2-compressed-tar"
|
||||
"application/x-xz-compressed-tar"
|
||||
];
|
||||
browser = [
|
||||
"text/html"
|
||||
@@ -145,7 +151,6 @@ in {
|
||||
enable = true;
|
||||
createDirectories = true;
|
||||
desktop = null;
|
||||
music = null;
|
||||
publicShare = null;
|
||||
templates = null;
|
||||
extraConfig = {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
pkgs,
|
||||
scripts,
|
||||
...
|
||||
}: {
|
||||
scripts.power-cycle = pkgs.writeShellScriptBin "power-cycle" ''
|
||||
ppd=${pkgs.power-profiles-daemon}/bin/powerprofilesctl
|
||||
cur=$("$ppd" get)
|
||||
case "$cur" in
|
||||
power-saver) next=balanced; icon="" ;;
|
||||
balanced) next=performance; icon="" ;;
|
||||
performance) next=power-saver; icon="" ;;
|
||||
*) next=balanced; icon="" ;;
|
||||
esac
|
||||
"$ppd" set "$next"
|
||||
OSD_TEXT="$icon ''${next^}"
|
||||
${scripts.waybar-osd}/bin/waybar-osd "$OSD_TEXT"
|
||||
'';
|
||||
|
||||
home.packages = [pkgs.poweralertd scripts.power-cycle];
|
||||
|
||||
xdg.desktopEntries.power-cycle = {
|
||||
name = "Power Profile";
|
||||
exec = "${scripts.power-cycle}/bin/power-cycle";
|
||||
icon = "power-profile-balanced-symbolic";
|
||||
comment = "Cycle power-saver / balanced / performance";
|
||||
categories = ["System"];
|
||||
terminal = false;
|
||||
settings.Keywords = "power;profile;performance;battery;";
|
||||
};
|
||||
|
||||
# Event-driven low-battery/critical/full notifications via UPower dbus signals
|
||||
# (thresholds set in nixos/utils.nix, services.upower.percentage*).
|
||||
systemd.user.services.poweralertd = {
|
||||
Unit.Description = "UPower-powered battery notifications";
|
||||
Service = {
|
||||
ExecStart = "${pkgs.poweralertd}/bin/poweralertd";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
Install.WantedBy = ["graphical-session.target"];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# Shared script registry: any module that owns a script contributes it here
|
||||
# (`scripts.<name> = ...`), and every module reads it back via the `scripts`
|
||||
# module argument.
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: {
|
||||
options.scripts = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.package;
|
||||
default = {};
|
||||
internal = true;
|
||||
};
|
||||
|
||||
config._module.args.scripts = config.scripts;
|
||||
}
|
||||
@@ -18,6 +18,28 @@
|
||||
in "${builtins.substring high 1 digits}${builtins.substring low 1 digits}";
|
||||
alphaHex = opacityToHex config.theme.inactive-opacity;
|
||||
in {
|
||||
scripts.dnd-toggle = pkgs.writeShellScriptBin "dnd-toggle" ''
|
||||
state=$(${pkgs.swaynotificationcenter}/bin/swaync-client -d)
|
||||
if [ "$state" = "true" ]; then
|
||||
OSD_TEXT=" Do Not Disturb On"
|
||||
else
|
||||
OSD_TEXT=" Do Not Disturb Off"
|
||||
fi
|
||||
${scripts.waybar-osd}/bin/waybar-osd "$OSD_TEXT"
|
||||
'';
|
||||
|
||||
home.packages = [scripts.dnd-toggle];
|
||||
|
||||
xdg.desktopEntries.dnd-toggle = {
|
||||
name = "Do Not Disturb";
|
||||
exec = "${scripts.dnd-toggle}/bin/dnd-toggle";
|
||||
icon = "notifications-disabled-symbolic";
|
||||
comment = "Toggle Do Not Disturb";
|
||||
categories = ["System"];
|
||||
terminal = false;
|
||||
settings.Keywords = "dnd;notifications;disturb;swaync;silence;";
|
||||
};
|
||||
|
||||
services.swaync = {
|
||||
enable = true;
|
||||
|
||||
@@ -409,13 +431,5 @@ in {
|
||||
|
||||
stylix.targets.swaync.enable = false;
|
||||
|
||||
# GTK 4.22 defaults to the Vulkan renderer, which compiles its graphics
|
||||
# pipelines lazily on the first frame. The control center has many distinct
|
||||
# visual elements, so the first open triggers a big burst of pipeline
|
||||
# compilation (a multi-second stall on a cold boot) while later opens reuse
|
||||
# the in-process cache. The GL (ngl) renderer avoids this: it stays
|
||||
# GPU-accelerated and benefits from Mesa's on-disk shader cache, so the
|
||||
# first open is as fast as the rest. Scoped to swaync so other GTK4 apps
|
||||
# keep the Vulkan renderer.
|
||||
systemd.user.services.swaync.Service.Environment = ["GSK_RENDERER=ngl"];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Use elio as the file picker for portal dialogs (browser "select file(s)" and
|
||||
# "save download to..."). The backend is xdg-desktop-portal-termfilechooser,
|
||||
# enabled system-wide in nixos/utils.nix (extraPortals + FileChooser routing).
|
||||
# "save download to...").
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
@@ -48,9 +47,6 @@
|
||||
fi
|
||||
fi
|
||||
'';
|
||||
# ghostty must run in the foreground instead of handing off to the
|
||||
# single-instance daemon, otherwise the wrapper returns before you've picked
|
||||
# anything and the portal reads an empty selection.
|
||||
termcmd = "ghostty --gtk-single-instance=false --title=termfilechooser -e";
|
||||
in {
|
||||
xdg.configFile."xdg-desktop-portal-termfilechooser/config".text = ''
|
||||
@@ -60,6 +56,6 @@ in {
|
||||
open_mode=suggested
|
||||
save_mode=suggested
|
||||
env=TERMCMD=${termcmd}
|
||||
PATH=${config.home.profileDirectory}/bin:/run/current-system/sw/bin:/run/wrappers/bin
|
||||
PATH=${config.home.profileDirectory}/bin:/run/current-system/sw/bin:/run/wrappers/bin
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
then barHeight / 2
|
||||
else config.theme.rounding;
|
||||
in {
|
||||
imports = [./desktop-actions.nix];
|
||||
imports = [./desktop-actions.nix ./scripts.nix];
|
||||
programs.tofi = {
|
||||
enable = true;
|
||||
settings = {
|
||||
|
||||
@@ -4,24 +4,6 @@
|
||||
...
|
||||
}: {
|
||||
xdg.desktopEntries = {
|
||||
focus-toggle = {
|
||||
name = "Focus Mode";
|
||||
exec = "${scripts.focus-toggle}/bin/focus-toggle";
|
||||
icon = "do-not-disturb-symbolic";
|
||||
comment = "Toggle focus mode";
|
||||
categories = ["System"];
|
||||
terminal = false;
|
||||
};
|
||||
|
||||
nightshift-toggle = {
|
||||
name = "Night Shift";
|
||||
exec = "${scripts.nightshift-toggle}/bin/nightshift-toggle";
|
||||
icon = "night-light-symbolic";
|
||||
comment = "Toggle night shift";
|
||||
categories = ["System"];
|
||||
terminal = false;
|
||||
};
|
||||
|
||||
mic-mute = {
|
||||
name = "Mute Microphone";
|
||||
exec = "${scripts.mic-mute}/bin/mic-mute";
|
||||
@@ -31,15 +13,6 @@
|
||||
terminal = false;
|
||||
};
|
||||
|
||||
lock = {
|
||||
name = "Lock";
|
||||
exec = "${pkgs.hyprlock}/bin/hyprlock";
|
||||
icon = "system-lock-screen-symbolic";
|
||||
comment = "Lock the screen";
|
||||
categories = ["System"];
|
||||
terminal = false;
|
||||
};
|
||||
|
||||
reboot = {
|
||||
name = "Reboot";
|
||||
exec = "systemctl reboot";
|
||||
@@ -49,6 +22,15 @@
|
||||
terminal = false;
|
||||
};
|
||||
|
||||
suspend = {
|
||||
name = "Suspend";
|
||||
exec = "systemctl suspend";
|
||||
icon = "system-suspend-symbolic";
|
||||
comment = "Suspend the system";
|
||||
categories = ["System"];
|
||||
terminal = false;
|
||||
};
|
||||
|
||||
shutdown = {
|
||||
name = "Shutdown";
|
||||
exec = "systemctl poweroff";
|
||||
@@ -67,15 +49,6 @@
|
||||
terminal = false;
|
||||
};
|
||||
|
||||
waybar-toggle = {
|
||||
name = "Toggle Waybar";
|
||||
exec = "${scripts.waybar-toggle}/bin/waybar-toggle";
|
||||
icon = "panel-applets-symbolic";
|
||||
comment = "Show or hide the status bar";
|
||||
categories = ["System"];
|
||||
terminal = false;
|
||||
};
|
||||
|
||||
wifi-toggle = {
|
||||
name = "Toggle Wi-Fi";
|
||||
exec = "${scripts.wifi-toggle}/bin/wifi-toggle";
|
||||
@@ -154,16 +127,6 @@
|
||||
settings.Keywords = "record;recording;screencast;video;";
|
||||
};
|
||||
|
||||
power-cycle = {
|
||||
name = "Power Profile";
|
||||
exec = "${scripts.power-cycle}/bin/power-cycle";
|
||||
icon = "power-profile-balanced-symbolic";
|
||||
comment = "Cycle power-saver / balanced / performance";
|
||||
categories = ["System"];
|
||||
terminal = false;
|
||||
settings.Keywords = "power;profile;performance;battery;";
|
||||
};
|
||||
|
||||
airplane-toggle = {
|
||||
name = "Airplane Mode";
|
||||
exec = "${scripts.airplane-toggle}/bin/airplane-toggle";
|
||||
@@ -194,16 +157,6 @@
|
||||
settings.Keywords = "icon;nerdfont;glyph;symbol;";
|
||||
};
|
||||
|
||||
caffeine-toggle = {
|
||||
name = "Keep Awake";
|
||||
exec = "${scripts.caffeine-toggle}/bin/caffeine-toggle";
|
||||
icon = "my-caffeine-on-symbolic";
|
||||
comment = "Pause or resume idle locking and suspend";
|
||||
categories = ["System"];
|
||||
terminal = false;
|
||||
settings.Keywords = "caffeine;idle;awake;inhibit;suspend;";
|
||||
};
|
||||
|
||||
logout = {
|
||||
name = "Logout";
|
||||
exec = "${pkgs.hyprland}/bin/hyprctl dispatch exit";
|
||||
@@ -213,16 +166,6 @@
|
||||
terminal = false;
|
||||
};
|
||||
|
||||
dnd-toggle = {
|
||||
name = "Do Not Disturb";
|
||||
exec = "${scripts.dnd-toggle}/bin/dnd-toggle";
|
||||
icon = "notifications-disabled-symbolic";
|
||||
comment = "Toggle Do Not Disturb";
|
||||
categories = ["System"];
|
||||
terminal = false;
|
||||
settings.Keywords = "dnd;notifications;disturb;swaync;silence;";
|
||||
};
|
||||
|
||||
vol-mute = {
|
||||
name = "Mute Volume";
|
||||
exec = "${scripts.vol-mute}/bin/vol-mute";
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# Picker scripts built on tofi's dmenu mode: emoji and Nerd Font icon input.
|
||||
{
|
||||
pkgs,
|
||||
config,
|
||||
scripts,
|
||||
...
|
||||
}: let
|
||||
tofiMenu = "${pkgs.tofi}/bin/tofi --horizontal false --anchor center --width 700 --height 500 --margin-top 0 --margin-left 0 --margin-right 0 --num-results 10";
|
||||
|
||||
nerdFontGlyphnames = pkgs.fetchurl {
|
||||
url = "https://raw.githubusercontent.com/ryanoasis/nerd-fonts/v3.4.0/glyphnames.json";
|
||||
sha256 = "sha256-4tENI/W/8L1vBnbpsB2XifzcZW3ntJiilVwncW6kQ5w=";
|
||||
};
|
||||
nerdFontIconList = pkgs.runCommand "nerd-font-icons.txt" {nativeBuildInputs = [pkgs.jq];} ''
|
||||
jq -r 'to_entries[] | select(.key != "METADATA") | "\(.value.char) \(.key)"' \
|
||||
${nerdFontGlyphnames} > "$out"
|
||||
'';
|
||||
in {
|
||||
scripts.emoji-picker = pkgs.writeShellScriptBin "emoji-picker" ''
|
||||
export PATH="${pkgs.wtype}/bin:${pkgs.wl-clipboard}/bin:$PATH"
|
||||
export BEMOJI_PICKER_CMD="${tofiMenu} --prompt-text 'emoji: '"
|
||||
# -t types the emoji, -c also copies it.
|
||||
exec ${pkgs.bemoji}/bin/bemoji -t -c
|
||||
'';
|
||||
|
||||
scripts.icon-picker = pkgs.writeShellScriptBin "icon-picker" ''
|
||||
selected=$(${tofiMenu} --font "${config.stylix.fonts.monospace.name}" --prompt-text 'icon: ' < ${nerdFontIconList})
|
||||
[ -z "$selected" ] || icon=$(printf '%s\n' "$selected" | awk '{print $1}')
|
||||
[ -n "''${icon:-}" ] || exit 0
|
||||
${pkgs.wtype}/bin/wtype "$icon"
|
||||
printf '%s' "$icon" | ${pkgs.wl-clipboard}/bin/wl-copy
|
||||
'';
|
||||
|
||||
home.packages = [scripts.emoji-picker scripts.icon-picker];
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
services.udiskie = {
|
||||
enable = true;
|
||||
notify = true;
|
||||
notify = false;
|
||||
automount = true;
|
||||
tray = "never";
|
||||
};
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
# battery-monitor: low-battery alerts for waybar/swaync.
|
||||
# Runs main once (triggered by the battery-monitor systemd timer).
|
||||
|
||||
POWER_SUPPLY_DIR="${POWER_SUPPLY_DIR:-/sys/class/power_supply}"
|
||||
STATE_FILE="${BATTERY_STATE_FILE:-/tmp/battery-alert-state}"
|
||||
CRIT_ID_FILE="${BATTERY_CRIT_ID_FILE:-/tmp/battery-crit-id}"
|
||||
|
||||
WARN_THRESHOLD=10
|
||||
CRIT_THRESHOLD=5
|
||||
|
||||
# Pure decision. Prints "<action>:<newstate>".
|
||||
# action ∈ noop | osd_warn | crit_show | crit_clear ; state ∈ none | warn10 | crit5
|
||||
battery_decide() {
|
||||
status=$1
|
||||
cap=$2
|
||||
prev=$3
|
||||
|
||||
if [ "$status" = "Discharging" ]; then
|
||||
if [ "$cap" -le "$CRIT_THRESHOLD" ]; then
|
||||
echo "crit_show:crit5"
|
||||
elif [ "$cap" -le "$WARN_THRESHOLD" ]; then
|
||||
if [ "$prev" = "crit5" ]; then
|
||||
echo "crit_clear:warn10"
|
||||
elif [ "$prev" = "warn10" ]; then
|
||||
echo "noop:warn10"
|
||||
else
|
||||
echo "osd_warn:warn10"
|
||||
fi
|
||||
else
|
||||
if [ "$prev" = "crit5" ]; then
|
||||
echo "crit_clear:none"
|
||||
else
|
||||
echo "noop:none"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if [ "$prev" = "crit5" ]; then
|
||||
echo "crit_clear:none"
|
||||
else
|
||||
echo "noop:none"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# First real battery (type == Battery, with status + capacity).
|
||||
resolve_battery() {
|
||||
for d in "$POWER_SUPPLY_DIR"/*; do
|
||||
[ -r "$d/type" ] || continue
|
||||
[ "$(cat "$d/type")" = "Battery" ] || continue
|
||||
[ -r "$d/status" ] && [ -r "$d/capacity" ] || continue
|
||||
printf '%s' "$d"
|
||||
return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
show_warn_osd() {
|
||||
waybar-osd " Low battery · $1%"
|
||||
}
|
||||
|
||||
show_crit_notification() {
|
||||
cap=$1
|
||||
id=$(cat "$CRIT_ID_FILE" 2>/dev/null || echo 0)
|
||||
newid=$(notify-send --print-id --replace-id="$id" \
|
||||
--urgency=critical --expire-time=0 \
|
||||
" Critical battery" "Battery at $cap%: plug in the charger")
|
||||
printf '%s' "$newid" >"$CRIT_ID_FILE"
|
||||
}
|
||||
|
||||
clear_crit_notification() {
|
||||
id=$(cat "$CRIT_ID_FILE" 2>/dev/null || echo 0)
|
||||
if [ "$id" != "0" ]; then
|
||||
gdbus call --session \
|
||||
--dest org.freedesktop.Notifications \
|
||||
--object-path /org/freedesktop/Notifications \
|
||||
--method org.freedesktop.Notifications.CloseNotification "$id" \
|
||||
>/dev/null 2>&1 || true
|
||||
fi
|
||||
rm -f "$CRIT_ID_FILE"
|
||||
}
|
||||
|
||||
main() {
|
||||
dir=$(resolve_battery) || exit 0
|
||||
|
||||
status=$(cat "$dir/status")
|
||||
cap=$(cat "$dir/capacity")
|
||||
prev=$(cat "$STATE_FILE" 2>/dev/null || echo none)
|
||||
|
||||
result=$(battery_decide "$status" "$cap" "$prev")
|
||||
action=${result%%:*}
|
||||
newstate=${result##*:}
|
||||
|
||||
case "$action" in
|
||||
osd_warn) show_warn_osd "$cap" ;;
|
||||
crit_show) show_crit_notification "$cap" ;;
|
||||
crit_clear) clear_crit_notification ;;
|
||||
noop) : ;;
|
||||
esac
|
||||
|
||||
printf '%s' "$newstate" >"$STATE_FILE"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -2,23 +2,13 @@
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}: let
|
||||
scripts = import ./scripts.nix {inherit pkgs config;};
|
||||
in {
|
||||
}: {
|
||||
imports = [
|
||||
./scripts
|
||||
./settings.nix
|
||||
./style.nix
|
||||
];
|
||||
|
||||
_module.args.scripts = scripts;
|
||||
_module.args.osdPath = scripts.osdPath;
|
||||
_module.args.networkScript = scripts.networkScript;
|
||||
_module.args.bluetoothScript = scripts.bluetoothScript;
|
||||
_module.args.volMuteScript = scripts.vol-mute;
|
||||
_module.args.caffeineToggleScript = scripts.caffeine-toggle;
|
||||
_module.args.osdStatusScript = scripts.waybar-osd-status;
|
||||
_module.args.nightshiftToggleScript = scripts.nightshift-toggle;
|
||||
|
||||
programs.waybar.enable = true;
|
||||
stylix.targets.waybar.enable = false;
|
||||
|
||||
@@ -26,35 +16,19 @@ in {
|
||||
[
|
||||
playerctl
|
||||
pavucontrol
|
||||
blueman
|
||||
hyprsunset
|
||||
]
|
||||
++ (with scripts; [waybar-osd waybar-osd-status battery-monitor vol-up vol-down vol-mute mic-mute bright-up bright-down nightshift-toggle focus-toggle waybar-toggle wifi-toggle bluetooth-toggle dnd-toggle output-cycle input-cycle color-pick screenshot-edit record-toggle power-cycle airplane-toggle clipboard-menu emoji-picker caffeine-toggle mic-status]);
|
||||
++ (with config.scripts; [waybar-osd waybar-osd-status vol-up vol-down vol-mute mic-mute bright-up bright-down waybar-toggle wifi-toggle bluetooth-toggle output-cycle input-cycle color-pick screenshot-edit record-toggle airplane-toggle mic-status]);
|
||||
|
||||
# Poll battery level every 30s; battery-monitor no-ops when no battery exists.
|
||||
systemd.user.services.battery-monitor = {
|
||||
Unit.Description = "Low-battery OSD and critical notification monitor";
|
||||
Service = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${scripts.battery-monitor}/bin/battery-monitor";
|
||||
};
|
||||
xdg.desktopEntries.waybar-toggle = {
|
||||
name = "Toggle Waybar";
|
||||
exec = "${config.scripts.waybar-toggle}/bin/waybar-toggle";
|
||||
icon = "panel-applets-symbolic";
|
||||
comment = "Show or hide the status bar";
|
||||
categories = ["System"];
|
||||
terminal = false;
|
||||
};
|
||||
|
||||
systemd.user.timers.battery-monitor = {
|
||||
Unit.Description = "Poll battery level for low-battery alerts";
|
||||
Timer = {
|
||||
OnBootSec = "1min";
|
||||
OnUnitActiveSec = "30s";
|
||||
};
|
||||
Install.WantedBy = ["timers.target"];
|
||||
};
|
||||
|
||||
# Prevent blueman-applet from auto-starting via XDG autostart (we use custom/bluetooth module instead)
|
||||
xdg.configFile."autostart/blueman.desktop".text = ''
|
||||
[Desktop Entry]
|
||||
Hidden=true
|
||||
'';
|
||||
|
||||
wayland.windowManager.hyprland.settings.exec-once = [
|
||||
"waybar"
|
||||
];
|
||||
|
||||
@@ -1,456 +0,0 @@
|
||||
{
|
||||
pkgs,
|
||||
config,
|
||||
}: let
|
||||
waybar-osd = pkgs.writeShellApplication {
|
||||
name = "waybar-osd";
|
||||
runtimeInputs = with pkgs; [procps coreutils];
|
||||
text = ''
|
||||
printf '%s' "$1" > "$XDG_RUNTIME_DIR/waybar-osd"
|
||||
pkill -f -RTMIN+8 '^waybar$' 2>/dev/null || true
|
||||
'';
|
||||
};
|
||||
|
||||
waybar-osd-status = pkgs.writeShellApplication {
|
||||
name = "waybar-osd-status";
|
||||
runtimeInputs = with pkgs; [coreutils];
|
||||
text = ''
|
||||
file="$XDG_RUNTIME_DIR/waybar-osd"
|
||||
[ -f "$file" ] || exit 1
|
||||
mtime=$(stat -c %Y "$file" 2>/dev/null) || exit 1
|
||||
age=$(( $(date +%s) - mtime ))
|
||||
if [ "$age" -ge 3 ]; then
|
||||
rm -f "$file"
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
# battery-monitor: low-battery alerts, run by the systemd user timer.
|
||||
battery-monitor = pkgs.writeShellApplication {
|
||||
name = "battery-monitor";
|
||||
runtimeInputs = with pkgs; [waybar-osd libnotify glib coreutils];
|
||||
text = builtins.readFile ./battery-monitor.sh;
|
||||
};
|
||||
|
||||
updateOsd = ''
|
||||
${waybar-osd}/bin/waybar-osd "$OSD_TEXT"
|
||||
'';
|
||||
|
||||
volGetText = ''
|
||||
VOL_RAW=$(${pkgs.wireplumber}/bin/wpctl get-volume @DEFAULT_SINK@)
|
||||
VOL=$(printf '%s' "$VOL_RAW" | awk '{printf "%d", $2*100}')
|
||||
if printf '%s' "$VOL_RAW" | grep -q MUTED; then
|
||||
OSD_TEXT=" $VOL%"
|
||||
elif [ "$VOL" -lt 33 ]; then
|
||||
OSD_TEXT=" $VOL%"
|
||||
elif [ "$VOL" -lt 66 ]; then
|
||||
OSD_TEXT=" $VOL%"
|
||||
else
|
||||
OSD_TEXT=" $VOL%"
|
||||
fi
|
||||
'';
|
||||
|
||||
brightGetText = ''
|
||||
BRIGHT=$(${pkgs.brightnessctl}/bin/brightnessctl -m | awk -F, '{print int($5)}')
|
||||
if [ "$BRIGHT" -lt 33 ]; then
|
||||
OSD_TEXT=" $BRIGHT%"
|
||||
elif [ "$BRIGHT" -lt 66 ]; then
|
||||
OSD_TEXT=" $BRIGHT%"
|
||||
else
|
||||
OSD_TEXT=" $BRIGHT%"
|
||||
fi
|
||||
'';
|
||||
|
||||
# Resolve $src: the default source, otherwise the first available source.
|
||||
# Some machines have no default audio source (@DEFAULT_AUDIO_SOURCE@ unresolved).
|
||||
micSource = ''
|
||||
src=$(${pkgs.wireplumber}/bin/wpctl inspect @DEFAULT_AUDIO_SOURCE@ 2>/dev/null | sed -n 's/^id \([0-9]*\),.*/\1/p')
|
||||
if [ -z "$src" ]; then
|
||||
src=$(${pkgs.pipewire}/bin/pw-dump | ${pkgs.jq}/bin/jq -r \
|
||||
'[.[] | select(.info.props."media.class"=="Audio/Source") | .id] | .[0] // empty')
|
||||
fi
|
||||
'';
|
||||
|
||||
# tofi in vertical dmenu mode: the global config is a 36px-tall horizontal
|
||||
# launcher, unusable for a list, so override the geometry here.
|
||||
tofiMenu = "${pkgs.tofi}/bin/tofi --horizontal false --anchor center --width 700 --height 500 --margin-top 0 --margin-left 0 --margin-right 0 --num-results 10";
|
||||
|
||||
# Name -> glyph table maintained by the nerd-fonts project itself, pinned to
|
||||
# the same release as the nerd-fonts packages in nixos/fonts.nix.
|
||||
nerdFontGlyphnames = pkgs.fetchurl {
|
||||
url = "https://raw.githubusercontent.com/ryanoasis/nerd-fonts/v3.4.0/glyphnames.json";
|
||||
sha256 = "sha256-4tENI/W/8L1vBnbpsB2XifzcZW3ntJiilVwncW6kQ5w=";
|
||||
};
|
||||
nerdFontIconList = pkgs.runCommand "nerd-font-icons.txt" {nativeBuildInputs = [pkgs.jq];} ''
|
||||
jq -r 'to_entries[] | select(.key != "METADATA") | "\(.value.char) \(.key)"' \
|
||||
${nerdFontGlyphnames} > "$out"
|
||||
'';
|
||||
osdPath = "$XDG_RUNTIME_DIR/waybar-osd";
|
||||
in {
|
||||
inherit waybar-osd waybar-osd-status battery-monitor osdPath;
|
||||
|
||||
bluetoothScript = pkgs.writeShellScript "waybar-bluetooth" ''
|
||||
jq=${pkgs.jq}/bin/jq
|
||||
nl=$'\n'
|
||||
# bluetoothctl hangs without a controller by default → always bounded by timeout.
|
||||
bt() { timeout 3 ${pkgs.bluez}/bin/bluetoothctl "$@" 2>/dev/null; }
|
||||
|
||||
powered=$(bt show | awk '/Powered:/ { print $2; exit }')
|
||||
if [ "$powered" != "yes" ]; then
|
||||
"$jq" -cn '{ text: "", class: "off", tooltip: "Bluetooth off" }'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
tip=""
|
||||
add_tip() { tip="''${tip:+$tip$nl}$1"; }
|
||||
|
||||
count=0
|
||||
for mac in $(bt devices Connected | awk '{ print $2 }'); do
|
||||
count=$((count + 1))
|
||||
info=$(bt info "$mac")
|
||||
name=$(printf '%s' "$info" | sed -n 's/^[[:space:]]*Name: //p' | head -1)
|
||||
[ -z "$name" ] && name="$mac"
|
||||
batt=$(printf '%s' "$info" | sed -n 's/.*Battery Percentage:.*(\([0-9][0-9]*\)).*/\1/p' | head -1)
|
||||
add_tip " $name''${batt:+ · $batt%}"
|
||||
done
|
||||
|
||||
if [ "$count" -gt 0 ]; then
|
||||
"$jq" -cn --arg tooltip "$tip" '{ text: "", class: "connected", tooltip: $tooltip }'
|
||||
else
|
||||
"$jq" -cn '{ text: "", class: "on", tooltip: "Bluetooth on" }'
|
||||
fi
|
||||
'';
|
||||
|
||||
networkScript = pkgs.writeShellScript "waybar-network" ''
|
||||
nmcli=${pkgs.networkmanager}/bin/nmcli
|
||||
ip=${pkgs.iproute2}/bin/ip
|
||||
jq=${pkgs.jq}/bin/jq
|
||||
nl=$'\n'
|
||||
|
||||
text=""
|
||||
tip=""
|
||||
class="disconnected"
|
||||
add_seg() { text="''${text:+$text }$1"; }
|
||||
add_tip() { tip="''${tip:+$tip$nl}$1"; }
|
||||
|
||||
# ── Ethernet ────────────────────────────────────────────────
|
||||
eth=$("$nmcli" -t -f DEVICE,TYPE,STATE device status 2>/dev/null \
|
||||
| awk -F: '$2 == "ethernet" && $3 == "connected" { print $1; exit }')
|
||||
if [ -n "$eth" ]; then
|
||||
eth_ip=$("$ip" -4 -br addr show "$eth" 2>/dev/null | awk '{ print $3 }' | cut -d/ -f1)
|
||||
add_seg ""
|
||||
class="ethernet"
|
||||
add_tip " Ethernet''${eth_ip:+ · $eth_ip}"
|
||||
fi
|
||||
|
||||
# ── Wi-Fi ───────────────────────────────────────────────────
|
||||
wifi=$("$nmcli" -t -f ACTIVE,SSID,SIGNAL device wifi 2>/dev/null | grep -m1 '^yes:')
|
||||
if [ -n "$wifi" ]; then
|
||||
ssid=$(printf '%s' "$wifi" | cut -d: -f2)
|
||||
signal=$(printf '%s' "$wifi" | cut -d: -f3)
|
||||
if [ "''${signal:-0}" -ge 80 ]; then icon=""
|
||||
elif [ "''${signal:-0}" -ge 60 ]; then icon=""
|
||||
elif [ "''${signal:-0}" -ge 40 ]; then icon=""
|
||||
elif [ "''${signal:-0}" -ge 20 ]; then icon=""
|
||||
else icon=""
|
||||
fi
|
||||
add_seg "$icon"
|
||||
[ "$class" = "disconnected" ] && class="wifi"
|
||||
add_tip "$icon ''${ssid:-Wi-Fi}''${signal:+ · $signal%}"
|
||||
fi
|
||||
|
||||
# ── VPN (nmcli, fallback interfaces) ────────────────────────
|
||||
vpn=$("$nmcli" -t -f TYPE,NAME connection show --active 2>/dev/null \
|
||||
| awk -F: '$1 == "vpn" || $1 == "wireguard" { print $2; exit }')
|
||||
if [ -z "$vpn" ]; then
|
||||
vpn=$("$ip" -br link show 2>/dev/null \
|
||||
| awk '($2 == "UP" || $2 == "UNKNOWN") && $1 ~ /^(tun|wg|proton|ppp)/ { sub(/@.*/, "", $1); print $1; exit }')
|
||||
fi
|
||||
if [ -n "$vpn" ]; then
|
||||
add_seg ""
|
||||
add_tip " VPN · $vpn"
|
||||
fi
|
||||
|
||||
if [ -z "$text" ]; then
|
||||
text=""
|
||||
tip="Disconnected"
|
||||
fi
|
||||
|
||||
"$jq" -cn --arg text "$text" --arg class "$class" --arg tooltip "$tip" \
|
||||
'{ text: $text, class: $class, tooltip: $tooltip }'
|
||||
'';
|
||||
|
||||
vol-up = pkgs.writeShellScriptBin "vol-up" ''
|
||||
${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_SINK@ 5%+ --limit 1.0
|
||||
${volGetText}
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
vol-down = pkgs.writeShellScriptBin "vol-down" ''
|
||||
${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_SINK@ 5%-
|
||||
${volGetText}
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
vol-mute = pkgs.writeShellScriptBin "vol-mute" ''
|
||||
${pkgs.wireplumber}/bin/wpctl set-mute @DEFAULT_SINK@ toggle
|
||||
${volGetText}
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
mic-mute = pkgs.writeShellScriptBin "mic-mute" ''
|
||||
wpctl=${pkgs.wireplumber}/bin/wpctl
|
||||
${micSource}
|
||||
[ -z "$src" ] && exit 0
|
||||
|
||||
"$wpctl" set-mute "$src" toggle
|
||||
if "$wpctl" get-volume "$src" | grep -q MUTED; then
|
||||
OSD_TEXT=" Muted"
|
||||
else
|
||||
OSD_TEXT=" Live"
|
||||
fi
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
mic-status = pkgs.writeShellScriptBin "mic-status" ''
|
||||
${micSource}
|
||||
if [ -n "$src" ] && ${pkgs.wireplumber}/bin/wpctl get-volume "$src" | grep -q MUTED; then
|
||||
echo true
|
||||
else
|
||||
echo false
|
||||
fi
|
||||
'';
|
||||
|
||||
bright-up = pkgs.writeShellScriptBin "bright-up" ''
|
||||
${pkgs.brightnessctl}/bin/brightnessctl set 5%+
|
||||
${brightGetText}
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
bright-down = pkgs.writeShellScriptBin "bright-down" ''
|
||||
${pkgs.brightnessctl}/bin/brightnessctl set 5%-
|
||||
${brightGetText}
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
wifi-toggle = pkgs.writeShellScriptBin "wifi-toggle" ''
|
||||
if ${pkgs.networkmanager}/bin/nmcli radio wifi | grep -q enabled; then
|
||||
${pkgs.networkmanager}/bin/nmcli radio wifi off
|
||||
else
|
||||
${pkgs.networkmanager}/bin/nmcli radio wifi on
|
||||
fi
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
bluetooth-toggle = pkgs.writeShellScriptBin "bluetooth-toggle" ''
|
||||
if ${pkgs.bluez}/bin/bluetoothctl show | grep -q "Powered: yes"; then
|
||||
${pkgs.bluez}/bin/bluetoothctl power off
|
||||
else
|
||||
${pkgs.bluez}/bin/bluetoothctl power on
|
||||
fi
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
waybar-toggle = pkgs.writeShellScriptBin "waybar-toggle" ''
|
||||
if pidof waybar > /dev/null; then
|
||||
pkill waybar
|
||||
else
|
||||
hyprctl dispatch exec waybar
|
||||
fi
|
||||
'';
|
||||
|
||||
nightshift-toggle = pkgs.writeShellScriptBin "nightshift-toggle" ''
|
||||
if ${pkgs.procps}/bin/pidof "hyprsunset" > /dev/null; then
|
||||
pkill hyprsunset
|
||||
OSD_TEXT=" Night Shift Off"
|
||||
else
|
||||
${pkgs.hyprsunset}/bin/hyprsunset -t 4500 &
|
||||
OSD_TEXT=" Night Shift On"
|
||||
fi
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
focus-toggle = pkgs.writeShellScriptBin "focus-toggle" ''
|
||||
if test -f "$XDG_RUNTIME_DIR/hypr-focus-mode"; then
|
||||
rm "$XDG_RUNTIME_DIR/hypr-focus-mode"
|
||||
OSD_TEXT=" Focus Off"
|
||||
${updateOsd}
|
||||
${pkgs.hyprland}/bin/hyprctl reload
|
||||
${pkgs.hyprland}/bin/hyprctl dispatch exec waybar
|
||||
else
|
||||
touch "$XDG_RUNTIME_DIR/hypr-focus-mode"
|
||||
OSD_TEXT=" Focus On"
|
||||
${updateOsd}
|
||||
${pkgs.procps}/bin/pkill waybar || true
|
||||
${pkgs.hyprland}/bin/hyprctl keyword animations:enabled false
|
||||
${pkgs.hyprland}/bin/hyprctl keyword general:gaps_in 0
|
||||
${pkgs.hyprland}/bin/hyprctl keyword general:gaps_out 0
|
||||
${pkgs.hyprland}/bin/hyprctl keyword decoration:active_opacity 1
|
||||
${pkgs.hyprland}/bin/hyprctl keyword decoration:inactive_opacity 1
|
||||
${pkgs.hyprland}/bin/hyprctl keyword decoration:rounding 0
|
||||
fi
|
||||
'';
|
||||
|
||||
dnd-toggle = pkgs.writeShellScriptBin "dnd-toggle" ''
|
||||
state=$(${pkgs.swaynotificationcenter}/bin/swaync-client -d)
|
||||
if [ "$state" = "true" ]; then
|
||||
OSD_TEXT=" Do Not Disturb On"
|
||||
else
|
||||
OSD_TEXT=" Do Not Disturb Off"
|
||||
fi
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
output-cycle = pkgs.writeShellScriptBin "output-cycle" ''
|
||||
wpctl=${pkgs.wireplumber}/bin/wpctl
|
||||
jq=${pkgs.jq}/bin/jq
|
||||
pwdump=${pkgs.pipewire}/bin/pw-dump
|
||||
|
||||
# Sink IDs in pw-dump order.
|
||||
ids=($("$pwdump" | "$jq" -r '.[] | select(.info.props."media.class"=="Audio/Sink") | .id'))
|
||||
n=''${#ids[@]}
|
||||
[ "$n" -eq 0 ] && exit 0
|
||||
|
||||
cur=$("$wpctl" inspect @DEFAULT_AUDIO_SINK@ 2>/dev/null | sed -n 's/^id \([0-9]*\),.*/\1/p')
|
||||
|
||||
next_index=0
|
||||
for i in "''${!ids[@]}"; do
|
||||
if [ "''${ids[$i]}" = "$cur" ]; then
|
||||
next_index=$(( (i + 1) % n ))
|
||||
break
|
||||
fi
|
||||
done
|
||||
next=''${ids[$next_index]}
|
||||
|
||||
"$wpctl" set-default "$next"
|
||||
desc=$("$pwdump" | "$jq" -r --argjson id "$next" '.[] | select(.id==$id) | .info.props."node.description"')
|
||||
OSD_TEXT=" ''${desc:-Output}"
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
input-cycle = pkgs.writeShellScriptBin "input-cycle" ''
|
||||
wpctl=${pkgs.wireplumber}/bin/wpctl
|
||||
jq=${pkgs.jq}/bin/jq
|
||||
pwdump=${pkgs.pipewire}/bin/pw-dump
|
||||
|
||||
# Source IDs in pw-dump order.
|
||||
ids=($("$pwdump" | "$jq" -r '.[] | select(.info.props."media.class"=="Audio/Source") | .id'))
|
||||
n=''${#ids[@]}
|
||||
[ "$n" -eq 0 ] && exit 0
|
||||
|
||||
cur=$("$wpctl" inspect @DEFAULT_AUDIO_SOURCE@ 2>/dev/null | sed -n 's/^id \([0-9]*\),.*/\1/p')
|
||||
|
||||
next_index=0
|
||||
for i in "''${!ids[@]}"; do
|
||||
if [ "''${ids[$i]}" = "$cur" ]; then
|
||||
next_index=$(( (i + 1) % n ))
|
||||
break
|
||||
fi
|
||||
done
|
||||
next=''${ids[$next_index]}
|
||||
|
||||
"$wpctl" set-default "$next"
|
||||
desc=$("$pwdump" | "$jq" -r --argjson id "$next" '.[] | select(.id==$id) | .info.props."node.description"')
|
||||
OSD_TEXT=" ''${desc:-Input}"
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
color-pick = pkgs.writeShellScriptBin "color-pick" ''
|
||||
# hyprpicker -a already copies the color to the clipboard.
|
||||
color=$(${pkgs.hyprpicker}/bin/hyprpicker -a 2>/dev/null)
|
||||
if [ -n "$color" ]; then
|
||||
OSD_TEXT=" $color"
|
||||
else
|
||||
OSD_TEXT=" Cancelled"
|
||||
fi
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
screenshot-edit = pkgs.writeShellScriptBin "screenshot-edit" ''
|
||||
${pkgs.hyprshot}/bin/hyprshot -m region --raw 2>/dev/null \
|
||||
| ${pkgs.satty}/bin/satty --filename -
|
||||
'';
|
||||
|
||||
record-toggle = pkgs.writeShellScriptBin "record-toggle" ''
|
||||
if pgrep -x wf-recorder >/dev/null; then
|
||||
# -INT lets wf-recorder finalize the file cleanly.
|
||||
pkill -INT -x wf-recorder
|
||||
OSD_TEXT=" Recording saved"
|
||||
else
|
||||
dir="$HOME/Videos"
|
||||
mkdir -p "$dir"
|
||||
file="$dir/rec-$(date +%Y%m%d-%H%M%S).mp4"
|
||||
${pkgs.wf-recorder}/bin/wf-recorder -f "$file" >/dev/null 2>&1 &
|
||||
OSD_TEXT=" Recording…"
|
||||
fi
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
power-cycle = pkgs.writeShellScriptBin "power-cycle" ''
|
||||
ppd=${pkgs.power-profiles-daemon}/bin/powerprofilesctl
|
||||
cur=$("$ppd" get)
|
||||
case "$cur" in
|
||||
power-saver) next=balanced; icon="" ;;
|
||||
balanced) next=performance; icon="" ;;
|
||||
performance) next=power-saver; icon="" ;;
|
||||
*) next=balanced; icon="" ;;
|
||||
esac
|
||||
"$ppd" set "$next"
|
||||
OSD_TEXT="$icon ''${next^}"
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
airplane-toggle = pkgs.writeShellScriptBin "airplane-toggle" ''
|
||||
# Reuse nmcli + bluetoothctl (unprivileged) instead of rfkill.
|
||||
on=false
|
||||
nmcli radio wifi 2>/dev/null | grep -q enabled && on=true
|
||||
bluetoothctl show 2>/dev/null | grep -q "Powered: yes" && on=true
|
||||
if $on; then
|
||||
nmcli radio wifi off 2>/dev/null
|
||||
bluetoothctl power off >/dev/null 2>&1 || true
|
||||
OSD_TEXT=" Airplane On"
|
||||
else
|
||||
nmcli radio wifi on 2>/dev/null
|
||||
bluetoothctl power on >/dev/null 2>&1 || true
|
||||
OSD_TEXT=" Airplane Off"
|
||||
fi
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
clipboard-menu = pkgs.writeShellScriptBin "clipboard-menu" ''
|
||||
list=$(${pkgs.cliphist}/bin/cliphist list)
|
||||
selected=$(printf '%s\n' "$list" | cut -f2- | ${tofiMenu} --prompt-text "Clipboard: ")
|
||||
[ -n "$selected" ] && printf '%s\n' "$list" \
|
||||
| awk -F'\t' -v s="$selected" '$2 == s {print; exit}' \
|
||||
| ${pkgs.cliphist}/bin/cliphist decode \
|
||||
| ${pkgs.wl-clipboard}/bin/wl-copy
|
||||
'';
|
||||
|
||||
emoji-picker = pkgs.writeShellScriptBin "emoji-picker" ''
|
||||
export PATH="${pkgs.wtype}/bin:${pkgs.wl-clipboard}/bin:$PATH"
|
||||
export BEMOJI_PICKER_CMD="${tofiMenu} --prompt-text 'emoji: '"
|
||||
# -t types the emoji (wtype), -c also copies it.
|
||||
exec ${pkgs.bemoji}/bin/bemoji -t -c
|
||||
'';
|
||||
|
||||
icon-picker = pkgs.writeShellScriptBin "icon-picker" ''
|
||||
selected=$(${tofiMenu} --font "${config.stylix.fonts.monospace.name}" --prompt-text 'icon: ' < ${nerdFontIconList})
|
||||
[ -z "$selected" ] || icon=$(printf '%s\n' "$selected" | awk '{print $1}')
|
||||
[ -n "''${icon:-}" ] || exit 0
|
||||
${pkgs.wtype}/bin/wtype "$icon"
|
||||
printf '%s' "$icon" | ${pkgs.wl-clipboard}/bin/wl-copy
|
||||
'';
|
||||
|
||||
caffeine-toggle = pkgs.writeShellScriptBin "caffeine-toggle" ''
|
||||
# Pause hypridle (stay awake) or resume it.
|
||||
if systemctl --user is-active --quiet hypridle; then
|
||||
systemctl --user stop hypridle
|
||||
OSD_TEXT=" Keep Awake On"
|
||||
else
|
||||
systemctl --user start hypridle
|
||||
OSD_TEXT=" Keep Awake Off"
|
||||
fi
|
||||
${updateOsd}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
pkgs,
|
||||
scripts,
|
||||
...
|
||||
}: let
|
||||
volGetText = ''
|
||||
VOL_RAW=$(${pkgs.wireplumber}/bin/wpctl get-volume @DEFAULT_SINK@)
|
||||
VOL=$(printf '%s' "$VOL_RAW" | awk '{printf "%d", $2*100}')
|
||||
if printf '%s' "$VOL_RAW" | grep -q MUTED; then
|
||||
OSD_TEXT=" $VOL%"
|
||||
elif [ "$VOL" -lt 33 ]; then
|
||||
OSD_TEXT=" $VOL%"
|
||||
elif [ "$VOL" -lt 66 ]; then
|
||||
OSD_TEXT=" $VOL%"
|
||||
else
|
||||
OSD_TEXT=" $VOL%"
|
||||
fi
|
||||
'';
|
||||
|
||||
# Resolve $src: the default source, otherwise the first available source.
|
||||
# Some machines have no default audio source (@DEFAULT_AUDIO_SOURCE@ unresolved).
|
||||
micSource = ''
|
||||
src=$(${pkgs.wireplumber}/bin/wpctl inspect @DEFAULT_AUDIO_SOURCE@ 2>/dev/null | sed -n 's/^id \([0-9]*\),.*/\1/p')
|
||||
if [ -z "$src" ]; then
|
||||
src=$(${pkgs.pipewire}/bin/pw-dump | ${pkgs.jq}/bin/jq -r \
|
||||
'[.[] | select(.info.props."media.class"=="Audio/Source") | .id] | .[0] // empty')
|
||||
fi
|
||||
'';
|
||||
|
||||
updateOsd = ''
|
||||
${scripts.waybar-osd}/bin/waybar-osd "$OSD_TEXT"
|
||||
'';
|
||||
in {
|
||||
scripts.vol-up = pkgs.writeShellScriptBin "vol-up" ''
|
||||
${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_SINK@ 5%+ --limit 1.0
|
||||
${volGetText}
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
scripts.vol-down = pkgs.writeShellScriptBin "vol-down" ''
|
||||
${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_SINK@ 5%-
|
||||
${volGetText}
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
scripts.vol-mute = pkgs.writeShellScriptBin "vol-mute" ''
|
||||
${pkgs.wireplumber}/bin/wpctl set-mute @DEFAULT_SINK@ toggle
|
||||
${volGetText}
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
scripts.mic-mute = pkgs.writeShellScriptBin "mic-mute" ''
|
||||
wpctl=${pkgs.wireplumber}/bin/wpctl
|
||||
${micSource}
|
||||
[ -z "$src" ] && exit 0
|
||||
|
||||
"$wpctl" set-mute "$src" toggle
|
||||
if "$wpctl" get-volume "$src" | grep -q MUTED; then
|
||||
OSD_TEXT=" Muted"
|
||||
else
|
||||
OSD_TEXT=" Live"
|
||||
fi
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
scripts.mic-status = pkgs.writeShellScriptBin "mic-status" ''
|
||||
${micSource}
|
||||
if [ -n "$src" ] && ${pkgs.wireplumber}/bin/wpctl get-volume "$src" | grep -q MUTED; then
|
||||
echo true
|
||||
else
|
||||
echo false
|
||||
fi
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
pkgs,
|
||||
scripts,
|
||||
...
|
||||
}: let
|
||||
updateOsd = ''
|
||||
${scripts.waybar-osd}/bin/waybar-osd "$OSD_TEXT"
|
||||
'';
|
||||
in {
|
||||
scripts.bluetoothScript = pkgs.writeShellScript "waybar-bluetooth" ''
|
||||
jq=${pkgs.jq}/bin/jq
|
||||
nl=$'\n'
|
||||
# bluetoothctl hangs without a controller by default → always bounded by timeout.
|
||||
bt() { timeout 3 ${pkgs.bluez}/bin/bluetoothctl "$@" 2>/dev/null; }
|
||||
|
||||
powered=$(bt show | awk '/Powered:/ { print $2; exit }')
|
||||
if [ "$powered" != "yes" ]; then
|
||||
"$jq" -cn '{ text: "", class: "off", tooltip: "Bluetooth off" }'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
tip=""
|
||||
add_tip() { tip="''${tip:+$tip$nl}$1"; }
|
||||
|
||||
count=0
|
||||
for mac in $(bt devices Connected | awk '{ print $2 }'); do
|
||||
count=$((count + 1))
|
||||
info=$(bt info "$mac")
|
||||
name=$(printf '%s' "$info" | sed -n 's/^[[:space:]]*Name: //p' | head -1)
|
||||
[ -z "$name" ] && name="$mac"
|
||||
batt=$(printf '%s' "$info" | sed -n 's/.*Battery Percentage:.*(\([0-9][0-9]*\)).*/\1/p' | head -1)
|
||||
add_tip " $name''${batt:+ · $batt%}"
|
||||
done
|
||||
|
||||
if [ "$count" -gt 0 ]; then
|
||||
"$jq" -cn --arg tooltip "$tip" '{ text: "", class: "connected", tooltip: $tooltip }'
|
||||
else
|
||||
"$jq" -cn '{ text: "", class: "on", tooltip: "Bluetooth on" }'
|
||||
fi
|
||||
'';
|
||||
|
||||
scripts.bluetooth-toggle = pkgs.writeShellScriptBin "bluetooth-toggle" ''
|
||||
if ${pkgs.bluez}/bin/bluetoothctl show | grep -q "Powered: yes"; then
|
||||
${pkgs.bluez}/bin/bluetoothctl power off
|
||||
else
|
||||
${pkgs.bluez}/bin/bluetoothctl power on
|
||||
fi
|
||||
${updateOsd}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
pkgs,
|
||||
scripts,
|
||||
...
|
||||
}: let
|
||||
brightGetText = ''
|
||||
BRIGHT=$(${pkgs.brightnessctl}/bin/brightnessctl -m | awk -F, '{print int($5)}')
|
||||
if [ "$BRIGHT" -lt 33 ]; then
|
||||
OSD_TEXT=" $BRIGHT%"
|
||||
elif [ "$BRIGHT" -lt 66 ]; then
|
||||
OSD_TEXT=" $BRIGHT%"
|
||||
else
|
||||
OSD_TEXT=" $BRIGHT%"
|
||||
fi
|
||||
'';
|
||||
|
||||
updateOsd = ''
|
||||
${scripts.waybar-osd}/bin/waybar-osd "$OSD_TEXT"
|
||||
'';
|
||||
in {
|
||||
scripts.bright-up = pkgs.writeShellScriptBin "bright-up" ''
|
||||
${pkgs.brightnessctl}/bin/brightnessctl set 5%+
|
||||
${brightGetText}
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
scripts.bright-down = pkgs.writeShellScriptBin "bright-down" ''
|
||||
${pkgs.brightnessctl}/bin/brightnessctl set 5%-
|
||||
${brightGetText}
|
||||
${updateOsd}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
imports = [
|
||||
./osd.nix
|
||||
./bluetooth.nix
|
||||
./network.nix
|
||||
./audio.nix
|
||||
./brightness.nix
|
||||
./devices.nix
|
||||
./misc.nix
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
pkgs,
|
||||
scripts,
|
||||
...
|
||||
}: let
|
||||
updateOsd = ''
|
||||
${scripts.waybar-osd}/bin/waybar-osd "$OSD_TEXT"
|
||||
'';
|
||||
in {
|
||||
scripts.output-cycle = pkgs.writeShellScriptBin "output-cycle" ''
|
||||
wpctl=${pkgs.wireplumber}/bin/wpctl
|
||||
jq=${pkgs.jq}/bin/jq
|
||||
pwdump=${pkgs.pipewire}/bin/pw-dump
|
||||
|
||||
# Sink IDs in pw-dump order.
|
||||
ids=($("$pwdump" | "$jq" -r '.[] | select(.info.props."media.class"=="Audio/Sink") | .id'))
|
||||
n=''${#ids[@]}
|
||||
[ "$n" -eq 0 ] && exit 0
|
||||
|
||||
cur=$("$wpctl" inspect @DEFAULT_AUDIO_SINK@ 2>/dev/null | sed -n 's/^id \([0-9]*\),.*/\1/p')
|
||||
|
||||
next_index=0
|
||||
for i in "''${!ids[@]}"; do
|
||||
if [ "''${ids[$i]}" = "$cur" ]; then
|
||||
next_index=$(( (i + 1) % n ))
|
||||
break
|
||||
fi
|
||||
done
|
||||
next=''${ids[$next_index]}
|
||||
|
||||
"$wpctl" set-default "$next"
|
||||
desc=$("$pwdump" | "$jq" -r --argjson id "$next" '.[] | select(.id==$id) | .info.props."node.description"')
|
||||
OSD_TEXT=" ''${desc:-Output}"
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
scripts.input-cycle = pkgs.writeShellScriptBin "input-cycle" ''
|
||||
wpctl=${pkgs.wireplumber}/bin/wpctl
|
||||
jq=${pkgs.jq}/bin/jq
|
||||
pwdump=${pkgs.pipewire}/bin/pw-dump
|
||||
|
||||
# Source IDs in pw-dump order.
|
||||
ids=($("$pwdump" | "$jq" -r '.[] | select(.info.props."media.class"=="Audio/Source") | .id'))
|
||||
n=''${#ids[@]}
|
||||
[ "$n" -eq 0 ] && exit 0
|
||||
|
||||
cur=$("$wpctl" inspect @DEFAULT_AUDIO_SOURCE@ 2>/dev/null | sed -n 's/^id \([0-9]*\),.*/\1/p')
|
||||
|
||||
next_index=0
|
||||
for i in "''${!ids[@]}"; do
|
||||
if [ "''${ids[$i]}" = "$cur" ]; then
|
||||
next_index=$(( (i + 1) % n ))
|
||||
break
|
||||
fi
|
||||
done
|
||||
next=''${ids[$next_index]}
|
||||
|
||||
"$wpctl" set-default "$next"
|
||||
desc=$("$pwdump" | "$jq" -r --argjson id "$next" '.[] | select(.id==$id) | .info.props."node.description"')
|
||||
OSD_TEXT=" ''${desc:-Input}"
|
||||
${updateOsd}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
pkgs,
|
||||
scripts,
|
||||
...
|
||||
}: let
|
||||
updateOsd = ''
|
||||
${scripts.waybar-osd}/bin/waybar-osd "$OSD_TEXT"
|
||||
'';
|
||||
in {
|
||||
scripts.waybar-toggle = pkgs.writeShellScriptBin "waybar-toggle" ''
|
||||
if pidof waybar > /dev/null; then
|
||||
pkill waybar
|
||||
else
|
||||
hyprctl dispatch exec waybar
|
||||
fi
|
||||
'';
|
||||
|
||||
scripts.color-pick = pkgs.writeShellScriptBin "color-pick" ''
|
||||
# hyprpicker -a already copies the color to the clipboard.
|
||||
color=$(${pkgs.hyprpicker}/bin/hyprpicker -a 2>/dev/null)
|
||||
if [ -n "$color" ]; then
|
||||
OSD_TEXT=" $color"
|
||||
else
|
||||
OSD_TEXT=" Cancelled"
|
||||
fi
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
scripts.screenshot-edit = pkgs.writeShellScriptBin "screenshot-edit" ''
|
||||
${pkgs.hyprshot}/bin/hyprshot -m region --raw 2>/dev/null \
|
||||
| ${pkgs.satty}/bin/satty --filename -
|
||||
'';
|
||||
|
||||
scripts.record-toggle = pkgs.writeShellScriptBin "record-toggle" ''
|
||||
if pgrep -x wf-recorder >/dev/null; then
|
||||
# -INT lets wf-recorder finalize the file cleanly.
|
||||
pkill -INT -x wf-recorder
|
||||
OSD_TEXT=" Recording saved"
|
||||
else
|
||||
dir="$HOME/Videos"
|
||||
mkdir -p "$dir"
|
||||
file="$dir/rec-$(date +%Y%m%d-%H%M%S).mp4"
|
||||
${pkgs.wf-recorder}/bin/wf-recorder -f "$file" >/dev/null 2>&1 &
|
||||
OSD_TEXT=" Recording…"
|
||||
fi
|
||||
${updateOsd}
|
||||
'';
|
||||
|
||||
scripts.airplane-toggle = pkgs.writeShellScriptBin "airplane-toggle" ''
|
||||
# Reuse nmcli + bluetoothctl (unprivileged) instead of rfkill.
|
||||
on=false
|
||||
nmcli radio wifi 2>/dev/null | grep -q enabled && on=true
|
||||
bluetoothctl show 2>/dev/null | grep -q "Powered: yes" && on=true
|
||||
if $on; then
|
||||
nmcli radio wifi off 2>/dev/null
|
||||
bluetoothctl power off >/dev/null 2>&1 || true
|
||||
OSD_TEXT=" Airplane On"
|
||||
else
|
||||
nmcli radio wifi on 2>/dev/null
|
||||
bluetoothctl power on >/dev/null 2>&1 || true
|
||||
OSD_TEXT=" Airplane Off"
|
||||
fi
|
||||
${updateOsd}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
pkgs,
|
||||
scripts,
|
||||
...
|
||||
}: let
|
||||
updateOsd = ''
|
||||
${scripts.waybar-osd}/bin/waybar-osd "$OSD_TEXT"
|
||||
'';
|
||||
in {
|
||||
scripts.networkScript = pkgs.writeShellScript "waybar-network" ''
|
||||
nmcli=${pkgs.networkmanager}/bin/nmcli
|
||||
ip=${pkgs.iproute2}/bin/ip
|
||||
jq=${pkgs.jq}/bin/jq
|
||||
nl=$'\n'
|
||||
|
||||
text=""
|
||||
tip=""
|
||||
class="disconnected"
|
||||
add_seg() { text="''${text:+$text }$1"; }
|
||||
add_tip() { tip="''${tip:+$tip$nl}$1"; }
|
||||
|
||||
# ── Ethernet ────────────────────────────────────────────────
|
||||
eth=$("$nmcli" -t -f DEVICE,TYPE,STATE device status 2>/dev/null \
|
||||
| awk -F: '$2 == "ethernet" && $3 == "connected" { print $1; exit }')
|
||||
if [ -n "$eth" ]; then
|
||||
eth_ip=$("$ip" -4 -br addr show "$eth" 2>/dev/null | awk '{ print $3 }' | cut -d/ -f1)
|
||||
add_seg ""
|
||||
class="ethernet"
|
||||
add_tip " Ethernet''${eth_ip:+ · $eth_ip}"
|
||||
fi
|
||||
|
||||
# ── Wi-Fi ───────────────────────────────────────────────────
|
||||
wifi=$("$nmcli" -t -f ACTIVE,SSID,SIGNAL device wifi 2>/dev/null | grep -m1 '^yes:')
|
||||
if [ -n "$wifi" ]; then
|
||||
ssid=$(printf '%s' "$wifi" | cut -d: -f2)
|
||||
signal=$(printf '%s' "$wifi" | cut -d: -f3)
|
||||
if [ "''${signal:-0}" -ge 80 ]; then icon=""
|
||||
elif [ "''${signal:-0}" -ge 60 ]; then icon=""
|
||||
elif [ "''${signal:-0}" -ge 40 ]; then icon=""
|
||||
elif [ "''${signal:-0}" -ge 20 ]; then icon=""
|
||||
else icon=""
|
||||
fi
|
||||
add_seg "$icon"
|
||||
[ "$class" = "disconnected" ] && class="wifi"
|
||||
add_tip "$icon ''${ssid:-Wi-Fi}''${signal:+ · $signal%}"
|
||||
fi
|
||||
|
||||
# ── VPN (nmcli, fallback interfaces) ────────────────────────
|
||||
vpn=$("$nmcli" -t -f TYPE,NAME connection show --active 2>/dev/null \
|
||||
| awk -F: '$1 == "vpn" || $1 == "wireguard" { print $2; exit }')
|
||||
if [ -z "$vpn" ]; then
|
||||
vpn=$("$ip" -br link show 2>/dev/null \
|
||||
| awk '($2 == "UP" || $2 == "UNKNOWN") && $1 ~ /^(tun|wg|proton|ppp)/ { sub(/@.*/, "", $1); print $1; exit }')
|
||||
fi
|
||||
if [ -n "$vpn" ]; then
|
||||
add_seg ""
|
||||
add_tip " VPN · $vpn"
|
||||
fi
|
||||
|
||||
if [ -z "$text" ]; then
|
||||
text=""
|
||||
tip="Disconnected"
|
||||
fi
|
||||
|
||||
"$jq" -cn --arg text "$text" --arg class "$class" --arg tooltip "$tip" \
|
||||
'{ text: $text, class: $class, tooltip: $tooltip }'
|
||||
'';
|
||||
|
||||
scripts.wifi-toggle = pkgs.writeShellScriptBin "wifi-toggle" ''
|
||||
if ${pkgs.networkmanager}/bin/nmcli radio wifi | grep -q enabled; then
|
||||
${pkgs.networkmanager}/bin/nmcli radio wifi off
|
||||
else
|
||||
${pkgs.networkmanager}/bin/nmcli radio wifi on
|
||||
fi
|
||||
${updateOsd}
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{pkgs, ...}: {
|
||||
scripts.waybar-osd = pkgs.writeShellApplication {
|
||||
name = "waybar-osd";
|
||||
runtimeInputs = with pkgs; [procps coreutils];
|
||||
text = ''
|
||||
printf '%s' "$1" > "$XDG_RUNTIME_DIR/waybar-osd"
|
||||
pkill -f -RTMIN+8 '^waybar$' 2>/dev/null || true
|
||||
'';
|
||||
};
|
||||
|
||||
scripts.waybar-osd-status = pkgs.writeShellApplication {
|
||||
name = "waybar-osd-status";
|
||||
runtimeInputs = with pkgs; [coreutils];
|
||||
text = ''
|
||||
file="$XDG_RUNTIME_DIR/waybar-osd"
|
||||
[ -f "$file" ] || exit 1
|
||||
mtime=$(stat -c %Y "$file" 2>/dev/null) || exit 1
|
||||
age=$(( $(date +%s) - mtime ))
|
||||
if [ "$age" -ge 3 ]; then
|
||||
rm -f "$file"
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
_module.args.osdPath = "$XDG_RUNTIME_DIR/waybar-osd";
|
||||
}
|
||||
@@ -2,11 +2,6 @@
|
||||
config,
|
||||
pkgs,
|
||||
osdPath,
|
||||
networkScript,
|
||||
bluetoothScript,
|
||||
caffeineToggleScript,
|
||||
osdStatusScript,
|
||||
nightshiftToggleScript,
|
||||
...
|
||||
}: let
|
||||
gaps-out = config.theme.gaps-out;
|
||||
@@ -18,9 +13,7 @@ in {
|
||||
position = "top";
|
||||
height = config.theme.bar-height;
|
||||
margin = "${toString gaps-out} ${toString gaps-out} 0";
|
||||
modules-center = ["custom/osd" "custom/osd-sep" "clock" "tray" "hyprland/workspaces" "custom/network" "custom/bluetooth" "battery" "group/drawer"];
|
||||
|
||||
# ── Modules ─────────────────────────────────────────────────────────
|
||||
modules-center = ["custom/osd" "custom/osd-sep" "clock" "tray" "hyprland/workspaces" "custom/network" "custom/bluetooth" "battery" "pulseaudio" "custom/nightshift" "custom/caffeine"];
|
||||
|
||||
"hyprland/workspaces" = {
|
||||
format = "{id}";
|
||||
@@ -55,7 +48,7 @@ in {
|
||||
exec-if = "! systemctl --user is-active --quiet hypridle";
|
||||
format = "{}";
|
||||
interval = 5;
|
||||
on-click = "${caffeineToggleScript}/bin/caffeine-toggle";
|
||||
on-click = "${config.scripts.caffeine-toggle}/bin/caffeine-toggle";
|
||||
tooltip = false;
|
||||
};
|
||||
|
||||
@@ -64,12 +57,12 @@ in {
|
||||
exec-if = "${pkgs.procps}/bin/pgrep -x hyprsunset";
|
||||
format = "{}";
|
||||
interval = 5;
|
||||
on-click = "${nightshiftToggleScript}/bin/nightshift-toggle";
|
||||
on-click = "${config.scripts.nightshift-toggle}/bin/nightshift-toggle";
|
||||
tooltip = false;
|
||||
};
|
||||
|
||||
"custom/bluetooth" = {
|
||||
exec = "${bluetoothScript}";
|
||||
exec = "${config.scripts.bluetoothScript}";
|
||||
exec-if = "${pkgs.bluez}/bin/bluetoothctl list 2>/dev/null | grep -q Controller";
|
||||
return-type = "json";
|
||||
interval = 5;
|
||||
@@ -78,7 +71,7 @@ in {
|
||||
|
||||
"custom/osd" = {
|
||||
exec = "cat ${osdPath}";
|
||||
exec-if = "${osdStatusScript}/bin/waybar-osd-status";
|
||||
exec-if = "${config.scripts.waybar-osd-status}/bin/waybar-osd-status";
|
||||
signal = 8;
|
||||
interval = 1;
|
||||
format = "{}";
|
||||
@@ -86,7 +79,7 @@ in {
|
||||
|
||||
"custom/osd-sep" = {
|
||||
exec = "echo '|'";
|
||||
exec-if = "${osdStatusScript}/bin/waybar-osd-status";
|
||||
exec-if = "${config.scripts.waybar-osd-status}/bin/waybar-osd-status";
|
||||
signal = 8;
|
||||
interval = 1;
|
||||
format = "{}";
|
||||
@@ -122,7 +115,7 @@ in {
|
||||
};
|
||||
|
||||
"custom/network" = {
|
||||
exec = "${networkScript}";
|
||||
exec = "${config.scripts.networkScript}";
|
||||
return-type = "json";
|
||||
interval = 10;
|
||||
on-click = "${pkgs.ghostty}/bin/ghostty +new-window -e ${pkgs.nur.repos.anotherhadi.settuings}/bin/settuings --page network";
|
||||
|
||||
Reference in New Issue
Block a user