# A "Python console" for Windows CE. # # Also works on NT/9x (with a few limitations!) - useful for debugging! # # Used 2 threads - one for UI (ie, the message loop) and another thread # for executing Python code. Uses very simple events to synchronise the 2! # # 02/10/03 13:43 # Added SimpleShell:readline method for input() function. # Added SimpleShell:flush method for some modules that try to flush on sys.stdout. # - Telion # # 02/10/14 21:22 # Fix for HourGlass poping up at input() # myWindowTitle = "Python CE" import sys, os from win32gui import * from win32event import * import string import thread, threading import traceback import code # std module for compilation utilities. import new import imp if sys.platform == 'Pocket PC': isPocketPC = 1 else: isPocketPC = 0 if sys.platform=="wince" or isPocketPC: OutputDebugString = NKDbgPrintfW else: from win32api import OutputDebugString # wince doesn't have CWD, so use program name basename as element of sys.path IDOK=1 IDCANCEL=2 GWL_WNDPROC=-4 FIXED_PITCH=1 ANSI_FIXED_FONT=11 IDC_WAIT = 32514 HWND_TOP=0 CS_VREDRAW=1 CS_HREDRAW=2 CW_USEDEFAULT=0x80000000 WM_CREATE=1 WM_CHAR=258 WM_COMMAND=273 WM_DESTROY=2 WM_QUIT=18 WM_SETFOCUS=7 WM_SETFONT=48 WM_SETREDRAW=11 WM_SIZE=5 WM_USER=1024 WM_SETTINGCHANGE=0x001a WM_ACTIVATE=0x6 EN_SETFOCUS=0x0100 EN_KILLFOCUS=0x0200 WM_PAINT=0xf WM_INITDIALOG=0x110 WHITE_BRUSH=0 SW_SHOW=5 SW_SHOWNORMAL=1 WS_SYSMENU=524288 WS_CLIPCHILDREN=33554432 WS_CHILD=1073741824 WS_VISIBLE=268435456 WS_BORDER=0x00800000 WS_HSCROLL=1048576 WS_VSCROLL=2097152 if sys.platform=="wince" or sys.platform == 'Pocket PC': WS_OVERLAPPEDWINDOW=0 else: WS_OVERLAPPEDWINDOW=13565952 EM_GETLINECOUNT=186 EM_GETSEL=176 EM_LINEINDEX=187 EM_LINEFROMCHAR=201 EM_LINELENGTH=193 EM_SETSEL=177 EM_REPLACESEL=194 ES_LEFT=0 ES_MULTILINE=4 ES_WANTRETURN=4096 ES_AUTOVSCROLL=64 ES_AUTOHSCROLL=128 IDR_MENU=101 IDM_EXIT=40001 IDM_ABOUT=40002 IDD_ABOUT=7234 if UNICODE: TEXT = Unicode else: TEXT = lambda x: x try: sys.ps1 except AttributeError: sys.ps1 = ">>> " sys.ps2 = "... " class SimpleShell: editMessageMap = {} def __init__(self): self.bInteract = 0 # Am I interacting? self.hwnd = None self.hwndEdit = None self.outputFile = None # optional output file self.outputQueue = [] self.outputQueueLock = threading.Lock() # Allocate some events for thread sync self.currentBlockItems = None self.eventInteractiveInputAvailable = CreateEvent(None, 0, 0, None) self.eventClosed = CreateEvent(None, 0, 0, None) # Added for input( ), etc.. self.reading = '' # readline flag and data self.pChar = 0 # readline current line number self.pLine = 0 # readline current line length def __del__(self): print "InteractiveManager dieing" if self.outputFile: del self.outputFile def write(self, text): if self.outputFile: try: self.outputFile.write(text) except: pass text = string.replace(text, "\n", "\r\n") self.outputQueueLock.acquire() self.outputQueue.append(text) self.outputQueueLock.release() try: PostMessage(self.hwnd, WM_USER, 0, 0) except: pass def Run(self): PumpMessages() def GetEditMessageMap(self): return {WM_CHAR : self.OnEditChar} def GetParentMessageMap(self): map={} map[WM_DESTROY] = self.OnParentDestroy map[WM_SIZE] = self.OnParentSize map[WM_SETFOCUS] = self.OnParentSetFocus map[WM_USER] = self.OnParentUser map[WM_COMMAND] = self.OnParentCommand map[WM_SETTINGCHANGE]= self.onParentSettingChange map[WM_ACTIVATE] = self.onParentActivate map[WM_CREATE] = self.onParentCreate return map def Init(self): try: self.hinst = GetModuleHandle(None) except NameError: # Not on CE?? self.hinst = sys.hinst # But this is :-) InitCommonControls() wc = WNDCLASS() wc.hInstance = self.hinst wc.style=CS_HREDRAW | CS_VREDRAW wc.hbrBackground = GetStockObject(WHITE_BRUSH) wc.lpszClassName = TEXT("PYTHON_CE") # This code passes a dictionary as the "wndproc", rather than a function. wc.lpfnWndProc = self.GetParentMessageMap() #self.MainWndProc self.classAtom = RegisterClass(wc) cx = CW_USEDEFAULT cy = CW_USEDEFAULT x = CW_USEDEFAULT y = CW_USEDEFAULT if sys.platform=="wince": style = WS_CLIPCHILDREN elif isPocketPC: style = WS_VISIBLE | WS_BORDER global sinfo, rect sinfo = SIPINFO() if not SipGetInfo(sinfo): raise RuntimeError('Unexpected error from SipGetInfo') rect = sinfo.rcVisibleDesktop cx = rect[2] - rect[0] # right - left cy = rect[3] - rect[1] # height x = rect[0] y = rect[1] if sinfo.fdwFlags & SIPF_ON: cy -= 26 # MENU_HEIGHT OutputDebugString('SIPF is on\r\n') else: style = WS_OVERLAPPEDWINDOW self.hwnd = CreateWindow( self.classAtom, myWindowTitle, style, \ x, y, cx, cy, \ 0, 0, self.hinst, None) left, top, right, bottom = GetClientRect(self.hwnd) # print sys.platform, type(sys.platform) if sys.platform=="wince": self.hCmdBar = CommandBar_Create(self.hinst, self.hwnd, 1) CommandBar_InsertMenubar(self.hCmdBar, self.hinst, IDR_MENU, 0) CommandBar_AddAdornments(self.hCmdBar, 0, 0) top = CommandBar_Height(self.hCmdBar) elif isPocketPC: # setup PPC menu here shmb = SHMENUBARINFO() shmb.hwndParent = self.hwnd shmb.nToolBarId = IDR_MENU shmb.hInstRes = self.hinst if SHCreateMenuBar(shmb): self.hwndMB = shmb.hwndMB else: OutputDebugString('init menu bar failed') self.hwndMB = None OutputDebugString('plat is '+sys.platform) style = WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_HSCROLL|ES_LEFT|ES_MULTILINE|ES_WANTRETURN|ES_AUTOHSCROLL self.hwndEdit=CreateWindow("EDIT", None, style, \ left, top, (right-left), (bottom-top), \ self.hwnd, 0, self.hinst, None) self.oldEditWndProc = SetWindowLong(self.hwndEdit, GWL_WNDPROC, self.GetEditMessageMap())# self.EditWndProc) if sys.platform != "wince" and not isPocketPC: SendMessage(self.hwndEdit, WM_SETFONT, GetStockObject(ANSI_FIXED_FONT), 0) ShowWindow(self.hwnd, SW_SHOW) UpdateWindow(self.hwnd) EnableWindow(self.hwndEdit, 1) SetFocus(self.hwndEdit) SetCursor(LoadCursor(0,0)) def Term(self): try: PostMessage(self.hwnd, WM_QUIT, 0, 0) except: pass UnregisterClass(self.classAtom, self.hinst) def flush(self): return def readline(self): SetCursor(LoadCursor(0,0)) # This should eliminate HourGlass #print "\nInside shell.readline" #pr = '-->' #lpr = len(pr) #sys.stdout.write( "\n"+pr) self.reading = 'on' #pypp rc = WaitForMultipleObjects( (self.eventInteractiveInputAvailable, self.eventClosed), 0, INFINITE) rc = WaitForMultipleObjects( (self.eventInteractiveInputAvailable, self.eventClosed), 0, INFINITE) #pypp if rc == WAIT_OBJECT_0: if rc == WAIT_OBJECT_0: s = self.reading else: s='Debug shell.readline nothing' #print "Going Out shell.readline with "+s self.reading = '' #return repr(s) return s def OnEditChar(self,hWnd, msg, wparam, lparam):# WindowProc for WM_CHAR call global pk # readline patch for getting currnt cursor pos by Telion if self.reading != '':# readline going on if self.reading == 'on':# first time after readline started self.reading = 'on2' # change it to avoid coming back #pypp self.pChar=SendMessage(hWnd, EM_LINEINDEX, -1) # get current line index self.pChar=SendMessage(hWnd, 187, -1) # get current line index #pypp self.pLine=SendMessage(hWnd, EM_LINELENGTH, self.pChar)# get the length self.pLine=SendMessage(hWnd, 193, self.pChar)# get the length #if 1: #pk.write(str(wparam)+"\n") if wparam!=0x0D: # if it is not return key, pass it to original procedure # by inserting other code here,, you may capture and deal with other keys return CallWindowProc(self.oldEditWndProc, hWnd, msg, wparam, lparam) else: # if it is return key #pypp cChar=SendMessage(hWnd, EM_LINEINDEX, -1) cChar=SendMessage(hWnd, 187, -1) #pypp cLine=SendMessage(hWnd, EM_LINEFROMCHAR, cChar) cLine=SendMessage(hWnd, 201, cChar) if self.reading != '': # readline in underway self.reading = str(Edit_GetLine(hWnd, cLine, 512)) # get current line if cChar != self.pChar: # if user moved away from input line pass # just return enitre line as the result else: if len(self.reading) < self.pLine: # if user erased prompt pass # just return enitre line as the result else: # self.reading = self.reading[self.pLine:]+'X-X'+repr(cChar)+','+repr(self.pChar)+"x"+repr(cLine)+','+repr(self.pLine)+"<= 0: line = str(Edit_GetLine(hWnd, cLine, maxLineSize)) if line[:4]==sys.ps1: blockStart = cLine break elif line[:4]!=sys.ps2: break cLine = cLine -1 if blockStart>=0: # Find the end of the block. while 1: cLine = cLine + 1 line = str(Edit_GetLine(hWnd, cLine, maxLineSize)) if line is None or line[:4]!=sys.ps2: break blockEnd = cLine # blockStart is the first line # blockEnd is one past the block end. firstLine=str(Edit_GetLine(hWnd, blockStart, maxLineSize))[len(sys.ps1):] # Special case for an empty command - mimic Python better by writing another ">>>" if len(firstLine)==0 and blockStart+1==blockEnd: # Empty prompt - write a new one. self.write("\n"+sys.ps1) else: items = [firstLine] for cLine in range(blockStart+1, blockEnd): items.append( str(Edit_GetLine(hWnd, cLine, maxLineSize))[len(sys.ps2):] ) # If the block is not at the end of the control, copy it there... if blockEnd != numLines: self.write("\n%s%s" % (sys.ps1, items[0])) for item in items[1:]: self.write("\n%s%s" % (sys.ps2, items[0])) else: # Ready to execute. self.currentBlockItems = items codeText = string.join(self.currentBlockItems, '\n') SetEvent(self.eventInteractiveInputAvailable) else: # Not in a block - write a new prompt. self.write("\n"+sys.ps1) ShowCaret(hWnd) return 0 # end of return key processing #return CallWindowProc(self.oldEditWndProc, hWnd, msg, wparam, lparam) # ^this is not needed any more def onParentSettingChange(self, hwnd, msg, wParam, lParam): """set sip""" OutputDebugString('parent setting change wParam='+str(wParam)+' ? '+str(SPI_SETSIPINFO)+"\r\n") if SPI_SETSIPINFO == wParam: sai = SHACTIVATEINFO() SHHandleWMSettingChange(hwnd,wParam,lParam,sai) def onParentActivate(self, hwnd, msg, wParam, lParam): """more sip setup""" OutputDebugString('parent activate wParam='+str(wParam)+' ? '+str(SPI_SETSIPINFO)+"\r\n") if SPI_SETSIPINFO == wParam: sai = SHACTIVATEINFO() SHHandleWMActivate(hwnd,wParam,lParam,sai,0) def onParentCreate(self, hwnd, msg, wparam, lparam): """Create window """ OutputDebugString('WM_CREATE') if isPocketPC: # setup PPC menu here shmb = SHMENUBARINFO() shmb.hwndParent = self.hwnd shmb.nToolBarId = 400 shmb.hInstRes = self.hinst if SHCreateMenuBar(shmb): self.hwndMB = shmb.hwndMB else: OutputDebugString('init menu bar failed') self.hwndMB = None def OnParentSize(self, hwnd, msg, wparam, lparam): left, top, right, bottom = GetClientRect(hwnd) if not isPocketPC: try: top=CommandBar_Height(self.hCmdBar); except NameError: # Only on CE pass if self.hwndEdit is not None: SetWindowPos(self.hwndEdit, HWND_TOP, left, top, right-left, bottom-top, 0) ShowWindow(self.hwndEdit, SW_SHOWNORMAL) def OnParentDestroy(self, hwnd, msg, wparam, lparam): PostQuitMessage(hwnd) # And tell the thread waiting for us we are done! SetEvent(self.eventClosed) def OnParentSetFocus(self, hwnd, msg, wparam, lparam): if self.hwndEdit is not None: SetFocus(self.hwndEdit) def OnParentUser(self, hwnd, msg, wparam, lparam): # Out write function post this message. # We dequeue the output, and write the text. while self.outputQueue: self.outputQueueLock.acquire() text = string.join(self.outputQueue, '') self.outputQueue = [] self.outputQueueLock.release() SendMessage(self.hwndEdit, EM_SETSEL, -2, -2) # Now check that we wont fill the control. # If so, remove the first lines until we are OK. selInfo = SendMessage(self.hwndEdit, EM_GETSEL, 0, 0) endPos = HIWORD(selInfo) lineLookIndex = 0 lineLookLength = 0 while endPos + len(text) - lineLookLength > 29000: lineLookIndex = lineLookIndex + 1 lineLookLength = SendMessage(self.hwndEdit, EM_LINEINDEX, lineLookIndex, 0) if lineLookIndex > 0: # The SETREDRAW has no effect on CE. If we really want this # I think we must respond to WM_PAINT, and ignore it for the duration # the redraw is turned off. SendMessage(self.hwndEdit, WM_SETREDRAW, 0, 0) SendMessage(self.hwndEdit, EM_SETSEL, 0, lineLookLength) SendMessage(self.hwndEdit, EM_REPLACESEL, 0, TEXT("")) # And back to the end. SendMessage(self.hwndEdit, EM_SETSEL, -2, -2) SendMessage(self.hwndEdit, WM_SETREDRAW, 1, 0) SendMessage(self.hwndEdit, EM_REPLACESEL, 0, TEXT(text)) def OnParentCommand(self, hwnd, msg, wparam, lparam): command = LOWORD(wparam) if command == IDM_EXIT: DestroyWindow(hwnd); elif command == IDM_ABOUT: DialogBox(self.hinst, IDD_ABOUT, hwnd, AboutBoxDlgProc) return 0 def AboutBoxDlgProc(hwnd, msg, wparam, lparam): if msg==WM_COMMAND: p=LOWORD(wparam) if p==IDOK or p==IDCANCEL: EndDialog(hwnd, 1) return 1 elif msg==WM_INITDIALOG: if isPocketPC: shidi = SHINITDLGINFO() shidi.dwMask = SHIDIM_FLAGS shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIZEDLGFULLSCREEN | SHIDIF_SIPDOWN shidi.hDlg = hwnd SHInitDialog(shidi) return 0 def Interact(shell): shell.bInteract = 1 locals = {} sys.stdout.write("Python %s on %s\n%s" % (sys.version, sys.platform, sys.ps1)) while 1: rc = WaitForMultipleObjects( (shell.eventInteractiveInputAvailable, shell.eventClosed), 0, INFINITE) if rc == WAIT_OBJECT_0: codeText = string.join(shell.currentBlockItems, '\n') try: codeOb = code.compile_command(codeText) except SyntaxError: sys.stdout.write("\n") list = traceback.print_exc(0) sys.stdout.write(sys.ps1) continue except: traceback.print_exc() continue if codeOb is None: sys.stdout.write("\n%s" % sys.ps2) continue sys.stdout.write("\n") SetCursor(LoadCursor(0, IDC_WAIT)) try: try: exec codeOb in locals except SystemExit: raise except: exc_type, exc_value, exc_traceback = sys.exc_info() l = len(traceback.extract_tb(sys.exc_traceback)) try: 1/0 except: m = len(traceback.extract_tb(sys.exc_traceback)) traceback.print_exception(exc_type, exc_value, exc_traceback, l-m) exc_traceback = None # Prevent a cycle finally: SetCursor(LoadCursor(0, 0)) sys.stdout.write(sys.ps1) else: break def RunCode(shell): try: # copy sys.argv before we stomp on it! sys.appargv = sys.argv[:] bKeepOpen = 0 bInteract = 1 cmdToExecute = None # Process sys.argv, removing args as we process them so any scripts # see _their_ argv! del sys.argv[0] # Remove some params the WCE debugger sometimes adds: sys.argv=filter(lambda arg: arg[:4]!="/WCE", sys.argv) i=0 while i < len(sys.argv): if not sys.argv[i] or sys.argv[i][0]!='-': break if sys.argv[i]=='-i': bInteract = 1 del sys.argv[i] continue elif sys.argv[i]=='-o' or sys.argv[i]=='-oa': # copy output to file if sys.argv[i] == "-oa": shell.outputFile = open(sys.argv[i+1],'at') else: shell.outputFile = open(sys.argv[i+1],'wt') del sys.argv[i] del sys.argv[i] i = i + 1 elif sys.argv[i]=='-c': cmdToExecute = string.join(sys.argv[i+1:], ' ') sys.argv = sys.argv[:i-1] break i = i + 1 if not sys.argv: sys.argv=[''] if cmdToExecute is not None: try: exec cmdToExecute except: traceback.print_exc() bKeepOpen = 1 elif len(sys.argv)>0 and sys.argv[0]: # Shift the args back to it sees itself as sys.argv[0] # Execute the named script fname = sys.argv[0] sys.path = [os.path.dirname(fname)] + sys.path OutputDebugString('argv0 %s\n' % sys.argv[0]) ext = os.path.splitext(fname)[1] if ext=='.pyc': mode="rb" imp_params=("pyc", mode, imp.PY_COMPILED) else: mode="r" imp_params=("py", mode, imp.PY_SOURCE) try: file = open(fname, mode) except IOError, (code, why): print "python: can't open %s: %s\n" % (fname, why) bKeepOpen = 1 file = None if file: try: try: imp.load_module("__main__", file, fname, imp_params) except: traceback.print_exc() bKeepOpen = 1 finally: file.close() else: bInteract = 1 if bInteract: try: Interact(shell) except SystemExit: bKeepOpen = 0 except: traceback.print_exc() bKeepOpen = 1 if not bKeepOpen: SendMessage(shell.hwnd, WM_COMMAND, IDM_EXIT, 0) except: traceback.print_exc() def main(): # We run the shell in the main thread, so that when it terminates # (accidently or otherwise) the application terminates. # A seperate thread is used to execute the Python code. # simple test for now, get smarter later global theOtherWindow global winList theOtherWindow = None winList = [] def enumProc(hwnd,l): global theOtherWindow global winList t = GetWindowText(hwnd) winList.append(t) if t == myWindowTitle: theOtherWindow = hwnd return 0 return 1 try: EnumWindows(enumProc,0) except: pass if theOtherWindow: SetForegroundWindow(theOtherWindow) return 0 __name__ = sys.argv[0] # Make "shell" global just for debugging purposes # ie, so interactive code can see it via __main__.shell (or "ceshell.shell" on CE) global shell shell = SimpleShell() global shellThreadId shellThreadId = thread.get_ident() # Create the windows, but dont start the message loop yet. shell.Init() # Can now write to the shell - assign the standard files. oldOut, oldErr, oldIn = sys.stdout, sys.stderr, sys.stdin sys.stderr = shell sys.stdout = shell sys.stdin = shell # Create the new thread to execute the code. thread.start_new(RunCode, (shell,) ) # Now run the shell. shell.Run() shell.Term() sys.stdout = oldOut sys.stderr = oldErr sys.stdin = oldIn # On Windows, run this as a script. # On CE, this module is imported and main() executed by # the startup C code. if __name__=='__main__': main()