Coverage for larch/wxlib/inputhook.py: 29%

153 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2024-10-16 21:04 +0000

1#!/usr/bin/env python 

2""" 

3Enable wxPython to be used interacive by setting PyOS_InputHook. 

4 

5based on inputhook.py and inputhookwx.py from IPython, 

6which has several authors, including 

7 Robin Dunn, Brian Granger, Ondrej Certik 

8 

9tweaked by M Newville for larch 

10""" 

11 

12import sys 

13import time 

14import signal 

15from select import select 

16from ctypes import c_void_p, c_int, cast, CFUNCTYPE, pythonapi 

17 

18import wx 

19 

20POLLTIME = 10 # milliseconds 

21ON_INTERRUPT = None 

22WXLARCH_SYM = None 

23WXLARCH_INP = None 

24UPDATE_GROUPNAME = '_sys.wx' 

25UPDATE_GROUP = None 

26UPDATE_VAR = 'force_wxupdate' 

27IN_MODAL_DIALOG = False 

28 

29def update_requested(): 

30 "check if update has been requested" 

31 global WXLARCH_SYM, UPDATE_VAR, UPDATE_GROUP, UPDATE_GROUPNAME 

32 if WXLARCH_SYM is not None: 

33 if UPDATE_GROUP is None: 

34 UPDATE_GROUP = WXLARCH_SYM.get_symbol(UPDATE_GROUPNAME) 

35 return getattr(UPDATE_GROUP, UPDATE_VAR, False) 

36 return False 

37 

38def clear_update_request(): 

39 "clear update request" 

40 global WXLARCH_SYM, UPDATE_VAR, UPDATE_GROUP, UPDATE_GROUPNAME 

41 if UPDATE_GROUP is not None: 

42 setattr(UPDATE_GROUP, UPDATE_VAR, False) 

43 

44clock = time.time 

45sleep = time.sleep 

46 

47def onCtrlC(*args, **kws): 

48 global WXLARCH_SYM 

49 if WXLARCH_SYM is not None: 

50 try: 

51 WXLARCH_SYM.set_symbol('_sys.wx.keyboard_interrupt', True) 

52 except AttributeError: 

53 pass 

54 raise KeyboardInterrupt 

55 return 0 

56 

57def capture_CtrlC(): 

58 signal.signal(signal.SIGINT, onCtrlC) 

59 

60def ignore_CtrlC(): 

61 signal.signal(signal.SIGINT, signal.SIG_IGN) 

62 

63def allow_idle(): 

64 # allow idle (needed for Mac OS X) 

65 pass 

66 

67def stdin_ready(): 

68 inp, out, err = select([sys.stdin],[],[],0) 

69 return bool(inp) 

70 

71if sys.platform == 'win32': 

72 from msvcrt import kbhit as stdin_ready 

73 clock = time.monotonic 

74 def ignore_CtrlC(): 

75 pass 

76 

77 

78class EnteredModalDialogHook(wx.ModalDialogHook): 

79 """ 

80 set Global flag IN_MODAL_DIALOG when in a Modal Dialog. 

81 

82 this will allow the event loop to *not* try to read stdina 

83 when a modal dialog is blocking, which causes problems on MacOS 

84 """ 

85 def __init__(self): 

86 wx.ModalDialogHook.__init__(self) 

87 

88 def Enter(self, dialog): 

89 global IN_MODAL_DIALOG 

90 IN_MODAL_DIALOG = True 

91 return wx.ID_NONE 

92 

93 def Exit(self, dialog): 

94 global IN_MODAL_DIALOG 

95 IN_MODAL_DIALOG = False 

96 

97 

98class EventLoopRunner(object): 

99 def __init__(self, parent): 

100 self.parent = parent 

101 self.timer = wx.Timer() 

102 self.evtloop = wx.GUIEventLoop() 

103 

104 def run(self, poll_time=None): 

105 if poll_time is None: 

106 poll_time = POLLTIME 

107 self.t0 = clock() 

108 self.evtloop = wx.GUIEventLoop() 

109 self.timer = wx.Timer() 

110 self.parent.Bind(wx.EVT_TIMER, self.check_stdin) 

111 self.timer.Start(poll_time) 

112 self.evtloop.Run() 

113 

114 def check_stdin(self, event=None): 

115 try: 

116 if (not IN_MODAL_DIALOG and (stdin_ready() or 

117 update_requested() or 

118 (clock() - self.t0) > 15)): 

119 self.timer.Stop() 

120 self.evtloop.Exit() 

121 del self.timer, self.evtloop 

122 clear_update_request() 

123 

124 except KeyboardInterrupt: 

125 print('Captured Ctrl-C!') 

126 except: 

