Skip to content

Commit 9866163

Browse files
committed
Merge remote-tracking branch 'origin/pr/646'
* origin/pr/646: rpm_spec: add test_getimgrgba to %files qubesagent/test_getimgrgba: skip when tools unavailable qubes.GetImageRGBA: add unit tests for SVG conversion qubes.GetImageRGBA: fix SVG conversion for images without explicit dimensions Pull request description: SVGs that specify dimensions only via viewBox (no explicit pixel width/height attributes) cause `gm identify` to return 0 for both width and height. Passing `-w 0 -h 0` to `rsvg-convert` produces an empty or corrupt PNG, which then causes the second `gm identify` to fail with "No decode delegate for this image format". When `gm identify` returns zero or empty dimensions for an SVG, let `rsvg-convert` determine the natural size from the viewBox instead. The actual dimensions are then read from the resulting PNG by the existing `gm identify` call that follows. Converting the Qubes logo (which has explicit pixel dimensions) is unaffected. Fixes: QubesOS/qubes-issues#9145
2 parents b8cd6ab + 437a0a8 commit 9866163

3 files changed

Lines changed: 134 additions & 8 deletions

File tree

qubes-rpc/qubes.GetImageRGBA

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,29 @@ elif ! [ -r "${filename}" ]; then
2121
exit 1
2222
fi
2323

24-
s="$(gm identify -format '%w %h %m' "$filename")"
25-
w="$(echo "$s"|cut -d " " -f 1)"
26-
h="$(echo "$s"|cut -d " " -f 2)"
27-
m="$(echo "$s"|cut -d " " -f 3)"
24+
read -r w h m << EOF
25+
$(gm identify -format '%w %h %m' "$filename")
26+
EOF
2827
if [ "$m" = SVG ]; then
2928
tmpfile2="$(mktemp /tmp/qimg-XXXXXXXX.png)"
30-
rsvg-convert -w "$w" -h "$h" -o "$tmpfile2" "$filename"
29+
if [ -n "$w" ] && [ "$w" -gt 0 ] && [ -n "$h" ] && [ "$h" -gt 0 ]; then
30+
rsvg-convert -w "$w" -h "$h" -o "$tmpfile2" "$filename"
31+
else
32+
rsvg-convert -o "$tmpfile2" "$filename"
33+
# re-read dimensions from the rendered PNG since SVG had no explicit size
34+
read -r w h << EOF
35+
$(gm identify -format '%w %h' "$tmpfile2")
36+
EOF
37+
fi
3138
# downscale the image if necessary
3239
if [ -n "$forcemaxsize" ] && \
3340
{ [ "$w" -gt "$forcemaxsize" ] || [ "$h" -gt "$forcemaxsize" ]; }; then
3441
gm convert "$tmpfile2" -scale "${forcemaxsize}x${forcemaxsize}" "$tmpfile2"
3542
fi
3643
# read the size again, because icon may not be a square or could have changed with convert
37-
s="$(gm identify -format '%w %h' "$tmpfile2")"
38-
w="$(echo "$s"|cut -d " " -f 1)"
39-
h="$(echo "$s"|cut -d " " -f 2)"
44+
read -r w h << EOF
45+
$(gm identify -format '%w %h' "$tmpfile2")
46+
EOF
4047
filename="$tmpfile2"
4148
fi
4249
echo "$w $h"

