{"id":19429791,"url":"https://github.com/chayleaf/rofi-menu-stack","last_synced_at":"2025-07-31T01:42:07.325Z","repository":{"id":200835817,"uuid":"706347075","full_name":"chayleaf/rofi-menu-stack","owner":"chayleaf","description":"a stack machine for rofi intended for building complex menus","archived":false,"fork":false,"pushed_at":"2023-11-09T12:45:27.000Z","size":78,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-07T20:12:22.108Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chayleaf.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-10-17T19:11:13.000Z","updated_at":"2023-12-18T05:56:35.000Z","dependencies_parsed_at":"2023-11-09T13:44:31.403Z","dependency_job_id":null,"html_url":"https://github.com/chayleaf/rofi-menu-stack","commit_stats":null,"previous_names":["chayleaf/rofi-menu-stack"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chayleaf%2Frofi-menu-stack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chayleaf%2Frofi-menu-stack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chayleaf%2Frofi-menu-stack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chayleaf%2Frofi-menu-stack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chayleaf","download_url":"https://codeload.github.com/chayleaf/rofi-menu-stack/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240612540,"owners_count":19829027,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-10T14:21:23.145Z","updated_at":"2025-02-25T05:42:09.370Z","avatar_url":"https://github.com/chayleaf.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# rofi-menu-stack\n\nThis is a stack machine for writing complex menus in `rofi{,-wayland}`.\nThink a Forth DSL for `bash`+`rofi`... Technically it can be adapted to\nother menus (like `bemenu`/`dmenu`), but I'm not interested in it\nbecause `rofi` has more features anyway.\n\nI've made this because I want to create a declarative alternative to\n[SXMO](https://sxmo.org), which uses dmenu/bemenu extensively.\n\nAs an example, let's assume we want to create a \"system settings\" menu,\nand that we will have a `Settings -\u003e Audio -\u003e \u003cdevice\u003e -\u003e Volume`\nsubmenu. If the user selects the \"close\" button, the menu closes, if the\nuser selects the \"back\" button, they get back to the per-audio device\nmenu, and if the user enters a number, the device volume changes to that\nnumber.\n\nIn that case, the script that manages that menu will have to know what\ndevice was chosen in a previous menu - hence the stack. And a call stack\nis necessary as well, so the back button can work.\n\n## Dependencies\n\nlib.sh depends on `jq`. Of course, `rofi` is required as well (I use\n[`rofi-wayland`](https://github.com/lbonn/rofi)).\n\n## Spec\n\n[JSON5](https://json5.org) is used everywhere (a subset of ECMAScript, a\nsuperset of JSON).\n\nEach submenu is generated with a shell script. The script arguments are\nwhatever is currently on the stack.\n\nWhen a user selects an option, a certain amount of values is popped from\nthe stack, certain values are pushed onto the stack, and a command may\nbe executed.\n\nBesides the value stack, there's also the call stack. Of course it's\ntechnically possible to merge them, but that isn't very convenient, so\nthey are separate.\n\nThe call stack is manipulated in a similar way to the value stack. The\ntop value in the call stack determines which script will be used for\nthe next menu.\n\nInitial call stack contents is provided in the `INITIAL_SCRIPT` env var,\ninitial value stack contents are provided in the `INITIAL_STACK` env\nvar. They can either be JSON5 arrays, or simple strings (in which case\nthat string will be the only value on the stack - by the way, the string\ncan't start with `[` and end with `]`, or it will be parsed as a JSON5\narray).\n\n### Menu options\n\nGlobal menu options must be printed by the script before all menu\nentries, and are mandatory. All options are optional, so an empty object\nis a valid instance of menu options.\n\n- `prompt: \u003cstring\u003e` - user prompt\n- `message: \u003cstring\u003e` - user-facing message (notice, etc)\n- `markup: \"pango\"` - to enable pango markup\n- `selection: \u003cnumber\u003e` - to select item by index (0-based)\n- `selection: \"keep\"` - to keep whatever was selected previously\n- `autoselect: true` - to autoselect the only option available if\n  there's only one (allows modifying the stacks without user input)\n- `fallback: {...}` - this allows the user to input custom text. The\n  format is similar to per-row options, but doesn't allow any cosmetic\n  fields (i.e. only stack operations/commands are accepted).\n\n### Menu Entry\n\nEach entry is defined as follows:\n\n- Cosmetic options:\n  - `text: \u003cstring\u003e` - user-facing text\n  - `icon: \u003cpath\u003e` - a path to this option's graphical icon\n  - `meta: \u003cstring\u003e` - search terms for this entry (hidden from the\n    user)\n  - `selectable: false` - marks the entry as unselectable\n  - `urgent: true` - marks the entry as urgent\n  - `active: true` - marks the entry as active\n- Operations to be executed on entry selection:\n  - `pop: null` - remove all values from the stack\n  - `pop: \u003cnumber\u003e` - remove a certain amount of values from the top of\n    the stack\n  - `push: \u003cstring/list/null\u003e` - add values on top of the stack when\n    this option is selected. `null` means add user input.\n    - If only one item is to be pushed, you can simply use that item\n      instead of enclosing it in a list (i.e. `push: \"a\"` instead of\n      `push: [\"a\"]`)\n    - If a list is one of the items of the list, the values will be\n      concatenated. For example, `[[\"a\", null], \"b\"]` will push the\n      concatenation of `a` and user input, and then push `b`.\n  - `jump: \u003cstring/list/null\u003e` - push a new script to the call stack,\n    exactly the same format as `push`\n  - `return: ...` - pop scripts from the call stack, exactly the same\n    format as `pop`.\n  - `goto: \u003cstring\u003e` - shorthand for `return: 1; jump: \u003cstring\u003e` (jumps\n    to another script without remembering this script)\n  - `exec: \u003cstring/list/null\u003e` - bash command to execute. The format\n    is the same as `push` and `jump`, each array element is an argument,\n    starting from argv0.\n    - If you only pass a single string not enclosed in an array, it will\n      be interpreted as the entire command line (rather than the argv0).\n  - `fork: true` - don't wait for the bash command's completion and\n    run it in the background\n  - `menu: {...}` - menu options to override after this option is\n    selected\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchayleaf%2Frofi-menu-stack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchayleaf%2Frofi-menu-stack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchayleaf%2Frofi-menu-stack/lists"}