'How might one make a PyQt window which is avoided by other windows, like how windows avoid going on top of or below a panel?

I'd like to make a very simple PyQt5 panel, something a little like a very simple version of the MATE panel. In order for it to work as a panel, other windows of the desktop environment must not go under or above the panel, and when a window is maximised, it enlarges to the edge of the panel window, but does not overlap and go on top of the panel. How might this behaviour be implemented, or how might the window manager be directed to treat the panel appropriately?

In the LXPanel application preferences, one can see properties like these specified. What is happening in the background here and how can it be replicated by PyQt?

For the purposes of this question, let us assume that the panel is a window at the left edge of a display. Minimal working example code follows:

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

import sys

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import (
    QApplication,
    QPushButton,
    QToolTip, 
    QWidget,
)

def main():
    app = QApplication(sys.argv)
    interface = Interface()
    sys.exit(app.exec_())

class Interface(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowFlags(Qt.WindowStaysOnTopHint)
        self.setWindowFlags(Qt.FramelessWindowHint)
        QToolTip.setFont(QFont('SansSerif', 10))
        self.setToolTip('This is a <b>QWidget</b> widget.')

        button_1 = QPushButton('Button 1', self)
        button_1.setToolTip('This is a <b>QPushButton</b> widget')
        button_1.resize(button_1.sizeHint())
        button_1.move(40, 50) # button width: 80

        button_2 = QPushButton('Button 2', self)
        button_2.setToolTip('This is a <b>QPushButton</b> widget')
        button_2.resize(button_2.sizeHint())
        button_2.move(40, 80) # button width: 80

        button_quit = QPushButton('Quit', self)
        button_quit.setToolTip('Quit the application.')
        button_quit.resize(button_quit.sizeHint())
        button_quit.move(40, 110) # button width: 80
        button_quit.clicked.connect(self.closeEvent)

        self.resize(160, 600)
        self.setWindowTitle('Panel')
        self.show()
    def closeEvent(self, event):
        sys.exit(0)

if __name__ == '__main__':
    main()

EDIT: With Python XLib, I appear to be a little closer to the answer. I appear to be reserving space of the right size, but with the window of the launcher appearing to the side of that reserved space. The updated code, this time including new XLib code, is shown below. Note that in order to run the example, both PyQt 5 and Python XLib are required:

sudo apt install python3-pyqt5 python3-xlib

The new minimal working example follows:

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

import sys

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import (
    QApplication,
    QPushButton,
    QToolTip, 
    QWidget,
)
from Xlib.display import Display

def main():
    app = QApplication(sys.argv)
    interface = Interface()
    sys.exit(app.exec_())

class Interface(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowFlags(Qt.WindowStaysOnTopHint)
        self.setWindowFlags(Qt.FramelessWindowHint)
        QToolTip.setFont(QFont('SansSerif', 10))
        self.setToolTip('This is a <b>QWidget</b> widget.')

        button_1 = QPushButton('Button 1', self)
        button_1.setToolTip('This is a <b>QPushButton</b> widget')
        button_1.resize(button_1.sizeHint())
        button_1.move(40, 50) # button width: 80

        button_2 = QPushButton('Button 2', self)
        button_2.setToolTip('This is a <b>QPushButton</b> widget')
        button_2.resize(button_2.sizeHint())
        button_2.move(40, 80) # button width: 80

        button_quit = QPushButton('Quit', self)
        button_quit.setToolTip('Quit the application.')
        button_quit.resize(button_quit.sizeHint())
        button_quit.move(40, 110) # button width: 80
        button_quit.clicked.connect(self.closeEvent)

        self.resize(160, 600)
        self.setWindowTitle('Panel')

        window_identification = self.winId().__int__()
        window_height = self.height()
        window_width = self.width()
        print(f'window identification: {window_identification}')
        print(f'window height: {window_height}, window width: {window_width}')
        window = Window(window_identification)
        window.reserve_space(window_width, 0, 0, 0)
        self.show()

    def closeEvent(self, event):
        sys.exit(0)

class Window(object):
    def __init__(self, window_ID):
        self._display = Display()
        self._window = self._display.create_resource_object('window', window_ID)
    def reserve_space(self, left=0, right=0, top=0, bottom=0):
        LEFT    = int(left)
        RIGHT   = int(right)
        TOP     = int(top)
        BOTTOM  = int(bottom)
        print([LEFT, RIGHT, TOP, BOTTOM])
        self._window.change_property(
            self._display.intern_atom('_NET_WM_STRUT'),
            self._display.intern_atom('CARDINAL'),
            32,
            [LEFT, RIGHT, TOP, BOTTOM])
        self._display.sync()

if __name__ == '__main__':
    main()


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source