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