Home > Mobile >  Place a Window behind desktop icons using PyQt on Linux
Place a Window behind desktop icons using PyQt on Linux

Time:11-27

I'm trying to develop a simple Wallpaper manager, but I am not able to find any method to place my PyQt Window between the current wallpaper and the desktop icons using XLib (on windows it's way easier and works perfectly using pywin32).

I'm trying to develop a simple Wallpaper manager, but I am not able to find any method to place my PyQt Window between the current wallpaper and the desktop icons using XLib (on windows it's way easier and works perfectly using pywin32).

I tried to reparent the window, but it doesn't work (possibly it's not the right strategy). I have tried this on Ubuntu/Unity, Mint/Cinnamon and Raspbian, with no success.

EDIT: I tried changing the window properties (commented in my sample code), as well as creating a new window, change its properties as DESKTOP, and set that new window as the target window parent... with no success. In fact, the newly created window does not show behind desktop icons (see modified code below)

Any help or clue is welcome!!!

#!/usr/bin/python
# -*- coding: utf-8 -*-

import signal
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
import Xlib
import Xlib.X
import Xlib.display
import ewmh
import traceback

DISP = Xlib.display.Display()
SCREEN = DISP.screen()
ROOT = SCREEN.root
EWMH = ewmh.EWMH(_display=DISP, root=ROOT)


