This commit is contained in:
Geoffrey Frogeye 2025-05-27 19:26:30 +02:00
parent 492f085d52
commit bb021a1aae
30 changed files with 487 additions and 573 deletions

View file

@ -24,7 +24,6 @@
hardware.enableRedistributableFirmware = true; hardware.enableRedistributableFirmware = true;
frogeye.desktop = { frogeye.desktop = {
x11_screens = [ "DP-2" "eDP-1" ];
maxVideoHeight = 1080; maxVideoHeight = 1080;
phasesCommands = { phasesCommands = {
@ -47,7 +46,7 @@
home-manager.users.geoffrey = home-manager.users.geoffrey =
{ ... }: { ... }:
{ {
xsession.windowManager.i3.config.modifier = "Mod1"; wayland.windowManager.sway.config.modifier = "Mod1";
}; };
# 8 makes it run out of memory when rebuilding. # 8 makes it run out of memory when rebuilding.

View file

@ -6,18 +6,9 @@
}: }:
let let
displays = { displays = {
embedded = { embedded = "Chimei Innolux Corporation 0x1738 Unknown";
output = "eDP-1"; deskLeft = "Samsung Electric Company S24B420 H4MCB03534"; # Internal HDMI
edid = "00ffffffffffff000dae381700000000011c01049526157802a155a556519d280b505400000001010101010101010101010101010101b43b804a71383440302035007dd61000001ac32f804a71383440302035007dd61000001a000000fe003059395747803137334843450a00000000000041319e001000000a010a2020004f"; deskRight = "Samsung Electric Company S24B420 H4MC800865"; # DisplayLink DVI
};
deskLeft = {
output = "HDMI-1-3"; # Internal HDMI port
edid = "00ffffffffffff004c2d7b09333032302f160103803420782a01f1a257529f270a505423080081c0810081809500a9c0b300d1c00101283c80a070b023403020360006442100001a000000fd00353f1e5111000a202020202020000000fc00533234423432300a2020202020000000ff0048344d434230333533340a2020010702010400023a80d072382d40102c458006442100001e011d007251d01e206e28550006442100001e011d00bc52d01e20b828554006442100001e8c0ad090204031200c4055000644210000188c0ad08a20e02d10103e9600064421000018000000000000000000000000000000000000000000000000000000000000000000d2";
};
deskRight = {
output = "DVI-I-2-1"; # DisplayLink
edid = "00ffffffffffff004c2d7b093330323020160103803420782a01f1a257529f270a505423080081c0810081809500a9c0b300d1c00101283c80a070b023403020360006442100001a000000fd00353f1e5111000a202020202020000000fc00533234423432300a2020202020000000ff0048344d433830303836350a2020011c02010400023a80d072382d40102c458006442100001e011d007251d01e206e28550006442100001e011d00bc52d01e20b828554006442100001e8c0ad090204031200c4055000644210000188c0ad08a20e02d10103e9600064421000018000000000000000000000000000000000000000000000000000000000000000000d2";
};
}; };
in in
{ {
@ -33,8 +24,6 @@ in
"rtsx_usb_sdmmc" "rtsx_usb_sdmmc"
]; ];
kernelModules = [ "kvm-intel" ]; kernelModules = [ "kvm-intel" ];
kernelPackages = pkgs.linuxKernel.packages.linux_6_6;
# displaylink doesn't seem to be working for kernels >= 6.9?
# UEFI works here, and variables can be touched # UEFI works here, and variables can be touched
loader = { loader = {
@ -55,12 +44,7 @@ in
hardware.keyboard.qmk.enable = true; hardware.keyboard.qmk.enable = true;
frogeye.desktop = { frogeye.desktop = {
x11_screens = [
displays.deskLeft.output
displays.deskRight.output
];
maxVideoHeight = 1440; maxVideoHeight = 1440;
numlock = true;
phasesCommands = { phasesCommands = {
jour = '' jour = ''
${pkgs.brightnessctl}/bin/brightnessctl set 40000 & ${pkgs.brightnessctl}/bin/brightnessctl set 40000 &
@ -80,44 +64,80 @@ in
# TODO Display 2 doesn't work anymore? # TODO Display 2 doesn't work anymore?
}; };
}; };
services = {
autorandr = { # Screens
profiles = { home-manager.users.geoffrey =
portable = { { ... }:
fingerprint.${displays.embedded.output} = displays.embedded.edid; {
config.${displays.embedded.output} = { }; services = {
}; kanshi.settings = [
extOnly = { {
fingerprint = { profile = {
${displays.embedded.output} = displays.embedded.edid; name = "portable";
${displays.deskLeft.output} = displays.deskLeft.edid; outputs = [
${displays.deskRight.output} = displays.deskRight.edid; { criteria = displays.embedded; }
}; ];
config = {
${displays.embedded.output}.enable = false;
${displays.deskLeft.output} = {
primary = true;
mode = "1920x1200";
rate = "59.95";
position = "0x0";
}; };
${displays.deskRight.output} = { }
mode = "1920x1200"; {
rate = "59.95"; profile = {
position = "1920x0"; name = "extOnly";
outputs = [
{
criteria = displays.embedded;
status = "disable";
}
{
criteria = displays.deskLeft;
position = "0,0";
}
{
criteria = displays.deskRight;
position = "1920,0";
}
];
}; };
}; }
}; {
# TODO leftOnly and other things.Might want to abstract a few things first. profile = {
name = "leftOnly";
outputs = [
{
criteria = displays.embedded;
status = "disable";
}
{
criteria = displays.deskLeft;
}
];
};
}
];
}; };
}; };
# Needs prefetched binary blobs, see https://nixos.wiki/wiki/Displaylink
xserver.videoDrivers = [ # Displaylink
"displaylink" environment.variables = {
"modesetting" WLR_EVDI_RENDER_DEVICE = "/dev/dri/card1";
];
# TODO See if nvidia and DL can work together.
}; };
nixpkgs.overlays = [
(final: prev: {
wlroots_0_18 = prev.wlroots_0_18.overrideAttrs (old: {
patches = (old.patches or [ ]) ++ [
(prev.fetchpatch {
url = "https://gitlab.freedesktop.org/wlroots/wlroots/uploads/bd115aa120d20f2c99084951589abf9c/DisplayLink_v2.patch";
hash = "sha256-vWQc2e8a5/YZaaHe+BxfAR/Ni8HOs2sPJ8Nt9pfxqiE=";
})
];
});
})
];
services.xserver.videoDrivers = [
"displaylink"
"modesetting"
];
systemd.services.dlm.wantedBy = [ "multi-user.target" ];
# Needs prefetched binary blobs, see https://wiki.nixos.org/wiki/Displaylink
}; };
imports = [ imports = [
nixos-hardware.nixosModules.dell-g3-3779 nixos-hardware.nixosModules.dell-g3-3779

19
flake.lock generated
View file

@ -654,7 +654,8 @@
"nur": "nur", "nur": "nur",
"onixpkgs": "onixpkgs", "onixpkgs": "onixpkgs",
"stylix": "stylix", "stylix": "stylix",
"unixpkgs": "unixpkgs" "unixpkgs": "unixpkgs",
"zelbarnixpkgs": "zelbarnixpkgs"
} }
}, },
"scss-reset": { "scss-reset": {
@ -909,6 +910,22 @@
"ref": "master", "ref": "master",
"type": "indirect" "type": "indirect"
} }
},
"zelbarnixpkgs": {
"locked": {
"lastModified": 1748415083,
"narHash": "sha256-jmIRxOA7kj1CFNiP6S6pg6e6XeQi3whBVvWsATpEiC4=",
"owner": "GeoffreyFrogeye",
"repo": "nixpkgs",
"rev": "bc0e8ee7d08241e8fbcb5e13a7abb445329e5644",
"type": "github"
},
"original": {
"owner": "GeoffreyFrogeye",
"ref": "zelbar",
"repo": "nixpkgs",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View file

@ -6,6 +6,7 @@
onixpkgs.url = "nixpkgs/nixos-24.11"; onixpkgs.url = "nixpkgs/nixos-24.11";
nixpkgs.url = "nixpkgs/nixos-25.05"; nixpkgs.url = "nixpkgs/nixos-25.05";
unixpkgs.url = "nixpkgs/master"; unixpkgs.url = "nixpkgs/master";
zelbarnixpkgs.url = "github:GeoffreyFrogeye/nixpkgs/zelbar";
# OS # OS
disko = { disko = {
url = "disko"; url = "disko";
@ -41,6 +42,7 @@
self, self,
nixpkgs, nixpkgs,
unixpkgs, unixpkgs,
zelbarnixpkgs,
disko, disko,
nix-on-droid, nix-on-droid,
flake-utils, flake-utils,
@ -62,9 +64,11 @@
self: super: self: super:
let let
upkgs = import unixpkgs { inherit (super) system; }; upkgs = import unixpkgs { inherit (super) system; };
zelbarpkgs = import zelbarnixpkgs { inherit (super) system; };
in in
{ {
hello = upkgs.hello; # Placeholder hello = upkgs.hello; # Placeholder
zelbar = zelbarpkgs.zelbar;
} }
) )
]; ];

