Skip to content

Commit 1495b8d

Browse files
committed
Send icons for legacy X11 Apps
Since very old X11 Apps (Xterm, Xcalc, Xlogo, Xclock, Xeyes, ...) do not have the `_NET_WM_ICON` Atom, revert to `WM_HINT` and extract icons and their transparency with that technology. This is mostly useful for Xterm fixes: QubesOS/qubes-issues#9973
1 parent 8f8c0a1 commit 1495b8d

File tree

1 file changed

+87
-2
lines changed

1 file changed

+87
-2
lines changed

window-icon-updater/icon-sender

100644100755
Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ import time
3333

3434
import xcffib
3535
from xcffib import xproto
36+
from Xlib.display import Display
37+
from Xlib import X
38+
from Xlib.error import XError
3639

3740
ICON_MAX_SIZE = 256
3841

@@ -49,6 +52,7 @@ class IconRetriever(object):
4952
self.conn = xcffib.connect()
5053
self.setup = self.conn.get_setup()
5154
self.root = self.setup.roots[0].root
55+
self.display = Display()
5256

5357
# just created windows for which icon wasn't sent yet - should
5458
# be send on MapNotifyEvent
@@ -60,6 +64,7 @@ class IconRetriever(object):
6064
def disconnect(self):
6165
log.info('disconnecting from X')
6266
self.conn.disconnect()
67+
self.display.close()
6368

6469
def watch_window(self, w):
6570
self.conn.core.ChangeWindowAttributesChecked(
@@ -80,9 +85,89 @@ class IconRetriever(object):
8085
except xproto.BadWindow:
8186
# Window disappeared in the meantime
8287
raise NoIconError()
83-
8488
if icon.format == 0:
85-
raise NoIconError()
89+
# Ancient X11 applications (Xterm, Xcalc, Xlogo, Xeyes, Xclock, ...)
90+
try:
91+
window = self.display.create_resource_object('window', w)
92+
wm_hints = window.get_wm_hints()
93+
if not wm_hints:
94+
raise NoIconError()
95+
geometry = wm_hints.icon_pixmap.get_geometry()
96+
pixmap = wm_hints.icon_pixmap.get_image(
97+
0,
98+
0,
99+
geometry.width,
100+
geometry.height,
101+
X.ZPixmap,
102+
0xFFFFFFFF
103+
)
104+
pixmap_data = pixmap.data
105+
icons = {}
106+
try:
107+
mask = wm_hints.icon_mask.get_image(
108+
0,
109+
0,
110+
geometry.width,
111+
geometry.height,
112+
X.ZPixmap,
113+
0xFFFFFFFF
114+
)
115+
mask_data = mask.data
116+
except:
117+
# Icons without transparency
118+
mask = None
119+
mask_data = []
120+
except XError:
121+
raise NoIconError()
122+
123+
# Finally we have the required data to construct icon
124+
if geometry.depth == 1:
125+
# 1 bit per pixel icons (i.e. xlogo, xeyes, xcalc, ...)
126+
icon_data = []
127+
# There might be trailing bytes at the end of each row since
128+
# each row should be multiples of 32 bits
129+
row_width = int(len(pixmap_data) / geometry.height)
130+
for y in range(0, geometry.height):
131+
offset = y * row_width
132+
for x in range(0, geometry.width):
133+
byte_offset = int(x / 8) + offset
134+
byte = int(pixmap_data[byte_offset])
135+
byte = byte >> (x % 8)
136+
bit = byte & 0x1
137+
if bit:
138+
# Can not decide the light/dark theme from vmside :/
139+
icon_data.append(0xff7f7f7f)
140+
else:
141+
icon_data.append(0x0)
142+
elif geometry.depth == 24:
143+
# 24 bit per pixel icons (i.e. Xterm)
144+
# Technically this could handle other programs as well
145+
icon_data = struct.unpack(
146+
"%dI" % (len(pixmap_data) / 4),
147+
pixmap_data
148+
)
149+
icon_data = [d | 0xff000000 for d in icon_data]
150+
else:
151+
# Could not find 8 bit icons of that era to work with
152+
raise NoIconError()
153+
if mask_data:
154+
row_width = int(len(mask_data) / geometry.height)
155+
for y in range(0, geometry.height):
156+
offset = y * row_width
157+
for x in range(0, geometry.width):
158+
byte_offset = int(x/8) + offset
159+
byte = int(mask_data[byte_offset])
160+
byte = byte >> (x % 8)
161+
bit = byte & 0x1
162+
pixel = x + y * geometry.height
163+
if bit:
164+
icon_data[pixel] = icon_data[pixel] & 0xffffffff
165+
else:
166+
icon_data[pixel] = icon_data[pixel] & 0x00ffffff
167+
size = (geometry.width, geometry.height)
168+
icons[size] = icon_data
169+
return icons
170+
86171
# convert it later to a proper int array
87172
icon_data = icon.value.buf()
88173
if icon.bytes_after:

0 commit comments

Comments
 (0)