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 thesubprocess
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
Plot a Horizontal Line on a Given Plot
Elegant Python Code for Integer Partitioning
How to Un-Escape a Backslash-Escaped String
How to Create a Custom String Representation for a Class Object
How to Convert Canvas Content to an Image
Sort() and Reverse() Functions Do Not Work
Pygame.Event.Get() Not Returning Any Events When Inside a Thread
How to Check If the String Is Empty
Is There a List of Pytz Timezones
How to Convert JSON Data into a Python Object
How to Find the Exact Intersection of a Curve (As Np.Array) with Y==0
Python Append() VS. + Operator on Lists, Why Do These Give Different Results
Print Multiple Arguments in Python
Taking Multiple Integers on the Same Line as Input from the User in Python