Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

meta.raw_input inaccessible inside NestedMenu #2

Closed
RafalSkolasinski opened this issue Jul 27, 2020 · 4 comments
Closed

meta.raw_input inaccessible inside NestedMenu #2

RafalSkolasinski opened this issue Jul 27, 2020 · 4 comments

Comments

@RafalSkolasinski
Copy link

Hi @miphreal,

It seems that making use of the raw input does not work fully inside NestedMenu (or I am doing something wrong ;)

Here's my approach:

#!/usr/bin/env python3
import rofi_menu


class WorkspaceItem(rofi_menu.Item):
    def __init__(self, workspace, i3, **kwargs):
        self.workspace = workspace
        self.i3 = i3

        flags = kwargs.get('flags', set())
        if workspace.focused:
            flags.add(rofi_menu.FLAG_STYLE_ACTIVE)

        kwargs['text'] = workspace.name
        kwargs['flags'] = flags
        super().__init__(**kwargs)

    def clone(self):
        return WorkspaceItem(self.workspace, self.i3)

    async def on_select(self, item_id, meta):
        await self.i3.command(f"workspace {self.workspace.name}")
        return rofi_menu.Operation(rofi_menu.OP_EXIT)


class I3Workspaces(rofi_menu.Menu):
    prompt = 'workspaces'

    async def generate_menu_items(self, prefix_path, meta):
        from i3ipc.aio import Connection

        self.i3 = await Connection(auto_reconnect=True).connect()
        self.workspaces = await self.i3.get_workspaces()

        return [(w.name, WorkspaceItem(workspace=w, i3=self.i3)) for w in self.workspaces]

    async def on_select(self, item_id, meta):
        if not item_id and meta.raw_input:
            await self.i3.command(f'workspace {meta.raw_input}')
            return rofi_menu.Operation(rofi_menu.OP_EXIT)

        return await super().on_select(item_id, meta)


class CounterItem(rofi_menu.Item):
    """Increment counter on selection"""
    async def load(self, meta):
        await super().load(meta)
        self.state = self.state or 0

    async def on_select(self, item_id, meta):
        self.state += 1
        return await super().on_select(item_id, meta)

    async def render(self, meta):
        return f"🏃 Selected #{self.state} time(s)"


class MainMenu(rofi_menu.Menu):
    prompt = "main menu"
    items = [
        rofi_menu.NestedMenu("Choose workspace", I3Workspaces()),
        CounterItem(),
    ]


if __name__ == "__main__":
    rofi_menu.run(MainMenu()),

Unfortunately when executing I am getting error

satriani ➜  i3-flow git:(master) ✗ rofi -modi mymenu:./test.py -show mymenu                                                                 (do-lon1-dev-rafal-jnj-simulator/default)
Traceback (most recent call last):
  File "./test.py", line 71, in <module>
    rofi_menu.run(MainMenu()),
  File "/home/rskolasinski/.local/lib/python3.8/site-packages/rofi_menu-0.3.0-py3.8.egg/rofi_menu/main.py", line 30, in run
    asyncio.run(main(menu, sys.argv[1] if len(sys.argv) > 1 else None))
  File "/usr/lib/python3.8/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
    return future.result()
  File "/home/rskolasinski/.local/lib/python3.8/site-packages/rofi_menu-0.3.0-py3.8.egg/rofi_menu/main.py", line 18, in main
    op = await menu.handle_select(meta.selected_id, meta)
  File "/home/rskolasinski/.local/lib/python3.8/site-packages/rofi_menu-0.3.0-py3.8.egg/rofi_menu/menu.py", line 226, in handle_select
    op = await self.on_select(item_id, meta)
  File "/home/rskolasinski/.local/lib/python3.8/site-packages/rofi_menu-0.3.0-py3.8.egg/rofi_menu/menu.py", line 211, in on_select
    if item.id == item_id[: len(item.id)]:
