Repository: qtile/qtile

Issue:
Error when attempting to set a new wallpaper inside a custom lazy function
### The issue:

qtile installed from pacman, 0.21.0

relevant logs:
```bash
2022-08-20 03:12:47,410 ERROR libqtile base.py:cmd_function():L214 Exception calling "<function download_wallpaper at 0x7f38fc933490>":
Traceback (most recent call last):
  File "/usr/lib/python3.10/site-packages/libqtile/command/base.py", line 211, in cmd_function
    function(self, *args, **kwargs)
  File "/home/dillan/.config/qtile/fetch_wallpaper.py", line 20, in download_wallpaper
    qtile.paint_screen(qtile.current_screen, f'{home}/Pictures/wallpapers/2560x1440_nzqwkw.jpeg', wallpaper_widget.option)
  File "/usr/lib/python3.10/site-packages/libqtile/core/manager.py", line 373, in paint_screen
    self.core.painter.paint(screen, image_path, mode)
  File "/usr/lib/python3.10/site-packages/libqtile/backend/x11/xcbq.py", line 708, in paint
    root_pixmap = self.default_screen.root.get_property(
  File "/usr/lib/python3.10/site-packages/libqtile/backend/x11/window.py", line 374, in get_property
    r = self.conn.conn.core.GetProperty(
  File "/usr/lib/python3.10/site-packages/xcffib/xproto.py", line 2720, in GetProperty
    return self.send_request(20, buf, GetPropertyCookie, is_checked=is_checked)
  File "/usr/lib/python3.10/site-packages/xcffib/__init__.py", line 391, in send_request
    seq = self.conn.send_request(flags, xcb_parts + 2, xcb_req)
  File "/usr/lib/python3.10/site-packages/xcffib/__init__.py", line 569, in wrapper
    self.invalid()
  File "/usr/lib/python3.10/site-packages/xcffib/__init__.py", line 559, in invalid
    raise ConnectionException(err)
xcffib.ConnectionException: xcb connection errors because of socket, pipe and other stream errors.
```

I created a helper function that downloads a new wallpaper when I click on the wallpaper widget:
```python
    mouse_callbacks={
        left_click: lazy.function(shuffle_wallpaper),
        middle_click: lazy.function(remove_wallpaper),
        right_click: lazy.function(download_wallpaper),
    }

def download_wallpaper(qtile: Qtile = None):
  ... # Download wallpaper
  if qtile:
    wallpaper_widget = qtile.current_screen._select('widget', 'wallpaper')
    wallpaper_widget.get_wallpapers()
    qtile.paint_screen(qtile.current_screen, image_file, wallpaper_widget.option)
```

The file downloads fine, but when it attempts to call paint_screen (also tried doing qtile.current_screen.cmd_set_wallpaper, same issue), it gives the above error. If I call `wallpaper_widget.set_wallpaper()` and let it internally shuffle, it updates fine. But I would like it to load the new wallpaper if possible.



### Required:

- [X] I have searched past issues to see if this bug has already been reported.


Discussion:
After you get this error please could you do `xprop -root` and post the output?
```bash
> xprop -root
_NET_CLIENT_LIST_STACKING(WINDOW): window id # 0x1200003, 0x220002d
_NET_CLIENT_LIST(WINDOW): window id # 0x1200003, 0x220002d
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x1200003
AT_SPI_BUS(STRING) = "unix:path=/run/user/1000/at-spi/bus_0,guid=4fc11f2d8aec79a10be209746301434e"
ESETROOT_PMAP_ID(PIXMAP): pixmap id # 0x2800001
_XROOTPMAP_ID(PIXMAP): pixmap id # 0x2800001
_NET_DESKTOP_VIEWPORT(CARDINAL) = 0, 0, 4800, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
_NET_CURRENT_DESKTOP(CARDINAL) = 0
_NET_DESKTOP_NAMES(UTF8_STRING) = "1", "2", "3", "4", "5", "6", "7", "8", "9"
_NET_NUMBER_OF_DESKTOPS(CARDINAL) = 9
_NET_SUPPORTING_WM_CHECK(WINDOW): window id # 0x40011d
_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) = "*.background:\t#061115\n*.color0:\t#1c252c\n*.color1:\t#df5b61\n*.color10:\t#8cd7aa\n*.color11:\t#e9967e\n*.color12:\t#79aaeb\n*.color13:\t#c488ec\n*.color14:\t#7acfe4\n*.color15:\t#e5e5e5\n*.color2:\t#78b892\n*.color3:\t#de8f78\n*.color4:\t#6791c9\n*.color5:\t#bc83e3\n*.color6:\t#67afc1\n*.color7:\t#d9d7d6\n*.color8:\t#484e5b\n*.color9:\t#f16269\n*.foreground:\t#d9d7d6\nXft.dpi:\t192\n"
GDK_VISUALS(INTEGER) = 43, 130
_XKB_RULES_NAMES(STRING) = "evdev", "pc105", "us", "", ""
XFree86_has_VT(INTEGER) = 1
XFree86_VT(INTEGER) = 7
Xorg_Seat(STRING) = "seat0"
```

