WayfireWM / wayfire

A modular and extensible wayland compositor
https://wayfire.org/
MIT License
2.36k stars 174 forks source link

GNOME-like hot corner variant #2160

Open slashdevslashurandom opened 6 months ago

slashdevslashurandom commented 6 months ago

The current implementation of hot corners in Wayfire checks if the mouse cursor has been located in a certain screen area for a period of time. This is a simple, but rather ineffective way to handle hot corners.

If the delay is set as too small, it is easily possible to accidentally cause a hot corner interaction, and if it's too large, then actually activating it becomes a slower process.

GNOME seems to have solved that issue. Instead of just looking at the cursor's position and timing, it (from what I can see) also checks the movement of the mouse as it enters the corner.

If the cursor hits the corner and the mouse moving quickly, then the hot corner action (in this case the Activities menu being opened or closed) is issued immediately. This removes the need to wait for any duration when activation is intended.

If the cursor hits the corner and the mouse moves slowly, then it waits until enough "out-of-bounds movement" has been accumulated before activating it. This makes accidental activations harder.

Implementing such "velocity-based" hot corners as an alternative to already-existing ones would make using them more comfortable, especially in cases where said hot corner might also be close to window title bars that have buttons (like the Firefox View button in Firefox or many other GNOME applications).

ammen99 commented 6 months ago

I personally am not really using hot corners so I am not sure I can properly write and tune the code, but PRs welcome.

The default implementation is rather self-contained so I think it should be easy to make the behavior better if you know a bit of C++: https://github.com/WayfireWM/wayfire/blob/master/src/core/seat/hotspot-manager.cpp

slashdevslashurandom commented 6 months ago

I decided to take a look at the source code and see what I could do to make it work. Here are some notes so far:

ammen99 commented 6 months ago
  • The hotspot pointer event (which would need to be modified) currently only takes the cursor position into account. I am currently trying to make it also look at the pointer motion event to get relative motion data -- I hope this data is not "clamped" to the screen coordinates and it will be possible to find how much, for example, the mouse has moved left even if the cursor is at the leftmost possible position.

The data is available but currently not passed to the function. See for example https://github.com/WayfireWM/wayfire/blob/master/src/core/seat/hotspot-manager.cpp#L123 : the (auto) parameter can be replaced with the actual event type, wf::post_input_event_signal<wlr_pointer_motion_event> *ev and then in ev->event->dx/dy you should be able to see the raw dx/dy values.

  • I do not know how to properly add configuration options to Wayfire. I'll try to document what I added and how to make my additions optional (though they shouldn't interfere with the regular usage).

To add new configuration options, you need to add the option to the corresponding XML file in metadata (hotspot-related stuff probably should go to input.xml or core.xml), then you can access the option via wf::option_wrapper_t. There are many examples in the plugins, for example https://github.com/WayfireWM/wayfire/blob/master/plugins/cube/cube.cpp#L204

slashdevslashurandom commented 6 months ago

Figured out the event handling part, now I have a working version.

Here's the patch file for the changes I made so far.