TypeError: 'NoneType' object is not subscriptable
satriani ➜  i3-flow git:(master) ✗ rofi -modi mymenu:./test.py -show mymenu                                                                 (do-lon1-dev-rafal-jnj-simulator/default)
Traceback (most recent call last):
  File "./test.py", line 68, in <module>
    rofi_menu.run(MainMenu()),
  File "/home/rskolasinski/.local/lib/python3.8/site-packages/rofi_menu-0.3.0-py3.8.egg/rofi_menu/main.py", line 30, in run
    asyncio.run(main(menu, sys.argv[1] if len(sys.argv) > 1 else None))
  File "/usr/lib/python3.8/asyncio/runners.py", line 43, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
    return future.result()
  File "/home/rskolasinski/.local/lib/python3.8/site-packages/rofi_menu-0.3.0-py3.8.egg/rofi_menu/main.py", line 18, in main
    op = await menu.handle_select(meta.selected_id, meta)
  File "/home/rskolasinski/.local/lib/python3.8/site-packages/rofi_menu-0.3.0-py3.8.egg/rofi_menu/menu.py", line 226, in handle_select
    op = await self.on_select(item_id, meta)
  File "/home/rskolasinski/.local/lib/python3.8/site-packages/rofi_menu-0.3.0-py3.8.egg/rofi_menu/menu.py", line 211, in on_select
    if item.id == item_id[: len(item.id)]:
TypeError: 'NoneType' object is not subscriptable
@miphreal
Copy link
Owner

Hi @RafalSkolasinski, unfortunately, currently there're some limitations of handling user's input for nested menus. Because of the way rofi interacts with the script, it's not possible to track which submenu should handle user's input.

let me describe the flow for handling selected items:

  1. user executes rofi -modi mymenu:./test.py -show mymenu
  2. ./test.py outputs to stdout menu items (e.g. Choose workspaces\r<some meta>\nSelected #0 time(s)\r<some meta>, and exits (<some meta> contains item's id and its state)
  3. rofi renders that output
  4. user selects "Choose workspaces" and rofi executes roughly ./test.py "Choose workspaces\r<some meta>"
  5. ./test.py generates workspaces submenu and outputs it (e.g. Workspace 1\r<some meta>\nWorkspace 2\r<some meta>)
  6. rofi renders that output
  7. if user selects a workspace, rofi passes that menu item back to the script, i.e. ./test.py "Workspace 1\r<some meta>" and the script can parse <some meta>, figure out menu item id and select proper menu item in the generated tree of menu/submenu/menu items.

if user enters any input, there's no way to understand that this input should be handled by concrete submenu, because on any user input rofi just executes the script with that input as the first param (e.g. ./test.py "new workspaces")

I see a couple solutions:

  1. ./test.py should be stateful and remember previously selected menus (tho it might be tricky to handle) -- I'll think of it 🤔
  2. rofi executes ./test.py only once and then interacts with it through stdin/out (I saw some discussion here [REQUEST] modi script mode - event based system davatorium/rofi#1082, looks like rofi-blocks can do a trick, but I'm not familiar with it)

@miphreal
Copy link
Owner

miphreal commented Aug 4, 2020

Some updates: pushed v0.4.0

@RafalSkolasinski, you might be interested in the examples here

Handling user input in submenu should work.

The example for i3 workspaces could look like

#!/home/user/.pyenv/versions/rofi/bin/python
from i3ipc.aio import Connection
import rofi_menu


class WorkspaceItem(rofi_menu.Item):
    def __init__(self, workspace, i3, **kwargs):
        self.workspace = workspace
        self.i3 = i3

        flags = kwargs.get('flags', set())
        if workspace.focused:
            flags.add(rofi_menu.FLAG_STYLE_ACTIVE)

        kwargs['flags'] = flags

        super().__init__(workspace.name, **kwargs)

    def clone(self):
        return WorkspaceItem(self.workspace, self.i3)

    async def on_select(self, meta):
        await self.i3.command(f"workspace {self.workspace.name}")
        return rofi_menu.Operation(rofi_menu.OP_EXIT)


class I3Workspaces(rofi_menu.Menu):
    prompt = 'workspaces'
    allow_user_input = True

    async def generate_menu_items(self, meta):
        self.i3 = await Connection(auto_reconnect=True).connect()
        self.workspaces = await self.i3.get_workspaces()
        return [WorkspaceItem(item_id=w.name, workspace=w, i3=self.i3) for w in self.workspaces]

    async def on_user_input(self, meta):
        await self.i3.command(f'workspace {meta.user_input}')
        return rofi_menu.Operation(rofi_menu.OP_EXIT)


class MainMenu(rofi_menu.Menu):
    prompt = "main menu"
    items = [
        rofi_menu.NestedMenu("Choose workspace", I3Workspaces()),
    ]


if __name__ == "__main__":
    rofi_menu.run(MainMenu())

@RafalSkolasinski
Copy link
Author

@miphreal Thanks for pinging me, I will give it a spin :)

I am afraid it may not happen until the weekend though

@miphreal
Copy link
Owner

miphreal commented Dec 6, 2020

closing this issue, handling user input in nested menus was implemented

@miphreal miphreal closed this as completed Dec 6, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants