##// END OF EJS Templates
update
Matthias Bussonnier -
Show More
@@ -1,45 +1,34 b''
1 1 name: Run MyPy
2 2
3 3 on:
4 4 push:
5 5 branches: [ master, 7.x]
6 6 pull_request:
7 7 branches: [ master, 7.x]
8 8
9 9 jobs:
10 10 build:
11 11
12 12 runs-on: ubuntu-latest
13 13 strategy:
14 14 matrix:
15 15 python-version: [3.8]
16 16
17 17 steps:
18 18 - uses: actions/checkout@v2
19 19 - name: Set up Python ${{ matrix.python-version }}
20 20 uses: actions/setup-python@v2
21 21 with:
22 22 python-version: ${{ matrix.python-version }}
23 23 - name: Install dependencies
24 24 run: |
25 25 python -m pip install --upgrade pip
26 26 pip install mypy pyflakes flake8
27 27 - name: Lint with mypy
28 28 run: |
29 mypy IPython/terminal/ptutils.py
30 mypy IPython/core/magics/*.py
31 mypy IPython/terminal/__init__.py
32 mypy IPython/terminal/console.py
33 #mypy IPython/terminal/debugger.py
34 #mypy IPython/terminal/embed.py
35 mypy IPython/terminal/interactiveshell.py
36 mypy IPython/terminal/ipapp.py
37 mypy IPython/terminal/magics.py
38 mypy IPython/terminal/prompts.py
39 mypy IPython/terminal/ptshell.py
40 mypy IPython/terminal/ptutils.py
41 mypy IPython/terminal/shortcuts.py
42 mypy IPython/core/c*.py
29 mypy -p IPython.terminal
30 mypy -p IPython.core.magics
43 31 - name: Lint with pyflakes
44 32 run: |
45 33 flake8 IPython/core/magics/script.py
34 flake8 IPython/core/magics/packaging.py
@@ -1,166 +1,166 b''
1 1 import asyncio
2 2 import sys
3 3 import threading
4 4
5 5 from IPython.core.debugger import Pdb
6 6
7 7 from IPython.core.completer import IPCompleter
8 8 from .ptutils import IPythonPTCompleter
9 9 from .shortcuts import create_ipython_shortcuts
10 10 from . import embed
11 11
12 12 from pygments.token import Token
13 13 from prompt_toolkit.shortcuts.prompt import PromptSession
14 14 from prompt_toolkit.enums import EditingMode
15 15 from prompt_toolkit.formatted_text import PygmentsTokens
16 16
17 17 from prompt_toolkit import __version__ as ptk_version
18 18 PTK3 = ptk_version.startswith('3.')
19 19
20 20
21 21 class TerminalPdb(Pdb):
22 22 """Standalone IPython debugger."""
23 23
24 24 def __init__(self, *args, pt_session_options=None, **kwargs):
25 25 Pdb.__init__(self, *args, **kwargs)
26 26 self._ptcomp = None
27 27 self.pt_init(pt_session_options)
28 28
29 29 def pt_init(self, pt_session_options=None):
30 30 """Initialize the prompt session and the prompt loop
31 31 and store them in self.pt_app and self.pt_loop.
32 32
33 33 Additional keyword arguments for the PromptSession class
34 34 can be specified in pt_session_options.
35 35 """
36 36 if pt_session_options is None:
37 37 pt_session_options = {}
38 38
39 39 def get_prompt_tokens():
40 40 return [(Token.Prompt, self.prompt)]
41 41
42 42 if self._ptcomp is None:
43 43 compl = IPCompleter(
44 44 shell=self.shell, namespace={}, global_namespace={}, parent=self.shell
45 45 )
46 46 # add a completer for all the do_ methods
47 47 methods_names = [m[3:] for m in dir(self) if m.startswith("do_")]
48 48
49 49 def gen_comp(self, text):
50 50 return [m for m in methods_names if m.startswith(text)]
51 51 import types
52 52 newcomp = types.MethodType(gen_comp, compl)
53 53 compl.custom_matchers.insert(0, newcomp)
54 54 # end add completer.
55 55
56 56 self._ptcomp = IPythonPTCompleter(compl)
57 57
58 58 options = dict(
59 59 message=(lambda: PygmentsTokens(get_prompt_tokens())),
60 60 editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()),
61 61 key_bindings=create_ipython_shortcuts(self.shell),
62 62 history=self.shell.debugger_history,
63 63 completer=self._ptcomp,
64 64 enable_history_search=True,
65 65 mouse_support=self.shell.mouse_support,
66 66 complete_style=self.shell.pt_complete_style,
67 67 style=self.shell.style,
68 68 color_depth=self.shell.color_depth,
69 69 )
70 70
71 71 if not PTK3:
72 72 options['inputhook'] = self.shell.inputhook
73 73 options.update(pt_session_options)
74 74 self.pt_loop = asyncio.new_event_loop()
75 75 self.pt_app = PromptSession(**options)
76 76
77 77 def cmdloop(self, intro=None):
78 78 """Repeatedly issue a prompt, accept input, parse an initial prefix
79 79 off the received input, and dispatch to action methods, passing them
80 80 the remainder of the line as argument.
81 81
82 82 override the same methods from cmd.Cmd to provide prompt toolkit replacement.
83 83 """
84 84 if not self.use_rawinput:
85 85 raise ValueError('Sorry ipdb does not support use_rawinput=False')
86 86
87 87 # In order to make sure that prompt, which uses asyncio doesn't
88 88 # interfere with applications in which it's used, we always run the
89 89 # prompt itself in a different thread (we can't start an event loop
90 90 # within an event loop). This new thread won't have any event loop
91 91 # running, and here we run our prompt-loop.
92 92
93 93 self.preloop()
94 94
95 95 try:
96 96 if intro is not None:
97 97 self.intro = intro
98 98 if self.intro:
99 99 self.stdout.write(str(self.intro)+"\n")
100 100 stop = None
101 101 while not stop:
102 102 if self.cmdqueue:
103 103 line = self.cmdqueue.pop(0)
104 104 else:
105 105 self._ptcomp.ipy_completer.namespace = self.curframe_locals
106 106 self._ptcomp.ipy_completer.global_namespace = self.curframe.f_globals
107 107
108 108 # Run the prompt in a different thread.
109 109 line = ''
110 110 keyboard_interrupt = False
111 111
112 112 def in_thread():
113 113 nonlocal line, keyboard_interrupt
114 114 try:
115 115 line = self.pt_app.prompt()
116 116 except EOFError:
117 117 line = 'EOF'
118 118 except KeyboardInterrupt:
119 119 keyboard_interrupt = True
120 120
121 121 th = threading.Thread(target=in_thread)
122 122 th.start()
123 123 th.join()
124 124
125 125 if keyboard_interrupt:
126 126 raise KeyboardInterrupt
127 127
128 128 line = self.precmd(line)
129 129 stop = self.onecmd(line)
130 130 stop = self.postcmd(stop, line)
131 131 self.postloop()
132 132 except Exception:
133 133 raise
134 134
135 135 def do_interact(self, arg):
136 136 ipshell = embed.InteractiveShellEmbed(
137 137 config=self.shell.config,
138 138 banner1="*interactive*",
139 139 exit_msg="*exiting interactive console...*",
140 140 )
141 141 global_ns = self.curframe.f_globals
142 142 ipshell(
143 143 module=sys.modules.get(global_ns["__name__"], None),
144 144 local_ns=self.curframe_locals,
145 145 )
146 146
147 147
148 148 def set_trace(frame=None):
149 149 """
150 150 Start debugging from `frame`.
151 151
152 152 If frame is not specified, debugging starts from caller's frame.
153 153 """
154 154 TerminalPdb().set_trace(frame or sys._getframe().f_back)
155 155
156 156
157 157 if __name__ == '__main__':
158 158 import pdb
159 159 # IPython.core.debugger.Pdb.trace_dispatch shall not catch
160 160 # bdb.BdbQuit. When started through __main__ and an exception
161 161 # happened after hitting "c", this is needed in order to
162 162 # be able to quit the debugging session (see #9950).
163 163 old_trace_dispatch = pdb.Pdb.trace_dispatch
164 pdb.Pdb = TerminalPdb
165 pdb.Pdb.trace_dispatch = old_trace_dispatch
164 pdb.Pdb = TerminalPdb # type: ignore
165 pdb.Pdb.trace_dispatch = old_trace_dispatch # type: ignore
166 166 pdb.main()
@@ -1,399 +1,401 b''
1 1 # encoding: utf-8
2 2 """
3 3 An embedded IPython shell.
4 4 """
5 5 # Copyright (c) IPython Development Team.
6 6 # Distributed under the terms of the Modified BSD License.
7 7
8 8
9 9 import sys
10 10 import warnings
11 11
12 12 from IPython.core import ultratb, compilerop
13 13 from IPython.core import magic_arguments
14 14 from IPython.core.magic import Magics, magics_class, line_magic
15 15 from IPython.core.interactiveshell import DummyMod, InteractiveShell
16 16 from IPython.terminal.interactiveshell import TerminalInteractiveShell
17 17 from IPython.terminal.ipapp import load_default_config
18 18
19 19 from traitlets import Bool, CBool, Unicode
20 20 from IPython.utils.io import ask_yes_no
21 21
22 from typing import Set
23
22 24 class KillEmbedded(Exception):pass
23 25
24 26 # kept for backward compatibility as IPython 6 was released with
25 27 # the typo. See https://github.com/ipython/ipython/pull/10706
26 28 KillEmbeded = KillEmbedded
27 29
28 30 # This is an additional magic that is exposed in embedded shells.
29 31 @magics_class
30 32 class EmbeddedMagics(Magics):
31 33
32 34 @line_magic
33 35 @magic_arguments.magic_arguments()
34 36 @magic_arguments.argument('-i', '--instance', action='store_true',
35 37 help='Kill instance instead of call location')
36 38 @magic_arguments.argument('-x', '--exit', action='store_true',
37 39 help='Also exit the current session')
38 40 @magic_arguments.argument('-y', '--yes', action='store_true',
39 41 help='Do not ask confirmation')
40 42 def kill_embedded(self, parameter_s=''):
41 43 """%kill_embedded : deactivate for good the current embedded IPython
42 44
43 45 This function (after asking for confirmation) sets an internal flag so
44 46 that an embedded IPython will never activate again for the given call
45 47 location. This is useful to permanently disable a shell that is being
46 48 called inside a loop: once you've figured out what you needed from it,
47 49 you may then kill it and the program will then continue to run without
48 50 the interactive shell interfering again.
49 51
50 52
51 53 Kill Instance Option:
52 54
53 55 If for some reasons you need to kill the location where the instance
54 56 is created and not called, for example if you create a single
55 57 instance in one place and debug in many locations, you can use the
56 58 ``--instance`` option to kill this specific instance. Like for the
57 59 ``call location`` killing an "instance" should work even if it is
58 60 recreated within a loop.
59 61
60 62 .. note::
61 63
62 64 This was the default behavior before IPython 5.2
63 65
64 66 """
65 67
66 68 args = magic_arguments.parse_argstring(self.kill_embedded, parameter_s)
67 69 print(args)
68 70 if args.instance:
69 71 # let no ask
70 72 if not args.yes:
71 73 kill = ask_yes_no(
72 74 "Are you sure you want to kill this embedded instance? [y/N] ", 'n')
73 75 else:
74 76 kill = True
75 77 if kill:
76 78 self.shell._disable_init_location()
77 79 print("This embedded IPython instance will not reactivate anymore "
78 80 "once you exit.")
79 81 else:
80 82 if not args.yes:
81 83 kill = ask_yes_no(
82 84 "Are you sure you want to kill this embedded call_location? [y/N] ", 'n')
83 85 else:
84 86 kill = True
85 87 if kill:
86 88 self.shell.embedded_active = False
87 89 print("This embedded IPython call location will not reactivate anymore "
88 90 "once you exit.")
89 91
90 92 if args.exit:
91 93 # Ask-exit does not really ask, it just set internals flags to exit
92 94 # on next loop.
93 95 self.shell.ask_exit()
94 96
95 97
96 98 @line_magic
97 99 def exit_raise(self, parameter_s=''):
98 100 """%exit_raise Make the current embedded kernel exit and raise and exception.
99 101
100 102 This function sets an internal flag so that an embedded IPython will
101 103 raise a `IPython.terminal.embed.KillEmbedded` Exception on exit, and then exit the current I. This is
102 104 useful to permanently exit a loop that create IPython embed instance.
103 105 """
104 106
105 107 self.shell.should_raise = True
106 108 self.shell.ask_exit()
107 109
108 110
109 111
110 112 class InteractiveShellEmbed(TerminalInteractiveShell):
111 113
112 114 dummy_mode = Bool(False)
113 115 exit_msg = Unicode('')
114 116 embedded = CBool(True)
115 117 should_raise = CBool(False)
116 118 # Like the base class display_banner is not configurable, but here it
117 119 # is True by default.
118 120 display_banner = CBool(True)
119 121 exit_msg = Unicode()
120 122
121 123 # When embedding, by default we don't change the terminal title
122 124 term_title = Bool(False,
123 125 help="Automatically set the terminal title"
124 126 ).tag(config=True)
125 127
126 _inactive_locations = set()
128 _inactive_locations: Set[str] = set()
129
130 def _disable_init_location(self):
131 """Disable the current Instance creation location"""
132 InteractiveShellEmbed._inactive_locations.add(self._init_location_id)
127 133
128 134 @property
129 135 def embedded_active(self):
130 136 return (self._call_location_id not in InteractiveShellEmbed._inactive_locations)\
131 137 and (self._init_location_id not in InteractiveShellEmbed._inactive_locations)
132 138
133 def _disable_init_location(self):
134 """Disable the current Instance creation location"""
135 InteractiveShellEmbed._inactive_locations.add(self._init_location_id)
136
137 139 @embedded_active.setter
138 140 def embedded_active(self, value):
139 141 if value:
140 142 InteractiveShellEmbed._inactive_locations.discard(
141 143 self._call_location_id)
142 144 InteractiveShellEmbed._inactive_locations.discard(
143 145 self._init_location_id)
144 146 else:
145 147 InteractiveShellEmbed._inactive_locations.add(
146 148 self._call_location_id)
147 149
148 150 def __init__(self, **kw):
149 151 if kw.get('user_global_ns', None) is not None:
150 152 raise DeprecationWarning(
151 153 "Key word argument `user_global_ns` has been replaced by `user_module` since IPython 4.0.")
152 154
153 155 clid = kw.pop('_init_location_id', None)
154 156 if not clid:
155 157 frame = sys._getframe(1)
156 158 clid = '%s:%s' % (frame.f_code.co_filename, frame.f_lineno)
157 159 self._init_location_id = clid
158 160
159 161 super(InteractiveShellEmbed,self).__init__(**kw)
160 162
161 163 # don't use the ipython crash handler so that user exceptions aren't
162 164 # trapped
163 165 sys.excepthook = ultratb.FormattedTB(color_scheme=self.colors,
164 166 mode=self.xmode,
165 167 call_pdb=self.pdb)
166 168
167 169 def init_sys_modules(self):
168 170 """
169 171 Explicitly overwrite :mod:`IPython.core.interactiveshell` to do nothing.
170 172 """
171 173 pass
172 174
173 175 def init_magics(self):
174 176 super(InteractiveShellEmbed, self).init_magics()
175 177 self.register_magics(EmbeddedMagics)
176 178
177 179 def __call__(self, header='', local_ns=None, module=None, dummy=None,
178 180 stack_depth=1, global_ns=None, compile_flags=None, **kw):
179 181 """Activate the interactive interpreter.
180 182
181 183 __call__(self,header='',local_ns=None,module=None,dummy=None) -> Start
182 184 the interpreter shell with the given local and global namespaces, and
183 185 optionally print a header string at startup.
184 186
185 187 The shell can be globally activated/deactivated using the
186 188 dummy_mode attribute. This allows you to turn off a shell used
187 189 for debugging globally.
188 190
189 191 However, *each* time you call the shell you can override the current
190 192 state of dummy_mode with the optional keyword parameter 'dummy'. For
191 193 example, if you set dummy mode on with IPShell.dummy_mode = True, you
192 194 can still have a specific call work by making it as IPShell(dummy=False).
193 195 """
194 196
195 197 # we are called, set the underlying interactiveshell not to exit.
196 198 self.keep_running = True
197 199
198 200 # If the user has turned it off, go away
199 201 clid = kw.pop('_call_location_id', None)
200 202 if not clid:
201 203 frame = sys._getframe(1)
202 204 clid = '%s:%s' % (frame.f_code.co_filename, frame.f_lineno)
203 205 self._call_location_id = clid
204 206
205 207 if not self.embedded_active:
206 208 return
207 209
208 210 # Normal exits from interactive mode set this flag, so the shell can't
209 211 # re-enter (it checks this variable at the start of interactive mode).
210 212 self.exit_now = False
211 213
212 214 # Allow the dummy parameter to override the global __dummy_mode
213 215 if dummy or (dummy != 0 and self.dummy_mode):
214 216 return
215 217
216 218 # self.banner is auto computed
217 219 if header:
218 220 self.old_banner2 = self.banner2
219 221 self.banner2 = self.banner2 + '\n' + header + '\n'
220 222 else:
221 223 self.old_banner2 = ''
222 224
223 225 if self.display_banner:
224 226 self.show_banner()
225 227
226 228 # Call the embedding code with a stack depth of 1 so it can skip over
227 229 # our call and get the original caller's namespaces.
228 230 self.mainloop(local_ns, module, stack_depth=stack_depth,
229 231 global_ns=global_ns, compile_flags=compile_flags)
230 232
231 233 self.banner2 = self.old_banner2
232 234
233 235 if self.exit_msg is not None:
234 236 print(self.exit_msg)
235 237
236 238 if self.should_raise:
237 239 raise KillEmbedded('Embedded IPython raising error, as user requested.')
238 240
239 241
240 242 def mainloop(self, local_ns=None, module=None, stack_depth=0,
241 243 display_banner=None, global_ns=None, compile_flags=None):
242 244 """Embeds IPython into a running python program.
243 245
244 246 Parameters
245 247 ----------
246 248
247 249 local_ns, module
248 250 Working local namespace (a dict) and module (a module or similar
249 251 object). If given as None, they are automatically taken from the scope
250 252 where the shell was called, so that program variables become visible.
251 253
252 254 stack_depth : int
253 255 How many levels in the stack to go to looking for namespaces (when
254 256 local_ns or module is None). This allows an intermediate caller to
255 257 make sure that this function gets the namespace from the intended
256 258 level in the stack. By default (0) it will get its locals and globals
257 259 from the immediate caller.
258 260
259 261 compile_flags
260 262 A bit field identifying the __future__ features
261 263 that are enabled, as passed to the builtin :func:`compile` function.
262 264 If given as None, they are automatically taken from the scope where
263 265 the shell was called.
264 266
265 267 """
266 268
267 269 if (global_ns is not None) and (module is None):
268 270 raise DeprecationWarning("'global_ns' keyword argument is deprecated, and has been removed in IPython 5.0 use `module` keyword argument instead.")
269 271
270 272 if (display_banner is not None):
271 273 warnings.warn("The display_banner parameter is deprecated since IPython 4.0", DeprecationWarning)
272 274
273 275 # Get locals and globals from caller
274 276 if ((local_ns is None or module is None or compile_flags is None)
275 277 and self.default_user_namespaces):
276 278 call_frame = sys._getframe(stack_depth).f_back
277 279
278 280 if local_ns is None:
279 281 local_ns = call_frame.f_locals
280 282 if module is None:
281 283 global_ns = call_frame.f_globals
282 284 try:
283 285 module = sys.modules[global_ns['__name__']]
284 286 except KeyError:
285 287 warnings.warn("Failed to get module %s" % \
286 288 global_ns.get('__name__', 'unknown module')
287 289 )
288 290 module = DummyMod()
289 291 module.__dict__ = global_ns
290 292 if compile_flags is None:
291 293 compile_flags = (call_frame.f_code.co_flags &
292 294 compilerop.PyCF_MASK)
293 295
294 296 # Save original namespace and module so we can restore them after
295 297 # embedding; otherwise the shell doesn't shut down correctly.
296 298 orig_user_module = self.user_module
297 299 orig_user_ns = self.user_ns
298 300 orig_compile_flags = self.compile.flags
299 301
300 302 # Update namespaces and fire up interpreter
301 303
302 304 # The global one is easy, we can just throw it in
303 305 if module is not None:
304 306 self.user_module = module
305 307
306 308 # But the user/local one is tricky: ipython needs it to store internal
307 309 # data, but we also need the locals. We'll throw our hidden variables
308 310 # like _ih and get_ipython() into the local namespace, but delete them
309 311 # later.
310 312 if local_ns is not None:
311 313 reentrant_local_ns = {k: v for (k, v) in local_ns.items() if k not in self.user_ns_hidden.keys()}
312 314 self.user_ns = reentrant_local_ns
313 315 self.init_user_ns()
314 316
315 317 # Compiler flags
316 318 if compile_flags is not None:
317 319 self.compile.flags = compile_flags
318 320
319 321 # make sure the tab-completer has the correct frame information, so it
320 322 # actually completes using the frame's locals/globals
321 323 self.set_completer_frame()
322 324
323 325 with self.builtin_trap, self.display_trap:
324 326 self.interact()
325 327
326 328 # now, purge out the local namespace of IPython's hidden variables.
327 329 if local_ns is not None:
328 330 local_ns.update({k: v for (k, v) in self.user_ns.items() if k not in self.user_ns_hidden.keys()})
329 331
330 332
331 333 # Restore original namespace so shell can shut down when we exit.
332 334 self.user_module = orig_user_module
333 335 self.user_ns = orig_user_ns
334 336 self.compile.flags = orig_compile_flags
335 337
336 338
337 339 def embed(**kwargs):
338 340 """Call this to embed IPython at the current point in your program.
339 341
340 342 The first invocation of this will create an :class:`InteractiveShellEmbed`
341 343 instance and then call it. Consecutive calls just call the already
342 344 created instance.
343 345
344 346 If you don't want the kernel to initialize the namespace
345 347 from the scope of the surrounding function,
346 348 and/or you want to load full IPython configuration,
347 349 you probably want `IPython.start_ipython()` instead.
348 350
349 351 Here is a simple example::
350 352
351 353 from IPython import embed
352 354 a = 10
353 355 b = 20
354 356 embed(header='First time')
355 357 c = 30
356 358 d = 40
357 359 embed()
358 360
359 361 Full customization can be done by passing a :class:`Config` in as the
360 362 config argument.
361 363 """
362 364 config = kwargs.get('config')
363 365 header = kwargs.pop('header', u'')
364 366 compile_flags = kwargs.pop('compile_flags', None)
365 367 if config is None:
366 368 config = load_default_config()
367 369 config.InteractiveShellEmbed = config.TerminalInteractiveShell
368 370 kwargs['config'] = config
369 371 using = kwargs.get('using', 'sync')
370 372 if using :
371 373 kwargs['config'].update({'TerminalInteractiveShell':{'loop_runner':using, 'colors':'NoColor', 'autoawait': using!='sync'}})
372 374 #save ps1/ps2 if defined
373 375 ps1 = None
374 376 ps2 = None
375 377 try:
376 378 ps1 = sys.ps1
377 379 ps2 = sys.ps2
378 380 except AttributeError:
379 381 pass
380 382 #save previous instance
381 383 saved_shell_instance = InteractiveShell._instance
382 384 if saved_shell_instance is not None:
383 385 cls = type(saved_shell_instance)
384 386 cls.clear_instance()
385 387 frame = sys._getframe(1)
386 388 shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % (
387 389 frame.f_code.co_filename, frame.f_lineno), **kwargs)
388 390 shell(header=header, stack_depth=2, compile_flags=compile_flags,
389 391 _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno))
390 392 InteractiveShellEmbed.clear_instance()
391 393 #restore previous instance
392 394 if saved_shell_instance is not None:
393 395 cls = type(saved_shell_instance)
394 396 cls.clear_instance()
395 397 for subclass in cls._walk_mro():
396 398 subclass._instance = saved_shell_instance
397 399 if ps1 is not None:
398 400 sys.ps1 = ps1
399 401 sys.ps2 = ps2
@@ -1,157 +1,157 b''
1 1 """Inputhook for OS X
2 2
3 3 Calls NSApp / CoreFoundation APIs via ctypes.
4 4 """
5 5
6 6 # obj-c boilerplate from appnope, used under BSD 2-clause
7 7
8 8 import ctypes
9 9 import ctypes.util
10 10 from threading import Event
11 11
12 objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc'))
12 objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("objc")) # type: ignore
13 13
14 14 void_p = ctypes.c_void_p
15 15
16 16 objc.objc_getClass.restype = void_p
17 17 objc.sel_registerName.restype = void_p
18 18 objc.objc_msgSend.restype = void_p
19 19 objc.objc_msgSend.argtypes = [void_p, void_p]
20 20
21 21 msg = objc.objc_msgSend
22 22
23 23 def _utf8(s):
24 24 """ensure utf8 bytes"""
25 25 if not isinstance(s, bytes):
26 26 s = s.encode('utf8')
27 27 return s
28 28
29 29 def n(name):
30 30 """create a selector name (for ObjC methods)"""
31 31 return objc.sel_registerName(_utf8(name))
32 32
33 33 def C(classname):
34 34 """get an ObjC Class by name"""
35 35 return objc.objc_getClass(_utf8(classname))
36 36
37 37 # end obj-c boilerplate from appnope
38 38
39 39 # CoreFoundation C-API calls we will use:
40 CoreFoundation = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation'))
40 CoreFoundation = ctypes.cdll.LoadLibrary(ctypes.util.find_library("CoreFoundation")) # type: ignore
41 41
42 42 CFFileDescriptorCreate = CoreFoundation.CFFileDescriptorCreate
43 43 CFFileDescriptorCreate.restype = void_p
44 44 CFFileDescriptorCreate.argtypes = [void_p, ctypes.c_int, ctypes.c_bool, void_p, void_p]
45 45
46 46 CFFileDescriptorGetNativeDescriptor = CoreFoundation.CFFileDescriptorGetNativeDescriptor
47 47 CFFileDescriptorGetNativeDescriptor.restype = ctypes.c_int
48 48 CFFileDescriptorGetNativeDescriptor.argtypes = [void_p]
49 49
50 50 CFFileDescriptorEnableCallBacks = CoreFoundation.CFFileDescriptorEnableCallBacks
51 51 CFFileDescriptorEnableCallBacks.restype = None
52 52 CFFileDescriptorEnableCallBacks.argtypes = [void_p, ctypes.c_ulong]
53 53
54 54 CFFileDescriptorCreateRunLoopSource = CoreFoundation.CFFileDescriptorCreateRunLoopSource
55 55 CFFileDescriptorCreateRunLoopSource.restype = void_p
56 56 CFFileDescriptorCreateRunLoopSource.argtypes = [void_p, void_p, void_p]
57 57
58 58 CFRunLoopGetCurrent = CoreFoundation.CFRunLoopGetCurrent
59 59 CFRunLoopGetCurrent.restype = void_p
60 60
61 61 CFRunLoopAddSource = CoreFoundation.CFRunLoopAddSource
62 62 CFRunLoopAddSource.restype = None
63 63 CFRunLoopAddSource.argtypes = [void_p, void_p, void_p]
64 64
65 65 CFRelease = CoreFoundation.CFRelease
66 66 CFRelease.restype = None
67 67 CFRelease.argtypes = [void_p]
68 68
69 69 CFFileDescriptorInvalidate = CoreFoundation.CFFileDescriptorInvalidate
70 70 CFFileDescriptorInvalidate.restype = None
71 71 CFFileDescriptorInvalidate.argtypes = [void_p]
72 72
73 73 # From CFFileDescriptor.h
74 74 kCFFileDescriptorReadCallBack = 1
75 75 kCFRunLoopCommonModes = void_p.in_dll(CoreFoundation, 'kCFRunLoopCommonModes')
76 76
77 77
78 78 def _NSApp():
79 79 """Return the global NSApplication instance (NSApp)"""
80 80 objc.objc_msgSend.argtypes = [void_p, void_p]
81 81 return msg(C('NSApplication'), n('sharedApplication'))
82 82
83 83
84 84 def _wake(NSApp):
85 85 """Wake the Application"""
86 86 objc.objc_msgSend.argtypes = [
87 87 void_p,
88 88 void_p,
89 89 void_p,
90 90 void_p,
91 91 void_p,
92 92 void_p,
93 93 void_p,
94 94 void_p,
95 95 void_p,
96 96 void_p,
97 97 void_p,
98 98 ]
99 99 event = msg(
100 100 C("NSEvent"),
101 101 n(
102 102 "otherEventWithType:location:modifierFlags:"
103 103 "timestamp:windowNumber:context:subtype:data1:data2:"
104 104 ),
105 105 15, # Type
106 106 0, # location
107 107 0, # flags
108 108 0, # timestamp
109 109 0, # window
110 110 None, # context
111 111 0, # subtype
112 112 0, # data1
113 113 0, # data2
114 114 )
115 115 objc.objc_msgSend.argtypes = [void_p, void_p, void_p, void_p]
116 116 msg(NSApp, n('postEvent:atStart:'), void_p(event), True)
117 117
118 118
119 119 _triggered = Event()
120 120
121 121 def _input_callback(fdref, flags, info):
122 122 """Callback to fire when there's input to be read"""
123 123 _triggered.set()
124 124 CFFileDescriptorInvalidate(fdref)
125 125 CFRelease(fdref)
126 126 NSApp = _NSApp()
127 127 objc.objc_msgSend.argtypes = [void_p, void_p, void_p]
128 128 msg(NSApp, n('stop:'), NSApp)
129 129 _wake(NSApp)
130 130
131 131 _c_callback_func_type = ctypes.CFUNCTYPE(None, void_p, void_p, void_p)
132 132 _c_input_callback = _c_callback_func_type(_input_callback)
133 133
134 134
135 135 def _stop_on_read(fd):
136 136 """Register callback to stop eventloop when there's data on fd"""
137 137 _triggered.clear()
138 138 fdref = CFFileDescriptorCreate(None, fd, False, _c_input_callback, None)
139 139 CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack)
140 140 source = CFFileDescriptorCreateRunLoopSource(None, fdref, 0)
141 141 loop = CFRunLoopGetCurrent()
142 142 CFRunLoopAddSource(loop, source, kCFRunLoopCommonModes)
143 143 CFRelease(source)
144 144
145 145
146 146 def inputhook(context):
147 147 """Inputhook for Cocoa (NSApp)"""
148 148 NSApp = _NSApp()
149 149 _stop_on_read(context.fileno())
150 150 objc.objc_msgSend.argtypes = [void_p, void_p]
151 151 msg(NSApp, n('run'))
152 152 if not _triggered.is_set():
153 153 # app closed without firing callback,
154 154 # probably due to last window being closed.
155 155 # Run the loop manually in this case,
156 156 # since there may be events still to process (#9734)
157 157 CoreFoundation.CFRunLoopRun()
General Comments 0
You need to be logged in to leave comments. Login now