1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-11-08 19:46:05 +01:00

labwc: Add module for Labwc (#6807)

Add a module for labwc compositor.
Add myself as labwc module maintainer
This commit is contained in:
LesVu 2025-04-17 00:15:07 +08:00 committed by GitHub
parent 5d48f3ded3
commit d8263c0b84
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 741 additions and 0 deletions

View file

@ -756,4 +756,10 @@
github = "mipmip";
githubId = 658612;
};
LesVu = {
name = "John Ferse";
email = "lesvu@ingressland.com";
github = "LesVu";
githubId = 66196443;
};
}

View file

@ -446,6 +446,7 @@ let
./services/window-managers/i3-sway/i3.nix
./services/window-managers/i3-sway/sway.nix
./services/window-managers/i3-sway/swaynag.nix
./services/window-managers/labwc/labwc.nix
./services/window-managers/river.nix
./services/window-managers/spectrwm.nix
./services/window-managers/wayfire.nix

View file

@ -0,0 +1,146 @@
{ lib, ... }:
let
# Escape XML special characters (e.g., <, >, &, etc.)
escape = lib.escapeXML;
# Indent each non-empty line of the given text by `level` using two spaces per level.
indent =
level: text:
let
indentation = lib.concatStrings (lib.genList (_: " ") level); # Two spaces per level
lines = lib.splitString "\n" text; # Split text into lines
indentedLines = map (line: if line == "" then "" else "${indentation}${line}") lines;
in
lib.concatStringsSep "\n" indentedLines;
# Generate a <menu> or <item> or <separator> XML entry based on a menu item definition
generateMenu =
item:
if item ? separator then
let
labelAttr = if item.separator ? label then " label=\"${escape item.separator.label}\"" else "";
in
"<separator${labelAttr} />"
else if item ? menuId then
let
idAttr = " id=\"${escape item.menuId}\"";
labelAttr = if item ? label then " label=\"${escape item.label}\"" else "";
children = if item ? items then lib.concatMapStringsSep "\n" generateMenu item.items else "";
in
"<menu${idAttr}${labelAttr}>\n${indent 1 children}\n</menu>"
else
let
labelAttr = " label=\"${escape item.label}\"";
action = item.action;
nameAttr = " name=\"${escape action.name}\"";
toAttr = if action ? to then " to=\"${escape action.to}\"" else "";
commandAttr = if action ? command then " command=\"${escape action.command}\"" else "";
in
"<item${labelAttr}>\n <action${nameAttr}${toAttr}${commandAttr} />\n</item>";
# Get keys in a preferred order
orderedKeys =
name: keys:
let
# Define key orderings for known structures
tagOrder = {
font = [ "@place" ];
keyboard = [ "default" ];
mouse = [ "default" ];
action = [ "@name" ];
mousebind = [ "@button" ];
};
preferred = lib.attrByPath [ name ] [ ] tagOrder;
cmp =
a: b:
let
ia = lib.lists.findFirstIndex (x: x == a) (-1) preferred;
ib = lib.lists.findFirstIndex (x: x == b) (-1) preferred;
in
if ia == -1 && ib == -1 then
builtins.lessThan a b
else if ia == -1 then
false
else if ib == -1 then
true
else
builtins.lessThan ia ib;
in
builtins.sort cmp keys;
generateRc =
name: value:
# If the value is an attribute set (i.e., a record / dictionary)
if builtins.isAttrs value then
let
# keys = builtins.attrNames value;
keys = orderedKeys name (builtins.attrNames value);
attrKeys = builtins.filter (k: lib.hasPrefix "@" k) keys;
childKeys = builtins.filter (k: !(lib.hasPrefix "@" k)) keys;
# Generate string of XML attributes from keys like "@id" → id="value"
attrs = lib.concatStrings (
map (
k:
let
attrName = builtins.substring 1 999 k; # Remove "@" prefix
attrValue = value.${k};
in
" ${attrName}=\"${escape (builtins.toString attrValue)}\""
) attrKeys
);
# Recursively convert children to XML, with increased indentation
children = lib.concatStringsSep "\n" (map (k: generateRc k value.${k}) childKeys);
in
if children == "" then
# Only attributes — use self-closing tag with attributes
"<${name}${attrs} />"
else
# Attributes and/or children — use full open/close tag
"<${name}${attrs}>\n${indent 1 children}\n</${name}>"
# If the value is a boolean `true`, render as self-closing tag
else if builtins.isBool value && value then
"<${name} />"
# If the value is a list, emit the same tag name for each item
else if builtins.isList value then
# Reuse the same tag name for each list item
lib.concatStringsSep "\n" (map (v: generateRc name v) value)
# All other primitive values: wrap in start/end tag
else
"<${name}>${escape (builtins.toString value)}</${name}>";
generateXML = name: config: extraConfig: ''
<?xml version="1.0" encoding="UTF-8"?>
<!-- ### This file was generated with Nix. Don't modify this file directly. -->
<${name}>
${indent 1 (
lib.concatStringsSep "\n" (
(
if name == "openbox_menu" then
map generateMenu
else if name == "labwc_config" then
lib.mapAttrsToList generateRc
else
builtins.throw "error ${name} is neither openbox_menu nor labwc_config"
)
config
)
)}
${indent 1 extraConfig}
</${name}>
'';
in
{
generateXML = generateXML;
}

