hyprland-community / pyprland

Scratchpads & many goodies for Hyprland [maintainer=@fdev31]
MIT License
392 stars 17 forks source link

[FEAT] Multiple windows support for scratchpads #87

Closed Asthestarsfalll closed 5 months ago

Asthestarsfalll commented 6 months ago

Some applications have a launch or login window and then it will invocate a new runing window and destroy itself. This will cause pyprland to miss the new window. So can we add a existed window to a scratchpad? The config can be

[scratchpads.tmp]
command = ""
size = "70% 60%"

Use empty command to mark the scratchpad will take any exited windows.

For the first time, the pyprland toggle tmp will add the avtivate window to scratchpad tmp, and hide it. Then the behavior will be as same as normal command util the window are killed.

fdev31 commented 6 months ago

Hm.... do you have a concrete example ? Maybe showing the output of hyprctl -j clients at the different stages ?

I recently added a feature to add any window to an existing scratchpad, but there are no options at the moment to have an "empty" scratchpad and it's hard for me to imagine how it would work...

I see something like:

but I'm not sure it matches what you have in mind...

Asthestarsfalll commented 6 months ago

For example, we use a chat app called QQ. There will be a login interface: 240424_15h53m25s_screenshot After I login, it will destory and a new window will be created: 240424_15h54m07s_screenshot

I see something like:

  • open scratchpad with no command (still recorded but does nothing)
  • "attach" the new window => detects that the scratchpad was empty & make this window the main app of the scratchpad

but I'm not sure it matches what you have in mind...

Yes, it is.

fdev31 commented 6 months ago

can you send either the full information or filter on the concerned client ? class & title were not what I was looking for... I understood there are two different windows, I would like to check other properties, including pid.

Asthestarsfalll commented 6 months ago

Sure. 240424_19h35m30s_screenshot 240424_19h36m00s_screenshot

fdev31 commented 6 months ago

Thank you! I can try something like this, tell me if you feel that it would work (You may need to test an experimental branch of pyprland to provide feedback if/when I implement it):

New option like "multi_window=true" would change the behavior as follows:

Which leads me to one question: isn't pyprland trying to start "QQ" multiple times and fail at it with the current implementation ? (once the login window has been dismissed)

Asthestarsfalll commented 6 months ago

New option like "multi_window=true" would change the behavior as follows:

  • When a scratchpad window has been closed and the scratchpad is requested to be shown

    • check if there is another window with the same PID, waiting for it ~1s
    • if found, use this as the scratchpad window
    • else proceed as of today: start the program again

It looks good to me.

Which leads me to one question: isn't pyprland trying to start "QQ" multiple times and fail at it with the current implementation ? (once the login window has been dismissed)

No, it will be silent. But I remeber in the previous version of pyprland it would raise error that cannot find the target window.

fdev31 commented 6 months ago

May I also ask for the pypr's logs (launch pypr with --debug /tmp/pypr.log) when you call the scratchpad again after the login window has been closed ? (or include everything and I'll try to figure it out)

Asthestarsfalll commented 6 months ago

