Tkinter embed Graphical subprocess

2019-05-26 16:40发布

I am trying to execute a compiled Pygame Graphical application from a Tkinter interface. However, I want the pygame interface to launch into a 800x600 Graphical frame. In the same root window below the 800x600 frame that is for the pygame application, I am looking for a way to embed either:

  1. a xterm that launches a python executable automatically located in a subdirectory called "lib/" OR

  2. directly call the executable located in the named "lib/" folder.

The only helpful documentation i have found on the subject is here: http://poultryandprogramming.wordpress.com/2012/10/28/embedded-terminal-in-pythontk-window/

The os.system route is the only method I see any form of documentation for.

Is it possible to do what I want with subprocess.Popen?

1条回答
Fickle 薄情
2楼-- · 2019-05-26 17:36

This seems to be two separate questions.


First, to directly run an executable in Python, all you need is the subprocess module. I don't know how you missed seeing this when you say you've seen the documentation for os.system, because that clearly says:

The subprocess module provides more powerful facilities for spawning new processes and retrieving their results; using that module is preferable to using this function. See the Replacing Older Functions with the subprocess Module section in the subprocess documentation for some helpful recipes.

In particular, to run some executable in the "lib/" folder, just do this:

p = subprocess.Popen(['lib/the_executable'])

However, I'm guessing you really want not lib from the current working directory, but lib from the directory the main script resides in, right? For that, you'll want to do something like this at script startup:

scriptdir = os.path.abspath(os.path.dirname(__file__))

… and then something like this when you launch the child:

path = os.path.join(scriptdir, 'lib/the_executable')
p = subprocess.Popen([path])

At any rate, once you have a Popen object, you can check whether p is still running, etc., by calling poll every so often, or by spawning a thread to block on wait; you can kill it by calling kill on it; etc. The docs on Popen objects show all the things you can do.


If you'd prefer to run an xterm executable that launches the Python executable, you can do that—just pass xterm as the first argument, and the relevant xterm arguments as the remaining arguments.

But I can't see what good that would do you. You're not trying to embed a terminal session in your window, but the game itself. When you launch a GUI app from an xterm session, it doesn't run inside the xterm window; it runs in a new window. The same thing will happen if the xterm window is embedded.


As for embedding the Pygame window in your own window, there are two ways to do that.


If you're writing the Pygame game yourself, as the display.init docs say:

On some platforms it is possible to embed the pygame display into an already existing window. To do this, the environment variable SDL_WINDOWID must be set to a string containing the window id or handle. The environment variable is checked when the pygame display is initialized. Be aware that there can be many strange side effects when running in an embedded display.

Notice that the Popen constructor takes an env argument:

If env is not None, it must be a mapping that defines the environment variables for the new process; these are used instead of inheriting the current process’ environment, which is the default behavior.

So, you can do this:

child_env = dict(os.environ)
child_env['SDL_WINDOWID'] = the_window_id
p = subprocess.Popen([path], env=child_env)

The problem is, what window ID do you give it? Well, the blog you posted to already has the answer. The same ID that you'd give xterm's -into.

The blog doesn't explain how to restrict it to part of your window, but the code it refers to must. The answer is to either (a) embed a child window in the main window, and give the child window's ID to the child process, or (b) give the entire window to the child process, then have the child immediately create a sub-surface and only draw to that instead of to the whole display.

However, for the particular case of Pygame (or other SDL) apps, as opposed to xterm, setting SDL_VIDEO_WINDOW_POS in the environment should also work. (As far as I can tell, this isn't a documented feature of Pygame, but it is a documented feature of SDL, so it ought to be reliable.)

Ultimately, you'll probably need a bit of cooperation between the two apps. Spmething like this:

Tkinter parent:

child_env = dict(os.environ)
child_env['SDL_WINDOWID'] = the_window_id
child_env['SDL_VIDEO_WINDOW_POS'] = '{},{}'.format(left, top)
p = subprocess.Popen([path, width, height], env=child_env)

Pygame child:

width, height = sys.argv[1:3]
pygame.display.init()
pygame.display.set_mode((width, height), pygame.NOFRAME)

If you can't modify the Pygame app, things will be trickier. You will have to create two separate windows embedded into a parent window, or two windows top-level docked together in some way. (The latter isn't as scary as it sounds in X11. Whenever one window moves, programmatically move the other one.) Either way, you then launch the Pygame app embedded in one child window, and cram the Tkinter stuff into the other. You may be able to do that all through Tkinter; you may have to make Xlib calls directly (either by ctypes-ing to Xlib, or by using something like python-xlib).

查看更多
登录 后发表回答