Python centering window on "monitor" not "screen" or "desktop" - python

There are many questions on how to center a python tkinter window on the screen and the answer works well. My problem is my so-called "screen" looks like this:
Although you can move windows partially (or entirely) to the grey areas they won't actually show up on any of my three monitors. Top left monitor is 1920x1080, top right monitor is 3840x2160 and bottom right monitor is 1920x1080.
A program can be started via desktop icon which could be on any monitor or via gnome-terminal which could be on any monitor. How does one discover:
Which monitor was active when python was invoked?
Coordinates of active monitor within the screen real estate?
Although I'm using Gnome Desktop I'd like support for all Linux flavors using X11 or Wayland. Additionally I tried out ChromeOS Linux Beta lately and support for it would also be nice. Furthermore support for Windows and OSX is highly desired.
I've already installed and used many tools gi, wnck, xdotool, wmctrl that hem me into a corner. I'm hoping their is a popular python library (preferably installed via apt-get and not pip or pip3) that can expose "screen", "desktop" and "monitors" to python.

I answered my own question. It was one of those answers that stops you from falling asleep Saturday night at midnight so you get up at 1:00 am on Sunday and code until 4:30 am.
Here's the code which you can adapt for non-Ubuntu environments (using the "future code" functions):
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#==============================================================================
#
# m - Wrapper for mserve.py
#
#==============================================================================
'''
Splash screen for mserve.
mserve has it's own list of required modules but this wrapper requires:
Gnome Desktop Toolkit (Gdk)
'''
from __future__ import print_function # Must be first import
try:
import tkinter as tk
PYTHON_VER="3"
except ImportError: # Python 2
import Tkinter as tk
PYTHON_VER="2"
import image as img # Routines for tk & photo images
import mserve # Script loaded as module for .pyc
# https://stackoverflow.com/a/36419702/6929343
import logging
logging.getLogger('PIL').setLevel(logging.WARNING)
import sys
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
level=logging.DEBUG,
stream=sys.stdout)
''' Future code '''
def get_active_window():
"""
From: https://stackoverflow.com/a/36419702/6929343
Get the currently active window.
Returns
-------
string :
Name of the currently active window.
"""
import sys
active_window_name = None
logging.info('sys.platform: ' + sys.platform)
print('sys.platform:', sys.platform)
if sys.platform in ['linux', 'linux2']:
# Alternatives: http://unix.stackexchange.com/q/38867/4784
try:
import wnck
except ImportError:
logging.info("wnck not installed")
wnck = None
if wnck is not None:
screen = wnck.screen_get_default()
screen.force_update()
window = screen.get_active_window()
if window is not None:
pid = window.get_pid()
with open("/proc/{pid}/cmdline".format(pid=pid)) as f:
active_window_name = f.read()
else:
try:
# Next 3 limes from: https://stackoverflow.com/a/43349245/6929343
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Wnck', '3.0')
# Continue with original code:
from gi.repository import Gtk, Wnck
gi = "Installed"
except ImportError:
logging.info("gi.repository not installed")
gi = None
if gi is not None:
Gtk.init([]) # necessary if not using a Gtk.main() loop
screen = Wnck.Screen.get_default()
screen.force_update() # recommended per Wnck documentation
active_window = screen.get_active_window()
pid = active_window.get_pid()
with open("/proc/{pid}/cmdline".format(pid=pid)) as f:
active_window_name = f.read()
elif sys.platform in ['Windows', 'win32', 'cygwin']:
# http://stackoverflow.com/a/608814/562769
import win32gui
window = win32gui.GetForegroundWindow()
active_window_name = win32gui.GetWindowText(window)
elif sys.platform in ['Mac', 'darwin', 'os2', 'os2emx']:
# http://stackoverflow.com/a/373310/562769
from AppKit import NSWorkspace
active_window_name = (NSWorkspace.sharedWorkspace()
.activeApplication()['NSApplicationName'])
else:
print("sys.platform={platform} is unknown. Please report."
.format(platform=sys.platform))
print(sys.version)
print("Active window: %s" % str(active_window_name))
return active_window_name
''' Future code '''
def get_GtkWindow(w):
# From: https://askubuntu.com/a/303754/307523
import gi
gi.require_version('Gdk', '3.0')
gi.require_version('Gtk', '3.0')
from gi.repository import Gdk, Gtk
# Replace w with the GtkWindow of your application
w = Gtk.Window()
# Get the screen from the GtkWindow
s = w.get_screen()
# Using the screen of the Window, the monitor it's on can be identified
m = s.get_monitor_at_window(s.get_active_window())
# Then get the geometry of that monitor
monitor = s.get_monitor_geometry(m)
# This is an example output
print("Height: %s, Width: %s, X: %s, Y: %s" % \
(monitor.height, monitor.width, monitor.x, monitor.y))
''' Future code '''
def get_monitors():
"""
Get list of monitors in Gnome Desktop
"""
import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk
global NUMBER_OF_MONITORS, GNOME, ACTIVE_MONITOR, MONITOR_GEOMETRY
display = Gdk.Display.get_default()
screen = display.get_default_screen()
window = screen.get_active_window()
ACTIVE_MONITOR = screen.get_monitor_at_window(window)
print('ACTIVE_MONITOR:', ACTIVE_MONITOR)
# Gnome version 3.22 developed new monitor object
try:
# Gnome 3.22
NUMBER_OF_MONITORS = display.get_n_monitors()
monitor = display.get_monitor(ACTIVE_MONITOR)
MONITOR_GEOMETRY = monitor.get_geometry()
GNOME=3.22
except:
# Gnome 3.18
NUMBER_OF_MONITORS = screen.get_n_monitors()
MONITOR_GEOMETRY = screen.get_monitor_geometry(ACTIVE_MONITOR)
GNOME=3.18
# collect data about monitors
for index in range(NUMBER_OF_MONITORS):
if GNOME==3.22:
monitor = display.get_monitor(index)
geometry = monitor.get_geometry()
name = monitor.get_monitor_plug_name()
else:
geometry = screen.get_monitor_geometry(index)
name = screen.get_monitor_plug_name(index)
print("Monitor {} = {}x{}+{}+{}".format(index, geometry.width, \
geometry.height, geometry.x, geometry.y), name)
#get_monitors()
#print('ACTIVE_MONITOR:', ACTIVE_MONITOR, 'MONITOR_GEOMETRY:', MONITOR_GEOMETRY)
''' Start of REAL code used today (May 2, 2021) '''
def get_window_monitor(window):
"""
Returns the Gdk monitor geometry rectangle tkinter window is on.
If window is off screen force it into Monitor 1 (index 0).
:param window: Tkinter root or Topleel
"""
import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk
# global variables that might be useful down the road but not on May 2, 2021
global NUMBER_OF_MONITORS, GNOME
display = Gdk.Display.get_default()
screen = display.get_default_screen()
# Gnome version 3.22 deprecated what used to work 3.18.
# Gonme wasn't built in a day but, it was burned over night in next release!
try:
# Gnome 3.22
NUMBER_OF_MONITORS = display.get_n_monitors()
GNOME=3.22
except:
# Gnome 3.18
NUMBER_OF_MONITORS = screen.get_n_monitors()
GNOME=3.18
x = window.winfo_x() # Window's left coordinate on screen
y = window.winfo_y() # Window's top coordinate on screen
if x < 0: x = 0 # Window top left may be off screen!
if y < 0: y = 0
first_monitor = None
for index in range (NUMBER_OF_MONITORS):
if GNOME==3.22:
# Gnome version 3.22 developed new monitor object
monitor = display.get_monitor(index)
mon_geom = monitor.get_geometry()
else:
# Gnome version 3.18 uses screen object for monitor properties
mon_geom = screen.get_monitor_geometry(index)
# Save first monitor if needed later
if not first_monitor:
first_monitor = mon_geom
# Copmare to monitor's coordinates on screen and monitor width x height
if x < mon_geom.x: continue
if x >= mon_geom.x + mon_geom.width: continue
if y < mon_geom.y: continue
if y >= mon_geom.y + mon_geom.height: continue
# Window is comletely on this monitor.
return mon_geom
# If window off of screen use first monitor
return first_monitor
def center(window):
"""
From: https://stackoverflow.com/a/10018670/6929343
centers a tkinter window on monitor in multi-monitor setup
:param win: the main window or Toplevel window to center
"""
window.update_idletasks() # Refresh window's current position
mon_geom=get_window_monitor(window) # Monitor geometry window is on
if mon_geom is None:
logging.error("No monitors found!")
return None
# Calcuate X, Y of window to center within monitors X, Y, width and height
x = mon_geom.width // 2 - window.winfo_width() // 2 + mon_geom.x
y = mon_geom.height // 2 - window.winfo_height() // 2 + mon_geom.y
if x < 0: x = 0 # Window top left may be off screen!
if y < 0: y = 0
window.geometry('+{}+{}'.format(x, y))
window.deiconify() # Forces window to appear
return mon_geom
def main():
"""
Create splash screen and invoke mserve.py which takes a second or more
"""
splash = tk.Tk() # "very top" toplevel
splash.title("Music Server - mserve")
''' Set font style for all fonts including tkSimpleDialog.py '''
img.set_font_style() # Make messagebox text larger for HDPI monitors
''' Get splash image '''
splash_image = img.m_splash_image(300, 'white', 'lightskyblue', 'black')
# create and pack the canvas. Then load image file
canvas = tk.Canvas(width=300, height=300, bg='black')
canvas.pack(expand=tk.YES, fill=tk.BOTH)
canvas.create_image(0, 0, image=splash_image, anchor=tk.NW)
splash.update_idletasks() # This is required for visibility
# Cemter splash screen on monitor and get monitors geometry
mon_geom=center(splash)
splash.update() # This is required for visibility
# At this point make window undecorated, don't do it sooner!
# From: https://stackoverflow.com/a/37199655/6929343
splash.overrideredirect(True) # Undecorated to prevent close
# Call mserve module about 10k lines of code
mserve.main(toplevel=splash, mon_geom=mon_geom)
exit() # Required to close mserve library
splash.mainloop()
if __name__ == "__main__":
main()
# End of m

