Repository: qtile/qtile

Issue:
Copy from qt5 app fails with "QXcbClipboard::setMimeData: Cannot set X11 selection owner"
I'm using keepassxc as a password manager, and on a new (work) laptop I installed a more recent qtile than on an old one without taking much note of it (just checked out qtile from github and setup the virtualenv).

Suddenly I could not copy passwords in keepassxc any more and noticed that it prints the error message if I start it from a terminal.

QXcbClipboard::setMimeData: Cannot set X11 selection owner

Googling for the error message gives several ancient qt bugs.

(I found an issue that was found to be caused by a window manager, but I can't find it any more at the moment. I think I saved it in a note on the new work laptop.)

Then I reverted to the commit I was using on the old laptop, after having eliminated all other causes I could think of, and the behaviour was fixed.

I have now bisected this on a completely different laptop (the newer and older work laptops I first tried this with are running openSUSE 15.1, the private laptop runs Ubuntu 18.04) and the first bad commit is 28d5c30005b5342db9ea400e638daad15d110bfe, the fix for #755.

If I go back to the commit before it, I can copy passwords in keepassxc.

I can also replicate the problem in qt5ct (go to a number field in the second tab and try to copy it with ctrl-c, same error printed in the shell, nothing is copied).

I suspect it's reproducible with other qt(5?) apps. I generally don't take note which toolkit an application is using, so I'm not aware of any other qt apps I might use.

The behaviour in #755 is annoying too. I see that this was a very hard one to figure out and fix. I wasn't aware that it was specific to emacs and I never have an empty desktop that often. Copy + paste from keepassxc however is more important to me. (So I'm obviously  using 0617235c3e5748de83b2d8d630f766e3cd5682a4 until this issue is closed.)


Discussion:
Sorry that it introduces another issue :(,  I also noticed that Zeal has this issue the other day, and the exact error message,  I wasn't aware that it was introduced by that commit though.

So some investigation should be conducted to see what goes wrong, unfortunately.
I use other qt5 applications and all they fails the copy action. But if I downgrade the qtile to the revision that TauPan coment, they work fine.

For example, I use a qt terminal. And I test on konsole too.

If you need any tests, I will be pleased to help.
@bernatnan Thanks, but it seems this can be reproduced steadily, at least with Zeal on my side.
Don't know why the qt app (I tried Zeal and konsole) can't work when being used with `xtrace`, so I failed to find out what exactly happened when the issue popped up.

Here are something that I found:
1. Qt seems to set and check the selection owner: https://code.woboq.org/qt5/qtbase/src/plugins/platforms/xcb/qxcbclipboard.cpp.html#370
2. Docs about selections: https://tronche.com/gui/x/icccm/sec-2.html
Ah, I see!

I was wondering what if the TIMESTAMP set to `WM_TAKE_FOCUS` is too big? As it says in the spec that
>  The client should set the specified selection to the atom that represents the selection, set the specified owner to some window that the client created, and set the specified time to some time *between the current last-change time of the selection concerned and the current server time.*

It could fail if qt5 apps set a wrong TIMESTAMP, somehow.

But I failed to find the exact value range for TIMESTAMP in the spec, so I tried it with `x11trace emacs -Q`, and found that emacs sets a way smaller value than `time.time()` in the Python code (`54846926` vs. `1582992026` in decimal):

    002:<:100e: 16: Request(22): SetSelectionOwner owner=0x05200147 selection=0x1("PRIMARY") time=0x0344e5e6
    002:<:100f:  4: Request(43): GetInputFocus 

Then I did a quick verification to just hard-code the timestamp to `54846920`, and Zeal this time, as expected, text can be copied and pasted to another app perfectly.

I will continue to find out how to fix it properly, cheers!

Resolving patch:
diff --git a/libqtile/backend/x11/xcore.py b/libqtile/backend/x11/xcore.py
--- a/libqtile/backend/x11/xcore.py
+++ b/libqtile/backend/x11/xcore.py
@@ -84,6 +84,8 @@ def __init__(self, display_name: str = None) -> None:
             "_NET_SUPPORTED", [self.conn.atoms[x] for x in xcbq.SUPPORTED_ATOMS]
         )
 
+        self._last_event_timestamp = xcffib.CurrentTime
+
         wmname = "qtile"
         self._supporting_wm_check_window = self.conn.create_window(-1, -1, 1, 1)
         self._supporting_wm_check_window.set_property("_NET_WM_NAME", wmname)
@@ -273,6 +275,9 @@ def _get_target_chain(self, event_type: str, event) -> List[Callable]:
         """
         assert self.qtile is not None
 
+        if hasattr(event, "time") and event.time > 0:
+            self._last_event_timestamp = event.time
+
         handler = "handle_{event_type}".format(event_type=event_type)
         # Certain events expose the affected window id as an "event" attribute.
         event_events = [
@@ -301,6 +306,12 @@ def _get_target_chain(self, event_type: str, event) -> List[Callable]:
             logger.info("Unhandled event: {event_type}".format(event_type=event_type))
         return chain
 
+    def get_valid_timestamp(self):
+        """Get a valid timestamp, i.e. not CurrentTime, for X server.
+
+        It may be used in cases where CurrentTime is unacceptable for X server."""
+        return self._last_event_timestamp
+
     @property
     def display_name(self) -> str:
         """The name of the connected display"""
diff --git a/libqtile/window.py b/libqtile/window.py
--- a/libqtile/window.py
+++ b/libqtile/window.py
@@ -21,7 +21,6 @@
 import contextlib
 import inspect
 import traceback
-import time
 import warnings
 from xcffib.xproto import EventMask, StackMode, SetMode
 import xcffib.xproto
@@ -558,7 +557,7 @@ def focus(self, warp):
                     # > window manager (as described in section 4.2.8) with
                     # > WM_TAKE_FOCUS in its data[0] field and a valid timestamp
                     # > (i.e. not *CurrentTime* ) in its data[1] field.
-                    int(time.time()),
+                    self.qtile.core.get_valid_timestamp(),
                     0,
                     0,
                     0


Test patch: