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....
Related
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.
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()
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.
I am looking for a way in which wxPython GUI elements can be added into pygame. If there is an alternative to solve the purpose of adding GUI elements to a pygame please suggest so. I am having problem in installing PGU.Thanks for help.
Nope; you CAN add wxWidgets to PyGame. It's trivial to add open/save dialogs, buttons, etc. Sometimes it gets nasty when you try to share areas, (as above, there's conflicting window systems), but it is definitely doable.
I wrote the following years ago, so it's messy; but at least it's simple. It should help you get started:
class SDLThread:
def __init__(self,screen):
self.m_bKeepGoing = self.m_bRunning = False
self.screen = screen
self.color = (255,0,0)
self.rect = (10,10,100,100)
def Start(self):
self.m_bKeepGoing = self.m_bRunning = True
thread.start_new_thread(self.Run, ())
def Stop(self):
self.m_bKeepGoing = False
def IsRunning(self):
return self.m_bRunning
def Run(self):
while self.m_bKeepGoing:
pass
## GetInput()
## Draw()
self.m_bRunning = False;
class SDLPanel(wx.Panel):
def __init__(self,parent,ID,tplSize):
global pygame
wx.Panel.__init__(self, parent, ID, size=tplSize)
self.Fit()
os.environ['SDL_WINDOWID'] = str(self.GetHandle())
os.environ['SDL_VIDEODRIVER'] = 'windib'
import pygame
pygame.init()
icon = pygame.Surface((1,1));icon.set_alpha(0);pygame.display.set_icon(icon)
global Surface
Surface = pygame.display.set_mode(tplSize)
self.thread = SDLThread(Surface)
self.thread.Start()
def __del__(self):
self.thread.Stop()
I dont think its possible since wxPython (using wxWidgets c++) is a wrapper around the window management system, it rely on the OS functions to draw on screen (it convert to the right system calls depending on OS).
Pygame uses SDL and a simple code to window management system, it calls few OS functions just to create the window and give it to SDL to draw on it, like a canvas.
One way is:
using ctypes to make C calls into OS library files to draw system native widgets, this way you aren't using any pygame functions.
Other way(ugly and not sure if works):
I dont know how, but finding a way to convert widgets into image buffer objects and use it like surfaces(plain bitmaps).
What I think you should do:
Use pygame, build your own GUI using surfaces and mouse/keyboard events to control it or download some pre-made.
Stick with wx, you can build your GUI easily on system native theme, also you can use DC (draw contexts) to draw like pygame, and maybe (not sure) include pygame context into wx as one widget.
You can benefit from wx having its own event handler.
I hope it helps you in some way.
I have been trying for many days to figure out a way to create a transparent Qtextedit with opaque text. Because the term "transparency" is often ambiguous, I define Qtextedit"transparency" as being able to see the text in the Qtextedit overlaid upon whatever is directly behind the main window (such as the desktop background, windows media player etc.) If possible I would like to be able to set the transparency at various levels and cross system compatible, but this is not required.
I am an extreme beginner, as I have only been using pyqt4 for 3 weeks and python 3.x for a few months and this is all the experience with programming that I have obtained in my existence. I have been attempting to decipher the Pyqt documentation with regard to this matter, but it is written in a way that seems to assume that one has been a gui programer for decades, not to mention having knowlege of C++. Furthermore, when this question is asked online it never seems to be resolved in way that is either: a) well documented or b) generalizable
This is very surprising because it seems like a basic operation that people would want to do
This solution works but doesn't seem to be directly useful for anything but displaying transparent images. I also don't really understand it all that well, as simply changing the base class from QWidget to QMainWindow makes the whole thing fail
http://www.loopbacking.info/blog/2008/07/11/transparent-windows-howto/
The following link embodies the common ways people suggest to solve problems similar to this, their pitfalls and why they don't work, but unfortunately they use the C++ version of Qt and are also a bit advanced for my skills at this point.
http://www.qtcentre.org/threads/18072-How-to-set-Qt-window-transparent
My system is windows 7 ultimate 32 bit on a dell latitude d830 with a Quadro NVS 140 whose driver version is current as of this post (Verde 275.33) My version of Pyqt is 4.8 (PyQt-Py3.2-x86-gpl-4.8.5-1.exe Windows 32 bit installer) I am also using Python 3.2.1 (Open Source version)
A basic example of my code lies beneath with the relevant (and failed) lines commented out:
When I tried the commented out code the color I generally just saw blackness. Also, when I resized my windows the darkness would randomly change intensity and the display of the main window seemed to get corrupted when maximized.
I would greatly appreciate any help on this matter!
import sys
import PyQt4
from PyQt4 import QtGui, QtCore
class Transparent(QtGui.QMainWindow):
def __init__(self,parent = None):
QtGui.QMainWindow.__init__(self,parent)
self.initialize()
def initialize(self):
#self.colorset(self,'Window',200,255,100,20)
#self.colorset(self,'Base',200,255,100,20)
#self.setBackgroundRole(QtGui.QPalette.Base)
#self.setAttribute(QtCore.Qt.WA_NoSystemBackground)
#self.setAutoFillBackground(True)
#self.mask()
self.setWindowTitle("Chernobyl-like Failure")
self.answerlabel = QtGui.QLabel('Text Response Display')
self.answerlabel.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Raised)
self.answerlabel.setMinimumHeight(25)
self.questionlabel = QtGui.QLabel("Question:")
self.questionlabel.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Raised)
self.questionbox = QtGui.QLineEdit()
self.questionbox.setMinimumWidth(500)
self.askbutton = QtGui.QPushButton("Ask it!")
self.historybox = QtGui.QTextEdit('Question & Answer history will be displayed here')
self.historybox.setReadOnly(True)
#self.colorset(self.historybox,'Base',200,255,100,127)
self.grid = QtGui.QGridLayout()
widgetlist = [['answerlabel',0,0,1,3],['questionlabel',1,0,1,1],
['questionbox',1,1,1,1],['askbutton',1,2,1,1],['historybox',2,0,1,3]]
for widget in widgetlist:
self.grid.addWidget(eval("self.{0}".format(widget[0])),*widget[1:])
self.centralwidget = QtGui.QFrame()
self.centralwidget.setFrameStyle(QtGui.QFrame.Box|QtGui.QFrame.Raised)
self.centralwidget.setLineWidth(5)
self.centralwidget.setLayout(self.grid)
#self.colorset(self.centralwidget,'Base',200,255,100,127)
self.setCentralWidget(self.centralwidget)
def colorset(self,widget,part,h,s,l,a):
pal = widget.palette()
color = QtGui.QColor()
color.setHsl(h,s,l,a)
pal.setColor(eval('QtGui.QPalette.{0}'.format(part)),color)
widget.setPalette(pal)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
main_window = Transparent()
main_window.show()
sys.exit(app.exec_())
To make your main window transparent, you have to set the Qt.WA_TranslucentBackground attribute (using setAttribute(Qt.WA_TranslucentBackground)). Under Windows, you also must set the Qt.FramelessWindowHint attribute on your main window. According to the docs, however, "The user cannot move or resize a borderless window via the window system." So, if you want that functionality, you have to implement it manually. Here is a thread giving an example of that in C++.
Once you have a transparent MainWindow you can control the opacity of it and any child widgets by setting the background color to an RGBA value. Here is a dumb example,
from PyQt4 import QtGui, QtCore
import sys
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
frame = QtGui.QFrame(parent=self)
frame.setStyleSheet("QFrame {background: rgba(0,255,0,20%)}")
box=QtGui.QHBoxLayout()
edit = QtGui.QTextEdit()
edit.setStyleSheet("background: rgba(0,0,255,20%)")
box.addWidget(edit)
edit2=QtGui.QTextEdit()
edit2.setStyleSheet("background: rgb(255,0,0)")
box.addWidget(edit2)
frame.setLayout(box)
pushbutton = QtGui.QPushButton('Quit')
pushbutton.clicked.connect(self.close)
box.addWidget(pushbutton)
self.setCentralWidget(frame)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Main()
main.show()
app.exec_()