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