jupyter / help

:sparkles: Need some help or have some questions? Please visit our Discourse page.
https://discourse.jupyter.org
291 stars 97 forks source link

ipython terminal GUI toolkit integration #438

Open AndySomogyi opened 5 years ago

AndySomogyi commented 5 years ago

I'm writing a library (C++ with Python API) that needs to open OpenGL windows, and display interactive content there.

I want users to be able to invoke this lib directly from the ipython interactive console, have it open a few windows, return AND then continue listening for user input at the console. Effectively, I want the user experience to be like:

s = Simulator()
s.showWindows() # this method should open some windows and return instantly
s.runAsync()    # this method will start the sim, display live content in the windows, 
                # and return

# the user is now back at the console prompt, with open windows with content. 

# the user now wants to tweak some parameters:
s.tweakSomeParameter() # the simulation is running in the background, 
                       # and the user here can tweak values.  

I've just started reading up on the C PyOS_InputHook routine, and this looks like the PERFECT way to hook up my own GUI event processing. Is there any issues using this with ipython interactive terminal?

But it looks like the IPython.lib.inputhook module has been deprecated. What has replaced this? Could you point me to some examples / docs where interactive GUI toolkits have installed their own message loop, preferably via the C PyOS_InputHook routine.

https://ipython.readthedocs.io/en/stable/api/generated/IPython.lib.inputhook.html

On Mac OSX, the message loop MUST BE ON THE MAIN THREAD. I've written a Mac message loop, looks something like:

- (void)run
{
    shouldKeepRunning = YES;
    do
    {
        // memory autorelease pool stuff...

       // this absolutely must be called on main thread
        NSEvent *event =
            [NSApplication
                nextEventMatchingMask:NSAnyEventMask
                untilDate:[NSDate distantFuture]
                inMode:NSDefaultRunLoopMode
                dequeue:YES];

       // also must be called on main thread. 
        [NSApplication sendEvent:event];
        // update windows, do stuff...
    } while (shouldKeepRunning);

    // pool stuff
}

What I could do, is on the C side, create some Python wrapper functions for the [NSApplication ... nextEventMatchingMask and [NSApplication sendEvent:event] calls, and then tie these into a custom main loop in python that combines the existing ipython interactive terminal with these window handling message calls.

So, my question is how can I integrate the Mac window GUI message loop with the ipython interactive terminal main loop? Is is possible to override the ipython interactive terminal main loop? Could you point me towards the source where it's at?

Ideally, I'd like to do so method like this:

# user is sitting, typing away at ipython interactive terminal, and 
# they load my library:

from Mechanica import simulator as s

s.installHooks()

Then my installHooks method would find the ipython interactive terminal main loop, either hook into it override it with with my own loop which invokes the two Cocoa GUI message handling routines.

AndySomogyi commented 5 years ago

I'm currently digging through the both the Python 3.7 and ipython source.

It looks like PyOS_InputHook is only called from the readline module, and nowhere else.

IPython, as of version 5.? no longer uses readline, rather, it uses it's own prompt_toolkit which does NOT call PyOS_InputHook.

Newer version is python now have their own input hook system, and a number of input hook backends in pt_inputhooks package.

I'm currently studying these to see how they work.

It looks like I could write a module (in C) that calls the various glfw poll / dispatch events functions I need, and expose them as python methods, that I could then call from a my own input hook backend.

minrk commented 5 years ago

ipykernel also has some integration with the mac eventloop, which you can call via ctypes if your interactions are simple enough. Calling objc from Python via ctypes isn't the simplest, though!