Resolution of a maximized window in Python - python

Is there a built-in function or straight-forward way to get the resolution of a maximized window in Python (e.g. on Windows full screen without the task bar)?
I have tried several things from other posts, which present some major drawbacks:
ctypes
import ctypes
user32 = ctypes.windll.user32
screensize = user32.GetSystemMetrics(0), user32.GetSystemMetrics(1)
Simple, but I get the resolution of the full screen.
tkinter
import tkinter as tk
root = tk.Tk() # Create an instance of the class.
root.state('zoomed') # Maximized the window.
root.update_idletasks() # Update the display.
screensize = [root.winfo_width(), root.winfo_height()]
root.mainloop()
Works, but it isn't really straight-forward and above all, I don't know how to exit the loop with root.destroy() or root.quit() successfully. Closing the window manually is of course not an option.
matplotlib
import matplotlib.pyplot as plt
plt.figure(1)
plt.switch_backend('QT5Agg')
figManager = plt.get_current_fig_manager()
figManager.window.showMaximized()
print(plt.gcf().get_size_inches())
[6.4 4.8] is then printed, but if I click on the created window, and execute print(plt.gcf().get_size_inches()) again, I get [19.2 10.69] printed, which I find higly inconsistent! (As you can imagine, having to interact to get that final value is definitely not an option.)

According to [MS.Docs]: GetSystemMetrics function (emphasis is mine):
SM_CXFULLSCREEN
16
The width of the client area for a full-screen window on the primary display monitor, in pixels. To get the coordinates of the portion of the screen that is not obscured by the system taskbar or by application desktop toolbars, call the SystemParametersInfo function with the SPI_GETWORKAREA value.
Same thing for SM_CYFULLSCREEN.
Example:
>>> import ctypes as ct
>>>
>>>
>>> SM_CXSCREEN = 0
>>> SM_CYSCREEN = 1
>>> SM_CXFULLSCREEN = 16
>>> SM_CYFULLSCREEN = 17
>>>
>>> user32 = ct.windll.user32
>>> GetSystemMetrics = user32.GetSystemMetrics
>>>
>>> # #TODO: Never forget about the 2 lines below !!!
>>> GetSystemMetrics.argtypes = [ct.c_int]
>>> GetSystemMetrics.restype = ct.c_int
>>>
>>> GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN) # Entire (primary) screen
(1920, 1080)
>>> GetSystemMetrics(SM_CXFULLSCREEN), GetSystemMetrics(SM_CYFULLSCREEN) # Full screen window
(1920, 1017)
Regarding the #TODO in the code: check [SO]: C function called from Python via ctypes returns incorrect value (#CristiFati's answer).

If you don't want the window to persist simply remove the mainloop method from the tkinter code.
import tkinter as tk
root = tk.Tk() # Create an instance of the class.
root.state('zoomed') # Maximized the window.
root.update_idletasks() # Update the display.
screensize = [root.winfo_width(), root.winfo_height()]
I also found this that might be helpful and more what you are looking for; I am using Linux, so I am unable to test it.
from win32api import GetSystemMetrics
print("Width =", GetSystemMetrics(0))
print("Height =", GetSystemMetrics(1))
How do I get monitor resolution in Python?

Related

How to disable Windows key using tkinter?

I am coding a sign in and I would like there to be no way out unless signin is complete. But in order to do that I need to disable the Windows key so they cannot leave the window. I is has no header, I disabled the WM delete window protocol, I have automatic full screen. I also need to make it dynamically set itself to the middle but I am not that far. Messagebox import works.
My Code:
from tkinter import *
import messagebox
from win32api import GetSystemMetrics
def getfullscreensize():
global width
width = GetSystemMetrics(0)
global height
height = GetSystemMetrics(1)
def donothing():
pass
root = Tk()
root.attributes('-fullscreen', True)
root.protocol("WM_DELETE_WINDOW", donothing)
root.overrideredirect(1)
root.bind("<key>", lambda e: "break")
root.mainloop()
How to disable Windows key using tkinter?
In short, you can't. Tkinter does not have any features that allow it to disable OS-level features like the windows key.
In order to disable this key you'll have to find some other platform-specific solution.

