Rofi allows defining custom modes (see the spec).
This lib is a reference implementation with some extra "sugar".
Features:
- simple menu definition via python
- extendable
- async in first place
- allows keeping state during rofi session
Simple demo:
- rofi >= 1.5.4
- python >= 3.6
Using pip
$ pip install rofi-menuCreate a python script which will be used for rofi mode
e.g. example.py (don't forget to mark it as executable -- chmod +x ./example.py)
Assuming you installed rofi-menu into a virtual environment (let's say it's ~/.pyenv/versions/rofi/bin/python).
Make sure shebang points to the right python executable, e.g. #!/home/user/pyenv/versions/rofi/bin/python.
#!/home/user/.pyenv/versions/rofi/bin/pythonimportrofi_menuclassProjectsMenu(rofi_menu.Menu):
prompt="Projects"items= [
rofi_menu.BackItem(),
rofi_menu.ShellItem("Project 1", "code-insiders ~/Develop/project1"),
rofi_menu.ShellItem("Project 2", "code-insiders ~/Develop/project2"),
rofi_menu.ShellItem("Project X", "code-insiders ~/Develop/projectx"),
]
classLogoutMenu(rofi_menu.Menu):
prompt="Logout"items= [
rofi_menu.ShellItem("Yes", "i3-msg exit", flags={rofi_menu.FLAG_STYLE_URGENT}),
rofi_menu.ExitItem("No", flags={rofi_menu.FLAG_STYLE_ACTIVE}),
]
classMainMenu(rofi_menu.Menu):
prompt="menu"items= [
rofi_menu.TouchpadItem(),
rofi_menu.NestedMenu("Projects >", ProjectsMenu()),
rofi_menu.ShellItem(
"Downloads (show size)", "du -csh ~/Downloads", show_output=True
),
rofi_menu.NestedMenu("Second monitor", rofi_menu.SecondMonitorMenu()),
rofi_menu.ShellItem("Lock screen", "i3lock -i ~/.config/i3/bg.png"),
rofi_menu.ShellItem("Sleep", "systemctl suspend"),
rofi_menu.NestedMenu("Logout", LogoutMenu()),
]
if__name__=="__main__":
rofi_menu.run(MainMenu(), rofi_version="1.6") # change to 1.5 if you use older rofi versionRun it as:
$ rofi -modi mymenu:/path/to/example.py -show mymenu -show-iconsIt'll result in
#!/home/user/pyenv/versions/rofi/bin/pythonimportasynciofromdatetimeimportdatetimeimportosimportrofi_menuclassOutputSomeTextItem(rofi_menu.Item):
"""Output arbitrary text on selection"""asyncdefon_select(self, meta):
# any python codeawaitasyncio.sleep(0.1)
returnrofi_menu.Operation(rofi_menu.OP_OUTPUT, (
"💢 simple\n""💥 multi-\n""💫 <b>line</b>\n""💣 <i>text</i>\n"
))
classDoAndExitItem(rofi_menu.Item):
"""Do something and exit"""asyncdefon_select(self, meta):
os.system('notify-send msg')
returnrofi_menu.Operation(rofi_menu.OP_EXIT)
classCurrentDatetimeItem(rofi_menu.Item):
"""Show current datetime inside menu item"""asyncdefload(self, meta):
self.state=datetime.now().strftime('%A %d. %B %Y (%H:%M:%S)')
asyncdefrender(self, meta):
returnf"🕑 {self.state}"classCounterItem(rofi_menu.Item):
"""Increment counter on selection"""asyncdefload(self, meta):
awaitsuper().load(meta)
self.state=self.stateor0meta.session.setdefault("counter_total", 0)
asyncdefon_select(self, meta):
self.state+=1meta.session["counter_total"] +=1returnawaitsuper().on_select(meta)
asyncdefrender(self, meta):
per_menu_item=self.statetotal=meta.session["counter_total"]
returnf"🏃 Selected #{per_menu_item} time(s) (across menu items #{total})"classHandleUserInputMenu(rofi_menu.Menu):
allow_user_input=TrueclassCustomItem(rofi_menu.Item):
asyncdefrender(self, meta):
entered_text=meta.session.get("text", "[ no text ]")
returnf"You entered: {entered_text}"items= [CustomItem()]
asyncdefon_user_input(self, meta):
meta.session['text'] =meta.user_inputreturnrofi_menu.Operation(rofi_menu.OP_REFRESH_MENU)
main_menu=rofi_menu.Menu(
prompt="menu",
items=[
OutputSomeTextItem("Output anything"),
DoAndExitItem("Do something and exit"),
CurrentDatetimeItem(),
CounterItem(),
CounterItem(),
rofi_menu.NestedMenu("User input", HandleUserInputMenu()),
],
)
if__name__=="__main__":
rofi_menu.run(main_menu)- documentation of API
- examples
- tests


