python - “监视器”而不是“屏幕”或“桌面”上的 Python 居中窗口
问题描述
关于如何在屏幕上居中显示 python tkinter 窗口有很多问题,答案很有效。我的问题是我所谓的“屏幕”是这样的:
尽管您可以将窗口部分(或全部)移动到灰色区域,但它们实际上不会出现在我的三台显示器上。左上显示器为 1920x1080,右上显示器为 3840x2160,右下显示器为 1920x1080。
可以通过桌面图标启动程序,该图标可以在任何监视器上,也可以通过可以在任何监视器上的图标启动gnome-terminal
。如何发现:
- 调用 python 时哪个监视器处于活动状态?
- 屏幕空间内活动监视器的坐标?
虽然我使用的是 Gnome Desktop,但我希望支持使用 X11 或 Wayland 的所有 Linux 风格。此外,我最近试用了 ChromeOS Linux Beta,对它的支持也很好。此外,非常需要对 Windows 和 OSX 的支持。
我已经安装并使用了许多工具gi
, wnck
, xdotool
,wmctrl
让我陷入了困境。我希望他们是一个流行的 python 库(最好是通过apt-get
而不是pip
or安装pip3
),可以将“屏幕”、“桌面”和“监视器”暴露给 python。
解决方案
我回答了我自己的问题。这是阻止您在周六晚上午夜入睡的答案之一,因此您在周日凌晨 1:00 起床并编码到凌晨 4:30。
这是您可以适应非 Ubuntu 环境的代码(使用“未来代码”功能):
#!/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
推荐阅读
- swagger - 模型中 List 的 Swagger 2.0 默认 null
- excel - 将数据逐行写入 Excel
- leaflet - Leaflet-React 地图渲染图块未到位和空图块
- javascript - 如何让我的议程表单数据在按钮单击时保存在本地?
- xpath - xpath 在 Scrapy 的解析中没有正确选择 HTML
- python - Jinja2“as”标签不起作用?
- css - @font-face 使用 src 时未通过 https 加载:url(data:font)
- node.js - Mongoose 仅更新列表中的第一个元素
- django - 使用 django 2.0 自定义错误页面
- java - 地图片段在从其他活动返回时重新创建