Related

HTML Video in Tkinter Window

is there any way to embed a HTML Video File in an tkinter window ?
I would be pleased for all kind of input!
Thanks a lot
One of solution is to use cv2 to read video frame-by-frame and replace frame on tk.Label or tk.Canvas - and you will see video.
(And cv2 uses ffmpeg for this)
cv2 can read from video file, local webcam or from remote stream (HTTP or RTMP)
Because it works with frames so you can draw some text or figures on frame before displaying - this way you can add some buttons or descriptions. You may also make modifications - crop, flip, convert to gray, etc.
But it has one problem: cv2 doesn't work with audio - so it can't play audio from file.
import tkinter as tk
from PIL import Image, ImageTk
import cv2
# --- functions ---
def update_frame():
ret, frame = video.read()
if ret: # check status - because sometimes it may have problem to read frame
# cv2 keeps image as `BGR` and it needs to convert to `RGB`
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# resize to tkinter's window
frame = cv2.resize(frame, (800, 600))
image = Image.fromarray(frame) # convert numpy.array to PIL.Image
photo.paste(image) # copy image on photo
# update again after some time (in milliseconds) (ie. 1000ms/25fps = 40ms)
root.after(int(1000/fps), update_frame)
# --- main ---
#video = cv2.VideoCapture(0) # local webcam
video = cv2.VideoCapture(BigBuckBunny.mp4")
#w = video.get(cv2.CAP_PROP_FRAME_WIDTH)
#h = video.get(cv2.CAP_PROP_FRAME_HEIGHT)
fps = video.get(cv2.CAP_PROP_FPS)
# get first frame to create photo
ret, frame = video.read()
# cv2 keeps image as `BGR` and it needs to convert to `RGB`
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# resize to tkinter's window
frame = cv2.resize(frame, (800, 600))
image = Image.fromarray(frame) # convert numpy.array to PIL.Image
# - GUI -
root = tk.Tk()
root.geometry('800x600')
photo = ImageTk.PhotoImage(image) # it has to be after `tk.Tk()`
canvas = tk.Canvas(root, width=photo.width(), height=photo.height())
canvas.pack(fill='both', expand=True)
image_id = canvas.create_image((0,0), image=photo, anchor='nw')
update_frame() # update it first time
root.mainloop() # run loop all time - it shows window
# - after close window -
# close stream
video.release()
Using Google I found module tkVideo.
It uses imageio to read frame-by-frame. (And imageio uses ffmpeg for this).
And it use threading instead of root.after().
It has the same problem: iamgeio doesn't work with audio - so it can't play audio from file.
EDIT:
Other method is to embed some Video Player like VLC, MPlayer, etc.. And it should gives video with audio but it doesn't allow to draw elements
I found example code in documentation for video player VLC but it seems complex because it adds also buttons to start/stop/pause video.
http://git.videolan.org/?p=vlc/bindings/python.git;a=blob;f=examples/tkvlc.py;h=55314cab09948fc2b7c84f14a76c6d1a7cbba127;hb=HEAD
#! /usr/bin/python
# -*- coding: utf-8 -*-
# tkinter example for VLC Python bindings
# Copyright (C) 2015 the VideoLAN team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
#
"""A simple example for VLC python bindings using tkinter.
Requires Python 3.4 or later.
Author: Patrick Fay
Date: 23-09-2015
"""
# Tested with Python 3.7.4, tkinter/Tk 8.6.9 on macOS 10.13.6 only.
__version__ = '20.05.04' # mrJean1 at Gmail
# import external libraries
import vlc
# import standard libraries
import sys
if sys.version_info[0] < 3:
import Tkinter as Tk
from Tkinter import ttk
from Tkinter.filedialog import askopenfilename
from Tkinter.tkMessageBox import showerror
else:
import tkinter as Tk
from tkinter import ttk
from tkinter.filedialog import askopenfilename
from tkinter.messagebox import showerror
from os.path import basename, expanduser, isfile, join as joined
from pathlib import Path
import time
_isMacOS = sys.platform.startswith('darwin')
_isWindows = sys.platform.startswith('win')
_isLinux = sys.platform.startswith('linux')
if _isMacOS:
from ctypes import c_void_p, cdll
# libtk = cdll.LoadLibrary(ctypes.util.find_library('tk'))
# returns the tk library /usr/lib/libtk.dylib from macOS,
# but we need the tkX.Y library bundled with Python 3+,
# to match the version number of tkinter, _tkinter, etc.
try:
libtk = 'libtk%s.dylib' % (Tk.TkVersion,)
prefix = getattr(sys, 'base_prefix', sys.prefix)
libtk = joined(prefix, 'lib', libtk)
dylib = cdll.LoadLibrary(libtk)
# getNSView = dylib.TkMacOSXDrawableView is the
# proper function to call, but that is non-public
# (in Tk source file macosx/TkMacOSXSubwindows.c)
# and dylib.TkMacOSXGetRootControl happens to call
# dylib.TkMacOSXDrawableView and return the NSView
_GetNSView = dylib.TkMacOSXGetRootControl
# C signature: void *_GetNSView(void *drawable) to get
# the Cocoa/Obj-C NSWindow.contentView attribute, the
# drawable NSView object of the (drawable) NSWindow
_GetNSView.restype = c_void_p
_GetNSView.argtypes = c_void_p,
del dylib
except (NameError, OSError): # image or symbol not found
def _GetNSView(unused):
return None
libtk = "N/A"
C_Key = "Command-" # shortcut key modifier
else: # *nix, Xwindows and Windows, UNTESTED
libtk = "N/A"
C_Key = "Control-" # shortcut key modifier
class _Tk_Menu(Tk.Menu):
'''Tk.Menu extended with .add_shortcut method.
Note, this is a kludge just to get Command-key shortcuts to
work on macOS. Other modifiers like Ctrl-, Shift- and Option-
are not handled in this code.
'''
_shortcuts_entries = {}
_shortcuts_widget = None
def add_shortcut(self, label='', key='', command=None, **kwds):
'''Like Tk.menu.add_command extended with shortcut key.
If needed use modifiers like Shift- and Alt_ or Option-
as before the shortcut key character. Do not include
the Command- or Control- modifier nor the <...> brackets
since those are handled here, depending on platform and
as needed for the binding.
'''
# <https://TkDocs.com/tutorial/menus.html>
if not key:
self.add_command(label=label, command=command, **kwds)
elif _isMacOS:
# keys show as upper-case, always
self.add_command(label=label, accelerator='Command-' + key,
command=command, **kwds)
self.bind_shortcut(key, command, label)
else: # XXX not tested, not tested, not tested
self.add_command(label=label, underline=label.lower().index(key),
command=command, **kwds)
self.bind_shortcut(key, command, label)
def bind_shortcut(self, key, command, label=None):
"""Bind shortcut key, default modifier Command/Control.
"""
# The accelerator modifiers on macOS are Command-,
# Ctrl-, Option- and Shift-, but for .bind[_all] use
# <Command-..>, <Ctrl-..>, <Option_..> and <Shift-..>,
# <https://www.Tcl.Tk/man/tcl8.6/TkCmd/bind.htm#M6>
if self._shortcuts_widget:
if C_Key.lower() not in key.lower():
key = "<%s%s>" % (C_Key, key.lstrip('<').rstrip('>'))
self._shortcuts_widget.bind(key, command)
# remember the shortcut key for this menu item
if label is not None:
item = self.index(label)
self._shortcuts_entries[item] = key
# The Tk modifier for macOS' Command key is called
# Meta, but there is only Meta_L[eft], no Meta_R[ight]
# and both keyboard command keys generate Meta_L events.
# Similarly for macOS' Option key, the modifier name is
# Alt and there's only Alt_L[eft], no Alt_R[ight] and
# both keyboard option keys generate Alt_L events. See:
# <https://StackOverflow.com/questions/6378556/multiple-
# key-event-bindings-in-tkinter-control-e-command-apple-e-etc>
def bind_shortcuts_to(self, widget):
'''Set the widget for the shortcut keys, usually root.
'''
self._shortcuts_widget = widget
def entryconfig(self, item, **kwds):
"""Update shortcut key binding if menu entry changed.
"""
Tk.Menu.entryconfig(self, item, **kwds)
# adjust the shortcut key binding also
if self._shortcuts_widget:
key = self._shortcuts_entries.get(item, None)
if key is not None and "command" in kwds:
self._shortcuts_widget.bind(key, kwds["command"])
class Player(Tk.Frame):
"""The main window has to deal with events.
"""
_geometry = ''
_stopped = None
def __init__(self, parent, title=None, video=''):
Tk.Frame.__init__(self, parent)
self.parent = parent # == root
self.parent.title(title or "tkVLCplayer")
self.video = expanduser(video)
# Menu Bar
# File Menu
menubar = Tk.Menu(self.parent)
self.parent.config(menu=menubar)
fileMenu = _Tk_Menu(menubar)
fileMenu.bind_shortcuts_to(parent) # XXX must be root?
fileMenu.add_shortcut("Open...", 'o', self.OnOpen)
fileMenu.add_separator()
fileMenu.add_shortcut("Play", 'p', self.OnPlay) # Play/Pause
fileMenu.add_command(label="Stop", command=self.OnStop)
fileMenu.add_separator()
fileMenu.add_shortcut("Mute", 'm', self.OnMute)
fileMenu.add_separator()
fileMenu.add_shortcut("Close", 'w' if _isMacOS else 's', self.OnClose)
if _isMacOS: # intended for and tested on macOS
fileMenu.add_separator()
fileMenu.add_shortcut("Full Screen", 'f', self.OnFullScreen)
menubar.add_cascade(label="File", menu=fileMenu)
self.fileMenu = fileMenu
self.playIndex = fileMenu.index("Play")
self.muteIndex = fileMenu.index("Mute")
# first, top panel shows video
self.videopanel = ttk.Frame(self.parent)
self.canvas = Tk.Canvas(self.videopanel)
self.canvas.pack(fill=Tk.BOTH, expand=1)
self.videopanel.pack(fill=Tk.BOTH, expand=1)
# panel to hold buttons
self.buttons_panel = Tk.Toplevel(self.parent)
self.buttons_panel.title("")
self.is_buttons_panel_anchor_active = False
buttons = ttk.Frame(self.buttons_panel)
self.playButton = ttk.Button(buttons, text="Play", command=self.OnPlay)
stop = ttk.Button(buttons, text="Stop", command=self.OnStop)
self.muteButton = ttk.Button(buttons, text="Mute", command=self.OnMute)
self.playButton.pack(side=Tk.LEFT)
stop.pack(side=Tk.LEFT)
self.muteButton.pack(side=Tk.LEFT)
self.volMuted = False
self.volVar = Tk.IntVar()
self.volSlider = Tk.Scale(buttons, variable=self.volVar, command=self.OnVolume,
from_=0, to=100, orient=Tk.HORIZONTAL, length=200,
showvalue=0, label='Volume')
self.volSlider.pack(side=Tk.RIGHT)
buttons.pack(side=Tk.BOTTOM, fill=Tk.X)
# panel to hold player time slider
timers = ttk.Frame(self.buttons_panel)
self.timeVar = Tk.DoubleVar()
self.timeSliderLast = 0
self.timeSlider = Tk.Scale(timers, variable=self.timeVar, command=self.OnTime,
from_=0, to=1000, orient=Tk.HORIZONTAL, length=500,
showvalue=0) # label='Time',
self.timeSlider.pack(side=Tk.BOTTOM, fill=Tk.X, expand=1)
self.timeSliderUpdate = time.time()
timers.pack(side=Tk.BOTTOM, fill=Tk.X)
# VLC player
args = []
if _isLinux:
args.append('--no-xlib')
self.Instance = vlc.Instance(args)
self.player = self.Instance.media_player_new()
self.parent.bind("<Configure>", self.OnConfigure) # catch window resize, etc.
self.parent.update()
# After parent.update() otherwise panel is ignored.
self.buttons_panel.overrideredirect(True)
# Estetic, to keep our video panel at least as wide as our buttons panel.
self.parent.minsize(width=502, height=0)
if _isMacOS:
# Only tested on MacOS so far. Enable for other OS after verified tests.
self.is_buttons_panel_anchor_active = True
# Detect dragging of the buttons panel.
self.buttons_panel.bind("<Button-1>", lambda event: setattr(self, "has_clicked_on_buttons_panel", event.y < 0))
self.buttons_panel.bind("<B1-Motion>", self._DetectButtonsPanelDragging)
self.buttons_panel.bind("<ButtonRelease-1>", lambda _: setattr(self, "has_clicked_on_buttons_panel", False))
self.has_clicked_on_buttons_panel = False
else:
self.is_buttons_panel_anchor_active = False
self._AnchorButtonsPanel()
self.OnTick() # set the timer up
def OnClose(self, *unused):
"""Closes the window and quit.
"""
# print("_quit: bye")
self.parent.quit() # stops mainloop
self.parent.destroy() # this is necessary on Windows to avoid
# ... Fatal Python Error: PyEval_RestoreThread: NULL tstate
def _DetectButtonsPanelDragging(self, _):
"""If our last click was on the boarder
we disable the anchor.
"""
if self.has_clicked_on_buttons_panel:
self.is_buttons_panel_anchor_active = False
self.buttons_panel.unbind("<Button-1>")
self.buttons_panel.unbind("<B1-Motion>")
self.buttons_panel.unbind("<ButtonRelease-1>")
def _AnchorButtonsPanel(self):
video_height = self.parent.winfo_height()
panel_x = self.parent.winfo_x()
panel_y = self.parent.winfo_y() + video_height + 23 # 23 seems to put the panel just below our video.
panel_height = self.buttons_panel.winfo_height()
panel_width = self.parent.winfo_width()
self.buttons_panel.geometry("%sx%s+%s+%s" % (panel_width, panel_height, panel_x, panel_y))
def OnConfigure(self, *unused):
"""Some widget configuration changed.
"""
# <https://www.Tcl.Tk/man/tcl8.6/TkCmd/bind.htm#M12>
self._geometry = '' # force .OnResize in .OnTick, recursive?
if self.is_buttons_panel_anchor_active:
self._AnchorButtonsPanel()
def OnFullScreen(self, *unused):
"""Toggle full screen, macOS only.
"""
# <https://www.Tcl.Tk/man/tcl8.6/TkCmd/wm.htm#M10>
f = not self.parent.attributes("-fullscreen") # or .wm_attributes
if f:
self._previouscreen = self.parent.geometry()
self.parent.attributes("-fullscreen", f) # or .wm_attributes
self.parent.bind("<Escape>", self.OnFullScreen)
else:
self.parent.attributes("-fullscreen", f) # or .wm_attributes
self.parent.geometry(self._previouscreen)
self.parent.unbind("<Escape>")
def OnMute(self, *unused):
"""Mute/Unmute audio.
"""
# audio un/mute may be unreliable, see vlc.py docs.
self.volMuted = m = not self.volMuted # self.player.audio_get_mute()
self.player.audio_set_mute(m)
u = "Unmute" if m else "Mute"
self.fileMenu.entryconfig(self.muteIndex, label=u)
self.muteButton.config(text=u)
# update the volume slider text
self.OnVolume()
def OnOpen(self, *unused):
"""Pop up a new dialow window to choose a file, then play the selected file.
"""
# if a file is already running, then stop it.
self.OnStop()
# Create a file dialog opened in the current home directory, where
# you can display all kind of files, having as title "Choose a video".
video = askopenfilename(initialdir = Path(expanduser("~")),
title = "Choose a video",
filetypes = (("all files", "*.*"),
("mp4 files", "*.mp4"),
("mov files", "*.mov")))
self._Play(video)
def _Pause_Play(self, playing):
# re-label menu item and button, adjust callbacks
p = 'Pause' if playing else 'Play'
c = self.OnPlay if playing is None else self.OnPause
self.fileMenu.entryconfig(self.playIndex, label=p, command=c)
# self.fileMenu.bind_shortcut('p', c) # XXX handled
self.playButton.config(text=p, command=c)
self._stopped = False
def _Play(self, video):
# helper for OnOpen and OnPlay
if isfile(video): # Creation
m = self.Instance.media_new(str(video)) # Path, unicode
self.player.set_media(m)
self.parent.title("tkVLCplayer - %s" % (basename(video),))
# set the window id where to render VLC's video output
h = self.videopanel.winfo_id() # .winfo_visualid()?
if _isWindows:
self.player.set_hwnd(h)
elif _isMacOS:
# XXX 1) using the videopanel.winfo_id() handle
# causes the video to play in the entire panel on
# macOS, covering the buttons, sliders, etc.
# XXX 2) .winfo_id() to return NSView on macOS?
v = _GetNSView(h)
if v:
self.player.set_nsobject(v)
else:
self.player.set_xwindow(h) # plays audio, no video
else:
self.player.set_xwindow(h) # fails on Windows
# FIXME: this should be made cross-platform
self.OnPlay()
def OnPause(self, *unused):
"""Toggle between Pause and Play.
"""
if self.player.get_media():
self._Pause_Play(not self.player.is_playing())
self.player.pause() # toggles
def OnPlay(self, *unused):
"""Play video, if none is loaded, open the dialog window.
"""
# if there's no video to play or playing,
# open a Tk.FileDialog to select a file
if not self.player.get_media():
if self.video:
self._Play(expanduser(self.video))
self.video = ''
else:
self.OnOpen()
# Try to play, if this fails display an error message
elif self.player.play(): # == -1
self.showError("Unable to play the video.")
else:
self._Pause_Play(True)
# set volume slider to audio level
vol = self.player.audio_get_volume()
if vol > 0:
self.volVar.set(vol)
self.volSlider.set(vol)
def OnResize(self, *unused):
"""Adjust the window/frame to the video aspect ratio.
"""
g = self.parent.geometry()
if g != self._geometry and self.player:
u, v = self.player.video_get_size() # often (0, 0)
if v > 0 and u > 0:
# get window size and position
g, x, y = g.split('+')
w, h = g.split('x')
# alternatively, use .winfo_...
# w = self.parent.winfo_width()
# h = self.parent.winfo_height()
# x = self.parent.winfo_x()
# y = self.parent.winfo_y()
# use the video aspect ratio ...
if u > v: # ... for landscape
# adjust the window height
h = round(float(w) * v / u)
else: # ... for portrait
# adjust the window width
w = round(float(h) * u / v)
self.parent.geometry("%sx%s+%s+%s" % (w, h, x, y))
self._geometry = self.parent.geometry() # actual
def OnStop(self, *unused):
"""Stop the player, resets media.
"""
if self.player:
self.player.stop()
self._Pause_Play(None)
# reset the time slider
self.timeSlider.set(0)
self._stopped = True
# XXX on macOS libVLC prints these error messages:
# [h264 # 0x7f84fb061200] get_buffer() failed
# [h264 # 0x7f84fb061200] thread_get_buffer() failed
# [h264 # 0x7f84fb061200] decode_slice_header error
# [h264 # 0x7f84fb061200] no frame!
def OnTick(self):
"""Timer tick, update the time slider to the video time.
"""
if self.player:
# since the self.player.get_length may change while
# playing, re-set the timeSlider to the correct range
t = self.player.get_length() * 1e-3 # to seconds
if t > 0:
self.timeSlider.config(to=t)
t = self.player.get_time() * 1e-3 # to seconds
# don't change slider while user is messing with it
if t > 0 and time.time() > (self.timeSliderUpdate + 2):
self.timeSlider.set(t)
self.timeSliderLast = int(self.timeVar.get())
# start the 1 second timer again
self.parent.after(1000, self.OnTick)
# adjust window to video aspect ratio, done periodically
# on purpose since the player.video_get_size() only
# returns non-zero sizes after playing for a while
if not self._geometry:
self.OnResize()
def OnTime(self, *unused):
if self.player:
t = self.timeVar.get()
if self.timeSliderLast != int(t):
# this is a hack. The timer updates the time slider.
# This change causes this rtn (the 'slider has changed' rtn)
# to be invoked. I can't tell the difference between when
# the user has manually moved the slider and when the timer
# changed the slider. But when the user moves the slider
# tkinter only notifies this rtn about once per second and
# when the slider has quit moving.
# Also, the tkinter notification value has no fractional
# seconds. The timer update rtn saves off the last update
# value (rounded to integer seconds) in timeSliderLast if
# the notification time (sval) is the same as the last saved
# time timeSliderLast then we know that this notification is
# due to the timer changing the slider. Otherwise the
# notification is due to the user changing the slider. If
# the user is changing the slider then I have the timer
# routine wait for at least 2 seconds before it starts
# updating the slider again (so the timer doesn't start
# fighting with the user).
self.player.set_time(int(t * 1e3)) # milliseconds
self.timeSliderUpdate = time.time()
def OnVolume(self, *unused):
"""Volume slider changed, adjust the audio volume.
"""
vol = min(self.volVar.get(), 100)
v_M = "%d%s" % (vol, " (Muted)" if self.volMuted else '')
self.volSlider.config(label="Volume " + v_M)
if self.player and not self._stopped:
# .audio_set_volume returns 0 if success, -1 otherwise,
# e.g. if the player is stopped or doesn't have media
if self.player.audio_set_volume(vol): # and self.player.get_media():
self.showError("Failed to set the volume: %s." % (v_M,))
def showError(self, message):
"""Display a simple error dialog.
"""
self.OnStop()
showerror(self.parent.title(), message)
if __name__ == "__main__":
_video = ''
while len(sys.argv) > 1:
arg = sys.argv.pop(1)
if arg.lower() in ('-v', '--version'):
# show all versions, sample output on macOS:
# % python3 ./tkvlc.py -v
# tkvlc.py: 2019.07.28 (tkinter 8.6 /Library/Frameworks/Python.framework/Versions/3.7/lib/libtk8.6.dylib)
# vlc.py: 3.0.6109 (Sun Mar 31 20:14:16 2019 3.0.6)
# LibVLC version: 3.0.6 Vetinari (0x3000600)
# LibVLC compiler: clang: warning: argument unused during compilation: '-mmacosx-version-min=10.7' [-Wunused-command-line-argument]
# Plugin path: /Applications/VLC3.0.6.app/Contents/MacOS/plugins
# Python: 3.7.4 (64bit) macOS 10.13.6
# Print version of this vlc.py and of the libvlc
print('%s: %s (%s %s %s)' % (basename(__file__), __version__,
Tk.__name__, Tk.TkVersion, libtk))
try:
vlc.print_version()
vlc.print_python()
except AttributeError:
pass
sys.exit(0)
elif arg.startswith('-'):
print('usage: %s [-v | --version] [<video_file_name>]' % (sys.argv[0],))
sys.exit(1)
elif arg: # video file
_video = expanduser(arg)
if not isfile(_video):
print('%s error: no such file: %r' % (sys.argv[0], arg))
sys.exit(1)
# Create a Tk.App() to handle the windowing event loop
root = Tk.Tk()
player = Player(root, video=_video)
root.protocol("WM_DELETE_WINDOW", player.OnClose) # XXX unnecessary (on macOS)
root.mainloop()

How to get rid of the white vertical line in a python gtk 3.0 window

I'm about to create a desktop based widget application using python via the gi.repository and the Gtk/Gdk v3.0. The goal is to have the window taking the whole screen being transparent, frameless and not accepting any focus. In addition it should be shown in every workspace and kept bellow any other window. After spending a long time I figured it out with the following code, yet with a drawback, the window has a vertical white line to the right side (kind of a border). So is there any solution to get rid of this line?
Os: Ubuntu 18.04
Python: 3.6
Screenshot
ui.py
import screeninfo
import gi
try:
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0")
gi.require_foreign("cairo")
except ImportError:
print("No Gtk v3.0 or pycairo integration")
from gi.repository import Gtk, Gdk, GLib
class Window (Gtk.Window):
def __init__ (self, canvas):
Gtk.Window.__init__(self)
self.set_skip_pager_hint(True)
self.set_skip_taskbar_hint(True)
self.set_type_hint(Gdk.WindowTypeHint.DESKTOP)
self.set_decorated(False)
self.set_keep_below(True)
self.set_accept_focus(False)
self.set_can_focus(False)
width = 500
height = 500
for monitor in screeninfo.get_monitors():
if monitor.is_primary:
width = monitor.width
height = monitor.height
# Todo: remove the annoying white border line on the right
self.set_size_request(width, height)
#Set transparency
screen = self.get_screen()
rgba = screen.get_rgba_visual()
self.set_visual(rgba)
self.override_background_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(0, 0, 0, 0))
drawingarea = Gtk.DrawingArea()
self.add(drawingarea)
drawingarea.connect('draw', canvas.draw)
self.connect('destroy', Gtk.main_quit)
self.show_all()
self.move(0, 0)
def launch (self):
GLib.timeout_add_seconds(1, self.refresh)
Gtk.main()
return True
def refresh (self):
self.queue_draw()
return True
canvas.py
import cairo
FONT = 'UbuntuCondensent'
SLANT = cairo.FONT_SLANT_NORMAL
BOLD = cairo.FONT_WEIGHT_NORMAL
def draw (da, ctx):
ctx.set_source_rgb(1, 1, 1)
ctx.select_font_face(FONT, SLANT, BOLD)
ctx.set_font_size(32)
ctx.move_to(100, 100)
ctx.text_path('Hi There!')
ctx.fill()
main.py
import signal
import ui
import canvas
def main ():
signal.signal(signal.SIGINT, signal.SIG_DFL)
win = ui.Window(canvas)
win.launch()
if __name__ == '__main__':
main()