View file

@ -0,0 +1,272 @@
{
lib,
pkgs,
config,
...
}:
let
function = import ./function.nix {
inherit lib;
};
xmlFormat = pkgs.formats.xml { };
cfg = config.wayland.windowManager.labwc;
variables = builtins.concatStringsSep " " cfg.systemd.variables;
extraCommands = builtins.concatStringsSep " " (map (f: "&& ${f}") cfg.systemd.extraCommands);
systemdActivation = "${pkgs.dbus}/bin/dbus-update-activation-environment --systemd ${variables} ${extraCommands}";
in
{
meta.maintainers = [ lib.hm.maintainers.LesVu ];
options.wayland.windowManager.labwc = {
enable = lib.mkEnableOption "Labwc, a wayland window-stacking compositor";
package = lib.mkPackageOption pkgs "labwc" {
nullable = true;
extraDescription = ''
Set to `null` to use Nixos labwc package.
'';
};
xwayland.enable = lib.mkEnableOption "XWayland" // {
default = true;
};
rc = lib.mkOption {
type = lib.types.submodule {
freeformType = xmlFormat.type;
};
default = { };
description = ''
Config to configure labwc options.
Use "@attributes" for attributes.
See <https://labwc.github.io/labwc-config.5.html> for configuration.
'';
example = lib.literalExpression ''
{
theme = {
name = "nord";
cornerRadius = 8;
font = {
"@name" = "FiraCode";
"@size" = "11";
};
};
keyboard = {
default = true;
keybind = [
# <keybind key="W-Return"><action name="Execute" command="foot"/></keybind>
{
"@key" = "W-Return";
action = {
"@name" = "Execute";
"@command" = "foot";
};
}
# <keybind key="W-Esc"><action name="Execute" command="loot"/></keybind>
{
"@key" = "W-Esc";
action = {
"@name" = "Execute";
"@command" = "loot";
};
}
];
};
}
'';
};
extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
example = ''
<tablet mapToOutput="" rotate="0" mouseEmulation="no">
<!-- Active area dimensions are in mm -->
<area top="0.0" left="0.0" width="0.0" height="0.0" />
<map button="Tip" to="Left" />
<map button="Stylus" to="Right" />
<map button="Stylus2" to="Middle" />
</tablet>
'';
description = "Extra lines appended to {file}`$XDG_CONFIG_HOME/labwc/rc.xml`.";
};
menu = lib.mkOption {
type = lib.types.listOf xmlFormat.type;
default = [ ];
description = "Config to configure labwc menu";
example = lib.literalExpression ''
[
{
menuId = "client-menu";
label = "Client Menu";
icon = "";
items = [
{
label = "Maximize";
icon = "";
action = {
name = "ToggleMaximize";
};
}
{
label = "Fullscreen";
action = {
name = "ToggleFullscreen";
};
}
{
label = "Always on Top";
action = {
name = "ToggleAlwaysOnTop";
};
}
{
label = "Alacritty";
action = {
name = "Execute";
command = "alacritty";
};
}
{
label = "Move Left";
action = {
name = "SendToDesktop";
to = "left";
};
}
{
separator = { };
}
{
label = "Workspace";
menuId = "workspace";
icon = "";
items = [
{
label = "Move Left";
action = {
name = "SendToDesktop";
to = "left";
};
}
];
}
{
separator = true;
}
];
}
];
'';
};
autostart = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Command to autostart when labwc start.
'';
example = [
"wayvnc &"
"waybar &"
"swaybg -c '#113344' >/dev/null 2>&1 &"
];
};
environment = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Environment variable to add when labwc start.
'';
example = [
"XDG_CURRENT_DESKTOP=labwc:wlroots"
"XKB_DEFAULT_LAYOUT=us"
];
};
systemd = {
enable = lib.mkEnableOption null // {
default = true;
description = ''
Whether to enable {file}`labwc-session.target` on
labwc startup. This links to {file}`graphical-session.target`.
Some important environment variables will be imported to systemd
and D-Bus user environment before reaching the target, including
- `DISPLAY`
- `WAYLAND_DISPLAY`
- `XDG_CURRENT_DESKTOP`
'';
};
variables = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [
"DISPLAY"
"WAYLAND_DISPLAY"
"XDG_CURRENT_DESKTOP"
];
example = [ "-all" ];
description = ''
Environment variables to be imported in the systemd & D-Bus user
environment.
'';
};
extraCommands = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [
"systemctl --user stop labwc-session.target"
"systemctl --user start labwc-session.target"
];
description = "Extra commands to be run after D-Bus activation.";
};
};
};
config = lib.mkIf cfg.enable {
assertions = [
(lib.hm.assertions.assertPlatform "wayland.windowManager.labwc" pkgs lib.platforms.linux)
];
home.packages = lib.mkIf (cfg.package != null) (
[ cfg.package ] ++ lib.optional cfg.xwayland.enable pkgs.xwayland
);
xdg.configFile."labwc/rc.xml".text = function.generateXML "labwc_config" cfg.rc cfg.extraConfig;
xdg.configFile."labwc/menu.xml".text = function.generateXML "openbox_menu" cfg.menu "";
xdg.configFile."labwc/autostart".source = pkgs.writeShellScript "autostart" (
''
### This file was generated with Nix. Don't modify this file directly.
### AUTOSTART SERVICE ###
${lib.concatStringsSep "\n" cfg.autostart}
''
+ (lib.optionalString cfg.systemd.enable ''
### SYSTEMD INTEGRATION ###
${systemdActivation}
'')
);
xdg.configFile."labwc/environment".text = lib.concatStringsSep "\n" (
cfg.environment ++ (lib.optionals (!cfg.xwayland.enable) [ "WLR_XWAYLAND=" ])
);
systemd.user.targets.labwc-session = lib.mkIf cfg.systemd.enable {
Unit = {
Description = "labwc compositor session";
Documentation = [ "man:systemd.special(7)" ];
BindsTo = [ "graphical-session.target" ];
Wants = [ "graphical-session-pre.target" ];
After = [ "graphical-session-pre.target" ];
};
};
};
}

View file

@ -629,6 +629,7 @@ import nmtSrc {
./modules/services/window-managers/herbstluftwm
./modules/services/window-managers/hyprland
./modules/services/window-managers/i3
./modules/services/window-managers/labwc
./modules/services/window-managers/river
./modules/services/window-managers/spectrwm
./modules/services/window-managers/sway

View file

@ -0,0 +1,11 @@
#!/nix/store/58br4vk3q5akf4g8lx0pqzfhn47k3j8d-bash-5.2p37/bin/bash
### This file was generated with Nix. Don't modify this file directly.
### AUTOSTART SERVICE ###
wayvnc &
waybar &
swaybg -c '#113344' >/dev/null 2>&1 &
### SYSTEMD INTEGRATION ###
@dbus@/bin/dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP && systemctl --user stop labwc-session.target && systemctl --user start labwc-session.target

View file

@ -0,0 +1,6 @@
{
labwc-rc-configuration = ./labwc-rc.nix;
labwc-menu-configuration = ./labwc-menu.nix;
labwc-autostart-configuration = ./labwc-autostart.nix;
labwc-environment-configuration = ./labwc-environment.nix;
}

View file

@ -0,0 +1,3 @@
XDG_CURRENT_DESKTOP=labwc:wlroots
XKB_DEFAULT_LAYOUT=us
WLR_XWAYLAND=

View file

@ -0,0 +1,18 @@
{
wayland.windowManager.labwc = {
enable = true;
package = null;
autostart = [
"wayvnc &"
"waybar &"
"swaybg -c '#113344' >/dev/null 2>&1 &"
];
};
nmt.script = ''
labwcAutostart=home-files/.config/labwc/autostart
assertFileExists "$labwcAutostart"
assertFileContent "$labwcAutostart" "${./autostart}"
'';
}

View file

@ -0,0 +1,18 @@
{
wayland.windowManager.labwc = {
enable = true;
package = null;
xwayland.enable = false;
environment = [
"XDG_CURRENT_DESKTOP=labwc:wlroots"
"XKB_DEFAULT_LAYOUT=us"
];
};
nmt.script = ''
labwcEnvironment=home-files/.config/labwc/environment
assertFileExists "$labwcEnvironment"
assertFileContent "$labwcEnvironment" "${./environment}"
'';
}

View file

@ -0,0 +1,99 @@
{
wayland.windowManager.labwc = {
enable = true;
package = null;
menu = [
{
menuId = "client-menu";
label = "Client Menu";
icon = "";
items = [
{
label = "Maximize";
icon = "";
action = {
name = "ToggleMaximize";
};
}
{
label = "Fullscreen";
action = {
name = "ToggleFullscreen";
};
}
{
label = "Always on Top";
action = {
name = "ToggleAlwaysOnTop";
};
}
{
label = "Alacritty";
action = {
name = "Execute";
command = "alacritty";
};
}
{
separator = { };
}
{
label = "Workspace";
menuId = "workspace";
icon = "";
items = [
{
label = "Move Left";
action = {
name = "SendToDesktop";
to = "left";
};
}
];
}
{
separator = {
label = "sep";
};
}
];
}
{
menuId = "menu-two";
label = "Client Menu Two";
icon = "";
items = [
{
label = "Menu In Menu";
menuId = "menu-in-menu";
icon = "";
items = [
{
label = "Menu In Menu In Menu";
menuId = "menu-in-menu-in-menu";
icon = "";
items = [
{
label = "Move Right";
action = {
name = "SendToDesktop";
to = "right";
};
}
];
}
];
}
];
}
{ menuId = ""; }
];
};
nmt.script = ''
labwcMenuConfig=home-files/.config/labwc/menu.xml
assertFileExists "$labwcMenuConfig"
assertFileContent "$labwcMenuConfig" "${./menu.xml}"
'';
}

View file

@ -0,0 +1,81 @@
{
wayland.windowManager.labwc = {
enable = true;
package = null;
rc = {
theme = {
name = "nord";
cornerRadius = 8;
font = {
"@name" = "FiraCode";
"@place" = "";
"@size" = "11";
};
};
mouse = {
default = { };
context = {
"@name" = "Root";
mousebind = [
{
"@button" = "Right";
"@action" = "Press";
action = {
"@name" = "ShowMenu";
"@menu" = "some-custom-menu";
};
}
];
};
};
keyboard = {
default = true;
keybind = [
{
"@key" = "W-Return";
action = {
"@command" = "alacritty";
"@name" = "Execute";
};
}
{
"@key" = "W-Esc";
action = {
"@name" = "Execute";
"@command" = "foot";
};
}
{
"@key" = "W-1";
action = {
"@to" = "1";
"@name" = "GoToDesktop";
};
}
];
};
desktops = {
"@number" = 10;
};
};
extraConfig = ''
<!-- ExtraConfig -->
<tabletTool motion="absolute" relativeMotionSensitivity="1.0" />
<libinput>
<device category="default">
<naturalScroll></naturalScroll>
<leftHanded></leftHanded>
<pointerSpeed></pointerSpeed>
<accelProfile></accelProfile>
</device>
</libinput>
'';
};
nmt.script = ''
labwcConfig=home-files/.config/labwc/rc.xml
assertFileExists "$labwcConfig"
assertFileContent "$labwcConfig" "${./rc.xml}"
'';
}

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- ### This file was generated with Nix. Don't modify this file directly. -->
<openbox_menu>
<menu id="client-menu" label="Client Menu">
<item label="Maximize">
<action name="ToggleMaximize" />
</item>
<item label="Fullscreen">
<action name="ToggleFullscreen" />
</item>
<item label="Always on Top">
<action name="ToggleAlwaysOnTop" />
</item>
<item label="Alacritty">
<action name="Execute" command="alacritty" />
</item>
<separator />
<menu id="workspace" label="Workspace">
<item label="Move Left">
<action name="SendToDesktop" to="left" />
</item>
</menu>
<separator label="sep" />
</menu>
<menu id="menu-two" label="Client Menu Two">
<menu id="menu-in-menu" label="Menu In Menu">
<menu id="menu-in-menu-in-menu" label="Menu In Menu In Menu">
<item label="Move Right">
<action name="SendToDesktop" to="right" />
</item>
</menu>
</menu>
</menu>
<menu id="">
</menu>
</openbox_menu>

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- ### This file was generated with Nix. Don't modify this file directly. -->
<labwc_config>
<desktops number="10" />
<keyboard>
<default />
<keybind key="W-Return">
<action name="Execute" command="alacritty" />
</keybind>
<keybind key="W-Esc">
<action name="Execute" command="foot" />
</keybind>
<keybind key="W-1">
<action name="GoToDesktop" to="1" />
</keybind>
</keyboard>
<mouse>
<default />
<context name="Root">
<mousebind button="Right" action="Press">
<action name="ShowMenu" menu="some-custom-menu" />
</mousebind>
</context>
</mouse>
<theme>
<cornerRadius>8</cornerRadius>
<font place="" name="FiraCode" size="11" />
<name>nord</name>
</theme>
<!-- ExtraConfig -->
<tabletTool motion="absolute" relativeMotionSensitivity="1.0" />
<libinput>
<device category="default">
<naturalScroll></naturalScroll>
<leftHanded></leftHanded>
<pointerSpeed></pointerSpeed>
<accelProfile></accelProfile>
</device>
</libinput>
</labwc_config>