It seems that the dispatch is now functioning properly, but QQ is still not being displayed. At the lower version (I upgraded both pyprland and QQ, so I don't know what's the reason), pyprland will directly give a error message about cannot find the window.

              scratchpads - run_toggle('qq',) // command.py:189
              scratchpads - visibility_check: ('1', 'eDP-1') == ('1', 'eDP-1') // __init__.py:423
              scratchpads - qq is visible = True (but True) // __init__.py:443
              scratchpads - Hiding qq // __init__.py:683
              scratchpads - dispatch movewindowpixel -326 0,address:0x55efce183cb0 // ipc.py:131
              scratchpads - dispatch movetoworkspacesilent special:scratch_qq,address:0x55efce183cb0 // ipc.py:131
              scratchpads - run_toggle('qq',) // command.py:189
              scratchpads - visibility_check: ('1', 'eDP-1') == ('1', 'eDP-1') // __init__.py:423
              scratchpads - qq is visible = False (but False) // __init__.py:443
              scratchpads - Showing qq // __init__.py:533
              scratchpads - clients // ipc.py:89
              scratchpads - monitors // ipc.py:89
              scratchpads - dispatch ['moveworkspacetomonitor special:scratch_qq eDP-1', 'movetoworkspacesilent 1,address:0x55efce183cb0', 'alterzorder top,address:0x55efce183cb0'] // ipc.py:131
              scratchpads - dispatch resizewindowpixel exact 326 464,address:0x55efce183cb0 // ipc.py:131
              scratchpads - clients // ipc.py:89
              scratchpads - dispatch movewindowpixel exact 806 531,address:0x55efce183cb0 // ipc.py:131
              scratchpads - dispatch focuswindow address:0x55efce183cb0 // ipc.py:131
              scratchpads - run_toggle('qq',) // command.py:189
              scratchpads - visibility_check: ('1', 'eDP-1') == ('1', 'eDP-1') // __init__.py:423
              scratchpads - qq is visible = True (but True) // __init__.py:443
              scratchpads - Hiding qq // __init__.py:683
              scratchpads - dispatch movewindowpixel 0 0,address:0x55efce183cb0 // ipc.py:131
              scratchpads - dispatch movetoworkspacesilent special:scratch_qq,address:0x55efce183cb0 // ipc.py:131
              scratchpads - run_toggle('qq',) // command.py:189
              scratchpads - visibility_check: ('1', 'eDP-1') == ('1', 'eDP-1') // __init__.py:423
              scratchpads - qq is visible = False (but False) // __init__.py:443
              scratchpads - Showing qq // __init__.py:533
              scratchpads - clients // ipc.py:89
              scratchpads - monitors // ipc.py:89
              scratchpads - dispatch ['moveworkspacetomonitor special:scratch_qq eDP-1', 'movetoworkspacesilent 1,address:0x55efce183cb0', 'alterzorder top,address:0x55efce183cb0'] // ipc.py:131
              scratchpads - dispatch resizewindowpixel exact 326 464,address:0x55efce183cb0 // ipc.py:131
              scratchpads - clients // ipc.py:89
              scratchpads - dispatch movewindowpixel exact 806 531,address:0x55efce183cb0 // ipc.py:131
              scratchpads - dispatch focuswindow address:0x55efce183cb0 // ipc.py:131
              scratchpads - run_toggle('qq',) // command.py:189
              scratchpads - visibility_check: ('1', 'eDP-1') == ('1', 'eDP-1') // __init__.py:423
              scratchpads - qq is visible = True (but True) // __init__.py:443
              scratchpads - Hiding qq // __init__.py:683
              scratchpads - dispatch movewindowpixel 0 0,address:0x55efce183cb0 // ipc.py:131
              scratchpads - dispatch movetoworkspacesilent special:scratch_qq,address:0x55efce183cb0 // ipc.py:131
                 pyprland - event_activewindowv2('55efce169a90',) // command.py:189
                 pyprland - active_window = 0x55efce169a90 // pyprland.py:44
              scratchpads - event_activewindowv2('55efce169a90',) // command.py:189
            layout_center - event_activewindowv2('55efce169a90',) // command.py:189
                 pyprland - event_activewindowv2('55efce169a90',) // command.py:189
                 pyprland - active_window = 0x55efce169a90 // pyprland.py:44
              scratchpads - event_activewindowv2('55efce169a90',) // command.py:189
            layout_center - event_activewindowv2('55efce169a90',) // command.py:189
fdev31 commented 6 months ago

Ok, I guess this is because the process is still running, so pyprland isn't restarting it. There is at the moment no additional check regarding the client window, it's assumed to not have changed...

If I understand it well, in your scenario there is always at least one window we can "link", do you confirm ?

I'll try to implement something, would be great if you can test when I push it on git...