class Window(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        QtWidgets.QMainWindow.__init__(self, *args, **kwargs)

        caption = "TestIt"
        self.setWindowTitle(caption)

        self.setWindowFlags(QtCore.Qt.WindowStaysOnBottomHint | QtCore.Qt.FramelessWindowHint)

        screenSize = QtWidgets.QApplication.primaryScreen().size()
        x = screenSize.width()
        y = screenSize.height()
        self.setGeometry(0, 0, x, y)

        self.bkg_label = QtWidgets.QLabel()
        self.bkg_label.setGeometry(0, 0, x, y)
        pixmap = resizeImageWithQT("resourcesB/Doom.jpg", x, y)
        self.bkg_label.setPixmap(pixmap)
        self.layout().addWidget(self.bkg_label)
        self.bkg_label.show()

        sendBehind(caption)


def resizeImageWithQT(src, width, height, keepAspect=True):
    try:
        pixmap = QtGui.QPixmap(src)
        if keepAspect:
            pixmap_resized = pixmap.scaled(int(width), int(height), QtCore.Qt.KeepAspectRatioByExpanding, QtCore.Qt.SmoothTransformation)
        else:
            pixmap_resized = pixmap.scaled(int(width), int(height), QtCore.Qt.IgnoreAspectRatio, QtCore.Qt.SmoothTransformation)
    except:
        pixmap_resized = None
    return pixmap_resized


def sendBehind(name):

    def getAllWindows():
        # windows = EWMH.getClientList()  # This will return the "user" apps only
        windows = ROOT.query_tree().children
        return windows

    def getWindowsWithTitle(title):
        matches = []
        for win in getAllWindows():
            if title == EWMH.getWmName(win):
                matches.append(win)

        return matches

    def getActiveWindow():
        win_id = EWMH.getActiveWindow()
        if win_id:
            return win_id
        return None

    win = getWindowsWithTitle(name)
    if win:
        win = win[0]
        w = DISP.create_resource_object('window', win)

        # https://stackoverflow.com/questions/58885803/can-i-use-net-wm-window-type-dock-ewhm-extension-in-openbox
        # Does not sends current window below. It does with the new window, but not  behind the desktop icons
        # w.change_property(DISP.intern_atom('_NET_WM_WINDOW_TYPE'), Xlib.Xatom.ATOM,
        #                              32, [DISP.intern_atom("_NET_WM_WINDOW_TYPE_DESKTOP"), ],
        #                              Xlib.X.PropModeReplace)
        # w.map()
        # DISP.next_event()
        # DISP.next_event()

        newWin = ROOT.create_window(0, 0, 500, 500, 1, SCREEN.root_depth,
                                    background_pixel=SCREEN.black_pixel,
                                    event_mask=Xlib.X.ExposureMask | Xlib.X.KeyPressMask)
        newWin.change_property(DISP.intern_atom('_NET_WM_WINDOW_TYPE'), Xlib.Xatom.ATOM,
                               32, [DISP.intern_atom("_NET_WM_WINDOW_TYPE_DESKTOP"), ],
                               Xlib.X.PropModeReplace)
        newWin.map()
        DISP.next_event()
        DISP.next_event()
        w.reparent(newWin, 0, 0)


def sigint_handler(*args):
    # https://stackoverflow.com/questions/4938723/what-is-the-correct-way-to-make-my-pyqt-application-quit-when-killed-from-the-co
    app.closeAllWindows()


def exception_hook(exctype, value, tb):
    # https://stackoverflow.com/questions/56991627/how-does-the-sys-excepthook-function-work-with-pyqt5
    traceback_formated = traceback.format_exception(exctype, value, tb)
    traceback_string = "".join(traceback_formated)
    print(traceback_string, file=sys.stderr)
    sys.exit(1)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    if "python" in sys.executable.lower():
        # This will allow to manage Ctl-C interruption (e.g. when running from IDE)
        signal.signal(signal.SIGINT, sigint_handler)
        timer = QtCore.QTimer()
        timer.start(500)
        timer.timeout.connect(lambda: None)
        # This will allow to show some tracebacks (not all, anyway)
        sys._excepthook = sys.excepthook
        sys.excepthook = exception_hook
    win = Window()
    win.show()
    try:
        app.exec_()
    except:
        pass

CodePudding user response:

I eventually (and by chance) found the solution. No need to use Xlib. This PyQt piece of code did the trick. I have to find a smarter way to bring the Desktop (desktop icons) on top of the wallpaper after showing it. Simulating a mouse click on the desktop is working by the moment.

#!/usr/bin/python
# -*- coding: utf-8 -*-

import signal
import sys
from PyQt5 import QtWidgets, QtCore, QtGui
import Xlib
import Xlib.X
import Xlib.display
import ewmh
import traceback

DISP = Xlib.display.Display()
SCREEN = DISP.screen()
ROOT = SCREEN.root
EWMH = ewmh.EWMH(_display=DISP, root=ROOT)


class Window(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        QtWidgets.QMainWindow.__init__(self, *args, **kwargs)

        caption = "TestIt"
        self.setWindowTitle(caption)
        if "Linux" in platform.platform():
            parent.setAttribute(QtCore.Qt.WA_X11NetWmWindowTypeDesktop)
            parent.setFocusPolicy(QtCore.Qt.NoFocus)
            parent.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
            parent.setAttribute(QtCore.Qt.WA_InputMethodTransparent)
        parent.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowStaysOnBottomHint | QtCore.Qt.FramelessWindowHint)

        screenSize = QtWidgets.QApplication.primaryScreen().size()
        x = screenSize.width()
        y = screenSize.height()
        self.setGeometry(0, 0, x, y)

        self.bkg_label = QtWidgets.QLabel()
        self.bkg_label.setGeometry(0, 0, x, y)
        pixmap = resizeImageWithQT("resourcesB/Doom.jpg", x, y)
        self.bkg_label.setPixmap(pixmap)
        self.layout().addWidget(self.bkg_label)
        self.bkg_label.show()

        sendBehind()


def resizeImageWithQT(src, width, height, keepAspect=True):
    try:
        pixmap = QtGui.QPixmap(src)
        if keepAspect:
            pixmap_resized = pixmap.scaled(int(width), int(height), QtCore.Qt.KeepAspectRatioByExpanding, QtCore.Qt.SmoothTransformation)
        else:
            pixmap_resized = pixmap.scaled(int(width), int(height), QtCore.Qt.IgnoreAspectRatio, QtCore.Qt.SmoothTransformation)
    except:
        pixmap_resized = None
    return pixmap_resized


def sendBehind():
        pyautogui.leftclick(x=1, y=1)


def sigint_handler(*args):
    # https://stackoverflow.com/questions/4938723/what-is-the-correct-way-to-make-my-pyqt-application-quit-when-killed-from-the-co
    app.closeAllWindows()


def exception_hook(exctype, value, tb):
    # https://stackoverflow.com/questions/56991627/how-does-the-sys-excepthook-function-work-with-pyqt5
    traceback_formated = traceback.format_exception(exctype, value, tb)
    traceback_string = "".join(traceback_formated)
    print(traceback_string, file=sys.stderr)
    sys.exit(1)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    if "python" in sys.executable.lower():
        # This will allow to manage Ctl-C interruption (e.g. when running from IDE)
        signal.signal(signal.SIGINT, sigint_handler)
        timer = QtCore.QTimer()
        timer.start(500)
        timer.timeout.connect(lambda: None)
        # This will allow to show some tracebacks (not all, anyway)
        sys._excepthook = sys.excepthook
        sys.excepthook = exception_hook
    win = Window()
    win.show()
    try:
        app.exec_()
    except:
        pass
  • Related