##// END OF EJS Templates
RF: Clean up wxphoenix input hook.
Paul McCarthy -
Show More
@@ -1,212 +1,217 b''
1 1 """Enable wxPython to be used interactively in prompt_toolkit
2 2 """
3 3
4 4 import sys
5 5 import signal
6 6 import time
7 7 from timeit import default_timer as clock
8 8 import wx
9 9
10 10
11 11 def ignore_keyboardinterrupts(func):
12 """Decorator which causes KeyboardInterrupt exceptions to be
13 ignored during execution of the decorated function.
12 """Decorator which causes KeyboardInterrupt exceptions to be ignored during
13 execution of the decorated function.
14
15 This is used by the inputhook functions to handle the event where the user
16 presses CTRL+C while IPython is idle, and the inputhook loop is running. In
17 this case, we want to ignore interrupts.
14 18 """
15 19 def wrapper(*args, **kwargs):
16 20 try:
17 21 func(*args, **kwargs)
18 22 except KeyboardInterrupt:
19 23 pass
20 24 return wrapper
21 25
22 26
23 27 @ignore_keyboardinterrupts
24 28 def inputhook_wx1(context):
25 29 """Run the wx event loop by processing pending events only.
26 30
27 31 This approach seems to work, but its performance is not great as it
28 32 relies on having PyOS_InputHook called regularly.
29 33 """
30 34 app = wx.GetApp()
31 35 if app is not None:
32 36 assert wx.Thread_IsMain()
33 37
34 38 # Make a temporary event loop and process system events until
35 39 # there are no more waiting, then allow idle events (which
36 40 # will also deal with pending or posted wx events.)
37 41 evtloop = wx.EventLoop()
38 42 ea = wx.EventLoopActivator(evtloop)
39 43 while evtloop.Pending():
40 44 evtloop.Dispatch()
41 45 app.ProcessIdle()
42 46 del ea
43 47 return 0
44 48
45 49
46 50 class EventLoopTimer(wx.Timer):
47 51
48 52 def __init__(self, func):
49 53 self.func = func
50 54 wx.Timer.__init__(self)
51 55
52 56 def Notify(self):
53 57 self.func()
54 58
55 59
56 60 class EventLoopRunner(object):
57 61
58 62 def Run(self, time, input_is_ready):
59 63 self.input_is_ready = input_is_ready
60 64 self.evtloop = wx.EventLoop()
61 65 self.timer = EventLoopTimer(self.check_stdin)
62 66 self.timer.Start(time)
63 67 self.evtloop.Run()
64 68
65 69 def check_stdin(self):
66 70 if self.input_is_ready():
67 71 self.timer.Stop()
68 72 self.evtloop.Exit()
69 73
70 74
71 75 @ignore_keyboardinterrupts
72 76 def inputhook_wx2(context):
73 77 """Run the wx event loop, polling for stdin.
74 78
75 79 This version runs the wx eventloop for an undetermined amount of time,
76 80 during which it periodically checks to see if anything is ready on
77 81 stdin. If anything is ready on stdin, the event loop exits.
78 82
79 83 The argument to elr.Run controls how often the event loop looks at stdin.
80 84 This determines the responsiveness at the keyboard. A setting of 1000
81 85 enables a user to type at most 1 char per second. I have found that a
82 86 setting of 10 gives good keyboard response. We can shorten it further,
83 87 but eventually performance would suffer from calling select/kbhit too
84 88 often.
85 89 """
86 90 app = wx.GetApp()
87 91 if app is not None:
88 92 assert wx.Thread_IsMain()
89 93 elr = EventLoopRunner()
90 94 # As this time is made shorter, keyboard response improves, but idle
91 95 # CPU load goes up. 10 ms seems like a good compromise.
92 96 elr.Run(time=10, # CHANGE time here to control polling interval
93 97 input_is_ready=context.input_is_ready)
94 98 return 0
95 99
96 100
97 101 @ignore_keyboardinterrupts
98 102 def inputhook_wx3(context):
99 103 """Run the wx event loop by processing pending events only.
100 104
101 105 This is like inputhook_wx1, but it keeps processing pending events
102 106 until stdin is ready. After processing all pending events, a call to
103 107 time.sleep is inserted. This is needed, otherwise, CPU usage is at 100%.
104 108 This sleep time should be tuned though for best performance.
105 109 """
106 110 app = wx.GetApp()
107 111 if app is not None:
108 112 assert wx.Thread_IsMain()
109 113
110 114 # The import of wx on Linux sets the handler for signal.SIGINT
111 115 # to 0. This is a bug in wx or gtk. We fix by just setting it
112 116 # back to the Python default.
113 117 if not callable(signal.getsignal(signal.SIGINT)):
114 118 signal.signal(signal.SIGINT, signal.default_int_handler)
115 119
116 120 evtloop = wx.EventLoop()
117 121 ea = wx.EventLoopActivator(evtloop)
118 122 t = clock()
119 123 while not context.input_is_ready():
120 124 while evtloop.Pending():
121 125 t = clock()
122 126 evtloop.Dispatch()
123 127 app.ProcessIdle()
124 128 # We need to sleep at this point to keep the idle CPU load
125 129 # low. However, if sleep to long, GUI response is poor. As
126 130 # a compromise, we watch how often GUI events are being processed
127 131 # and switch between a short and long sleep time. Here are some
128 132 # stats useful in helping to tune this.
129 133 # time CPU load
130 134 # 0.001 13%
131 135 # 0.005 3%
132 136 # 0.01 1.5%
133 137 # 0.05 0.5%
134 138 used_time = clock() - t
135 139 if used_time > 10.0:
136 140 # print 'Sleep for 1 s' # dbg
137 141 time.sleep(1.0)
138 142 elif used_time > 0.1:
139 143 # Few GUI events coming in, so we can sleep longer
140 144 # print 'Sleep for 0.05 s' # dbg
141 145 time.sleep(0.05)
142 146 else:
143 147 # Many GUI events coming in, so sleep only very little
144 148 time.sleep(0.001)
145 149 del ea
146 150 return 0
147 151
148 152
149 153 @ignore_keyboardinterrupts
150 154 def inputhook_wxphoenix(context):
151 155 """Run the wx event loop until the user provides more input.
152 156
153 157 This function uses the same approach to that used in
154 158 ipykernel.eventloops.loop_wx.
155 159 """
156 160
157 161 app = wx.GetApp()
158 162
159 163 if app is None:
160 164 return
161 165
162 # Wx uses milliseconds
166 if context.input_is_ready():
167 return
168
169 assert wx.IsMainThread()
170
171 # Wx uses milliseconds
163 172 poll_interval = 100
164 173
165 # This function gets polled periodically; when
166 # input is ready, the wx main loop is stopped.
167 def wake():
174 # We have to create a dummy wx.Frame, otherwise wx.App.MainLoop will know
175 # that it has nothing to do, and will return immediately.
176 frame = getattr(inputhook_wxphoenix, '_frame', None)
177 if frame is None:
178 inputhook_wxphoenix._frame = frame = wx.Frame(None)
179 frame.Show(False)
180
181 # Use a wx.Timer to periodically check whether input is ready - as soon as
182 # it is, we exit the main loop
183 def poll(ev):
168 184 if context.input_is_ready():
169 185 app.ExitMainLoop()
170 186
171 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
172 # We make the Frame hidden when we create it in the main app below.
173 class TimerFrame(wx.Frame):
174 def __init__(self, func):
175 wx.Frame.__init__(self, None, -1)
176 self.timer = wx.Timer(self)
177 self.timer.Start(poll_interval)
178 self.Bind(wx.EVT_TIMER, self.on_timer)
179 self.func = func
180
181 def on_timer(self, event):
182 self.func()
183
184 frame = TimerFrame(wake)
185 frame.Show(False)
186
187 # The import of wx on Linux sets the handler for signal.SIGINT
188 # to 0. This is a bug in wx or gtk. We fix by just setting it
189 # back to the Python default.
187 timer = wx.Timer()
188 timer.Start(poll_interval)
189 timer.Bind(wx.EVT_TIMER, poll)
190
191 # The import of wx on Linux sets the handler for signal.SIGINT to 0. This
192 # is a bug in wx or gtk. We fix by just setting it back to the Python
193 # default.
190 194 if not callable(signal.getsignal(signal.SIGINT)):
191 195 signal.signal(signal.SIGINT, signal.default_int_handler)
192 196
193 197 app.MainLoop()
194 198
195 199
196 200 # Get the major wx version number to figure out what input hook we should use.
197 201 major_version = 3
202
198 203 try:
199 204 major_version = int(wx.__version__[0])
200 205 except Exception:
201 206 pass
202 207
203 208 # Use the phoenix hook on all platforms for wxpython >= 4
204 209 if major_version >= 4:
205 210 inputhook = inputhook_wxphoenix
206 211 # On OSX, evtloop.Pending() always returns True, regardless of there being
207 212 # any events pending. As such we can't use implementations 1 or 3 of the
208 213 # inputhook as those depend on a pending/dispatch loop.
209 214 elif sys.platform == 'darwin':
210 215 inputhook = inputhook_wx2
211 216 else:
212 217 inputhook = inputhook_wx3
General Comments 0
You need to be logged in to leave comments. Login now