Skip to content

Commit

Permalink
Add support for rofi 1.6
Browse files Browse the repository at this point in the history
  • Loading branch information
miphreal committed Dec 6, 2020
1 parent de317d4 commit 9cd59ab
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 38 deletions.
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Assuming you installed `rofi-menu` into a virtual environment (let's say it's `~
Make sure shebang points to the right python executable, e.g. `#!/home/user/pyenv/versions/rofi/bin/python`.

```python
#!/home/user/pyenv/versions/rofi/bin/python
#!/home/user/.pyenv/versions/rofi/bin/python
import rofi_menu


Expand Down Expand Up @@ -82,13 +82,13 @@ class MainMenu(rofi_menu.Menu):


if __name__ == "__main__":
rofi_menu.run(MainMenu())
rofi_menu.run(MainMenu(), rofi_version="1.6") # change to 1.5 if you use older rofi version
```

Run it as:

```sh
$ rofi -modi mymenu:/path/to/example.py -show mymenu
$ rofi -modi mymenu:/path/to/example.py -show mymenu -show-icons
```

It'll result in
Expand Down Expand Up @@ -156,6 +156,8 @@ class CounterItem(rofi_menu.Item):


class HandleUserInputMenu(rofi_menu.Menu):
allow_user_input = True

class CustomItem(rofi_menu.Item):
async def render(self, meta):
entered_text = meta.session.get("text", "[ no text ]")
Expand All @@ -168,7 +170,6 @@ class HandleUserInputMenu(rofi_menu.Menu):
return rofi_menu.Operation(rofi_menu.OP_REFRESH_MENU)



main_menu = rofi_menu.Menu(
prompt="menu",
items=[
Expand All @@ -193,5 +194,3 @@ if __name__ == "__main__":
- [ ] documentation of API
- [ ] examples
- [ ] tests
- [ ] `nonselectable` and other new
- [ ] check what ROFI_* envs can do
5 changes: 3 additions & 2 deletions example.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/home/user/pyenv/versions/rofi/bin/python
#!/home/user/.pyenv/versions/rofi/bin/python
import rofi_menu


Expand All @@ -8,6 +8,7 @@ class ProjectsMenu(rofi_menu.Menu):
rofi_menu.BackItem(),
rofi_menu.ShellItem("Project 1", "code-insiders ~/Develop/project1"),
rofi_menu.ShellItem("Project 2", "code-insiders ~/Develop/project2"),
rofi_menu.Delimiter(),
rofi_menu.ShellItem("Project X", "code-insiders ~/Develop/projectx"),
]

Expand Down Expand Up @@ -36,4 +37,4 @@ class MainMenu(rofi_menu.Menu):


if __name__ == "__main__":
rofi_menu.run(MainMenu())
rofi_menu.run(MainMenu(), rofi_version="1.6", debug=True)
2 changes: 1 addition & 1 deletion rofi_menu/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .constants import *
from .contrib import *
from .main import run
from .menu import Menu, NestedMenu, Item, BackItem, ExitItem, Operation
from .menu import Menu, NestedMenu, Item, BackItem, ExitItem, Operation, Delimiter
2 changes: 0 additions & 2 deletions rofi_menu/constants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
MENU_ITEM_META_DELIM = "\r!"

FLAG_STYLE_URGENT = "URGENT"
FLAG_STYLE_ACTIVE = "ACTIVE"

Expand Down
44 changes: 39 additions & 5 deletions rofi_menu/main.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import asyncio
import sys
import os

from rofi_menu.constants import ROOT_MENU_ID, OP_EXIT, OP_OUTPUT
from rofi_menu.menu import MetaStore, Menu
from rofi_menu.session import FileSession, session_middleware


def _output_menu(data: str) -> None:
def _output_menu(data: str, meta: MetaStore) -> None:
sys.stdout.write(data)

if meta.debug:
sys.stderr.write("{===== Menu output:\n")
sys.stderr.writelines(f"{line!r}\n" for line in data.split("\n"))
sys.stderr.write("\n}===== End menu output.\n")


async def main(menu: Menu, meta: MetaStore) -> None:
menu = await menu.build(menu_id=[ROOT_MENU_ID], meta=meta)
Expand All @@ -22,19 +28,47 @@ async def main(menu: Menu, meta: MetaStore) -> None:
# User entered a text and hasn't selected any menu item.
op = await menu.propagate_user_input(meta)

if meta.debug:
sys.stderr.write(f"Result: {op.code}\n")

if op.code == OP_OUTPUT:
_output_menu(op.data)
_output_menu(op.data, meta)

elif op.code == OP_EXIT:
exit(op.data or 1)

else:
_output_menu(await menu.handle_render(meta))
_output_menu(await menu.handle_render(meta), meta)


def run(menu: Menu, stateful: bool=True, middlewares=None) -> None:
def run(
menu: Menu,
stateful: bool = True,
middlewares=None,
rofi_version="1.6",
debug: bool = False,
) -> None:
"""Shortcut for running menu generation."""
meta = MetaStore(sys.argv[1] if len(sys.argv) > 1 else None)
if debug:
sys.stderr.writelines(
[
f"\n\n=> Script call \n",
f"* Configured to work with Rofi v{rofi_version}\n",
f"* Debug mode: {debug}\n",
f"* Stateful script mode: {stateful}\n",
f"* Script params {sys.argv[1:]}\n",
f"* Script envs:\n",
f"\tROFI_OUTSIDE={os.getenv('ROFI_OUTSIDE')}\n",
f"\tROFI_RETV={os.getenv('ROFI_RETV')}\n",
f"\tROFI_INFO={os.getenv('ROFI_INFO')}\n",
]
)

meta = MetaStore(
sys.argv[1] if len(sys.argv) > 1 else None,
rofi_version=rofi_version,
debug=debug,
)

middlewares = list(middlewares or [])
if stateful:
Expand Down
94 changes: 74 additions & 20 deletions rofi_menu/menu.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
import asyncio
import sys
import json

from rofi_menu import constants, rofi_mode
from rofi_menu import constants
from rofi_menu import rofi_mode
from rofi_menu.rofi_mode import get_rofi_mode


class MetaStore:
def __init__(self, raw_script_input):
def __init__(
self, raw_script_input, rofi_version: str = "1.5", debug: bool = False
):
self.rofi_mode = get_rofi_mode(rofi_version)
self.debug = debug
self.raw_script_input = raw_script_input
self._meta = rofi_mode.parse_meta(raw_script_input)
self._meta = self.rofi_mode.parse_meta(raw_script_input)

if debug:
debug_meta = json.dumps(self._meta, indent=2, sort_keys=True)
sys.stderr.write(f"* Parsed meta:\n{debug_meta}\n")

@property
def selected_id(self):
Expand Down Expand Up @@ -36,6 +48,8 @@ def __init__(self, code, data=None):
class Item:
icon = None
text = None
searchable_text = None
nonselectable = False

def __init__(self, text=None, **kwargs):
self.id = kwargs.get("item_id")
Expand All @@ -47,6 +61,8 @@ def __init__(self, text=None, **kwargs):
# render options
self.text = text or self.text
self.icon = kwargs.get("icon", self.icon)
self.searchable_text = kwargs.get("searchable_text", self.searchable_text)
self.nonselectable = kwargs.get("nonselectable", self.nonselectable)
self.flags = kwargs.get("flags") or set()

# filled after attaching to menu
Expand All @@ -64,7 +80,9 @@ async def build(self, parent_menu, item_id, meta):
It also links item to concreate menu, assigns an id and returns "bound" element.
"""
obj = self.clone()
obj.id = obj.id or (item_id if isinstance(item_id, list) else [*parent_menu.id, item_id])
obj.id = obj.id or (
item_id if isinstance(item_id, list) else [*parent_menu.id, item_id]
)
obj.parent_menu = parent_menu
return obj

Expand Down Expand Up @@ -107,6 +125,11 @@ async def handle_select(self, meta):
return op


class Delimiter(Item):
text = "<span foreground='gray' strikethrough='true'> </span>"
nonselectable = True


class BackItem(Item):
text = ".."

Expand Down Expand Up @@ -145,10 +168,14 @@ async def handle_select(self, meta):
op = await self.sub_menu.propagate_select(meta)

if op.code == constants.OP_REFRESH_MENU:
return Operation(constants.OP_OUTPUT, await self.sub_menu.handle_render(meta))
return Operation(
constants.OP_OUTPUT, await self.sub_menu.handle_render(meta)
)

if op.code == constants.OP_BACK_TO_PARENT_MENU:
return Operation(constants.OP_OUTPUT, await self.parent_menu.handle_render(meta))
return Operation(
constants.OP_OUTPUT, await self.parent_menu.handle_render(meta)
)

return op

Expand All @@ -159,22 +186,30 @@ async def propagate_user_input(self, meta):
op = await self.sub_menu.propagate_user_input(meta)

if op.code == constants.OP_REFRESH_MENU:
return Operation(constants.OP_OUTPUT, await self.sub_menu.handle_render(meta))
return Operation(
constants.OP_OUTPUT, await self.sub_menu.handle_render(meta)
)

if op.code == constants.OP_BACK_TO_PARENT_MENU:
return Operation(constants.OP_OUTPUT, await self.parent_menu.handle_render(meta))
return Operation(
constants.OP_OUTPUT, await self.parent_menu.handle_render(meta)
)

return op


class Menu:
prompt = "menu"
items = ()
allow_user_input = False

def __init__(self, prompt=None, items=None, **kwargs):
def __init__(self, prompt=None, items=None, allow_user_input=None, **kwargs):
self.id = kwargs.get("menu_id")
self.prompt = prompt or self.prompt
self.items = items or self.items
self.allow_user_input = (
allow_user_input if allow_user_input is not None else self.allow_user_input
)

def clone(self):
obj = self.__class__()
Expand All @@ -189,10 +224,16 @@ async def build(self, menu_id, meta):

async def build_menu_items(self, meta):
items = await self.generate_menu_items(meta=meta)
return await asyncio.gather(*[
item.build(parent_menu=self, item_id=item.id or [*self.id, str(item_index)], meta=meta)
for item_index, item in enumerate(items)
])
return await asyncio.gather(
*[
item.build(
parent_menu=self,
item_id=item.id or [*self.id, str(item_index)],
meta=meta,
)
for item_index, item in enumerate(items)
]
)

async def generate_menu_items(self, meta):
return self.items
Expand All @@ -205,24 +246,33 @@ async def render(self, meta):
*[item.handle_render(meta) for item in self.items]
)

_rofi_menu = []
_rofi_menu = [
meta.rofi_mode.menu_prompt(self.prompt),
meta.rofi_mode.menu_enable_markup(),
]

if not self.allow_user_input:
_rofi_menu.append(meta.rofi_mode.menu_no_input())

for num, item in enumerate(self.items):
if constants.FLAG_STYLE_ACTIVE in item.flags:
_rofi_menu.append(rofi_mode.menu_active(num))
_rofi_menu.append(meta.rofi_mode.menu_active(num))
if constants.FLAG_STYLE_URGENT in item.flags:
_rofi_menu.append(rofi_mode.menu_urgent(num))
_rofi_menu.append(meta.rofi_mode.menu_urgent(num))

common_meta = meta.as_dict()
_rofi_menu.extend(
rofi_mode.menu_item(
meta.rofi_mode.menu_item(
text=text,
icon=item.icon,
searchable_text=item.searchable_text,
nonselectable=item.nonselectable,
meta_data={**common_meta, "text": text, "id": item.id},
)
for text, item in zip(rendered_items, self.items)
)

return rofi_mode.render_menu(self.prompt, *_rofi_menu)
return meta.rofi_mode.render_menu(self.prompt, *_rofi_menu)

async def post_render(self, meta):
if hasattr(meta, "session"):
Expand All @@ -242,14 +292,18 @@ async def propagate_select(self, meta):
op = await item.handle_select(meta)

if op.code == constants.OP_REFRESH_MENU:
return Operation(constants.OP_OUTPUT, await self.handle_render(meta))
return Operation(
constants.OP_OUTPUT, await self.handle_render(meta)
)

return op

return Operation(constants.OP_OUTPUT, await self.handle_render(meta))

async def propagate_user_input(self, meta):
menu_id = meta.session.get("last_active_menu") if hasattr(meta, "session") else None
menu_id = (
meta.session.get("last_active_menu") if hasattr(meta, "session") else None
)

op = Operation(constants.OP_REFRESH_MENU)

Expand Down
14 changes: 14 additions & 0 deletions rofi_menu/rofi_mode/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
def get_rofi_mode(version: str):
ver = tuple(int(part) for part in version.split("."))

if ver < (1, 6):
from . import rofi_mode_15

return rofi_mode_15

if ver >= (1, 6):
from . import rofi_mode_16

return rofi_mode_16

raise RuntimeError("Wrong version configuration")
Loading

0 comments on commit 9cd59ab

Please sign in to comment.