Patch-Source: https://github.com/qtile/qtile/pull/4289
--
From 419acd038407bdb302d66b7028f6e147da845d24 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho@tycho.pizza>
Date: Thu, 1 Jun 2023 21:00:58 -0600
Subject: [PATCH] use xcffib 1.4.0's new in-line APIs

xcffib 1.4.0 changed the way it builds cffi bindings in an attempt to solve
the install ordering problem:
https://github.com/Kozea/cairocffi/issues/214#issuecomment-1572816369

As a result, the location of the ffi import changed. I tried to patch
all the places we list an xcffib version, but it's possible I missed one.

Signed-off-by: Tycho Andersen <tycho@tycho.pizza>
---
 CHANGELOG                                     |  28 +++++
 dev.sh                                        |   4 -
 docs/conf.py                                  | 106 +++++++++---------
 docs/requirements-rtd.txt                     |  14 ++-
 docs/requirements.txt                         |   2 +-
 libqtile/backend/x11/xcursors.py              |  10 +-
 ...{xcursors_ffi_build.py => xcursors_ffi.py} |   6 +-
 libqtile/{pango_ffi_build.py => pango_ffi.py} |   6 +-
 libqtile/pangocffi.py                         |  17 ++-
 requirements.txt                              |   4 +-
 scripts/ffibuild                              |   6 -
 setup.cfg                                     |   6 +-
 setup.py                                      |   7 +-
 stubs/cairocffi/ffi.pyi                       |   1 +
 tox.ini                                       |  10 +-
 15 files changed, 122 insertions(+), 105 deletions(-)
 rename libqtile/backend/x11/{xcursors_ffi_build.py => xcursors_ffi.py} (90%)
 rename libqtile/{pango_ffi_build.py => pango_ffi.py} (96%)
 create mode 100644 stubs/cairocffi/ffi.pyi

diff --git a/CHANGELOG b/CHANGELOG
index 29ebf8e4..a80e8001 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,4 +1,32 @@
 Qtile x.xx.x, released XXXX-XX-XX:
+    !!! Dependency Changes !!!
+      - xcffib must be upgraded to >= 1.4.0
+      - cairocffi must be upgraded to >= 1.6.0
+    !!! config breakage/changes !!!
+      - The `cmd_` prefix has been dropped from all commands (this means command names are common when accessed
+        via the command interface or internal python objects).
+      - Custom widgets should now expose command methods with the `@expose_command` decorator (available via
+        `from libqtile.command.base import expose_command`).
+      - Some commands have been renamed (in addition to dropping the 'cmd_' prefix):
+          `hints` -> `get_hints`
+          `groups` -> `get_groups`
+          `screens` -> `get_screens`
+      - Layouts need to rename some methods:
+        - `add` to `add_client`
+        - `cmd_next` to `next`
+        - `cmd_previous` to `previous`
+      - Layouts or widgets that redefine the `commands` property need to update the signature:
+          `@expose_command()`
+          `def commands(self) -> list[str]:`
+      - `Window.getsize` has been renamed `Window.get_size` (i.e. merged with the get_size command).
+      - `Window.getposition` has been renamed `Window.get_position` (i.e. merged with the get_position command).
+      - The `StockTicker` widget `function` option is being deprecated: rename it to `func`.
+      - The formatting of `NetWidget` has changed, if you use the `format` parameter in your config include 
+        `up_suffix`, `total_suffix` and `down_suffix` to display the respective units.
+      - The `Notify` widget now has separate `default_timeout` properties for differenct urgency levels. Previously,
+        `default_timeout` was `None` which meant that there was no timeout for all notifications (unless this had been
+        set by the client sending the notification). Now, `default_timeout` is for normal urgency notifications and this
+        has been set to a default of 10 seconds. `default_timeout_urgent`, for critical notifications, has a timeout of `None`.
     * features
     * bugfixes
 
diff --git a/dev.sh b/dev.sh
index e0ca4c4f..b933825e 100755
--- a/dev.sh
+++ b/dev.sh
@@ -13,10 +13,6 @@ ${python} -m venv venv
 . venv/bin/activate
 pip install -U pip setuptools wheel
 
-# https://github.com/qtile/qtile/issues/994#issuecomment-497984551
-echo "Installing xcffib then cairocffi..."
-pip install 'xcffib >= 0.10.1' && pip install --no-cache-dir 'cairocffi >= 0.9.0'
-
 echo "Installing other required packages..."
 pip install -r requirements.txt
 pip install -r requirements-dev.txt