View file

@ -20,7 +20,7 @@ let
specialisation = "dark"; specialisation = "dark";
} }
]; ];
mod = config.xsession.windowManager.i3.config.modifier; mod = config.wayland.windowManager.sway.config.modifier;
in in
{ {
config = { config = {
@ -48,7 +48,7 @@ in
++ (with pkgs; [ ++ (with pkgs; [
brightnessctl brightnessctl
]); ]);
xsession.windowManager.i3.config.keybindings = { wayland.windowManager.sway.config.keybindings = lib.mkOptionDefault {
XF86MonBrightnessUp = "exec ${pkgs.brightnessctl}/bin/brightnessctl set +5%"; XF86MonBrightnessUp = "exec ${pkgs.brightnessctl}/bin/brightnessctl set +5%";
XF86MonBrightnessDown = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%-"; XF86MonBrightnessDown = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%-";
"${mod}+F6" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 1%-"; "${mod}+F6" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 1%-";

View file

@ -6,7 +6,7 @@
}: }:
let let
pactl = "exec ${pkgs.pulseaudio}/bin/pactl"; # TODO Use NixOS package if using NixOS pactl = "exec ${pkgs.pulseaudio}/bin/pactl"; # TODO Use NixOS package if using NixOS
mod = config.xsession.windowManager.i3.config.modifier; mod = config.wayland.windowManager.sway.config.modifier;
in in
{ {
config = lib.mkIf config.frogeye.desktop.xorg { config = lib.mkIf config.frogeye.desktop.xorg {
@ -32,7 +32,7 @@ in
text = ''cookie-file = .config/pulse/pulse-cookie''; text = ''cookie-file = .config/pulse/pulse-cookie'';
}; };
}; };
xsession.windowManager.i3.config.keybindings = { wayland.windowManager.sway.config.keybindings = lib.mkOptionDefault {
"XF86AudioRaiseVolume" = "${pactl} set-sink-mute @DEFAULT_SINK@ false; ${pactl} set-sink-volume @DEFAULT_SINK@ +5%"; "XF86AudioRaiseVolume" = "${pactl} set-sink-mute @DEFAULT_SINK@ false; ${pactl} set-sink-volume @DEFAULT_SINK@ +5%";
"XF86AudioLowerVolume" = "${pactl} set-sink-mute @DEFAULT_SINK@ false; ${pactl} set-sink-volume @DEFAULT_SINK@ -5%"; "XF86AudioLowerVolume" = "${pactl} set-sink-mute @DEFAULT_SINK@ false; ${pactl} set-sink-volume @DEFAULT_SINK@ -5%";
"XF86AudioMute" = "${pactl} set-sink-mute @DEFAULT_SINK@ true"; "XF86AudioMute" = "${pactl} set-sink-mute @DEFAULT_SINK@ true";

View file

@ -1,72 +0,0 @@
{
pkgs,
lib,
config,
...
}:
let
builtin_configs = [
"off"
"common"
"clone-largest"
"horizontal"
"vertical"
"horizontal-reverse"
"vertical-reverse"
];
autorandrmenu =
{
title,
option,
builtin ? false,
}:
pkgs.writeShellScript "autorandrmenu" ''
shopt -s nullglob globstar
profiles="${
if builtin then lib.strings.concatLines builtin_configs else ""
}$(${pkgs.autorandr}/bin/autorandr | ${pkgs.gawk}/bin/awk '{ print $1 }')"
profile="$(echo "$profiles" | ${config.programs.rofi.package}/bin/rofi -dmenu -p "${title}")"
[[ -n "$profile" ]] || exit
${pkgs.autorandr}/bin/autorandr ${option} "$profile"
'';
in
{
config = lib.mkIf config.frogeye.desktop.xorg {
frogeye.desktop.i3.bindmodes = {
"Screen setup [A] Auto [L] Load [S] Save [R] Remove [D] Default" = {
bindings = {
"a" = "exec ${pkgs.autorandr}/bin/autorandr --change --force, mode default";
"l" = "exec ${
autorandrmenu {
title = "Load profile";
option = "--load";
builtin = true;
}
}, mode default";
"s" = "exec ${
autorandrmenu {
title = "Save profile";
option = "--save";
}
}, mode default";
"r" = "exec ${
autorandrmenu {
title = "Remove profile";
option = "--remove";
}
}, mode default";
"d" = "exec ${
autorandrmenu {
title = "Default profile";
option = "--default";
builtin = true;
}
}, mode default";
};
mod_enter = "t";
};
};
programs.autorandr.enable = true;
services.autorandr.enable = true;
};
}

View file

@ -1,13 +0,0 @@
{
pkgs,
config,
...
}:
{
config = {
# This correctly sets the background on some occasions, below does the rest
programs.autorandr.hooks.postswitch = {
background = "${pkgs.feh}/bin/feh --no-fehbg --bg-fill ${config.stylix.image}";
};
};
}

View file

@ -122,7 +122,7 @@
github = "https://github.com/search?q={}"; github = "https://github.com/search?q={}";
google = "https://www.google.fr/search?q={}"; google = "https://www.google.fr/search?q={}";
hm = homemanager; hm = homemanager;
homemanager = "https://home-manager-options.extranix.com/?query={}&release=${config.home.version.release}"; homemanager = "https://home-manager-options.extranix.com/?query={}&release=release-${config.home.version.release}";
invidious = "https://invidious.frogeye.fr/search?q={}"; invidious = "https://invidious.frogeye.fr/search?q={}";
inv = invidious; inv = invidious;
nixos = "https://search.nixos.org/options?channel=${config.home.version.release}&query={}"; nixos = "https://search.nixos.org/options?channel=${config.home.version.release}&query={}";
@ -194,8 +194,8 @@
}; };
}; };
}; };
xsession.windowManager.i3.config.keybindings = { wayland.windowManager.sway.config.keybindings = lib.mkOptionDefault {
"${config.xsession.windowManager.i3.config.modifier}+m" = "${config.wayland.windowManager.sway.config.modifier}+m" =
"exec ${config.programs.qutebrowser.package}/bin/qutebrowser --override-restore"; "exec ${config.programs.qutebrowser.package}/bin/qutebrowser --override-restore";
}; };
}; };

View file