Asthestarsfalll commented 6 months ago

If I understand it well, in your scenario there is always at least one window we can "link", do you confirm ?

yes!

I'll try to implement something, would be great if you can test when I push it on git...

You can push code to a test branch, and I will test it once I have time!

fdev31 commented 6 months ago

Ok, I believe we didn't get something in your scenario, after a code review + writing a test app to simulate your case, it should raise an error + show a notification when you try to show again.

It means the original window is still available... so I'll ask you a more detailed info, please provide pypr's log for the full session (after a fresh start of the window manager + pypr) + the result of hyprctl -j clients | jq '.[] | select(.class=="QQ")' and execute the following:

fdev31 commented 6 months ago

One more thing, having a binding such as: bind = $mainMod, S, togglefloating,

when you "show" the client, but it's not visible, can you try triggering this to see if it appears in the current workspace as a tiled window ?

fdev31 commented 6 months ago

I believe it can be a problem with the coordinates... but I need a full picture of a single scenario to better understand what's going on :)

fdev31 commented 6 months ago

Are you using preserve_aspect ? Can you provide the config for the QQ scratchpad ? :)

Asthestarsfalll commented 6 months ago

One more thing, having a binding such as: bind = $mainMod, S, togglefloating,

when you "show" the client, but it's not visible, can you try triggering this to see if it appears in the current workspace as a tiled window ?

The login window cannot be set to tiled

Asthestarsfalll commented 6 months ago

At the lower version (I upgraded both pyprland and QQ, so I don't know what's the reason), pyprland will directly give a error message about cannot find the window.

I reproduce this ! This will happen when I enable multi monitors. 240425_11h33m24s_screenshot THe traceback is:

ERROR:scratch:client_info of 5588bc402d30 must be a dict: None
This could be a bug in Pyprland, if you think so, report on https://github.com/fdev31/pyprland/issues
Not a dict: None
Traceback (most recent call last):
  File "/usr/lib/python3.11/site-packages/pyprland/command.py", line 195, in _run_plugin_handler
    await getattr(plugin, full_name)(*params)
  File "/usr/lib/python3.11/site-packages/pyprland/plugins/scratchpads/__init__.py", line 450, in run_toggle
    await asyncio.gather(*(asyncio.create_task(t()) for t in tasks))
  File "/usr/lib/python3.11/site-packages/pyprland/plugins/scratchpads/__init__.py", line 563, in run_show
    await self._show_transition(scratch, monitor, was_alive)
  File "/usr/lib/python3.11/site-packages/pyprland/plugins/scratchpads/__init__.py", line 606, in _show_transition
    await scratch.updateClientInfo()  # update position, size & workspace information (workspace properties have been created)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/pyprland/plugins/scratchpads/objects.py", line 118, in updateClientInfo
    raise AssertionError(f"Not a dict: {client_info}")
AssertionError: Not a dict: None
Asthestarsfalll commented 6 months ago

Are you using preserve_aspect ? Can you provide the config for the QQ scratchpad ? :)

[scratchpads.qq]
animation = "fromLeft"
command = "linuxqq"
position = "42% 30%"
class = "QQ"
size = "17% 43%"
margin = 0
lazy = true
Asthestarsfalll commented 6 months ago

Ok, I believe we didn't get something in your scenario, after a code review + writing a test app to simulate your case, it should raise an error + show a notification when you try to show again.

It means the original window is still available... so I'll ask you a more detailed info, please provide pypr's log for the full session (after a fresh start of the window manager + pypr) + the result of hyprctl -j clients | jq '.[] | select(.class=="QQ")' and execute the following:

  • toggle QQ scratchpad => should show
  • login => should disappear
  • toggle QQ scratchpad => should "break"
  • repeat last toggle operation, just in case we capture something interesting...

