How to display a window on a secondary display in PyQT? - python

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();

Related

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 make my pyqt5 frameless application which maximizes to Max according to different resolutions

I already have a custom frameless window application in Pyqt5. I just want the application to be once opened, get to full size depending upon the different resolutions of different PC's.
I want only _ and X buttons and not a [] (maximize) button, because I want it to maximize automatically. Is there any way to do that??
The best approach to achieve that is to use a combination of showMaximized() and the disabled WindowMaximizeButtonHint window flag:
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowFlags(
self.windowFlags() & ~QtCore.Qt.WindowMaximizeButtonHint)
# ...
myWindow = MyWindow()
myWindow.showMaximized()
The only problem with this is that (at least on Windows) as soon as you disable the maximize button, the window becomes movable.
I just found that solution. We can do that by :
from PySide import QtGui
app = QtGui.QApplication([])
screen_resolution = app.desktop().screenGeometry()
width, height = screen_resolution.width(), screen_resolution.height()

PyQt setText not rendering text properly in MacOs

I am creating an application using PyQt5 (pythyon 3.7 , MacOs X)
When I modify the text in a textbox using the instruction
self.line_main.setText(final_text)
from a function (which is connected to a push button)
The new text does not render properly in the textbox (see screenshot) and both the old and new text are overlapping in a strange way.
An over-simplified code to illustrate the problem is this one:
import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QWidget, QLabel, QLineEdit
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtCore import QSize
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setMinimumSize(QSize(200, 200))
self.setWindowTitle("PyQt test")
self.line_main = QLineEdit(self)
self.line_main.move(20,20)
bt_upperCase = QPushButton('Upper Case', self)
bt_upperCase.move(20, 60)
bt_upperCase.clicked.connect(self.click_upCase)
def click_upCase(self):
final_text=self.line_main.text().upper()
self.line_main.setText(final_text)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit( app.exec_() )
screenshot of mini-app, with 're' as input as 'RE' as output
The same thing may happen for labels, although at times, only the old text is visible, and I need to select the text with the mouse or resize the main window to 'refresh' the textbox and see the new value.
The problem does not happen if I run the code in a PC, only in MacOs (tested in High Sierra and Mojave with identical results)
Some weird behaviours are:
If position the textbox at the (0,0) position in the main window, which is not very practical, then the setText functions correctly. In some other locations of the box, too, but only if it is very close to the top-left corner, and no other element is near.
If I modify the textbox value at the initialization of the main window, then it renders correctly in all cases, The problem appears when you try to do it from a function (after clicking a button, for instance), like in the code attached.
The problem does not happen if I run the code in a PC, only in MacOs (tested in High Sierra and Mojave with identical results)
If position the textbox at the (0,0) position in the main window, (which is not very practical), then the setText functions correctly. In some other locations of the box, too, but only if it is very close to the top-left corner, and no other element is near.
Does anyone have a guess why this may happen, and how could it be solved?
UPDATE.
The problem dissapears if i run the code with python 3.6 in a conda installation (PyQt version 5.9.2). Still very intrigued to know what caused the problem.

Qt application blinking at startup because of drawing text in a custom paint event

I discovered a very strange behavior which causes blinking (white rectangle is shown as the main window for about half a second) at application start up in some cases. After a long time of test and trial I narrowed down the problem to the situation when a text is first drawn in paint event of my custom widget. If a text is drawn also in another widget such as QLabel, the problem is gone. But my application main window only has tool buttons with icons and a custom widget which draws text, no other widgets. The white blinking is very ugly and I would like to get rid of it, ideally with some proper solution and without nasty hacks of introducing some artificial text drawing widgets. Moreover I am not very comfortable because I really do not know what is actually going on. Why the custom widget causes blinking and QLabel not? To prove the behavior try the following code (the same problem is in C++/Qt so it is not caused by Python wrapper). Then try to uncomment the marked line and comment the next one to see that the blinking is gone.
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QLabel
class CustomWidget(QWidget):
def paintEvent(self, event):
p = QPainter(self)
p.drawText(20, 20, "XYZ")
app = QApplication([])
container = QWidget()
layout = QVBoxLayout(container)
# label = QLabel("ABC") # uncomment this to prevent blinking
label = QLabel() # comment this out to prevent blinking
layout.addWidget(label)
layout.addWidget(CustomWidget())
container.resize(600, 600)
container.show()
app.exec()
Any ideas what is going on there? I am using Qt 5.9.2.

