From b270fcef2fdbf9144c1f4b3528d35b455e116082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9fix=20Estrada?= Date: Tue, 28 Aug 2018 15:46:24 +0200 Subject: bspwm: add module PR #362, #981 Co-authored-by: Vincent Breitmoser --- modules/services/window-managers/bspwm/default.nix | 71 +++++++ modules/services/window-managers/bspwm/options.nix | 221 +++++++++++++++++++++ 2 files changed, 292 insertions(+) create mode 100644 modules/services/window-managers/bspwm/default.nix create mode 100644 modules/services/window-managers/bspwm/options.nix (limited to 'modules/services/window-managers') diff --git a/modules/services/window-managers/bspwm/default.nix b/modules/services/window-managers/bspwm/default.nix new file mode 100644 index 00000000000..e9fa064a8ba --- /dev/null +++ b/modules/services/window-managers/bspwm/default.nix @@ -0,0 +1,71 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.xsession.windowManager.bspwm; + bspwm = cfg.package; + + camelToSnake = s: + builtins.replaceStrings lib.upperChars (map (c: "_${c}") lib.lowerChars) s; + + formatConfig = n: v: + let + formatList = x: + if isList x + then throw "can not convert 2-dimensional lists to bspwm format" + else formatValue x; + + formatValue = v: + if isBool v then (if v then "true" else "false") + else if isList v then concatMapStringsSep ", " formatList v + else if isString v then "${lib.strings.escapeShellArg v}" + else toString v; + in + "bspc config ${n} ${formatValue v}"; + + formatMonitors = n: v: "bspc monitor ${n} -d ${concatStringsSep " " v}"; + + formatRules = target: directiveOptions: + let + formatDirective = n: v: + if isBool v then (if v then "${camelToSnake n}=on" else "${camelToSnake n}=off") + else if (n == "desktop" || n == "node") then "${camelToSnake n}='${v}'" + else "${camelToSnake n}=${lib.strings.escapeShellArg v}"; + + directives = filterAttrs (n: v: v != null && !(lib.strings.hasPrefix "_" n)) directiveOptions; + directivesStr = builtins.concatStringsSep " " (mapAttrsToList formatDirective directives); + in + "bspc rule -a ${target} ${directivesStr}"; + + formatStartupPrograms = map (s: "${s} &"); + +in + +{ + options = import ./options.nix { inherit pkgs; inherit lib; }; + + config = mkIf cfg.enable { + home.packages = [ bspwm ]; + xsession.windowManager.command = + let + configFile = pkgs.writeShellScript "bspwmrc" ( + concatStringsSep "\n" ( + (mapAttrsToList formatMonitors cfg.monitors) + ++ (mapAttrsToList formatConfig cfg.settings) + ++ (mapAttrsToList formatRules cfg.rules) + ++ [ '' + # java gui fixes + export _JAVA_AWT_WM_NONREPARENTING=1 + bspc rule -a sun-awt-X11-XDialogPeer state=floating + '' ] + ++ [ cfg.extraConfig ] + ++ (formatStartupPrograms cfg.startupPrograms) + ) + ); + configCmdOpt = optionalString (cfg.settings != null) "-c ${configFile}"; + in + "${cfg.package}/bin/bspwm ${configCmdOpt}"; + }; +} diff --git a/modules/services/window-managers/bspwm/options.nix b/modules/services/window-managers/bspwm/options.nix new file mode 100644 index 00000000000..9d1955aa57c --- /dev/null +++ b/modules/services/window-managers/bspwm/options.nix @@ -0,0 +1,221 @@ +{ pkgs, lib }: + +with lib; + +let + + rule = types.submodule { + options = { + monitor = mkOption { + type = types.nullOr types.str; + default = null; + description = "The monitor where the rule should be applied."; + example = "HDMI-0"; + }; + + desktop = mkOption { + type = types.nullOr types.str; + default = null; + description = "The desktop where the rule should be applied."; + example = "^8"; + }; + + node = mkOption { + type = types.nullOr types.str; + default = null; + description = "The node where the rule should be applied."; + example = "1"; + }; + + state = mkOption { + type = types.nullOr (types.enum [ "tiled" "pseudo_tiled" "floating" "fullscreen" ]); + default = null; + description = "The state in which a new window should spawn."; + example = "floating"; + }; + + layer = mkOption { + type = types.nullOr (types.enum [ "below" "normal" "above" ]); + default = null; + description = "The layer where a new window should spawn."; + example = "above"; + }; + + splitDir = mkOption { + type = types.nullOr (types.enum [ "north" "west" "south" "east" ]); + default = null; + description = "The direction where the container is going to be split."; + example = "south"; + }; + + splitRatio = mkOption { + type = types.nullOr types.float; + default = null; + description = '' + The ratio between the new window and the previous existing window in + the desktop. + ''; + example = 0.65; + }; + + hidden = mkOption { + type = types.nullOr types.bool; + default = null; + description = ''Whether the node should occupy any space.''; + example = true; + }; + + sticky = mkOption { + type = types.nullOr types.bool; + default = null; + description = "Whether the node should stay on the focused desktop."; + example = true; + }; + + private = mkOption { + type = types.nullOr types.bool; + default = null; + description = '' + Whether the node should stay in the same tiling position and size. + ''; + example = true; + }; + + locked = mkOption { + type = types.nullOr types.bool; + default = null; + description = '' + Whether the node should ignore node --close + messages. + ''; + example = true; + }; + + marked = mkOption { + type = types.nullOr types.bool; + default = null; + description = "Whether the node will be marked for deferred actions."; + example = true; + }; + + center = mkOption { + type = types.nullOr types.bool; + default = null; + description = '' + Whether the node will be put in the center, in floating mode. + ''; + example = true; + }; + + follow = mkOption { + type = types.nullOr types.bool; + default = null; + description = "Whether focus should follow the node when it is moved."; + example = true; + }; + + manage = mkOption { + type = types.nullOr types.bool; + default = null; + description = '' + Whether the window should be managed by bspwm. If false, the window + will be ignored by bspwm entirely. This is useful for overlay apps, + e.g. screenshot tools. + ''; + example = true; + }; + + focus = mkOption { + type = types.nullOr types.bool; + default = null; + description = "Whether the node should gain focus on creation."; + example = true; + }; + + border = mkOption { + type = types.nullOr types.bool; + default = null; + description = "Whether the node should have border."; + example = true; + }; + }; + }; + +in + +{ + xsession.windowManager.bspwm = { + enable = mkEnableOption "bspwm window manager."; + + package = mkOption { + type = types.package; + default = pkgs.bspwm; + defaultText = literalExample "pkgs.bspwm"; + description = "bspwm package to use."; + example = literalExample "pkgs.bspwm-unstable"; + }; + + settings = mkOption { + type = with types; let + primitive = either bool (either int (either float str)); + in + attrsOf (either primitive (listOf primitive)); + default = {}; + description = "bspwm configuration"; + example = { + "border_width" = 2; + "split_ratio" = 0.52; + "gapless_monocle" = true; + }; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = "Additional configuration to add."; + example = '' + bspc subscribe all > ~/bspc-report.log & + ''; + }; + + monitors = mkOption { + type = types.attrsOf (types.listOf types.str); + default = {}; + description = "bspc monitor configurations"; + example = { + "HDMI-0" = [ "web" "terminal" "III" "IV" ]; + }; + }; + + rules = mkOption { + type = types.attrsOf rule; + default = {}; + description = "bspc rules"; + example = literalExample '' + { + "Gimp" = { + desktop = "^8"; + state = "floating"; + follow = true; + }; + "Kupfer.py" = { + focus = true; + }; + "Screenkey" = { + manage = false; + }; + } + ''; + }; + + startupPrograms = mkOption { + type = types.listOf types.str; + default = []; + description = "Programs to be executed during startup."; + example = [ + "numlockx on" + "tilda" + ]; + }; + }; +} -- cgit v1.2.3