Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package libwacom for openSUSE:Factory 
checked in at 2026-10-26 09:57:53
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/libwacom (Old)
 and      /work/SRC/openSUSE:Factory/.libwacom.new.2252680 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "libwacom"

Mon Oct 26 09:57:53 2026 rev:51 rq:1392141 version:2.20.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/libwacom/libwacom.changes        2026-08-24 
12:02:53.900356118 +0200
+++ /work/SRC/openSUSE:Factory/.libwacom.new.2252680/libwacom.changes   
2026-10-26 09:57:56.916542619 +0100
@@ -1,0 +2,13 @@
+Fri Oct 23 16:33:41 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to 2.20.0:
+  * New devices: Lenovo Yoga 9 2-in-1 14IPH11, IdeaPad 5
+    14AHP9 and ThinkBook 14s Yoga (ISDv4 526b), Huion HS610
+    (new USB PID), XP-Pen Deco SE, Gaomon S620, Ugee M808
+  * tools: stay quiet when /dev/input is not present
+- Bump meson floor to >= 0.58.0 and add glib >= 2.68 floor,
+  matching the minimums declared upstream
+- Spec cleanup: drop obsolete Group tags, canonical
+  spec-cleaner style
+
+-------------------------------------------------------------------

Old:
----
  libwacom-2.19.1.tar.xz
  libwacom-2.19.1.tar.xz.sig

New:
----
  libwacom-2.20.0.tar.xz
  libwacom-2.20.0.tar.xz.sig

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ libwacom.spec ++++++
--- /var/tmp/diff_new_pack.CVjFoc/_old  2026-10-26 09:57:57.822580629 +0100
+++ /var/tmp/diff_new_pack.CVjFoc/_new  2026-10-26 09:57:57.824580713 +0100
@@ -17,28 +17,26 @@
 
 # library version from upstream meson.build
 %define sover 9
-
 Name:           libwacom
-Version:        2.19.1
+Version:        2.20.0
 Release:        0
 Summary:        Tablet description library
 License:        HPND
-Group:          Development/Libraries/C and C++
 URL:            https://linuxwacom.github.io/
 Source0:        
https://github.com/linuxwacom/libwacom/releases/download/libwacom-%{version}/libwacom-%{version}.tar.xz
 Source1:        
https://github.com/linuxwacom/libwacom/releases/download/libwacom-%{version}/libwacom-%{version}.tar.xz.sig
 # Fetched from https://github.com/whot.gpg
 Source2:        %{name}.keyring
 Source99:       baselibs.conf
-BuildRequires:  meson >= 0.57.0
+BuildRequires:  doxygen
 BuildRequires:  fdupes
+BuildRequires:  meson >= 0.58.0
 BuildRequires:  ninja
 BuildRequires:  pkgconfig
-BuildRequires:  pkgconfig(glib-2.0)
+BuildRequires:  pkgconfig(glib-2.0) >= 2.68
 BuildRequires:  pkgconfig(gudev-1.0)
 BuildRequires:  pkgconfig(libevdev) >= 1.7.0
 BuildRequires:  pkgconfig(libxml-2.0)
-BuildRequires:  doxygen
 
 %description
 libwacom is a library to identify graphics tablets and their model-specific
@@ -47,7 +45,6 @@
 
 %package -n libwacom%{sover}
 Summary:        Library to identify wacom tablets
-Group:          System/Libraries
 Requires:       %{name}-data >= %{version}
 
 %description -n libwacom%{sover}
@@ -59,7 +56,6 @@
 
 %package data
 Summary:        Data files for libwacom, a table identification library
-Group:          System/Libraries
 
 %description data
 libwacom is a library to identify graphics tablets and their model-specific
@@ -68,7 +64,6 @@
 
 %package tools
 Summary:        Command-line tools for libwacom
-Group:          Hardware/Other
 Requires:       python3-libevdev
 Requires:       python3-pyudev
 
@@ -79,7 +74,6 @@
 
 %package devel
 Summary:        Header files for libwacom
-Group:          Development/Libraries/C and C++
 Requires:       libwacom%{sover} = %{version}
 
 %description devel
@@ -97,9 +91,11 @@
 %install
 %meson_install
 
+# Keep the literal shebang text below: the pattern matches upstream file
+# content, it is not a path we emit (spec-cleaner macro-ifies it).
 sed -e 's-#!/usr/bin/env python3-#!/usr/bin/python3-g' -i 