The process_pointer_motion function is copied from the process_input_motion one, except it now also receives delta motion values. When the cursor is within the hotspot area and delta motion's both X and Y coordinates are pointed towards the corner, "pressure" is calculated. Whenever the cursor leaves the area or moves in a direction that doesn't get it closer to the corner, the hotspot is reset (which now also involves resetting pressure to zero). When "pressure" reaches a value of 128.0 (derived subjectively by trying to match GNOME's behavior), the callback is activated (and the timer is reset, to prevent double activations), after which the "pressure" is set to negative infinity (also to prevent double activations, since it gets reset to 0 the moment the cursor moves away or leaves the hotspot).

I'll try adding configuration options and extra customizations later, but as far as I see it, the defaults should work fine for most (if not all) users who have hotspots activated.

slashdevslashurandom commented 6 months ago

Adding configuration options seems more complicated than I thought. Your suggestion of using input.xml would work for adding a global option, but hotspots are configured individually -- a user has to write a string value which tells in which corner the hotspot is, how large is it and what timeout it has.

A full configuration for something like that would require adding options for:

Additionally, the following changes might be desired:

One might also consider defining pressure-based hotspots as separate from the timer-based ones entirely, but then it might lead to conflicts if both are defined in the same part of the screen.

ammen99 commented 6 months ago

Oh, I see what you mean. The config file is parsed by wf-config, which defines the options that hotspots have and how they are parsed. If you want to adapt the hotspots themselves, you will need to patch wf-config as well. Here are some starting points:

https://github.com/WayfireWM/wf-config/blob/af1bddc9d7191b9902edcb4c74572eac65577806/include/wayfire/config/types.hpp#L360

https://github.com/WayfireWM/wf-config/blob/master/src/types.cpp#L936

lukefromdc commented 5 months ago

The "expo" plugin can do a top left hotspot now. Here's my expo configuration, it works after a slight delay each time the hotspot is triggered:


[expo]
background = \#0A7576FF
duration = 300
inactive_brightness = 0.700000
keyboard_interaction = true
offset = 10
select_workspace_1 = KEY_1
select_workspace_2 = KEY_2
select_workspace_3 = KEY_3
select_workspace_4 = KEY_4
select_workspace_5 = KEY_5
select_workspace_6 = KEY_6
select_workspace_7 = KEY_7
select_workspace_8 = KEY_8
select_workspace_9 = KEY_9
toggle = <super>  | hotspot left-top 100x10 1000
transition_length = 200
killown commented 5 months ago

The "expo" plugin can do a top left hotspot now. Here's my expo configuration, it works after a slight delay each time the hotspot is triggered:


[expo]
background = \#0A7576FF
duration = 300
inactive_brightness = 0.700000
keyboard_interaction = true
offset = 10
select_workspace_1 = KEY_1
select_workspace_2 = KEY_2
select_workspace_3 = KEY_3
select_workspace_4 = KEY_4
select_workspace_5 = KEY_5
select_workspace_6 = KEY_6
select_workspace_7 = KEY_7
select_workspace_8 = KEY_8
select_workspace_9 = KEY_9
toggle = <super>  | hotspot left-top 100x10 1000
transition_length = 200

Very nice! what if you have a multi-monitor setup, with the main monitor positioned on the right and another on the left? In this scenario, when you want to use the hot spot in the left-top corner in the monitor from the right, the mouse would keep moving to the left side. Wouldn't it be nice to create a mouse barrier to disallow movement to the left in the hotspot area?

lukefromdc commented 5 months ago

I would not know offhand how to code that but it's an interesting idea. Does GNOME or Cinnamon have that feature yet? Most immediate improvement here would be to get expo to respond right away, or to aboid accidental trigggering offer a configurable intentional delay. Delay is apparently the same using the key or the hot corner.

killown commented 5 months ago

You could monitor the mouse cursor position once it enters the hotspot area. For instance, if the cursor reaches, for example, x:1 y:1, you can reset the cursor position to x:1 y:1. Then, you can establish a height barrier, such as y:10. Whenever the cursor enters this area, you reset its position again. This is an idea that I haven't tested, and I'm uncertain about the implications if the cursor could still exit the defined area, among other potential issues.

ammen99 commented 5 months ago

If you guys are discussing how to implement resistance when at the edge of a monitor, this can be done similarly to how pointer-constraints are implemented (for example games use them, to make sure the pointer does not leave the game window) but you'd simply have to let the cursor move on after the resistance threshold is reached.

https://github.com/WayfireWM/wayfire/blob/4f65d16420aad0dae6f26018088cfb839e93a622/src/view/wlr-surface-pointer-interaction.hpp#L82-L118

The idea is conceptually very simple: pointer constraints intercept motion events before they are processed by core, and if a hot corner is currently active and there should be resistance, you can overwrite the motion deltas so that the pointer doesn't move.

killown commented 5 months ago

I would not know offhand how to code that but it's an interesting idea. Does GNOME or Cinnamon have that feature yet? Most immediate improvement here would be to get expo to respond right away, or to aboid accidental trigggering offer a configurable intentional delay. Delay is apparently the same using the key or the hot corner.

can you implement this with ammen99 example?

lukefromdc commented 5 months ago

Just testing it would require remaking my setup, which is normally either one monitor or my big monitor on the left as a de factor primary. No place on my desk for a secondary to the left, and I would have difficulty using a "left" monitor that is actually to the right. Thus not a setup I use. Also note that I don't have much of an understanding of pointer resistance at all. This should be considered beyond me barring a major project I don't have time for. You have however possibly given anyone else working on this a starting point, and often that's the first step.

My immediate priority is researching how https://github.com/marcof-nikogo/wf-external-decoration works so I can speed up getting marco themes to work for the mate-wayfire session.