Embedding a Pygame Window into a Tkinter or Wxpython Frame

Embedding a Pygame window into a Tkinter or WxPython frame

(Note this solution does not work on Windows systems with Pygame 2.
See Using 'SDL_WINDOWID' does not embed pygame display correctly into another application #1574. You can currently download older versions of Pygame here.)

According to this SO question and the accepted answer, the simplest way to do this would be to use an SDL drawing frame.

This code is the work of SO user Alex Sallons.

import os
import pygame
import Tkinter as tk
from Tkinter import *

root = tk.Tk()
embed = tk.Frame(root, width = 500, height = 500) #creates embed frame for pygame window
embed.grid(columnspan = (600), rowspan = 500) # Adds grid
embed.pack(side = LEFT) #packs window to the left
buttonwin = tk.Frame(root, width = 75, height = 500)
buttonwin.pack(side = LEFT)
os.environ['SDL_WINDOWID'] = str(embed.winfo_id())
os.environ['SDL_VIDEODRIVER'] = 'windib'
screen = pygame.display.set_mode((500,500))
screen.fill(pygame.Color(255,255,255))
pygame.display.init()
pygame.display.update()

def draw():
pygame.draw.circle(screen, (0,0,0), (250,250), 125)
pygame.display.update()

button1 = Button(buttonwin,text = 'Draw', command=draw)
button1.pack(side=LEFT)
root.update()

while True:
pygame.display.update()
root.update()

This code is cross-platform, as long as the windib SDL_VIDEODRIVER line is omitted on non Windows systems. I would suggest

# [...]
import platform
if platform.system == "Windows":
os.environ['SDL_VIDEODRIVER'] = 'windib'
# [...]

How to open a Pygame window and a Tkinter window simultaneously?

There is a fundamental design issue in pygame that makes it unable to open a window if the process already has a window. It will also prevent other windows from opening while it is running. However, you can open as many TK windows as you like and you can embed a pygame window inside a SDL drawing frame inside a TK window.

See this answer on how to use the drawing frame: Embedding a Pygame window into a Tkinter or WxPython frame

See this answer on how to create multiple windows in tkinter: How to open multiple windows in Tkinter

tkinter and pygame do not want to work in one window

Thanks to acw1668! Here you can download 1.9.6 pygame wheels: https://pypi.org/project/pygame/1.9.6/#files. And everything works fine with python 3.8!

Draw a circle in Pygame using Tkinter

Been meaning to do this for a while, but I had time now, this is some basic code, the program makes a tkinter window and then embeds a pygame window in a frame, it then makes another frame and puts a button on that window that when pressed, calls a function that tells pygame to draw a circle on the pygame window.

import pygame
import Tkinter as tk
from Tkinter import *
import os

root = tk.Tk()

embed = tk.Frame(root, width = 500, height = 500) #creates embed frame for pygame window
embed.grid(columnspan = (600), rowspan = 500) # Adds grid
embed.pack(side = LEFT) #packs window to the left

buttonwin = tk.Frame(root, width = 75, height = 500)
buttonwin.pack(side = LEFT)

os.environ['SDL_WINDOWID'] = str(embed.winfo_id())
os.environ['SDL_VIDEODRIVER'] = 'windib'

screen = pygame.display.set_mode((500,500))
screen.fill(pygame.Color(255,255,255))

pygame.display.init()
pygame.display.update()

def draw():
pygame.draw.circle(screen, (0,0,0), (250,250), 125)
pygame.display.update()

button1 = Button(buttonwin,text = 'Draw', command=draw)
button1.pack(side=LEFT)

root.update()

while True:
pygame.display.update()
root.update()

Tkinter embed Graphical subprocess

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).

pygame.camera livefeed inside a tkinter window (on raspbian)

You have to use pygame after you set os.environ['SDL_WINDOWID']

os.environ['SDL_WINDOWID'] = str(...)
pygame.init()

EDIT: it works on Linux Mint 18.2

import pygame as pg
import pygame.camera
import tkinter as tk
import os
import threading as th

#tk.Frame = tk.LabelFrame

class Viewer(tk.Frame):

def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent # there is self.master which keeps parent

self.parent.protocol('WM_DELETE_WINDOW', self.parent.destroy)

self.screen_width = parent.winfo_screenwidth()
self.screen_height = parent.winfo_screenheight()

self.embed = tk.Frame(self.parent, width=650, height=490)
self.embed.pack(side='left')

self.buttonFrame = tk.Frame(self.parent, width=100, height=490)
self.buttonFrame.pack(side='right')

self.parent.update() # need it to get embed.winfo_id() in Capture

self.c = Capture(self)

self.refreshButton = tk.Button(self.buttonFrame,
text='Refresh',
command=self.refresh)
self.refreshButton.pack()

def refresh(self):
self.c.snap()

class Capture():

def __init__(self, parent):
os.environ['SDL_WINDOWID'] = str(parent.embed.winfo_id())

pg.display.init()
pg.camera.init()

self.size = (640,480)
self.display = pg.display.set_mode(self.size)
self.display.fill(pg.Color(255,255,255))

pg.display.update()

self.clist = pg.camera.list_cameras()

if not self.clist:
raise ValueError("Sorry, no cameras detected.")
print('cameras:', self.clist)

self.cam = pg.camera.Camera(self.clist[0], self.size)
self.cam.start()

self.snapshot = pg.surface.Surface(self.size, 0, self.display)

self.event = th.Thread(target=self.eventCatcher)
self.event.start()

def snap(self):
print('snap ready:', self.cam.query_image())
self.cam.get_image(self.snapshot)
self.display.blit(self.snapshot, self.snapshot.get_rect())
pg.display.update()

def eventCatcher(self):
closed = False
while not closed:
events = pg.event.get()
for e in events:
if e.type == pg.QUIT:
self.cam.stop()
closed = True

root = tk.Tk()
run = Viewer(root)
root.mainloop()

Using pygame features in Tkinter

This works on Linux. If you're lucky, it might work on other operating systems as well.

import Tkinter as tk
import os

w, h = 500, 200

# Add a couple widgets. We're going to put pygame in `embed`.
root = tk.Tk()
embed = tk.Frame(root, width=w, height=h)
embed.pack()
text = tk.Button(root, text='Blah.')
text.pack()

# Tell pygame's SDL window which window ID to use
os.environ['SDL_WINDOWID'] = str(embed.winfo_id())

# The wxPython wiki says you might need the following line on Windows
# (http://wiki.wxpython.org/IntegratingPyGame).
#os.environ['SDL_VIDEODRIVER'] = 'windib'

# Show the window so it's assigned an ID.
root.update()

# Usual pygame initialization
import pygame as pg
pg.display.init()
screen = pg.display.set_mode((w,h))

pos = 0
while 1:
# Do some pygame stuff
screen.fill(pg.Color(0,0,0))
pos = (pos + 1) % screen.get_width()
pg.draw.circle(screen, pg.Color(255,255,255), (pos,100), 30)

# Update the pygame display
pg.display.flip()

# Update the Tk display
root.update()


Related Topics



Leave a reply



Submit