Kivy Mouse click events when a desktop window is not in Focus

171 views
Skip to first unread message

justplaying0101

unread,
Oct 20, 2022, 7:09:26 AM10/20/22
to kivy-...@googlegroups.com
Hi, When the app window is not in focus the default procedure for activating a button in Kivy requires two clicks, one to focus the window and one to press the button.  I would like to be able to activate button and text input on_release events when the window is not in focus with a single click.  Other online solutions auto-focuses the window by binding to the Window classes on_cursor_enter event then calling the Window.raise_window method but this has drawback of occasionally bringing the window to the foreground when only hovering over the app which is undesirable.  I want the app only to come to the foreground if a mouse click occurs.  I have a feeling that binding to the Window's on_motion event and listening for touch events might work but am not sure how to do so.  Binding to Window.on_mouse_down and on_touch_down does not get passed to the root widget if the window is not in focus.  However, binding to Window.mouse_pos does get passed so I am able to change the cursor to hand instead of the arrow by hovering over a button when the window is not focused.  This is why I think on_motion might work. Can anyone provide a suggestion on how best to do this?  I'm writing this app for Windows 10 and Windows 7.


Sent from my Verizon, Samsung Galaxy smartphone

Elliot Garbus

unread,
Oct 20, 2022, 7:35:09 PM10/20/22
to kivy-...@googlegroups.com

I did a few experiments on Windows11, there does not seem to be a way to get a touch event for a window that is not in focus.  The touch that gets the Window in focus, does not fire the on_mouse_down or on_touch_down events.  I also experimented with the EventManagerBase class, same result.  The hover events work as expected, but the touch events are only sent when the Window is in focus.

 

I realized that we know that when the window comes into focus, we know that there was a touch on the window.  Below is the idea roughly implemented – but It demonstrates the idea can work.   I bind a callback to Window.focus.  We know that a touch event occurred at the current mouse position.  I use that info to walk with widget tree, and dispatch the proper events.  The example below will work for a button, this can be easily extended to work for other widgets including a TextInput.

 

The code below includes a number of failed experiments that I have left in – but commented out, in case you want to experiment with them…

I manually changed the state of the button so the user sees the familiar button press.

 

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.eventmanager import EventManagerBase

from functools import partial

kv =
"""
AnchorLayout:
    Button:
        size_hint: None, None
        size: 200, 48
        text: 'Touch Me'
        on_press: print('button pressed')
        on_release: print('button released')
"""


# class MotionButton(Button):
#     def on_kv_post(self, base_widget):
#         self.register_for_motion_event('hover')
#
#     def on_motion(self, etype, me):
#         print(f'{etype=} {me=}')
#
# class TouchHoverManager(EventManagerBase):
#     type_ids = ('touch', 'hover')
#
#     def start(self):
#         # Create additional resources, bind callbacks to self.window
#         pass
#
#     def dispatch(self, etype, me):
#         if me.type_id == 'touch':
#             print('Touch event in dispatch')
#             # Handle touch event
#         elif me.type_id == 'hover':
#             print('hover event in dispatch')
#             # Handle hover event
#
#     def stop(self):
#         # Release resources
#         pass

class WindowTouchApp(App):
   
def build(self):
       
# Window.bind(on_mouse_down=self._mouse_down)
        # Window.bind(on_touch_down=self._touch_down)
        # Window.bind(on_cursor_enter=self._cursor_enter)
        # Window.bind(on_motion=self._motion)
       
Window.bind(focus=self._focus)
       
# Window.register_event_manager(TouchHoverManager())
       
return Builder.load_string(kv)

   
# def _mouse_down(self, obj, x, y, button, modifiers):
    #     print('mouse down')
    #     print(f'{obj=} {x=} {y=} {button=} {modifiers=}')
    #
    # def _touch_down(self, obj, touch):
    #     print('touch down')
    #     print(f'{obj=} {touch=}')
    #
    # def _cursor_enter(self, obj):
    #     print('cursor enter')
    #     print(f'{Window.focus=}')
    #     # if Window.focus == False:
    #     #     Window.minimize()
    #     #     Window.restore()

   
def _focus(self, win, value):
       
print(f'focus changed: focus={value} ')
       
if value:
            pos = win.mouse_pos
           
