Lib/Rofi: Replace mkSimpleMenu with multi-layer-menu capable mkMenu
This commit is contained in:
197
lib/rofi.nix
197
lib/rofi.nix
@ -3,79 +3,164 @@
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}: rec {
|
||||
}: {
|
||||
# Receives attrs like:
|
||||
# {
|
||||
# "Poweroff" = "poweroff";
|
||||
# "Reload Hyprland" = "hyprctl reload";
|
||||
# }
|
||||
mkSimpleMenu = let
|
||||
# Makes a string like ''"Poweroff" "Reload Hyprland"''
|
||||
unpack-options = attrs: "\"${lib.concatStringsSep "\" \"" (builtins.attrNames attrs)}\"";
|
||||
# mkSimpleMenu = let
|
||||
# # Makes a string like ''"Poweroff" "Reload Hyprland"''
|
||||
# unpack-options = attrs: "\"${lib.concatStringsSep "\" \"" (builtins.attrNames attrs)}\"";
|
||||
#
|
||||
# mkCase = option: action: "else if test \"${option}\" = $OPTION\n set ACTION \"${action}\"";
|
||||
#
|
||||
# cases = attrs:
|
||||
# attrs
|
||||
# |> builtins.mapAttrs mkCase
|
||||
# |> builtins.attrValues
|
||||
# |> builtins.concatStringsSep "\n";
|
||||
# in
|
||||
# {
|
||||
# prompt,
|
||||
# attrs,
|
||||
# command ? ''rofi -dmenu -i'',
|
||||
# }:
|
||||
# pkgs.writeScriptBin "rofi-menu-${prompt}" ''
|
||||
# #! ${pkgs.fish}/bin/fish
|
||||
#
|
||||
# # OPTIONS contains all possible values Rofi will display
|
||||
# set OPTIONS ${unpack-options attrs}
|
||||
#
|
||||
# # We choose a single OPTION using Rofi
|
||||
# set OPTION (echo -e (string join "\n" $OPTIONS) | ${command} -p "${prompt}")
|
||||
#
|
||||
# # Check if the chosen OPTION is a valid choice from OPTIONS
|
||||
# if not contains $OPTION $OPTIONS
|
||||
# exit
|
||||
# end
|
||||
#
|
||||
# # Set a command to execute based on the chosen OPTION
|
||||
# if false
|
||||
# exit # Easier to generate with this
|
||||
# ${cases attrs}
|
||||
# else
|
||||
# exit
|
||||
# end
|
||||
#
|
||||
# # Execute the command
|
||||
# eval $ACTION
|
||||
# '';
|
||||
|
||||
mkCase = option: action: "else if test \"${option}\" = $OPTION\n set ACTION \"${action}\"";
|
||||
# Rofi/Dmenu menu generator.
|
||||
#
|
||||
# Each element in `layers` is one of:
|
||||
# - attrset { "Label" = "value"; } # static options: selected value → $OPTIONn
|
||||
# - string "shell-cmd" # dynamic options from command: selected text → $OPTIONn
|
||||
# # may reference $OPTION0, $OPTION1, ... from earlier layers
|
||||
#
|
||||
# The "command" is the last action, it can reference $OPTION0, $OPTION1, ...
|
||||
# If no "command" is given and the last layer is a static attrset, its selected value is evaluated directly.
|
||||
#
|
||||
# The "prompts" list are optional per-layer prompt strings (falls back to "prompt" if not provided).
|
||||
#
|
||||
# vpn.fish equivalent:
|
||||
# mkMenu {
|
||||
# prompt = "vpn";
|
||||
# layers = [
|
||||
# "cat /etc/rofi-vpns"
|
||||
# { "start" = "start"; "stop" = "stop"; "status" = "status"; }
|
||||
# ];
|
||||
# command = "systemctl $OPTION1 $OPTION0.service";
|
||||
# }
|
||||
#
|
||||
# lectures.fish equivalent:
|
||||
# mkMenu {
|
||||
# prompt = "lecture";
|
||||
# layers = [
|
||||
# "eza -1 -D ~/Notes/TU"
|
||||
# "eza -1 ~/Notes/TU/$OPTION0/Lecture | grep '.pdf'"
|
||||
# ];
|
||||
# command = "xdg-open ~/Notes/TU/$OPTION0/Lecture/$OPTION1";
|
||||
# }
|
||||
mkMenu = {
|
||||
prompt,
|
||||
layers,
|
||||
prompts ? [],
|
||||
command ? null,
|
||||
rofiCmd ? "rofi -dmenu -i",
|
||||
}: let
|
||||
isStaticLayer = layer: builtins.isAttrs layer && !(layer ? options);
|
||||
isDynamicLayer = layer: builtins.isString layer;
|
||||
|
||||
cases = attrs:
|
||||
attrs
|
||||
|> builtins.mapAttrs mkCase
|
||||
|> builtins.attrValues
|
||||
|> builtins.concatStringsSep "\n";
|
||||
in
|
||||
{
|
||||
prompt,
|
||||
attrs,
|
||||
command ? ''rofi -dmenu -p " ${prompt} " -i'',
|
||||
}:
|
||||
pkgs.writeScriptBin "rofi-menu-${prompt}" ''
|
||||
#! ${pkgs.fish}/bin/fish
|
||||
escStr = s: builtins.replaceStrings [''"'' "\\"] [''\"'' "\\\\"] s;
|
||||
|
||||
# OPTIONS contains all possible values Rofi will display
|
||||
set OPTIONS ${unpack-options attrs}
|
||||
layerPrompt = i:
|
||||
if i < builtins.length prompts
|
||||
then lib.elemAt prompts i
|
||||
else prompt;
|
||||
|
||||
# We choose a single OPTION using Rofi
|
||||
set OPTION (echo -e (string join "\n" $OPTIONS) | ${command})
|
||||
|
||||
# Check if the chosen OPTION is a valid choice from OPTIONS
|
||||
if not contains $OPTION $OPTIONS
|
||||
# Static layer: attrset of label -> value
|
||||
# Displays labels in rofi; maps selected label to its value -> $OPTIONi
|
||||
mkStaticLayer = i: attrs: let
|
||||
lp = layerPrompt i;
|
||||
labels = builtins.attrNames attrs;
|
||||
optsStr = "\"${lib.concatStringsSep "\" \"" (map escStr labels)}\"";
|
||||
mkCase = label: value: "else if test \"${escStr label}\" = $_LABEL${toString i}\n set OPTION${toString i} \"${escStr value}\"";
|
||||
casesStr =
|
||||
builtins.concatStringsSep "\n"
|
||||
(builtins.attrValues (builtins.mapAttrs mkCase attrs));
|
||||
in {
|
||||
script = ''
|
||||
set _OPTS${toString i} ${optsStr}
|
||||
set _LABEL${toString i} (echo -e (string join "\n" $_OPTS${toString i}) | ${rofiCmd} -p "${lp}")
|
||||
if not contains $_LABEL${toString i} $_OPTS${toString i}
|
||||
exit
|
||||
end
|
||||
|
||||
# Set a command to execute based on the chosen OPTION
|
||||
if false
|
||||
exit # Easier to generate with this
|
||||
${cases attrs}
|
||||
exit
|
||||
${casesStr}
|
||||
else
|
||||
exit
|
||||
end
|
||||
|
||||
# Execute the command
|
||||
eval $ACTION
|
||||
'';
|
||||
};
|
||||
|
||||
# TODO: I want to generate the containers menu using the actionsA and actionsB attrs:
|
||||
# - actionsA will be generated from the stuff in oci-containers.containers
|
||||
# - actionsB will be set statically for start, stop, status
|
||||
# Dynamic layer: shell command string whose output is piped to rofi
|
||||
# Selected text -> $OPTIONi; may reference earlier $OPTIONn variables
|
||||
mkDynamicLayer = i: cmd: let
|
||||
lp = layerPrompt i;
|
||||
in {
|
||||
script = ''
|
||||
set OPTION${toString i} (${cmd} | ${rofiCmd} -p "${lp}")
|
||||
if test -z $OPTION${toString i}
|
||||
exit
|
||||
end
|
||||
'';
|
||||
};
|
||||
|
||||
# Receives attrs like:
|
||||
# {
|
||||
# optionA = "exa -1 -D ~/Notes/TU";
|
||||
# optionB = "exa -1 -D ~/Notes/TU/$OPTIONA/Lecture | grep \".pdf\"";
|
||||
# commandB = "xdg-open ~/Notes/TU/$OPTIONA/Lecture/$OPTIONB";
|
||||
# }
|
||||
#
|
||||
# Keys:
|
||||
# - optionA, optionB # Command that generates Rofi options:
|
||||
# exa -1 -D ~/Notes/TU
|
||||
# cat /etc/rofi-vpns
|
||||
# - commandA, commandB # Action to execute after sth. was chosen (mutually excl. with command)
|
||||
# - actionsA, actionsB # Configure actions by lookup (mutually excl. with command):
|
||||
# actionsB = {"status" = "systemctl status..."}
|
||||
# - colorA, colorB # Configure highlighting conditions:
|
||||
# colorA = {"red" = "systemctl ... | grep ..."};
|
||||
#
|
||||
# Use $OPTIONA and $OPTIONB to use the options chosen by option<A/B>-command and rofi
|
||||
# Use $EVALA and $EVALB to use the outputs generated by command<A/B>
|
||||
mkMenu = let
|
||||
mkLayer = i: layer:
|
||||
if isStaticLayer layer
|
||||
then mkStaticLayer i layer
|
||||
else if isDynamicLayer layer
|
||||
then mkDynamicLayer i layer
|
||||
else throw "mkMenu: layer ${toString i} has invalid type";
|
||||
|
||||
layerResults = lib.imap0 mkLayer layers; # Map with 0-based index
|
||||
layerScripts = map (r: r.script) layerResults;
|
||||
lastLayer = lib.last layers;
|
||||
|
||||
finalCmd =
|
||||
if command != null
|
||||
then command
|
||||
else if isStaticLayer lastLayer
|
||||
then "$OPTION${toString (builtins.length layers - 1)}"
|
||||
else throw "mkMenu: 'command' must be set when the last layer is not a static attrset";
|
||||
in
|
||||
prompt: attrs: "";
|
||||
pkgs.writeScriptBin "rofi-menu-${prompt}" ''
|
||||
#! ${pkgs.fish}/bin/fish
|
||||
|
||||
${lib.concatStringsSep "\n" layerScripts}
|
||||
eval "${finalCmd}"
|
||||
'';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user