From a1b4228627a108dab946c47efec4ae03ef6726fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geoffrey=20=E2=80=9CFrogeye=E2=80=9D=20Preud=27homme?= Date: Tue, 27 May 2025 19:26:30 +0200 Subject: [PATCH] sway --- cranberry/hardware.nix | 2 +- curacao/hardware.nix | 123 +++++++------ flake.lock | 19 ++- flake.nix | 4 + hm/brightness/default.nix | 4 +- hm/desktop/audio/default.nix | 4 +- hm/desktop/autorandr/default.nix | 52 +----- hm/desktop/background/default.nix | 7 +- hm/desktop/browser/default.nix | 4 +- hm/desktop/default.nix | 34 ++-- hm/desktop/frobar/default.nix | 29 +--- hm/desktop/frobar/frobar/__init__.py | 6 +- hm/desktop/frobar/frobar/common.py | 247 ++++++++++++++++----------- hm/desktop/frobar/module.nix | 4 +- hm/desktop/i3.nix | 24 +-- hm/desktop/lock/default.nix | 108 ++++++------ hm/desktop/mpd/default.nix | 2 +- hm/desktop/presentation/default.nix | 2 +- hm/desktop/screenshots/default.nix | 19 ++- hm/desktop/terminal/default.nix | 4 +- hm/password/default.nix | 4 +- os/desktop/autorandr.nix | 1 + os/desktop/default.nix | 1 + 23 files changed, 373 insertions(+), 331 deletions(-) diff --git a/cranberry/hardware.nix b/cranberry/hardware.nix index 86c8547..c5fbeda 100644 --- a/cranberry/hardware.nix +++ b/cranberry/hardware.nix @@ -47,7 +47,7 @@ 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. diff --git a/curacao/hardware.nix b/curacao/hardware.nix index c46d628..8fc74e9 100644 --- a/curacao/hardware.nix +++ b/curacao/hardware.nix @@ -6,18 +6,9 @@ }: let displays = { - embedded = { - output = "eDP-1"; - edid = "00ffffffffffff000dae381700000000011c01049526157802a155a556519d280b505400000001010101010101010101010101010101b43b804a71383440302035007dd61000001ac32f804a71383440302035007dd61000001a000000fe003059395747803137334843450a00000000000041319e001000000a010a2020004f"; - }; - deskLeft = { - output = "HDMI-1-3"; # Internal HDMI port - edid = "00ffffffffffff004c2d7b09333032302f160103803420782a01f1a257529f270a505423080081c0810081809500a9c0b300d1c00101283c80a070b023403020360006442100001a000000fd00353f1e5111000a202020202020000000fc00533234423432300a2020202020000000ff0048344d434230333533340a2020010702010400023a80d072382d40102c458006442100001e011d007251d01e206e28550006442100001e011d00bc52d01e20b828554006442100001e8c0ad090204031200c4055000644210000188c0ad08a20e02d10103e9600064421000018000000000000000000000000000000000000000000000000000000000000000000d2"; - }; - deskRight = { - output = "DVI-I-2-1"; # DisplayLink - edid = "00ffffffffffff004c2d7b093330323020160103803420782a01f1a257529f270a505423080081c0810081809500a9c0b300d1c00101283c80a070b023403020360006442100001a000000fd00353f1e5111000a202020202020000000fc00533234423432300a2020202020000000ff0048344d433830303836350a2020011c02010400023a80d072382d40102c458006442100001e011d007251d01e206e28550006442100001e011d00bc52d01e20b828554006442100001e8c0ad090204031200c4055000644210000188c0ad08a20e02d10103e9600064421000018000000000000000000000000000000000000000000000000000000000000000000d2"; - }; + embedded = "Chimei Innolux Corporation 0x1738 Unknown"; + deskLeft = "Samsung Electric Company S24B420 H4MCB03534"; # Internal HDMI + deskRight = "Samsung Electric Company S24B420 H4MC800865"; # DisplayLink DVI }; in { @@ -33,8 +24,6 @@ in "rtsx_usb_sdmmc" ]; 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 loader = { @@ -56,8 +45,8 @@ in frogeye.desktop = { x11_screens = [ - displays.deskLeft.output - displays.deskRight.output + displays.deskLeft + displays.deskRight ]; maxVideoHeight = 1440; numlock = true; @@ -80,44 +69,80 @@ in # TODO Display 2 doesn't work anymore? }; }; - services = { - autorandr = { - profiles = { - portable = { - fingerprint.${displays.embedded.output} = displays.embedded.edid; - config.${displays.embedded.output} = { }; - }; - extOnly = { - fingerprint = { - ${displays.embedded.output} = displays.embedded.edid; - ${displays.deskLeft.output} = displays.deskLeft.edid; - ${displays.deskRight.output} = displays.deskRight.edid; - }; - config = { - ${displays.embedded.output}.enable = false; - ${displays.deskLeft.output} = { - primary = true; - mode = "1920x1200"; - rate = "59.95"; - position = "0x0"; + + # Screens + home-manager.users.geoffrey = + { ... }: + { + services = { + kanshi.settings = [ + { + profile = { + name = "portable"; + outputs = [ + { criteria = displays.embedded; } + ]; }; - ${displays.deskRight.output} = { - mode = "1920x1200"; - rate = "59.95"; - position = "1920x0"; + } + { + profile = { + 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" - "modesetting" - ]; - # TODO See if nvidia and DL can work together. + + # Displaylink + environment.variables = { + WLR_EVDI_RENDER_DEVICE = "/dev/dri/card1"; }; + 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 = [ nixos-hardware.nixosModules.dell-g3-3779 diff --git a/flake.lock b/flake.lock index dfde4bb..4016821 100644 --- a/flake.lock +++ b/flake.lock @@ -654,7 +654,8 @@ "nur": "nur", "onixpkgs": "onixpkgs", "stylix": "stylix", - "unixpkgs": "unixpkgs" + "unixpkgs": "unixpkgs", + "zelbarnixpkgs": "zelbarnixpkgs" } }, "scss-reset": { @@ -909,6 +910,22 @@ "ref": "master", "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", diff --git a/flake.nix b/flake.nix index c4c3acf..f57665b 100644 --- a/flake.nix +++ b/flake.nix @@ -6,6 +6,7 @@ onixpkgs.url = "nixpkgs/nixos-24.11"; nixpkgs.url = "nixpkgs/nixos-25.05"; unixpkgs.url = "nixpkgs/master"; + zelbarnixpkgs.url = "github:GeoffreyFrogeye/nixpkgs/zelbar"; # OS disko = { url = "disko"; @@ -41,6 +42,7 @@ self, nixpkgs, unixpkgs, + zelbarnixpkgs, disko, nix-on-droid, flake-utils, @@ -62,9 +64,11 @@ self: super: let upkgs = import unixpkgs { inherit (super) system; }; + zelbarpkgs = import zelbarnixpkgs { inherit (super) system; }; in { hello = upkgs.hello; # Placeholder + zelbar = zelbarpkgs.zelbar; } ) ]; diff --git a/hm/brightness/default.nix b/hm/brightness/default.nix index 3ae518f..3f72148 100644 --- a/hm/brightness/default.nix +++ b/hm/brightness/default.nix @@ -20,7 +20,7 @@ let specialisation = "dark"; } ]; - mod = config.xsession.windowManager.i3.config.modifier; + mod = config.wayland.windowManager.sway.config.modifier; in { config = { @@ -48,7 +48,7 @@ in ++ (with pkgs; [ brightnessctl ]); - xsession.windowManager.i3.config.keybindings = { + wayland.windowManager.sway.config.keybindings = { XF86MonBrightnessUp = "exec ${pkgs.brightnessctl}/bin/brightnessctl set +5%"; XF86MonBrightnessDown = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%-"; "${mod}+F6" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 1%-"; diff --git a/hm/desktop/audio/default.nix b/hm/desktop/audio/default.nix index f154f48..a9c7dfe 100644 --- a/hm/desktop/audio/default.nix +++ b/hm/desktop/audio/default.nix @@ -6,7 +6,7 @@ }: let 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 { config = lib.mkIf config.frogeye.desktop.xorg { @@ -32,7 +32,7 @@ in text = ''cookie-file = .config/pulse/pulse-cookie''; }; }; - xsession.windowManager.i3.config.keybindings = { + wayland.windowManager.sway.config.keybindings = { "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%"; "XF86AudioMute" = "${pactl} set-sink-mute @DEFAULT_SINK@ true"; diff --git a/hm/desktop/autorandr/default.nix b/hm/desktop/autorandr/default.nix index 7fffbe8..5984a31 100644 --- a/hm/desktop/autorandr/default.nix +++ b/hm/desktop/autorandr/default.nix @@ -14,59 +14,9 @@ let "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; + services.kanshi.enable = true; }; } diff --git a/hm/desktop/background/default.nix b/hm/desktop/background/default.nix index e18e6d7..4ea0ffe 100644 --- a/hm/desktop/background/default.nix +++ b/hm/desktop/background/default.nix @@ -1,13 +1,8 @@ { - 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}"; - }; + # FIXME DELETE }; } diff --git a/hm/desktop/browser/default.nix b/hm/desktop/browser/default.nix index e58ac73..25265b5 100644 --- a/hm/desktop/browser/default.nix +++ b/hm/desktop/browser/default.nix @@ -194,8 +194,8 @@ }; }; }; - xsession.windowManager.i3.config.keybindings = { - "${config.xsession.windowManager.i3.config.modifier}+m" = + wayland.windowManager.sway.config.keybindings = { + "${config.wayland.windowManager.sway.config.modifier}+m" = "exec ${config.programs.qutebrowser.package}/bin/qutebrowser --override-restore"; }; }; diff --git a/hm/desktop/default.nix b/hm/desktop/default.nix index 09d1c0f..0df0ccb 100644 --- a/hm/desktop/default.nix +++ b/hm/desktop/default.nix @@ -22,28 +22,34 @@ ]; config = lib.mkIf config.frogeye.desktop.xorg { - xsession = { + # 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; + # }; + + wayland.windowManager.sway = { 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 = { # Terminal bash.shellAliases = { - x = "startx ${config.home.homeDirectory}/${config.xsession.scriptPath}; logout"; lmms = "lmms --config ${config.xdg.configHome}/lmmsrc.xml"; }; rofi = { # TODO This theme template, that was used for Arch, looks much better: # https://gitlab.com/jordiorlando/base16-rofi/-/blob/master/templates/default.mustache enable = true; - pass.enable = true; + pass = { + enable = true; + package = pkgs.rofi-pass-wayland; + }; extraConfig = { lazy-grab = false; matching = "regex"; @@ -111,7 +117,6 @@ }; services = { blueman-applet.enable = true; - unclutter.enable = true; dunst = { enable = true; settings = with config.lib.stylix.colors.withHashtag; { @@ -180,9 +185,10 @@ # x11-exclusive simplescreenrecorder trayer - xclip - xorg.xinit - scrot + + # wayland exclusive + wl-clipboard + # TODO Clipboard history? ]; sessionVariables = { # XAUTHORITY = "${config.xdg.configHome}/Xauthority"; # Disabled as this causes lock-ups with DMs diff --git a/hm/desktop/frobar/default.nix b/hm/desktop/frobar/default.nix index 28bfbfc..180e58f 100644 --- a/hm/desktop/frobar/default.nix +++ b/hm/desktop/frobar/default.nix @@ -1,23 +1,12 @@ { - pkgs ? import { + # nixpkgs ? builtins.getFlake "github:GeoffreyFrogeye/nixpkgs/zelbar", + nixpkgs ? /nix/store/8g86qw3c2fr56bhhvqznrlic4jig9hb3-source, + pkgs ? import nixpkgs { config = { }; 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, # is called pyton-mpd2 on PyPi but mpd2 in nixpkgs. pkgs.python3Packages.buildPythonApplication rec { @@ -31,12 +20,12 @@ pkgs.python3Packages.buildPythonApplication rec { pygobject3 rich ]; - nativeBuildInputs = - [ lemonbar ] - ++ (with pkgs; [ - wirelesstools - playerctl - ]); + # TODO Might just be buildInputs, maybe without the need for prefix? + nativeBuildInputs = with pkgs; [ + wirelesstools + playerctl + zelbar + ]; makeWrapperArgs = [ "--prefix PATH : ${pkgs.lib.makeBinPath nativeBuildInputs}" "--prefix GI_TYPELIB_PATH : ${GI_TYPELIB_PATH}" diff --git a/hm/desktop/frobar/frobar/__init__.py b/hm/desktop/frobar/frobar/__init__.py index 82caa66..0b7e2e0 100644 --- a/hm/desktop/frobar/frobar/__init__.py +++ b/hm/desktop/frobar/frobar/__init__.py @@ -36,9 +36,7 @@ def main() -> None: theme = rich.terminal_theme.TerminalTheme( base16_color(0x0), - base16_color( - 0x0 - ), # TODO should be 7, currently 0 so it's compatible with v2 + base16_color(0x7), [ base16_color(0x0), # black base16_color(0x8), # red @@ -68,7 +66,7 @@ def main() -> None: workspaces_suffixes = "▲■" 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 diff --git a/hm/desktop/frobar/frobar/common.py b/hm/desktop/frobar/frobar/common.py index f54bc75..235d552 100644 --- a/hm/desktop/frobar/frobar/common.py +++ b/hm/desktop/frobar/frobar/common.py @@ -4,6 +4,7 @@ import datetime import enum import logging import signal +import sys import typing import gi @@ -62,6 +63,10 @@ def clip(text: str, length: int = 30) -> str: 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]): def __init__( self, @@ -73,6 +78,7 @@ class ComposableText(typing.Generic[P, C]): self.sortKey = sort_key if parent: self.set_parent(parent) + self.screen = self.get_first_parent_of_type(Screen) self.bar = self.get_first_parent_of_type(Bar) 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: parent = self 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 return parent def update_markup(self) -> None: - self.bar.refresh.set() + self.parent.update_markup() # TODO OPTI See if worth caching the output def generate_markup(self) -> str: @@ -191,23 +199,24 @@ class Section(ComposableText): else: self.animationTask = self.bar.taskGroup.create_task(self.animate()) - def set_action( - self, button: Button, callback: typing.Callable | None - ) -> None: + def set_action(self, button: Button, callback: typing.Callable | None) -> None: if button in self.actions: command = self.actions[button] self.bar.remove_action(command) del self.actions[button] if callback: - command = self.bar.add_action(callback) + command = self.screen.add_action(callback) self.actions[button] = command def generate_markup(self) -> str: assert not self.is_hidden() pad = max(0, self.size - len(self.text)) text = self.text[: self.size] + " " * pad - for button, command in self.actions.items(): - text = "%{A" + button.value + ":" + command + ":}" + text + "%{A}" + if text: + 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 @@ -239,8 +248,8 @@ class Module(ComposableText): class Alignment(enum.Enum): LEFT = "l" - RIGHT = "r" CENTER = "c" + RIGHT = "r" class Side(ComposableText): @@ -255,38 +264,64 @@ class Side(ComposableText): def generate_markup(self) -> str: if not self.children: return "" - text = "%{" + self.alignment.value + "}" + markup = "" 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 section in module.get_sections(): if section.is_hidden(): 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: - text += ( - "%{B" + hexa + "}%{F-}" - if self.alignment == Alignment.LEFT - else "%{B-}%{F" + hexa + "}%{R}%{F-}" + markup += ( + text("", default, current) + if self.alignment != Alignment.LEFT + else "" ) 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: - if self.alignment == Alignment.RIGHT: - text += ( - "" - if last_section.color == section.color - else "%{F" + hexa + "}%{R}" - ) - elif last_section.color == section.color: - text += "" - else: - text += "%{R}%{B" + hexa + "}" - text += "%{F-}" - text += section.get_markup() + lastone = last_section.color.get_truecolor(theme=self.bar.theme) + markup += ( + text("", lastone, current) + if self.alignment == Alignment.RIGHT + else text("", current, lastone) + ) + markup += text(section.get_markup(), current, default) last_section = section - if self.alignment != Alignment.RIGHT and last_section: - text += "%{R}%{B-}" - return text + markup += ( + text("", default, current) + if self.alignment != Alignment.RIGHT and last_section + else "" + ) + return markup class Screen(ComposableText): @@ -296,15 +331,79 @@ class Screen(ComposableText): self.children: typing.MutableSequence[Side] self.output = output + self.refresh = asyncio.Event() + + self.actionIndex = 0 + self.actions: dict[str, typing.Callable] = {} for alignment in Alignment: Side(parent=self, alignment=alignment) + def update_markup(self) -> str: + self.screen.refresh.set() + def generate_markup(self) -> str: - return ("%{Sn" + self.output + "}") + "".join( - side.get_markup() for side in self.children + return "".join(side.get_markup() for side in self.children) + "\n" + + 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 @@ -322,11 +421,8 @@ class Bar(ComposableText): self.longRunningTasks: list[asyncio.Task] = [] self.theme = theme - self.refresh = asyncio.Event() self.taskGroup = asyncio.TaskGroup() self.providers: list[Provider] = [] - self.actionIndex = 0 - self.actions: dict[str, typing.Callable] = {} self.periodicProviderTask: typing.Coroutine | None = None @@ -343,45 +439,9 @@ class Bar(ComposableText): self.longRunningTasks.append(task) 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: - self.add_long_running_task(refresher()) - self.add_long_running_task(action_handler()) + for screen in self.children: + await screen.run() for provider in self.providers: self.add_long_running_task(provider.run()) @@ -393,9 +453,6 @@ class Bar(ComposableText): loop = asyncio.get_event_loop() 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( self, provider: "Provider", @@ -405,24 +462,13 @@ class Bar(ComposableText): modules = [] for s, screen in enumerate(self.children): if screen_num is None or s == screen_num: - side = next( - filter(lambda s: s.alignment == alignment, screen.children) - ) + side = next(filter(lambda s: s.alignment == alignment, screen.children)) module = Module(parent=side) modules.append(module) provider.modules = modules if modules: 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: # Using GLib's event loop so we can run GLib's code policy = gi.events.GLibEventLoopPolicy() @@ -430,6 +476,9 @@ class Bar(ComposableText): loop = policy.get_event_loop() loop.run_until_complete(self.run()) + def update_markup(self) -> None: + pass + class Provider: section_type: type[Section] = Section @@ -462,9 +511,7 @@ class SingleSectionProvider(MirrorProvider): class StaticProvider(SingleSectionProvider): - def __init__( - self, text: str, color: rich.color.Color = RICH_DEFAULT_COLOR - ) -> None: + def __init__(self, text: str, color: rich.color.Color = RICH_DEFAULT_COLOR) -> None: super().__init__(color=color) self.text = text @@ -524,9 +571,7 @@ class StatefulSectionProvider(Provider): section_type = StatefulSection -class SingleStatefulSectionProvider( - StatefulSectionProvider, SingleSectionProvider -): +class SingleStatefulSectionProvider(StatefulSectionProvider, SingleSectionProvider): section: StatefulSection @@ -545,9 +590,7 @@ class MultiSectionsProvider(Provider): async def do_nothing() -> None: pass - async def update_sections( - self, sections: set[Sortable], module: Module - ) -> None: + async def update_sections(self, sections: set[Sortable], module: Module) -> None: module_sections = self.sectionKeys[module] async with asyncio.TaskGroup() as tg: for sort_key in sections: @@ -556,9 +599,7 @@ class MultiSectionsProvider(Provider): section = self.section_type( parent=module, sort_key=sort_key, color=self.color ) - self.updaters[section] = await self.get_section_updater( - section - ) + self.updaters[section] = await self.get_section_updater(section) module_sections[sort_key] = section updater = self.updaters[section] @@ -608,9 +649,7 @@ class PeriodicProvider(Provider): bar.add_long_running_task(bar.periodicProviderTask) -class PeriodicStatefulProvider( - SingleStatefulSectionProvider, PeriodicProvider -): +class PeriodicStatefulProvider(SingleStatefulSectionProvider, PeriodicProvider): async def run(self) -> None: await super().run() self.section.set_changed_state(self.loop) diff --git a/hm/desktop/frobar/module.nix b/hm/desktop/frobar/module.nix index 6fab6ed..b1c5d03 100644 --- a/hm/desktop/frobar/module.nix +++ b/hm/desktop/frobar/module.nix @@ -6,7 +6,7 @@ }: { config = lib.mkIf config.frogeye.desktop.xorg { - xsession.windowManager.i3.config.bars = [ ]; + wayland.windowManager.sway.config.bars = [ ]; programs.autorandr.hooks.postswitch = { frobar = "${pkgs.systemd}/bin/systemctl --user restart frobar"; }; @@ -20,7 +20,7 @@ Service = { # 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 - 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 = { diff --git a/hm/desktop/i3.nix b/hm/desktop/i3.nix index c6fe803..afd7312 100644 --- a/hm/desktop/i3.nix +++ b/hm/desktop/i3.nix @@ -6,6 +6,7 @@ }: let # FOCUS + # FIXME There should be an option in sway 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 @@ -57,15 +58,13 @@ let forEachWorkspace = f: map (w: f w) workspaces; # 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"; modes = config.frogeye.desktop.i3.bindmodes; x11_screens = config.frogeye.desktop.x11_screens; in { - config = lib.mkIf config.xsession.windowManager.i3.enable { - stylix.targets.i3.enable = false; - services.picom.enable = true; + config = lib.mkIf config.wayland.windowManager.sway.enable { xdg.configFile = { "rofimoji.rc" = { text = '' @@ -75,10 +74,12 @@ in ''; }; }; - xsession.windowManager.i3.config = { + wayland.windowManager.sway.config = { + input."*".xkb_variant = "qwerty-fr"; modifier = lib.mkDefault "Mod4"; fonts = { names = [ config.stylix.fonts.sansSerif.name ]; + size = lib.mkForce 8.0; }; terminal = "alacritty"; colors = @@ -167,7 +168,7 @@ in "${mod}+Shift+r" = "restart"; "${mod}+Shift+e" = "exit"; } - // 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.attrsets.mergeAttrsList ( @@ -223,9 +224,12 @@ in { window_role = "task_dialog"; } ]; }; + seat."*" = { + hide_cursor = "10"; + }; startup = [ { - notification = false; + # notification = false; command = "${ pkgs.writeShellApplication { name = "batteryNotify"; @@ -259,10 +263,10 @@ in }; "[L] Vérouillage [E] Déconnexion [S] Veille [H] Hibernation [R] Redémarrage [P] Extinction" = { bindings = { - "l" = "exec --no-startup-id exec xlock, mode default"; + "l" = "exec --no-startup-id exec ${pkgs.systemd}/bin/loginctl lock-session, mode default"; "e" = "exit, mode default"; - "s" = "exec --no-startup-id exec xlock & ${pkgs.systemd}/bin/systemctl suspend --check-inhibitors=no, mode default"; - "h" = "exec --no-startup-id exec xlock & ${pkgs.systemd}/bin/systemctl hibernate, mode default"; + "s" = "exec --no-startup-id exec ${pkgs.systemd}/bin/systemctl suspend --check-inhibitors=no, mode default"; + "h" = "exec --no-startup-id exec ${pkgs.systemd}/bin/systemctl hibernate, 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"; }; diff --git a/hm/desktop/lock/default.nix b/hm/desktop/lock/default.nix index 08807fb..94d8a3b 100644 --- a/hm/desktop/lock/default.nix +++ b/hm/desktop/lock/default.nix @@ -19,68 +19,74 @@ let ''; lockPng = pkgs.runCommand "lock.png" { } "${pkgs.imagemagick}/bin/convert ${lockSvg} $out"; - mod = config.xsession.windowManager.i3.config.modifier; - xautolockState = "${config.xdg.cacheHome}/xautolock"; + mod = config.wayland.windowManager.sway.config.modifier; + idleTime = 10; # minutes + preLock = "${pkgs.writeShellScript "prelock" '' + ${config.frogeye.hooks.lock} + ${pkgs.procps}/bin/pkill -USR1 swayidle + ''}"; in { + # FIXME Not really working, needs test config = lib.mkIf config.frogeye.desktop.xorg { - home.packages = [ - (pkgs.writeShellApplication { - name = "xlock"; - text = '' - ${config.frogeye.hooks.lock} - # TODO Reevaluate whether we want this or not - if ! ${pkgs.lightdm}/bin/dm-tool lock - then - if [ -d ${config.xdg.cacheHome}/lockpatterns ] - then - 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 = { + programs.swaylock = { + enable = true; + settings = { + color = lib.mkForce (builtins.substring 1 6 lockColors.d); + image = lib.mkForce lockPng; + tiling = true; + ignore-empty-password = true; + indicator-idle-visible = false; + show-failed-attempts = true; + indicator-radius = true; + }; + }; + services.swayidle = { + enable = true; + events = [ + { + event = "lock"; + command = "${preLock}"; + } + # { + # event = "before-sleep"; + # command = "xlock"; + # } + ]; + timeouts = [ + { + # Warn + timeout = (idleTime - 1) * 60; + command = ''${lib.getExe pkgs.libnotify} Turning screen off in 1 minute"''; + resumeCommand = ''${config.wayland.windowManager.sway.package}/bin/swaymsg "output * dpms on"''; + } + { + # Screen off + timeout = idleTime * 60; + command = ''${config.wayland.windowManager.sway.package}/bin/swaymsg "output * dpms off"''; + resumeCommand = ''${config.wayland.windowManager.sway.package}/bin/swaymsg "output * dpms off"''; + } + { + # Lock + timeout = (idleTime + 1) * 60; + command = ''${config.wayland.windowManager.sway.package}/bin/swaymsg "output * dpms off"''; + } + ]; + }; + wayland.windowManager.sway.config = { keybindings = { # Screen off commands - "${mod}+F1" = "--release exec --no-startup-id ${pkgs.xorg.xset}/bin/xset dpms force off"; + "${mod}+F1" = ''--release exec ${pkgs.procps}/bin/pkill -USR1 swayidle''; # 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" ] + "${mod}+F5" = "exec --no-startup-id ${pkgs.writeShellScript "swayidle-toggle" '' + if ${pkgs.systemd}/bin/systemctl --user is-active swayidle --quiet then - ${pkgs.xautolock}/bin/xautolock -enable - echo enabled > ${xautolockState} + ${pkgs.systemd}/bin/systemctl --user stop swayidle else - ${pkgs.xautolock}/bin/xautolock -disable - echo disabled > ${xautolockState} + ${pkgs.systemd}/bin/systemctl --user start swayidle fi ''}"; }; - startup = [ - # Stop screen after 10 minutes, 1 minutes after lock it - { - notification = false; - command = "${pkgs.writeShellScript "xautolock-start" '' - echo enabled > ${xautolockState} - ${pkgs.xautolock}/bin/xautolock -time 10 -locker '${pkgs.xorg.xset}/bin/xset dpms force standby' -killtime 1 -killer xlock - ''}"; - } - # services.screen-locker.xautolock is hardcoded to use systemd for -locker (doesn't even work...) - ]; }; }; } diff --git a/hm/desktop/mpd/default.nix b/hm/desktop/mpd/default.nix index 38efb14..4b1b508 100644 --- a/hm/desktop/mpd/default.nix +++ b/hm/desktop/mpd/default.nix @@ -58,7 +58,7 @@ }; }; }; - xsession.windowManager.i3.config.keybindings = { + wayland.windowManager.sway.config.keybindings = { "XF86AudioPrev" = "exec ${lib.getExe pkgs.playerctl} previous"; "XF86AudioPlay" = "exec ${lib.getExe pkgs.playerctl} play-pause"; "XF86AudioNext" = "exec ${lib.getExe pkgs.playerctl} next"; diff --git a/hm/desktop/presentation/default.nix b/hm/desktop/presentation/default.nix index bcde39e..543296c 100644 --- a/hm/desktop/presentation/default.nix +++ b/hm/desktop/presentation/default.nix @@ -33,7 +33,7 @@ in return_bindings = false; }; }; - xsession.windowManager.i3.config.window.commands = [ + wayland.windowManager.sway.config.window.commands = [ # Open specific applications in floating mode { criteria = { diff --git a/hm/desktop/screenshots/default.nix b/hm/desktop/screenshots/default.nix index d4c9c77..ba25ae8 100644 --- a/hm/desktop/screenshots/default.nix +++ b/hm/desktop/screenshots/default.nix @@ -6,16 +6,23 @@ }: let 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'"; - mod = config.xsession.windowManager.i3.config.modifier; + gs = + 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 { config = lib.mkIf config.frogeye.desktop.xorg { frogeye.folders.screenshots.path = "Screenshots"; - xsession.windowManager.i3.config.keybindings = { - "Print" = "exec ${scrot} --focused"; - "${mod}+Print" = "exec ${scrot}"; - "Ctrl+Print" = "--release exec ${scrot} --select"; + home.packages = [ pkgs.sway-contrib.grimshot ]; + wayland.windowManager.sway.config.keybindings = { + "Print" = "exec ${gs "active"}"; + "${mod}+Print" = "exec ${gs "screen"}"; + "Ctrl+Print" = "exec ${gs "anything"}"; }; }; } diff --git a/hm/desktop/terminal/default.nix b/hm/desktop/terminal/default.nix index 8f92ce8..2f8120d 100644 --- a/hm/desktop/terminal/default.nix +++ b/hm/desktop/terminal/default.nix @@ -5,7 +5,7 @@ ... }: let - mod = config.xsession.windowManager.i3.config.modifier; + mod = config.wayland.windowManager.sway.config.modifier; in { config = lib.mkIf config.frogeye.desktop.xorg { @@ -149,7 +149,7 @@ in }; }; }; - xsession.windowManager.i3.config.keybindings = { + wayland.windowManager.sway.config.keybindings = { "${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 "${mod}+Shift+Return" = "exec ${config.programs.urxvt.package}/bin/urxvt"; diff --git a/hm/password/default.nix b/hm/password/default.nix index 032c3cc..a68d635 100644 --- a/hm/password/default.nix +++ b/hm/password/default.nix @@ -5,7 +5,7 @@ ... }: let - mod = config.xsession.windowManager.i3.config.modifier; + mod = config.wayland.windowManager.sway.config.modifier; in { config = { @@ -67,7 +67,7 @@ in }; 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."${mod}+c" = "exec --no-startup-id ${config.programs.rofi.pass.package}/bin/rofi-pass --last-used"; # TODO Try autopass.cr }; options = { diff --git a/os/desktop/autorandr.nix b/os/desktop/autorandr.nix index 78a2664..9a1bf04 100644 --- a/os/desktop/autorandr.nix +++ b/os/desktop/autorandr.nix @@ -5,6 +5,7 @@ ... }: { + # FIXME config = lib.mkIf (builtins.length config.frogeye.desktop.x11_screens > 1) { services = { autorandr.enable = true; diff --git a/os/desktop/default.nix b/os/desktop/default.nix index 601833f..c72c87c 100644 --- a/os/desktop/default.nix +++ b/os/desktop/default.nix @@ -7,6 +7,7 @@ { config = lib.mkIf config.frogeye.desktop.xorg { boot.kernelModules = [ "i2c-dev" ]; # Allows using ddcutil + programs.sway.enable = true; security.rtkit.enable = true; # Recommended for pipewire services = { blueman.enable = true;