error.log normal.log the result of hyprctl -j clients | jq '.[] | select(.class=="QQ"): Before login: 240425_11h57m34s_screenshot after login: 240425_11h57m49s_screenshot Then it will not change when I pypr toggle qq. And I also reproduce the error in single monitor. I think this because there is a little gap between the login window destory and new window create. So if I toggle pypr during the period, It will raise error.

fdev31 commented 6 months ago

What I'm very surprised with, is that if there was no window with the "correct" address, it should ALSO throw this error... there is current absolutely no code (that I know!) which handles that kind of unexpected case, and I tested it yesterday on an app.

It makes me doubt this will be detected on your system... I need to know if the client address of the first window is still available later (which is what would make sense when you don't get an error). To achieve this, just note the address of the login / first client window:

image

Then, later on, type something like:

hyprctl -j clients | jq '.[] | select(.address=="0xXXXX")'

Replacing "0xXXX..." with the address* of the initial client window....

Asthestarsfalll commented 6 months ago

@fdev31, Hi, I don't know is it still avilable, It will quit when I stop the whole process of QQ.

240426_09h23m31s_screenshot

fdev31 commented 6 months ago

Ok, I think it starts to make sense, QQ main window stays, but get a pid = -1 and no title or class anymore... hence when the scratchpad thinks the window is still fine. Do you confirm this understanding ?

It almost sounds like a bug in QQ.

I can try to find a solution for that too but it looks very "qq" specific...

Asthestarsfalll commented 6 months ago

Ok, I think it starts to make sense, QQ main window stays, but get a pid = -1 and no title or class anymore... hence when the scratchpad thinks the window is still fine. Do you confirm this understanding ?

Yes

I can try to find a solution for that too but it looks very "qq" specific...

So for more general, can we implement a method to catch an existing window? So that I just need to login, then add it to scratch pad.

fdev31 commented 6 months ago

Yes, I could automatically attach new windows to the same scratch, the new windows seemed to preserve the pid. The only challenge I see here is detecting the "dead" client window, having a trigger for it which isn't using additional API calls to not slow down anything.

having a pid==-1 seems to be a decent test, but this may be detected with a delay... I'll do some experiments over the weekend when I have time.

Asthestarsfalll commented 6 months ago

Oh, I mean just manully toggle pypr to add a window to scratchpad like the title of this issue. Automatically detecting the dead window seems not cost-effective for its narrow scenario.

fdev31 commented 6 months ago

Oh so you can find your way with the current attach command, if that works "okay" it's good :) I may still try to push some code with tricks if they are not impacting the overall complexity too much... in short if it fits well in the code I'll make experiments that you can test to see if it improves the user experience... I'll update this ticket accordingly to let you know if I abandon the idea or if I come with something in the future...

Asthestarsfalll commented 6 months ago

Thank you for your efforts!

fdev31 commented 6 months ago

I pushed some experimental option which sounds generic and may address your scenario, try setting multi = true on this scratchpad using the latest git version, hoping it helps!

Asthestarsfalll commented 6 months ago

Hi, It almost works, but it seems there should be a full address:

https://github.com/hyprland-community/pyprland/blob/fa3b7b39885e5fd44449bacbdab29c4552e05900/pyprland/plugins/scratchpads/__init__.py#L584

After I change this, the QQ window can be dispatched between workspace: 240427_16h22m55s_screenshot

But the position of window are not handled correctly.

But it works well for my typora, which will pop a menu and not handle properly in the pervious version:

240427_16h25m55s_screenshot

fdev31 commented 6 months ago

I don't know why the position isn't changed in case of QQ... I guess this is because the original process & window are still here so every other are only "attached", but they still should slide in some way...

Thank you for reporting the address issue, I just pushed the fix.

We can try to improve it even further but it may lead to too much specific code & complexity... are you satisfied with the current version or do you think there is still something that can be done to try to improve the QQ scenarios?

We can try starting again generating full logs & dumps with the current version on QQ and try to figure what is still wrong with it. The position should be at least a bit changed when you toggle it.

Asthestarsfalll commented 6 months ago

We can try to improve it even further but it may lead to too much specific code & complexity... are you satisfied with the current version or do you think there is still something that can be done to try to improve the QQ scenarios?

I think simple is better. So I just write a script to do this:

addr=$(hyprctl -j clients | jq '.[] | select(.class=="'$1'") | .["address"]' | sed 's/"//g')
hyprctl dispatch focuswindow address:"$addr"
hyprctl dispatch centerwindow

Once I move it into the workspace, pypr will work well with QQ. But it seems there's no animation of multi window. And I think multi should be disabled by default for its search costs

fdev31 commented 6 months ago

You might be interested in https://github.com/hyprland-community/pyprland/wiki/fetch_client_menu - I think it can also fit the requirement... if not, I'm happy to improve it by supporting the "centerwindow" call for instance... (I could use this if I detect a floating window).

About the search cost, I managed to not do additional IPC call for it if I recall, so it should not be noticeable... My main worry is that maybe for some scenarios this is not a good thing to have, but I imagine it's more useful than it's annoying...

You got the Typora app working out of the box, which is good, not everybody will dig the advanced options to fix such app...

Asthestarsfalll commented 6 months ago

You might be interested in https://github.com/hyprland-community/pyprland/wiki/fetch_client_menu - I think it can also fit the requirement... if not, I'm happy to improve it by supporting the "centerwindow" call for instance... (I could use this if I detect a floating window).

It seems fetch_client_menu do not works well with scrtachpads. The chosen client will disappear from the menu, and no window will show. But I am busy with my ungraduate design and paper. So I will dig this functionality after them done.

About the search cost, I managed to not do additional IPC call for it if I recall, so it should not be noticeable... My main worry is that maybe for some scenarios this is not a good thing to have, but I imagine it's more useful than it's annoying...

You got the Typora app working out of the box, which is good, not everybody will dig the advanced options to fix such app...

I agree with you. It's a great exprience to use out of the box.

Asthestarsfalll commented 6 months ago

Hi, I just update hyprland to 0.39.0. allow_special_worspace works as expect. And need to set smart_focus to False, I think this should be written in wiki.

And the animation of special workspace is abnormal. It looks like the special workspace are closed and reopened. This maybe a bug of Hyprland

fdev31 commented 6 months ago

What do you experience wrong with smart focus when you enable special workspaces?

EDIT: I also experienced the flickering, I don't think it's in pyprland, I guess this is because this feature is a bit fragile since scratchpads are heavily relying on special workspaces and from experience they are a bit less stable than standard workspaces.

Le dim. 28 avr. 2024 à 19:20, Asthestarsfalll @.***> a écrit :

Hi, I just update hyprland to 0.39.0. allow_special_worspace works as expect. And need to set smart_focus to False, I think this should be written in wiki.

And the animation of special workspace is abnormal. It looks like the special workspace are closed and reopened. This maybe a bug of Hyprland

— Reply to this email directly, view it on GitHub https://github.com/hyprland-community/pyprland/issues/87#issuecomment-2081561139, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAB2IHWR5QCRY6HQ6BZSS2DY7UVT7AVCNFSM6AAAAABGU2KK6SVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAOBRGU3DCMJTHE . You are receiving this because you were mentioned.Message ID: @.***>

Asthestarsfalll commented 6 months ago

What do you experience wrong with smart focus when you enable special workspaces?

Just cannot keep the special workspace open. I don't know what will smart_focus do.

EDIT: I also experienced the flickering, I don't think it's in pyprland, I guess this is because this feature is a bit fragile since scratchpads are heavily relying on special workspaces and from experience they are a bit less stable than standard workspaces.

Yes, the focuswindow still does not work well with sepcial workspace.

fdev31 commented 6 months ago

Just cannot keep the special workspace open. I don't know what will smart_focus do.

You mean, when you hide a scratchpad when a special workspace was open, then it hides the special workspace at the same time ?

If so, this is something I can try to improve, for instance disabling smart focus in that case...

Asthestarsfalll commented 6 months ago

You mean, when you hide a scratchpad when a special workspace was open, then it hides the special workspace at the same time ?

yes. And I want to know what is the behavior of smart focus. There'is no document about this.

fdev31 commented 6 months ago

So, this is because it's a bit complicated and subject to changes/refinements... the explanation is rather long to fit in the wiki under this option, and I consider this a topic to be worked on, not optimal at the moment with some corner cases not well handled as you noticed.

First, it's useful only for multi-monitor scenarios, so I could change the defaults according to that.

The problem with monitors is the following:

So the scratchpads plugin is constantly keeping track of the window which was focused before the scratchpad was first displayed or refocused. When you hide the scratchpad, after a tiny delay, it will force the focus back on this tracked client window.

There are few cases where this is auto-disabled, but it's quite heuristic.

I wanted to experiment with different solutions, but in my tests using the focused monitor or workspace (instead of focused client) didn't work well while it sounded optimal.

Hope that clarifies!

I'm really open for suggestions & experiments here, I was even considering opening for multiple heuristics (say changing smart_focus from a boolean to a string with the name of the "smart" heuristic ;P)

fdev31 commented 6 months ago

I don't see the issue with special workspaces, would you mind sharing the relevant configuration items + a video of the problem so I better understand how it's produced?

fdev31 commented 6 months ago

Please try the last push, keeping the smart focus on :)

