location (position) of tray icon in windows from python

I saw this question posted to the list a few times, and just wrote a solution. There are certainly no API calls / sane way to do this in Windows, but where there is a will there is a way.

The basic idea is, turn your icon black, scan the screen for a black square, and then turn it back to your icon. In practice, this can be done quick enough for the user to not notice. If the loops were in C, it would definitely be too fast to notice :wink: It would be nice to just hunt for the icon itself, but unfortunately, various versions of windows (win2k especially) play with the coloring too heavily for this method to be at all reliable.

the ico file i use as the “black” icon while I’m searching can be downloaded from here: http://dl.getdropbox.com/u/100516/blank1.ico

this implementation uses pywin32. If you are not using pywin32 and don’t want to add a dependency on it, it should be relatively easy to use ctypes to make the win32 api calls. Note that YOU need to fill in the code that sets/unsets the black icon!

I’ve used this successfully on win2k, xp, vista, and windows 7 (when the bar is showing). If your icon isn’t showing, it’s not gonna work :slight_smile: In that case, you could fall back to using the rect of the whole notification area (see the first line of the function get_screen_rect. note that win32’s GetWindowRect returns (left, top, right, bottom) NOT (left, top, width, height)). The rect returned assumes your icon is 16x16, if the user has “big icons” turned on, then you may have to detect that in a more fancy way!!

¡¡¡

################

import win32gui

def get_notification_hwnd():

tray_window_class = ‘Shell_TrayWnd’

tray_hwnd = win32gui.FindWindowEx(0, 0,

tray_window_class, “”)

assert tray_hwnd, “Couldn’t get tray hwnd”

notification_window_class = ‘TrayNotifyWnd’

notification_hwnd = win32gui.FindWindowEx(tray_hwnd, 0,

notification_window_class, “”)

return notification_hwnd

This function tries its best to return a rect similar to the GetScreenRect function from wx.Window

the rect returned is (left, top, width, height)

Returns None if there were problems, excepts if you don’t have a notification area at all

def get_screen_rect():

notification_rect = win32gui.GetWindowRect(get_notification_hwnd())

screendc = win32gui.GetDC(None)

be lenient about what we call “black”, windows messes with colors sometimes

potential_blacks = set()

for r in xrange(4):

for g in xrange(4):

for b in xrange(4):

potential_blacks.add(r | (g << 8) | (b << 16))

!!! SET YOUR ICON TO BLACK.ICO HERE !!!

try:

the_rect = None

for y in xrange(notification_rect[3]-3, notification_rect[1], -1):

num_consecutive_black = 0

for x in xrange(notification_rect[0], notification_rect[2], 1):

if all((win32gui.GetPixel(screendc,

x,

y+yoff) in potential_blacks) \

for yoff in (-2, 0, 2)):

num_consecutive_black += 1

else:

num_consecutive_black = 0

if num_consecutive_black > 8: # p sure this is us

the_rect = (x-8, y-13, 16, 16)

break

if the_rect is not None:

break

return the_rect

finally:

!!! RESTORE YOUR ICON TO WHATEVER IT WAS HERE !!!

################

Note: this is a dirty, dirty hack. Don’t blame me if it doesn’t work. This is /certainly/ not the “right” way to do it, but it just might be the /only/ way. :slight_smile: There is an even more fun hack that works up to xp, involving allocating memory in the notifcation process itself, but Vista’s UIPI has ended that as a possibility. WOOOO.

Happy to answer any questions you may have.

Hope this helps! Happy hacking,

Mike