I'm taking a screenshot using this line
screen = QPixmap.grabWindow(QApplication.desktop().winId())
but apparently that doesn't grab the full desktop if the user has several monitors.
Is there a way to grab the desktop of all monitors into a single image?
According to this blog, just add the x, y, width, and height to grab the full desktop.
Just need to loop over QApplication.screens() and grab them one at a time like so...
Note: using Python 3.8 and PyQt5
import os
from PyQt5.QtWidgets import QApplication
app = QApplication(sys.argv)
for screen in QApplication.screens():
screen_path = os.path.expanduser(f"~/Desktop/{screen.name()}.jpg")
screen.grabWindow(0).save(screen_path, 'jpg')
# grabWindow(0) means full screen
# for area use following format; x=0, y=0, w=-1, h=-1
Related
I am using pyqtgraph and trying to use a histogramLUTable inside of a viewbox to try and plot some data and I have this black box with a little A that is obstructing the graph origin/axis in the lower left hand corner and I can't seem to find what it is named and/or how to get rid of it. I attached a picture with a little red box around what I am talking about.
The image in question with little red box circling what I am referring to:
There is also a little black box on the lower right hadn side obfuscating the x axis but doesn't go into the level adjustment on the right hand side.
image showing issue with first code written :
For reference, my code so far:
import PyQt5
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pandas as pd
import pyqtgraph as pg
up = np.linspace(0,1,100)
down = np.flip(up)
nparray = np.hstack([up, down])
data = np.vstack([[nparray], [nparray],[nparray]])
data = np.transpose(data)
app = QtGui.QApplication([])
win = pg.GraphicsLayoutWidget(show=True, title="pyqtGraph attempt", size=[800,600])
view = pg.PlotItem()
win.addItem(view)
img = pg.ImageItem(data, border='w')
histogram = pg.HistogramLUTItem()
histogram.setImageItem(img)
win.addItem(histogram)
if __name__ == '__main__':
pg.mkQApp().exec_()
EDIT (again):
I took a different approach to try and mitigate the little black box by using a plotItem and Viewbox rather than the HistogramLUTable but when I go this route I get the same issue. Originally I said this gives me another black box in the lower right hand corner but if you look at my original screen shot the black box is there originally as well.
import PyQt5
from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pandas as pd
import pyqtgraph as pg
up = np.linspace(0,1,100)
down = np.flip(up)
nparray = np.hstack([up, down])
data = np.vstack([[nparray], [nparray],[nparray]])
app = QtGui.QApplication([])
win = pg.GraphicsLayoutWidget(show=True, title="pyqtGraph attempt", size=[800,600])
view = pg.PlotItem()
view.hideButtons()
win.addItem(view)
img = pg.ImageItem()
view.addItem(img)
img.setImage(data)
if __name__ == '__main__':
pg.mkQApp().exec_()
image showing issue with second code written:
This element is a button that allows the plot to auto-scale, therefore it appears when you zoom. One possible solution is to hide it using hideButtons() method:
view = pg.PlotItem()
view.hideButtons()
win.addItem(view)
So after a lot of playing with the code the issue appeared to be using:
app = QtGui.QApplication([])
in conjunction with:
pg.mkQApp().exec_()
It causes issues, I assume from interfering with each other, because your trying to call two application execution methods but this is just an assumption.
Preferred solution if using pyqtgraph is to stick with removing
app = QtGui.QApplication([])
altogether and only including
if __name__ == '__main__': pg.mkQapp().exec_()
But an alternate also includes using
app = QtGui.QApplication([])
with
if __name__ == '__main__': app.exec_()
But it depends on how you are using pyqtgraph, if you are using it for it's graphing methods then you will probably need to use app.addItem(plotItem) and therefore you need to call app.exec_() however if you allow pyqtgraph to take care of generating the GUI you can let it take care of executing the application via pg.mkQapp().exec_()
I was following this tutorial on how to write a chess program in Python.
It uses the python-chess engine. The functions from that engine apparently return SVG data, that could be used to display a chessboard.
Code from the tutorial:
import chess
import chess.svg
from IPython.display import SVG
board = chess.Board()
SVG(chess.svg.board(board=board,size=400))
but when I run that code, all I see is a line in the terminal and no image.
<IPython.core.display.SVG object>
The tutorial makes a passing reference to Jupyter Notebooks and how they can be used to display SVG images. I have no experience with Jupyter Notebooks and even though I installed the package from pip and I dabbled a little into how to use it, I couldn't make much progress with regards to my original chessboard problem. But what I do have, is, experience with Qt development using C++ and since Qt has Python bindings, I decided to use those bindings.
Here is what I wrote:
import sys
import chess
import chess.svg
from PyQt5 import QtGui, QtSvg
from PyQt5.QtWidgets import QApplication
from IPython.display import SVG, display
app = QApplication(sys.argv);
board = chess.Board();
svgWidget = QtSvg.QSvgWidget(chess.svg.board(board=board, size=400));
#svgWidget.setGeometry(50,50,759,668)
svgWidget.show()
sys.exit(app.exec_())
A Qt window opens and shows nothing and in the terminal I see a lot of text - (apparently the SVG data is ending up in the console and not in the Qt window that is opening?).
I figured I have to install some SVG library under python so I installed drawSvg from pip. But it seems that library generates SVG images. And was of no use for me.
What is even more strange is, after seeing this SO question, I tried the following:
import sys
import chess
import chess.svg
from PyQt5 import QtGui, QtSvg
from PyQt5.QtWidgets import QApplication
from IPython.display import SVG, display
app = QApplication(sys.argv);
board = chess.Board();
svgWidget = QtSvg.QSvgWidget('d:\projects\python_chess\Zeichen_123.svg');
#svgWidget.setGeometry(50,50,759,668)
svgWidget.show()
sys.exit(app.exec_())
And it showed an image - an SVG image! What is the difference then between my case and this case?
Question: So my question is, what I am doing wrong in the case of the chessboard SVG data? Is the SVG data generated by the python-chess library not compatible with QtSvg?
I think you are getting confused by the scripting nature of Python. You say, you have experience with Qt development under C++. Wouldn't you create a main window widget there first and add to it your SVG widget within which you would call or load SVG data?
I would rewrite your code something like this.
import chess
import chess.svg
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtWidgets import QApplication, QWidget
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(100, 100, 1100, 1100)
self.widgetSvg = QSvgWidget(parent=self)
self.widgetSvg.setGeometry(10, 10, 1080, 1080)
self.chessboard = chess.Board()
self.chessboardSvg = chess.svg.board(self.chessboard).encode("UTF-8")
self.widgetSvg.load(self.chessboardSvg)
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
window.show()
app.exec()
EDIT
It would be even better if you would add a paint function to the MainWindow class. Because for sure in future, you would want to repaint your board image many times, whenever you would move a piece. So I would do something like this.
def paintEvent(self, event):
self.chessboardSvg = chess.svg.board(self.chessboard).encode("UTF-8")
self.widgetSvg.load(self.chessboardSvg)
this is how i display SVG image with python, no need third party to do that.
First i save SVG image on disk
second i just call it like os.Startfile("exemple.svg")
Exemple:
boardsvg = svg.board(board(fen), size=600, coordinates=True)
with open('temp.svg', 'w') as outputfile:
outputfile.write(boardsvg)
time.sleep(0.1)
os.startfile('temp.svg')
that's it !
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 found this question : Get screenshot on Windows with Python?
to take a screenshot of the full desktop:
import sys
from PyQt4.QtGui import QPixmap, QApplication
from datetime import datetime
date = datetime.now()
filename = date.strftime('%Y-%m-%d_%H-%M-%S.jpg')
app = QApplication(sys.argv)
QPixmap.grabWindow(QApplication.desktop().winId()).save(filename, 'jpg')
However, I would like to take a screenshot of an external window.
I have the hwnd of the window I want to take the screenshot off using win32gui.
According to the documentation for winId, the returned value is platform dependent.
So for Windows, it surely must (famous last words), return a hwnd and thus need no further conversion. If so, then try:
QPixmap.grabWindow(hwnd).save(filename, 'jpg')
(PS: I have actually tested this now on WinXP, and it works okay for me).
I am developing an application; which will run on a system with 2 displays. I want my PyQt app to be able to automatically route a particular window to the second screen.
How can this be done in Qt? (either in Python or C++)
The following works for me in PyQt5
import sys
from PyQt5.QtWidgets import QApplication, QDesktopWidget
app = QApplication(sys.argv)
widget = ... # define your widget
display_monitor = ... # the number of the monitor you want to display your widget
monitor = QDesktopWidget().screenGeometry(display_monitor)
widget.move(monitor.left(), monitor.top())
widget.showFullScreen()
Update. In PyQt6 one should use:
...
from PyQt6.QtGui import QScreen
...
monitors = QScreen.virtualSiblings(widget.screen())
monitor = monitors[display_monitor].availableGeometry()
...
Monitors should be counted starting from 0.
Use QDesktopWidget to access to screen information on multi-head systems.
Here is pseudo code to make a widget cover first screen.
QDesktopWidget *pDesktop = QApplication::desktop ();
//Get 1st screen's geometry
QRect RectScreen0 = pDesktop->screenGeometry (0);
//Move the widget to first screen without changing its geometry
my_Widget->move (RectScreen0.left(),RectScreen0.top());
my_pWidget->resize (RectScreen0.width(),RectScreen0.height());
my_Widget->showMaximized();