joshgoebel / keyszer

a smart, flexible keymapper for X11 (a fork/reboot of xkeysnail )
Other
69 stars 15 forks source link

Lack of window leads to AttributeError. #141

Closed Fom123 closed 1 year ago

Fom123 commented 1 year ago

Describe the bug or unexpected behavior

When I switch to empty workspace in my window manager (qtile), I get the following error.

Exception in callback receive_input(InputDevice('...nput/event16')) at **/keyszer/src/keyszer/input.py:102
handle: <Handle receive_input(InputDevice('...nput/event16')) at **/keyszer/src/keyszer/input.py:102>
Traceback (most recent call last):
  File "/usr/lib/python3.10/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "**/keyszer/src/keyszer/input.py", line 117, in receive_input    on_event(event, device)
  File "**/keyszer/src/keyszer/transform.py", line 346, in on_event
    if context.x_error:
  File "**/keyszer/src/keyszer/lib/key_context.py", line 27, in x_error
    self._query_window_context()
  File "**/keyszer/src/keyszer/lib/key_context.py", line 13, in _query_window_context
    self._X_ctx = get_xorg_context()
  File "**/keyszer/src/keyszer/xorg.py", line 32, in get_xorg_context
    window      = get_actual_window(input_focus)
  File "**/keyszer/src/keyszer/xorg.py", line 60, in get_actual_window
    wmname  = window.get_full_text_property(_display.get_atom("_NET_WM_NAME"))
AttributeError: 'int' object has no attribute 'get_full_text_property'

Instructions to Reproduce

  1. Run the last git version.
  2. Open the empty workspace/desktop of your window manager.
  3. See, that keyszer hangs with attribute error.

Your setup

Expected behavior It shouldn't give an error.

Additional context xprop of the desktop

XFree86_DDC_EDID1_RAWDATA(INTEGER) = 0, -1, -1, -1, -1, -1, -1, 0, 9, -27, -33, 8, 0, 0, 0, 0, 37, 29, 1, 4, -91, 34, 19, 120, 7, 84, -91, -89, 84, 76, -101, 38, 15, 80, 84, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -104, -119, -128, -96, 112, 56, 96, 64, 48, 32, 54, 0, 88, -62, 16, 0, 0, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, 0, 66, 79, 69, 32, 67, 81, 10, 32, 32, 32, 32, 32, 32, 0, 0, 0, -2, 0, 78, 86, 49, 53, 54, 70, 72, 77, 45, 78, 89, 52, 10, 0, -3
_NET_CLIENT_LIST_STACKING(WINDOW): window id # 0x3c000aa, 0x4000014, 0x3c01662, 0x5800002, 0x6200001, 0x5600002, 0x4e00002
_NET_CLIENT_LIST(WINDOW): window id # 0x3c000aa, 0x4000014, 0x3c01662, 0x5800002, 0x6200001, 0x5600002, 0x4e00002
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x4e00002
_NET_CURRENT_DESKTOP(CARDINAL) = 5
_NET_DESKTOP_NAMES(UTF8_STRING) = "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"
_NET_NUMBER_OF_DESKTOPS(CARDINAL) = 10
AT_SPI_BUS(STRING) = "unix:path=/run/user/1000/at-spi/bus_0,guid=4fb45f75fa71b770ab7743f064076eb9"
GDK_VISUALS(INTEGER) = 1391, 1809
ESETROOT_PMAP_ID(PIXMAP): pixmap id # 0x1200001
_XROOTPMAP_ID(PIXMAP): pixmap id # 0x1200001
_NET_DESKTOP_VIEWPORT(CARDINAL) = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
_NET_SUPPORTING_WM_CHECK(WINDOW): window id # 0x4000bf
_NET_SUPPORTED(ATOM) = _NET_SUPPORTED, _NET_CLIENT_LIST, _NET_CLIENT_LIST_STACKING, _NET_CURRENT_DESKTOP, _NET_DESKTOP_VIEWPORT, _NET_ACTIVE_WINDOW, _NET_SUPPORTING_WM_CHECK, _NET_WM_NAME, _NET_WM_VISIBLE_NAME, _NET_WM_ICON_NAME, _NET_WM_DESKTOP, _NET_WM_WINDOW_TYPE, _NET_WM_STATE, _NET_WM_STRUT_PARTIAL, _NET_WM_PID, _NET_WM_WINDOW_TYPE_DESKTOP, _NET_WM_WINDOW_TYPE_DOCK, _NET_WM_WINDOW_TYPE_TOOLBAR, _NET_WM_WINDOW_TYPE_MENU, _NET_WM_WINDOW_TYPE_UTILITY, _NET_WM_WINDOW_TYPE_SPLASH, _NET_WM_WINDOW_TYPE_DIALOG, _NET_WM_WINDOW_TYPE_DROPDOWN_MENU, _NET_WM_WINDOW_TYPE_POPUP_MENU, _NET_WM_WINDOW_TYPE_TOOLTIP, _NET_WM_WINDOW_TYPE_NOTIFICATION, _NET_WM_WINDOW_TYPE_COMBO, _NET_WM_WINDOW_TYPE_DND, _NET_WM_WINDOW_TYPE_NORMAL, _NET_WM_STATE_MODAL, _NET_WM_STATE_STICKY, _NET_WM_STATE_MAXIMIZED_VERT, _NET_WM_STATE_MAXIMIZED_HORZ, _NET_WM_STATE_SHADED, _NET_WM_STATE_SKIP_TASKBAR, _NET_WM_STATE_SKIP_PAGER, _NET_WM_STATE_HIDDEN, _NET_WM_STATE_FULLSCREEN, _NET_WM_STATE_ABOVE, _NET_WM_STATE_BELOW, _NET_WM_STATE_DEMANDS_ATTENTION, _NET_WM_STATE_FOCUSED
RESOURCE_MANAGER(STRING) = "Xft.dpi:\t120\n"
_XKB_RULES_NAMES(STRING) = "evdev", "pc105", "us", "colemak", "grp:alt_shift_toggle"
XFree86_has_VT(INTEGER) = 1
XFree86_VT(INTEGER) = 1