diff --git a/docs/conf.py b/docs/conf.py
index ad9c9b8b..015d8668 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -25,58 +25,60 @@ class Mock(MagicMock):
         return []
 
 MOCK_MODULES = [
-    'libqtile._ffi_pango',
-    'libqtile.backend.x11._ffi_xcursors',
-    'libqtile.widget._pulse_audio',
-    'cairocffi',
-    'cairocffi.xcb',
-    'cairocffi.pixbuf',
-    'cffi',
-    'dateutil',
-    'dateutil.parser',
-    'dbus_next',
-    'dbus_next.aio',
-    'dbus_next.errors',
-    'dbus_next.service',
-    'dbus_next.constants',
-    'iwlib',
-    'keyring',
-    'mpd',
-    'psutil',
-    'pywayland',
-    'pywayland.protocol.wayland',
-    'pywayland.protocol.wayland.wl_output',
-    'pywayland.server',
-    'wlroots',
-    'wlroots.helper',
-    'wlroots.util',
-    'wlroots.util.box',
-    'wlroots.util.clock',
-    'wlroots.util.edges',
-    'wlroots.util.region',
-    'wlroots.wlr_types',
-    'wlroots.wlr_types.cursor',
-    'wlroots.wlr_types.foreign_toplevel_management_v1',
-    'wlroots.wlr_types.idle',
-    'wlroots.wlr_types.idle_inhibit_v1',
-    'wlroots.wlr_types.keyboard',
-    'wlroots.wlr_types.layer_shell_v1',
-    'wlroots.wlr_types.output_management_v1',
-    'wlroots.wlr_types.pointer_constraints_v1',
-    'wlroots.wlr_types.output_power_management_v1',
-    'wlroots.wlr_types.server_decoration',
-    'wlroots.wlr_types.virtual_keyboard_v1',
-    'wlroots.wlr_types.virtual_pointer_v1',
-    'wlroots.wlr_types.xdg_shell',
-    'xcffib',
-    'xcffib.randr',
-    'xcffib.render',
-    'xcffib.wrappers',
-    'xcffib.xfixes',
-    'xcffib.xinerama',
-    'xcffib.xproto',
-    'xdg.IconTheme',
-    'xkbcommon'
+    "libqtile._ffi_pango",
+    "libqtile.backend.x11._ffi_xcursors",
+    "libqtile.widget._pulse_audio",
+    "cairocffi.ffi",
+    "cairocffi.xcb",
+    "cairocffi.pixbuf",
+    "cffi",
+    "dateutil",
+    "dateutil.parser",
+    "dbus_next",
+    "dbus_next.aio",
+    "dbus_next.errors",
+    "dbus_next.service",
+    "dbus_next.constants",
+    "iwlib",
+    "keyring",
+    "mpd",
+    "psutil",
+    "pywayland",
+    "pywayland.protocol.wayland",
+    "pywayland.protocol.wayland.wl_output",
+    "pywayland.server",
+    "wlroots",
+    "wlroots.helper",
+    "wlroots.util",
+    "wlroots.util.box",
+    "wlroots.util.clock",
+    "wlroots.util.edges",
+    "wlroots.util.region",
+    "wlroots.wlr_types",
+    "wlroots.wlr_types.cursor",
+    "wlroots.wlr_types.foreign_toplevel_management_v1",
+    "wlroots.wlr_types.idle",
+    "wlroots.wlr_types.idle_inhibit_v1",
+    "wlroots.wlr_types.keyboard",
+    "wlroots.wlr_types.layer_shell_v1",
+    "wlroots.wlr_types.output_management_v1",
+    "wlroots.wlr_types.pointer_constraints_v1",
+    "wlroots.wlr_types.output_power_management_v1",
+    "wlroots.wlr_types.server_decoration",
+    "wlroots.wlr_types.virtual_keyboard_v1",
+    "wlroots.wlr_types.virtual_pointer_v1",
+    "wlroots.wlr_types.xdg_shell",
+    "xcffib",
+    "xcffib.ffi",
+    "xcffib.randr",
+    "xcffib.render",
+    "xcffib.wrappers",
+    "xcffib.xfixes",
+    "xcffib.xinerama",
+    "xcffib.xproto",
+    "xcffib.xtest",
+    "xdg.IconTheme",
+    "xkbcommon",
 ]
 sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
 
diff --git a/docs/requirements-rtd.txt b/docs/requirements-rtd.txt
index 9011d873..8c478f9b 100644
--- a/docs/requirements-rtd.txt
+++ b/docs/requirements-rtd.txt
@@ -2,4 +2,16 @@
 # seems to have some mismatch of the libffi library
 # and the latest release of cffi.
 # We can pin the version here for the docs build.
