wayfire icon indicating copy to clipboard operation
wayfire copied to clipboard

layer-shell: strange behavior if keyboard interactivity is set before showing the view

Open dkondor opened this issue 1 year ago • 3 comments

Describe the bug If keyboard interactivity is set before initially showing (mapping) a layer-shell view, it takes effect, but is not fully updated the next time.

To Reproduce Steps to reproduce the behavior (Python example below):

  1. Create a layer-shell view without showing
  2. set keyboard interactivity to ON_DEMAND
  3. show the view (it will have keyboard focus which is expected)
  4. set keyboard interactivity to NONE
  5. the layer shell view retains keyboard focus (and takes keyboard input) until clicked outside or until keyboard interactivity is set again to ON_DEMAND -> NONE (i.e. it works the second time)

Expected behavior Keyboard focus is lost immediately.

If steps #2 and #3 are swapped, it works as expected.

Wayfire version git: 44e1fa9c62e1f8c9f35cedbf3e86c6a0247e0b79

Simple example Use the buttons to switch keyboard interactivity settings:

#!/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


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('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)
		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(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 main(args):
	win1 = KBWindow()
	Gtk.main()
	return 0

if __name__ == '__main__':
	sys.exit(main(sys.argv[1:]))

dkondor avatar Aug 24 '24 18:08 dkondor

Nice catch. After setting the keyboard mode to NONE, it only works as intended after focusing on another view, excluding the dialog. The issue is that the input still retains keyboard focus, and the update only occurs after a keyboard focus change.

killown avatar Aug 24 '24 18:08 killown

it's up to gtk to disable the keyboard input on the fly

#!/usr/bin/env python3

import os
import sys
import gi
#pip install wayfire
from wayfire import WayfireSocket as ws

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

from gi.repository import Gtk, GtkLayerShell


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(label='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)

        self.entry = Gtk.Entry()
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        box.add(lbl)
        box.add(btn1)
        box.add(btn2)
        box.add(btn3)
        box.add(self.entry)
        box.add(btn4)

        self.add(box)
        self.connect('destroy', Gtk.main_quit)
        self.show_all()

    def kb_none(self, btn):
        GtkLayerShell.set_keyboard_mode(self, GtkLayerShell.KeyboardMode.NONE)
        # Make the input field non-editable
        self.entry.set_sensitive(False)

    def kb_ondemand(self, btn):
        GtkLayerShell.set_keyboard_mode(self, GtkLayerShell.KeyboardMode.ON_DEMAND)
        # Make the input field editable again
        self.entry.set_sensitive(True)

    def kb_excl(self, btn):
        GtkLayerShell.set_keyboard_mode(self, GtkLayerShell.KeyboardMode.EXCLUSIVE)
        # Make the input field editable again
        self.entry.set_sensitive(True)


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

if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))

killown avatar Aug 24 '24 18:08 killown

The following seems to fix this issue for me (although I'm unsure if it would have additional side-effects, related to the other elements in prev_state, so I'm not creating a PR for it):

diff --git a/src/view/layer-shell/layer-shell.cpp b/src/view/layer-shell/layer-shell.cpp
index a85ca8a3..1c9f45cc 100644
--- a/src/view/layer-shell/layer-shell.cpp
+++ b/src/view/layer-shell/layer-shell.cpp
@@ -440,6 +440,9 @@ std::shared_ptr<wayfire_layer_shell_view> wayfire_layer_shell_view::create(wlr_l
 
     lsurface->output = self->get_output()->handle;
 
+    // Initial state
+    self->prev_state = lsurface->current;
+
     // Initial configure
     self->on_commit_unmapped.emit(NULL);

dkondor avatar Aug 25 '24 10:08 dkondor