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