PyGTK moving two windows at once - python

I am using Python 2.7 with PyGTK and GTK of the according versions. (>>> import gtk >>> gtk.pygtk_version (2, 24, 0) >>> gtk.gtk_version (2, 24, 8)) I am writing an application where there is a main window and optionally (according to the state of a toggle button) also a settings window next to it.
I am trying to move the two windows at once (make the settings window STICK to the main window, move it with the main window). It works by default on my friends MacBook (no effort on my part), but not on my Windows 7 machine. I found a workaround that makes the settings window jump to the main one AFTER the move of the main window is finished - that is however not what I am aiming for.
Edit: FYI, the "settings_window" has the parent "main_window" which is (i guess?) doing the right job for Mac OS.
Any ideas will be greatly appreciated.
Thx, Erthy

this example works (on Ubuntu):
#!/usr/bin/env python
#coding:utf8
"""
This PyGtk example shows two windows, the master and his dog.
After master window moves or changes size, the dog window moves to always stay at its right border.
This example should also account for variable thickness of the window border.
Public domain, Filip Dominec, 2012
"""
import sys, gtk
class Main:
def __init__(self):
self.window1 = gtk.Window(); self.window1.set_title("Master")
self.window2 = gtk.Window(); self.window2.set_title("Dog")
self.window1.connect('configure_event', self.on_window1_configure_event) # move master -> move dog
self.window1.connect('destroy', lambda w: gtk.main_quit()) # close master -> end program
self.window1.show_all()
self.window2.show_all()
def on_window1_configure_event(self, *args):
print "Window 1 moved!"
x, y = self.window1.get_position()
sx, sy = self.window1.get_size()
tx = self.window1.get_style().xthickness
self.window2.move(x+sx+2*tx,y)
MainInstance = Main()
gtk.main()

Related

How to make my Program Display Always on top