print(f'{win.mouse_pos=}'# we know there was a touch event here
           
for w in self.root.walk_reverse(loopback=True):
               
print(w)
               
if w.collide_point(*pos) and isinstance(w, Button):
                   
print('Button Collision Detected')
                    w.dispatch(
'on_press')
                    w.state =
'down'
                   
p = partial(self._release_button, w)
                    Clock.schedule_once(p,
.3)
                   
return

    def
_release_button(self, button, _):
        button.state =
'normal'
       
button.dispatch('on_release')

   
# def _motion(self, obj, etype, motionevent):
    #     print(f'motion {etype=} {motionevent=}')

WindowTouchApp().run()

--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/63512c5f.9d0a0220.1d7c7.f5a4%40mx.google.com.

 

justplaying0101

unread,
Oct 20, 2022, 10:31:34 PM10/20/22
to kivy-...@googlegroups.com
Awesome!  I look forward to trying it out.  It might be a few days before I get a chance but I'll respond with my results.  Thank you Elliot! 



Sent from my Verizon, Samsung Galaxy smartphone


justplaying0101

unread,
Oct 24, 2022, 10:04:21 PM10/24/22
to kivy-...@googlegroups.com
Elliot, 

Your solution worked flawlessly.  Thank you for taking the time to answer my question.  Binding to Window.focus instead of trying to pass the touch/motion/mouse down events was keen to solving the problem.  I played around with your suggested code to use a list of buttons instead of looping through the widget tree to reduce processing load.  I may also take a look at using pyautogui to generate a click() upon a Window.focus event to eliminate checking for a widget collision but your solution works so well that it's probably not worth the hassle.  Here's an image of my test app which includes changing the icon while hovering over a button.  



Sent from my Verizon, Samsung Galaxy smartphone


-------- Original message --------
From: Elliot Garbus <elli...@cox.net>
Date: 10/20/22 6:35 PM (GMT-06:00)
20221024_204502.jpg
20221024_204625.jpg

Elliot Garbus

unread,
Oct 24, 2022, 10:46:50 PM10/24/22
to kivy-...@googlegroups.com

I’m glad to hear that worked out for you.  One thing that has occurred to me and may require some additional testing is if a popup window is open.  When the Popup is open the Window has 2 objects in the children list, the root widget and the popup.  I’m not sure how Widget.walk() treats the popup window.   You would want to make sure you look at the widgets in the Popup first (when the popup is open).  Using pyautogui to generate a click() is a clever idea – that would cover all of the cases. 

 

I suspect you could also create a MotionEvent with the pos data and have the Window Dispatch it…

https://kivy.org/doc/stable/guide/architecture.html?highlight=touch#widgets-and-event-dispatching

https://kivy.org/doc/stable/api-kivy.input.motionevent.html#module-kivy.input.motionevent

 

 

Good luck and enjoy!

justplaying0101

unread,
Oct 25, 2022, 9:32:06 AM10/25/22
to kivy-...@googlegroups.com
Elliot, 
Thanks for the tip about the pop up window.  Fortunately, this app will not have pop-ups so I won't have to tackle that scenario.  On the other hand, I did come across a scenario regarding the current solution which produces unwanted behavior.  As it stands,  if you use hot-keys to switch windows (depending on the mouse position) a button press can be unexpectedly activated.  This occurs both: 1) if the cursor is hovering on a button when Window.focus changes to True and 2) when the cursor was over a button prior to switching away to another window then switching back to the app regardless of the current cursor's position.  I believe the second scenario can be mitigated by setting win.mouse_pos to a non-button x, y position when Window.focus changes to False.  The first scenario seems more challenging.  I'm unsure how to mitigate it currently.  Perhaps, I can bind to Window.on_keyboard and don't do anything if Ctrl + Tab is pressed.  I believe the scan code would be 148 according to https://www.lookuptabkes.com/coding/keyboard-scan-codes.  I may not to use the Ctrl modifier since the scan code number is different than the Tab button scan code which is 15.  There's an example in kivy_install.modules.Joycursor.py that I'm looking at now. 

justplaying0101

unread,
Oct 25, 2022, 10:01:21 AM10/25/22
to kivy-...@googlegroups.com
Oops.  Alt+tab is used for window switching, not CTRL+TAB as mentioned in my previous message. Scan code = 165.

justplaying0101

unread,
Oct 25, 2022, 12:37:18 PM10/25/22
to kivy-...@googlegroups.com
By reviewing window.__init__.py, I found that the key codes for kivy are different than the link I provided.  Also, using print(key,, scancode, *args) told me that Tab's scancode = 9 and using modifiers = args[-1] can be used to check if modifiers == ['alt'] returns True.  However, the on_keyboard event does not get passed if the window is not in focus so I'm not able to check which keys were pressed prior to switching back. 

justplaying0101

unread,
Oct 25, 2022, 3:34:58 PM10/25/22
to kivy-...@googlegroups.com
Elliot, I'm pivoting to a different approach.  Would you happen to know if kivy-deps.sdl2 0.5.0 corresponds with SDL 2.0.5? 

the following should enable mouse click events to be sent when clicking to focus a window. This feature was made available in SDL 2.0.5.

My app (Kivy v2.0.0) currently uses KIVY-DEPS.SDL2 0.3.1 so I'd have to set up a new environment in an offline PC which I haven't done in awhile. 

The following code should enable the feature if you don't mind testing it. 

import os
os.environ["SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH"] = "1"

justplaying0101

unread,
Oct 25, 2022, 3:50:30 PM10/25/22
to kivy-...@googlegroups.com
I forgot to mention that it should be placed at the top of the app. 

Elliot Garbus

unread,
Oct 25, 2022, 8:03:15 PM10/25/22
to kivy-...@googlegroups.com

Very interesting as I read the first of these recent messages, I thought there is no way to fix this – but perhaps hacking the underlying windowing system.  I will be amazing if you can get the desired behavior from an environment variable!

 

I’ll give it a try.

 

“Would you happen to know if kivy-deps.sdl2 0.5.0 corresponds with SDL 2.0.5?” I don’t know.  Some of the kivy devs are in the kivy discord.  They might know.

Elliot Garbus

unread,
Oct 25, 2022, 8:31:30 PM10/25/22
to kivy-...@googlegroups.com

I did a quick test, setting the environment variable does not work. Then I found this: https://wiki.libsdl.org/CategoryHints

 

“The convention for naming hints is SDL_HINT_X, where "SDL_X" is the environment variable that can be used to override the default.”

I changed the environment variable name and…

 

It WORKS!!!

import os
os.environ[
"SDL_MOUSE_FOCUS_CLICKTHROUGH"] = '1'

justplaying0101

unread,
Oct 25, 2022, 9:07:11 PM10/25/22
to kivy-...@googlegroups.com
Yes!!!  You rock Elliot!  

I always felt like this feature shouldn't have to be so damn difficult to implement as sophisticated as Kivy is. Glad to see it's not if you know where to look but dang that took serious digging.  I feel like it's a feature that many desktop apps would want to implement so Kivy should add an easy to find example to their docs/api, if there isn't one already. 

I can't wait to try it out tomorrow. Elliot, which versions of kivy and kivy-deps.sdl2 did you use?  It should print the versions out when you first start up the app (red text, I think).  

Elliot Garbus

unread,
Oct 25, 2022, 9:13:51 PM10/25/22
to kivy-...@googlegroups.com

Here you go:

 

[INFO   ] [deps        ] Successfully imported "kivy_deps.gstreamer" 0.3.3

[INFO   ] [deps        ] Successfully imported "kivy_deps.angle" 0.3.2

[INFO   ] [deps        ] Successfully imported "kivy_deps.glew" 0.3.1

[INFO   ] [deps        ] Successfully imported "kivy_deps.sdl2" 0.4.5

[INFO   ] [Kivy        ] v2.1.0

justplaying0101

unread,
Oct 25, 2022, 9:20:05 PM10/25/22
to kivy-...@googlegroups.com
Looks like the naming convention does not correspond.  SDL is currently on version 2.24.x so maybe I'll get lucky and won't have to update my kivy-drops.sdl2 library. 

Elliot Garbus

unread,
Oct 25, 2022, 9:22:23 PM10/25/22
to kivy-...@googlegroups.com

Looking through the SDL docs – this is not a new feature. I would expect it will work for you on the older version.

justplaying0101

unread,
Oct 26, 2022, 8:34:46 AM10/26/22
to kivy-...@googlegroups.com
I'm happy to report that it is a sweet success! 

justplaying0101

unread,
Oct 26, 2022, 9:24:28 AM10/26/22
to kivy-...@googlegroups.com
For anyone interested in sample code that passes the mouse click event to an unfocused window and changes the cursor icon when hovering over a button or textinput. 
20221026_082028.jpg
20221026_081942.jpg

Elliot Garbus

unread,
Oct 26, 2022, 9:44:42 AM10/26/22
to kivy-...@googlegroups.com
😎

Sent from my iPhone

On Oct 26, 2022, at 5:34 AM, justplaying0101 <justpla...@gmail.com> wrote:


Reply all
Reply to author
Forward
0 new messages