-cffi<=1.15.0
\ No newline at end of file
+cffi<=1.15.0
+setuptools_scm
+# Sphinx 7.0.0 is not currently supported by the sphinx RTD theme
+sphinx<7.0.0
+sphinx_rtd_theme
+funcparserlib==1.0.0a0
+sphinxcontrib-seqdiag
+numpydoc
+pytest
+PyGObject
+dbus-next
+cairocffi>=1.6.0
+xcffib>=1.4.0
diff --git a/docs/requirements.txt b/docs/requirements.txt
index c791cd39..ff07f35f 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -5,6 +5,6 @@ funcparserlib==1.0.0a0
 sphinxcontrib-seqdiag
 numpydoc
 pytest
-xcffib >= 0.10.1
+xcffib >= 1.4.0
 PyGObject
 dbus-next
diff --git a/libqtile/backend/x11/xcursors.py b/libqtile/backend/x11/xcursors.py
index be1f290c..ff9427ed 100644
--- a/libqtile/backend/x11/xcursors.py
+++ b/libqtile/backend/x11/xcursors.py
@@ -1,12 +1,6 @@
+from libqtile.backend.x11.xcursors_ffi import xcursors_ffi as ffi
 from libqtile.log_utils import logger
 
-try:
-    from libqtile.backend.x11._ffi_xcursors import ffi
-except ImportError:
-    raise ImportError(
-        "No module named libqtile.backend.x11._ffi_xcursors, be sure to run `./scripts/ffibuild`"
-    )
-
 
 # Stolen from samurai-x
 # (Don't know where to put it, so I'll put it here)
@@ -110,7 +104,7 @@ class Cursors(dict):
     def _setup_xcursor_binding(self):
         try:
             xcursor = ffi.dlopen("libxcb-cursor.so.0")
-        except OSError:
+        except Exception:
             logger.info("xcb-cursor not found, fallback to font pointer")
             return False
 
diff --git a/libqtile/backend/x11/xcursors_ffi_build.py b/libqtile/backend/x11/xcursors_ffi.py
similarity index 90%
rename from libqtile/backend/x11/xcursors_ffi_build.py
rename to libqtile/backend/x11/xcursors_ffi.py
index 08596ec7..7a95564d 100644
--- a/libqtile/backend/x11/xcursors_ffi_build.py
+++ b/libqtile/backend/x11/xcursors_ffi.py
@@ -22,10 +22,9 @@
 # SOFTWARE.
 
 from cffi import FFI
-from xcffib.ffi_build import ffi as xcffib_ffi
+from xcffib.ffi import ffi as xcffib_ffi
 
 xcursors_ffi = FFI()
-xcursors_ffi.set_source("libqtile.backend.x11._ffi_xcursors", None)
 
 xcursors_ffi.include(xcffib_ffi)
 
@@ -48,6 +47,3 @@ xcursors_ffi.cdef(
     void xcb_cursor_context_free(xcb_cursor_context_t *ctx);
 """
 )
-
-if __name__ == "__main__":
-    xcursors_ffi.compile()
diff --git a/libqtile/pango_ffi_build.py b/libqtile/pango_ffi.py
similarity index 96%
rename from libqtile/pango_ffi_build.py
rename to libqtile/pango_ffi.py
index 971dfae1..17ef2a47 100644
--- a/libqtile/pango_ffi_build.py
+++ b/libqtile/pango_ffi.py
@@ -21,11 +21,10 @@
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
 
-from cairocffi.ffi_build import ffi as cairocffi_ffi
+from cairocffi.ffi import ffi as cairocffi_ffi
 from cffi import FFI
 
 pango_ffi = FFI()
-pango_ffi.set_source("libqtile._ffi_pango", None)
 
 pango_ffi.include(cairocffi_ffi)
 
@@ -141,6 +140,3 @@ pango_ffi.cdef(
                          gssize length);
 """
 )
-
-if __name__ == "__main__":
-    pango_ffi.compile()
diff --git a/libqtile/pangocffi.py b/libqtile/pangocffi.py
index 00d72251..8fefdceb 100644
--- a/libqtile/pangocffi.py
+++ b/libqtile/pangocffi.py
@@ -47,24 +47,23 @@
 # This is not intended to be a complete cffi-based pango binding.
 
 
-try:
-    from libqtile._ffi_pango import ffi
-except ImportError:
-    raise ImportError("No module named libqtile._ffi_pango, be sure to run `./scripts/ffibuild`")
+from libqtile.pango_ffi import pango_ffi as ffi
 
-gobject = ffi.dlopen("libgobject-2.0.so.0")
-pango = ffi.dlopen("libpango-1.0.so.0")
-pangocairo = ffi.dlopen("libpangocairo-1.0.so.0")
+gobject = ffi.dlopen("libgobject-2.0.so.0")  # type: ignore
+pango = ffi.dlopen("libpango-1.0.so.0")  # type: ignore
+pangocairo = ffi.dlopen("libpangocairo-1.0.so.0")  # type: ignore
 
 
 def patch_cairo_context(cairo_t):
     def create_layout():