127 print(sys.exc_info()) 

128 

129 

130def inputhook_wx(): 

131 """Run the wx event loop by processing pending events only. 

132 

133 This keeps processing pending events until stdin is ready. 

134 After processing all pending events, a call to time.sleep is inserted. 

135 This is needed, otherwise, CPU usage is at 100%. 

136 This sleep time should be tuned though for best performance. 

137 """ 

138 # We need to protect against a user pressing Control-C when IPython is 

139 # idle and this is running. We trap KeyboardInterrupt and pass. 

140 try: 

141 app = wx.GetApp() 

142 if app is not None: 

143 assert wx.IsMainThread() 

144 

145 if not callable(signal.getsignal(signal.SIGINT)): 

146 signal.signal(signal.SIGINT, signal.default_int_handler) 

147 evtloop = wx.GUIEventLoop() 

148 ea = wx.EventLoopActivator(evtloop) 

149 t = clock() 

150 while not stdin_ready() and not update_requested(): 

151 while evtloop.Pending(): 

152 t = clock() 

153 evtloop.Dispatch() 

154 

155 if callable(getattr(app, 'ProcessIdle', None)): 

156 app.ProcessIdle() 

157 if callable(getattr(evtloop, 'ProcessIdle', None)): 

158 evtloop.ProcessIdle() 

159 

160 # We need to sleep at this point to keep the idle CPU load 

161 # low. However, if sleep to long, GUI response is poor. 

162 used_time = clock() - t 

163 ptime = 0.001 

164 if used_time > 0.10: ptime = 0.05 

165 if used_time > 3.00: ptime = 0.25 

166 if used_time > 30.00: ptime = 1.00 

167 sleep(ptime) 

168 del ea 

169 clear_update_request() 

170 except KeyboardInterrupt: 

171 if callable(ON_INTERRUPT): 

172 ON_INTERRUPT() 

173 return 0 

174 

175def ping(timeout=0.001): 

176 "ping wx" 

177 try: 

178 t0 = clock() 

179 app = wx.GetApp() 

180 if app is not None: 

181 assert wx.IsMainThread() 

182 # Make a temporary event loop and process system events until 

183 # there are no more waiting, then allow idle events (which 

184 # will also deal with pending or posted wx events.) 

185 evtloop = wx_EventLoop() 

186 ea = wx.EventLoopActivator(evtloop) 

187 t0 = clock() 

188 while clock()-t0 < timeout: 

189 evtloop.Dispatch() 

190 app.ProcessIdle() 

191 del ea 

192 except: 

193 pass 

194 

195def inputhook_darwin(): 

196 """Run the wx event loop, polling for stdin. 

197 

198 This version runs the wx eventloop for an undetermined amount of time, 

199 during which it periodically checks to see if anything is ready on 

200 stdin. If anything is ready on stdin, the event loop exits. 

201 

202 The argument to eloop.run controls how often the event loop looks at stdin. 

203 This determines the responsiveness at the keyboard. A setting of 20ms 

204 gives decent keyboard response. It can be shortened if we know the loop will 

205 exit (as from update_requested()), but CPU usage of the idle process goes up 

206 if we run this too frequently. 

207 """ 

208 global POLLTIME, ON_INTERRUPT 

209 try: 

210 app = wx.GetApp() 

211 if app is not None: 

212 assert wx.IsMainThread() 

213 modal_hook = EnteredModalDialogHook() 

214 modal_hook.Register() 

215 eloop = EventLoopRunner(parent=app) 

216 ptime = POLLTIME 

217 if update_requested(): 

218 ptime /= 10 

219 eloop.run(poll_time=int(ptime+1)) 

220 except KeyboardInterrupt: 

221 if callable(ON_INTERRUPT): 

222 ON_INTERRUPT() 

223 return 0 

224 

225 

226def ping_darwin(timeout=0.001): 

227 inputhook_darwin() 

228 

229if sys.platform == 'darwin': 

230 # On OSX, evtloop.Pending() always returns True, regardless of there being 

231 # any events pending. As such we can't use implementations 1 or 3 of the 

232 # inputhook as those depend on a pending/dispatch loop. 

233 inputhook_wx = inputhook_darwin 

234 

235try: 

236 capture_CtrlC() 

237except: 

238 pass 

239cback = CFUNCTYPE(c_int)(inputhook_wx) 

240py_inphook = c_void_p.in_dll(pythonapi, 'PyOS_InputHook') 

241py_inphook.value = cast(cback, c_void_p).value 

242 

243# import for Darwin! 

244if sys.platform == 'darwin': 

245 from .allow_idle_macosx import allow_idle 

246 # allow_idle() 

247# print("no allow idle")