I tried running from the command line like so:
```bash
qtile cmd-obj -o screen -f set_wallpaper -a /home/dillan/Pictures/wallpapers/6000x3375_pkq3zp.jpeg fill
```

But it gave the same error message:
```bash
error: Sorry cannot run function 'set_wallpaper' with arguments ['/home/dillan/Pictures/wallpapers/6000x3375_pkq3zp.jpeg', 'fill']: Traceback (most recent call last):
  File "/usr/lib/python3.10/site-packages/libqtile/command/interface.py", line 312, in call
    return SUCCESS, cmd(*args, **kwargs)
  File "/usr/lib/python3.10/site-packages/libqtile/config.py", line 599, in cmd_set_wallpaper
    self.paint(path, mode)
  File "/usr/lib/python3.10/site-packages/libqtile/config.py", line 404, in paint
    self.qtile.paint_screen(self, path, mode)
  File "/usr/lib/python3.10/site-packages/libqtile/core/manager.py", line 379, in paint_screen
    self.core.painter.paint(screen, image_path, mode)
  File "/usr/lib/python3.10/site-packages/libqtile/backend/x11/xcbq.py", line 701, in paint
    root_pixmap = self.default_screen.root.get_property(
  File "/usr/lib/python3.10/site-packages/libqtile/backend/x11/window.py", line 374, in get_property
    r = self.conn.conn.core.GetProperty(
  File "/usr/lib/python3.10/site-packages/xcffib/xproto.py", line 2720, in GetProperty
    return self.send_request(20, buf, GetPropertyCookie, is_checked=is_checked)
  File "/usr/lib/python3.10/site-packages/xcffib/__init__.py", line 391, in send_request
    seq = self.conn.send_request(flags, xcb_parts + 2, xcb_req)
  File "/usr/lib/python3.10/site-packages/xcffib/__init__.py", line 569, in wrapper
    self.invalid()
  File "/usr/lib/python3.10/site-packages/xcffib/__init__.py", line 559, in invalid
    raise ConnectionException(err)
xcffib.ConnectionException: xcb connection errors because of socket, pipe and other stream errors.
```
Not sure what's going on here. That shell command works fine for me.

Does it give the same error on every image you try?
Does making this change fix it?
```diff
diff --git a/libqtile/backend/x11/xcbq.py b/libqtile/backend/x11/xcbq.py
index 6e5ceb5a..11ebb3ae 100644
--- a/libqtile/backend/x11/xcbq.py
+++ b/libqtile/backend/x11/xcbq.py
@@ -698,9 +698,12 @@ class Painter:
         width = max((win.x + win.width for win in root_windows))
         height = max((win.y + win.height for win in root_windows))
 
-        root_pixmap = self.default_screen.root.get_property(
-            "_XROOTPMAP_ID", xcffib.xproto.Atom.PIXMAP, int
-        )
+        try:
+            root_pixmap = self.default_screen.root.get_property(
+                "_XROOTPMAP_ID", xcffib.xproto.Atom.PIXMAP, int
+            )
+        except xcffib.ConnectionException:
+            root_pixmap = None
         if not root_pixmap:
             root_pixmap = self.default_screen.root.get_property(
                 "ESETROOT_PMAP_ID", xcffib.xproto.Atom.PIXMAP, int

```
Yes, that worked! I had switched to using feh to set the background which was working fine, but with that change, I can use qtile to handle it directly. I don't know if there is a reason to use one over the other though. 
> Not sure what's going on here. That shell command works fine for me.
> 
> Does it give the same error on every image you try?

Yes, it did. 
Has this change been merged in yet?

Resolving patch:
diff --git a/libqtile/backend/x11/xcbq.py b/libqtile/backend/x11/xcbq.py
--- a/libqtile/backend/x11/xcbq.py
+++ b/libqtile/backend/x11/xcbq.py
@@ -698,9 +698,13 @@ def paint(self, screen, image_path, mode=None):
         width = max((win.x + win.width for win in root_windows))
         height = max((win.y + win.height for win in root_windows))
 
-        root_pixmap = self.default_screen.root.get_property(
-            "_XROOTPMAP_ID", xcffib.xproto.Atom.PIXMAP, int
-        )
+        try:
+            root_pixmap = self.default_screen.root.get_property(
+                "_XROOTPMAP_ID", xcffib.xproto.Atom.PIXMAP, int
+            )
+        except xcffib.ConnectionException:
+            root_pixmap = None
+
         if not root_pixmap:
             root_pixmap = self.default_screen.root.get_property(
                 "ESETROOT_PMAP_ID", xcffib.xproto.Atom.PIXMAP, int


Test patch: