1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2025-11-09 03:56:04 +01:00
home-manager/modules/services/window-managers/labwc/function.nix
LesVu 2fbd694fec labwc: Fix pipemenu(#7236) and icon in menu generation
Add the generation of icon and pipemenu
2025-06-22 22:51:51 -05:00

154 lines
5.1 KiB
Nix

{ 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 "";
iconAttr = if item ? icon then " icon=\"${escape item.icon}\"" else "";
children = if item ? items then lib.concatMapStringsSep "\n" generateMenu item.items else "";
executeAttr = if item ? execute then " execute=\"${escape item.execute}\"" else "";
outputMenu =
if item ? execute || children == "" then
"<menu${idAttr}${labelAttr}${iconAttr}${executeAttr} />"
else
"<menu${idAttr}${labelAttr}${iconAttr}${executeAttr}>\n${indent 1 children}\n</menu>";
in
outputMenu
else
let
labelAttr = " label=\"${escape item.label}\"";
iconAttr = if item ? icon then " icon=\"${escape item.icon}\"" else "";
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}${iconAttr}>\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;
}