Pour pouvoir s’immiscer dans la boucle principale d’une application WX, c’est à dire exécuter des fonctions sans action de la part de l’utilisateur, le plus simple est d’utiliser un thread.
Mais attention, il est impossible d’exécuter une fonction de votre code WX depuis un programme externe comme un thread.
La manipulation consiste donc à faire en sorte que le thread n’appelle aucune fonction de votre appli, mais lui envoie un événement, de la même manière qu’un bouton.
Ainsi, dans votre appli WX, vous créerez une fonction qui sera associé à cet événement ( bind ).
Voici un exemple avec un petit clavier virtuel, originalement développé pour le raspberry pi .
#!/usr/bin/env python # -*- coding: utf-8 -*- # # ################################################################### # # Copyright (C) DDRDEV 2015 # # Author Gonzague Defos du Rau # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # #################################################################### import threading, time, os def getWinId(TITLE): cmd = "xwininfo -root -tree | grep "+TITLE+" | awk -F' ' '{print $1}'" print cmd winId = os.popen(cmd).read().strip() winId = winId.strip() return winId # Check dependencies : wx and xlib from sys import exit, argv IMPORTERRORS = "" try: import wx except ImportError: IMPORTERRORS += "\nCan not import wx, you shall install python-wxgtk" try: from Xlib import X except ImportError: IMPORTERRORS += "\nCan not import Xlib.display, you shall install python-xlib" if IMPORTERRORS != "": print IMPORTERRORS exit() from Xlib.display import Display from Xlib.protocol import event # Check if XEVFILE is given, and build keys datas, see BuildXkeys() below if len(argv) > 1: XEVFILE = argv[1] else : XEVFILE = "xevLog" def BuildXkeys( xevLog ): ''' First build the X keycodes dictionary using xev : Type this command line "xev | grep -e state -e XLookupString > xevLog", and than press all keys you need ( all keys + Shift all keys + AltGr all keys ) . After this you can run this program. If you miss some keys, re-run the command line using the "append" sign so you can keep your xevLog and press only the missing keys : xev | grep -e state -e XLookupString >> xevLog ''' h = open( xevLog, "r") c = h.read() h.close() logLines = [ l.strip() for l in c.split("\n") if l.strip() != "" ] nLine = 0 XKEYS = {} XKEYSBYCARAC = {} SUPAKEYS = [] for line in logLines: if line.startswith("state"): datas = line.split(" ") if len(datas) > 6: state = datas[1].replace(",", "") keycode = datas[3] keysym = datas[5].replace(",", "") keyname = datas[6].replace(")", "").replace(",", "") tstAscii = logLines[nLine+1].split('"') if len(tstAscii) > 1: ascii = tstAscii[1] else: ascii = keyname if not keyname in SUPAKEYS: SUPAKEYS.append(keyname) print keyname if ascii in "æâ€êþÿûîœôô~ø¨£äßë‘’ðüïŀö´`≤«»©↓¬¿×÷¡µ§ù": ascii = unicode(ascii, 'utf-8') if not keyname in XKEYS: d = {"state":int(state,16), "keycode":int(keycode), "keysym":keysym, "ascii":ascii, "keyname": keyname } XKEYS[ keyname ] = d XKEYSBYCARAC[ascii] = d #print keyname, ascii, d, type(ascii) nLine += 1 return [ XKEYS, XKEYSBYCARAC, SUPAKEYS ] # Than build our keys dictionaries XKEYS, XKEYSBYCARAC, SUPAKEYS = BuildXkeys( XEVFILE ) # The X keyboard class Keyboard(): ''' This Keyboard() class is use to send the KeyPress event to X windows . ''' def __init__(self, parent): self.ready = False self.parent = parent self.display = Display() self.rootWindow = self.display.get_input_focus()._data["focus"] self.lastWindow = False self.currentWindow = False self.kbThread = KbThread(self) self.supaKeysOn = {} for k in ["Shift_L", "Shift_R"]: self.supaKeysOn[k] = False def ResetSupaKeys(self): for k in self.supaKeysOn: self.supaKeysOn[k] = False def KeyPress(self, key): if not self.lastWindow or not self.parent.wxWindow: print "No Window selected" return #if not self.ready : print "Please wait while initialising" keycode = False if key in XKEYSBYCARAC : keycode = XKEYSBYCARAC[key]["keycode"] state = XKEYSBYCARAC[key]["state"] elif key in XKEYS : keycode = XKEYS[key]["keycode"] state = XKEYS[key]["state"] if not keycode: print "No keycode for "+key+", "+str(type(key))+", you shall re-run xev..." else: keyevt = event.KeyPress( detail=keycode, time=X.CurrentTime, root=self.display.screen().root, window=self.lastWindow, child=X.NONE, root_x=1, root_y=1, event_x=1, event_y=1, state=state, same_screen=1 ) #print "SENDING \""+key+"\" ( "+str(keycode)+", "+str(state)+" )" #, keycode, state, self.lastWindow.id self.lastWindow.send_event(keyevt) self.display.sync() def __del__(self): self.kbThread.stop() # The X thread class KbThread(threading.Thread): ''' This is the main thread that detect the last selected windows. It first check its parent's windows to not select them : parent.rootWindow ( console ) and parent.parent.wxWindow ( wx gui ), where parent is the Keyboard() instance and parent.parent is the KbFrame() instance. ''' def __init__(self, parent): self.parent = parent # Have a "slow" delay so we can Alt+Tab without getting the desktop # as the lastDetectedWin. Note that it could happen. self.delay = 1 self.tmpCheck = time.time() self.lastDetectedWin = False self._stopevent = threading.Event() threading.Thread.__init__(self, target=self.run, name="MainThread", args=() ) self.winEvtType = wx.NewEventType() self.winEvt = MyEvent( etype=self.winEvtType, eid=9, value="Event Window Focus" ) self.start() def run(self): ''' Detect the last focused window and send it to WX as an event. ''' while not self._stopevent.isSet(): currentWindow = self.parent.display.get_input_focus()._data["focus"] # TODO: Find something else, currently we're setting the WX windows as the 1rst win # that is focus on if it's not the rootWindow, so we shall not focus any windows # between the start of the program and the end of wx initialisation, brrr . if not self.parent.parent.wxWindow and self.parent.rootWindow and currentWindow.id != self.parent.rootWindow.id : #self.parent.parent.wxWindow = currentWindow self.parent.parent.wxWindow = self.parent.display.create_resource_object('window', int(getWinId("MyKb"), 16)+1) print "R C W", self.parent.rootWindow.id , currentWindow.id , self.parent.parent.wxWindow.id self.parent.ready = True '''if self.parent.parent.mainXID: print "mainXID", self.parent.parent.mainXID self.parent.parent.wxWindow.id = self.parent.parent.mainXID''' '''if not self.parent.parent.wxWindow and self.parent.rootWindow and currentWindow.id != self.parent.rootWindow.id : mainXID = getWinId("MyKb") self.parent.parent.wxWindow = self.parent.display.create_resource_object('window', int(mainXID, 16)+1) print "***", mainXID, self.parent.parent.wxWindow.id''' # We don't want the last selected windows to be this program if currentWindow.id != self.parent.rootWindow.id and currentWindow.id != self.parent.parent.wxWindow.id : self.parent.lastWindow = currentWindow # And we change lastDetectedWin only if it's a different window if not self.lastDetectedWin or self.lastDetectedWin.id != currentWindow.id : self.lastDetectedWin = currentWindow # Get the name of the window wName = self.lastDetectedWin.get_wm_class() if wName == None: w = self.lastDetectedWin.query_tree().parent wName = w.get_wm_class() # Sending the event to wx so GUI knows witch is the lastDetectedWin msg = "lastDetectedWin has changed to "+str(wName)+", "+str(self.lastDetectedWin.id) self.winEvt.SetValue( msg ) wx.PostEvent(self.parent.parent, self.winEvt) time.sleep( self.delay ) def stop(self): print "\n[INFO] KbThread : stopping thread, please wait ..." self._stopevent.set() print "[INFO] KbThread : thread is stopped." # A WX event class MyEvent(wx.PyCommandEvent): """ A WX event we can send from anywhere """ def __init__(self, etype=wx.NewEventType(), eid=wx.ID_ANY, value=None): """Creates the event object""" wx.PyCommandEvent.__init__(self, etype, eid) self._value = value self._eid = eid def GetValue(self): """ @return: the value of this event """ return self._value def SetValue(self, value): """ Set the value of this event """ self._value = value return def GetId(self): """ @return: the id of this event """ return self._eid # The main WX frame class KbFrame(wx.Frame): ''' KbFrame() is the main wx ''' def __init__(self, parent, id, title): self.wxWindow = False self.kb = Keyboard(self) self.specialValues = { "CR": "Return", "TAB" : "Tab", "ESC" : "Escape", "\"":"quotedbl", "BS":"BackSpace", } self.specialFunc = {"SW": self.SwitchPanel, "SH": self.OnShift, "AG": self.OnAltGr, } self.currentPanel = 0 self.panelsName = ["Basic", "Upper case", "Alt Gr", "Misc"] self.lastPanel = 0 self.isShift = False self.isAltGr = False self.isCtrl = False # Initialisation displays = (wx.Display(i) for i in range(wx.Display.GetCount())) sizes = [display.GetGeometry().GetSize() for display in displays] szx, szy = sizes[0] width, height = 550, 115 if width > szx: width = szx if height > ( szy / 2): height = ( szy / 2) x = 0 #szx-width y = szy-height print szx, szy, width, height, x,y wx.Frame.__init__(self, parent, id, title, (x,y), wx.Size(width, height) ) # Bind event from thread to the WindowEvt function EVT_TYPE = wx.PyEventBinder( self.kb.kbThread.winEvtType , 1) self.Bind(EVT_TYPE, self.WindowEvt) self.SetBackgroundColour( wx.Colour(55, 55, 66) ) # Store keys by event id self.keysByEvtId = {} # Build default panel self.SwitchPanel(False) self.SetPosition((x, y)) #print "GetId", self.GetId() #mainXID = getWinId("MyKb") #self.wxWindow = self.kb.display.create_resource_object('window', mainXID) #print "***", mainXID, self.wxWindow.id def OnShift(self, event): ''' Switch to panel 1 or 0 ''' if not self.isShift: self.isShift = True self.currentPanel = 0 self.SwitchPanel( True ) # To panel 1 else: self.isShift = False self.currentPanel = 3 self.SwitchPanel( True ) # To panel 0 def OnAltGr(self, event): ''' Switch to panel 2 or 0 ''' if not self.isAltGr: self.isAltGr = True self.currentPanel = 1 self.SwitchPanel( True ) # To panel 2 else: self.isAltGr = False self.currentPanel = 3 self.SwitchPanel( True ) # To panel 0 def SwitchPanel(self, event): ''' Should have been call SwitchToNextPanel ''' if event: self.lastPanel = self.currentPanel sx,sy = self.GetClientSize() px, py = self.GetPosition() self.currentPanel += 1 if self.currentPanel > 3: self.currentPanel = 0 for child in self.gs.GetChildren(): child.GetWindow().Destroy() # Build the layout self.buttons = self.BuildLayout( self.currentPanel ) # On laisse WX placer nos bouton par ligne de 14 self.sizer = wx.BoxSizer(wx.VERTICAL) # La grille principale, sur 5 lignes, 14 colonnes self.gs = wx.GridSizer(5, 14, 0, 0) self.gs.AddMany( self.buttons ) self.sizer.Add(self.gs, 1, wx.EXPAND) self.SetSizer(self.sizer) self.Centre() if event: self.SetPosition((px, py)) print (px, py) self.SetTitle('MyKb - '+self.panelsName[self.currentPanel] ) # Refresh the layout self.Layout() def BuildLayout(self, layout): ''' Ordering and coloring buttons' label, then binding to function ''' if layout == 0: # 1rst line self.buttonsLabels = ["SW"] for i in range(1,10): self.buttonsLabels.append( str(i) ) self.buttonsLabels += ["0", "°", "+", "BS"] # 2nd line self.buttonsLabels += ["TAB"] for c in "azertyuiop^$": self.buttonsLabels.append(c) self.buttonsLabels.append("CR") # 3rd line self.buttonsLabels.append("SH") for c in "qsdfghjklm"+u"ù"+"*": self.buttonsLabels.append(c) self.buttonsLabels.append("Left") # 4rth line self.buttonsLabels.append("AG") for c in "WXCVBN?./"+u"§"+" ": self.buttonsLabels.append(c) elif layout == 2: # 1rst line self.buttonsLabels = ["SW"] self.buttonsLabels += [ "²", "~", "#", "{", "[", "|", "`", "\\", "^", "@", "]", "}" ] self.buttonsLabels += ["BS"] # 2nd line self.buttonsLabels += ["TAB"] for c in u"æâ€êþÿûîœô~ø": self.buttonsLabels.append(c) self.buttonsLabels.append("CR") # 3rd line self.buttonsLabels.append("SH") for c in u"äßë‘’ðüïŀö´` ": self.buttonsLabels.append(c) # 4rth line self.buttonsLabels.append("AG") for c in u"≤«»© ↓¬¿×÷¡ ": self.buttonsLabels.append(c) elif layout == 3: kDone = ["Left", "Down", "Right", "Up"] self.buttonsLabels = ["SW"] n = 1 for sk in SUPAKEYS: if not sk in kDone : kDone.append(sk) self.buttonsLabels += [ sk ] if n == 12: # end 1rst, start 2nd line at TAB self.buttonsLabels += ["BS", "TAB"] if n == 24: # end 2nd line, start 3rd at SH self.buttonsLabels += ["CR", "SH", "", "Up", ""] if n == 32: # end 3rd, start 4rth line at AG self.buttonsLabels += ["", " ", " ", "AG", "Left", "Down", "Right"] n += 1 # Une petite touche de couleurs defaultColor = wx.Colour(240, 240, 255) numColor = wx.Colour(200, 222, 200) buttonsColor = {} for i in range(10): buttonsColor[str(i)] = numColor buttonsColor["SW"] = wx.Colour(253, 202, 200) # Identifiant WX buttonId = 10 # Liste des boutons buttons = [] # Loop into labels for buttonLabel in self.buttonsLabels : # If there's a label if buttonLabel not in [ "" ]: #print "Building "+buttonLabel # Création du bouton button = wx.Button(self, buttonId, " "+buttonLabel+" ", style=wx.BU_EXACTFIT ) # Assignation à la fonction if buttonLabel in self.specialFunc : self.Bind(wx.EVT_BUTTON, self.specialFunc[ buttonLabel ], id=buttonId) else: self.Bind(wx.EVT_BUTTON, self.OnPress, id=buttonId) self.keysByEvtId[buttonId] = buttonLabel else: button = wx.StaticText(self, -1, buttonLabel) # Assignation des couleurs if buttonLabel in buttonsColor : button.SetBackgroundColour( buttonsColor[ buttonLabel ] ) else: button.SetBackgroundColour( defaultColor ) # Ajout du bouton dans la liste buttons.append( button ) # Incrémenter l'id du boutton buttonId += 1 return buttons def OnPress(self, event): ''' Call the Keyboard's KeyPress method ''' key = self.keysByEvtId[ event.GetId() ] self.SetTitle('MyKb - '+self.panelsName[self.currentPanel]+' - '+key) #print "\nPress "+key+" " if key in self.specialValues : key = self.specialValues[ key ] self.kb.KeyPress(key) if self.isShift: self.OnShift(False) if self.isAltGr: self.OnAltGr(False) def WindowEvt(self, event): ''' Receive event from the thread ''' print "\nEVT from thread :", event.GetId(), event.GetValue(), "\n" self.SetTitle('MyKb - '+self.panelsName[self.currentPanel]+' - '+str(event.GetValue())) def __del__(self): self.kb.__del__() # The WX App class KbGui(wx.App): ''' The wx class to create the main frame ''' def OnInit(self): frame = KbFrame(None, -1, 'MyKb') frame.Show(True) self.SetTopWindow(frame) return True zkbd = KbGui(0) zkbd.MainLoop()