Coverage for /Users/Newville/Codes/xraylarch/larch/wxlib/inputhook.py: 31%
154 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
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 try:
50 WXLARCH_SYM.set_symbol('_sys.wx.keyboard_interrupt', True)
51 except AttributeError:
52 pass
53 raise KeyboardInterrupt
54 return 0
56def capture_CtrlC():
57 signal.signal(signal.SIGINT, onCtrlC)
59def ignore_CtrlC():
60 signal.signal(signal.SIGINT, signal.SIG_IGN)
62def allow_idle():
63 # allow idle (needed for Mac OS X)
64 pass
66def stdin_ready():
67 inp, out, err = select([sys.stdin],[],[],0)
68 return bool(inp)
70if sys.platform == 'win32':
71 from msvcrt import kbhit as stdin_ready
72 clock = time.monotonic
73 def ignore_CtrlC():
74 pass
77class EnteredModalDialogHook(wx.ModalDialogHook):
78 """
79 set Global flag IN_MODAL_DIALOG when in a Modal Dialog.
81 this will allow the event loop to *not* try to read stdina
82 when a modal dialog is blocking, which causes problems on MacOS
83 """
84 def __init__(self):
85 wx.ModalDialogHook.__init__(self)
87 def Enter(self, dialog):
88 global IN_MODAL_DIALOG
89 IN_MODAL_DIALOG = True
90 return wx.ID_NONE
92 def Exit(self, dialog):
93 global IN_MODAL_DIALOG
94 IN_MODAL_DIALOG = False
97class EventLoopRunner(object):
98 def __init__(self, parent):
99 self.parent = parent
100 self.timer = wx.Timer()
101 self.evtloop = wx.GUIEventLoop()
103 def run(self, poll_time=None):
104 if poll_time is None:
105 poll_time = POLLTIME
106 self.t0 = clock()
107 self.evtloop = wx.GUIEventLoop()
108 self.timer = wx.Timer()
109 self.parent.Bind(wx.EVT_TIMER, self.check_stdin)
110 self.timer.Start(poll_time)
111 self.evtloop.Run()
113 def check_stdin(self, event=None):
114 try:
115 if (not IN_MODAL_DIALOG and (stdin_ready() or
116 update_requested() or
117 (clock() - self.t0) > 15)):
118 self.timer.Stop()
119 self.evtloop.Exit()
120 del self.timer, self.evtloop
121 clear_update_request()
123 except KeyboardInterrupt:
124 print('Captured Ctrl-C!')
125 except:
126 print(sys.exc_info())
129def inputhook_wx():
130 """Run the wx event loop by processing pending events only.
132 This keeps processing pending events until stdin is ready.
133 After processing all pending events, a call to time.sleep is inserted.
134 This is needed, otherwise, CPU usage is at 100%.
135 This sleep time should be tuned though for best performance.
136 """
137 # We need to protect against a user pressing Control-C when IPython is
138 # idle and this is running. We trap KeyboardInterrupt and pass.
139 try:
140 app = wx.GetApp()
141 if app is not None:
142 assert wx.IsMainThread()
144 if not callable(signal.getsignal(signal.SIGINT)):
145 signal.signal(signal.SIGINT, signal.default_int_handler)
146 evtloop = wx.GUIEventLoop()
147 ea = wx.EventLoopActivator(evtloop)
148 t = clock()
149 while not stdin_ready() and not update_requested():
150 while evtloop.Pending():
151 t = clock()
152 evtloop.Dispatch()
154 if callable(getattr(app, 'ProcessIdle', None)):
155 app.ProcessIdle()
156 if callable(getattr(evtloop, 'ProcessIdle', None)):
157 evtloop.ProcessIdle()
159 # We need to sleep at this point to keep the idle CPU load
160 # low. However, if sleep to long, GUI response is poor.
161 used_time = clock() - t
162 ptime = 0.001
163 if used_time > 0.10: ptime = 0.05
164 if used_time > 3.00: ptime = 0.25
165 if used_time > 30.00: ptime = 1.00
166 sleep(ptime)
167 del ea
168 clear_update_request()
169 except KeyboardInterrupt:
170 if callable(ON_INTERRUPT):
171 ON_INTERRUPT()
172 return 0
174def ping(timeout=0.001):
175 "ping wx"
176 try:
177 t0 = clock()
178 app = wx.GetApp()
179 if app is not None:
180 assert wx.IsMainThread()
181 # Make a temporary event loop and process system events until
182 # there are no more waiting, then allow idle events (which
183 # will also deal with pending or posted wx events.)
184 evtloop = wx_EventLoop()
185 ea = wx.EventLoopActivator(evtloop)
186 t0 = clock()
187 while clock()-t0 < timeout:
188 evtloop.Dispatch()
189 app.ProcessIdle()
190 del ea
191 except:
192 pass
194def inputhook_darwin():
195 """Run the wx event loop, polling for stdin.
197 This version runs the wx eventloop for an undetermined amount of time,
198 during which it periodically checks to see if anything is ready on
199 stdin. If anything is ready on stdin, the event loop exits.
201 The argument to eloop.run controls how often the event loop looks at stdin.
202 This determines the responsiveness at the keyboard. A setting of 20ms
203 gives decent keyboard response. It can be shortened if we know the loop will
204 exit (as from update_requested()), but CPU usage of the idle process goes up
205 if we run this too frequently.
206 """
207 global POLLTIME, ON_INTERRUPT
208 try:
209 app = wx.GetApp()
210 if app is not None:
211 assert wx.IsMainThread()
212 modal_hook = EnteredModalDialogHook()
213 modal_hook.Register()
214 eloop = EventLoopRunner(parent=app)
215 ptime = POLLTIME
216 if update_requested():
217 ptime /= 10
218 eloop.run(poll_time=int(ptime+1))
219 except KeyboardInterrupt:
220 if callable(ON_INTERRUPT):
221 ON_INTERRUPT()
222 return 0
225def ping_darwin(timeout=0.001):
226 inputhook_darwin()
228if sys.platform == 'darwin':
229 # On OSX, evtloop.Pending() always returns True, regardless of there being
230 # any events pending. As such we can't use implementations 1 or 3 of the
231 # inputhook as those depend on a pending/dispatch loop.
232 inputhook_wx = inputhook_darwin
234try:
235 capture_CtrlC()
236except:
237 pass
238cback = CFUNCTYPE(c_int)(inputhook_wx)
239py_inphook = c_void_p.in_dll(pythonapi, 'PyOS_InputHook')
240py_inphook.value = cast(cback, c_void_p).value
242# import for Darwin!
243if sys.platform == 'darwin':
244 from .allow_idle_macosx import allow_idle
245 allow_idle()
246# print("no allow idle")