##// END OF EJS Templates
Add prompt_toolkit input hooks for wx
Thomas Kluyver -
Show More
@@ -0,0 +1,148 b''
1 """Enable wxPython to be used interacively in prompt_toolkit
2 """
3 from __future__ import absolute_import
4
5 import sys
6 import signal
7 import time
8 from timeit import default_timer as clock
9 import wx
10
11
12 def inputhook_wx1(context):
13 """Run the wx event loop by processing pending events only.
14
15 This approach seems to work, but its performance is not great as it
16 relies on having PyOS_InputHook called regularly.
17 """
18 try:
19 app = wx.GetApp()
20 if app is not None:
21 assert wx.Thread_IsMain()
22
23 # Make a temporary event loop and process system events until
24 # there are no more waiting, then allow idle events (which
25 # will also deal with pending or posted wx events.)
26 evtloop = wx.EventLoop()
27 ea = wx.EventLoopActivator(evtloop)
28 while evtloop.Pending():
29 evtloop.Dispatch()
30 app.ProcessIdle()
31 del ea
32 except KeyboardInterrupt:
33 pass
34 return 0
35
36 class EventLoopTimer(wx.Timer):
37
38 def __init__(self, func):
39 self.func = func
40 wx.Timer.__init__(self)
41
42 def Notify(self):
43 self.func()
44
45 class EventLoopRunner(object):
46
47 def Run(self, time, input_is_ready):
48 self.input_is_ready = input_is_ready
49 self.evtloop = wx.EventLoop()
50 self.timer = EventLoopTimer(self.check_stdin)
51 self.timer.Start(time)
52 self.evtloop.Run()
53
54 def check_stdin(self):
55 if self.input_is_ready():
56 self.timer.Stop()
57 self.evtloop.Exit()
58
59 def inputhook_wx2(context):
60 """Run the wx event loop, polling for stdin.
61
62 This version runs the wx eventloop for an undetermined amount of time,
63 during which it periodically checks to see if anything is ready on
64 stdin. If anything is ready on stdin, the event loop exits.
65
66 The argument to elr.Run controls how often the event loop looks at stdin.
67 This determines the responsiveness at the keyboard. A setting of 1000
68 enables a user to type at most 1 char per second. I have found that a
69 setting of 10 gives good keyboard response. We can shorten it further,
70 but eventually performance would suffer from calling select/kbhit too
71 often.
72 """
73 try:
74 app = wx.GetApp()
75 if app is not None:
76 assert wx.Thread_IsMain()
77 elr = EventLoopRunner()
78 # As this time is made shorter, keyboard response improves, but idle
79 # CPU load goes up. 10 ms seems like a good compromise.
80 elr.Run(time=10, # CHANGE time here to control polling interval
81 input_is_ready=context.input_is_ready)
82 except KeyboardInterrupt:
83 pass
84 return 0
85
86 def inputhook_wx3(context):
87 """Run the wx event loop by processing pending events only.
88
89 This is like inputhook_wx1, but it keeps processing pending events
90 until stdin is ready. After processing all pending events, a call to
91 time.sleep is inserted. This is needed, otherwise, CPU usage is at 100%.
92 This sleep time should be tuned though for best performance.
93 """
94 # We need to protect against a user pressing Control-C when IPython is
95 # idle and this is running. We trap KeyboardInterrupt and pass.
96 try:
97 app = wx.GetApp()
98 if app is not None:
99 assert wx.Thread_IsMain()
100
101 # The import of wx on Linux sets the handler for signal.SIGINT
102 # to 0. This is a bug in wx or gtk. We fix by just setting it
103 # back to the Python default.
104 if not callable(signal.getsignal(signal.SIGINT)):
105 signal.signal(signal.SIGINT, signal.default_int_handler)
106
107 evtloop = wx.EventLoop()
108 ea = wx.EventLoopActivator(evtloop)
109 t = clock()
110 while not context.input_is_ready():
111 while evtloop.Pending():
112 t = clock()
113 evtloop.Dispatch()
114 app.ProcessIdle()
115 # We need to sleep at this point to keep the idle CPU load
116 # low. However, if sleep to long, GUI response is poor. As
117 # a compromise, we watch how often GUI events are being processed
118 # and switch between a short and long sleep time. Here are some
119 # stats useful in helping to tune this.
120 # time CPU load
121 # 0.001 13%
122 # 0.005 3%
123 # 0.01 1.5%
124 # 0.05 0.5%
125 used_time = clock() - t
126 if used_time > 10.0:
127 # print 'Sleep for 1 s' # dbg
128 time.sleep(1.0)
129 elif used_time > 0.1:
130 # Few GUI events coming in, so we can sleep longer
131 # print 'Sleep for 0.05 s' # dbg
132 time.sleep(0.05)
133 else:
134 # Many GUI events coming in, so sleep only very little
135 time.sleep(0.001)
136 del ea
137 except KeyboardInterrupt:
138 pass
139 return 0
140
141 if sys.platform == 'darwin':
142 # On OSX, evtloop.Pending() always returns True, regardless of there being
143 # any events pending. As such we can't use implementations 1 or 3 of the
144 # inputhook as those depend on a pending/dispatch loop.
145 inputhook = inputhook_wx2
146 else:
147 # This is our default implementation
148 inputhook = inputhook_wx3
@@ -1,16 +1,16 b''
1 import importlib
1 import importlib
2 import os
2 import os
3
3
4 aliases = {
4 aliases = {
5 'qt4': 'qt'
5 'qt4': 'qt',
6 }
6 }
7
7
8 def get_inputhook_func(gui):
8 def get_inputhook_func(gui):
9 if gui in aliases:
9 if gui in aliases:
10 return get_inputhook_func(aliases[gui])
10 return get_inputhook_func(aliases[gui])
11
11
12 if gui == 'qt5':
12 if gui == 'qt5':
13 os.environ['QT_API'] = 'pyqt5'
13 os.environ['QT_API'] = 'pyqt5'
14
14
15 mod = importlib.import_module('IPython.terminal.pt_inputhooks.'+gui)
15 mod = importlib.import_module('IPython.terminal.pt_inputhooks.'+gui)
16 return mod.inputhook
16 return mod.inputhook
@@ -1,55 +1,56 b''
1 # Code borrowed from python-prompt-toolkit examples
1 # Code borrowed from python-prompt-toolkit examples
2 # https://github.com/jonathanslenders/python-prompt-toolkit/blob/77cdcfbc7f4b4c34a9d2f9a34d422d7152f16209/examples/inputhook.py
2 # https://github.com/jonathanslenders/python-prompt-toolkit/blob/77cdcfbc7f4b4c34a9d2f9a34d422d7152f16209/examples/inputhook.py
3
3
4 # Copyright (c) 2014, Jonathan Slenders
4 # Copyright (c) 2014, Jonathan Slenders
5 # All rights reserved.
5 # All rights reserved.
6 #
6 #
7 # Redistribution and use in source and binary forms, with or without modification,
7 # Redistribution and use in source and binary forms, with or without modification,
8 # are permitted provided that the following conditions are met:
8 # are permitted provided that the following conditions are met:
9 #
9 #
10 # * Redistributions of source code must retain the above copyright notice, this
10 # * Redistributions of source code must retain the above copyright notice, this
11 # list of conditions and the following disclaimer.
11 # list of conditions and the following disclaimer.
12 #
12 #
13 # * Redistributions in binary form must reproduce the above copyright notice, this
13 # * Redistributions in binary form must reproduce the above copyright notice, this
14 # list of conditions and the following disclaimer in the documentation and/or
14 # list of conditions and the following disclaimer in the documentation and/or
15 # other materials provided with the distribution.
15 # other materials provided with the distribution.
16 #
16 #
17 # * Neither the name of the {organization} nor the names of its
17 # * Neither the name of the {organization} nor the names of its
18 # contributors may be used to endorse or promote products derived from
18 # contributors may be used to endorse or promote products derived from
19 # this software without specific prior written permission.
19 # this software without specific prior written permission.
20 #
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
22 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
24 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
25 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
31
32 """
32 """
33 PyGTK input hook for prompt_toolkit.
33 PyGTK input hook for prompt_toolkit.
34
34
35 Listens on the pipe prompt_toolkit sets up for a notification that it should
35 Listens on the pipe prompt_toolkit sets up for a notification that it should
36 return control to the terminal event loop.
36 return control to the terminal event loop.
37 """
37 """
38 from __future__ import absolute_import
38
39
39 import gtk, gobject
40 import gtk, gobject
40
41
41 def inputhook(context):
42 def inputhook(context):
42 """
43 """
43 When the eventloop of prompt-toolkit is idle, call this inputhook.
44 When the eventloop of prompt-toolkit is idle, call this inputhook.
44
45
45 This will run the GTK main loop until the file descriptor
46 This will run the GTK main loop until the file descriptor
46 `context.fileno()` becomes ready.
47 `context.fileno()` becomes ready.
47
48
48 :param context: An `InputHookContext` instance.
49 :param context: An `InputHookContext` instance.
49 """
50 """
50 def _main_quit(*a, **kw):
51 def _main_quit(*a, **kw):
51 gtk.main_quit()
52 gtk.main_quit()
52 return False
53 return False
53
54
54 gobject.io_add_watch(context.fileno(), gobject.IO_IN, _main_quit)
55 gobject.io_add_watch(context.fileno(), gobject.IO_IN, _main_quit)
55 gtk.main()
56 gtk.main()
General Comments 0
You need to be logged in to leave comments. Login now