Making GStreamer video/audio in Python smooth and loop

I'm trying to use GStreamer to loop MPEG-4 files smoothly. I want to play the video and audio if there is any and loop the playback. My app uses GTK for UI.
Right now I have three problems:
The video playback is choppy/jerky on the Raspberry Pi 4, I'm running it on. By choppy/jerky, I mean that every ~1-2 seconds, playback freezes for some fraction of a second. When playing the same video in the VLC app, it is smooth.
Audio is not played. Again, when played in VLC, the audio is there as expected. It was my understanding that playbin elements automatically play both audio and video.
When the end of the video is reached, the last frame is frozen for 1-2 seconds before the video starts playing from the first frame again.
I currently have the following code.
video_player.py:
#!/usr/bin/python3
import os
import gi
gi.require_version("Gst", "1.0")
gi.require_version("Gtk", "3.0")
gi.require_version("GstVideo", "1.0")
from gi.repository import Gst, Gtk, GstVideo
class VideoPlayer(Gtk.DrawingArea):
def __init__(self, video_uri: str, loop: bool):
super().__init__()
self.__loop = loop
self.__video_uri = "file:///" + os.path.abspath(video_uri)
self.__xid = None
Gst.init(None)
self.connect("realize", self.__on_realize)
self.set_size_request(1920, 1080) # Hardcoded for this example
self.__playbin = Gst.ElementFactory.make("playbin", "player")
self.__bus = self.__playbin.get_bus()
self.__bus.add_signal_watch()
self.__bus.connect("message::eos", self.__on_video_end)
self.__bus.enable_sync_message_emission()
self.__bus.connect("sync-message::element", self.__on_sync_message)
self.__playbin.set_property("uri", self.__video_uri)
def __on_realize(self, widget: Gtk.Window, data=None) -> None:
window = widget.get_window()
self.__xid = window.get_xid()
def __on_sync_message(self, bus: Gst.Bus, message: Gst.Message) -> None:
if message.get_structure().get_name() == "prepare-window-handle":
image_sink = message.src
image_sink.set_property("force-aspect-ratio", True)
image_sink.set_window_handle(self.__xid)
def __on_video_end(self, bus: Gst.Bus, message: Gst.Message) -> None:
if self.__loop:
self.__playbin.set_state(Gst.State.NULL)
self.__playbin.set_state(Gst.State.PLAYING)
def play(self) -> None:
if self.__playbin.get_state(0).state != Gst.State.PLAYING:
self.__playbin.set_state(Gst.State.PLAYING)
main.py:
#!/usr/bin/python3
from video_player import VideoPlayer
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
window = Gtk.Window()
video_player = VideoPlayer("test_video.mp4", loop=True)
window.add(video_player)
window.fullscreen()
window.show_all()
video_player.play()
Gtk.main()
This answer provides an example that uses VLC; This was accepted by the author of the question (see comments) - GStreamer on Raspberry Pi 4 and other similar SOCs is often times laggy and a soft solution, without starting to modify the Gstreamer library, is probably not going to help the OP.
Please notice that the code has been inspired by https://www.codementor.io/#princerapa/python-media-player-vlc-gtk-favehuy2b but has been modified to accommodate for your needs.
The required change to make the video loop, which is not provided in the aforementioned link is passing the argument '--input-repeat=-1' to the vlcinstance.
Install dependencies (this assumes you already have gtk installed)
pip install python-vlc
Your code:
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
gi.require_version('GdkX11', '3.0')
from gi.repository import GdkX11
import vlc
MRL = "" # File to play
WIDTH = 300
HEIGHT = 300
class ApplicationWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Python-Vlc Media Player")
self.player_paused=False
self.is_player_active = False
self.connect("destroy",Gtk.main_quit)
def show(self):
self.show_all()
def setup_objects_and_events(self):
self.playback_button = Gtk.Button()
self.stop_button = Gtk.Button()
self.play_image = Gtk.Image.new_from_icon_name(
"gtk-media-play",
Gtk.IconSize.MENU
)
self.pause_image = Gtk.Image.new_from_icon_name(
"gtk-media-pause",
Gtk.IconSize.MENU
)
self.stop_image = Gtk.Image.new_from_icon_name(
"gtk-media-stop",
Gtk.IconSize.MENU
)
self.playback_button.set_image(self.play_image)
self.stop_button.set_image(self.stop_image)
self.playback_button.connect("clicked", self.toggle_player_playback)
self.stop_button.connect("clicked", self.stop_player)
self.draw_area = Gtk.DrawingArea()
self.draw_area.set_size_request(WIDTH,HEIGHT)
self.draw_area.connect("realize",self._realized)
self.hbox = Gtk.Box(spacing=6)
self.hbox.pack_start(self.playback_button, True, True, 0)
self.hbox.pack_start(self.stop_button, True, True, 0)
self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.add(self.vbox)
self.vbox.pack_start(self.draw_area, True, True, 0)
self.vbox.pack_start(self.hbox, False, False, 0)
def stop_player(self, widget, data=None):
self.player.stop()
self.is_player_active = False
self.playback_button.set_image(self.play_image)
def toggle_player_playback(self, widget, data=None):
"""
Handler for Player's Playback Button (Play/Pause).
"""
if self.is_player_active == False and self.player_paused == False:
self.player.play()
self.playback_button.set_image(self.pause_image)
self.is_player_active = True
elif self.is_player_active == True and self.player_paused == True:
self.player.play()
self.playback_button.set_image(self.pause_image)
self.player_paused = False
elif self.is_player_active == True and self.player_paused == False:
self.player.pause()
self.playback_button.set_image(self.play_image)
self.player_paused = True
else:
pass
def _realized(self, widget, data=None):
self.vlcInstance = vlc.Instance("--no-xlib", "--input-repeat=-1")
self.player = self.vlcInstance.media_player_new()
win_id = widget.get_window().get_xid()
self.player.set_xwindow(win_id)
self.player.set_mrl(MRL)
self.player.play()
self.playback_button.set_image(self.pause_image)
self.is_player_active = True
if __name__ == '__main__':
if not sys.argv[1:]:
print("Exiting \nMust provide the MRL.")
sys.exit(1)
if len(sys.argv[1:]) == 1:
MRL = sys.argv[1]
window = ApplicationWindow()
window.setup_objects_and_events()
window.show()
Gtk.main()
window.player.stop()
window.vlcInstance.release()