EDIT: it wasn't good enough, I reverted, we need to focus specific client else the "recovered" focus is random :(

fdev31 commented 6 months ago

I tested and if there is a focused client window in the special workspace it works as expected here, not for you ?

Asthestarsfalll commented 6 months ago

I don't see the issue with special workspaces, would you mind sharing the relevant configuration items + a video of the problem so I better understand how it's produced?

Sorry, I have no proper app to record my screen due to the dependencies of hyprland-git. xdg-desktop-portal-hyprland-git cannot work.

In fact, I use single monitor at most time. When I enable smart_focus, then I show scratchpads in a special workspace, nothing abnormal. But when hiding the scratchpads, it will "close" the special workspace, and lost the focus.

I believe this is a bug of Hyprland. If you are using verison of 0.39.0, you may reproduce it by the following procedure:

  1. Open a special workspace and open a app, get the address of it.
  2. Open a terminal in this sepcial workspace, use hyprctl dispatch focuswindow address:.......

Then you will see the special workspace "close", and lost your focus. But in fact it just hide, if you toggle this speical workspace again, it will truly close.

Asthestarsfalll commented 6 months ago

It may be fixed by this commit. But I cannot verify it util the new version publish.

fdev31 commented 6 months ago

Then you will see the special workspace "close", and lost your focus. But in fact it just hide, if you toggle this speical workspace again, it will truly close.

Looks like an issue with your Hyprland version, I can't reproduce this problem... using v0.39.1

fdev31 commented 6 months ago

Ok, I worked around some bug and hit the same behavior as you over special workspaces and can now explain:

.1 Hyprland sends an invalid address when showing a scratchpad, which was masking the other bug .2 Hyprland blinks when we show the scratchpad for no obvious reason, as a side effect, it will send an event telling that some window BELOW the special workspace is focused, which is messing up the focus tracking

As a workaround, once a scratchpad is displayed over a special workspace, focus at least once/shortly another window.

fdev31 commented 6 months ago

The latest git version may improve things a bit.

Asthestarsfalll commented 6 months ago

The latest git version may improve things a bit.

I use the latest commit, but it can not detect pypr daemon. 240501_17h48m32s_screenshot 240501_17h49m41s_screenshot

fdev31 commented 6 months ago

And it's running? If yes try killing and restarting

fdev31 commented 6 months ago

Did you figure your problem? Check #98, it might be of interest for you.

Asthestarsfalll commented 6 months ago

Now smart focus can work well with scratchpads in speical workspace!