@@ -35,6 +35,8 @@ import xcffib
3535from xcffib import xproto
3636
3737ICON_MAX_SIZE = 256
38+ IconPixmapHint = 0b1 << 2
39+ IconMaskHint = 0b1 << 5
3840
3941
4042log = 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