WayfireWM / wayfire

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

layer-shell: setting keyboard-interactivity to ON_DEMAND always grabs the focus #2450

Open dkondor opened 2 months ago

dkondor commented 2 months ago

Describe the bug Per here, this is not intended.

To Reproduce See Python code below. It creates a layer-shell view with buttons to toggle keyboard-interactivity + a separate button below the text entry ("Release keyboard") which tries to "lose" keyboard interactivity by (1) switching to NONE; (2) waiting 500 ms (to make what's happening more visible); and (3) switching back to ON_DEMAND

In any case when keyboard-interactivity is switched to ON_DEMAND, the layer-shell surface grabs keyboard focus.

Expected behavior When clicking on "Release keyboard", the layer-shell surface should stay unfocused.

(Note: I'm unsure what should happen when clicking the "On demand" button; on the one hand, a mouse click should lead to keyboard focus; on the other hand, even in this case, switching to ON_DEMAND happens after the mouse click finished. In any case, this is not relevant to my use case and I'm fine with any behavior)

Use case Primarily, I use ON_DEMAND to be able to register modifier + click on a layer-shell surface (while using standard GTK API). This requires ON_DEMAND to be the default state. I do want to "lose" the keyboard focus in specific cases: (1) when a menu is closed (e.g. by ESC or wf-shell::toggle_menu); (2) when triggering scale via IPC (e.g. see here).

Wayfire version git 44e1fa9c62e1f8c9f35cedbf3e86c6a0247e0b79

Test code

#!/usr/bin/env python3

import os
import sys
import gi
import wayfire_socket as ws

gi.require_version('Gtk', '3.0')
gi.require_version('GtkLayerShell', '0.1')

from gi.repository import Gtk, GtkLayerShell, GLib

class KBWindow(Gtk.Window):
    def __init__(self):
        self.timeout_id = 0
        Gtk.Window.__init__(self)
        GtkLayerShell.init_for_window(self)
        GtkLayerShell.set_keyboard_mode(self, GtkLayerShell.KeyboardMode.ON_DEMAND)
        lbl = Gtk.Label.new('Set keyboard mode')
        btn1 = Gtk.Button.new_with_label('None')
        btn1.connect("clicked", self.kb_none)
        btn2 = Gtk.Button.new_with_label('On demand')
        btn2.connect("clicked", self.kb_ondemand)
        btn3 = Gtk.Button.new_with_label('Exclusive')
        btn3.connect("clicked", self.kb_excl)
        btn4 = Gtk.Button.new_with_label('Quit')
        btn4.connect("clicked", Gtk.main_quit)
        btn5 = Gtk.Button.new_with_label('Release keyboard')
        btn5.connect("clicked", self.release_kb)
        entry = Gtk.Entry()
        box = Gtk.Box(orientation = Gtk.Orientation.VERTICAL)
        box.add(lbl)
        box.add(btn1)
        box.add(btn2)
        box.add(btn3)
        box.add(entry)
        box.add(btn5)
        box.add(btn4)
        self.add(box)
        self.connect('destroy', Gtk.main_quit)
        self.show_all()
        # GtkLayerShell.set_keyboard_mode(self, GtkLayerShell.KeyboardMode.ON_DEMAND)

    def kb_none(self, btn):
        GtkLayerShell.set_keyboard_mode(self, GtkLayerShell.KeyboardMode.NONE)

    def kb_ondemand(self, btn):
        GtkLayerShell.set_keyboard_mode(self, GtkLayerShell.KeyboardMode.ON_DEMAND)

    def kb_excl(self, btn):
        GtkLayerShell.set_keyboard_mode(self, GtkLayerShell.KeyboardMode.EXCLUSIVE)

    def reset_keyboard(self):
        GtkLayerShell.set_keyboard_mode(self, GtkLayerShell.KeyboardMode.ON_DEMAND)
        self.timeout_id = 0
        return False

    def release_kb(self, btn):
        # we set keyboard mode to NONE first to lose keyboard focus
        GtkLayerShell.set_keyboard_mode(self, GtkLayerShell.KeyboardMode.NONE)
        if self.timeout_id == 0:
            # we wait 500 ms before setting it back to ON_DEMAND to see if focus really changes
            self.timeout_id = GLib.timeout_add(500, self.reset_keyboard)

def main(args):
    win1 = KBWindow()
    Gtk.main()
    return 0

if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))
dkondor commented 2 months ago

The following fixes the issue for me:

diff --git a/src/view/layer-shell/layer-shell.cpp b/src/view/layer-shell/layer-shell.cpp
index a85ca8a3..c243868e 100644
--- a/src/view/layer-shell/layer-shell.cpp
+++ b/src/view/layer-shell/layer-shell.cpp
@@ -565,10 +565,10 @@ void wayfire_layer_shell_view::commit()

         if (prev_state.keyboard_interactive != state->keyboard_interactive)
         {
-            if ((state->keyboard_interactive >= 1) && (state->layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP))
+            if ((state->keyboard_interactive == 1) && (state->layer >= ZWLR_LAYER_SHELL_V1_LAYER_TOP))
             {
                 wf::get_core().seat->focus_view(self());
-            } else
+            } else if (state->keyboard_interactive == 0)
             {
                 wf::get_core().seat->refocus();
             }

So far I've only tested with my Python example and gtk-layer-demo. Since it avoids calling refocus in a lot of cases, it can have further, unintended consequences. One thing to note is that this results in clicking the "On demand" button also not grabbing focus which can be counter-intuitive in some cases.