Program Info & Problem
I have created a Python Program Using Pygame Module which displays the Ads on the monitor.
It shows The Ad on screen But as soon as I launch different applications like kodi or vlc or chrome, etc. It goes behind those applications.
The Problem is: The program runs but behind those applications if these applications are launched after my Ad Program.
Ideal Working
Program Laucnhed
Ad Displayed on screen
Launched Other Application
The Program still displayes the ad on top of screen.
System Info
OS: Linux - Ubuntu 20
Language: Python
Module: Pygame, Pymovie, GTK3+
Architecture: amd64
Desktop Enviroment: OpenBOX
Code Launch: CLI using a bash script which launches the python program of advertisment.
Sample Screenshot of Advertiesment
Please Help!
Thank you.
Looks like the best answer I can find is from this outdated website (https://www.mail-archive.com/pygtk#daa.com.au/msg01370.html)
they say to use code below, it should work on all OSs...things are never that easy
transient.set_transient_for(main_window)
Alternatively I have four other answers lol
Taken from (How to keep a python window on top of all others (python 3.1))
stackoverflow user pyfunc says for windows you can just do
import win32gui
import win32con
win32gui.SetWindowPos(hWnd, win32con.HWND_TOPMOST, 0,0,0,0,
win32con.SWP_NOMOVE | win32con.SWP_NOSIZE)
Since this only works for windows I think you should try python-tinker, I believe it works on linux
root = Tk()
root.wm_attributes("-topmost", 1)
Also whatnick says you can use PyGTK
gtk.Window.set_keep_above
reference: How to make python window run as "Always On Top"?
Let me know if any of these work for you, I will keep looking for a better answer.
I think PyWinCtl can make the trick in most cases. You can invoke alwaysOnTop() once for your window, or you can call it inside your loop to assure it stays on top after other apps open. Check this:
import tkinter as tk
import pywinctl as pwc
class Window(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.overrideredirect(True)
self.geometry('300x200')
self.config(background='black')
self.attributes('-alpha', 0.5)
self.label = tk.Label(text="Hello World!")
self.label.pack(fill=tk.BOTH, expand=1)
self.wait_visibility()
self.window = pwc.Window(int(self.frame(), base=16))
# Call it once at the beginning of your program...
try:
self.window.alwaysOnTop()
except:
pass
self.counter = 0
self.display()
def display(self):
if self.state() == "normal":
try:
# ... or call it repeatedly to assure it stays on top
self.window.alwaysOnTop()
except:
# On Linux, sometimes it takes more time to recognize the new window
self.window = pwc.Window(int(self.frame(), base=16))
self.label.config(text=str(self.counter))
self.counter += 1
self.after(1000, self.display)
root = Window()
root.mainloop()

How do I take a screenshot of a specfic window in Qt (Python, Linux), even if the windows are overlapped?

I'm trying to take a screenshot of the current active window in PyQt5. I know the generic method to take an screenshot of any window is QScreen::grabWindow(winID), for which winID is an implementation-specific ID depending on the window system. Since I'm running X and KDE, I plan to eventual use CTypes to call Xlib, but for now, I simply execute "xdotool getactivewindow" to obtain the windowID in a shell.
For a minimum exmaple, I created a QMainWindow with a QTimer. When the timer is fired, I identify the active window ID by executing "xdotool getactivewindow", get its return value, call grabWindow() to capture the active window, and display the screetshot in a QLabel. On startup, I also set my window a fixed 500x500 size for observation, and activate Qt.WindowStaysOnTopHint flag, so that my window is still visible when it's not in focus. To put them together, the implementation is the following code.
from PyQt5 import QtCore, QtGui, QtWidgets
import subprocess
class ScreenCapture(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
self.setFixedHeight(500)
self.setFixedWidth(500)
self.label = QtWidgets.QLabel(self)
self.timer = QtCore.QTimer(self)
self.timer.setInterval(500)
self.timer.timeout.connect(self.timer_handler)
self.timer.start()
self.screen = QtWidgets.QApplication.primaryScreen()
#QtCore.pyqtSlot()
def timer_handler(self):
window = int(subprocess.check_output(["xdotool", "getactivewindow"]).decode("ascii"))
self.screenshot = self.screen.grabWindow(window)
self.label.setPixmap(self.screenshot)
self.label.setFixedSize(self.screenshot.size())
if __name__ == '__main__':
app = QtWidgets.QApplication([])
window = ScreenCapture()
window.show()
app.exec()
To test the implementation, I started the script and clicked another window. It appears to work without problems if there is no overlap between my application window and the active window. See the following screenshot, when Firefox (right) is selected, my application is able to capture the active window of Firefox and display it in the QLabel.
However, the screenshot doesn't work as expected if there is an overlap between the application window and the active window. The window of the application itself will be captured, and creates a positive feedback.
If there is an overlap between the application window and the active window. The window of the application itself will be captured, and creates a positive feedback.
I've already disabled the 3D composite in KDE's settings, but the problem remains. The examples above are taken with all composite effects disabled.
Question
Why isn't this implementation working correctly when the application window and the active window are overlapped? I suspect it's an issue caused by some forms of unwanted interaction between graphics systems (Qt toolkit, window manager, X, etc), but I'm not sure.
Is it even possible solve this problem? (Note: I know I can hide() before the screenshot and show() it again, but it doesn't really solve this problem, which is taking a screenshot even if an overlap exists.)
As pointed out by #eyllanesc, it appears that it is not possible to do it in Qt, at least not with QScreen::grabWindow, because grabWindow() doesn't actually grab the window itself, but merely the area occupied by the window. The documentation contains the following warning.
The grabWindow() function grabs pixels from the screen, not from the window, i.e. if there is another window partially or entirely over the one you grab, you get pixels from the overlying window, too. The mouse cursor is generally not grabbed.
The conclusion is that it's impossible do to it in pure Qt. It's only possible to implement such a functionality by writing a low-level X program. Since the question asks for a solution "in Qt", any answer that potentially involves deeper, low-level X solutions are out-of-scope. This question can be marked as resolved.
The lesson to learn here: Always check the documentation before using a function or method.
Update: I managed to solve the problem by reading the window directly from X via Xlib. Somewhat ironically, my solution uses GTK to grab the window and sends its result to Qt... Anyway, you can write the same program with Xlib directly if you don't want to use GTK, but I used GTK since the Xlib-related functions in GDK is pretty convenient to demonstrate the basic concept.
To get a screenshot, we first convert our window ID to an GdkWindow suitable for use within GDK, and we call Gdk.pixbuf_get_from_window() to grab the window and store it in a gdk_pixbuf. Finally, we call save_to_bufferv() to convert the raw pixbuf to a suitable image format and store it in a buffer. At this point, the image in the buffer is suitable to use in any program, including Qt.
The documentation contains the following warning:
If the window is off the screen, then there is no image data in the obscured/offscreen regions to be placed in the pixbuf. The contents of portions of the pixbuf corresponding to the offscreen region are undefined.
If the window you’re obtaining data from is partially obscured by other windows, then the contents of the pixbuf areas corresponding to the obscured regions are undefined.
If the window is not mapped (typically because it’s iconified/minimized or not on the current workspace), then NULL will be returned.
If memory can’t be allocated for the return value, NULL will be returned instead.
It also has some remarks about compositing,
gdk_display_supports_composite has been deprecated since version 3.16 and should not be used in newly-written code.
Compositing is an outdated technology that only ever worked on X11.
So basically, it's only possible to grab a partially obscured window under X11 (not possible in Wayland!), with a compositing window manager. I tested it without compositing, and found the window is blacked-out when compositing is disabled. But when composition is enabled, it seems to work without problem. It may or may not work for your application. But I think if you are using compositing under X11, it probably will work.
from PyQt5 import QtCore, QtGui, QtWidgets
import subprocess
class ScreenCapture(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
self.setFixedHeight(500)
self.setFixedWidth(500)
self.label = QtWidgets.QLabel(self)
self.screen = QtWidgets.QApplication.primaryScreen()
self.timer = QtCore.QTimer(self)
self.timer.setInterval(500)
self.timer.timeout.connect(self.timer_handler)
self.timer.start()
#staticmethod
def grab_screenshot():
from gi.repository import Gdk, GdkX11
window_id = int(subprocess.check_output(["xdotool", "getactivewindow"]).decode("ascii"))
display = GdkX11.X11Display.get_default()
window = GdkX11.X11Window.foreign_new_for_display(display, window_id)
x, y, width, height = window.get_geometry()
pb = Gdk.pixbuf_get_from_window(window, 0, 0, width, height)
if pb:
buf = pb.save_to_bufferv("bmp", (), ())
return buf[1]
else:
return
#QtCore.pyqtSlot()
def timer_handler(self):
screenshot = self.grab_screenshot()
self.pixmap = QtGui.QPixmap()
if not self.pixmap:
return
self.pixmap.loadFromData(screenshot)
self.label.setPixmap(self.pixmap)
self.label.setFixedSize(self.pixmap.size())
if __name__ == '__main__':
app = QtWidgets.QApplication([])
window = ScreenCapture()
window.show()
app.exec()
Now it captures an active window perfectly, even if there are overlapping windows on top of it.

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 make click-through windows PyQt

I would like to make a window in PyQt that you can click through; ie click on a window and the click is passed through so you can interact with whatever is behind it, while the window remains on top. An example of the effect I am trying to achieve is like the notifications on Ubuntu which appear in the top-right hand corner by default, which you can click through.
I would like to be able to do this in PyQt ideally; if not, my platform is linux but windows solutions are also welcome!
Cheers for the help in advance! I've been giving this a bit of thought and research, it would be great to be able to do this.
EDIT: I am trying to make a window you can use like tracing paper for the window behind
Here is a solution on Windows using PyQt4.
You need to override the eventFilter in the Front widget (on Windows it is winEvent) and then forward the events to the Back window.
I'm not completely sure, but there must be a similar approach that can be used on other platforms (instead of winEvent, maybe x11Event?)
Good luck!
from PyQt4 import QtCore, QtGui
import win32api, win32con, win32gui, win32ui
class Front(QtGui.QPushButton):
def __init__(self,text="",whndl=None):
super(Front,self).__init__(text)
self.pycwnd = win32ui.CreateWindowFromHandle(whndl)
# install an event filter for Windows' messages. Forward messages to
# the other HWND
def winEvent(self,MSG):
# forward Left button down message to the other window. Not sure
# what you want to do exactly, so I'm only showing a left button click. You could
if MSG.message == win32con.WM_LBUTTONDOWN or \
MSG.message == win32con.WM_LBUTTONUP:
print "left click in front window"
self.pycwnd.SendMessage(MSG.message, MSG.wParam, MSG.lParam)
return True, 0 # tells Qt to ignore the message
return super(Front,self).winEvent(MSG)
class Back(QtGui.QPushButton):
def __init__(self,text=""):
super(Back,self).__init__(text)
self.clicked.connect(self.onClick)
def onClick(self):
print 'back has been clicked'
def main():
a = QtGui.QApplication([])
back = Back("I'm in back...")
back.setWindowTitle("I'm in back...")
back.show()
# Get the HWND of the window in back (You need to use the exact title of that window)
whndl = win32gui.FindWindowEx(0, 0, None, "I'm in back...")
# I'm just making the front button bigger so that it is obvious it is in front ...
front = Front(text="*____________________________*",whndl=whndl)
front.setWindowOpacity(0.8)
front.show()
a.exec_()
if __name__ == "__main__":
main()
self.setAttribute(Qt.WA_TransparentForMouseEvents, True)
self.setAttribute(Qt.WA_NoChildEventsForParent, True)
self.setWindowFlags(Qt.Window|Qt.X11BypassWindowManagerHint|Qt.WindowStaysOnTopHint|Qt.FramelessWindowHint)
self.setAttribute(Qt.WA_TranslucentBackground)
This works. It is tested on Linux Mint 20.1 Cinnamon.
That means the parent blocked it WA_TransparentForMouseEvents all the time

pygame dual monitors and fullscreen

I am using pygame to program a simple behavioral test. I'm running it on my macbook pro and have almost all the functionality working. However, during testing I'll have a second, external monitor that the subject sees and the laptop monitor. I'd like to have the game so up fullscreen on the external monitor and not on the laptop's monitor so that I can monitor performance. Currently, the start of the file looks something like:
#! /usr/bin/env python2.6
import pygame
import sys
stdscr = curses.initscr()
pygame.init()
screen = pygame.display.set_mode((1900, 1100), pygame.RESIZABLE)
I was thinking of starting the game in a resizable screen, but that OS X has problems resizing the window.
Pygame doesn't support two displays in a single pygame process(yet). See the question here and developer answer immediately after, where he says
Once SDL 1.3 is finished then pygame will get support for using multiple windows in the same process.
So, your options are:
Use multiple processes. Two pygame instances, each maximized on its own screen, communicating back and forth (you could use any of: the very cool python multiprocessing module, local TCP, pipes, writing/reading files, etc)
Set the same resolution on both of your displays, and create a large (wide) window that spans them with your information on one half and the user display on the other. Then manually place the window so that the user side is on their screen and yours is on the laptop screen. It's hacky, but might a better use of your time than engineering a better solution ("If it's studpid and it works, it ain't stupid" ;).
Use pyglet, which is similar to pygame and supports full screen windows: pyglet.window.Window(fullscreen=True, screens[1])
Good luck.
I do not know if you can do this in OS X, but this is worth mentioning for the Windows users out there, if you just want to have your program to run full screen on the second screen and you are on windows, just set the other screen as the main one.
The setting can be found under Rearrange Your Displays in settings.
So far for me anything that I can run on my main display can run this way, no need to change your code.
I did something silly but it works.
i get the number of monitors with get_monitors()
than i use SDL to change the pygame window's display position by adding to it the width of the smallest screen, to be sure that the window will be positionned in the second monitor.
from screeninfo import get_monitors
numberOfmonitors = 0
smallScreenWidth = 9999
for monitor in get_monitors():
#getting the smallest screen width
smallScreenWidth = min(smallScreenWidth, monitor.width)
numberOfmonitors += 1
if numberOfmonitors > 1:
x = smallScreenWidth
y = 0
#this will position the pygame window in the second monitor
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (x,y)
#you can check with a small window
#screen = pygame.display.set_mode((100,100))
#or go full screen in second monitor
screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
#if you want to do other tasks on the laptop (first monitor) while the pygame window is being displayed on the second monitor, you shoudn't use fullscreen but instead get the second monitor's width and heigh using monitor.width and monitor.height, and set the display mode like
screen = pygame.display.set_mode((width,height))
display = pyglet.canvas.get_display()
display = display.get_screens()
win = pyglet.window.Window(screen=display[1])
------------------------------------------------------
screen=display[Номер монитора]
------------------------------------------------------
display = pyglet.canvas.get_display()
display = display.get_screens()
print(display) # Все мониторы которые есть

Categories

Resources