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
« 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.
5based on inputhook.py and inputhookwx.py from IPython,
6which has several authors, including
7 Robin Dunn, Brian Granger, Ondrej Certik
9tweaked by M Newville for larch
10"""
12import sys
13import time
14import signal
15from select import select
16from ctypes import c_void_p, c_int, cast, CFUNCTYPE, pythonapi
18import wx
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
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
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)
44clock = time.time
45sleep = time.sleep
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
57def capture_CtrlC():
58 signal.signal(signal.SIGINT, onCtrlC)
60def ignore_CtrlC():
61 signal.signal(signal.SIGINT, signal.SIG_IGN)
63def allow_idle():
64 # allow idle (needed for Mac OS X)
65 pass
67def stdin_ready():
68 inp, out, err = select([sys.stdin],[],[],0)
69 return bool(inp)
71if sys.platform == 'win32':
72 from msvcrt import kbhit as stdin_ready
73 clock = time.monotonic
74 def ignore_CtrlC():
75 pass
78class EnteredModalDialogHook(wx.ModalDialogHook):
79 """
80 set Global flag IN_MODAL_DIALOG when in a Modal Dialog.
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)
88 def Enter(self, dialog):
89 global IN_MODAL_DIALOG
90 IN_MODAL_DIALOG = True
91 return wx.ID_NONE
93 def Exit(self, dialog):
94 global IN_MODAL_DIALOG
95 IN_MODAL_DIALOG = False
98class EventLoopRunner(object):
99 def __init__(self, parent):
100 self.parent = parent
101 self.timer = wx.Timer()
102 self.evtloop = wx.GUIEventLoop()
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()
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()
124 except KeyboardInterrupt:
125 print('Captured Ctrl-C!')
126 except:
127 print(sys.exc_info())
130def inputhook_wx():
131 """Run the wx event loop by processing pending events only.
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()
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()
155 if callable(getattr(app, 'ProcessIdle', None)):
156 app.ProcessIdle()
157 if callable(getattr(evtloop, 'ProcessIdle', None)):
158 evtloop.ProcessIdle()
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
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
195def inputhook_darwin():
196 """Run the wx event loop, polling for stdin.
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.
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
226def ping_darwin(timeout=0.001):
227 inputhook_darwin()
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
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
243# import for Darwin!
244if sys.platform == 'darwin':
245 from .allow_idle_macosx import allow_idle
246 # allow_idle()
247# print("no allow idle")