Skip to content

Commit 774ee8f

Browse files
committed
Merge remote-tracking branch 'origin/pr/231'
* origin/pr/231: Rewrite legacy X11 App icons extraction in xcffib Send icons for legacy X11 Apps
2 parents 95eba6d + 34ab7a5 commit 774ee8f

File tree

1 file changed

+132
-1
lines changed

1 file changed

+132
-1
lines changed

window-icon-updater/icon-sender

100644100755
Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import xcffib
3535
from xcffib import xproto
3636

3737
ICON_MAX_SIZE = 256
38+
IconPixmapHint = 0b1 << 2
39+
IconMaskHint = 0b1 << 5
3840

3941

4042
log = logging.getLogger('icon-sender')
@@ -82,7 +84,136 @@ class IconRetriever(object):
8284
raise NoIconError()
8385

8486
if icon.format == 0:
85-
raise NoIconError()
87+
# Legacy case
88+
# Ancient X11 applications (Xterm, Xcalc, Xlogo, Xeyes, Xclock, ...)
89+
try:
90+
prop_cookie = self.conn.core.GetProperty(
91+
False, # delete
92+
w, # window
93+
xproto.Atom.WM_HINTS,
94+
xproto.GetPropertyType.Any,
95+
0, # long_offset
96+
48 # long_length -> XWMHints struct length on x86_64
97+
)
98+
reply = prop_cookie.reply()
99+
if not reply.value_len:
100+
raise NoIconError()
101+
atoms = reply.value.to_atoms()
102+
flags = atoms[0]
103+
if not flags & IconPixmapHint:
104+
# A menu, pop-up or similar icon-less window
105+
raise NoIconError()
106+
icon_pixmap_geometry = self.conn.core.GetGeometry(
107+
atoms[3]
108+
).reply()
109+
if (
110+
# Only 1bit/pixel & 24bit/pixel icons are tested
111+
not icon_pixmap_geometry.depth in [1, 24]
112+
or icon_pixmap_geometry.width > ICON_MAX_SIZE
113+
or icon_pixmap_geometry.height > ICON_MAX_SIZE
114+
):
115+
raise NoIconError()
116+
image = self.conn.core.GetImage(
117+
xproto.ImageFormat.ZPixmap,
118+
atoms[3],
119+
0,
120+
0,
121+
icon_pixmap_geometry.width,
122+
icon_pixmap_geometry.height,
123+
0xFFFFFFFF
124+
).reply()
125+
icon_pixmap_data = image.data.raw
126+
if not flags & IconMaskHint:
127+
# We have an icon without transparency (mask)
128+
icon_mask_geometry = None
129+
icon_mask_data = None
130+
else:
131+
icon_mask_geometry = self.conn.core.GetGeometry(
132+
atoms[7]
133+
).reply()
134+
image = self.conn.core.GetImage(
135+
xproto.ImageFormat.ZPixmap,
136+
atoms[7],
137+
0,
138+
0,
139+
icon_mask_geometry.width,
140+
icon_mask_geometry.height,
141+
0xFFFFFFFF
142+
).reply()
143+
icon_mask_data = image.data.raw
144+
except (
145+
xproto.BadWindow,
146+
xproto.WindowError,
147+
xproto.AccessError,
148+
xproto.DrawableError
149+
):
150+
raise NoIconError
151+
152+
# Finally we have the required data to construct icon
153+
icons = {}
154+
if icon_pixmap_geometry.depth == 1:
155+
# 1 bit per pixel icons (i.e. xlogo, xeyes, xcalc, ...)
156+
icon_data = []
157+
# There might be trailing bytes at the end of each row since
158+
# each row should be multiples of 32 bits
159+
row_width = int(
160+
len(icon_pixmap_data) / icon_pixmap_geometry.height
161+
)
162+
for y in range(0, icon_pixmap_geometry.height):
163+
offset = y * row_width
164+
for x in range(0, icon_pixmap_geometry.width):
165+
byte_offset = int(x / 8) + offset
166+
byte = int(icon_pixmap_data[byte_offset])
167+
byte = byte >> (x % 8)
168+
bit = byte & 0x1
169+
if bit:
170+
# Can not decide the light/dark theme from vmside :/
171+
icon_data.append(0xff7f7f7f)
172+
else:
173+
icon_data.append(0x0)
174+
elif icon_pixmap_geometry.depth == 24:
175+
# 24 bit per pixel icons (i.e. Xterm)
176+
# Technically this could handle modern programs as well
177+
# However, _NET_WM_ICON is faster
178+
icon_data = struct.unpack(
179+
"%dI" % (len(icon_pixmap_data) / 4),
180+
icon_pixmap_data
181+
)
182+
icon_data = [d | 0xff000000 for d in icon_data]
183+
else:
184+
# Could not find 8 bit icons of that era to work with
185+
raise NoIconError()
186+
if icon_mask_data and icon_mask_geometry.depth == 1:
187+
# Even Xterm uses 1 bit/pixel mask. I do not know why
188+
row_width = int(len(icon_mask_data) / icon_mask_geometry.height)
189+
for y in range(0, icon_mask_geometry.height):
190+
offset = y * row_width
191+
for x in range(0, icon_mask_geometry.width):
192+
byte_offset = int(x/8) + offset
193+
byte = int(icon_mask_data[byte_offset])
194+
byte = byte >> (x % 8)
195+
bit = byte & 0x1
196+
pixel = x + y * icon_mask_geometry.height
197+
if bit:
198+
icon_data[pixel] = icon_data[pixel] & 0xffffffff
199+
else:
200+
icon_data[pixel] = icon_data[pixel] & 0x00ffffff
201+
elif icon_mask_data and icon_mask_geometry.depth == 8:
202+
# Technically this is not tested (No X prog uses 8bit/pix mask)
203+
# At least not on Qubes OS 4.3 & default Xfwm4
204+
row_width = int(len(icon_mask_data) / icon_mask_geometry.height)
205+
for y in range(0, icon_mask_geometry.height):
206+
offset = y * row_width
207+
for x in range(0, icon_mask_geometry.width):
208+
byte_offset = x + offset
209+
byte = int(icon_mask_data[byte_offset])
210+
pixmask = (byte << 24) | 0x00ffffff
211+
icon_data[pixel] = icon_data[pixel] & pixmask
212+
size = (icon_pixmap_geometry.width, icon_pixmap_geometry.height)
213+
icons[size] = icon_data
214+
return icons
215+
216+
# We have sane _NET_WM_ICON Atom for modern programs
86217
# convert it later to a proper int array
87218
icon_data = icon.value.buf()
88219
if icon.bytes_after:

0 commit comments

Comments
 (0)