I try to set just a minimal taskbar (like Polybar or i3bar, or any task bar you know).
The constraint are the folowing ones:
For the moment, I could make it always on top of the screen (constraint 1), but it jut like floating window here, other windows don’t see it space as dedicate (constraint 2), as you can see in the following screenshot:
And this is the code who produced the above rendering:
import sys
from PyQt6.QtWidgets import QApplication, QWidget, QHBoxLayout, QLabel
from PyQt6.QtCore import Qt, QRect
from PyQt6.QtGui import QGuiApplication
from Xlib import display, Xatom, X
class StatusBar(QWidget):
def __init__(self):
super().__init__()
# 1. Set height and get screen width
self.setFixedHeight(58)
screen_geometry = QGuiApplication.primaryScreen().geometry()
self.setGeometry(QRect(0, 0, screen_geometry.width(), 58))
# 2. Style
self.setStyleSheet("background-color: black; color: white;")
# 3. Layout
layout = QHBoxLayout()
layout.addWidget(QLabel("Test"))
layout.setContentsMargins(10, 0, 10, 0)
self.setLayout(layout)
# 4. Window flags (no decoration, always on top)
self.setWindowFlags(
Qt.WindowType.FramelessWindowHint |
Qt.WindowType.Tool |
Qt.WindowType.X11BypassWindowManagerHint # Important for override redirect
)
#############################################################################
# The accurate part #
#############################################################################
def reserve_space(self): #
d = display.Display() #
wid = int(self.winId()) #
win = d.create_resource_object('window', wid) #
#
screen_width = QGuiApplication.primaryScreen().geometry().width() #
bar_height = self.height() #
#
# Set window type to DOCK #
win.change_property( #
d.intern_atom('_NET_WM_WINDOW_TYPE'), #
Xatom.ATOM, 32, #
[d.intern_atom('_NET_WM_WINDOW_TYPE_DOCK')] #
) #
#
# Set struts #
win.change_property( #
d.intern_atom('_NET_WM_STRUT'), #
Xatom.CARDINAL, 32, #
[0, 0, bar_height, 0] # left, right, top, bottom #
) #
#
win.change_property( #
d.intern_atom('_NET_WM_STRUT_PARTIAL'), #
Xatom.CARDINAL, 32, #
[ #
0, 0, bar_height, 0, # left, right, top, bottom #
0, 0, # left_start_y, left_end_y #
0, 0, # right_start_y, right_end_y #
0, screen_width - 1, # top_start_x, top_end_x #
0, 0 # bottom_start_x, bottom_end_x #
] #
) #
#
d.flush() #
#############################################################################
if __name__ == "__main__":
app = QApplication(sys.argv)
bar = StatusBar()
bar.show()
bar.reserve_space()
sys.exit(app.exec())
Lot of applications witch have this behavior exist, like Polybar or i3bar as mentioned before. They also work very fine, if I run two of this kind of taskbar, they don’t overlap, they respect each other position and the last one come under the previous one. So it seems to be a stand.
How to get a behavior similar to the other taskbars?
After many tries, I find the following working examples.
import sys
from PyQt6.QtWidgets import QApplication, QLabel, QWidget, QHBoxLayout
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QGuiApplication
from Xlib import display, X, Xatom
class TopBar(QWidget):
def __init__(self):
super().__init__()
# Screen Size
screen_geometry = QGuiApplication.primaryScreen().geometry()
screen_width = screen_geometry.width()
self.setFixedSize(screen_width, 50)
self.move(0, 0)
# Some contents
self.setStyleSheet("background-color: #222; color: white; font-size: 20px;")
layout = QHBoxLayout()
label = QLabel("Test")
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(label)
self.setLayout(layout)
# Panelization
self.setWindowFlags(
Qt.WindowType.FramelessWindowHint |
Qt.WindowType.WindowStaysOnTopHint | # <- One of the most important lines
Qt.WindowType.Tool
)
# Set
self.reserve_space()
def reserve_space(self):
d = display.Display()
root = d.screen().root
window_id = self.winId().__int__()
NET_WM_STRUT_PARTIAL = d.intern_atom('_NET_WM_STRUT_PARTIAL')
NET_WM_STRUT = d.intern_atom('_NET_WM_STRUT')
NET_WM_WINDOW_TYPE = d.intern_atom('_NET_WM_WINDOW_TYPE')
NET_WM_WINDOW_TYPE_DOCK = d.intern_atom('_NET_WM_WINDOW_TYPE_DOCK')
# Retrive the size to reserve it
width = self.width()
height = self.height()
top = height # reserv the height of the bar
strut = [0, 0, top, 0]
strut_partial = [0, 0, top, 0, 0, width, 0, 0, 0, 0, 0, 0]
root.change_property(NET_WM_STRUT, Xatom.CARDINAL, 32, strut)
root.change_property(NET_WM_STRUT_PARTIAL, Xatom.CARDINAL, 32, strut_partial)
window = d.create_resource_object('window', window_id)
window.change_property(NET_WM_WINDOW_TYPE, Xatom.ATOM, 32, [NET_WM_WINDOW_TYPE_DOCK])
d.sync()
if __name__ == "__main__":
app = QApplication(sys.argv)
bar = TopBar()
bar.show()
sys.exit(app.exec())
I tried quickly to compare it with the original one to see what is the main change, but I didn’t really find. It seems that Qt.WindowType.WindowStaysOnTopHint changed many things.