@@ -33,11 +33,10 @@ import time
3333
3434import xcffib
3535from xcffib import xproto
36- from Xlib .display import Display
37- from Xlib import X
38- from Xlib .error import XError
3936
4037ICON_MAX_SIZE = 256
38+ IconPixmapHint = 0b1 << 2
39+ IconMaskHint = 0b1 << 5
4140
4241
4342log = logging .getLogger ('icon-sender' )
@@ -52,7 +51,6 @@ class IconRetriever(object):
5251 self .conn = xcffib .connect ()
5352 self .setup = self .conn .get_setup ()
5453 self .root = self .setup .roots [0 ].root
55- self .display = Display ()
5654
5755 # just created windows for which icon wasn't sent yet - should
5856 # be send on MapNotifyEvent
@@ -64,7 +62,6 @@ class IconRetriever(object):
6462 def disconnect (self ):
6563 log .info ('disconnecting from X' )
6664 self .conn .disconnect ()
67- self .display .close ()
6865
6966 def watch_window (self , w ):
7067 self .conn .core .ChangeWindowAttributesChecked (
@@ -85,102 +82,138 @@ class IconRetriever(object):
8582 except xproto .BadWindow :
8683 # Window disappeared in the meantime
8784 raise NoIconError ()
85+
8886 if icon .format == 0 :
87+ # Legacy case
8988 # Ancient X11 applications (Xterm, Xcalc, Xlogo, Xeyes, Xclock, ...)
9089 try :
91- window = self .display .create_resource_object ('window' , w )
92- wm_hints = window .get_wm_hints ()
93- if not wm_hints :
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+ ):
94115 raise NoIconError ()
95- geometry = wm_hints .icon_pixmap .get_geometry ()
96- pixmap = wm_hints .icon_pixmap .get_image (
116+ image = self .conn .core .GetImage (
117+ xproto .ImageFormat .ZPixmap ,
118+ atoms [3 ],
97119 0 ,
98120 0 ,
99- geometry .width ,
100- geometry .height ,
101- X .ZPixmap ,
121+ icon_pixmap_geometry .width ,
122+ icon_pixmap_geometry .height ,
102123 0xFFFFFFFF
103- )
104- pixmap_data = pixmap .data
105- icons = {}
106- try :
107- mask = wm_hints .icon_mask .get_image (
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 ],
108137 0 ,
109138 0 ,
110- geometry .width ,
111- geometry .height ,
112- X .ZPixmap ,
139+ icon_mask_geometry .width ,
140+ icon_mask_geometry .height ,
113141 0xFFFFFFFF
114- )
115- mask_geometry = wm_hints .icon_mask .get_geometry ()
116- mask_data = mask .data
117- except :
118- # Icons without transparency
119- mask = None
120- mask_geometry = None
121- mask_data = []
122- except XError :
123- raise NoIconError ()
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
124151
125152 # Finally we have the required data to construct icon
126- if geometry .depth == 1 :
153+ icons = {}
154+ if icon_pixmap_geometry .depth == 1 :
127155 # 1 bit per pixel icons (i.e. xlogo, xeyes, xcalc, ...)
128156 icon_data = []
129157 # There might be trailing bytes at the end of each row since
130158 # each row should be multiples of 32 bits
131- row_width = int (len (pixmap_data ) / geometry .height )
132- for y in range (0 , geometry .height ):
159+ row_width = int (
160+ len (icon_pixmap_data ) / icon_pixmap_geometry .height
161+ )
162+ for y in range (0 , icon_pixmap_geometry .height ):
133163 offset = y * row_width
134- for x in range (0 , geometry .width ):
164+ for x in range (0 , icon_pixmap_geometry .width ):
135165 byte_offset = int (x / 8 ) + offset
136- byte = int (pixmap_data [byte_offset ])
166+ byte = int (icon_pixmap_data [byte_offset ])
137167 byte = byte >> (x % 8 )
138168 bit = byte & 0x1
139169 if bit :
140170 # Can not decide the light/dark theme from vmside :/
141171 icon_data .append (0xff7f7f7f )
142172 else :
143173 icon_data .append (0x0 )
144- elif geometry .depth == 24 :
174+ elif icon_pixmap_geometry .depth == 24 :
145175 # 24 bit per pixel icons (i.e. Xterm)
146- # Technically this could handle other programs as well
176+ # Technically this could handle modern programs as well
177+ # However, _NET_WM_ICON is faster
147178 icon_data = struct .unpack (
148- "%dI" % (len (pixmap_data ) / 4 ),
149- pixmap_data
179+ "%dI" % (len (icon_pixmap_data ) / 4 ),
180+ icon_pixmap_data
150181 )
151182 icon_data = [d | 0xff000000 for d in icon_data ]
152183 else :
153184 # Could not find 8 bit icons of that era to work with
154185 raise NoIconError ()
155- if mask_data and mask_geometry .depth == 1 :
186+ if icon_mask_data and icon_mask_geometry .depth == 1 :
156187 # Even Xterm uses 1 bit/pixel mask. I do not know why
157- row_width = int (len (mask_data ) / geometry .height )
158- for y in range (0 , geometry .height ):
188+ row_width = int (len (icon_mask_data ) / icon_mask_geometry .height )
189+ for y in range (0 , icon_mask_geometry .height ):
159190 offset = y * row_width
160- for x in range (0 , geometry .width ):
191+ for x in range (0 , icon_mask_geometry .width ):
161192 byte_offset = int (x / 8 ) + offset
162- byte = int (mask_data [byte_offset ])
193+ byte = int (icon_mask_data [byte_offset ])
163194 byte = byte >> (x % 8 )
164195 bit = byte & 0x1
165- pixel = x + y * geometry .height
196+ pixel = x + y * icon_mask_geometry .height
166197 if bit :
167198 icon_data [pixel ] = icon_data [pixel ] & 0xffffffff
168199 else :
169200 icon_data [pixel ] = icon_data [pixel ] & 0x00ffffff
170- elif mask_data and mask_geometry .depth == 8 :
201+ elif icon_mask_data and icon_mask_geometry .depth == 8 :
171202 # Technically this is not tested (No X prog uses 8bit/pix mask)
172203 # At least not on Qubes OS 4.3 & default Xfwm4
173- for y in range (0 , geometry .height ):
204+ row_width = int (len (icon_mask_data ) / icon_mask_geometry .height )
205+ for y in range (0 , icon_mask_geometry .height ):
174206 offset = y * row_width
175- for x in range (0 , geometry .width ):
207+ for x in range (0 , icon_mask_geometry .width ):
176208 byte_offset = x + offset
177- byte = int (mask_data [byte_offset ])
178- pixmask = (byte < 24 ) | 0x00ffffff
209+ byte = int (icon_mask_data [byte_offset ])
210+ pixmask = (byte << 24 ) | 0x00ffffff
179211 icon_data [pixel ] = icon_data [pixel ] & pixmask
180- size = (geometry .width , geometry .height )
212+ size = (icon_pixmap_geometry .width , icon_pixmap_geometry .height )
181213 icons [size ] = icon_data
182214 return icons
183215
216+ # We have sane _NET_WM_ICON Atom for modern programs
184217 # convert it later to a proper int array
185218 icon_data = icon .value .buf ()
186219 if icon .bytes_after :
0 commit comments