How do I write text to the root window using Python's Xlib?

I'm running a Debian 10 stable x64 system with the dwm window manager, and I'm using Python 3.7.3. From what I can tell from some example code and the draw_text method itself, I should be able to draw text on the root window with code like this:
#!/usr/bin/env python3
import Xlib
import Xlib.display
display = Xlib.display.Display()
screen = display.screen()
root = screen.root
gc = root.create_gc(foreground = screen.white_pixel, background = screen.black_pixel)
root.draw_text(gc, 1, 1, b"Hello, world!")
This code runs without error, but no text is displayed. I've also experimented with different coordinates without any luck. My root window background is just the default black, so I don't think the text is failing to show up, since I set the foreground pixel color to white.
You are right that your code should draw text on root window. You just need to:
Ensure that your background is indeed the root window (xwininfo is great)
Check the coordinates again: (i) if dwm, as by default, shows a topbar, it may hide the text. Or just [Alt]+b, to toggle the bar (ii) if you have other windows, for example your terminal, on top, you will not see the text.
Perform an XFlush in the end. Without it the request stays in the client.
The code that works here(Gentoo amd64/desktop/stable, dwm-6.2, python-3.6.9):
#!/usr/bin/env python3
import Xlib
import Xlib.display
display = Xlib.display.Display()
screen = display.screen()
root = screen.root
gc = root.create_gc(foreground = screen.white_pixel, background = screen.black_pixel)
root.draw_text(gc, 100, 100, b"Hello, world!") # changed the coords more towards the center
display.flush() # To actually send the request to the server
Notice that the text will disappear, if other windows overlap or refresh that spot. The text remains until, for example, you move a window over (erases it), or you change to another dwm-Tab that has a window covering these coordinates.
If you want to prevent the text from disappearing, you need a loop:
either a while True loop on the code as is, which is going to redraw it no matter what
or, better, an event loop, which is going to redraw it only when it is necessary (see below)
The expose events (refer https://tronche.com/gui/x/xlib/events/exposure/expose.html and http://python-xlib.sourceforge.net/doc/html/python-xlib_13.html#SEC12)
are generated when regions of a window has to be redrawn
BUT, if we listen for the expose event for root window, we get none (reason: (see the setup function in the dwm's source code) no ExposureMask for root). What i tried and worked:
#!/usr/bin/env python3
import Xlib
from Xlib import display, X # X is also needed
display = Xlib.display.Display()
screen = display.screen()
root = screen.root
#print(root.get_attributes())
root.change_attributes(event_mask=X.ExposureMask) # "adds" this event mask
#print(root.get_attributes()) # see the difference
gc = root.create_gc(foreground = screen.white_pixel, background = screen.black_pixel)
def draw_it():
root.draw_text(gc, 100, 100, b"Hello, world!")
display.flush()
draw_it()
while 1:
if display.pending_events() != 0: # check to safely apply next_event
event = display.next_event()
if event.type == X.Expose and event.count == 0:
draw_it()

How to get the height of a tkinter window title bar

I'm trying to figure out how to get the height of a tkInter window title bar but can't seem to find any info on how it's done.
I have tried using root.geometry() and it seems that root.geometry() only returns the size of the window content and not the total size of the window with title bar and border sizes. I have seen other people say that you need to ask the OS for those things. I was hoping to avoid this because it will make it harder to make the code platform independent. There must be a way to do this without going to the OS for this. Anyone know what it is I must do to get this info?
My system:
OS: Linux
KDE Plasma: 5.16.4
KDE Frameworks: 5.61.0
import tkinter
root = tkinter.Tk()
root.geometry("250x250+100+100")
root.update_idletasks()
print('root.winfo_x() = ', root.winfo_x())
print('root.winfo_y() = ', root.winfo_y())
print('root.geometry() = ', root.geometry())
root.mainloop()
Test code results:
root.winfo_x() = 100
root.winfo_y() = 100
root.geometry() = 250x250+100+100
The height of the window when measured with a screen ruler app is:
x=102, y=286
Title bar(default) is a system setting.As far as I know,it depends on many factors.(System zoom ratio,different OS,DPI awareness and so on).
In windows,change the zoom ratio will get different value of height.
About the question:
tkinter will be recognized a old software in windows,you need to set DPI awareness to make it have the system normal height(fit the system zoom ratio if your system zoom ratio is not 100%).
Normally,the height of system title bar are same:but some exception(I am not really know about winapi),different DPI awareness will show you the different height of title bar:
The same:
To make them same and get the normal height of title bar:
import tkinter
import ctypes
ctypes.windll.shcore.SetProcessDpiAwareness(2)
print(ctypes.windll.user32.GetSystemMetrics(4))
root = tkinter.Tk()
root.mainloop()
Result:
Refer:
MSDN doc:GetSystemMetrics,DPI awareness(value of DPI awareness).
I have figured this out finally. In order to get the height of the title bar I first added a frame to the window and set the window geometry('1x1'). I also had to use update_idletasks() to update tkinter internal data. After doing this I got the correct height. Seems strange to have to do it this way but it worked. Below is the code I used to get the height. Tested and works in Windows and Linux. If anyone knows of a more correct way to do this I would sure like to know. Also if anyone is using apple please let me know if the code below works.
UPDATE: I have updated the code and it's been tested in (Linux, Windows and MacOS) to give the correct title bar height. I think this is the best way to do this because you don't need to make OS dependent system calls.But I don't know whether it could work for any zoom level.
Update:now could work for any zoom level.(Test passed in windows 10,windows 7)
import tkinter as tk
from sys import platform
class App(tk.Tk):
def __init__(self):
super().__init__()
tk.Frame(self).update_idletasks()
self.geometry('350x200+100+100')
self.update_idletasks()
offset_y = 0
if platform in ('win32', 'darwin'):
import ctypes
try: # >= win 8.1
ctypes.windll.shcore.SetProcessDpiAwareness(2)
except: # win 8.0 or less
ctypes.windll.user32.SetProcessDPIAware()
offset_y = int(self.geometry().rsplit('+', 1)[-1])
bar_height = self.winfo_rooty() - offset_y
print(f'Height: {bar_height}\nPlatform: {platform}')
# self.destroy()
def main():
app = App()
app.mainloop()
if __name__ == '__main__':
main()
Simpler way is just testing how self.winfo_y() changes after setting geometry first time and then correct for this change.

How to get transparent background in window with PyGTK and PyCairo?

I've been trying really hard to create a window with no decoration and a transparent background using PyGTK. I would then draw the content of the window with Cairo. But I can't get it to work.
I've tried a lot of different ways, they all failed, this is one of them
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk, sys, cairo
win = None
def expose (widget, event):
cr = widget.window.cairo_create()
#Start drawing
cr.set_operator(cairo.OPERATOR_CLEAR)
cr.set_source_rgba(0.5,1.0,0.0,0.5)
cr.rectangle(0, 0, 0.9, 0.8)
cr.fill()
def main (argc):
global win
win = gtk.Window()
win.set_decorated(False)
win.connect('delete_event', gtk.main_quit)
win.connect('expose-event', expose)
win.set_app_paintable(True)
win.show()
gtk.main()
if __name__ == '__main__':
sys.exit(main(sys.argv))
So, what is the simplest way to do this?
So, I actually figured this out myself.
This is a working example. I've commented the relevant parts just in case somebody else is interested in how to do this.
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk, sys, cairo
from math import pi
def expose (widget, event):
cr = widget.window.cairo_create()
# Sets the operator to clear which deletes everything below where an object is drawn
cr.set_operator(cairo.OPERATOR_CLEAR)
# Makes the mask fill the entire window
cr.rectangle(0.0, 0.0, *widget.get_size())
# Deletes everything in the window (since the compositing operator is clear and mask fills the entire window
cr.fill()
# Set the compositing operator back to the default
cr.set_operator(cairo.OPERATOR_OVER)
# Draw a fancy little circle for demonstration purpose
cr.set_source_rgba(0.5,1.0,0.0,1)
cr.arc(widget.get_size()[0]/2,widget.get_size()[1]/2,
widget.get_size()[0]/2,0,pi*2)
cr.fill()
def main (argc):
win = gtk.Window()
win.set_decorated(False)
# Makes the window paintable, so we can draw directly on it
win.set_app_paintable(True)
win.set_size_request(100, 100)
# This sets the windows colormap, so it supports transparency.
# This will only work if the wm support alpha channel
screen = win.get_screen()
rgba = screen.get_rgba_colormap()
win.set_colormap(rgba)
win.connect('expose-event', expose)
win.show()
The exact problem has been addressed in a forum . But it is in C++ . Try to understand that .
Follow this :
Linux Questions
See the comment posted by phorgan1 .
Hope this helps....

Using TCL extensions to set native window style in Tkinter

pythonware.com/library/tkinter/introduction/…
documents a overrideredirect method
that will remove thetitlebar and
borders, if that is not enough you
must set the native window style, I'm
not sure if Tkinter gives you that
kind of low-level access, if not, try
the something like
twapi.magicsplat.com/ui.html#set_window_style
TCL extension
In an earlier post I got this as a reply on how to get a border in Tkinter similar to the one pictured below. I am not familiar with Tcl and it's extensions. So how would go about doing this? The end goal is basicaly to get the border below on a Tkinter window.
Edit :
I used the following on Windows 7 and it didn't seem to change the style. It's probably missing something. Any help would be appreciated, this could be really cool!
import string, win32ui, win32con
import Tkinter as tk
root = tk.Tk()
frame = win32ui.CreateWindowFromHandle(string.atoi(root.wm_frame(), 0))
frame.ModifyStyle(win32con.WS_CAPTION, 0, win32con.SWP_FRAMECHANGED)
root.mainloop()
You can do this using a combination of the Python win32 api packages and Tkinter. What you need to know is that a Tk window is the client section of a Win32 window. The window manager interactions are handled using a wrapper that is the parent of Tk window itself. If you have a Tkinter window 'w' then you can create a PyWin32 window for the frame or just manipulate it directly. You can get the frame hwnd using w.wm_frame() and parsing the hex string returned or by using GetParent on the winfo_id value from the Tk window (although wm_frame is likely to be more reliable).
import string, win32ui, win32con
from Tkinter import *
w = Tk()
frame = win32ui.CreateWindowFromHandle(string.atoi(w.wm_frame(), 0))
frame.ModifyStyle(win32con.WS_CAPTION, 0, win32con.SWP_FRAMECHANGED)
This removes the WS_CAPTION style and notifies the window that its frame is modified which forces a geometry recalculation so that the change propagates to the Tk child window.
EDIT ---
The following arranges to ensure we modify the window style after the window has been fully created and mapped to the display.
import string, win32ui, win32con
from Tkinter import *
def decaption(event):
w = event.widget
frame = win32ui.CreateWindowFromHandle(string.atoi(w.wm_frame(), 0))
frame.ModifyStyle(win32con.WS_CAPTION, 0, win32con.SWP_FRAMECHANGED)
w.bind("<Map>", None)
root = Tk()
root.bind("<Map>", decaption)
root.mainloop()
One solution is to draw your own border. Use overrideredirect to remove all decorations, grid/pack/place a canvas that fills the window, then draw or use bitmaps to get the visual effect you want. You'll have to add your own bindings for moving and resizing tne window, but that's not too difficult.

Categories

Resources