qubesagent/test_getimgrgba.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#!/usr/bin/env python3
2+
# The Qubes OS Project, https://www.qubes-os.org/
3+
#
4+
# Copyright (C) 2026 Jayant Saxena <jayantmcom@gmail.com>
5+
#
6+
# This program is free software; you can redistribute it and/or
7+
# modify it under the terms of the GNU General Public License
8+
# as published by the Free Software Foundation; either version 2
9+
# of the License, or (at your option) any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with this program; if not, write to the Free Software
18+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
"""Unit tests for qubes.GetImageRGBA script.
20+
21+
Tests the fix for QubesOS/qubes-issues#9145: SVG images without explicit
22+
width/height attributes should be converted successfully.
23+
"""
24+
25+
import os
26+
import shutil
27+
import subprocess
28+
import tempfile
29+
import unittest
30+
31+
SCRIPT_PATH = os.path.join(
32+
os.path.dirname(__file__), '../qubes-rpc/qubes.GetImageRGBA')
33+
34+
# Minimal SVG with explicit width/height
35+
SVG_WITH_DIMENSIONS = b'''\
36+
<?xml version="1.0" encoding="UTF-8"?>
37+
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10">
38+
<rect width="10" height="10" fill="#3366cc"/>
39+
</svg>
40+
'''
41+
42+
# SVG with only viewBox, no explicit width/height (reproduces issue #9145)
43+
SVG_WITHOUT_DIMENSIONS = b'''\
44+
<?xml version="1.0" encoding="UTF-8"?>
45+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
46+
<rect width="10" height="10" fill="#3366cc"/>
47+
</svg>
48+
'''
49+
50+
51+
def _run_script(svg_content):
52+
"""Run qubes.GetImageRGBA with given SVG content, return (returncode, stdout, stderr)."""
53+
with tempfile.NamedTemporaryFile(suffix='.svg', delete=False) as f:
54+
f.write(svg_content)
55+
svg_path = f.name
56+
try:
57+
result = subprocess.run(
58+
[SCRIPT_PATH],
59+
input=svg_path.encode() + b'\n',
60+
capture_output=True,
61+
timeout=10,
62+
)
63+
return result.returncode, result.stdout, result.stderr
64+
finally:
65+
os.unlink(svg_path)
66+
67+
68+
@unittest.skipUnless(
69+
os.path.exists(SCRIPT_PATH), 'qubes.GetImageRGBA script not found')
70+
@unittest.skipUnless(
71+
shutil.which('gm'), 'GraphicsMagick (gm) not installed')
72+
@unittest.skipUnless(
73+
shutil.which('rsvg-convert'), 'rsvg-convert not installed')
74+
class TestGetImageRGBA(unittest.TestCase):
75+
76+
def _assert_valid_rgba_output(self, stdout, stderr):
77+
"""Assert stdout contains valid 'W H\\nRGBA_DATA' output."""
78+
self.assertEqual(b'', stderr, f'Unexpected stderr: {stderr.decode()}')
79+
lines = stdout.split(b'\n', 1)
80+
self.assertGreaterEqual(len(lines), 1)
81+
dims = lines[0].decode().split()
82+
self.assertEqual(len(dims), 2, f'Expected "W H" on first line, got: {lines[0]}')
83+
width, height = int(dims[0]), int(dims[1])
84+
self.assertGreater(width, 0)
85+
self.assertGreater(height, 0)
86+
if len(lines) > 1:
87+
expected = width * height * 4
88+
self.assertGreaterEqual(len(lines[1]), expected)
89+
return width, height
90+
91+
def test_svg_with_explicit_dimensions(self):
92+
"""SVG with explicit width/height should convert successfully."""
93+
rc, stdout, stderr = _run_script(SVG_WITH_DIMENSIONS)
94+
self.assertEqual(rc, 0, f'Script failed: {stderr.decode()}')
95+
self._assert_valid_rgba_output(stdout, b'')
96+
97+
def test_svg_without_dimensions(self):
98+
"""SVG without explicit width/height (only viewBox) should convert successfully.
99+
100+
Regression test for QubesOS/qubes-issues#9145.
101+
"""
102+
rc, stdout, stderr = _run_script(SVG_WITHOUT_DIMENSIONS)
103+
self.assertEqual(rc, 0, f'Script failed: {stderr.decode()}')
104+
self._assert_valid_rgba_output(stdout, b'')
105+
106+
def test_nonexistent_file(self):
107+
"""Non-existent file should cause script to exit with non-zero code."""
108+
result = subprocess.run(
109+
[SCRIPT_PATH],
110+
input=b'/nonexistent/file.svg\n',
111+
capture_output=True,
112+
timeout=10,
113+
)
114+
self.assertNotEqual(result.returncode, 0)
115+
116+
117+
if __name__ == '__main__':
118+
unittest.main()

rpm_spec/core-agent.spec.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,7 @@ rm -f %{name}-%{version}
10791079
%{python3_sitelib}/qubesagent/xdg.py*
10801080
%{python3_sitelib}/qubesagent/test_xdg.py*
10811081
%{python3_sitelib}/qubesagent/test_tools.py*
1082+
%{python3_sitelib}/qubesagent/test_getimgrgba.py*
10821083

10831084
%dir /usr/share/qubes/mime-override
10841085
/usr/share/qubes/mime-override/globs

0 commit comments

Comments
 (0)