# HG changeset patch # User etisserant # Date 1203815202 -3600 # Node ID a05e8b30c024665b8b49e219c4dba7f9acbe68ca # Parent f27ca37b6e7a0904a5e83fe137dbca16bf70bc5a Fixed way apps are launched in parralel with single log window... Tested in win32 only. diff -r f27ca37b6e7a -r a05e8b30c024 Beremiz.py --- a/Beremiz.py Fri Feb 22 19:04:01 2008 +0100 +++ b/Beremiz.py Sun Feb 24 02:06:42 2008 +0100 @@ -36,8 +36,6 @@ from plugger import PluginsRoot -from wxPopen import wxPopen3 - SCROLLBAR_UNIT = 10 WINDOW_COLOUR = wx.Colour(240,240,240) TITLE_COLOUR = wx.Colour(200,200,220) @@ -72,30 +70,14 @@ # Patch wx.lib.imageutils so that gray is supported on alpha images import wx.lib.imageutils +from wx.lib.imageutils import grayOut as old_grayOut def grayOut(anImage): - """ - Convert the given image (in place) to a grayed-out - version, appropriate for a 'disabled' appearance. - """ - factor = 0.7 # 0 < f < 1. Higher is grayer. - if anImage.HasMask(): - maskColor = (anImage.GetMaskRed(), anImage.GetMaskGreen(), anImage.GetMaskBlue()) - else: - maskColor = None - if anImage.HasAlpha(): AlphaData = anImage.GetAlphaData() else : AlphaData = None - data = map(ord, list(anImage.GetData())) - - for i in range(0, len(data), 3): - pixel = (data[i], data[i+1], data[i+2]) - pixel = wx.lib.imageutils.makeGray(pixel, factor, maskColor) - for x in range(3): - data[i+x] = pixel[x] - anImage.SetData(''.join(map(chr, data))) + old_grayOut(anImage) if AlphaData is not None: anImage.SetAlphaData(AlphaData) @@ -136,22 +118,23 @@ class LogPseudoFile: """ Base class for file like objects to facilitate StdOut for the Shell.""" - def __init__(self, output = None): + def __init__(self, output): self.red_white = wx.TextAttr("RED", "WHITE") self.red_yellow = wx.TextAttr("RED", "YELLOW") self.black_white = wx.TextAttr("BLACK", "WHITE") self.default_style = None self.output = output - def writelines(self, l): - map(self.write, l) - def write(self, s, style = None): - if not style : style=self.black_white + if style is None : style=self.black_white + self.output.Freeze(); if self.default_style != style: self.output.SetDefaultStyle(style) self.default_style = style - self.output.AppendText(s) + self.output.AppendText(s) + self.output.ScrollLines(s.count('\n')+1) + self.output.ShowPosition(self.output.GetLastPosition()) + self.output.Thaw() def write_warning(self, s): self.write(s,self.red_white) @@ -165,42 +148,6 @@ def isatty(self): return false - def LogCommand(self, Command, sz_limit = 100, no_stdout=False): - self.errlen = 0 - self.exitcode = None - self.outdata = "" - self.errdata = "" - - def output(v): - self.outdata += v - if not no_stdout: - self.write(v) - - def errors(v): - self.errdata += v - self.errlen += 1 - if self.errlen > sz_limit: - p.kill() - self.write_warning(v) - - def fin(pid,ecode): - self.exitcode = ecode - if self.exitcode != 0: - self.write(Command + "\n") - self.write_warning("exited with status %d (pid %d)\n"%(ecode,pid)) - - def spin(p): - while not p.finished: - wx.Yield() - time.sleep(0.01) - - input = [] - p = wxPopen3(Command, input, output, errors, fin, self.output) - if p.pid: - spin(p) - - return (self.exitcode, self.outdata, self.errdata) - [ID_BEREMIZ, ID_BEREMIZMAINSPLITTER, ID_BEREMIZPLCCONFIG, ID_BEREMIZLOGCONSOLE, ] = [wx.NewId() for _init_ctrls in range(4)] @@ -869,9 +816,15 @@ return AddPluginMenu def GetButtonCallBackFunction(self, plugin, method): + """ Generate the callbackfunc for a given plugin method""" def OnButtonClick(event): + # Disable button to prevent re-entrant call + event.GetEventObject().Disable() + # Call getattr(plugin,method)(self.Log) - #self.RefreshVariableLists() + # Re-enable button + event.GetEventObject().Enable() + # Trigger refresh on Idle wx.CallAfter(self.RefreshAll) event.Skip() return OnButtonClick diff -r f27ca37b6e7a -r a05e8b30c024 plugger.py --- a/plugger.py Fri Feb 22 19:04:01 2008 +0100 +++ b/plugger.py Sun Feb 24 02:06:42 2008 +0100 @@ -8,13 +8,13 @@ import shutil from xml.dom import minidom import wx -import subprocess, ctypes, time, shutil #Quick hack to be able to find Beremiz IEC tools. Should be config params. base_folder = os.path.split(sys.path[0])[0] sys.path.append(os.path.join(base_folder, "plcopeneditor")) from xmlclass import GenerateClassesFromXSDstring +from wxPopen import ProcessLogger _BaseParamsClass = GenerateClassesFromXSDstring(""" @@ -670,6 +670,9 @@ # copy PluginMethods so that it can be later customized self.PluginMethods = [dic.copy() for dic in self.PluginMethods] + + self.runningPLC = None + def HasProjectOpened(self): """ @@ -860,7 +863,13 @@ logger.write("Compiling IEC Program in to C code...\n") # Now compile IEC code into many C files # files are listed to stdout, and errors to stderr. - status, result, err_result = logger.LogCommand("%s \"%s\" -I \"%s\" \"%s\""%(iec2c_path, self._getIECcodepath(), ieclib_path, buildpath), no_stdout=True) + status, result, err_result = ProcessLogger( + logger, + "%s \"%s\" -I \"%s\" \"%s\""%( + iec2c_path, + self._getIECcodepath(), + ieclib_path, buildpath), + no_stdout=True).spin() if status: # Failed ! logger.write_error("Error : IEC to C compiler returned %d\n"%status) @@ -893,12 +902,16 @@ logger.flush() logger.write("Start build in %s\n" % buildpath) + + self.EnableMethod("_Clean", True) + self.EnableMethod("_showIECcode", True) # Generate SoftPLC code if not self._Generate_SoftPLC(logger): logger.write_error("SoftPLC code generation failed !\n") return False + #logger.write("SoftPLC code generation successfull\n") logger.write("Generating plugins code ...\n") @@ -960,7 +973,13 @@ obns.append(obn) logger.write(" [CC] "+bn+" -> "+obn+"\n") objectfilename = os.path.splitext(CFile)[0]+".o" - status, result, err_result = logger.LogCommand("\"%s\" -c \"%s\" -o \"%s\" %s %s"%(compiler, CFile, objectfilename, _CFLAGS, CFLAGS)) + + status, result, err_result = ProcessLogger( + logger, + "\"%s\" -c \"%s\" -o \"%s\" %s %s"% + (compiler, CFile, objectfilename, _CFLAGS, CFLAGS) + ).spin() + if status != 0: logger.write_error("Build failed\n") return False @@ -972,7 +991,14 @@ exe += ".exe" exe_path = os.path.join(buildpath, exe) logger.write(" [CC] " + ' '.join(obns)+" -> " + exe + "\n") - status, result, err_result = logger.LogCommand("\"%s\" \"%s\" -o \"%s\" %s"%(linker, '" "'.join(objs), exe_path, ' '.join(LDFLAGS+[_LDFLAGS]))) + status, result, err_result = ProcessLogger( + logger, + "\"%s\" \"%s\" -o \"%s\" %s"% + (linker, + '" "'.join(objs), + exe_path, + ' '.join(LDFLAGS+[_LDFLAGS])) + ).spin() if status != 0: logger.write_error("Build failed\n") self.EnableMethod("_Run", False) @@ -1034,24 +1060,42 @@ shutil.rmtree(os.path.join(self._getBuildPath())) else: logger.write_error("Build directory already clean\n") + self.EnableMethod("_showIECcode", False) + self.EnableMethod("_Clean", False) + self.EnableMethod("_Run", False) def _Run(self, logger): - logger.write("\n") - self.pid_plc = 0 command_start_plc = os.path.join(self._getBuildPath(),self.GetProjectName() + exe_ext) if os.path.isfile(command_start_plc): - logger.write("\nStarting PLC\n") - self.pid_plc = subprocess.Popen(command_start_plc).pid + logger.write("Starting PLC\n") + def this_plc_finish_callback(*args): + if self.runningPLC is not None: + self.runningPLC = None + self._Stop(logger) + self.runningPLC = ProcessLogger( + logger, + command_start_plc, + finish_callback = this_plc_finish_callback) + self.EnableMethod("_Clean", False) + self.EnableMethod("_Run", False) + self.EnableMethod("_Stop", True) + self.EnableMethod("_build", False) else: logger.write_error("%s doesn't exist\n" %command_start_plc) + def reset_finished(self): + self.EnableMethod("_Clean", True) + self.EnableMethod("_Run", True) + self.EnableMethod("_Stop", False) + self.EnableMethod("_build", True) + def _Stop(self, logger): - PROCESS_TERMINATE = 1 - if self.pid_plc != 0: + if self.runningPLC is not None: logger.write("Stopping PLC\n") - handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, self.pid_plc) - ctypes.windll.kernel32.TerminateProcess(handle, -1) - ctypes.windll.kernel32.CloseHandle(handle) + was_runningPLC = self.runningPLC + self.runningPLC = None + was_runningPLC.kill() + self.reset_finished() PluginMethods = [ {"bitmap" : os.path.join("images", "editPLC"), @@ -1078,10 +1122,10 @@ "method" : "_Stop"}, {"bitmap" : os.path.join("images", "ShowIECcode"), "name" : "Show IEC code", + "enabled" : False, "tooltip" : "Show IEC code generated by PLCGenerator", "method" : "_showIECcode"}, {"name" : "Edit raw IEC code", "tooltip" : "Edit raw IEC code added to code generated by PLCGenerator", "method" : "_editIECrawcode"} ] - diff -r f27ca37b6e7a -r a05e8b30c024 runtime/plc_Win32_main.c --- a/runtime/plc_Win32_main.c Fri Feb 22 19:04:01 2008 +0100 +++ b/runtime/plc_Win32_main.c Sun Feb 24 02:06:42 2008 +0100 @@ -3,10 +3,15 @@ #include #include +int localcount = 0; + void timer_notify() { struct _timeb timebuffer; - printf("."); + if(++localcount % 50 == 0){ + printf("PLC tick : %d\n",localcount); + fflush(stdout); + } _ftime( &timebuffer ); __CURRENT_TIME.tv_sec = timebuffer.time; diff -r f27ca37b6e7a -r a05e8b30c024 wxPopen.py --- a/wxPopen.py Fri Feb 22 19:04:01 2008 +0100 +++ b/wxPopen.py Sun Feb 24 02:06:42 2008 +0100 @@ -22,168 +22,128 @@ #License along with this library; if not, write to the Free Software #Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# -# based on wxPopen.py from boa-constructor -# import time -from StringIO import StringIO +import wx +import subprocess, ctypes +import threading +import os -import wx + +class outputThread(threading.Thread): + """ + Thread is used to print the output of a command to the stdout + """ + def __init__(self, Proc, fd, callback=None, endcallback=None): + threading.Thread.__init__(self) + self.killed = False + self.finished = False + self.retval = None + self.Proc = Proc + self.callback = callback + self.endcallback = endcallback + self.fd = fd -class ProcessRunnerMix: + def run(self): + outeof = False + self.retval = self.Proc.poll() + while not self.retval and not self.killed and not outeof: + outchunk = self.fd.readline() + if outchunk == '': outeof = True + if self.callback : + wx.CallAfter(self.callback,outchunk) + self.retval=self.Proc.poll() + if self.endcallback: + err = self.Proc.wait() + self.finished = True + wx.CallAfter(self.endcallback, self.Proc.pid, self.retval) - if wx.VERSION < (2, 6, 0): - def Bind(self, event, function, id = None): - if id is not None: - event(self, id, function) - else: - event(self, function) - - def __init__(self, input, handler=None): - if handler is None: - handler = self - self.handler = handler - handler.Bind(wx.EVT_IDLE, self.OnIdle) - handler.Bind(wx.EVT_END_PROCESS, self.OnProcessEnded) +class ProcessLogger: + def __init__(self, logger, Command, finish_callback=None, no_stdout=False, no_stderr=False): + self.logger = logger + self.Command = Command + self.finish_callback = finish_callback + self.no_stdout = no_stdout + self.no_stderr = no_stderr + self.errlen = 0 + self.outlen = 0 + self.exitcode = None + self.outdata = "" + self.errdata = "" + self.finished = False - input.reverse() # so we can pop - self.input = input - - self.reset() + self.Proc = subprocess.Popen(self.Command, + cwd = os.getcwd(), + stdin = subprocess.PIPE, + stdout = subprocess.PIPE, + stderr = subprocess.STDOUT) +# stderr = subprocess.PIPE) - def reset(self): - self.process = None - self.pid = -1 - self.output = [] - self.errors = [] - self.inputStream = None - self.errorStream = None - self.outputStream = None - self.outputFunc = None - self.errorsFunc = None - self.finishedFunc = None - self.finished = False - self.responded = False + self.outt = outputThread( + self.Proc, + self.Proc.stdout, + self.output, + self.finish) - def execute(self, cmd): - self.process = wx.Process(self.handler) - self.process.Redirect() + self.outt.start() - self.pid = wx.Execute(cmd, wx.EXEC_ASYNC, self.process) +# self.errt = outputThread( +# self.Proc, +# self.Proc.stderr, +# self.errors) +# +# self.errt.start() - self.inputStream = self.process.GetOutputStream() - self.errorStream = self.process.GetErrorStream() - self.outputStream = self.process.GetInputStream() + def output(self,v): + self.outdata += v + self.outlen += 1 + if not self.no_stdout: + self.logger.write(v) - #self.OnIdle() - wx.WakeUpIdle() - - def setCallbacks(self, output, errors, finished): - self.outputFunc = output - self.errorsFunc = errors - self.finishedFunc = finished + def errors(self,v): + self.errdata += v + self.errlen += 1 + if not self.no_stderr: + self.logger.write_warning(v) - def detach(self): - if self.process is not None: - self.process.CloseOutput() - self.process.Detach() - self.process = None + def finish(self, pid,ecode): + self.finished = True + self.exitcode = ecode + if self.exitcode != 0: + self.logger.write(self.Command + "\n") + self.logger.write_warning("exited with status %s (pid %s)\n"%(str(ecode),str(pid))) + if self.finish_callback is not None: + self.finish_callback(self,ecode,pid) def kill(self): - if self.process is not None: - self.process.CloseOutput() - if wx.Process.Kill(self.pid, wx.SIGTERM) != wx.KILL_OK: - wx.Process.Kill(self.pid, wx.SIGKILL) - self.process = None + self.outt.killed = True +# self.errt.killed = True + if wx.Platform == '__WXMSW__': + PROCESS_TERMINATE = 1 + handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, self.Proc.pid) + ctypes.windll.kernel32.TerminateProcess(handle, -1) + ctypes.windll.kernel32.CloseHandle(handle) + else: + os.kill(self.Proc.pid) - def updateStream(self, stream, data): - if stream and stream.CanRead(): - if not self.responded: - self.responded = True - text = stream.read() - data.append(text) - return text - else: - return None - - def updateInpStream(self, stream, input): - if stream and input: - line = input.pop() - stream.write(line) - - def updateErrStream(self, stream, data): - return self.updateStream(stream, data) - - def updateOutStream(self, stream, data): - return self.updateStream(stream, data) - - def OnIdle(self, event=None): - if self.process is not None: - self.updateInpStream(self.inputStream, self.input) - e = self.updateErrStream(self.errorStream, self.errors) - if e is not None and self.errorsFunc is not None: - wx.CallAfter(self.errorsFunc, e) - o = self.updateOutStream(self.outputStream, self.output) - if o is not None and self.outputFunc is not None: - wx.CallAfter(self.outputFunc, o) - - #wx.WakeUpIdle() - #time.sleep(0.001) - - def OnProcessEnded(self, event): - self.OnIdle() - pid,exitcode = event.GetPid(), event.GetExitCode() - if self.process: - self.process.Destroy() - self.process = None - - self.finished = True - - # XXX doesn't work ??? - #self.handler.Disconnect(-1, wx.EVT_IDLE) - - if self.finishedFunc: - wx.CallAfter(self.finishedFunc, pid, exitcode) - -class ProcessRunner(wx.EvtHandler, ProcessRunnerMix): - def __init__(self, input): - wx.EvtHandler.__init__(self) - ProcessRunnerMix.__init__(self, input) - -def wxPopen3(cmd, input, output, errors, finish, handler=None): - p = ProcessRunnerMix(input, handler) - p.setCallbacks(output, errors, finish) - p.execute(cmd) - return p - -def _test(): - app = wx.PySimpleApp() - f = wx.Frame(None, -1, 'asd')#, style=0) - f.Show() - - def output(v): - print 'OUTPUT:', v - def errors(v): - print 'ERRORS:', v - def fin(): - p.Close() - f.Close() - print 'FINISHED' - - - def spin(p): - while not p.finished: + def spin(self, timeout=None, out_limit=None, err_limit=None, keyword = None, kill_it = True): + count = 0 + while not self.finished: + if err_limit and self.errlen > err_limit: + break + if out_limit and self.outlen > out_limit: + break + if timeout: + if count > timeout: + break + count += 1 + if keyword and self.outdata.find(keyword)!=-1: + break wx.Yield() time.sleep(0.01) - def evt(self, event): - input = [] - p = wxPopen3('''c:\\python23\\python.exe -c "print '*'*5000"''', - input, output, errors, fin, f) - print p.pid + if not self.outt.finished and kill_it: + self.kill() - app.MainLoop() + return [self.exitcode, self.outdata, self.errdata] -if __name__ == '__main__': - _test()