Compare commits
8 Commits
ce60bc3677
...
3bfbd935fc
| Author | SHA1 | Date |
|---|---|---|
|
|
3bfbd935fc | |
|
|
801d3626d7 | |
|
|
54f0e0bbc5 | |
|
|
2627eda709 | |
|
|
c4ae794622 | |
|
|
dbec898d65 | |
|
|
a044f9b8ac | |
|
|
5d00af45fc |
18
flake.lock
18
flake.lock
|
|
@ -30,11 +30,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1784476090,
|
||||
"narHash": "sha256-flVUft590tsDX9z97O4DzVFg6zvOOmGeYhPeDdaP1DI=",
|
||||
"lastModified": 1784907849,
|
||||
"narHash": "sha256-wtypRfcfVvofMON6xemTObJCpPWLRyBoqLrvzgnLYlI=",
|
||||
"owner": "aylur",
|
||||
"repo": "astal",
|
||||
"rev": "fd94e333c8bd45557291c805f1df79d5f787d590",
|
||||
"rev": "9dac92f20e6c89b9373bbb238c49b1cb115724db",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -111,11 +111,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1784546264,
|
||||
"narHash": "sha256-jxVr5EHMYAOkFvS2k0abIvt6NqyshyWmiHHzV17sT2g=",
|
||||
"lastModified": 1785958398,
|
||||
"narHash": "sha256-5r/JgsoNMTx+qIh83WkxpujEKBR7PF5ssnh5vYCuSAw=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "e3dea8e4792b09a6c0eb5836b0284ad05b1acba0",
|
||||
"rev": "a7c70cc290290f373f50cd820403833d250459ac",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -163,11 +163,11 @@
|
|||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1784497964,
|
||||
"narHash": "sha256-vlHUuqAcbcH2RKmHbPiuQzbv1pnzzavXnI62RD0bqCU=",
|
||||
"lastModified": 1785967620,
|
||||
"narHash": "sha256-IItrdb7Puk05RqOBWZYFC5X6Wl1sJmCfh5MWVHw5iMM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "241313f4e8e508cb9b13278c2b0fa25b9ca27163",
|
||||
"rev": "b7c2ada94fe99c15b0dbcf4d11fd7850b957a436",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ in {
|
|||
smile
|
||||
podman-compose
|
||||
vesktop
|
||||
sox
|
||||
];
|
||||
|
||||
programs.alacritty.settings.font.size = 13;
|
||||
|
|
|
|||
|
|
@ -183,6 +183,32 @@ function Right() {
|
|||
);
|
||||
}
|
||||
|
||||
// Icon names to try for the current battery state, most specific first.
|
||||
// Papirus does not ship every combination, so each entry needs a fallback:
|
||||
// "-charged-symbolic" only exists at level 100, and "-charging-symbolic" only
|
||||
// up to level 90 (above that the panel icon has to be named directly).
|
||||
function batteryIconNames(): string[] {
|
||||
const percentage = battery.percentage;
|
||||
const thresholds = [...Array(11).keys()].map((i) => i * 10);
|
||||
const level = thresholds.find((threshold) => threshold >= percentage * 100);
|
||||
const plain = `battery-level-${level}-symbolic`;
|
||||
|
||||
// Being full is a state, not a 100% reading: with a charge limit set the
|
||||
// battery stops well below 100 and sits there reporting FULLY_CHARGED.
|
||||
if (battery.state === Battery.State.FULLY_CHARGED) {
|
||||
return [`battery-level-${level}-charged-symbolic`, plain];
|
||||
}
|
||||
if (battery.charging) {
|
||||
const padded = String(level).padStart(3, "0");
|
||||
return [
|
||||
`battery-level-${level}-charging-symbolic`,
|
||||
`battery-${padded}-charging`,
|
||||
plain,
|
||||
];
|
||||
}
|
||||
return [plain];
|
||||
}
|
||||
|
||||
// Papirus encodes the battery level as a 35%-opacity overlay inside its
|
||||
// *-symbolic icons. GTK4's symbolic recoloring flattens that opacity, so every
|
||||
// level renders as a solid, full battery. The Papirus *-symbolic icons are just
|
||||
|
|
@ -190,13 +216,12 @@ function Right() {
|
|||
// normally — so we resolve each name to that underlying file and load it
|
||||
// directly (as a non-symbolic file icon), which renders the correct level.
|
||||
function batteryGicon(): Gio.Icon {
|
||||
const percentage = battery.percentage;
|
||||
const thresholds = [...Array(11).keys()].map((i) => i * 10);
|
||||
const icon = thresholds.find((threshold) => threshold >= percentage * 100);
|
||||
const name = battery.charging
|
||||
? `battery-level-${icon}-${percentage >= 0.99 ? "charged" : "charging"}-symbolic`
|
||||
: `battery-level-${icon}-symbolic`;
|
||||
const symbolic = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()!)
|
||||
const theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default()!);
|
||||
// lookup_icon() never fails: for an unknown name it hands back the
|
||||
// "image-missing" paintable, so check availability before resolving.
|
||||
const names = batteryIconNames();
|
||||
const name = names.find((n) => theme.has_icon(n)) ?? names[names.length - 1];
|
||||
const symbolic = theme
|
||||
.lookup_icon(name, null, 16, 1, Gtk.TextDirection.NONE, 0)
|
||||
.get_file();
|
||||
if (!symbolic) return Gio.ThemedIcon.new(name);
|
||||
|
|
@ -217,15 +242,21 @@ function batteryGicon(): Gio.Icon {
|
|||
return new Gio.FileIcon({ file: symbolic });
|
||||
}
|
||||
|
||||
function batteryTooltip(): string {
|
||||
return `${Math.round(battery.percentage * 100)}%`;
|
||||
}
|
||||
|
||||
function BatteryIcon(): JSX.Element {
|
||||
if (battery.get_state() == 0) return <box />;
|
||||
// AstalBattery does not emit notify::percentage/charging/state on this
|
||||
// system, so a signal binding stays frozen at its initial value (e.g.
|
||||
// showing "charged" forever). Poll the live values instead.
|
||||
const gicon = createPoll<Gio.Icon>(batteryGicon(), 10000, batteryGicon);
|
||||
const tooltip = createPoll<string>(batteryTooltip(), 10000, batteryTooltip);
|
||||
return (
|
||||
<button
|
||||
class="battery-item"
|
||||
tooltipText={tooltip}
|
||||
onClicked={() => execAsync(["gnome-power-statistics"])}
|
||||
>
|
||||
<box>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,16 @@ import style from "./style.scss";
|
|||
import Bar from "./Bar";
|
||||
import NotificationPopups from "./notifications/NotificationPopups";
|
||||
|
||||
const PREFERRED_MONITOR = "eDP-1";
|
||||
|
||||
function pick_monitor(): Gdk.Monitor | undefined {
|
||||
const monitors = app.get_monitors();
|
||||
return (
|
||||
monitors.find((m: Gdk.Monitor) => m.get_connector() === PREFERRED_MONITOR) ??
|
||||
monitors[0]
|
||||
);
|
||||
}
|
||||
|
||||
function register_windows(monitor: Gdk.Monitor) {
|
||||
let scale = (monitor.get_geometry().width >= 3000) ? 1.2 : 1;
|
||||
Bar(monitor, scale);
|
||||
|
|
@ -14,6 +24,11 @@ app.start({
|
|||
css: style,
|
||||
iconTheme: "Papirus",
|
||||
main() {
|
||||
register_windows(app.get_monitors()[0]);
|
||||
const monitor = pick_monitor();
|
||||
if (!monitor) {
|
||||
console.error("No monitors available, not creating the bar.");
|
||||
return;
|
||||
}
|
||||
register_windows(monitor);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,9 +18,15 @@ const app_icons = new Apps.Apps().list.reduce(
|
|||
export function getIconName(app_id: string | null | undefined, title: string | null | undefined) {
|
||||
if (!app_id && !title) return "";
|
||||
|
||||
// Our foot terminals carry a unique per-window app-id ("footclient-<pid>") so the
|
||||
// "duplicate window" keybind can tell them apart; strip that suffix back to the
|
||||
// base app-id ("footclient") so the icon still resolves.
|
||||
const norm = app_id ? app_id.replace(/-\d+$/, "") : app_id;
|
||||
|
||||
// try fields matching Niri outputs
|
||||
const possibleKeys = [
|
||||
app_id,
|
||||
norm,
|
||||
title
|
||||
].filter(Boolean) as string[];
|
||||
|
||||
|
|
|
|||
|
|
@ -62,10 +62,10 @@
|
|||
autoStageResolvedConflicts = false;
|
||||
};
|
||||
git.overrideGpg = true;
|
||||
git.pagers = [
|
||||
git.diffRenderers = [
|
||||
{
|
||||
colorArg = "always";
|
||||
pager = "delta --dark --color-only --paging=never";
|
||||
command = "delta --dark --color-only --paging=never";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,12 +3,21 @@
|
|||
scale ? null,
|
||||
...
|
||||
}: let
|
||||
terminal = "${pkgs.foot}/bin/footclient";
|
||||
nautilus = "${pkgs.nautilus}/bin/nautilus";
|
||||
playerctl = "${pkgs.playerctl}/bin/playerctl";
|
||||
wpctl = "${pkgs.wireplumber}/bin/wpctl";
|
||||
brightnessctl = "${pkgs.brightnessctl}/bin/brightnessctl";
|
||||
terminal-name = "foot";
|
||||
# Spawn a foot terminal that the "duplicate window" keybind can later trace back to
|
||||
# its shell. foot's --server mode makes every window report the *server's* PID to the
|
||||
# compositor, so niri can't distinguish windows by PID. Instead we give each window a
|
||||
# unique app-id ("footclient-<pid>") and export the same value as FOOT_APPID into its
|
||||
# shell; -E hands footclient's environment (incl. FOOT_APPID) to the server-spawned
|
||||
# shell. launch_terminal then finds the focused window's shell by matching FOOT_APPID.
|
||||
spawn_terminal = pkgs.writeShellScriptBin "spawn_terminal" ''
|
||||
ID="footclient-$$"
|
||||
export FOOT_APPID="$ID"
|
||||
exec ${pkgs.foot}/bin/footclient -E --app-id="$ID" "$@"
|
||||
'';
|
||||
launch_terminal = pkgs.writeShellScriptBin "launch_terminal" ''
|
||||
function find_leaf_pid(){
|
||||
local PID
|
||||
|
|
@ -29,28 +38,46 @@
|
|||
fi
|
||||
}
|
||||
|
||||
LEAF_PID=$(find_leaf_pid "$1")
|
||||
# foot runs in --server mode, so niri reports the shared server PID for every foot
|
||||
# window and cannot tell them apart. Resolve the focused window to its own shell by
|
||||
# matching the FOOT_APPID marker that spawn_terminal exports into each window: scan
|
||||
# the server's direct children for the one whose environment carries our app-id.
|
||||
FOCUSED=$(niri msg --json focused-window)
|
||||
SERVER_PID=$(echo "$FOCUSED" | ${pkgs.jq}/bin/jq -r '.pid // empty')
|
||||
APP_ID=$(echo "$FOCUSED" | ${pkgs.jq}/bin/jq -r '.app_id // empty')
|
||||
|
||||
ROOT_PID=""
|
||||
if [ -n "$APP_ID" ] && [ -n "$SERVER_PID" ]; then
|
||||
for CHILD in $(pgrep -P "$SERVER_PID"); do
|
||||
if grep -qzF "FOOT_APPID=$APP_ID" "/proc/$CHILD/environ" 2>/dev/null; then
|
||||
ROOT_PID="$CHILD"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Focused window isn't one of our tagged foot terminals: just open a plain terminal.
|
||||
if [ -z "$ROOT_PID" ]; then
|
||||
exec ${spawn_terminal}/bin/spawn_terminal
|
||||
fi
|
||||
|
||||
LEAF_PID=$(find_leaf_pid "$ROOT_PID")
|
||||
if [ "$(ps -p "$LEAF_PID" -o comm=)" == "ssh" ]; then
|
||||
SSH_COMMAND=$(ps -p "$LEAF_PID" -o args --no-headers)
|
||||
if [[ "$SSH_COMMAND" == *"waypipe"* ]]; then
|
||||
CLIENT=$(echo "$SSH_COMMAND" | awk '{ print $5 }')
|
||||
PREVIOUS_SESSION_ID=$(grep -z "SSH_SESSION_ID" "/proc/$LEAF_PID/environ" | xargs -0 -n 1 | grep -oP "[0-9]*")
|
||||
SSH_SESSION_ID=$RANDOM
|
||||
${terminal} -e zsh -c "SSH_SESSION_ID=$SSH_SESSION_ID waypipe ssh -t \"$CLIENT\" env SSH_SESSION_ID=\"$SSH_SESSION_ID\" PREVIOUS_SESSION_ID=\"$PREVIOUS_SESSION_ID\" \"zsh --login\""
|
||||
${spawn_terminal}/bin/spawn_terminal zsh -c "SSH_SESSION_ID=$SSH_SESSION_ID waypipe ssh -t \"$CLIENT\" env SSH_SESSION_ID=\"$SSH_SESSION_ID\" PREVIOUS_SESSION_ID=\"$PREVIOUS_SESSION_ID\" \"zsh --login\""
|
||||
else
|
||||
${terminal} -e zsh -c "ssh -t \"$(echo "$SSH_COMMAND" | awk '{ print $2 }')\""
|
||||
${spawn_terminal}/bin/spawn_terminal zsh -c "ssh -t \"$(echo "$SSH_COMMAND" | awk '{ print $2 }')\""
|
||||
fi
|
||||
|
||||
else # Not an ssh session
|
||||
if [ "$(ps -p "$1" -o comm=)" == "${terminal-name}" ]; then
|
||||
else # Not an ssh session: open a local terminal in the same directory.
|
||||
CWD=$(readlink -e /proc/"$LEAF_PID"/cwd)
|
||||
if [ "$CWD" != "" ]; then
|
||||
${terminal} --working-directory "$CWD"
|
||||
${spawn_terminal}/bin/spawn_terminal --working-directory "$CWD"
|
||||
else
|
||||
${terminal}
|
||||
fi
|
||||
else
|
||||
${terminal}
|
||||
${spawn_terminal}/bin/spawn_terminal
|
||||
fi
|
||||
fi
|
||||
'';
|
||||
|
|
@ -156,8 +183,8 @@ in {
|
|||
|
||||
binds {
|
||||
// General Binds
|
||||
Mod+Return { spawn "sh" "-c" "${launch_terminal}/bin/launch_terminal $(niri msg --json focused-window | ${pkgs.jq}/bin/jq .pid)"; }
|
||||
Mod+Shift+Return { spawn "${terminal}"; }
|
||||
Mod+Return { spawn "${launch_terminal}/bin/launch_terminal"; }
|
||||
Mod+Shift+Return { spawn "${spawn_terminal}/bin/spawn_terminal"; }
|
||||
Mod+Q { close-window; }
|
||||
Mod+A { spawn "${nautilus}"; }
|
||||
Mod+B { spawn "Helium"; }
|
||||
|
|
@ -233,9 +260,10 @@ in {
|
|||
XF86AudioMute { spawn "${wpctl}" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle"; }
|
||||
XF86AudioMicMute { spawn "${wpctl}" "set-mute" "@DEFAULT_AUDIO_SOURCE@" "toggle"; }
|
||||
|
||||
// Horizontal mouse scroll controls volume
|
||||
WheelScrollRight cooldown-ms=10 { spawn "${wpctl}" "set-volume" "@DEFAULT_AUDIO_SINK@" "1%+" "--limit" "1.0"; }
|
||||
WheelScrollLeft cooldown-ms=10 { spawn "${wpctl}" "set-volume" "@DEFAULT_AUDIO_SINK@" "1%-"; }
|
||||
// Ctrl + horizontal mouse scroll controls volume
|
||||
// (unmodified WheelScroll binds capture the whole wheel in niri and break normal scrolling)
|
||||
Ctrl+Alt+WheelScrollRight cooldown-ms=10 { spawn "${wpctl}" "set-volume" "@DEFAULT_AUDIO_SINK@" "1%+" "--limit" "1.0"; }
|
||||
Ctrl+Alt+WheelScrollLeft cooldown-ms=10 { spawn "${wpctl}" "set-volume" "@DEFAULT_AUDIO_SINK@" "1%-"; }
|
||||
|
||||
// Mod + horizontal scroll moves between windows (columns) left/right
|
||||
Mod+WheelScrollRight cooldown-ms=200 { focus-column-right; }
|
||||
|
|
|
|||
|
|
@ -17,6 +17,26 @@
|
|||
name = "ayu-dark";
|
||||
};
|
||||
};
|
||||
providers = {
|
||||
clipboard = {
|
||||
entrypoints = {
|
||||
history = {
|
||||
preferences = {
|
||||
defaultAction = "copy";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
core = {
|
||||
entrypoints = {
|
||||
"search-emojis" = {
|
||||
preferences = {
|
||||
defaultAction = "copy";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
launcher_window = {
|
||||
opacity = 0.95;
|
||||
client_side_decorations = {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,17 @@ in {
|
|||
services.logind.settings.Login = {
|
||||
HandlePowerKey = "suspend";
|
||||
};
|
||||
services.throttled.enable = true;
|
||||
# 'throttled' raises PL1/PL2 to 44W, but this machine is a Core Ultra 7 155U
|
||||
# with a 15W spec sustained power. Holding it at 44W drives the chassis sensor
|
||||
# (acpitz) past its 109C critical trip, which makes the kernel do a hardware
|
||||
# protection shutdown. Leave the firmware's own power limits alone.
|
||||
services.throttled.enable = false;
|
||||
|
||||
# thermald refuses to run on ThinkPads exposing thinkpad_acpi/dytc_lapmode,
|
||||
# because the platform's DPTF firmware owns thermal management there. It exits
|
||||
# immediately at boot, so enabling it only produces noise in the journal.
|
||||
services.thermald.enable = lib.mkForce false;
|
||||
|
||||
services.thinkfan.enable = true;
|
||||
services.thinkfan.settings = {
|
||||
sensors = [
|
||||
|
|
@ -60,7 +70,8 @@ in {
|
|||
[2 50 60]
|
||||
[3 56 68]
|
||||
[5 64 78]
|
||||
[7 76 95]
|
||||
[7 76 90]
|
||||
["level disengaged" 85 32767]
|
||||
];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -53,9 +53,6 @@
|
|||
hardware.trackpoint.enable = lib.mkDefault true;
|
||||
hardware.trackpoint.emulateWheel = lib.mkDefault config.hardware.trackpoint.enable;
|
||||
hardware.trackpoint.device = "TPPS/2 Synaptics TrackPoint";
|
||||
nixpkgs.config.packageOverrides = pkgs: {
|
||||
zfs = pkgs.zfs_unstable;
|
||||
};
|
||||
boot.kernelPackages = pkgs.linuxPackages;
|
||||
boot.zfs.forceImportRoot = false;
|
||||
hardware.firmware = with pkgs; [
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
{
|
||||
inputs,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
{pkgs, ...}: {
|
||||
services.udev.packages = [pkgs.yubikey-personalization];
|
||||
programs.appimage.enable = true;
|
||||
programs.appimage.binfmt = true;
|
||||
|
|
|
|||
Loading…
Reference in New Issue