How to make a window fullscreen in a secondary display with tkinter?

I know how to make a window fullscreen in the "main" display, but even when moving my app's window to a secondary display connected to my PC, when I call:
self.master.attributes('-fullscreen', True)
to fullscreen that window, it does so in the "main" display and not in the secondary one (the app's window disappears from the secondary display and instantly appears in the "main" one, in fullscreen).
How can I make it fullscreen in the secondary display?
This works on Windows 7: If the second screen width and height are the same as the first one, you can use win1 or win2 geometry of the following code depending its relative position(leftof or rightof) to have a fullscreen in a secondary display:
from Tkinter import *
def create_win():
def close(): win1.destroy();win2.destroy()
win1 = Toplevel()
win1.geometry('%dx%d%+d+%d'%(sw,sh,-sw,0))
Button(win1,text="Exit1",command=close).pack()
win2 = Toplevel()
win2.geometry('%dx%d%+d+%d'%(sw,sh,sw,0))
Button(win2,text="Exit2",command=close).pack()
root=Tk()
sw,sh = root.winfo_screenwidth(),root.winfo_screenheight()
print "screen1:",sw,sh
w,h = 800,600
a,b = (sw-w)/2,(sh-h)/2
Button(root,text="Exit",command=lambda r=root:r.destroy()).pack()
Button(root,text="Create win2",command=create_win).pack()
root.geometry('%sx%s+%s+%s'%(w,h,a,b))
root.mainloop()
Try:
from Tkinter import *
rot = Tk()
wth,hgh = rot.winfo_screenwidth(),rot.winfo_screenheight()
#take desktop width and hight (pixel)
_w,_h = 800,600 #root width and hight
a,b = (wth-_w)/2,(hgh-_h)/2 #Put root to center of display(Margin_left,Margin_top)
def spann():
def _exit():
da.destroy()
da = Toplevel()
da.geometry('%dx%d+%d+%d' % (wth, hgh,0, 0))
Button(da,text="Exit",command=_exit).pack()
da.overrideredirect(1)
da.focus_set()#Restricted access main menu
Button(rot,text="Exit",command=lambda rot=rot : rot.destroy()).pack()
but = Button(rot,text="Show SUB",command=spann)
but.pack()
rot.geometry('%sx%s+%s+%s'%(_w,_h,a,b))
rot.mainloop()
""" Geometry pattern 'WxH+a+b'
W = Width
H = Height
a = Margin_left+Margin_Top"""
Super simple method working in 2021
This works even if both displays are different resolutions. Use geometry to offset the second display by the width of the first display. The format of the geometry string is <width>x<height>+xoffset+yoffset:
root = tkinter.Tk()
# specify resolutions of both windows
w0, h0 = 3840, 2160
w1, h1 = 1920, 1080
# set up a window for first display, if wanted
win0 = tkinter.Toplevel()
win0.geometry(f"{w0}x{h0}+0+0")
# set up window for second display with fullscreen
win1 = tkinter.Toplevel()
win1.geometry(f"{w1}x{h1}+{w0}+0") # <- this is the key, offset to the right by w0
win1.attributes("-fullscreen", True)
As long as you know the width of the first display, this will work fine. The X system TK runs on puts the second monitor to the right of the first one by default.
Windows, Python 3.8
In this solution, pressing F11 will make the window fullscreen on the current screen.
Note that self.root.state("zoomed") is Windows specific according to doc.
self.root.overrideredirect(True) is weird in Windows and may have unwanted side effects. For instance I've had issues related to changing screen configuration with this option active.
#!/usr/bin/env python3
import tkinter as tk
class Gui:
fullScreen = False
def __init__(self):
self.root = tk.Tk()
self.root.bind("<F11>", self.toggleFullScreen)
self.root.bind("<Alt-Return>", self.toggleFullScreen)
self.root.bind("<Control-w>", self.quit)
self.root.mainloop()
def toggleFullScreen(self, event):
if self.fullScreen:
self.deactivateFullscreen()
else:
self.activateFullscreen()
def activateFullscreen(self):
self.fullScreen = True
# Store geometry for reset
self.geometry = self.root.geometry()
# Hides borders and make truly fullscreen
self.root.overrideredirect(True)
# Maximize window (Windows only). Optionally set screen geometry if you have it
self.root.state("zoomed")
def deactivateFullscreen(self):
self.fullScreen = False
self.root.state("normal")
self.root.geometry(self.geometry)
self.root.overrideredirect(False)
def quit(self, event=None):
print("quiting...", event)
self.root.quit()
if __name__ == '__main__':
Gui()

How can I get the screen size in Tkinter?

I would like to know if it is possible to calculate the screen size using Tkinter.
I wanted this so that can make the program open up in the center of the screen...
import tkinter as tk
root = tk.Tk()
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
A possible solution
import os
os.system("xrandr | grep \* | cut -d' ' -f4")
My output:
1440x900
0
For Windows:
You can make the process aware of DPI to handle scaled displays.
import ctypes
try: # Windows 8.1 and later
ctypes.windll.shcore.SetProcessDpiAwareness(2)
except Exception as e:
pass
try: # Before Windows 8.1
ctypes.windll.user32.SetProcessDPIAware()
except: # Windows 8 or before
pass
Expanding on mouad's answer, this function is capable of handling multi-displays and returns the resolution of the current screen:
import tkinter
def get_display_size():
root = tkinter.Tk()
root.update_idletasks()
root.attributes('-fullscreen', True)
root.state('iconic')
height = root.winfo_screenheight()
width = root.winfo_screenwidth()
root.destroy()
return height, width

Categories