-        return PangoLayout(cairo_t._pointer)
+        return PangoLayout(ffi.cast("struct _cairo *", cairo_t._pointer))
 
     cairo_t.create_layout = create_layout
 
     def show_layout(layout):
-        pangocairo.pango_cairo_show_layout(cairo_t._pointer, layout._pointer)
+        pangocairo.pango_cairo_show_layout(
+            ffi.cast("struct _cairo *", cairo_t._pointer), layout._pointer
+        )
 
     cairo_t.show_layout = show_layout
 
diff --git a/requirements.txt b/requirements.txt
index ebbe4c5e..ac9e2a5f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,3 @@
-cairocffi >= 0.9.0
+cairocffi >= 1.6.0
 cffi >= 1.1.0
-
+xcffib >= 1.4.0
diff --git a/scripts/ffibuild b/scripts/ffibuild
index 1abc784f..79b0fc64 100755
--- a/scripts/ffibuild
+++ b/scripts/ffibuild
@@ -21,12 +21,6 @@ class Builder(NamedTuple):
 
 
 scripts = [
-    Builder("Pango interface", "./libqtile/pango_ffi_build.py", None),
-    Builder(
-        "xcursors",
-        "./libqtile/backend/x11/xcursors_ffi_build.py",
-        "X11 backend",
-    ),
     Builder("PulseAudio interface", "./libqtile/widget/pulseaudio_ffi.py", "PulseVolume widget"),
     Builder(
         "Wayland libinput interface",
diff --git a/setup.cfg b/setup.cfg
index 5fb72561..8d07aa7a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -39,13 +39,13 @@ packages = find:
 python_requires >= 3.7
 setup_requires =
   cffi >= 1.1.0
-  cairocffi[xcb] >= 0.9.0
+  cairocffi[xcb] >= 1.6.0
   setuptools >= 40.5.0
   setuptools_scm >= 3.2.0
 install_requires =
   cffi >= 1.1.0
-  cairocffi[xcb] >= 0.9.0
-  xcffib >= 0.10.1
+  cairocffi[xcb] >= 1.6.0
+  xcffib >= 1.4.0
 tests_require =
   flake8
   pep8-naming
diff --git a/setup.py b/setup.py
index eb333589..f1ddf782 100755
--- a/setup.py
+++ b/setup.py
@@ -49,7 +49,7 @@ class CheckCairoXcb(install):
 
             It looks like your cairocffi was not built with xcffib support.  To fix this:
 
-              - Ensure a recent xcffib is installed (pip install 'xcffib>=0.5.0')
+              - Ensure a recent xcffib is installed (pip install 'xcffib>=1.4.0')
               - The pip cache is cleared (remove ~/.cache/pip, if it exists)
               - Reinstall cairocffi, either:
 
@@ -65,10 +65,7 @@ class CheckCairoXcb(install):
 
 
 def get_cffi_modules():
-    cffi_modules = [
-        'libqtile/pango_ffi_build.py:pango_ffi',
-        'libqtile/backend/x11/xcursors_ffi_build.py:xcursors_ffi',
-    ]
+    cffi_modules = []
     try:
         from cffi.error import PkgConfigError
         from cffi.pkgconfig import call
diff --git a/stubs/cairocffi/ffi.pyi b/stubs/cairocffi/ffi.pyi
new file mode 100644
index 00000000..f37839c6
--- /dev/null
+++ b/stubs/cairocffi/ffi.pyi
@@ -0,0 +1 @@
+ffi = ...
diff --git a/tox.ini b/tox.ini
index fdb37972..0a91eee3 100644
--- a/tox.ini
+++ b/tox.ini
@@ -41,10 +41,12 @@ commands =
     # However, rebuilding on pypy doesn't work so we'll pin the version instead
     # See: https://github.com/tych0/xcffib/issues/134
     pypy3: pip install cffi==1.15.0
-    pip install xcffib>=0.10.1
-    pip install cairocffi
-    pip install pywlroots>=0.15.21,<0.16.0
-    python3 setup.py -q install
+    pip install xcffib>=1.4.0 wheel
+    # The 1.5.0 release of cairocffi cannot be built in a clean folder as it will fail to
+    # to build with xcb suppport. https://github.com/Kozea/cairocffi/issues/212
+    pip install --no-cache --upgrade --no-build-isolation cairocffi
+    pip install pywlroots>=0.15.24,<0.16.0
+    pip install --no-build-isolation .
     {toxinidir}/scripts/ffibuild
 # py310 currently fails with -W error, see: https://gitlab.gnome.org/GNOME/pygobject/-/issues/476
 # pypy3 is very slow when running coverage reports so we skip it (coverage is reported on py37 to py310)
-- 
2.41.0

