diff --git a/cranberry/hardware.nix b/cranberry/hardware.nix index 86c8547..d78c732 100644 --- a/cranberry/hardware.nix +++ b/cranberry/hardware.nix @@ -24,7 +24,6 @@ hardware.enableRedistributableFirmware = true; frogeye.desktop = { - x11_screens = [ "DP-2" "eDP-1" ]; maxVideoHeight = 1080; phasesCommands = { @@ -47,7 +46,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..3852e5b 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 = { @@ -55,12 +44,7 @@ in hardware.keyboard.qmk.enable = true; frogeye.desktop = { - x11_screens = [ - displays.deskLeft.output - displays.deskRight.output - ]; maxVideoHeight = 1440; - numlock = true; phasesCommands = { jour = '' ${pkgs.brightnessctl}/bin/brightnessctl set 40000 & @@ -80,44 +64,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..94bd07e 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 = lib.mkOptionDefault { 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..0e8dc00 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 = lib.mkOptionDefault { "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 deleted file mode 100644 index 7fffbe8..0000000 --- a/hm/desktop/autorandr/default.nix +++ /dev/null @@ -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; - }; -} diff --git a/hm/desktop/background/default.nix b/hm/desktop/background/default.nix deleted file mode 100644 index e18e6d7..0000000 --- a/hm/desktop/background/default.nix +++ /dev/null @@ -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}"; - }; - }; -} diff --git a/hm/desktop/browser/default.nix b/hm/desktop/browser/default.nix index e58ac73..f94e0ef 100644 --- a/hm/desktop/browser/default.nix +++ b/hm/desktop/browser/default.nix @@ -122,7 +122,7 @@ github = "https://github.com/search?q={}"; google = "https://www.google.fr/search?q={}"; 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={}"; inv = invidious; nixos = "https://search.nixos.org/options?channel=${config.home.version.release}&query={}"; @@ -194,8 +194,8 @@ }; }; }; - xsession.windowManager.i3.config.keybindings = { - "${config.xsession.windowManager.i3.config.modifier}+m" = + wayland.windowManager.sway.config.keybindings = lib.mkOptionDefault { + "${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..adfe0d3 100644 --- a/hm/desktop/default.nix +++ b/hm/desktop/default.nix @@ -7,43 +7,32 @@ { imports = [ ./audio - ./autorandr - ./background ./browser ./cursor ./frobar/module.nix - ./i3.nix ./lock ./mpd ./presentation - ./redness ./screenshots + ./sway ./terminal ]; config = lib.mkIf config.frogeye.desktop.xorg { - - 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; 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 +100,6 @@ }; services = { blueman-applet.enable = true; - unclutter.enable = true; dunst = { enable = true; settings = with config.lib.stylix.colors.withHashtag; { @@ -139,7 +127,7 @@ }; }; }; - keynav.enable = true; + kanshi.enable = true; }; home = { @@ -176,13 +164,13 @@ meld python3Packages.magic bluetuith + trayer # For occasional applications that want to put themselves in tray - # x11-exclusive - simplescreenrecorder - trayer - xclip - xorg.xinit - scrot + # wayland exclusive + wl-clipboard # Easy copy/paste from/to terminal + # TODO Clipboard history? + wdisplays # Ad-hoc display setup GUI + wf-recorder # Screen recorder (much more basic than simplescreenrecorder but alright) ]; 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..31aecd5 100644 --- a/hm/desktop/frobar/module.nix +++ b/hm/desktop/frobar/module.nix @@ -6,25 +6,25 @@ }: { 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"; }; systemd.user.services.frobar = { Unit = { Description = "frobar"; - After = [ "graphical-session-pre.target" ]; - PartOf = [ "graphical-session.target" ]; + After = [ "kanshi.service" ]; + PartOf = [ "kanshi.service" ]; }; 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 = { - WantedBy = [ "graphical-session.target" ]; + WantedBy = [ "kanshi.service" ]; }; }; }; diff --git a/hm/desktop/lock/default.nix b/hm/desktop/lock/default.nix index 08807fb..f34f94e 100644 --- a/hm/desktop/lock/default.nix +++ b/hm/desktop/lock/default.nix @@ -19,68 +19,77 @@ 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 + # 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 { + # 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 { - 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 = { - 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 - ''}"; + programs.swaylock = { + enable = true; + settings = { + color = (builtins.substring 1 6 lockColors.d); + image = lockPng; + tiling = true; + ignore-empty-password = true; + indicator-idle-visible = false; + show-failed-attempts = true; + indicator-radius = 100; }; - startup = [ - # Stop screen after 10 minutes, 1 minutes after lock it + }; + services.swayidle = { + enable = true; + timeouts = [ { - 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 + # Warn + timeout = (idleTime - 1) * 60; + command = ''${lib.getExe pkgs.libnotify} "Screen turns off in 1 minute"''; + } + { + # 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 + ''}"; + }; }; } diff --git a/hm/desktop/lock/wayresume.diff b/hm/desktop/lock/wayresume.diff new file mode 100644 index 0000000..08087e7 --- /dev/null +++ b/hm/desktop/lock/wayresume.diff @@ -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) { +- if let ext_idle_notification_v1::Event::Idled = ctx.event { ++ if let ext_idle_notification_v1::Event::Resumed = ctx.event { + ctx.state.exit = true; + } + } + diff --git a/hm/desktop/mpd/default.nix b/hm/desktop/mpd/default.nix index 38efb14..7476de0 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 = lib.mkOptionDefault { "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/redness/default.nix b/hm/desktop/redness/default.nix deleted file mode 100644 index 616ac46..0000000 --- a/hm/desktop/redness/default.nix +++ /dev/null @@ -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 ]; - }; -} diff --git a/hm/desktop/redness/sct_aarch64.patch b/hm/desktop/redness/sct_aarch64.patch deleted file mode 100644 index 8a04378..0000000 --- a/hm/desktop/redness/sct_aarch64.patch +++ /dev/null @@ -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 diff --git a/hm/desktop/screenshots/default.nix b/hm/desktop/screenshots/default.nix index d4c9c77..98ecf53 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 = lib.mkOptionDefault { + "Print" = "exec ${gs "active"}"; + "${mod}+Print" = "exec ${gs "screen"}"; + "Ctrl+Print" = "exec ${gs "anything"}"; }; }; } diff --git a/hm/desktop/batteryNotify.sh b/hm/desktop/sway/batteryNotify.sh similarity index 100% rename from hm/desktop/batteryNotify.sh rename to hm/desktop/sway/batteryNotify.sh diff --git a/hm/desktop/i3.nix b/hm/desktop/sway/default.nix similarity index 62% rename from hm/desktop/i3.nix rename to hm/desktop/sway/default.nix index c6fe803..560588d 100644 --- a/hm/desktop/i3.nix +++ b/hm/desktop/sway/default.nix @@ -5,15 +5,6 @@ ... }: 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 = [ { @@ -57,15 +48,34 @@ 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; + + 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 { - 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 +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"; fonts = { names = [ config.stylix.fonts.sansSerif.name ]; + size = lib.mkForce 8.0; }; terminal = "alacritty"; colors = @@ -125,8 +143,11 @@ in background = base07; # I set the color of the active tab as the the background color of the terminal so they merge together. }; - focus.followMouse = false; - keybindings = + focus = { + followMouse = false; + mouseWarping = "container"; + }; + keybindings = lib.mkOptionDefault ( { # Compatibility layer for people coming from other backgrounds "Mod1+Tab" = "${rofi} -modi window -show window"; @@ -142,68 +163,56 @@ in "${mod}+Shift+d" = "${rofi} -modi drun -show drun"; # Start Applications "${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) - "${mod}+b" = "workspace back_and_forth; ${focus}"; - "${mod}+Shift+b" = "move container to workspace back_and_forth; 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"; + # Split horizontally (rebound from ${mod}+b because used above) + "${mod}+g" = "splith"; # Change container layout - "${mod}+g" = "split h; ${focus}"; - "${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}"; + "${mod}+q" = "focus child"; # i3 control - "${mod}+Shift+c" = "reload"; "${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.attrsets.mergeAttrsList ( 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 - "${mod}+Ctrl+${c.vi}" = "workspace ${c.workspace}; ${focus}"; + "${mod}+Ctrl+${c.vi}" = "workspace ${c.workspace}"; # 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) - "${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 ( forEachWorkspace (w: { - # Switch to workspace - "${mod}+${w.key}" = "workspace ${w.name}; ${focus}"; - # move focused container to workspace - "${mod}+ctrl+${w.key}" = "move container to workspace ${w.name}; ${focus}"; - # move to workspace with focused container - "${mod}+shift+${w.key}" = "move container to workspace ${w.name}; workspace ${w.name}; ${focus}"; + # move container to workspace, do not follow (normally bound to ${mod}+Shift+${w.key} + "${mod}+Ctrl+${w.key}" = "move container to workspace number ${w.name}"; + # move container to workspace, follow it + "${mod}+Shift+${w.key}" = + "move container to workspace number ${w.name}; workspace number ${w.name}"; }) - ); - 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 = { hideEdgeBorders = "both"; titlebar = false; # So that single-container screens are basically almost fullscreen @@ -223,9 +232,11 @@ in { window_role = "task_dialog"; } ]; }; + seat."*" = { + hide_cursor = "10"; + }; startup = [ { - notification = false; command = "${ pkgs.writeShellApplication { name = "batteryNotify"; @@ -241,28 +252,24 @@ in } ]; workspaceLayout = "tabbed"; - focus.mouseWarping = true; # i3 only supports warping to workspace, hence ${focus} - workspaceOutputAssign = forEachWorkspace (w: { - output = builtins.elemAt x11_screens (lib.mod w.id (builtins.length x11_screens)); - workspace = w.name; - }); + workspaceOutputAssign = lib.mkIf (builtins.length screenCombinations > 0) ( + forEachWorkspace (w: { + output = builtins.map ( + combination: builtins.elemAt combination (lib.mod w.id (builtins.length combination)) + ) screenCombinations; + workspace = w.name; + }) + ); }; 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" = { 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"; - "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"; + # TODO Sometimes, exit gets stuck on terminal. Restarting greetd helps. + "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"; "p" = "exec --no-startup-id ${pkgs.systemd}/bin/systemctl poweroff -i, mode default"; }; diff --git a/hm/desktop/terminal/default.nix b/hm/desktop/terminal/default.nix index 8f92ce8..8f53173 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 = 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"; # -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..cabb2fc 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,9 @@ 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 = lib.mkOptionDefault { + "${mod}+c" = "exec --no-startup-id ${config.programs.rofi.pass.package}/bin/rofi-pass --last-used"; + }; # TODO Try autopass.cr }; options = { diff --git a/options.nix b/options.nix index 706f5f6..ce51d29 100644 --- a/options.nix +++ b/options.nix @@ -32,14 +32,8 @@ in ]; }; desktop = { - xorg = lib.mkEnableOption "Enable X11 support"; - # TODO Use appropriate OS/HM option(s) instead - 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; - }; + xorg = lib.mkEnableOption "Enable graphics support"; + # TODO Rename maxVideoHeight = lib.mkOption { type = lib.types.int; description = "Maximum video height in pixel the machine can reasonably watch"; diff --git a/os/desktop/autorandr.nix b/os/desktop/autorandr.nix deleted file mode 100644 index 78a2664..0000000 --- a/os/desktop/autorandr.nix +++ /dev/null @@ -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} - ''; - }; - }; -} diff --git a/os/desktop/default.nix b/os/desktop/default.nix index 601833f..32ac05b 100644 --- a/os/desktop/default.nix +++ b/os/desktop/default.nix @@ -7,10 +7,23 @@ { 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; - 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 pipewire = { enable = true; @@ -34,40 +47,20 @@ ]; }; }; - # Video - xserver = { - enable = true; - windowManager.i3.enable = true; - - # Keyboard layout - xkb = { - extraLayouts.qwerty-fr = { - description = "QWERTY-fr"; - languages = [ "fr" ]; - symbolsFile = "${ - pkgs.stdenv.mkDerivation { - name = "qwerty-fr-keypad"; - 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"; - }; + # Keyboard layout + xserver.xkb.extraLayouts.us_qwerty-fr = { + description = "QWERTY-fr"; + languages = [ + "fre" + "eng" + ]; + symbolsFile = "${ + pkgs.qwerty-fr.overrideAttrs (old: { + patches = (old.patches or [ ]) ++ [ ./qwerty-fr-keypad.diff ]; + # TODO Does this work? + }) + }/share/X11/xkb/symbols/us_qwerty-fr"; }; - # Disco }; # Enable sound & bluetooth @@ -76,7 +69,4 @@ # So we can use gnome3 pinentry flavour services.dbus.packages = [ pkgs.gcr ]; }; - imports = [ - ./autorandr.nix - ]; } diff --git a/os/geoffrey.nix b/os/geoffrey.nix index d935b15..b93a1cb 100644 --- a/os/geoffrey.nix +++ b/os/geoffrey.nix @@ -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 programs.dconf.enable = true; - - # Because everything is encrypted and I'm the only user, this is fine. - services.displayManager.autoLogin.user = "geoffrey"; }; imports = [ home-manager.nixosModules.home-manager diff --git a/pindakaas/hardware.nix b/pindakaas/hardware.nix index 94c0d23..7226100 100644 --- a/pindakaas/hardware.nix +++ b/pindakaas/hardware.nix @@ -15,10 +15,6 @@ }; frogeye.desktop = { - x11_screens = [ - "DP-1" - "eDP-1" - ]; maxVideoHeight = 1080; phasesCommands = { jour = ''