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