@ -7,43 +7,32 @@
{ {
imports = [ imports = [
./audio ./audio
./autorandr
./background
./browser ./browser
./cursor ./cursor
./frobar/module.nix ./frobar/module.nix
./i3.nix
./lock ./lock
./mpd ./mpd
./presentation ./presentation
./redness
./screenshots ./screenshots
./sway
./terminal ./terminal
]; ];
config = lib.mkIf config.frogeye.desktop.xorg { config = lib.mkIf config.frogeye.desktop.xorg {
wayland.windowManager.sway.enable = true;
xsession = {
enable = true;
# Not using config.xdg.configHome because it needs to be $HOME-relative paths and path manipulation is hard
scriptPath = ".config/xsession";
profilePath = ".config/xprofile";
windowManager = {
i3.enable = true;
};
numlock.enable = config.frogeye.desktop.numlock;
};
programs = { programs = {
# Terminal # Terminal
bash.shellAliases = { bash.shellAliases = {
x = "startx ${config.home.homeDirectory}/${config.xsession.scriptPath}; logout";
lmms = "lmms --config ${config.xdg.configHome}/lmmsrc.xml"; lmms = "lmms --config ${config.xdg.configHome}/lmmsrc.xml";
}; };
rofi = { rofi = {
# TODO This theme template, that was used for Arch, looks much better: # TODO This theme template, that was used for Arch, looks much better:
# https://gitlab.com/jordiorlando/base16-rofi/-/blob/master/templates/default.mustache # https://gitlab.com/jordiorlando/base16-rofi/-/blob/master/templates/default.mustache
enable = true; enable = true;
pass.enable = true; pass = {
enable = true;
package = pkgs.rofi-pass-wayland;
};
extraConfig = { extraConfig = {
lazy-grab = false; lazy-grab = false;
matching = "regex"; matching = "regex";
@ -111,7 +100,6 @@
}; };
services = { services = {
blueman-applet.enable = true; blueman-applet.enable = true;
unclutter.enable = true;
dunst = { dunst = {
enable = true; enable = true;
settings = with config.lib.stylix.colors.withHashtag; { settings = with config.lib.stylix.colors.withHashtag; {
@ -139,7 +127,7 @@
}; };
}; };
}; };
keynav.enable = true; kanshi.enable = true;
}; };
home = { home = {
@ -176,13 +164,13 @@
meld meld
python3Packages.magic python3Packages.magic
bluetuith bluetuith
trayer # For occasional applications that want to put themselves in tray
# x11-exclusive # wayland exclusive
simplescreenrecorder wl-clipboard # Easy copy/paste from/to terminal
trayer # TODO Clipboard history?
xclip wdisplays # Ad-hoc display setup GUI
xorg.xinit wf-recorder # Screen recorder (much more basic than simplescreenrecorder but alright)
scrot
]; ];
sessionVariables = { sessionVariables = {
# XAUTHORITY = "${config.xdg.configHome}/Xauthority"; # Disabled as this causes lock-ups with DMs # XAUTHORITY = "${config.xdg.configHome}/Xauthority"; # Disabled as this causes lock-ups with DMs

View file

@ -1,23 +1,12 @@
{ {
pkgs ? import <nixpkgs> { # nixpkgs ? builtins.getFlake "github:GeoffreyFrogeye/nixpkgs/zelbar",
nixpkgs ? /nix/store/8g86qw3c2fr56bhhvqznrlic4jig9hb3-source,
pkgs ? import nixpkgs {
config = { }; config = { };
overlays = [ ]; overlays = [ ];
}, },
... ...
}: }:
let
lemonbar = (
pkgs.lemonbar-xft.overrideAttrs (old: {
src = pkgs.fetchFromGitHub {
owner = "drscream";
repo = "lemonbar-xft";
rev = "a64a2a6a6d643f4d92f9d7600722710eebce7bdb";
sha256 = "sha256-T5FhEPIiDt/9paJwL9Sj84CBtA0YFi1hZz0+87Hd6jU=";
# https://github.com/drscream/lemonbar-xft/pull/2
};
})
);
in
# Tried using pyproject.nix but mpd2 dependency wouldn't resolve, # Tried using pyproject.nix but mpd2 dependency wouldn't resolve,
# is called pyton-mpd2 on PyPi but mpd2 in nixpkgs. # is called pyton-mpd2 on PyPi but mpd2 in nixpkgs.
pkgs.python3Packages.buildPythonApplication rec { pkgs.python3Packages.buildPythonApplication rec {
@ -31,12 +20,12 @@ pkgs.python3Packages.buildPythonApplication rec {
pygobject3 pygobject3
rich rich
]; ];
nativeBuildInputs = # TODO Might just be buildInputs, maybe without the need for prefix?
[ lemonbar ] nativeBuildInputs = with pkgs; [
++ (with pkgs; [ wirelesstools
wirelesstools playerctl
playerctl zelbar
]); ];
makeWrapperArgs = [ makeWrapperArgs = [
"--prefix PATH : ${pkgs.lib.makeBinPath nativeBuildInputs}" "--prefix PATH : ${pkgs.lib.makeBinPath nativeBuildInputs}"
"--prefix GI_TYPELIB_PATH : ${GI_TYPELIB_PATH}" "--prefix GI_TYPELIB_PATH : ${GI_TYPELIB_PATH}"

View file

@ -36,9 +36,7 @@ def main() -> None:
theme = rich.terminal_theme.TerminalTheme( theme = rich.terminal_theme.TerminalTheme(
base16_color(0x0), base16_color(0x0),
base16_color( base16_color(0x7),
0x0
), # TODO should be 7, currently 0 so it's compatible with v2
[ [
base16_color(0x0), # black base16_color(0x0), # black
base16_color(0x8), # red base16_color(0x8), # red
@ -68,7 +66,7 @@ def main() -> None:
workspaces_suffixes = "▲■" workspaces_suffixes = "▲■"
workspaces_names = { workspaces_names = {
str(i + 1): f"{i+1} {c}" for i, c in enumerate(workspaces_suffixes) str(i + 1): f"{i + 1} {c}" for i, c in enumerate(workspaces_suffixes)
} }
color = rich.color.Color.parse color = rich.color.Color.parse

View file

@ -4,6 +4,7 @@ import datetime
import enum import enum
import logging import logging
import signal import signal
import sys
import typing import typing
import gi import gi
@ -62,6 +63,10 @@ def clip(text: str, length: int = 30) -> str:
return text return text
def color_0x(color: rich.color.ColorTriplet) -> str:
return f"0x{color.red:02X}{color.green:02X}{color.blue:02X}"
class ComposableText(typing.Generic[P, C]): class ComposableText(typing.Generic[P, C]):
def __init__( def __init__(
self, self,
@ -73,6 +78,7 @@ class ComposableText(typing.Generic[P, C]):
self.sortKey = sort_key self.sortKey = sort_key
if parent: if parent:
self.set_parent(parent) self.set_parent(parent)
self.screen = self.get_first_parent_of_type(Screen)
self.bar = self.get_first_parent_of_type(Bar) self.bar = self.get_first_parent_of_type(Bar)
def set_parent(self, parent: P) -> None: def set_parent(self, parent: P) -> None:
@ -92,12 +98,14 @@ class ComposableText(typing.Generic[P, C]):
def get_first_parent_of_type(self, typ: type[T]) -> T: def get_first_parent_of_type(self, typ: type[T]) -> T:
parent = self parent = self
while not isinstance(parent, typ): while not isinstance(parent, typ):
assert parent.parent, f"{self} doesn't have a parent of {typ}" if not parent.parent:
msg = f"{self} doesn't have a parent of {typ}"
raise RuntimeError(msg)
parent = parent.parent parent = parent.parent
return parent return parent
def update_markup(self) -> None: def update_markup(self) -> None:
self.bar.refresh.set() self.parent.update_markup()
# TODO OPTI See if worth caching the output # TODO OPTI See if worth caching the output
def generate_markup(self) -> str: def generate_markup(self) -> str:
@ -191,23 +199,24 @@ class Section(ComposableText):
else: else:
self.animationTask = self.bar.taskGroup.create_task(self.animate()) self.animationTask = self.bar.taskGroup.create_task(self.animate())
def set_action( def set_action(self, button: Button, callback: typing.Callable | None) -> None:
self, button: Button, callback: typing.Callable | None
) -> None:
if button in self.actions: if button in self.actions:
command = self.actions[button] command = self.actions[button]
self.bar.remove_action(command) self.bar.remove_action(command)
del self.actions[button] del self.actions[button]
if callback: if callback:
command = self.bar.add_action(callback) command = self.screen.add_action(callback)
self.actions[button] = command self.actions[button] = command
def generate_markup(self) -> str: def generate_markup(self) -> str:
assert not self.is_hidden() assert not self.is_hidden()
pad = max(0, self.size - len(self.text)) pad = max(0, self.size - len(self.text))
text = self.text[: self.size] + " " * pad text = self.text[: self.size] + " " * pad
for button, command in self.actions.items(): if text:
text = "%{A" + button.value + ":" + command + ":}" + text + "%{A}" for button, command in self.actions.items():
# TODO zelbar doesn't support other button types
if button == Button.CLICK_LEFT:
text = "%{A:" + command + "}" + text
return text return text
@ -239,8 +248,8 @@ class Module(ComposableText):
class Alignment(enum.Enum): class Alignment(enum.Enum):
LEFT = "l" LEFT = "l"
RIGHT = "r"
CENTER = "c" CENTER = "c"
RIGHT = "r"
class Side(ComposableText): class Side(ComposableText):
@ -255,38 +264,64 @@ class Side(ComposableText):
def generate_markup(self) -> str: def generate_markup(self) -> str:
if not self.children: if not self.children:
return "" return ""
text = "%{" + self.alignment.value + "}" markup = ""
last_section: Section | None = None last_section: Section | None = None
default = self.bar.theme.background_color
current = default # Fallback value
def text(
text: str,
bg: rich.color.ColorTriplet = default,
fg: rich.color.ColorTriplet = default,
) -> None:
if not text:
return ""
return (
"%{F:"
+ color_0x(fg)
+ "}%{B:"
+ color_0x(bg)
+ "}%{"
+ self.alignment.value
+ "}"
+ text
)
for module in self.children: for module in self.children:
for section in module.get_sections(): for section in module.get_sections():
if section.is_hidden(): if section.is_hidden():
continue continue
hexa = section.color.get_truecolor(theme=self.bar.theme).hex current = section.color.get_truecolor(theme=self.bar.theme)
if last_section is None: if last_section is None:
text += ( markup += (
"%{B" + hexa + "}%{F-}" text("", default, current)
if self.alignment == Alignment.LEFT if self.alignment != Alignment.LEFT
else "%{B-}%{F" + hexa + "}%{R}%{F-}" else ""
) )
elif isinstance(last_section, SpacerSection): elif isinstance(last_section, SpacerSection):
text += "%{B-}%{F" + hexa + "}%{R}%{F-}" markup += text("", default, current)
elif last_section.color == section.color:
markup += text(
"" if self.alignment == Alignment.RIGHT else "",
current,
default,
)
else: else:
if self.alignment == Alignment.RIGHT: lastone = last_section.color.get_truecolor(theme=self.bar.theme)
text += ( markup += (
"" text("", lastone, current)
if last_section.color == section.color if self.alignment == Alignment.RIGHT
else "%{F" + hexa + "}%{R}" else text("", current, lastone)
) )
elif last_section.color == section.color: markup += text(section.get_markup(), current, default)
text += ""
else:
text += "%{R}%{B" + hexa + "}"
text += "%{F-}"
text += section.get_markup()
last_section = section last_section = section
if self.alignment != Alignment.RIGHT and last_section: markup += (
text += "%{R}%{B-}" text("", default, current)
return text if self.alignment != Alignment.RIGHT and last_section
else ""
)
return markup
class Screen(ComposableText): class Screen(ComposableText):
@ -296,15 +331,79 @@ class Screen(ComposableText):
self.children: typing.MutableSequence[Side] self.children: typing.MutableSequence[Side]
self.output = output self.output = output
self.refresh = asyncio.Event()
self.actionIndex = 0
self.actions: dict[str, typing.Callable] = {}
for alignment in Alignment: for alignment in Alignment:
Side(parent=self, alignment=alignment) Side(parent=self, alignment=alignment)
def update_markup(self) -> str:
self.screen.refresh.set()
def generate_markup(self) -> str: def generate_markup(self) -> str:
return ("%{Sn" + self.output + "}") + "".join( return "".join(side.get_markup() for side in self.children) + "\n"
side.get_markup() for side in self.children
async def run(self) -> None:
cmd = [
"zelbar",
"-btm",
"-g",
"0:20",
"-fn",
"DejaVuSansM Nerd Font:size=13",
"-F",
color_0x(self.bar.theme.foreground_color),
"-B",
color_0x(self.bar.theme.background_color),
"-o",
self.output,
]
print(" ".join(cmd))
proc = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stdin=asyncio.subprocess.PIPE,
) )
async def refresher() -> None:
assert proc.stdin
while True:
await self.refresh.wait()
self.refresh.clear()
markup = self.get_markup()
# sys.stdout.write(markup) # DEBUG
proc.stdin.write(markup.encode())
async def action_handler() -> None:
assert proc.stdout
while True:
line = await proc.stdout.readline()
try:
command = line.decode().strip()
except UnicodeDecodeError:
# FIXME zelbar seems to have some memory issues
log.exception("Not unicode: %s", str(line))
continue
callback = self.actions.get(command)
if callback is None:
log.error("Unknown command: %s", command)
continue
callback()
self.bar.add_long_running_task(refresher())
self.bar.add_long_running_task(action_handler())
def add_action(self, callback: typing.Callable) -> str:
command = f"com{self.actionIndex:x}"
self.actions[command] = callback
self.actionIndex += 1
return command
def remove_action(self, command: str) -> None:
del self.actions[command]
RICH_DEFAULT_THEME = rich.terminal_theme.DEFAULT_TERMINAL_THEME RICH_DEFAULT_THEME = rich.terminal_theme.DEFAULT_TERMINAL_THEME
@ -322,11 +421,8 @@ class Bar(ComposableText):
self.longRunningTasks: list[asyncio.Task] = [] self.longRunningTasks: list[asyncio.Task] = []
self.theme = theme self.theme = theme
self.refresh = asyncio.Event()
self.taskGroup = asyncio.TaskGroup() self.taskGroup = asyncio.TaskGroup()
self.providers: list[Provider] = [] self.providers: list[Provider] = []
self.actionIndex = 0
self.actions: dict[str, typing.Callable] = {}
self.periodicProviderTask: typing.Coroutine | None = None self.periodicProviderTask: typing.Coroutine | None = None
@ -343,45 +439,9 @@ class Bar(ComposableText):
self.longRunningTasks.append(task) self.longRunningTasks.append(task)
async def run(self) -> None: async def run(self) -> None:
cmd = [
"lemonbar",
"-b",
"-a",
"64",
"-f",
"DejaVuSansM Nerd Font:size=10",
"-F",
self.theme.foreground_color.hex,
"-B",
self.theme.background_color.hex,
]
proc = await asyncio.create_subprocess_exec(
*cmd, stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE
)
async def refresher() -> None:
assert proc.stdin
while True:
await self.refresh.wait()
self.refresh.clear()
markup = self.get_markup()
proc.stdin.write(markup.encode())
async def action_handler() -> None:
assert proc.stdout
while True:
line = await proc.stdout.readline()
command = line.decode().strip()
callback = self.actions.get(command)
if callback is None:
# In some conditions on start it's empty
log.error("Unknown command: %s", command)
return
callback()
async with self.taskGroup: async with self.taskGroup:
self.add_long_running_task(refresher()) for screen in self.children:
self.add_long_running_task(action_handler()) await screen.run()
for provider in self.providers: for provider in self.providers:
self.add_long_running_task(provider.run()) self.add_long_running_task(provider.run())
@ -393,9 +453,6 @@ class Bar(ComposableText):
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
loop.add_signal_handler(signal.SIGINT, finish) loop.add_signal_handler(signal.SIGINT, finish)
def generate_markup(self) -> str:
return "".join(screen.get_markup() for screen in self.children) + "\n"
def add_provider( def add_provider(
self, self,
provider: "Provider", provider: "Provider",
@ -405,24 +462,13 @@ class Bar(ComposableText):
modules = [] modules = []
for s, screen in enumerate(self.children): for s, screen in enumerate(self.children):
if screen_num is None or s == screen_num: if screen_num is None or s == screen_num:
side = next( side = next(filter(lambda s: s.alignment == alignment, screen.children))
filter(lambda s: s.alignment == alignment, screen.children)
)
module = Module(parent=side) module = Module(parent=side)
modules.append(module) modules.append(module)
provider.modules = modules provider.modules = modules
if modules: if modules:
self.providers.append(provider) self.providers.append(provider)
def add_action(self, callback: typing.Callable) -> str:
command = f"{self.actionIndex:x}"
self.actions[command] = callback
self.actionIndex += 1
return command
def remove_action(self, command: str) -> None:
del self.actions[command]
def launch(self) -> None: def launch(self) -> None:
# Using GLib's event loop so we can run GLib's code # Using GLib's event loop so we can run GLib's code
policy = gi.events.GLibEventLoopPolicy() policy = gi.events.GLibEventLoopPolicy()
@ -430,6 +476,9 @@ class Bar(ComposableText):
loop = policy.get_event_loop() loop = policy.get_event_loop()
loop.run_until_complete(self.run()) loop.run_until_complete(self.run())
def update_markup(self) -> None:
pass
class Provider: class Provider:
section_type: type[Section] = Section section_type: type[Section] = Section
@ -462,9 +511,7 @@ class SingleSectionProvider(MirrorProvider):
class StaticProvider(SingleSectionProvider): class StaticProvider(SingleSectionProvider):
def __init__( def __init__(self, text: str, color: rich.color.Color = RICH_DEFAULT_COLOR) -> None:
self, text: str, color: rich.color.Color = RICH_DEFAULT_COLOR
) -> None:
super().__init__(color=color) super().__init__(color=color)
self.text = text self.text = text
@ -524,9 +571,7 @@ class StatefulSectionProvider(Provider):
section_type = StatefulSection section_type = StatefulSection
class SingleStatefulSectionProvider( class SingleStatefulSectionProvider(StatefulSectionProvider, SingleSectionProvider):
StatefulSectionProvider, SingleSectionProvider
):
section: StatefulSection section: StatefulSection
@ -545,9 +590,7 @@ class MultiSectionsProvider(Provider):
async def do_nothing() -> None: async def do_nothing() -> None:
pass pass
async def update_sections( async def update_sections(self, sections: set[Sortable], module: Module) -> None:
self, sections: set[Sortable], module: Module
) -> None:
module_sections = self.sectionKeys[module] module_sections = self.sectionKeys[module]
async with asyncio.TaskGroup() as tg: async with asyncio.TaskGroup() as tg:
for sort_key in sections: for sort_key in sections:
@ -556,9 +599,7 @@ class MultiSectionsProvider(Provider):
section = self.section_type( section = self.section_type(
parent=module, sort_key=sort_key, color=self.color parent=module, sort_key=sort_key, color=self.color
) )
self.updaters[section] = await self.get_section_updater( self.updaters[section] = await self.get_section_updater(section)
section
)
module_sections[sort_key] = section module_sections[sort_key] = section
updater = self.updaters[section] updater = self.updaters[section]
@ -608,9 +649,7 @@ class PeriodicProvider(Provider):
bar.add_long_running_task(bar.periodicProviderTask) bar.add_long_running_task(bar.periodicProviderTask)
class PeriodicStatefulProvider( class PeriodicStatefulProvider(SingleStatefulSectionProvider, PeriodicProvider):
SingleStatefulSectionProvider, PeriodicProvider
):
async def run(self) -> None: async def run(self) -> None:
await super().run() await super().run()
self.section.set_changed_state(self.loop) self.section.set_changed_state(self.loop)

View file

@ -6,25 +6,25 @@
}: }:
{ {
config = lib.mkIf config.frogeye.desktop.xorg { config = lib.mkIf config.frogeye.desktop.xorg {
xsession.windowManager.i3.config.bars = [ ]; wayland.windowManager.sway.config.bars = [ ];
programs.autorandr.hooks.postswitch = { programs.autorandr.hooks.postswitch = {
frobar = "${pkgs.systemd}/bin/systemctl --user restart frobar"; frobar = "${pkgs.systemd}/bin/systemctl --user restart frobar";
}; };
systemd.user.services.frobar = { systemd.user.services.frobar = {
Unit = { Unit = {
Description = "frobar"; Description = "frobar";
After = [ "graphical-session-pre.target" ]; After = [ "kanshi.service" ];
PartOf = [ "graphical-session.target" ]; PartOf = [ "kanshi.service" ];
}; };
Service = { Service = {
# Wait for i3 to start. Can't use ExecStartPre because otherwise it blocks graphical-session.target, and there's nothing i3/systemd # Wait for i3 to start. Can't use ExecStartPre because otherwise it blocks graphical-session.target, and there's nothing i3/systemd
# TODO Do that better # TODO Do that better
ExecStart = ''${pkgs.bash}/bin/bash -c "while ! ${pkgs.i3}/bin/i3-msg; do ${pkgs.coreutils}/bin/sleep 1; done; ${pkgs.callPackage ./. { }}/bin/frobar"''; ExecStart = ''${pkgs.bash}/bin/bash -c "while ! ${pkgs.sway}/bin/swaymsg; do ${pkgs.coreutils}/bin/sleep 1; done; ${pkgs.callPackage ./. { }}/bin/frobar"'';
}; };
Install = { Install = {
WantedBy = [ "graphical-session.target" ]; WantedBy = [ "kanshi.service" ];
}; };
}; };
}; };

View file

@ -19,68 +19,77 @@ let
</svg> </svg>
''; '';
lockPng = pkgs.runCommand "lock.png" { } "${pkgs.imagemagick}/bin/convert ${lockSvg} $out"; lockPng = pkgs.runCommand "lock.png" { } "${pkgs.imagemagick}/bin/convert ${lockSvg} $out";
mod = config.xsession.windowManager.i3.config.modifier; mod = config.wayland.windowManager.sway.config.modifier;
xautolockState = "${config.xdg.cacheHome}/xautolock"; idleTime = 10; # minutes
# Like wayidle but exit when there is activity
wayresume = pkgs.wayidle.overrideAttrs (old: {
pname = "wayresume";
patches = (old.patches or [ ]) ++ [ ./wayresume.diff ];
});
powerScreen =
state: ''${config.wayland.windowManager.sway.package}/bin/swaymsg "output * power ${state}"'';
in in
{ {
# Can't use loginctl lock-session, auto-opened session has type greeter, even without that it couldn't figure it out
config = lib.mkIf config.frogeye.desktop.xorg { config = lib.mkIf config.frogeye.desktop.xorg {
home.packages = [ programs.swaylock = {
(pkgs.writeShellApplication { enable = true;
name = "xlock"; settings = {
text = '' color = (builtins.substring 1 6 lockColors.d);
${config.frogeye.hooks.lock} image = lockPng;
# TODO Reevaluate whether we want this or not tiling = true;
if ! ${pkgs.lightdm}/bin/dm-tool lock ignore-empty-password = true;
then indicator-idle-visible = false;
if [ -d ${config.xdg.cacheHome}/lockpatterns ] show-failed-attempts = true;
then indicator-radius = 100;
pattern=$(${pkgs.findutils} ${config.xdg.cacheHome}/lockpatterns | sort -R | head -1)
else
pattern=${lockPng}
fi
revert() {
${pkgs.xorg.xset}/bin/xset dpms 0 0 0
}
trap revert SIGHUP SIGINT SIGTERM
${pkgs.xorg.xset}/bin/xset dpms 5 5 5
${pkgs.i3lock}/bin/i3lock --nofork --color ${
builtins.substring 1 6 lockColors.d
} --image="$pattern" --tiling --ignore-empty-password
revert
fi
'';
})
];
xsession.windowManager.i3.config = {
keybindings = {
# Screen off commands
"${mod}+F1" = "--release exec --no-startup-id ${pkgs.xorg.xset}/bin/xset dpms force off";
# Toggle to save on buttons
# xautolock -toggle doesn't allow to read state.
# Writing into a file also allows frobar to display a lock icon
"${mod}+F5" = "exec --no-startup-id ${pkgs.writeShellScript "xautolock-toggle" ''
state="$(cat "${xautolockState}")"
if [ "$state" = "disabled" ]
then
${pkgs.xautolock}/bin/xautolock -enable
echo enabled > ${xautolockState}
else
${pkgs.xautolock}/bin/xautolock -disable
echo disabled > ${xautolockState}
fi
''}";
}; };
startup = [ };
# Stop screen after 10 minutes, 1 minutes after lock it services.swayidle = {
enable = true;
timeouts = [
{ {
notification = false; # Warn
command = "${pkgs.writeShellScript "xautolock-start" '' timeout = (idleTime - 1) * 60;
echo enabled > ${xautolockState} command = ''${lib.getExe pkgs.libnotify} "Screen turns off in 1 minute"'';
${pkgs.xautolock}/bin/xautolock -time 10 -locker '${pkgs.xorg.xset}/bin/xset dpms force standby' -killtime 1 -killer xlock }
{
# Screen off
timeout = idleTime * 60;
command = powerScreen "off";
resumeCommand = powerScreen "on";
}
{
# Lock
# Call swaylock, but also call another swayidle so screen is turned off after 10 seconds instead of minutes
timeout = (idleTime + 1) * 60;
command = "${pkgs.writeShellScript "lock" ''
${config.frogeye.hooks.lock}
${lib.getExe config.services.swayidle.package} timeout 10 ${lib.strings.escapeShellArg (powerScreen "off")} resume ${lib.strings.escapeShellArg (powerScreen "on")} &
si=$!
revert() {
kill $si
}
trap revert 0
${lib.getExe config.programs.swaylock.package}
''}"; ''}";
} }
# services.screen-locker.xautolock is hardcoded to use systemd for -locker (doesn't even work...)
]; ];
}; };
stylix.targets.swaylock.enable = false;
wayland.windowManager.sway.config.keybindings = lib.mkOptionDefault {
# Turn screen off until activity
"${mod}+F1" =
''--release exec ${powerScreen "off"} && ${lib.getExe wayresume} -t0; ${powerScreen "on"}'';
# Toggle to save on buttons
# TODO Bar indicator
"${mod}+F5" = "exec --no-startup-id ${pkgs.writeShellScript "swayidle-toggle" ''
if ${pkgs.systemd}/bin/systemctl --user is-active swayidle --quiet
then
${pkgs.systemd}/bin/systemctl --user stop swayidle
else
${pkgs.systemd}/bin/systemctl --user start swayidle
fi
''}";
};
}; };
} }

View file

@ -0,0 +1,14 @@
diff --git a/src/main.rs b/src/main.rs
index 85930bd..586ee55 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -110,7 +110,7 @@ impl SeatHandler for State {
}
fn notification_cb(ctx: EventCtx<State, ExtIdleNotificationV1>) {
- if let ext_idle_notification_v1::Event::Idled = ctx.event {
+ if let ext_idle_notification_v1::Event::Resumed = ctx.event {
ctx.state.exit = true;
}
}

View file

@ -58,7 +58,7 @@
}; };
}; };
}; };
xsession.windowManager.i3.config.keybindings = { wayland.windowManager.sway.config.keybindings = lib.mkOptionDefault {
"XF86AudioPrev" = "exec ${lib.getExe pkgs.playerctl} previous"; "XF86AudioPrev" = "exec ${lib.getExe pkgs.playerctl} previous";
"XF86AudioPlay" = "exec ${lib.getExe pkgs.playerctl} play-pause"; "XF86AudioPlay" = "exec ${lib.getExe pkgs.playerctl} play-pause";
"XF86AudioNext" = "exec ${lib.getExe pkgs.playerctl} next"; "XF86AudioNext" = "exec ${lib.getExe pkgs.playerctl} next";

View file

@ -33,7 +33,7 @@ in
return_bindings = false; return_bindings = false;
}; };
}; };
xsession.windowManager.i3.config.window.commands = [ wayland.windowManager.sway.config.window.commands = [
# Open specific applications in floating mode # Open specific applications in floating mode
{ {
criteria = { criteria = {

View file

@ -1,32 +0,0 @@
{
pkgs,
lib,
config,
...
}:
let
# UPST
sct = pkgs.sct.overrideAttrs (old: {
patches = (old.patches or [ ]) ++ [
./sct_aarch64.patch
];
});
in
{
config = lib.mkIf config.frogeye.desktop.xorg {
frogeye.desktop.i3.bindmodes = {
"Temperature [R] Red [D] Dust storm [C] Campfire [O] Normal [A] All nighter [B] Blue" = {
bindings = {
"r" = "exec ${sct}/bin/sct 1000";
"d" = "exec ${sct}/bin/sct 2000";
"c" = "exec ${sct}/bin/sct 4500";
"o" = "exec ${sct}/bin/sct";
"a" = "exec ${sct}/bin/sct 8000";
"b" = "exec ${sct}/bin/sct 10000";
};
mod_enter = "y";
};
};
home.packages = [ sct ];
};
}

View file

@ -1,10 +0,0 @@
--- a/Makefile
+++ b/Makefile
@@ -32,6 +32,7 @@ LIBDIR_i386=$(PREFIX)/lib
LIBDIR_i686=$(PREFIX)/lib
LIBDIR_amd64=$(PREFIX)/lib
LIBDIR_x86_64=$(PREFIX)/lib64
+LIBDIR_aarch64=$(PREFIX)/lib
LIBDIR?=$(LIBDIR_$(MACHINE))
MANDIR_Darwin=$(PREFIX)/share/man
MANDIR_Linux=$(PREFIX)/share/man

View file

@ -6,16 +6,23 @@
}: }:
let let
dir = config.xdg.userDirs.extraConfig.XDG_SCREENSHOTS_DIR; dir = config.xdg.userDirs.extraConfig.XDG_SCREENSHOTS_DIR;
scrot = "${pkgs.scrot}/bin/scrot --exec '${pkgs.coreutils}/bin/mv $f ${dir}/ && ${pkgs.optipng}/bin/optipng ${dir}/$f'"; gs =
mod = config.xsession.windowManager.i3.config.modifier; mode:
pkgs.writeShellScript "grimshot-${mode}" ''
path="${dir}/$(date -Isec).png"
${lib.getExe pkgs.sway-contrib.grimshot} savecopy ${mode} "$path"
${pkgs.optipng}/bin/optipng "$path"
'';
mod = config.wayland.windowManager.sway.config.modifier;
in in
{ {
config = lib.mkIf config.frogeye.desktop.xorg { config = lib.mkIf config.frogeye.desktop.xorg {
frogeye.folders.screenshots.path = "Screenshots"; frogeye.folders.screenshots.path = "Screenshots";
xsession.windowManager.i3.config.keybindings = { home.packages = [ pkgs.sway-contrib.grimshot ];
"Print" = "exec ${scrot} --focused"; wayland.windowManager.sway.config.keybindings = lib.mkOptionDefault {
"${mod}+Print" = "exec ${scrot}"; "Print" = "exec ${gs "active"}";
"Ctrl+Print" = "--release exec ${scrot} --select"; "${mod}+Print" = "exec ${gs "screen"}";
"Ctrl+Print" = "exec ${gs "anything"}";
}; };
}; };
} }

View file

@ -5,15 +5,6 @@
... ...
}: }:
let let
# FOCUS
focus = "exec ${pkgs.writeShellScript "i3-focus-window" ''
WINDOW=`${pkgs.xdotool}/bin/xdotool getwindowfocus`
eval `${pkgs.xdotool}/bin/xdotool getwindowgeometry --shell $WINDOW` # this brings in variables WIDTH and HEIGHT
TX=`${pkgs.coreutils}/bin/expr $WIDTH / 2`
TY=`${pkgs.coreutils}/bin/expr $HEIGHT / 2`
${pkgs.xdotool}/bin/xdotool mousemove -window $WINDOW $TX $TY
''}";
# CARDINALS # CARDINALS
cardinals = [ cardinals = [
{ {
@ -57,15 +48,34 @@ let
forEachWorkspace = f: map (w: f w) workspaces; forEachWorkspace = f: map (w: f w) workspaces;
# MISC # MISC
mod = config.xsession.windowManager.i3.config.modifier; mod = config.wayland.windowManager.sway.config.modifier;
rofi = "exec --no-startup-id ${config.programs.rofi.package}/bin/rofi"; rofi = "exec --no-startup-id ${config.programs.rofi.package}/bin/rofi";
modes = config.frogeye.desktop.i3.bindmodes; modes = config.frogeye.desktop.i3.bindmodes;
x11_screens = config.frogeye.desktop.x11_screens;
screenCombinations = lib.trivial.pipe config.services.kanshi.settings [
# All settings
(builtins.map (
setting:
lib.trivial.pipe setting [
# Profile/setting (assumes one profile is defined at most over one setting)
(lib.attrsets.attrByPath [ "profile" "outputs" ] [ ])
# List of output objects
(builtins.filter (output: (lib.attrsets.attrByPath [ "status" ] [ "enable" ] output) != "disable"))
# List of output objects that are enabled
(builtins.filter (output: (lib.attrsets.attrByPath [ "criteria" ] [ "*" ] output) != "*"))
# Lost of outputs objects with a defined criteria
# (at this point we could sort by position like we do in frobar, but we can rely on definition order)
(builtins.map (output: output.criteria))
# List of output names
]
))
# List of list of output name
(builtins.filter (outputs: builtins.length outputs >= 2))
# List of list of output name with at least two screens
];
in in
{ {
config = lib.mkIf config.xsession.windowManager.i3.enable { config = lib.mkIf config.wayland.windowManager.sway.enable {
stylix.targets.i3.enable = false;
services.picom.enable = true;
xdg.configFile = { xdg.configFile = {
"rofimoji.rc" = { "rofimoji.rc" = {
text = '' text = ''
@ -75,10 +85,18 @@ in
''; '';
}; };
}; };
xsession.windowManager.i3.config = { wayland = {
systemd.target = "sway-session.target";
windowManager.sway.checkConfig = false; # us_qwerty-fr is not in the testing environment
};
# https://github.com/nix-community/home-manager/issues/5311
# Setting a compiled file is not desirable as it depends on the keyboard's, uuuh, key placement
wayland.windowManager.sway.config = {
input."*".xkb_layout = "us_qwerty-fr";
modifier = lib.mkDefault "Mod4"; modifier = lib.mkDefault "Mod4";
fonts = { fonts = {
names = [ config.stylix.fonts.sansSerif.name ]; names = [ config.stylix.fonts.sansSerif.name ];
size = lib.mkForce 8.0;
}; };
terminal = "alacritty"; terminal = "alacritty";
colors = colors =
@ -125,8 +143,11 @@ in
background = base07; background = base07;
# I set the color of the active tab as the the background color of the terminal so they merge together. # I set the color of the active tab as the the background color of the terminal so they merge together.
}; };
focus.followMouse = false; focus = {
keybindings = followMouse = false;
mouseWarping = "container";
};
keybindings = lib.mkOptionDefault (
{ {
# Compatibility layer for people coming from other backgrounds # Compatibility layer for people coming from other backgrounds
"Mod1+Tab" = "${rofi} -modi window -show window"; "Mod1+Tab" = "${rofi} -modi window -show window";
@ -142,68 +163,56 @@ in
"${mod}+Shift+d" = "${rofi} -modi drun -show drun"; "${mod}+Shift+d" = "${rofi} -modi drun -show drun";
# Start Applications # Start Applications
"${mod}+p" = "exec ${pkgs.xfce.thunar}/bin/thunar"; "${mod}+p" = "exec ${pkgs.xfce.thunar}/bin/thunar";
# Misc
"${mod}+F10" = "exec ${pkgs.writeShellScript "show-keyboard-layout" ''
layout=`${pkgs.xorg.setxkbmap}/bin/setxkbmap -query | ${pkgs.gnugrep}/bin/grep ^layout: | ${pkgs.gawk}/bin/awk '{ print $2 }'`
${pkgs.libgnomekbd}/bin/gkbd-keyboard-display -l $layout
''}";
# workspace back and forth (with/without active container) # workspace back and forth (with/without active container)
"${mod}+b" = "workspace back_and_forth; ${focus}"; "${mod}+b" = "workspace back_and_forth";
"${mod}+Shift+b" = "move container to workspace back_and_forth; workspace back_and_forth; ${focus}"; "${mod}+Shift+b" = "move container to workspace back_and_forth; workspace back_and_forth";
# Split horizontally (rebound from ${mod}+b because used above)
"${mod}+g" = "splith";
# Change container layout # Change container layout
"${mod}+g" = "split h; ${focus}"; "${mod}+q" = "focus child";
"${mod}+v" = "split v; ${focus}";
"${mod}+f" = "fullscreen toggle; ${focus}";
"${mod}+s" = "layout stacking; ${focus}";
"${mod}+w" = "layout tabbed; ${focus}";
"${mod}+e" = "layout toggle split; ${focus}";
"${mod}+Shift+space" = "floating toggle; ${focus}";
# Focus container
"${mod}+space" = "focus mode_toggle; ${focus}";
"${mod}+a" = "focus parent; ${focus}";
"${mod}+q" = "focus child; ${focus}";
# i3 control # i3 control
"${mod}+Shift+c" = "reload";
"${mod}+Shift+r" = "restart"; "${mod}+Shift+r" = "restart";
"${mod}+Shift+e" = "exit"; # Warp around (ex-keynav)
"Mod4+Mod1+x" = "exec ${lib.getExe pkgs.warpd} --hint";
"Mod4+Mod1+c" = "exec ${lib.getExe pkgs.warpd} --normal";
"Mod4+Mod1+g" = "exec ${lib.getExe pkgs.warpd} --grid";
} }
// lib.mapAttrs' (k: v: lib.nameValuePair v.enter "mode ${v.name}") ( // lib.mapAttrs' (k: v: lib.nameValuePair v.enter ''mode "${v.name}"'') (
lib.filterAttrs (k: v: v.enter != null) modes lib.filterAttrs (k: v: v.enter != null) modes
) )
// lib.attrsets.mergeAttrsList ( // lib.attrsets.mergeAttrsList (
forEachCardinal (c: { forEachCardinal (c: {
# change focus
"${mod}+${c.vi}" = "focus ${c.container}; ${focus}";
# move focused window
"${mod}+Shift+${c.vi}" = "move ${c.container}; ${focus}";
#navigate workspaces next / previous #navigate workspaces next / previous
"${mod}+Ctrl+${c.vi}" = "workspace ${c.workspace}; ${focus}"; "${mod}+Ctrl+${c.vi}" = "workspace ${c.workspace}";
# Move to workspace next / previous with focused container # Move to workspace next / previous with focused container
"${mod}+Ctrl+Shift+${c.vi}" = "move container to workspace ${c.workspace}; workspace ${c.workspace}; ${focus}"; "${mod}+Ctrl+Shift+${c.vi}" =
"move container to workspace ${c.workspace}; workspace ${c.workspace}";
# move workspaces to screen (arrow keys) # move workspaces to screen (arrow keys)
"${mod}+Ctrl+Shift+${c.arrow}" = "move workspace to output ${c.output}; ${focus}"; "${mod}+Ctrl+Shift+${c.arrow}" = "move workspace to output ${c.output}";
}) })
) )
// lib.attrsets.mergeAttrsList ( // lib.attrsets.mergeAttrsList (
forEachWorkspace (w: { forEachWorkspace (w: {
# Switch to workspace # move container to workspace, do not follow (normally bound to ${mod}+Shift+${w.key}
"${mod}+${w.key}" = "workspace ${w.name}; ${focus}"; "${mod}+Ctrl+${w.key}" = "move container to workspace number ${w.name}";
# move focused container to workspace # move container to workspace, follow it
"${mod}+ctrl+${w.key}" = "move container to workspace ${w.name}; ${focus}"; "${mod}+Shift+${w.key}" =
# move to workspace with focused container "move container to workspace number ${w.name}; workspace number ${w.name}";
"${mod}+shift+${w.key}" = "move container to workspace ${w.name}; workspace ${w.name}; ${focus}";
}) })
);
modes = lib.mapAttrs' (
k: v:
lib.nameValuePair v.name (
v.bindings
// lib.optionalAttrs v.return_bindings {
"Return" = "mode default";
"Escape" = "mode default";
}
) )
) modes; );
modes = lib.mkOptionDefault (
lib.mapAttrs' (
k: v:
lib.nameValuePair v.name (
v.bindings
// lib.optionalAttrs v.return_bindings {
"Return" = "mode default";
"Escape" = "mode default";
}
)
) modes
);
window = { window = {
hideEdgeBorders = "both"; hideEdgeBorders = "both";
titlebar = false; # So that single-container screens are basically almost fullscreen titlebar = false; # So that single-container screens are basically almost fullscreen
@ -223,9 +232,11 @@ in
{ window_role = "task_dialog"; } { window_role = "task_dialog"; }
]; ];
}; };
seat."*" = {
hide_cursor = "10";
};
startup = [ startup = [
{ {
notification = false;
command = "${ command = "${
pkgs.writeShellApplication { pkgs.writeShellApplication {
name = "batteryNotify"; name = "batteryNotify";
@ -241,28 +252,24 @@ in
} }
]; ];
workspaceLayout = "tabbed"; workspaceLayout = "tabbed";
focus.mouseWarping = true; # i3 only supports warping to workspace, hence ${focus} workspaceOutputAssign = lib.mkIf (builtins.length screenCombinations > 0) (
workspaceOutputAssign = forEachWorkspace (w: { forEachWorkspace (w: {
output = builtins.elemAt x11_screens (lib.mod w.id (builtins.length x11_screens)); output = builtins.map (
workspace = w.name; combination: builtins.elemAt combination (lib.mod w.id (builtins.length combination))
}); ) screenCombinations;
workspace = w.name;
})
);
}; };
frogeye.desktop.i3.bindmodes = { frogeye.desktop.i3.bindmodes = {
"Resize" = {
bindings = {
"h" = "resize shrink width 10 px or 10 ppt; ${focus}";
"j" = "resize grow height 10 px or 10 ppt; ${focus}";
"k" = "resize shrink height 10 px or 10 ppt; ${focus}";
"l" = "resize grow width 10 px or 10 ppt; ${focus}";
};
mod_enter = "r";
};
"[L] Vérouillage [E] Déconnexion [S] Veille [H] Hibernation [R] Redémarrage [P] Extinction" = { "[L] Vérouillage [E] Déconnexion [S] Veille [H] Hibernation [R] Redémarrage [P] Extinction" = {
bindings = { bindings = {
"l" = "exec --no-startup-id exec xlock, mode default"; "l" = "exec --no-startup-id ${pkgs.procps}/bin/pkill -USR1 swayidle, mode default";
"e" = "exit, mode default"; "e" = "exit, mode default";
"s" = "exec --no-startup-id exec xlock & ${pkgs.systemd}/bin/systemctl suspend --check-inhibitors=no, mode default"; # TODO Sometimes, exit gets stuck on terminal. Restarting greetd helps.
"h" = "exec --no-startup-id exec xlock & ${pkgs.systemd}/bin/systemctl hibernate, mode default"; "s" =
"exec --no-startup-id ${pkgs.systemd}/bin/systemctl suspend --check-inhibitors=no, mode default";
"h" = "exec --no-startup-id ${pkgs.systemd}/bin/systemctl hibernate, mode default";
"r" = "exec --no-startup-id ${pkgs.systemd}/bin/systemctl reboot, mode default"; "r" = "exec --no-startup-id ${pkgs.systemd}/bin/systemctl reboot, mode default";
"p" = "exec --no-startup-id ${pkgs.systemd}/bin/systemctl poweroff -i, mode default"; "p" = "exec --no-startup-id ${pkgs.systemd}/bin/systemctl poweroff -i, mode default";
}; };

View file

@ -5,7 +5,7 @@
... ...
}: }:
let let
mod = config.xsession.windowManager.i3.config.modifier; mod = config.wayland.windowManager.sway.config.modifier;
in in
{ {
config = lib.mkIf config.frogeye.desktop.xorg { config = lib.mkIf config.frogeye.desktop.xorg {
@ -149,7 +149,7 @@ in
}; };
}; };
}; };
xsession.windowManager.i3.config.keybindings = { wayland.windowManager.sway.config.keybindings = lib.mkOptionDefault {
"${mod}+Return" = "exec ${config.programs.alacritty.package}/bin/alacritty msg create-window -e zsh || exec ${config.programs.alacritty.package}/bin/alacritty -e zsh"; "${mod}+Return" = "exec ${config.programs.alacritty.package}/bin/alacritty msg create-window -e zsh || exec ${config.programs.alacritty.package}/bin/alacritty -e zsh";
# -e zsh is for systems where I can't configure my user's shell # -e zsh is for systems where I can't configure my user's shell
"${mod}+Shift+Return" = "exec ${config.programs.urxvt.package}/bin/urxvt"; "${mod}+Shift+Return" = "exec ${config.programs.urxvt.package}/bin/urxvt";

View file

@ -5,7 +5,7 @@
... ...
}: }:
let let
mod = config.xsession.windowManager.i3.config.modifier; mod = config.wayland.windowManager.sway.config.modifier;
in in
{ {
config = { config = {
@ -67,7 +67,9 @@ in
}; };
password-store.enable = true; password-store.enable = true;
}; };
xsession.windowManager.i3.config.keybindings."${mod}+c" = "exec --no-startup-id ${config.programs.rofi.pass.package}/bin/rofi-pass --last-used"; wayland.windowManager.sway.config.keybindings = lib.mkOptionDefault {
"${mod}+c" = "exec --no-startup-id ${config.programs.rofi.pass.package}/bin/rofi-pass --last-used";
};
# TODO Try autopass.cr # TODO Try autopass.cr
}; };
options = { options = {

View file

@ -32,14 +32,8 @@ in
]; ];
}; };
desktop = { desktop = {
xorg = lib.mkEnableOption "Enable X11 support"; xorg = lib.mkEnableOption "Enable graphics support";
# TODO Use appropriate OS/HM option(s) instead # TODO Rename
numlock = lib.mkEnableOption "Auto-enable numlock";
x11_screens = lib.mkOption {
default = [ "UNSET1" ];
description = "A list of xrandr screen names from left to right.";
type = lib.types.listOf lib.types.str;
};
maxVideoHeight = lib.mkOption { maxVideoHeight = lib.mkOption {
type = lib.types.int; type = lib.types.int;
description = "Maximum video height in pixel the machine can reasonably watch"; description = "Maximum video height in pixel the machine can reasonably watch";

View file

@ -1,29 +0,0 @@
{
pkgs,
lib,
config,
...
}:
{
config = lib.mkIf (builtins.length config.frogeye.desktop.x11_screens > 1) {
services = {
autorandr.enable = true;
xserver.displayManager.lightdm.extraConfig =
let
setupScript = "${
pkgs.writeShellApplication {
name = "greeter-setup-script";
runtimeInputs = [ pkgs.autorandr ];
text = ''
autorandr --change
'';
}
}/bin/greeter-setup-script";
in
''
[Seat:*]
display-setup-script = ${setupScript}
'';
};
};
}

View file

@ -7,10 +7,23 @@
{ {
config = lib.mkIf config.frogeye.desktop.xorg { config = lib.mkIf config.frogeye.desktop.xorg {
boot.kernelModules = [ "i2c-dev" ]; # Allows using ddcutil boot.kernelModules = [ "i2c-dev" ]; # Allows using ddcutil
programs.sway.enable = true;
security.rtkit.enable = true; # Recommended for pipewire security.rtkit.enable = true; # Recommended for pipewire
services = { services = {
blueman.enable = true; blueman.enable = true;
displayManager.defaultSession = "none+i3"; greetd = {
enable = true;
settings = rec {
# Automatically log in as myself.
# Because everything is encrypted and I'm the only user, this is fine.
initial_session = {
command = "${lib.getExe config.programs.sway.package} --unsupported-gpu";
# --unsupported-gpu is for curacao (DisplayLink)
user = "geoffrey";
};
default_session = initial_session;
};
};
udev.packages = with pkgs; [ ddcutil ]; # TODO Doesn't seem to help udev.packages = with pkgs; [ ddcutil ]; # TODO Doesn't seem to help
pipewire = { pipewire = {
enable = true; enable = true;
@ -34,40 +47,20 @@
]; ];
}; };
}; };
# Video # Keyboard layout
xserver = { xserver.xkb.extraLayouts.us_qwerty-fr = {
enable = true; description = "QWERTY-fr";
windowManager.i3.enable = true; languages = [
"fre"
# Keyboard layout "eng"
xkb = { ];
extraLayouts.qwerty-fr = { symbolsFile = "${
description = "QWERTY-fr"; pkgs.qwerty-fr.overrideAttrs (old: {
languages = [ "fr" ]; patches = (old.patches or [ ]) ++ [ ./qwerty-fr-keypad.diff ];
symbolsFile = "${ # TODO Does this work?
pkgs.stdenv.mkDerivation { })
name = "qwerty-fr-keypad"; }/share/X11/xkb/symbols/us_qwerty-fr";
src = pkgs.fetchFromGitHub {
owner = "qwerty-fr";
repo = "qwerty-fr";
rev = "3a4d13089e8ef016aa20baf6b2bf3ea53de674b8";
sha256 = "sha256-wn5n6jJVDrQWJze8xYF2nEY8a7mHI3hVO4xsT4LMo9c=";
};
patches = [ ./qwerty-fr-keypad.diff ];
# TODO This doesn't seem to be applied... it's the whole point of the derivation :(
installPhase = ''
runHook preInstall
mkdir -p $out/linux
cp $src/linux/us_qwerty-fr $out/linux
runHook postInstall
'';
}
}/linux/us_qwerty-fr";
};
layout = "qwerty-fr";
};
}; };
# Disco
}; };
# Enable sound & bluetooth # Enable sound & bluetooth
@ -76,7 +69,4 @@
# So we can use gnome3 pinentry flavour # So we can use gnome3 pinentry flavour
services.dbus.packages = [ pkgs.gcr ]; services.dbus.packages = [ pkgs.gcr ];
}; };
imports = [
./autorandr.nix
];
} }

View file

@ -49,9 +49,6 @@
# Fix https://nix-community.github.io/home-manager/index.html#_why_do_i_get_an_error_message_about_literal_ca_desrt_dconf_literal_or_literal_dconf_service_literal # Fix https://nix-community.github.io/home-manager/index.html#_why_do_i_get_an_error_message_about_literal_ca_desrt_dconf_literal_or_literal_dconf_service_literal
programs.dconf.enable = true; programs.dconf.enable = true;
# Because everything is encrypted and I'm the only user, this is fine.
services.displayManager.autoLogin.user = "geoffrey";
}; };
imports = [ imports = [
home-manager.nixosModules.home-manager home-manager.nixosModules.home-manager

View file

@ -15,10 +15,6 @@
}; };
frogeye.desktop = { frogeye.desktop = {
x11_screens = [
"DP-1"
"eDP-1"
];
maxVideoHeight = 1080; maxVideoHeight = 1080;
phasesCommands = { phasesCommands = {
jour = '' jour = ''