From 0bb693c864623425bb33b8ef29ba21170765d6d2 Mon Sep 17 00:00:00 2001 From: Jayant-Kernel Date: Thu, 9 Apr 2026 12:11:08 +0530 Subject: [PATCH 1/4] qubes.GetImageRGBA: fix SVG conversion for images without explicit dimensions --- qubes-rpc/qubes.GetImageRGBA | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/qubes-rpc/qubes.GetImageRGBA b/qubes-rpc/qubes.GetImageRGBA index b56ab02c..aa8b3aab 100755 --- a/qubes-rpc/qubes.GetImageRGBA +++ b/qubes-rpc/qubes.GetImageRGBA @@ -21,22 +21,29 @@ elif ! [ -r "${filename}" ]; then exit 1 fi -s="$(gm identify -format '%w %h %m' "$filename")" -w="$(echo "$s"|cut -d " " -f 1)" -h="$(echo "$s"|cut -d " " -f 2)" -m="$(echo "$s"|cut -d " " -f 3)" +read -r w h m << EOF +$(gm identify -format '%w %h %m' "$filename") +EOF if [ "$m" = SVG ]; then tmpfile2="$(mktemp /tmp/qimg-XXXXXXXX.png)" - rsvg-convert -w "$w" -h "$h" -o "$tmpfile2" "$filename" + if [ -n "$w" ] && [ "$w" -gt 0 ] && [ -n "$h" ] && [ "$h" -gt 0 ]; then + rsvg-convert -w "$w" -h "$h" -o "$tmpfile2" "$filename" + else + rsvg-convert -o "$tmpfile2" "$filename" + # re-read dimensions from the rendered PNG since SVG had no explicit size + read -r w h << EOF +$(gm identify -format '%w %h' "$tmpfile2") +EOF + fi # downscale the image if necessary if [ -n "$forcemaxsize" ] && \ { [ "$w" -gt "$forcemaxsize" ] || [ "$h" -gt "$forcemaxsize" ]; }; then gm convert "$tmpfile2" -scale "${forcemaxsize}x${forcemaxsize}" "$tmpfile2" fi # read the size again, because icon may not be a square or could have changed with convert - s="$(gm identify -format '%w %h' "$tmpfile2")" - w="$(echo "$s"|cut -d " " -f 1)" - h="$(echo "$s"|cut -d " " -f 2)" + read -r w h << EOF +$(gm identify -format '%w %h' "$tmpfile2") +EOF filename="$tmpfile2" fi echo "$w $h" From d17f762285f5e31f01dd294bf8e696d788019617 Mon Sep 17 00:00:00 2001 From: Jayant-Kernel Date: Wed, 22 Apr 2026 12:19:09 +0530 Subject: [PATCH 2/4] qubes.GetImageRGBA: add unit tests for SVG conversion Add unit tests covering: - SVG with explicit width/height (normal case) - SVG with only viewBox, no explicit dimensions (regression for #9145) - Non-existent file input (error handling) Tests live in qubesagent/ alongside existing tests and run without installing to dom0. --- qubesagent/test_getimgrgba.py | 113 ++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 qubesagent/test_getimgrgba.py diff --git a/qubesagent/test_getimgrgba.py b/qubesagent/test_getimgrgba.py new file mode 100644 index 00000000..b4c81bb2 --- /dev/null +++ b/qubesagent/test_getimgrgba.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# The Qubes OS Project, https://www.qubes-os.org/ +# +# Copyright (C) 2026 Jayant Saxena +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +"""Unit tests for qubes.GetImageRGBA script. + +Tests the fix for QubesOS/qubes-issues#9145: SVG images without explicit +width/height attributes should be converted successfully. +""" + +import os +import subprocess +import tempfile +import unittest + +SCRIPT_PATH = os.path.join( + os.path.dirname(__file__), '../qubes-rpc/qubes.GetImageRGBA') + +# Minimal SVG with explicit width/height +SVG_WITH_DIMENSIONS = b'''\ + + + + +''' + +# SVG with only viewBox, no explicit width/height (reproduces issue #9145) +SVG_WITHOUT_DIMENSIONS = b'''\ + + + + +''' + + +def _run_script(svg_content): + """Run qubes.GetImageRGBA with given SVG content, return (returncode, stdout, stderr).""" + with tempfile.NamedTemporaryFile(suffix='.svg', delete=False) as f: + f.write(svg_content) + svg_path = f.name + try: + result = subprocess.run( + [SCRIPT_PATH], + input=svg_path.encode() + b'\n', + capture_output=True, + timeout=10, + ) + return result.returncode, result.stdout, result.stderr + finally: + os.unlink(svg_path) + + +@unittest.skipUnless( + os.path.exists(SCRIPT_PATH), 'qubes.GetImageRGBA script not found') +class TestGetImageRGBA(unittest.TestCase): + + def _assert_valid_rgba_output(self, stdout, stderr): + """Assert stdout contains valid 'W H\\nRGBA_DATA' output.""" + self.assertEqual(b'', stderr, f'Unexpected stderr: {stderr.decode()}') + lines = stdout.split(b'\n', 1) + self.assertGreaterEqual(len(lines), 1) + dims = lines[0].decode().split() + self.assertEqual(len(dims), 2, f'Expected "W H" on first line, got: {lines[0]}') + width, height = int(dims[0]), int(dims[1]) + self.assertGreater(width, 0) + self.assertGreater(height, 0) + if len(lines) > 1: + expected = width * height * 4 + self.assertGreaterEqual(len(lines[1]), expected) + return width, height + + def test_svg_with_explicit_dimensions(self): + """SVG with explicit width/height should convert successfully.""" + rc, stdout, stderr = _run_script(SVG_WITH_DIMENSIONS) + self.assertEqual(rc, 0, f'Script failed: {stderr.decode()}') + self._assert_valid_rgba_output(stdout, b'') + + def test_svg_without_dimensions(self): + """SVG without explicit width/height (only viewBox) should convert successfully. + + Regression test for QubesOS/qubes-issues#9145. + """ + rc, stdout, stderr = _run_script(SVG_WITHOUT_DIMENSIONS) + self.assertEqual(rc, 0, f'Script failed: {stderr.decode()}') + self._assert_valid_rgba_output(stdout, b'') + + def test_nonexistent_file(self): + """Non-existent file should cause script to exit with non-zero code.""" + result = subprocess.run( + [SCRIPT_PATH], + input=b'/nonexistent/file.svg\n', + capture_output=True, + timeout=10, + ) + self.assertNotEqual(result.returncode, 0) + + +if __name__ == '__main__': + unittest.main() From 20fc1774793be8bc22d2af769c095a56401595ea Mon Sep 17 00:00:00 2001 From: Jayant-Kernel Date: Wed, 22 Apr 2026 12:50:12 +0530 Subject: [PATCH 3/4] qubesagent/test_getimgrgba: skip when tools unavailable gm and rsvg-convert may not be present in all test environments. Skip the tests gracefully instead of failing. --- qubesagent/test_getimgrgba.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qubesagent/test_getimgrgba.py b/qubesagent/test_getimgrgba.py index b4c81bb2..48812fd8 100644 --- a/qubesagent/test_getimgrgba.py +++ b/qubesagent/test_getimgrgba.py @@ -23,6 +23,7 @@ """ import os +import shutil import subprocess import tempfile import unittest @@ -66,6 +67,10 @@ def _run_script(svg_content): @unittest.skipUnless( os.path.exists(SCRIPT_PATH), 'qubes.GetImageRGBA script not found') +@unittest.skipUnless( + shutil.which('gm'), 'GraphicsMagick (gm) not installed') +@unittest.skipUnless( + shutil.which('rsvg-convert'), 'rsvg-convert not installed') class TestGetImageRGBA(unittest.TestCase): def _assert_valid_rgba_output(self, stdout, stderr): From 437a0a88d2b5cd7799d8b0c737530fef72ec5b4f Mon Sep 17 00:00:00 2001 From: Jayant-Kernel Date: Fri, 24 Apr 2026 01:11:09 +0530 Subject: [PATCH 4/4] rpm_spec: add test_getimgrgba to %files New test file was installed by setup.py but missing from the RPM spec, causing Fedora builds to fail with unpackaged file error. --- rpm_spec/core-agent.spec.in | 1 + 1 file changed, 1 insertion(+) diff --git a/rpm_spec/core-agent.spec.in b/rpm_spec/core-agent.spec.in index a474c9ee..5e6c9f4a 100644 --- a/rpm_spec/core-agent.spec.in +++ b/rpm_spec/core-agent.spec.in @@ -1079,6 +1079,7 @@ rm -f %{name}-%{version} %{python3_sitelib}/qubesagent/xdg.py* %{python3_sitelib}/qubesagent/test_xdg.py* %{python3_sitelib}/qubesagent/test_tools.py* +%{python3_sitelib}/qubesagent/test_getimgrgba.py* %dir /usr/share/qubes/mime-override /usr/share/qubes/mime-override/globs