QPlainTextEdit widget in PyQt5 with clickable text

I am trying to build a simple code/text editor in PyQt5. Three important functionalities of a code-editor come to my mind:
(1) Syntax highlighting
(2) Code completion
(3) Clickable functions and variables
I plan to base the code editor on a QPlainTextEdit widget. For the syntax highlighting, I will use the QSyntaxHighlighter:
https://doc.qt.io/qt-5/qsyntaxhighlighter.html#details
I have not yet figured out how to do code completion, but will worry about that feature later on. The showstopper right now is 'clickable functions and variables'. In mature code editors, one can click on a function call and jump to the definition of that function. The same holds for variables.
I know that asking "how do I implement this feature?" is way too broad. So let's narrow it down to the following problem:
How can you make a certain word clickable in a QPlainTextEdit widget? An arbitrary Python function should get called when the word gets clicked. That Python function should also know what word was clicked to take appropriate action. Making the word light up blue-ish when the mouse hovers over it would be a nice bonus.
I have written a small test-code, so you have a Qt window with a QPlainTextEdit widget in the middle to play around with:
Here is the code:
import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
##################################################
# 'testText' is a snippet of C-code that #
# will get displayed in the text editor. #
##################################################
testText = '\
# include <stdio.h>\n\
# include <stdbool.h>\n\
# include <stdint.h>\n\
# include "../FreeRTOS/Kernel/kernel.h"\n\
\n\
int main(void)\n\
{\n\
/* Reset all peripherals*/\n\
HAL_Init();\n\
\n\
/* Configure the system clock */\n\
SystemClock_Config();\n\
\n\
/* Initialize all configured peripherals */\n\
MX_GPIO_Init();\n\
MX_SPI1_Init();\n\
MX_SPI2_Init();\n\
MX_SPI3_Init();\n\
}\n\
'
##################################################
# A simple text editor #
# #
##################################################
class MyMainWindow(QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
# Define the geometry of the main window
self.setGeometry(200, 200, 800, 800)
self.setWindowTitle("text editor test")
# Create center frame
self.centerFrm = QFrame(self)
self.centerFrm.setStyleSheet("QWidget { background-color: #ddeeff; }")
self.centerLyt = QVBoxLayout()
self.centerFrm.setLayout(self.centerLyt)
self.setCentralWidget(self.centerFrm)
# Create QTextEdit
self.myTextEdit = QPlainTextEdit()
self.myTextEdit.setPlainText(testText)
self.myTextEdit.setStyleSheet("QPlainTextEdit { background-color: #ffffff; }")
self.myTextEdit.setMinimumHeight(500)
self.myTextEdit.setMaximumHeight(500)
self.myTextEdit.setMinimumWidth(700)
self.myTextEdit.setMaximumWidth(700)
self.centerLyt.addWidget(self.myTextEdit)
self.show()
if __name__== '__main__':
app = QApplication(sys.argv)
QApplication.setStyle(QStyleFactory.create('Fusion'))
myGUI = MyMainWindow()
app.exec_()
del app
sys.exit()
EDIT
I just revisited this question after long time. In the meantime, I've constructed a website about QScintilla: https://qscintilla.com
You can find all info there :-)
Maybe you can utilize qutepart. It looks like it does what you need. If you can't use it, then maybe you can look at the source code to see how they implemented different features, like clickable text.

Categories

Resources