%{buildroot}%{_bindir}/*
 find %{buildroot} -type f -name "*.la" -delete -print
-%python3_fix_shebang
+%{python3_fix_shebang}
 
 %fdupes %{buildroot}%{_datadir}/libwacom/layouts
 

++++++ libwacom-2.19.1.tar.xz -> libwacom-2.20.0.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libwacom-2.19.1/.github/workflows/check-for-sysinfo.yml 
new/libwacom-2.20.0/.github/workflows/check-for-sysinfo.yml
--- old/libwacom-2.19.1/.github/workflows/check-for-sysinfo.yml 2026-07-20 
05:12:17.000000000 +0200
+++ new/libwacom-2.20.0/.github/workflows/check-for-sysinfo.yml 2026-09-07 
08:48:53.000000000 +0200
@@ -1,6 +1,6 @@
 name: "Check for sysinfo in data files"
 
-on: [ pull_request ]
+on: [ pull_request, merge_group ]
 
 permissions:
   contents: read
@@ -9,7 +9,7 @@
   checksysinfo:
     runs-on: ubuntu-24.04
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v7
         with:
           fetch-depth: 0
       - uses: tj-actions/changed-files@v46
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/.github/workflows/freebsd.yml 
new/libwacom-2.20.0/.github/workflows/freebsd.yml
--- old/libwacom-2.19.1/.github/workflows/freebsd.yml   2026-07-20 
05:12:17.000000000 +0200
+++ new/libwacom-2.20.0/.github/workflows/freebsd.yml   2026-09-07 
08:48:53.000000000 +0200
@@ -1,6 +1,6 @@
 name: "FreeBSD build and test"
 
-on: [ push, pull_request ]
+on: [ push, pull_request, merge_group ]
 
 permissions:
   contents: read
@@ -13,7 +13,7 @@
   freebsd:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v7
     - name: meson test
       uses: vmactions/freebsd-vm@v1
       with:
@@ -25,7 +25,7 @@
           meson setup builddir
           meson test -C builddir --print-errorlogs
     # Capture all the meson logs, even if we failed
-    - uses: actions/upload-artifact@v4
+    - uses: actions/upload-artifact@v7
       if: ${{ always() }}  # even if we fail
       with:
         name: meson test logs
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/libwacom-2.19.1/.github/workflows/label-commenter.yml 
new/libwacom-2.20.0/.github/workflows/label-commenter.yml
--- old/libwacom-2.19.1/.github/workflows/label-commenter.yml   2026-07-20 
05:12:17.000000000 +0200
+++ new/libwacom-2.20.0/.github/workflows/label-commenter.yml   2026-09-07 
08:48:53.000000000 +0200
@@ -15,6 +15,6 @@
   comment:
     runs-on: ubuntu-24.04
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v7
       - name: Label Commenter
         uses: peaceiris/actions-label-commenter@v1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/.github/workflows/main.yml 
new/libwacom-2.20.0/.github/workflows/main.yml
--- old/libwacom-2.19.1/.github/workflows/main.yml      2026-07-20 
05:12:17.000000000 +0200
+++ new/libwacom-2.20.0/.github/workflows/main.yml      2026-09-07 
08:48:53.000000000 +0200
@@ -1,6 +1,6 @@
 name: "Build and test"
 
-on: [ push, pull_request ]
+on: [ push, pull_request, merge_group ]
 
 permissions:
   contents: read
@@ -19,9 +19,9 @@
           - gcc
           - clang
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v7
       # install python so we get pip for meson
-      - uses: actions/setup-python@v5
+      - uses: actions/setup-python@v7
         with:
           python-version: '3.12'
       - uses: ./.github/actions/pkginstall
@@ -44,7 +44,7 @@
         with:
           ninja_args: dist
       # Capture all the meson logs, even if we failed
-      - uses: actions/upload-artifact@v4
+      - uses: actions/upload-artifact@v7
         if: ${{ always() }}  # even if we fail
         with:
           name: meson test logs-${{matrix.compiler}} ${{matrix.meson_options}}
@@ -54,7 +54,7 @@
       - name: move tarballs to top level
         run: mv builddir/meson-dist/libwacom-*tar.xz .
       # We only need one tarball for the build-from-tarball job
-      - uses: actions/upload-artifact@v4
+      - uses: actions/upload-artifact@v7
         if: ${{ matrix.compiler == 'gcc' && matrix.meson_options == '' }}
         with:
           name: tarball
@@ -67,9 +67,9 @@
   clang-tidy:
     runs-on: ubuntu-24.04
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v7
       # install python so we get pip for meson
-      - uses: actions/setup-python@v5
+      - uses: actions/setup-python@v7
         with:
           python-version: '3.12'
       - uses: ./.github/actions/pkginstall
@@ -91,9 +91,9 @@
   valgrind:
     runs-on: ubuntu-24.04
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v7
       # install python so we get pip for meson
-      - uses: actions/setup-python@v5
+      - uses: actions/setup-python@v7
         with:
           python-version: '3.12'
       - uses: ./.github/actions/pkginstall
@@ -113,7 +113,7 @@
         env:
           CC: ${{matrix.compiler}}
       # Capture all the meson logs, even if we failed
-      - uses: actions/upload-artifact@v4
+      - uses: actions/upload-artifact@v7
         if: ${{ always() }}  # even if we fail
         with:
           name: meson test logs-valgrind
@@ -141,9 +141,9 @@
           - sudo csplit data/wacom.stylus '/^\[0x56a:0x822\]/' && sudo mv xx00 
/etc/libwacom/first.stylus && sudo mv xx01 /usr/share/libwacom/wacom.stylus
 
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v7
       # install python so we get pip for meson
-      - uses: actions/setup-python@v5
+      - uses: actions/setup-python@v7
         with:
           python-version: '3.12'
       # Run as sudo because we install to /etc and thus need the pip
@@ -176,9 +176,9 @@
     needs: build-and-dist
     runs-on: ubuntu-24.04
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v7
       # install python so we get pip for meson
-      - uses: actions/setup-python@v5
+      - uses: actions/setup-python@v7
         with:
           python-version: '3.12'
       # Run as sudo because we install to /etc and thus need the pip
@@ -211,8 +211,8 @@
       # We expect the overridden match *not* to be listed
       - name: check for the expected devices to be present (or not present)
         run: |
-          test "$(libwacom-list-devices --format=yaml | yq -r '.devices[] | 
select(.bus == "usb") | select(.vid == "0x056a") | select(.pid == "0x032a") | 
.name')" == "Wacom Cintiq 27QHD MODIFIED"
-          test "$(libwacom-list-devices --format=yaml | yq -r '.devices[] | 
select(.bus == "bluetooth") | select(.vid == "0x056a") | select(.pid == 
"0x0361") | .name')" == "Wacom Intuos Pro L MODIFIED"
+          test "$(libwacom-list-devices --format=yaml | yq -r '.devices[] | 
select(.bus == "usb") | select(.vid == "0x056a") | select(.pid == "0x032a") | 
.name')" = "Wacom Cintiq 27QHD MODIFIED"
+          test "$(libwacom-list-devices --format=yaml | yq -r '.devices[] | 
select(.bus == "bluetooth") | select(.vid == "0x056a") | select(.pid == 
"0x0361") | .name')" = "Wacom Intuos Pro L MODIFIED"
           test $(libwacom-list-devices --format=yaml | yq -r '.devices[] | 
select(.bus == "usb") | select(.vid == "0x056a") | select(.pid == "0x0358") | 
.name' | wc -l) -eq 0
           test $(libwacom-list-devices --format=yaml | yq -r '.devices[] | 
select(.bus == "usb") | select(.vid == "0x056a") | select(.pid == "0x032a") | 
.name' | wc -l) -eq 1
 
@@ -222,9 +222,9 @@
     needs: build-and-dist
     runs-on: ubuntu-24.04
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v7
       # install python so we get pip for meson
-      - uses: actions/setup-python@v5
+      - uses: actions/setup-python@v7
         with:
           python-version: '3.12'
       # Run as sudo because we install to /etc and thus need the pip
@@ -251,8 +251,8 @@
     needs: build-and-dist
     runs-on: ubuntu-24.04
     steps:
-      - uses: actions/checkout@v4
-      - uses: actions/setup-python@v5
+      - uses: actions/checkout@v7
+      - uses: actions/setup-python@v7
         with:
           python-version: '3.12'
       - name: run clean-svg on all .tablet files
@@ -273,9 +273,9 @@
       TARBALLDIR: '_tarball_dir'
       INSTALLDIR: '/tmp/libwacom/_inst'
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v7
       - name: install python
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@v7
         with:
           python-version: '3.12'
       - uses: ./.github/actions/pkginstall
@@ -283,7 +283,7 @@
           apt: $UBUNTU_PACKAGES
           pip: $PIP_PACKAGES
       - name: fetch tarball from previous job(s)
-        uses: actions/download-artifact@v4
+        uses: actions/download-artifact@v8
         with:
           name: tarball
       - name: extract tarball
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/.github/workflows/pages.yml 
new/libwacom-2.20.0/.github/workflows/pages.yml
--- old/libwacom-2.19.1/.github/workflows/pages.yml     2026-07-20 
05:12:17.000000000 +0200
+++ new/libwacom-2.20.0/.github/workflows/pages.yml     2026-09-07 
08:48:53.000000000 +0200
@@ -18,12 +18,12 @@
     permissions:
       contents: write
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v7
       - uses: ./.github/actions/pkginstall
         with:
           apt: $UBUNTU_PACKAGES
       # install python so we get pip for meson
-      - uses: actions/setup-python@v5
+      - uses: actions/setup-python@v7
         with:
           python-version: '3.12'
       - uses: ./.github/actions/pkginstall
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/.github/workflows/style.yml 
new/libwacom-2.20.0/.github/workflows/style.yml
--- old/libwacom-2.19.1/.github/workflows/style.yml     2026-07-20 
05:12:17.000000000 +0200
+++ new/libwacom-2.20.0/.github/workflows/style.yml     2026-09-07 
08:48:53.000000000 +0200
@@ -1,6 +1,6 @@
 name: "Coding style"
 
-on: [ push, pull_request ]
+on: [ push, pull_request, merge_group ]
 
 permissions:
   contents: read
@@ -13,8 +13,8 @@
   clang-format:
     runs-on: ubuntu-24.04
     steps:
-      - uses: actions/checkout@v4
-      - uses: actions/setup-python@v5
+      - uses: actions/checkout@v7
+      - uses: actions/setup-python@v7
         with:
           python-version: '3.12'
       - uses: ./.github/actions/pkginstall
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/NEWS new/libwacom-2.20.0/NEWS
--- old/libwacom-2.19.1/NEWS    2026-07-20 05:12:17.000000000 +0200
+++ new/libwacom-2.20.0/NEWS    2026-09-07 08:48:53.000000000 +0200
@@ -1,3 +1,11 @@
+libwacom 2.20.0:
+- New Devices:
+  - Lenovo Yoga 9, IdeaPad 5, ThinkBook 14s Yoga
+  - Huion HS610
+  - XP-Pen Deco SE
+  - Gaomon S620
+  - Ugee M808
+
 libwacom 2.19.1:
 - Build fixes for older systems and other arches
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/data/gaomon-s620.tablet 
new/libwacom-2.20.0/data/gaomon-s620.tablet
--- old/libwacom-2.19.1/data/gaomon-s620.tablet 2026-07-20 05:12:17.000000000 
+0200
+++ new/libwacom-2.20.0/data/gaomon-s620.tablet 2026-09-07 08:48:53.000000000 
+0200
@@ -11,7 +11,7 @@
 [Device]
 Name=GAOMON S620
 ModelName=
-DeviceMatch=usb|256c|006d|GAOMON Gaomon Tablet Pen;usb|256c|006d|GAOMON Gaomon 
Tablet Pad;usb|256c|006d|GAOMON Gaomon Tablet 
Pen|OEM02_T18e;usb|256c|006d|GAOMON Gaomon Tablet Pad|OEM02_T18e;
+DeviceMatch=usb|256c|006d|GAOMON Gaomon Tablet Pen;usb|256c|006d|GAOMON Gaomon 
Tablet Pad;usb|256c|006d|GAOMON Gaomon Tablet 
Pen|OEM02_T18e;usb|256c|006d|GAOMON Gaomon Tablet 
Pad|OEM02_T18e;usb|256c|006f|GAOMON Gaomon Tablet;usb|256c|006f|GAOMON Gaomon 
Tablet Pen
 Width=152
 Height=102
 Layout=gaomon-s620.svg
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/data/huion-hs610.tablet 
new/libwacom-2.20.0/data/huion-hs610.tablet
--- old/libwacom-2.19.1/data/huion-hs610.tablet 2026-07-20 05:12:17.000000000 
+0200
+++ new/libwacom-2.20.0/data/huion-hs610.tablet 2026-09-07 08:48:53.000000000 
+0200
@@ -5,7 +5,7 @@
 [Device]
 Name=Huion HS610
 ModelName=HS610
-DeviceMatch=usb|256c|0064||HUION_T194
+DeviceMatch=usb|256c|0064||HUION_T194;usb|256c|006d||HUION_T194
 Width=254
 Height=152
 Layout=huion-hs610.svg
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/data/layouts/ugee-m808.svg 
new/libwacom-2.20.0/data/layouts/ugee-m808.svg
--- old/libwacom-2.19.1/data/layouts/ugee-m808.svg      1970-01-01 
01:00:00.000000000 +0100
+++ new/libwacom-2.20.0/data/layouts/ugee-m808.svg      2026-09-07 
08:48:53.000000000 +0200
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg id="ugee-m808" width="330" height="210" 
style="color:#000000;stroke:#7f7f7f;fill:none;stroke-width:.25;font-size:8" 
version="1.1" xmlns="http://www.w3.org/2000/svg";>
+    <title id="title">ugee-m808</title>
+    <rect id="DrawingArea" class="DrawingArea" x="59" y="29" width="254" 
height="152"/>
+    <rect id="Outline" class="Outline" x="0" y="0" width="330" height="210" 
rx="29.5" ry="58"/>
+    <g id="g8">
+        <path id="LeaderE" class="E Leader" d="M 52 104 H 70"/>
+        <text id="LabelE" class="E Label" x="71" y="107" 
style="text-anchor:start;">E</text>
+    </g>
+    <g id="g9">
+        <path id="LeaderC" class="C Leader" d="M 38 90 H 70"/>
+        <text id="LabelC" class="C Label" x="71" y="93" 
style="text-anchor:start;">C</text>
+    </g>
+    <g id="g6">
+        <path id="LeaderD" class="D Leader" d="m 19 108 v 21 h 50"/>
+        <text id="LabelD" class="D Label" x="70.5" y="132" 
style="text-anchor:start;">D</text>
+    </g>
+    <g id="g3">
+        <circle id="Ring" class="Ring" cx="34" cy="104" r="19.5"/>
+        <circle id="InnerRing" class="Inner Ring" cx="34" cy="104" r="9.5"/>
+        <circle id="ButtonE" class="E Button" cx="48.5" cy="104" r="1"/>
+        <circle id="ButtonC" class="C Button" cx="34" cy="89.5" r="1"/>
+        <circle id="ButtonD" class="D Button" cx="19.5" cy="104" r="1"/>
+        <circle id="ButtonF" class="F Button" cx="34" cy="118.5" r="1"/>
+    </g>
+    <g id="g7">
+        <path id="LeaderF" class="F Leader" d="M 38 118 H 70"/>
+        <text id="LabelF" class="F Label" x="71" y="121" 
style="text-anchor:start;">F</text>
+    </g>
+    <g id="g11">
+        <path id="LeaderB" class="B Leader" d="M 43.5 60 V 41"/>
+        <text id="LabelB" class="B Label" x="41" y="39.5" 
style="text-anchor:start;">B</text>
+    </g>
+    <g id="ButtonA" class="A Button">
+        <rect id="rect14" x="23.5" y="70.5" width="2.5" height="0.5" ry="0"/>
+        <circle id="path14" cx="24.5" cy="70.5" r="8"/>
+    </g>
+    <g id="ButtonB" class="B Button">
+        <path id="rect18" d="m 43 69.5 c 0 0.5 0 0.5 0 1 -0.5 0 -0.5 0 -1 0 0 
0 0 0.5 0 0.5 0.5 0 0.5 0 1 0 0 0.5 0 0.5 0 1 0 0 0.5 0 0.5 0 0 -0.5 0 -0.5 0 
-1 0.5 0 0.5 0 1 0 0 0 0 -0.5 0 -0.5 -0.5 0 -0.5 0 -1 0 0 -0.5 0 -0.5 0 -1 0 0 
-0.5 0 -0.5 0 z"/>
+        <ellipse id="circle14" cx="43.5" cy="70.5" rx="8" ry="8"/>
+    </g>
+    <g id="g10">
+        <path id="LeaderA" class="A Leader" d="M 24.5 60 V 41"/>
+        <text id="LabelA" class="A Label" x="22" y="39.5" 
style="text-anchor:start;">A</text>
+    </g>
+    <g id="ButtonG" class="G Button">
+        <rect id="rect21" x="24.5" y="140.5" width="2.5" height="0.5" ry="0"/>
+        <circle id="circle20" cx="26" cy="140.5" r="8"/>
+    </g>
+    <g id="ButtonH" class="H Button">
+        <circle id="circle21" cx="44.5" cy="140.5" r="8"/>
+        <path id="path21" d="m 44 139.5 c 0 0.5 0 0.5 0 1 -0.5 0 -0.5 0 -1 0 0 
0 0 0.5 0 0.5 0.5 0 0.5 0 1 0 0 0.5 0 0.5 0 1 0 0 0.5 0 0.5 0 0 -0.5 0 -0.5 0 
-1 0.5 0 0.5 0 1 0 0 0 0 -0.5 0 -0.5 -0.5 0 -0.5 0 -1 0 0 -0.5 0 -0.5 0 -1 0 0 
-0.5 0 -0.5 0 z"/>
+    </g>
+    <g id="g5">
+        <path id="LeaderG" class="G Leader" d="M 26 170 V 151"/>
+        <text id="LabelG" class="G Label" x="23" y="184.5" 
style="text-anchor:start;">G</text>
+    </g>
+    <g id="g4">
+        <path id="LeaderH" class="H Leader" d="M 44.5 170 V 151"/>
+        <text id="LabelH" class="H Label" x="41.5" y="184.5" 
style="text-anchor:start;">H</text>
+    </g>
+</svg>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/data/layouts/xp-pen-deco-se.svg 
new/libwacom-2.20.0/data/layouts/xp-pen-deco-se.svg
--- old/libwacom-2.19.1/data/layouts/xp-pen-deco-se.svg 1970-01-01 
01:00:00.000000000 +0100
+++ new/libwacom-2.20.0/data/layouts/xp-pen-deco-se.svg 2026-09-07 
08:48:53.000000000 +0200
@@ -0,0 +1,46 @@
+<?xml version='1.0' encoding='ASCII'?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd";>
+<svg xmlns="http://www.w3.org/2000/svg"; version="1.1" 
style="color:#000000;stroke:#7f7f7f;fill:none;stroke-width:.25;font-size:8" 
id="xp-pen-deco-se" width="721" height="427">
+  <title id="title">XP-Pen Deco SE</title>
+  <rect x="161" y="59" width="491" height="311"/>
+  <g>
+    <rect id="ButtonA" class="A Button" x="45" y="65" width="44" height="26"/>
+    <text id="LabelA" class="A Label" x="115.0" y="80" 
style="text-anchor:start;">A</text>
+    <path style="fill:none" d="M 100.0,78.0 H 107.0" id="LeaderA" 
class="Leader A"/>
+  </g>
+  <g>
+    <rect id="ButtonB" class="B Button" x="45" y="102" width="44" height="23"/>
+    <text id="LabelB" class="B Label" x="115.0" y="115.5" 
style="text-anchor:start;">B</text>
+    <path style="fill:none" d="M 100.0,113.5 H 107.0" id="LeaderB" 
class="Leader B"/>
+  </g>
+  <g>
+    <rect id="ButtonC" class="C Button" x="45" y="137" width="45" height="25"/>
+    <text id="LabelC" class="C Label" x="115.5" y="151.5" 
style="text-anchor:start;">C</text>
+    <path style="fill:none" d="M 100.5,149.5 H 107.5" id="LeaderC" 
class="Leader C"/>
+  </g>
+  <g>
+    <rect id="ButtonD" class="D Button" x="45" y="172" width="44" height="25"/>
+    <text id="LabelD" class="D Label" x="115.0" y="186.5" 
style="text-anchor:start;">D</text>
+    <path style="fill:none" d="M 100.0,184.5 H 107.0" id="LeaderD" 
class="Leader D"/>
+  </g>
+  <g>
+    <rect id="ButtonE" class="E Button" x="45" y="229" width="44" height="26"/>
+    <text id="LabelE" class="E Label" x="115.0" y="244" 
style="text-anchor:start;">E</text>
+    <path style="fill:none" d="M 100.0,242.0 H 107.0" id="LeaderE" 
class="Leader E"/>
+  </g>
+  <g>
+    <rect id="ButtonF" class="F Button" x="45" y="265" width="44" height="25"/>
+    <text id="LabelF" class="F Label" x="115.0" y="279.5" 
style="text-anchor:start;">F</text>
+    <path style="fill:none" d="M 100.0,277.5 H 107.0" id="LeaderF" 
class="Leader F"/>
+  </g>
+  <g>
+    <rect id="ButtonG" class="G Button" x="45" y="301" width="44" height="24"/>
+    <text id="LabelG" class="G Label" x="115.0" y="315" 
style="text-anchor:start;">G</text>
+    <path style="fill:none" d="M 100.0,313.0 H 107.0" id="LeaderG" 
class="Leader G"/>
+  </g>
+  <g>
+    <rect id="ButtonH" class="H Button" x="45" y="337" width="44" height="24"/>
+    <text id="LabelH" class="H Label" x="115.0" y="351" 
style="text-anchor:start;">H</text>
+    <path style="fill:none" d="M 100.0,349.0 H 107.0" id="LeaderH" 
class="Leader H"/>
+  </g>
+</svg>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/data/lenovo.stylus 
new/libwacom-2.20.0/data/lenovo.stylus
--- old/libwacom-2.19.1/data/lenovo.stylus      2026-07-20 05:12:17.000000000 
+0200
+++ new/libwacom-2.20.0/data/lenovo.stylus      2026-09-07 08:48:53.000000000 
+0200
@@ -33,3 +33,12 @@
 EraserType=Button
 Axes=Tilt;Pressure
 Type=Mobile
+
+[0x56a:0x8d15]
+# Lenovo ; VID_LENOVO     | 0x544b | BAT_CHRG
+Name=Lenovo Yoga Pen Gen 2 (2026)
+Group=isdv4-aes
+Buttons=1
+EraserType=Button
+Axes=Tilt;Pressure
+Type=Mobile
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/data/ugee-m808.tablet 
new/libwacom-2.20.0/data/ugee-m808.tablet
--- old/libwacom-2.19.1/data/ugee-m808.tablet   1970-01-01 01:00:00.000000000 
+0100
+++ new/libwacom-2.20.0/data/ugee-m808.tablet   2026-09-07 08:48:53.000000000 
+0200
@@ -0,0 +1,28 @@
+# Ugee
+# M808
+#
+# sysinfo.SRphWtpzlc.tar.gz
+#https://github.com/linuxwacom/wacom-hid-descriptors/issues/641
+
+[Device]
+Name=UGTABLET M808 Pen
+ModelName=M808
+DeviceMatch=usb|28bd|2903
+PairedIDs=
+Width=254
+Height=152
+IntegratedIn=
+Layout=ugee-m808.svg
+Styli=@generic-no-eraser
+
+[Features]
+Stylus=true
+Reversible=true
+Touch=false
+TouchSwitch=false
+NumRings=0
+NumStrips=0
+
+[Buttons]
+Left=A;B;C;D;E;F;G;H
+EvdevCodes=BTN_0;BTN_1;BTN_2;BTN_3;BTN_4;BTN_5;BTN_6;BTN_7
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/data/wacom-hid-53bb-pen.tablet 
new/libwacom-2.20.0/data/wacom-hid-53bb-pen.tablet
--- old/libwacom-2.19.1/data/wacom-hid-53bb-pen.tablet  1970-01-01 
01:00:00.000000000 +0100
+++ new/libwacom-2.20.0/data/wacom-hid-53bb-pen.tablet  2026-09-07 
08:48:53.000000000 +0200
@@ -0,0 +1,21 @@
+# Lenovo IdeaPad 5-14AHP9
+# Pen: Wacom HID 53BB Pen
+# Screen: Touchscreen 14", 1920x1200
+# Features: Stylus (1 button, eraser-invert), Touchscreen
+#
+# sysinfo.WlvlvxLwpX.tar.gz
+# https://github.com/linuxwacom/wacom-hid-descriptors/issues/636
+
+[Device]
+Name=Wacom HID 53BB Pen
+Class=ISDV4
+DeviceMatch=i2c|056a|53bb
+Width=300
+Height=190
+IntegratedIn=Display;System
+Styli=@isdv4-aes;
+
+[Features]
+Stylus=true
+Touch=true
+Buttons=0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/data/wacom-isdv4-526b.tablet 
new/libwacom-2.20.0/data/wacom-isdv4-526b.tablet
--- old/libwacom-2.19.1/data/wacom-isdv4-526b.tablet    1970-01-01 
01:00:00.000000000 +0100
+++ new/libwacom-2.20.0/data/wacom-isdv4-526b.tablet    2026-09-07 
08:48:53.000000000 +0200
@@ -0,0 +1,21 @@
+# Lenovo ThinkBook 14s Yoga ITL
+# Sensor Type: AES
+# Features: Touch (Integrated), Tilt
+# HW Resolution: 30931 x 17399 (2540 x 2540 lpi)
+#
+# Manually created; copied from wacom-isdv4-526a; Tarball: 
sysinfo.EKNM3yvMma.tar.gz
+# https://github.com/linuxwacom/wacom-hid-descriptors/issues/640
+
+[Device]
+Name=ISDv4 526b
+ModelName=
+DeviceMatch=i2c|056a|526b
+Class=ISDV4
+Width=305
+Height=178
+IntegratedIn=Display;System
+Styli=@isdv4-aes;
+
+[Features]
+Stylus=true
+Touch=true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/data/wacom-isdv4-544b.tablet 
new/libwacom-2.20.0/data/wacom-isdv4-544b.tablet
--- old/libwacom-2.19.1/data/wacom-isdv4-544b.tablet    1970-01-01 
01:00:00.000000000 +0100
+++ new/libwacom-2.20.0/data/wacom-isdv4-544b.tablet    2026-09-07 
08:48:53.000000000 +0200
@@ -0,0 +1,22 @@
+# Lenovo Yoga 9 2-in-1 14IPH11
+# Sensor Type: AES
+# Features: Touch (Integrated), Tilt
+# HW Resolution: 30182 x 18864 (2540 x 2540 lpi)
+#
+# sysinfo.EHmHQmxCz8
+# https://github.com/linuxwacom/wacom-hid-descriptors/issues/626
+
+[Device]
+Name=Wacom ISDv4 544b
+ModelName=
+DeviceMatch=i2c|056a|544b
+Class=ISDV4
+Width=301
+Height=188
+IntegratedIn=Display;System
+Styli=@isdv4-aes;
+
+[Features]
+Stylus=true
+Touch=true
+Buttons=0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/data/xp-pen-deco-se.tablet 
new/libwacom-2.20.0/data/xp-pen-deco-se.tablet
--- old/libwacom-2.19.1/data/xp-pen-deco-se.tablet      1970-01-01 
01:00:00.000000000 +0100
+++ new/libwacom-2.20.0/data/xp-pen-deco-se.tablet      2026-09-07 
08:48:53.000000000 +0200
@@ -0,0 +1,48 @@
+# XP-Pen
+# Deco SE
+# CT850B
+#
+# sysinfo.CPdD8fL4wX
+# https://github.com/linuxwacom/wacom-hid-descriptors/issues/638
+# sysinfo.3HxvPMRGCU
+# https://github.com/linuxwacom/wacom-hid-descriptors/issues/638
+#
+# Button Map:
+# (A=1, B=2, C=3, ...)
+#
+#      *------------------------------------*
+#      |                                    |
+#  A   |                                    |
+#  B   |                                    |
+#  C   |                                    |
+#  D   |                                    |
+#      |              TABLET                |
+#  E   |                                    |
+#  F   |                                    |
+#  G   |                                    |
+#  H   |                                    |
+#      |                                    |
+#      *------------------------------------*
+
+[Device]
+Name=XP-Pen Deco SE
+ModelName=CT850B
+DeviceMatch=usb|28bd|0949;bluetooth|28bd|0949;
+PairedIDs=
+Width=178
+Height=111
+IntegratedIn=
+Layout=xp-pen-deco-se.svg
+Styli=@generic-no-eraser
+
+[Features]
+Stylus=true
+Reversible=true
+Touch=false
+TouchSwitch=false
+NumRings=0
+NumStrips=0
+
+[Buttons]
+Left=A;B;C;D;E;F;G;H
+EvdevCodes=BTN_0;BTN_1;BTN_2;BTN_3;BTN_4;BTN_5;BTN_6;BTN_7
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/meson.build 
new/libwacom-2.20.0/meson.build
--- old/libwacom-2.19.1/meson.build     2026-07-20 05:12:17.000000000 +0200
+++ new/libwacom-2.20.0/meson.build     2026-09-07 08:48:53.000000000 +0200
@@ -1,5 +1,5 @@
 project('libwacom', 'c',
-        version: '2.19.1',
+        version: '2.20.0',
         license: 'HPND',
         default_options: [ 'c_std=gnu99', 'warning_level=2' ],
         meson_version: '>= 0.58.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/test/__init__.py 
new/libwacom-2.20.0/test/__init__.py
--- old/libwacom-2.19.1/test/__init__.py        2026-07-20 05:12:17.000000000 
+0200
+++ new/libwacom-2.20.0/test/__init__.py        2026-09-07 08:48:53.000000000 
+0200
@@ -1,5 +1,3 @@
-#!/usr/bin/env python3
-#
 # This file is formatted with ruff format
 #
 # Run with pytest:
@@ -20,16 +18,14 @@
 # - WacomDevice, WacomDatabase, ...: pythonic wrappers around the
 #   underlying C object.
 
-from ctypes import c_char_p, c_char, c_int, c_uint32, c_void_p
-from typing import Optional, Tuple, Type, List
-from dataclasses import dataclass
-from pathlib import Path
-
 import ctypes
 import enum
 import itertools
 import logging
-
+from ctypes import c_char, c_char_p, c_int, c_uint32, c_void_p
+from dataclasses import dataclass
+from pathlib import Path
+from typing import ClassVar
 
 logger = logging.getLogger(__name__)
 
@@ -40,8 +36,8 @@
 @dataclass
 class _Api:
     name: str
-    args: Tuple[Type[ctypes._SimpleCData], ...]
-    return_type: Optional[Type[ctypes._SimpleCData]]
+    args: tuple[type[ctypes._SimpleCData], ...]
+    return_type: type[ctypes._SimpleCData] | None
 
     @property
     def basename(self) -> str:
@@ -61,7 +57,7 @@
 class GlibC:
     _lib = None
 
-    _api_prototypes: List[_Api] = [
+    _api_prototypes: ClassVar[list[_Api]] = [
         _Api(name="free", args=(c_void_p,), return_type=None),
     ]
 
@@ -127,7 +123,7 @@
             cls._load()
         return cls
 
-    _api_prototypes: List[_Api] = [
+    _api_prototypes: ClassVar[list[_Api]] = [
         _Api(name="libwacom_error_new", args=(c_void_p,), 
return_type=c_void_p),
         _Api(name="libwacom_error_free", args=(c_void_p,), return_type=None),
         _Api(name="libwacom_error_get_code", args=(c_void_p,), 
return_type=c_int),
@@ -329,7 +325,7 @@
         ),
     ]
 
-    _enums: List[_Enum] = [
+    _enums: ClassVar[list[_Enum]] = [
         _Enum(name="WERROR_NONE", value=0),
         _Enum(name="WERROR_BAD_ALLOC", value=1),
         _Enum(name="WERROR_INVALID_PATH", value=2),
@@ -439,12 +435,12 @@
                 setattr(self, api.basename.removeprefix("match_"), 
wrapper(func))
 
     @property
-    def name(self) -> Optional[str]:
+    def name(self) -> str | None:
         name = self.get_name()
         return name.decode("utf-8") if name else None
 
     @property
-    def uniq(self) -> Optional[str]:
+    def uniq(self) -> str | None:
         uniq = self.get_uniq()
         return uniq.decode("utf-8") if uniq else None
 
@@ -482,23 +478,23 @@
                 setattr(self, api.basename.removeprefix("builder_"), 
wrapper(func))
 
     @property
-    def device_name(self) -> Optional[str]:
+    def device_name(self) -> str | None:
         return self._device_name
 
     @property
-    def match_name(self) -> Optional[str]:
+    def match_name(self) -> str | None:
         return self._match_name
 
     @property
-    def uniq(self) -> Optional[str]:
+    def uniq(self) -> str | None:
         return self._uniq
 
     @property
-    def bustype(self) -> Optional[WacomBustype]:
+    def bustype(self) -> WacomBustype | None:
         return self._bustype
 
     @property
-    def usbid(self) -> Optional[Tuple[int, int]]:
+    def usbid(self) -> tuple[int, int] | None:
         return self._usbid
 
     @bustype.setter
@@ -507,7 +503,7 @@
         self.set_bustype(bus.value)
 
     @usbid.setter
-    def usbid(self, usbid: Tuple[int, int]):
+    def usbid(self, usbid: tuple[int, int]):
         self._usbid = usbid
         self.set_usbid(usbid[0], usbid[1])
 
@@ -529,11 +525,11 @@
     @classmethod
     def create(
         cls,
-        device_name: Optional[str] = None,
-        match_name: Optional[str] = None,
-        uniq: Optional[str] = None,
-        usbid: Optional[Tuple[int, int]] = None,
-        bus: Optional[WacomBustype] = None,
+        device_name: str | None = None,
+        match_name: str | None = None,
+        uniq: str | None = None,
+        usbid: tuple[int, int] | None = None,
+        bus: WacomBustype | None = None,
     ) -> "WacomBuilder":
         lib = LibWacom.instance()
         builder = WacomBuilder(lib.builder_new())
@@ -660,7 +656,7 @@
     def eraser_type(self) -> WacomEraserType:
         return WacomEraserType(self.get_eraser_type())
 
-    def get_paired_styli(self) -> List["WacomStylus"]:
+    def get_paired_styli(self) -> list["WacomStylus"]:
         lib = LibWacom.instance()
         paired = lib.stylus_get_paired_styli(self.stylus, None)
         styli = [
@@ -705,7 +701,7 @@
         DIAL2_MODESWITCH = 1 << 11
 
         @staticmethod
-        def modeswitch_flags() -> List["WacomDevice.ButtonFlags"]:
+        def modeswitch_flags() -> list["WacomDevice.ButtonFlags"]:
             return [
                 WacomDevice.ButtonFlags.RING_MODESWITCH,
                 WacomDevice.ButtonFlags.RING2_MODESWITCH,
@@ -745,12 +741,12 @@
             val = getattr(lib, e.basename)
             setattr(self, e.basename, val)
 
-    def get_paired_device(self) -> Optional[WacomMatch]:
+    def get_paired_device(self) -> WacomMatch | None:
         lib = LibWacom.instance()
         match = lib.get_paired_device(self.device)
         return WacomMatch(match) if match else None
 
-    def get_matches(self) -> List[WacomMatch]:
+    def get_matches(self) -> list[WacomMatch]:
         lib = LibWacom.instance()
         matches = lib.get_matches(self.device)
 
@@ -759,7 +755,7 @@
             for m in itertools.takewhile(lambda ptr: ptr is not None, matches)
         ]
 
-    def get_styli(self) -> List[WacomStylus]:
+    def get_styli(self) -> list[WacomStylus]:
         lib = LibWacom.instance()
         styli = lib.get_styli(self.device, None)
 
@@ -774,7 +770,7 @@
             lib.destroy(self.device)
 
     @property
-    def paired_device(self) -> Optional[WacomMatch]:
+    def paired_device(self) -> WacomMatch | None:
         return self.get_paired_device()
 
     @property
@@ -864,11 +860,11 @@
         return self.get_matches()
 
     @property
-    def integration_flags(self) -> List[IntegrationFlags]:
+    def integration_flags(self) -> list[IntegrationFlags]:
         flags = self.get_integration_flags()
         return [f for f in WacomDevice.IntegrationFlags if f & flags != 0]
 
-    def button_flags(self, button: str) -> List[ButtonFlags]:
+    def button_flags(self, button: str) -> list[ButtonFlags]:
         flags = self.get_button_flag(button.encode("utf-8"))
         return [f for f in WacomDevice.ButtonFlags if f & flags != 0]
 
@@ -879,11 +875,11 @@
         mode = self.get_button_modeswitch_mode(button.encode("utf-8"))
         return WacomDevice.ModeSwitch(mode)
 
-    def button_led_group(self, button: str) -> List[ButtonFlags]:
+    def button_led_group(self, button: str) -> list[ButtonFlags]:
         return self.get_button_led_group(button.encode("utf-8"))
 
     @property
-    def status_leds(self) -> List["WacomStatusLed"]:
+    def status_leds(self) -> list["WacomStatusLed"]:
         nleds = c_int()
         leds = self.get_status_leds(ctypes.byref(nleds))
 
@@ -899,7 +895,7 @@
         NONE = 0x0
         GENERIC = 0x1
 
-    def __init__(self, path: Optional[Path] = None):
+    def __init__(self, path: Path | None = None):
         lib = LibWacom.instance()
         if path is None:
             self.db = lib.database_new()  # type: ignore
@@ -926,27 +922,27 @@
             lib = LibWacom.instance()
             lib.database_destroy(db)
 
-    def new_from_name(self, name: str) -> Optional[WacomDevice]:
+    def new_from_name(self, name: str) -> WacomDevice | None:
         device = self.libwacom_new_from_name(name.encode("utf-8"), 0)
         return WacomDevice(device) if device else None
 
     def new_from_path(
         self, path: str, fallback: Fallback = Fallback.NONE
-    ) -> Optional[WacomDevice]:
+    ) -> WacomDevice | None:
         device = self.libwacom_new_from_path(path.encode("utf-8"), fallback, 0)
         return WacomDevice(device) if device else None
 
-    def new_from_usbid(self, vid: int, pid: int) -> Optional[WacomDevice]:
+    def new_from_usbid(self, vid: int, pid: int) -> WacomDevice | None:
         device = self.libwacom_new_from_usbid(vid, pid, 0)
         return WacomDevice(device) if device else None
 
     def new_from_builder(
         self, builder: WacomBuilder, fallback: Fallback = Fallback.NONE
-    ) -> Optional[WacomDevice]:
+    ) -> WacomDevice | None:
         device = self.libwacom_new_from_builder(builder.builder, 
fallback.value, 0)
         return WacomDevice(device) if device else None
 
-    def list_devices(self) -> List[WacomDevice]:
+    def list_devices(self) -> list[WacomDevice]:
         devices = self.libwacom_list_devices_from_database(self.db, 0)
         devs = [
             WacomDevice(d, destroy=False)
@@ -955,7 +951,7 @@
         GlibC.instance().free(devices)
         return devs
 
-    def list_styli(self) -> List[WacomStylus]:
+    def list_styli(self) -> list[WacomStylus]:
         styli = self.libwacom_list_styli_from_database(self.db, 0)
         result = [
             WacomStylus(s)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/test/conftest.py 
new/libwacom-2.20.0/test/conftest.py
--- old/libwacom-2.19.1/test/conftest.py        2026-07-20 05:12:17.000000000 
+0200
+++ new/libwacom-2.20.0/test/conftest.py        2026-09-07 08:48:53.000000000 
+0200
@@ -1,11 +1,9 @@
-#!/usr/bin/env python3
-#
 # This file is formatted with ruff format
-#
 
-from pathlib import Path
 import logging
 import os
+from pathlib import Path
+
 import pytest
 
 from . import WacomDatabase
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/test/test_data_files.py 
new/libwacom-2.20.0/test/test_data_files.py
--- old/libwacom-2.19.1/test/test_data_files.py 2026-07-20 05:12:17.000000000 
+0200
+++ new/libwacom-2.20.0/test/test_data_files.py 2026-09-07 08:48:53.000000000 
+0200
@@ -7,7 +7,6 @@
 import re
 from pathlib import Path
 
-
 WACOM_RECEIVER_USBIDS = [
     (0x56A, 0x84),
 ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/test/test_libwacom.py 
new/libwacom-2.20.0/test/test_libwacom.py
--- old/libwacom-2.19.1/test/test_libwacom.py   2026-07-20 05:12:17.000000000 
+0200
+++ new/libwacom-2.20.0/test/test_libwacom.py   2026-09-07 08:48:53.000000000 
+0200
@@ -1,14 +1,12 @@
-#!/usr/bin/env python3
-#
 # This file is formatted with ruff format
 
+import ctypes
+import logging
+import string
 from configparser import ConfigParser
 from dataclasses import dataclass, field
 
-import ctypes
-import logging
 import pytest
-import string
 
 from . import (
     WacomAxisType,
@@ -97,8 +95,9 @@
 
         if logger.getEffectiveLevel() == logging.DEBUG:
             logger.debug(f"{dir}/{filename}:")
-            for line in open(dir / filename).readlines():
-                logger.debug(f"  {line.rstrip()}")
+            with open(dir / filename) as f:
+                for line in f:
+                    logger.debug(f"  {line.rstrip()}")
 
 
 @dataclass
@@ -148,8 +147,9 @@
 
         if logger.getEffectiveLevel() == logging.DEBUG:
             logger.debug(f"{filename}:")
-            for line in open(filename).readlines():
-                logger.debug(f"  {line.rstrip()}")
+            with open(filename) as f:
+                for line in f:
+                    logger.debug(f"  {line.rstrip()}")
 
 
 @pytest.fixture()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/test/test_svg.py 
new/libwacom-2.20.0/test/test_svg.py
--- old/libwacom-2.19.1/test/test_svg.py        2026-07-20 05:12:17.000000000 
+0200
+++ new/libwacom-2.20.0/test/test_svg.py        2026-09-07 08:48:53.000000000 
+0200
@@ -1,15 +1,12 @@
-#!/usr/bin/env python3
-#
 # Run with pytest
 
-from typing import Optional, List
-from pathlib import Path
-from dataclasses import dataclass
-import xml.etree
-
-import os
 import logging
+import os
 import string
+import xml.etree
+from dataclasses import dataclass
+from pathlib import Path
+
 import pytest
 
 from . import WacomDatabase, WacomDevice
@@ -45,7 +42,7 @@
     svg: xml.etree.ElementTree
     is_autogenerated: bool
 
-    def has_item(self, id: str, classes: Optional[List[str]] = None):
+    def has_item(self, id: str, classes: list[str] | None = None):
         root = self.svg.getroot()
         nodes = root.findall(f".//*[@id='{id}']")
         assert nodes, f"Failed to find required element with id {id}"
@@ -87,7 +84,7 @@
     ]
     devices = sorted(devices, key=lambda d: d.name)
 
-    def filenames(devices: List[SvgDevice]) -> List[str]:
+    def filenames(devices: list[SvgDevice]) -> list[str]:
         return [Path(d.device.layout_filename).name for d in devices]
 
     if "svgdevice" in metafunc.fixturenames:
@@ -130,7 +127,7 @@
     )
 
 
-def has_item(root, id: str, classes: Optional[List[str]] = None):
+def has_item(root, id: str, classes: list[str] | None = None):
     nodes = root.findall(f".//*[@id='{id}']")
     assert nodes, f"Failed to find required element with id {id}"
     assert len(nodes) == 1, f"Expected on element with id {id}, have 
{len(nodes)}"
@@ -193,7 +190,7 @@
     except AssertionError as e:
         if stripdevice.is_autogenerated:
             pytest.skip(f"Autogenerated device has errors in SVG: {e}")
-        raise e
+        raise
 
 
 def test_svg_dials(dialdevice):
@@ -224,7 +221,7 @@
     except AssertionError as e:
         if dialdevice.is_autogenerated:
             pytest.skip(f"Autogenerated device has errors in SVG: {e}")
-        raise e
+        raise
 
 
 def test_svg_button(buttondevice):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/test/test_udev_rules.py 
new/libwacom-2.20.0/test/test_udev_rules.py
--- old/libwacom-2.19.1/test/test_udev_rules.py 2026-07-20 05:12:17.000000000 
+0200
+++ new/libwacom-2.20.0/test/test_udev_rules.py 2026-09-07 08:48:53.000000000 
+0200
@@ -1,5 +1,3 @@
-#!/usr/bin/env python3
-#
 # This test will only work where /dev/uinput is available.
 # This test will reload the hwdb and udev rules
 #
@@ -9,13 +7,16 @@
 # - check if that device has the udev properties set we expect
 
 import configparser
+import logging
 import os
+import shutil
+import subprocess
+import sys
 from pathlib import Path
+
 import pytest
-import logging
-import sys
-import subprocess
-import shutil
+
+logger = logging.getLogger(__name__)
 
 
 @pytest.fixture(scope="session", autouse=True)
@@ -42,13 +43,13 @@
 
         subprocess.run(["systemd-hwdb", "update"], check=True)
 
-    except (IOError, FileNotFoundError, subprocess.CalledProcessError) as e:
+    except (OSError, FileNotFoundError, subprocess.CalledProcessError) as e:
         # If any of the commands above are not found (most likely the system
         # simply does not use systemd), just skip.
-        logging.critical(f"{e}")
+        logger.critical(f"{e}")
         pytest.skip(f"Skipping test: {e}")
-    except Exception as e:
-        logging.critical(f"{e}")
+    except Exception as e:  # noqa: BLE001
+        logger.critical(f"{e}")
         pytest.fail(f"Aborting test: {e}")
 
 
@@ -67,7 +68,7 @@
             want_pad = config["Device"].get("Buttons", 0)
             want_finger = config["Features"].get("Touch") == "true"
             integrated_in = config["Device"].get("IntegratedIn", "").split(";")
-            is_touchscreen = set(integrated_in) & set(["Display", "System"])
+            is_touchscreen = set(integrated_in) & {"Display", "System"}
 
             for match in config["Device"]["DeviceMatch"].split(";"):
                 if not match or match == "generic":
@@ -80,16 +81,16 @@
                 try:
                     vid = int(vid, 16)
                     pid = int(pid, 16)
-                except ValueError as e:
+                except ValueError:
                     print(f"Invalid vid/pid in {match} in {f}", 
file=sys.stderr)
-                    raise e
+                    raise
 
                 if bus == "usb":
                     bus = 0x3
                 elif bus == "bluetooth":
                     bus = 0x5
 
-                class Tablet(object):
+                class Tablet:
                     def __init__(self, name, bus, vid, pid, 
is_touchscreen=False):
                         self.name = name
                         self.bus = bus
@@ -119,14 +120,14 @@
     # Note: the actual name doesn't really matter, all our hwdb files use 
either "*"
     # or "* Finger", etc. It does matter for that "Finger" suffix though.
     query = 
f"libwacom:name:{tablet.name}:input:b{tablet.bus:04X}v{tablet.vid:04X}p{tablet.pid:04X}"
-    logging.debug(query)
+    logger.debug(query)
 
     r = subprocess.run(
         ["systemd-hwdb", "query", query], check=True, capture_output=True
     )
     stdout = r.stdout.decode("utf-8").strip()
     assert stdout, f"No output recorded for query {query}"
-    logging.debug(stdout)
+    logger.debug(stdout)
     props = {}
     for line in filter(lambda line: len(line) > 1, stdout.split("\n")):
         print(line)
@@ -149,6 +150,7 @@
             assert "ID_INPUT_TOUCHPAD" in props
 
     # For the Wacom Bamboo Pad we check for "Pad Pad" in the device name
-    if "Pad" in tablet.name:
-        if "Wacom Bamboo Pad" not in tablet.name or "Pad Pad" in tablet.name:
-            assert "ID_INPUT_TABLET_PAD" in props
+    if "Pad" in tablet.name and (
+        "Wacom Bamboo Pad" not in tablet.name or "Pad Pad" in tablet.name
+    ):
+        assert "ID_INPUT_TABLET_PAD" in props
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/tools/clean_svg.py 
new/libwacom-2.20.0/tools/clean_svg.py
--- old/libwacom-2.19.1/tools/clean_svg.py      2026-07-20 05:12:17.000000000 
+0200
+++ new/libwacom-2.20.0/tools/clean_svg.py      2026-09-07 08:48:53.000000000 
+0200
@@ -20,8 +20,8 @@
 
 import logging
 import sys
-from pathlib import Path
 from argparse import ArgumentParser
+from pathlib import Path
 from xml.etree import ElementTree as ET
 
 logging.basicConfig(level=logging.INFO)
@@ -84,8 +84,7 @@
 
 
 def remove_non_svg_nodes_and_strip_namespace(root):
-    if root.tag.startswith(BRACKETS_NAMESPACE):
-        root.tag = root.tag[len(BRACKETS_NAMESPACE) :]
+    root.tag = root.tag.removeprefix(BRACKETS_NAMESPACE)
     for elem in list(root):
         if (
             not elem.tag.startswith(BRACKETS_NAMESPACE)
@@ -109,7 +108,7 @@
         values = transform[len(TRANSLATE) + 1 : -1].split(",")
         try:
             x, y = float(values[0]), float(values[1])
-        except Exception:
+        except Exception:  # noqa: BLE001
             return
 
         apply_translation(node, 1, 0, 0, 1, x, y)
@@ -117,7 +116,7 @@
         values = transform[len(MATRIX) + 1 : -1].split(",")
         try:
             a, b, c, d, e, f = [float(value.strip()) for value in values]
-        except Exception:
+        except Exception:  # noqa: BLE001
             return
         apply_translation(node, a, b, c, d, e, f)
         apply_scaling(node, a, d)
@@ -137,8 +136,8 @@
         new_y = x * b + y * d + 1 * f
         node.attrib[x_attr] = str(new_x)
         node.attrib[y_attr] = str(new_y)
-    except Exception:
-        pass
+    except Exception:  # noqa:BLE001
+        print(f"Failed to apply translation: {e}", file=sys.stderr)
 
 
 def apply_translation_to_path(node, x, y):
@@ -183,8 +182,8 @@
         h = float(node.attrib[h_attr])
         node.attrib[w_attr] = str(w * x)
         node.attrib[h_attr] = str(h * y)
-    except Exception:
-        pass
+    except Exception as e:  # noqa: BLE001
+        print(f"Failed to apply scaling: {e}", file=sys.stderr)
 
 
 def to_string_rec(node, level=0):
@@ -202,7 +201,7 @@
     for attr in get_node_attrs_sorted(node):
         attr_value = node.attrib.get(attr)
         if attr_value is not None:
-            attribs.append(indent + '   %s="%s"' % (attr, attr_value))
+            attribs.append(indent + f'   {attr}="{attr_value}"')
 
     string = indent + "<" + tag_name + "".join(attribs)
     if len(node) or node.text:
@@ -215,7 +214,7 @@
             for child in get_node_children_sorted(node):
                 string += to_string_rec(child, level + 1)
             string += indent
-        string += "</%s>" % tag_name
+        string += f"</{tag_name}>"
     else:
         string += " />"
     return string
@@ -269,20 +268,20 @@
         if child.tag == "rect" or child.tag == "circle":
             if button_assigned:
                 continue
-            child.attrib["id"] = "Button%s" % _id
-            child.attrib["class"] = "%s Button" % _id
+            child.attrib["id"] = f"Button{_id}"
+            child.attrib["class"] = f"{_id} Button"
             button_assigned = True
         elif child.tag == "path":
             if path_assigned:
                 continue
-            child.attrib["id"] = "Leader%s" % _id
-            child.attrib["class"] = "%s Leader" % _id
+            child.attrib["id"] = f"Leader{_id}"
+            child.attrib["class"] = f"{_id} Leader"
             path_assigned = True
         elif child.tag == "text":
             if label_assigned:
                 continue
-            child.attrib["id"] = "Label%s" % _id
-            child.attrib["class"] = "%s Label" % _id
+            child.attrib["id"] = f"Label{_id}"
+            child.attrib["class"] = f"{_id} Label"
             child.text = _id
             label_assigned = True
 
@@ -359,7 +358,7 @@
     ET.register_namespace("", NAMESPACE)
     try:
         tree = ET.parse(svgfile)
-    except Exception as e:
+    except Exception as e:  # noqa: BLE001
         sys.stderr.write(str(e) + "\n")
         sys.exit(1)
     root = tree.getroot()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/tools/libwacom-update-db.py 
new/libwacom-2.20.0/tools/libwacom-update-db.py
--- old/libwacom-2.19.1/tools/libwacom-update-db.py     2026-07-20 
05:12:17.000000000 +0200
+++ new/libwacom-2.20.0/tools/libwacom-update-db.py     2026-09-07 
08:48:53.000000000 +0200
@@ -24,8 +24,8 @@
 import argparse
 import configparser
 import os
-import sys
 import subprocess
+import sys
 import tempfile
 from pathlib import Path
 
@@ -34,7 +34,7 @@
     return Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config")) / 
"libwacom"
 
 
-class Tablet(object):
+class Tablet:
     def __init__(self, name, bus, vid, pid):
         self.name = name
         self.bus = bus
@@ -46,7 +46,7 @@
 
         # We have everything in strings so let's use that for sorting later
         # This will sort bluetooth before usb but meh
-        self.cmpstr = ":".join((bus, vid, pid, name))
+        self.cmpstr = f"{bus}:{vid}:{pid}:{name}"
 
     def __lt__(self, other):
         return self.cmpstr < other.cmpstr
@@ -137,9 +137,9 @@
                 # for tablets with re-used usbids and that doesn't matter
                 try:
                     bus, vid, pid, *_ = match.split("|")
-                except ValueError as e:
+                except ValueError:
                     print(f"Failed to process match {match} in {file}", 
file=sys.stderr)
-                    raise e
+                    raise
 
                 name = config["Device"]["Name"]
                 t = Tablet(name, bus, vid, pid)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/tools/list-local-devices.c 
new/libwacom-2.20.0/tools/list-local-devices.c
--- old/libwacom-2.19.1/tools/list-local-devices.c      2026-07-20 
05:12:17.000000000 +0200
+++ new/libwacom-2.20.0/tools/list-local-devices.c      2026-09-07 
08:48:53.000000000 +0200
@@ -344,6 +344,9 @@
 
        dir = g_dir_open("/dev/input", 0, &error);
        if (!dir) {
+               /* Probably running in a container */
+               if (error->domain == G_FILE_ERROR && error->code == 
G_FILE_ERROR_NOENT)
+                       return EXIT_SUCCESS;
                fprintf(stderr, "%s\n", error->message);
                return EXIT_FAILURE;
        }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/libwacom-2.19.1/tools/show-stylus.py 