Possible workaround Ignore all exceptions, as it was before f8e5014 commit.

def get_actual_window(window):
    # use _NET_WM_NAME string instead of WM_NAME to bypass (COMPOUND_TEXT) encoding problems
    try:
        wmname = window.get_full_text_property(
            _display.get_atom("_NET_WM_NAME")
        )
        wmclass = window.get_wm_class()
        # workaround for Java app
        # https://github.com/JetBrains/jdk8u_jdk/blob/master/src/solaris/classes/sun/awt/X11/XFocusProxyWindow.java#L35
        if (wmclass is None and wmname is None) or "FocusProxy" in (
            wmclass or ""
        ):
            parent_window = window.query_tree().parent
            if parent_window:
                return get_actual_window(parent_window)
            return None
    except Exception:
        return None

    return window
RedBearAK commented 1 year ago

@Fom123

Sorry you're having this issue.

Do you have the ability to print out what this returns so we can see why it's saying it's giving an int to get_full_text_property()? I can't replicate this on an empty workspace on my system (Fedora/GNOME). I've never run into anything like this in testing.

print(f'###  Bad result of _NET_WM_NAME: {_display.get_atom("_NET_WM_NAME")}')

The proposed try/except should make it work, but it would also be good to get a better understanding of exactly what's happening.

I'd do it myself but I have no experience with qtile.

Fom123 commented 1 year ago

@RedBearAK it doesn't give int to get_full_text_property. The window object is an integer(1 in my case) and that's why the error occurs.

From the xlib source code

def get_input_focus(self):
        """Return an object with the following attributes:
        focus
            The window which currently holds the input
            focus, X.NONE or X.PointerRoot.

X.NONE is 0 X.PointerRoot is 1.

So we have to handle the case when focus is a non-window object. Something like this would work.

from Xlib.xobject.drawable import Window

def get_actual_window(window):
    # use _NET_WM_NAME string instead of WM_NAME to bypass (COMPOUND_TEXT) encoding problems
    if not isinstance(window, Window):
        return None

    wmname = window.get_full_text_property(_display.get_atom("_NET_WM_NAME"))
    wmclass = window.get_wm_class()
    # workaround for Java app
    # https://github.com/JetBrains/jdk8u_jdk/blob/master/src/solaris/classes/sun/awt/X11/XFocusProxyWindow.java#L35
    if (wmclass is None and wmname is None) or "FocusProxy" in (wmclass or ""):
        parent_window = window.query_tree().parent
        if parent_window:
            return get_actual_window(parent_window)
        return None

    return window
RedBearAK commented 1 year ago

I see. In that case it looks like it could just as easily be:

if isinstance(window, int):
    return None

Without needing to import anything. If I'm reading the description right, it's either going to be a valid window object, or an int (0 or 1).

But either way should work.

joshgoebel commented 1 year ago
    if not isinstance(window, Window):
        return None

Looks good to me. PR?

RedBearAK commented 1 year ago

@joshgoebel

Submitted #144 to fix this.

Seems to work OK for me, and I don't know why it wouldn't, but I have no way of testing the int condition that this user ran into.

Fom123 commented 1 year ago

Thanks! I guess it can be closed now.