123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731 |
- # Pure ctypes windows taskbar notification icon
- # via https://gist.github.com/jasonbot/5759510
- # Modified for ZeroNet
- import ctypes
- import ctypes.wintypes
- import os
- import uuid
- import time
- import gevent
- import threading
- try:
- from queue import Empty as queue_Empty # Python 3
- except ImportError:
- from Queue import Empty as queue_Empty # Python 2
- __all__ = ['NotificationIcon']
- # Create popup menu
- CreatePopupMenu = ctypes.windll.user32.CreatePopupMenu
- CreatePopupMenu.restype = ctypes.wintypes.HMENU
- CreatePopupMenu.argtypes = []
- MF_BYCOMMAND = 0x0
- MF_BYPOSITION = 0x400
- MF_BITMAP = 0x4
- MF_CHECKED = 0x8
- MF_DISABLED = 0x2
- MF_ENABLED = 0x0
- MF_GRAYED = 0x1
- MF_MENUBARBREAK = 0x20
- MF_MENUBREAK = 0x40
- MF_OWNERDRAW = 0x100
- MF_POPUP = 0x10
- MF_SEPARATOR = 0x800
- MF_STRING = 0x0
- MF_UNCHECKED = 0x0
- InsertMenu = ctypes.windll.user32.InsertMenuW
- InsertMenu.restype = ctypes.wintypes.BOOL
- InsertMenu.argtypes = [ctypes.wintypes.HMENU, ctypes.wintypes.UINT, ctypes.wintypes.UINT, ctypes.wintypes.UINT, ctypes.wintypes.LPCWSTR]
- AppendMenu = ctypes.windll.user32.AppendMenuW
- AppendMenu.restype = ctypes.wintypes.BOOL
- AppendMenu.argtypes = [ctypes.wintypes.HMENU, ctypes.wintypes.UINT, ctypes.wintypes.UINT, ctypes.wintypes.LPCWSTR]
- SetMenuDefaultItem = ctypes.windll.user32.SetMenuDefaultItem
- SetMenuDefaultItem.restype = ctypes.wintypes.BOOL
- SetMenuDefaultItem.argtypes = [ctypes.wintypes.HMENU, ctypes.wintypes.UINT, ctypes.wintypes.UINT]
- class POINT(ctypes.Structure):
- _fields_ = [ ('x', ctypes.wintypes.LONG),
- ('y', ctypes.wintypes.LONG)]
- GetCursorPos = ctypes.windll.user32.GetCursorPos
- GetCursorPos.argtypes = [ctypes.POINTER(POINT)]
- SetForegroundWindow = ctypes.windll.user32.SetForegroundWindow
- SetForegroundWindow.argtypes = [ctypes.wintypes.HWND]
- TPM_LEFTALIGN = 0x0
- TPM_CENTERALIGN = 0x4
- TPM_RIGHTALIGN = 0x8
- TPM_TOPALIGN = 0x0
- TPM_VCENTERALIGN = 0x10
- TPM_BOTTOMALIGN = 0x20
- TPM_NONOTIFY = 0x80
- TPM_RETURNCMD = 0x100
- TPM_LEFTBUTTON = 0x0
- TPM_RIGHTBUTTON = 0x2
- TPM_HORNEGANIMATION = 0x800
- TPM_HORPOSANIMATION = 0x400
- TPM_NOANIMATION = 0x4000
- TPM_VERNEGANIMATION = 0x2000
- TPM_VERPOSANIMATION = 0x1000
- TrackPopupMenu = ctypes.windll.user32.TrackPopupMenu
- TrackPopupMenu.restype = ctypes.wintypes.BOOL
- TrackPopupMenu.argtypes = [ctypes.wintypes.HMENU, ctypes.wintypes.UINT, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.wintypes.HWND, ctypes.c_void_p]
- PostMessage = ctypes.windll.user32.PostMessageW
- PostMessage.restype = ctypes.wintypes.BOOL
- PostMessage.argtypes = [ctypes.wintypes.HWND, ctypes.wintypes.UINT, ctypes.wintypes.WPARAM, ctypes.wintypes.LPARAM]
- DestroyMenu = ctypes.windll.user32.DestroyMenu
- DestroyMenu.restype = ctypes.wintypes.BOOL
- DestroyMenu.argtypes = [ctypes.wintypes.HMENU]
- # Create notification icon
- GUID = ctypes.c_ubyte * 16
- class TimeoutVersionUnion(ctypes.Union):
- _fields_ = [('uTimeout', ctypes.wintypes.UINT),
- ('uVersion', ctypes.wintypes.UINT),]
- NIS_HIDDEN = 0x1
- NIS_SHAREDICON = 0x2
- class NOTIFYICONDATA(ctypes.Structure):
- def __init__(self, *args, **kwargs):
- super(NOTIFYICONDATA, self).__init__(*args, **kwargs)
- self.cbSize = ctypes.sizeof(self)
- _fields_ = [
- ('cbSize', ctypes.wintypes.DWORD),
- ('hWnd', ctypes.wintypes.HWND),
- ('uID', ctypes.wintypes.UINT),
- ('uFlags', ctypes.wintypes.UINT),
- ('uCallbackMessage', ctypes.wintypes.UINT),
- ('hIcon', ctypes.wintypes.HICON),
- ('szTip', ctypes.wintypes.WCHAR * 64),
- ('dwState', ctypes.wintypes.DWORD),
- ('dwStateMask', ctypes.wintypes.DWORD),
- ('szInfo', ctypes.wintypes.WCHAR * 256),
- ('union', TimeoutVersionUnion),
- ('szInfoTitle', ctypes.wintypes.WCHAR * 64),
- ('dwInfoFlags', ctypes.wintypes.DWORD),
- ('guidItem', GUID),
- ('hBalloonIcon', ctypes.wintypes.HICON),
- ]
- NIM_ADD = 0
- NIM_MODIFY = 1
- NIM_DELETE = 2
- NIM_SETFOCUS = 3
- NIM_SETVERSION = 4
- NIF_MESSAGE = 1
- NIF_ICON = 2
- NIF_TIP = 4
- NIF_STATE = 8
- NIF_INFO = 16
- NIF_GUID = 32
- NIF_REALTIME = 64
- NIF_SHOWTIP = 128
- NIIF_NONE = 0
- NIIF_INFO = 1
- NIIF_WARNING = 2
- NIIF_ERROR = 3
- NIIF_USER = 4
- NOTIFYICON_VERSION = 3
- NOTIFYICON_VERSION_4 = 4
- Shell_NotifyIcon = ctypes.windll.shell32.Shell_NotifyIconW
- Shell_NotifyIcon.restype = ctypes.wintypes.BOOL
- Shell_NotifyIcon.argtypes = [ctypes.wintypes.DWORD, ctypes.POINTER(NOTIFYICONDATA)]
- # Load icon/image
- IMAGE_BITMAP = 0
- IMAGE_ICON = 1
- IMAGE_CURSOR = 2
- LR_CREATEDIBSECTION = 0x00002000
- LR_DEFAULTCOLOR = 0x00000000
- LR_DEFAULTSIZE = 0x00000040
- LR_LOADFROMFILE = 0x00000010
- LR_LOADMAP3DCOLORS = 0x00001000
- LR_LOADTRANSPARENT = 0x00000020
- LR_MONOCHROME = 0x00000001
- LR_SHARED = 0x00008000
- LR_VGACOLOR = 0x00000080
- OIC_SAMPLE = 32512
- OIC_HAND = 32513
- OIC_QUES = 32514
- OIC_BANG = 32515
- OIC_NOTE = 32516
- OIC_WINLOGO = 32517
- OIC_WARNING = OIC_BANG
- OIC_ERROR = OIC_HAND
- OIC_INFORMATION = OIC_NOTE
- LoadImage = ctypes.windll.user32.LoadImageW
- LoadImage.restype = ctypes.wintypes.HANDLE
- LoadImage.argtypes = [ctypes.wintypes.HINSTANCE, ctypes.wintypes.LPCWSTR, ctypes.wintypes.UINT, ctypes.c_int, ctypes.c_int, ctypes.wintypes.UINT]
- # CreateWindow call
- WNDPROC = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.wintypes.HWND, ctypes.c_uint, ctypes.wintypes.WPARAM, ctypes.wintypes.LPARAM)
- DefWindowProc = ctypes.windll.user32.DefWindowProcW
- DefWindowProc.restype = ctypes.c_int
- DefWindowProc.argtypes = [ctypes.wintypes.HWND, ctypes.c_uint, ctypes.wintypes.WPARAM, ctypes.wintypes.LPARAM]
- WS_OVERLAPPED = 0x00000000
- WS_POPUP = 0x80000000
- WS_CHILD = 0x40000000
- WS_MINIMIZE = 0x20000000
- WS_VISIBLE = 0x10000000
- WS_DISABLED = 0x08000000
- WS_CLIPSIBLINGS = 0x04000000
- WS_CLIPCHILDREN = 0x02000000
- WS_MAXIMIZE = 0x01000000
- WS_CAPTION = 0x00C00000
- WS_BORDER = 0x00800000
- WS_DLGFRAME = 0x00400000
- WS_VSCROLL = 0x00200000
- WS_HSCROLL = 0x00100000
- WS_SYSMENU = 0x00080000
- WS_THICKFRAME = 0x00040000
- WS_GROUP = 0x00020000
- WS_TABSTOP = 0x00010000
- WS_MINIMIZEBOX = 0x00020000
- WS_MAXIMIZEBOX = 0x00010000
- WS_OVERLAPPEDWINDOW = (WS_OVERLAPPED |
- WS_CAPTION |
- WS_SYSMENU |
- WS_THICKFRAME |
- WS_MINIMIZEBOX |
- WS_MAXIMIZEBOX)
- SM_XVIRTUALSCREEN = 76
- SM_YVIRTUALSCREEN = 77
- SM_CXVIRTUALSCREEN = 78
- SM_CYVIRTUALSCREEN = 79
- SM_CMONITORS = 80
- SM_SAMEDISPLAYFORMAT = 81
- WM_NULL = 0x0000
- WM_CREATE = 0x0001
- WM_DESTROY = 0x0002
- WM_MOVE = 0x0003
- WM_SIZE = 0x0005
- WM_ACTIVATE = 0x0006
- WM_SETFOCUS = 0x0007
- WM_KILLFOCUS = 0x0008
- WM_ENABLE = 0x000A
- WM_SETREDRAW = 0x000B
- WM_SETTEXT = 0x000C
- WM_GETTEXT = 0x000D
- WM_GETTEXTLENGTH = 0x000E
- WM_PAINT = 0x000F
- WM_CLOSE = 0x0010
- WM_QUERYENDSESSION = 0x0011
- WM_QUIT = 0x0012
- WM_QUERYOPEN = 0x0013
- WM_ERASEBKGND = 0x0014
- WM_SYSCOLORCHANGE = 0x0015
- WM_ENDSESSION = 0x0016
- WM_SHOWWINDOW = 0x0018
- WM_CTLCOLOR = 0x0019
- WM_WININICHANGE = 0x001A
- WM_SETTINGCHANGE = 0x001A
- WM_DEVMODECHANGE = 0x001B
- WM_ACTIVATEAPP = 0x001C
- WM_FONTCHANGE = 0x001D
- WM_TIMECHANGE = 0x001E
- WM_CANCELMODE = 0x001F
- WM_SETCURSOR = 0x0020
- WM_MOUSEACTIVATE = 0x0021
- WM_CHILDACTIVATE = 0x0022
- WM_QUEUESYNC = 0x0023
- WM_GETMINMAXINFO = 0x0024
- WM_PAINTICON = 0x0026
- WM_ICONERASEBKGND = 0x0027
- WM_NEXTDLGCTL = 0x0028
- WM_SPOOLERSTATUS = 0x002A
- WM_DRAWITEM = 0x002B
- WM_MEASUREITEM = 0x002C
- WM_DELETEITEM = 0x002D
- WM_VKEYTOITEM = 0x002E
- WM_CHARTOITEM = 0x002F
- WM_SETFONT = 0x0030
- WM_GETFONT = 0x0031
- WM_SETHOTKEY = 0x0032
- WM_GETHOTKEY = 0x0033
- WM_QUERYDRAGICON = 0x0037
- WM_COMPAREITEM = 0x0039
- WM_GETOBJECT = 0x003D
- WM_COMPACTING = 0x0041
- WM_COMMNOTIFY = 0x0044
- WM_WINDOWPOSCHANGING = 0x0046
- WM_WINDOWPOSCHANGED = 0x0047
- WM_POWER = 0x0048
- WM_COPYDATA = 0x004A
- WM_CANCELJOURNAL = 0x004B
- WM_NOTIFY = 0x004E
- WM_INPUTLANGCHANGEREQUEST = 0x0050
- WM_INPUTLANGCHANGE = 0x0051
- WM_TCARD = 0x0052
- WM_HELP = 0x0053
- WM_USERCHANGED = 0x0054
- WM_NOTIFYFORMAT = 0x0055
- WM_CONTEXTMENU = 0x007B
- WM_STYLECHANGING = 0x007C
- WM_STYLECHANGED = 0x007D
- WM_DISPLAYCHANGE = 0x007E
- WM_GETICON = 0x007F
- WM_SETICON = 0x0080
- WM_NCCREATE = 0x0081
- WM_NCDESTROY = 0x0082
- WM_NCCALCSIZE = 0x0083
- WM_NCHITTEST = 0x0084
- WM_NCPAINT = 0x0085
- WM_NCACTIVATE = 0x0086
- WM_GETDLGCODE = 0x0087
- WM_SYNCPAINT = 0x0088
- WM_NCMOUSEMOVE = 0x00A0
- WM_NCLBUTTONDOWN = 0x00A1
- WM_NCLBUTTONUP = 0x00A2
- WM_NCLBUTTONDBLCLK = 0x00A3
- WM_NCRBUTTONDOWN = 0x00A4
- WM_NCRBUTTONUP = 0x00A5
- WM_NCRBUTTONDBLCLK = 0x00A6
- WM_NCMBUTTONDOWN = 0x00A7
- WM_NCMBUTTONUP = 0x00A8
- WM_NCMBUTTONDBLCLK = 0x00A9
- WM_KEYDOWN = 0x0100
- WM_KEYUP = 0x0101
- WM_CHAR = 0x0102
- WM_DEADCHAR = 0x0103
- WM_SYSKEYDOWN = 0x0104
- WM_SYSKEYUP = 0x0105
- WM_SYSCHAR = 0x0106
- WM_SYSDEADCHAR = 0x0107
- WM_KEYLAST = 0x0108
- WM_IME_STARTCOMPOSITION = 0x010D
- WM_IME_ENDCOMPOSITION = 0x010E
- WM_IME_COMPOSITION = 0x010F
- WM_IME_KEYLAST = 0x010F
- WM_INITDIALOG = 0x0110
- WM_COMMAND = 0x0111
- WM_SYSCOMMAND = 0x0112
- WM_TIMER = 0x0113
- WM_HSCROLL = 0x0114
- WM_VSCROLL = 0x0115
- WM_INITMENU = 0x0116
- WM_INITMENUPOPUP = 0x0117
- WM_MENUSELECT = 0x011F
- WM_MENUCHAR = 0x0120
- WM_ENTERIDLE = 0x0121
- WM_MENURBUTTONUP = 0x0122
- WM_MENUDRAG = 0x0123
- WM_MENUGETOBJECT = 0x0124
- WM_UNINITMENUPOPUP = 0x0125
- WM_MENUCOMMAND = 0x0126
- WM_CTLCOLORMSGBOX = 0x0132
- WM_CTLCOLOREDIT = 0x0133
- WM_CTLCOLORLISTBOX = 0x0134
- WM_CTLCOLORBTN = 0x0135
- WM_CTLCOLORDLG = 0x0136
- WM_CTLCOLORSCROLLBAR = 0x0137
- WM_CTLCOLORSTATIC = 0x0138
- WM_MOUSEMOVE = 0x0200
- WM_LBUTTONDOWN = 0x0201
- WM_LBUTTONUP = 0x0202
- WM_LBUTTONDBLCLK = 0x0203
- WM_RBUTTONDOWN = 0x0204
- WM_RBUTTONUP = 0x0205
- WM_RBUTTONDBLCLK = 0x0206
- WM_MBUTTONDOWN = 0x0207
- WM_MBUTTONUP = 0x0208
- WM_MBUTTONDBLCLK = 0x0209
- WM_MOUSEWHEEL = 0x020A
- WM_PARENTNOTIFY = 0x0210
- WM_ENTERMENULOOP = 0x0211
- WM_EXITMENULOOP = 0x0212
- WM_NEXTMENU = 0x0213
- WM_SIZING = 0x0214
- WM_CAPTURECHANGED = 0x0215
- WM_MOVING = 0x0216
- WM_DEVICECHANGE = 0x0219
- WM_MDICREATE = 0x0220
- WM_MDIDESTROY = 0x0221
- WM_MDIACTIVATE = 0x0222
- WM_MDIRESTORE = 0x0223
- WM_MDINEXT = 0x0224
- WM_MDIMAXIMIZE = 0x0225
- WM_MDITILE = 0x0226
- WM_MDICASCADE = 0x0227
- WM_MDIICONARRANGE = 0x0228
- WM_MDIGETACTIVE = 0x0229
- WM_MDISETMENU = 0x0230
- WM_ENTERSIZEMOVE = 0x0231
- WM_EXITSIZEMOVE = 0x0232
- WM_DROPFILES = 0x0233
- WM_MDIREFRESHMENU = 0x0234
- WM_IME_SETCONTEXT = 0x0281
- WM_IME_NOTIFY = 0x0282
- WM_IME_CONTROL = 0x0283
- WM_IME_COMPOSITIONFULL = 0x0284
- WM_IME_SELECT = 0x0285
- WM_IME_CHAR = 0x0286
- WM_IME_REQUEST = 0x0288
- WM_IME_KEYDOWN = 0x0290
- WM_IME_KEYUP = 0x0291
- WM_MOUSEHOVER = 0x02A1
- WM_MOUSELEAVE = 0x02A3
- WM_CUT = 0x0300
- WM_COPY = 0x0301
- WM_PASTE = 0x0302
- WM_CLEAR = 0x0303
- WM_UNDO = 0x0304
- WM_RENDERFORMAT = 0x0305
- WM_RENDERALLFORMATS = 0x0306
- WM_DESTROYCLIPBOARD = 0x0307
- WM_DRAWCLIPBOARD = 0x0308
- WM_PAINTCLIPBOARD = 0x0309
- WM_VSCROLLCLIPBOARD = 0x030A
- WM_SIZECLIPBOARD = 0x030B
- WM_ASKCBFORMATNAME = 0x030C
- WM_CHANGECBCHAIN = 0x030D
- WM_HSCROLLCLIPBOARD = 0x030E
- WM_QUERYNEWPALETTE = 0x030F
- WM_PALETTEISCHANGING = 0x0310
- WM_PALETTECHANGED = 0x0311
- WM_HOTKEY = 0x0312
- WM_PRINT = 0x0317
- WM_PRINTCLIENT = 0x0318
- WM_HANDHELDFIRST = 0x0358
- WM_HANDHELDLAST = 0x035F
- WM_AFXFIRST = 0x0360
- WM_AFXLAST = 0x037F
- WM_PENWINFIRST = 0x0380
- WM_PENWINLAST = 0x038F
- WM_APP = 0x8000
- WM_USER = 0x0400
- WM_REFLECT = WM_USER + 0x1c00
- class WNDCLASSEX(ctypes.Structure):
- def __init__(self, *args, **kwargs):
- super(WNDCLASSEX, self).__init__(*args, **kwargs)
- self.cbSize = ctypes.sizeof(self)
- _fields_ = [("cbSize", ctypes.c_uint),
- ("style", ctypes.c_uint),
- ("lpfnWndProc", WNDPROC),
- ("cbClsExtra", ctypes.c_int),
- ("cbWndExtra", ctypes.c_int),
- ("hInstance", ctypes.wintypes.HANDLE),
- ("hIcon", ctypes.wintypes.HANDLE),
- ("hCursor", ctypes.wintypes.HANDLE),
- ("hBrush", ctypes.wintypes.HANDLE),
- ("lpszMenuName", ctypes.wintypes.LPCWSTR),
- ("lpszClassName", ctypes.wintypes.LPCWSTR),
- ("hIconSm", ctypes.wintypes.HANDLE)]
- ShowWindow = ctypes.windll.user32.ShowWindow
- ShowWindow.argtypes = [ctypes.wintypes.HWND, ctypes.c_int]
- def GenerateDummyWindow(callback, uid):
- newclass = WNDCLASSEX()
- newclass.lpfnWndProc = callback
- newclass.lpszClassName = uid.replace("-", "")
- ATOM = ctypes.windll.user32.RegisterClassExW(ctypes.byref(newclass))
- hwnd = ctypes.windll.user32.CreateWindowExW(0, newclass.lpszClassName, None, WS_POPUP, 0, 0, 0, 0, 0, 0, 0, 0)
- return hwnd
- # Message loop calls
- TIMERCALLBACK = ctypes.WINFUNCTYPE(None,
- ctypes.wintypes.HWND,
- ctypes.wintypes.UINT,
- ctypes.POINTER(ctypes.wintypes.UINT),
- ctypes.wintypes.DWORD)
- SetTimer = ctypes.windll.user32.SetTimer
- SetTimer.restype = ctypes.POINTER(ctypes.wintypes.UINT)
- SetTimer.argtypes = [ctypes.wintypes.HWND,
- ctypes.POINTER(ctypes.wintypes.UINT),
- ctypes.wintypes.UINT,
- TIMERCALLBACK]
- KillTimer = ctypes.windll.user32.KillTimer
- KillTimer.restype = ctypes.wintypes.BOOL
- KillTimer.argtypes = [ctypes.wintypes.HWND,
- ctypes.POINTER(ctypes.wintypes.UINT)]
- class MSG(ctypes.Structure):
- _fields_ = [ ('HWND', ctypes.wintypes.HWND),
- ('message', ctypes.wintypes.UINT),
- ('wParam', ctypes.wintypes.WPARAM),
- ('lParam', ctypes.wintypes.LPARAM),
- ('time', ctypes.wintypes.DWORD),
- ('pt', POINT)]
- GetMessage = ctypes.windll.user32.GetMessageW
- GetMessage.restype = ctypes.wintypes.BOOL
- GetMessage.argtypes = [ctypes.POINTER(MSG), ctypes.wintypes.HWND, ctypes.wintypes.UINT, ctypes.wintypes.UINT]
- TranslateMessage = ctypes.windll.user32.TranslateMessage
- TranslateMessage.restype = ctypes.wintypes.ULONG
- TranslateMessage.argtypes = [ctypes.POINTER(MSG)]
- DispatchMessage = ctypes.windll.user32.DispatchMessageW
- DispatchMessage.restype = ctypes.wintypes.ULONG
- DispatchMessage.argtypes = [ctypes.POINTER(MSG)]
- def LoadIcon(iconfilename, small=False):
- return LoadImage(0,
- str(iconfilename),
- IMAGE_ICON,
- 16 if small else 0,
- 16 if small else 0,
- LR_LOADFROMFILE)
- class NotificationIcon(object):
- def __init__(self, iconfilename, tooltip=None):
- assert os.path.isfile(str(iconfilename)), "{} doesn't exist".format(iconfilename)
- self._iconfile = str(iconfilename)
- self._hicon = LoadIcon(self._iconfile, True)
- assert self._hicon, "Failed to load {}".format(iconfilename)
- #self._pumpqueue = Queue.Queue()
- self._die = False
- self._timerid = None
- self._uid = uuid.uuid4()
- self._tooltip = str(tooltip) if tooltip else ''
- #self._thread = threading.Thread(target=self._run)
- #self._thread.start()
- self._info_bubble = None
- self.items = []
- def _bubble(self, iconinfo):
- if self._info_bubble:
- info_bubble = self._info_bubble
- self._info_bubble = None
- message = str(self._info_bubble)
- iconinfo.uFlags |= NIF_INFO
- iconinfo.szInfo = message
- iconinfo.szInfoTitle = message
- iconinfo.dwInfoFlags = NIIF_INFO
- iconinfo.union.uTimeout = 10000
- Shell_NotifyIcon(NIM_MODIFY, ctypes.pointer(iconinfo))
- def _run(self):
- self.WM_TASKBARCREATED = ctypes.windll.user32.RegisterWindowMessageW('TaskbarCreated')
- self._windowproc = WNDPROC(self._callback)
- self._hwnd = GenerateDummyWindow(self._windowproc, str(self._uid))
- iconinfo = NOTIFYICONDATA()
- iconinfo.hWnd = self._hwnd
- iconinfo.uID = 100
- iconinfo.uFlags = NIF_ICON | NIF_SHOWTIP | NIF_MESSAGE | (NIF_TIP if self._tooltip else 0)
- iconinfo.uCallbackMessage = WM_MENUCOMMAND
- iconinfo.hIcon = self._hicon
- iconinfo.szTip = self._tooltip
- Shell_NotifyIcon(NIM_ADD, ctypes.pointer(iconinfo))
- self.iconinfo = iconinfo
- PostMessage(self._hwnd, WM_NULL, 0, 0)
- message = MSG()
- last_time = -1
- ret = None
- while not self._die:
- try:
- ret = GetMessage(ctypes.pointer(message), 0, 0, 0)
- TranslateMessage(ctypes.pointer(message))
- DispatchMessage(ctypes.pointer(message))
- except Exception as err:
- # print "NotificationIcon error", err, message
- message = MSG()
- time.sleep(0.125)
- print("Icon thread stopped, removing icon (hicon: %s, hwnd: %s)..." % (self._hicon, self._hwnd))
- Shell_NotifyIcon(NIM_DELETE, ctypes.cast(ctypes.pointer(iconinfo), ctypes.POINTER(NOTIFYICONDATA)))
- ctypes.windll.user32.DestroyWindow(self._hwnd)
- ctypes.windll.user32.DestroyIcon.argtypes = [ctypes.wintypes.HICON]
- ctypes.windll.user32.DestroyIcon(self._hicon)
- def _menu(self):
- if not hasattr(self, 'items'):
- return
- menu = CreatePopupMenu()
- func = None
- try:
- iidx = 1000
- defaultitem = -1
- item_map = {}
- for fs in self.items:
- iidx += 1
- if isinstance(fs, str):
- if fs and not fs.strip('-_='):
- AppendMenu(menu, MF_SEPARATOR, iidx, fs)
- else:
- AppendMenu(menu, MF_STRING | MF_GRAYED, iidx, fs)
- elif isinstance(fs, tuple):
- if callable(fs[0]):
- itemstring = fs[0]()
- else:
- itemstring = str(fs[0])
- flags = MF_STRING
- if itemstring.startswith("!"):
- itemstring = itemstring[1:]
- defaultitem = iidx
- if itemstring.startswith("+"):
- itemstring = itemstring[1:]
- flags = flags | MF_CHECKED
- itemcallable = fs[1]
- item_map[iidx] = itemcallable
- if itemcallable is False:
- flags = flags | MF_DISABLED
- elif not callable(itemcallable):
- flags = flags | MF_GRAYED
- AppendMenu(menu, flags, iidx, itemstring)
- if defaultitem != -1:
- SetMenuDefaultItem(menu, defaultitem, 0)
- pos = POINT()
- GetCursorPos(ctypes.pointer(pos))
- PostMessage(self._hwnd, WM_NULL, 0, 0)
- SetForegroundWindow(self._hwnd)
- ti = TrackPopupMenu(menu, TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, pos.x, pos.y, 0, self._hwnd, None)
- if ti in item_map:
- func = item_map[ti]
- PostMessage(self._hwnd, WM_NULL, 0, 0)
- finally:
- DestroyMenu(menu)
- if func: func()
- def clicked(self):
- self._menu()
- def _callback(self, hWnd, msg, wParam, lParam):
- # Check if the main thread is still alive
- if msg == WM_TIMER:
- if not any(thread.getName() == 'MainThread' and thread.isAlive()
- for thread in threading.enumerate()):
- self._die = True
- elif msg == WM_MENUCOMMAND and lParam == WM_LBUTTONUP:
- self.clicked()
- elif msg == WM_MENUCOMMAND and lParam == WM_RBUTTONUP:
- self._menu()
- elif msg == self.WM_TASKBARCREATED: # Explorer restarted, add the icon again.
- Shell_NotifyIcon(NIM_ADD, ctypes.pointer(self.iconinfo))
- else:
- return DefWindowProc(hWnd, msg, wParam, lParam)
- return 1
- def die(self):
- self._die = True
- PostMessage(self._hwnd, WM_NULL, 0, 0)
- time.sleep(0.2)
- try:
- Shell_NotifyIcon(NIM_DELETE, self.iconinfo)
- except Exception as err:
- print("Icon remove error", err)
- ctypes.windll.user32.DestroyWindow(self._hwnd)
- ctypes.windll.user32.DestroyIcon(self._hicon)
- def pump(self):
- try:
- while not self._pumpqueue.empty():
- callable = self._pumpqueue.get(False)
- callable()
- except queue_Empty:
- pass
- def announce(self, text):
- self._info_bubble = text
- def hideConsole():
- ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)
- def showConsole():
- ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 1)
- def hasConsole():
- return ctypes.windll.kernel32.GetConsoleWindow() != 0
- if __name__ == "__main__":
- import time
- def greet():
- ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)
- print("Hello")
- def quit():
- ni._die = True
- def announce():
- ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 1)
- ni.announce("Hello there")
- def clicked():
- ni.announce("Hello")
- def dynamicTitle():
- return "!The time is: %s" % time.time()
- ni = NotificationIcon(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../trayicon.ico'), "ZeroNet 0.2.9")
- ni.items = [
- (dynamicTitle, False),
- ('Hello', greet),
- ('Title', False),
- ('!Default', greet),
- ('+Popup bubble', announce),
- 'Nothing',
- '--',
- ('Quit', quit)
- ]
- ni.clicked = clicked
- import atexit
- @atexit.register
- def goodbye():
- print("You are now leaving the Python sector.")
- ni._run()
|