new/libwacom-2.20.0/tools/show-stylus.py
--- old/libwacom-2.19.1/tools/show-stylus.py    2026-07-20 05:12:17.000000000 
+0200
+++ new/libwacom-2.20.0/tools/show-stylus.py    2026-09-07 08:48:53.000000000 
+0200
@@ -30,7 +30,7 @@
     import libevdev
     import pyudev
 except ModuleNotFoundError as e:
-    print("Error: {}".format(str(e)), file=sys.stderr)
+    print(f"Error: {e!s}", file=sys.stderr)
     print(
         "One or more python modules are missing. Please install those "
         "modules and re-run this tool."
@@ -92,9 +92,7 @@
         if not d.absinfo[libevdev.EV_ABS.ABS_MISC]:
             die("Device only supports generic styli")
 
-        tool_bits = set(
-            c for c in libevdev.EV_KEY.codes if c.name.startswith("BTN_TOOL_")
-        )
+        tool_bits = {c for c in libevdev.EV_KEY.codes if 
c.name.startswith("BTN_TOOL_")}
         styli = {}  # dict of (type, serial) = proximity_state
         current_type, current_serial = 0, 0
         in_prox = False
@@ -132,7 +130,7 @@
         except KeyboardInterrupt:
             print("Terminating")
 
-        return [s[0] for s in styli.keys()]
+        return [s[0] for s in styli]
 
 
 def load_data_files():
@@ -164,7 +162,7 @@
         for stylus_id in config.sections():
             ids = stylus_id.split(":")
             if len(ids) > 1:
-                _, sid = map(lambda x: int(x, 16), ids)
+                _, sid = (int(x, 16) for x in ids)
             else:
                 _ = 0x56A  # vid
                 sid = int(ids[0], 16)

Reply via email to