# Creates a task-bar icon. Run from Python.exe to see the# messages printed. Takes an optional logfile as first command# line parameterimportsysifhasattr(sys,"frozen"):classBlackHole(object):closed=Truesoftspace=0defwrite(self,data):passdefclose(self):passdefflush(self):passsys.stdout=BlackHole()sys.stderr=BlackHole()importosimporttimeimportthreadingimportcStringIOimportQueueimporttracebackimportgctry:fromwin32apiimport*fromwin32guiimport*importwin32pipeimportwin32conimportwin32eventimportwin32fileimportwinerrorimportpywintypesimportwin32securityexceptImportError,e:print'Fatal error at startup',esys.exit(1)frommercurialimportdemandimportdemandimport.ignore.append('win32com.shell')demandimport.enable()frommercurialimportui,errorfrommercurial.windowsimportposixfile,unlink,renamefromtortoisehg.util.i18nimportagettextas_fromtortoisehg.utilimportthread2,paths,shlib,versionAPP_TITLE=_('TortoiseHg Overlay Icon Server')EXIT_CMD=1025classLogger():def__init__(self):self.file=Nonedefsetfile(self,name):oname=name+'.old'try:rename(name,oname)except:passself.file=posixfile(name,'wb')self.msg('%s, Version %s'%(APP_TITLE,version.version()))self.msg('Logging to file started')defmsg(self,msg):ts='[%s] '%time.strftime('%c')f=self.fileiff:f.write(ts+msg+'\n')f.flush()os.fsync(f.fileno())print'L'+ts+msgelse:printts+msglogger=Logger()defSetIcon(hwnd,name,add=False):# Try and find a custom iconif'--noicon'insys.argv:returnprint"SetIcon(%s)"%namehinst=GetModuleHandle(None)fromtortoisehg.util.pathsimportget_tortoise_iconiconPathName=get_tortoise_icon(name)ificonPathNameandos.path.isfile(iconPathName):icon_flags=win32con.LR_LOADFROMFILE|win32con.LR_DEFAULTSIZEhicon=LoadImage(hinst,iconPathName,win32con.IMAGE_ICON,0,0,icon_flags)else:print"Can't find a Python icon file - using default"hicon=LoadIcon(0,win32con.IDI_APPLICATION)flags=NIF_ICON|NIF_MESSAGE|NIF_TIPnid=(hwnd,0,flags,win32con.WM_USER+20,hicon,APP_TITLE)action=NIM_MODIFYifadd:action=NIM_ADDtry:Shell_NotifyIcon(action,nid)exceptpywintypes.error:# This is common when windows is starting, and this code is hit# before the taskbar has been created.print"Failed to add the taskbar icon - is explorer running?"# but keep running anyway - when explorer starts, we get the# TaskbarCreated message.classMainWindow:def__init__(self):self.pipethread=Nonemsg_TaskbarRestart=RegisterWindowMessage("TaskbarCreated");message_map={msg_TaskbarRestart:self.OnRestart,win32con.WM_DESTROY:self.OnDestroy,win32con.WM_COMMAND:self.OnCommand,win32con.WM_USER+20:self.OnTaskbarNotify,}# Register the Window class.wc=WNDCLASS()hinst=wc.hInstance=GetModuleHandle(None)wc.lpszClassName="THgRpcServer"wc.style=win32con.CS_VREDRAW|win32con.CS_HREDRAW;wc.hCursor=LoadCursor(0,win32con.IDC_ARROW)wc.hbrBackground=win32con.COLOR_WINDOWwc.lpfnWndProc=message_map# could also specify a wndproc.classAtom=RegisterClass(wc)# Create the Window.style=win32con.WS_OVERLAPPED|win32con.WS_SYSMENUself.hwnd=CreateWindow(classAtom,APP_TITLE,style, \
0,0,win32con.CW_USEDEFAULT,win32con.CW_USEDEFAULT, \
0,0,hinst,None)UpdateWindow(self.hwnd)self._DoCreateIcons()def_DoCreateIcons(self):show,highlight=get_config()ifshow:SetIcon(self.hwnd,"hg.ico",add=True)# start namepipe server for hg statusself.start_pipe_server()defOnRestart(self,hwnd,msg,wparam,lparam):logger.msg("MainWindow.OnRestart")self._DoCreateIcons()defOnDestroy(self,hwnd,msg,wparam,lparam):logger.msg("MainWindow.OnDestroy")nid=(self.hwnd,0)try:Shell_NotifyIcon(NIM_DELETE,nid)exceptpywintypes.error:pass# happens when we run without iconPostQuitMessage(0)# Terminate the app.defOnTaskbarNotify(self,hwnd,msg,wparam,lparam):iflparam==win32con.WM_RBUTTONUPorlparam==win32con.WM_LBUTTONUP:menu=CreatePopupMenu()# AppendMenu(menu, win32con.MF_SEPARATOR, 0, '')AppendMenu(menu,win32con.MF_STRING,EXIT_CMD,_('Exit'))pos=GetCursorPos()# See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/menus_0hdi.asptry:SetForegroundWindow(self.hwnd)TrackPopupMenu(menu,win32con.TPM_LEFTALIGN,pos[0],pos[1],0,self.hwnd,None)PostMessage(self.hwnd,win32con.WM_NULL,0,0)exceptpywintypes.error:passreturn1defOnCommand(self,hwnd,msg,wparam,lparam):id=LOWORD(wparam)ifid==EXIT_CMD:self.exit_application()else:print"Unknown command -",iddefexit_application(self):logger.msg("MainWindow.exit_application")ifself.stop_pipe_server():DestroyWindow(self.hwnd)logger.msg("Goodbye")defstop_pipe_server(self):logger.msg("MainWindow.stop_pipe_server")ifnotself.pipethread.isAlive():logger.msg("pipethread is not alive")returnTrue# Try the nice way firstself.svc.SvcStop()max_try=10cnt=1whilecnt<=max_tryandself.pipethread.isAlive():print"testing pipe [try %d] ..."%cnttry:try:self.pipethread.terminate()exceptValueError:passwin32pipe.CallNamedPipe(PIPENAME,'',PIPEBUFSIZE,0)except:logger.msg(traceback.format_exc())passcnt+=1ifself.pipethread.isAlive():msg="WARNING: unable to stop server after %d trys."%max_trylogger.msg(msg)returnFalseelse:returnTruedefstart_pipe_server(self):ifself.pipethreadisnotNone:returndefservepipe():self.svc=PipeServer(self.hwnd)self.svc.SvcDoRun()self.pipethread=thread2.Thread(target=servepipe)self.pipethread.start()PIPENAME=r"\\.\pipe\TortoiseHgRpcServer-bc0c27107423-"PIPENAME+=GetUserName()PIPEBUFSIZE=4096defgetrepos(batch):roots=set()notifypaths=set()forpathinbatch:r=paths.find_root(path)ifrisNone:try:forninos.listdir(path):r=paths.find_root(os.path.join(path,n))if(risnotNone):roots.add(r)notifypaths.add(r)exceptException,e:# This exception raises in case of fixutf8 extension enabled# and folder name contains '0x5c'(backslash).logger.msg('Failed listdir %s (%s)'%(path,str(e)))else:roots.add(r);notifypaths.add(path)returnroots,notifypathsdefupdate_batch(batch):'''updates thgstatus for all paths in batch'''roots,notifypaths=getrepos(batch)ifroots:_ui=ui.ui();failedroots=set()errorstream=cStringIO.StringIO()_stderr=sys.stderrsys.stderr=errorstreamtry:forrinsorted(roots):try:shlib.update_thgstatus(_ui,r,wait=False)shlib.shell_notify([r])logger.msg('Updated '+r)except(IOError,OSError):print"IOError or OSError on updating %s (check permissions)"%rlogger.msg('Failed updating %s (check permissions)'%r)failedroots.add(r)except(error.Abort,error.ConfigError,error.RepoError,error.RevlogError,ImportError),e:logger.msg('Failed updating %s (%s)'%(r,str(e)))failedroots.add(r)notifypaths-=failedrootsifnotifypaths:time.sleep(2)shlib.shell_notify(list(notifypaths))logger.msg('Shell notified')errmsg=errorstream.getvalue()iferrmsg:logger.msg('stderr: %s'%errmsg)finally:sys.stderr=_stderrrequests=Queue.Queue(0)defget_config():show_taskbaricon=Truehgighlight_taskbaricon=Trueversion2cmenu=Falsetry:from_winregimportHKEY_CURRENT_USER,OpenKey,QueryValueExhkey=OpenKey(HKEY_CURRENT_USER,r'Software\TortoiseHg')t=('1','True')try:show_taskbaricon=QueryValueEx(hkey,'ShowTaskbarIcon')[0]intexceptEnvironmentError:passtry:hgighlight_taskbaricon=QueryValueEx(hkey,'HighlightTaskbarIcon')[0]intexceptEnvironmentError:pass# Upgrade user's context menu, once per major releasetry:version2cmenu=QueryValueEx(hkey,'ContextMenuVersion')[0]=='2'exceptEnvironmentError:passtry:ifnotversion2cmenu:from_winregimportCreateKey,SetValueEx,REG_SZtry:promoted=QueryValueEx(hkey,'PromotedItems')[0]exceptEnvironmentError:promoted=''plist=[i.strip()foriinpromoted.split(',')]hkey=CreateKey(HKEY_CURRENT_USER,r'Software\TortoiseHg')ifu'log'inplist:idx=plist.index(u'log')plist[idx]=u'workbench'SetValueEx(hkey,'PromotedItems',0,REG_SZ,','.join(plist))SetValueEx(hkey,'ContextMenuVersion',0,REG_SZ,'2')exceptEnvironmentError:passexcept(ImportError,WindowsError):passreturn(show_taskbaricon,hgighlight_taskbaricon)defupdate(args,hwnd):batch=[]r=args[0]print"got update request %s (first in batch)"%rbatch.append(r)print"wait a bit for additional requests..."show,highlight=get_config()ifshowandhighlight:SetIcon(hwnd,"hgB.ico")time.sleep(0.2)deferred_requests=[]try:whileTrue:req=requests.get_nowait()s=req.split('|')cmd,args=s[0],s[1:]ifcmd=='update':print"got update request %s"%reqbatch.append(args[0])else:deferred_requests.append(req)exceptQueue.Empty:passforreqindeferred_requests:requests.put(req)msg="processing batch with %i update requests"printmsg%len(batch)update_batch(batch)ifshowandhighlight:SetIcon(hwnd,"hg.ico")defremove(args):path=args[0]logger.msg('Removing '+path)roots,notifypaths=getrepos([path])ifroots:forrinsorted(roots):tfn=os.path.join(r,'.hg','thgstatus')try:f=posixfile(tfn,'rb')e=f.readline()f.close()ifnote.startswith('@@noicons'):unlink(tfn)except(IOError,OSError),e:logger.msg("Error while trying to remove %s (%s)"%(tfn,e))passifnotifypaths:shlib.shell_notify(list(notifypaths))defdispatch(req,cmd,args,hwnd):print"dispatch(%s)"%reqifcmd=='update':update(args,hwnd)elifcmd=='remove':remove(args)elifcmd=='error':logger.msg("**** Error: %s"%args[0])else:logger.msg("**** Error: unknown request '%s'"%req)classUpdater(threading.Thread):def__init__(self,hwnd):threading.Thread.__init__(self)self.hwnd=hwnddefrun(self):whileTrue:req=requests.get()s=req.split('|')cmd,args=s[0],s[1:]ifcmd=='terminate':logger.msg('Updater thread terminating')returndispatch(req,cmd,args,self.hwnd)gc.collect()classPipeServer:def__init__(self,hwnd):self.hwnd=hwndself.updater=Updater(hwnd)self.updater.start()# Create an event which we will use to wait on.# The "service stop" request will set this event.self.hWaitStop=win32event.CreateEvent(None,0,0,None)# We need to use overlapped IO for this, so we dont block when# waiting for a client to connect. This is the only effective way# to handle either a client connection, or a service stop request.self.overlapped=pywintypes.OVERLAPPED()# And create an event to be used in the OVERLAPPED object.self.overlapped.hEvent=win32event.CreateEvent(None,0,0,None)defSvcStop(self):logger.msg("PipeServer.SvcStop")win32event.SetEvent(self.hWaitStop)requests.put('terminate|')defSvcDoRun(self):logger.msg("PipeServer.SvcDoRun")# We create our named pipe.pipeName=PIPENAMEopenMode=win32pipe.PIPE_ACCESS_DUPLEX|win32file.FILE_FLAG_OVERLAPPEDpipeMode=win32pipe.PIPE_TYPE_MESSAGE# When running as a service, we must use special security for the pipesa=pywintypes.SECURITY_ATTRIBUTES()# Say we do have a DACL, and it is empty# (ie, allow full access!)sa.SetSecurityDescriptorDacl(1,None,0)pipeHandle=win32pipe.CreateNamedPipe(pipeName,openMode,pipeMode,win32pipe.PIPE_UNLIMITED_INSTANCES,0,0,6000,# default buffers, and 6 second timeout.sa)# Loop accepting and processing connectionswhileTrue:try:hr=win32pipe.ConnectNamedPipe(pipeHandle,self.overlapped)exceptpywintypes.error,inst:logger.msg("Error connecting pipe: %s"%inst)pipeHandle.Close()breakifhr==winerror.ERROR_PIPE_CONNECTED:# Client is fast, and already connected - signal eventwin32event.SetEvent(self.overlapped.hEvent)# Wait for either a connection, or a service stop request.timeout=win32event.INFINITEwaitHandles=self.hWaitStop,self.overlapped.hEventrc=win32event.WaitForMultipleObjects(waitHandles,0,timeout)ifrc==win32event.WAIT_OBJECT_0:# Stop eventreturnelse:# read pipe and process requesttry:hr,data=win32file.ReadFile(pipeHandle,PIPEBUFSIZE)ifnotdata:raiseSystemExit# signal by dispatch terminatewin32pipe.DisconnectNamedPipe(pipeHandle)exceptwin32file.error:# Client disconnected without sending data# or before reading the response.# Thats OK - just get the next connectioncontinuetry:requests.put(data)ifdata=='terminate|':logger.msg('PipeServer received terminate from pipe')PostMessage(self.hwnd,win32con.WM_COMMAND,EXIT_CMD,0)breakexceptSystemExit:raiseSystemExit# interrupted by thread2.terminate()except:logger.msg("WARNING: something went wrong in requests.put")logger.msg(traceback.format_exc())status="ERROR"# Clean up when we exitself.SvcStop()RUNMUTEXNAME='thgtaskbar-'+GetUserName()defehook(etype,values,tracebackobj):elist=traceback.format_exception(etype,values,tracebackobj)logger.msg(''.join(elist))defmain():args=sys.argv[1:]sa=win32security.SECURITY_ATTRIBUTES()sa.SetSecurityDescriptorDacl(1,None,0)# allow full accessrunmutex=win32event.CreateMutex(sa,1,RUNMUTEXNAME)ifGetLastError()==winerror.ERROR_ALREADY_EXISTS:print"another instance is already running"returnlogfilename=Noneforarginargs:ifarg[0]=='-':passelse:logfilename=argiflogfilename:logger.setfile(logfilename)else:try:fromwin32com.shellimportshell,shellconappdir=shell.SHGetSpecialFolderPath(0,shellcon.CSIDL_APPDATA)exceptpywintypes.com_error:appdir=os.environ['APPDATA']logfilename=os.path.join(appdir,'TortoiseHg','OverlayServerLog.txt')try:os.makedirs(os.path.dirname(logfilename))exceptEnvironmentError:passlogger.setfile(logfilename)sys.excepthook=ehookw=MainWindow()PumpMessages()if__name__=='__main__':main()