You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用PyQt6实现兼容i3wm的独占空间置顶任务栏

问题概述

我想实现一个类似Polybar或i3bar的极简任务栏,需要满足以下要求:

  • 拥有专属空间,不会和其他窗口重叠;
  • 始终显示在屏幕最顶层;
  • 以上特性在i3wm中同样有效。
当前问题

目前已经实现了任务栏始终置顶,但它现在就像个浮动窗口,其他窗口不会自动避开它的专属空间,具体效果见截图。

最小可运行示例

以下是生成当前效果的代码:

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. 设置高度并获取屏幕宽度
        self.setFixedHeight(58)
        screen_geometry = QGuiApplication.primaryScreen().geometry()
        self.setGeometry(QRect(0, 0, screen_geometry.width(), 58))

        # 2. 样式设置
        self.setStyleSheet("background-color: black; color: white;")

        # 3. 布局设置
        layout = QHBoxLayout()
        layout.addWidget(QLabel("Test"))
        layout.setContentsMargins(10, 0, 10, 0)
        self.setLayout(layout)

        # 4. 窗口标志(无装饰、始终置顶)
        self.setWindowFlags(
            Qt.WindowType.FramelessWindowHint |
            Qt.WindowType.Tool |
            Qt.WindowType.X11BypassWindowManagerHint  # 用于跳过窗口管理器的关键设置
        )

#############################################################################
# 核心处理部分                                                             #
#############################################################################
    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()                                          #
                                                                            #
        # 设置窗口类型为DOCK                                                 #
        win.change_property(                                                #
            d.intern_atom('_NET_WM_WINDOW_TYPE'),                           #
            Xatom.ATOM, 32,                                                 #
            [d.intern_atom('_NET_WM_WINDOW_TYPE_DOCK')]                     #
        )                                                                   #
                                                                            #
        # 设置STRUT属性                                                      #
        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())
相关观察

已有多款同类应用(比如Polybar、i3bar),它们之间可以互相兼容,不会重叠,遵循统一的层级规则,这是一种标准行为。

提问

如何让我的任务栏实现和同类应用一致的行为?

解决方案

你的代码核心问题在于使用了Qt.WindowType.X11BypassWindowManagerHint这个窗口标志,它会让窗口完全绕过X11窗口管理器(包括i3wm)的管理,导致窗口管理器无法识别你设置的_NET_WM_WINDOW_TYPE_DOCK和STRUT属性,自然不会为任务栏预留空间。

修改步骤如下:

  1. 移除X11BypassWindowManagerHint标志
    这个标志会让窗口脱离窗口管理器控制,直接去掉它,保留FramelessWindowHint和Tool,并改用WindowStaysOnTopHint确保置顶:

    self.setWindowFlags(
        Qt.WindowType.FramelessWindowHint |
        Qt.WindowType.WindowStaysOnTopHint |
        Qt.WindowType.Tool
    )
    

    注:Tool标志可以让窗口不显示在系统任务栏中,符合任务栏的特性。

  2. 调整属性设置时机
    确保在窗口显示后再设置X11属性,将reserve_space调用移到showEvent中,避免窗口管理器未初始化窗口就接收属性:

    def showEvent(self, event):
        super().showEvent(event)
        self.reserve_space()
    

    主函数中无需手动调用bar.reserve_space(),show()时会自动触发showEvent。

  3. 适配i3wm的额外设置
    i3wm对DOCK窗口的处理需要明确告知不要将其设为浮动,在reserve_space方法中添加:

    # 告诉i3wm窗口保持固定且置顶
    win.change_property(
        d.intern_atom('_NET_WM_STATE'),
        Xatom.ATOM, 32,
        [d.intern_atom('_NET_WM_STATE_STICKY'), d.intern_atom('_NET_WM_STATE_ABOVE')]
    )
    

    同时确保i3配置文件中允许DOCK窗口:

    for_window [window_type="dock"] sticky enable
    for_window [window_type="dock"] border none
    
  4. 验证STRUT属性正确性
    确保_NET_WM_STRUT_PARTIAL的参数覆盖完整屏幕宽度,当前设置是正确的,多屏幕场景需适配对应屏幕尺寸。

修改后的完整代码:

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. 设置高度并获取屏幕宽度
        self.setFixedHeight(58)
        screen_geometry = QGuiApplication.primaryScreen().geometry()
        self.setGeometry(QRect(0, 0, screen_geometry.width(), 58))

        # 2. 样式设置
        self.setStyleSheet("background-color: black; color: white;")

        # 3. 布局设置
        layout = QHBoxLayout()
        layout.addWidget(QLabel("Test"))
        layout.setContentsMargins(10, 0, 10, 0)
        self.setLayout(layout)

        # 4. 窗口标志(无装饰、始终置顶、工具窗口)
        self.setWindowFlags(
            Qt.WindowType.FramelessWindowHint |
            Qt.WindowType.WindowStaysOnTopHint |
            Qt.WindowType.Tool
        )

    def showEvent(self, event):
        super().showEvent(event)
        self.reserve_space()

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

        # 设置窗口类型为DOCK
        win.change_property(
            d.intern_atom('_NET_WM_WINDOW_TYPE'),
            Xatom.ATOM, 32,
            [d.intern_atom('_NET_WM_WINDOW_TYPE_DOCK')]
        )

        # 设置STRUT属性,预留顶部空间
        win.change_property(
            d.intern_atom('_NET_WM_STRUT'),
            Xatom.CARDINAL, 32,
            [0, 0, bar_height, 0]  # left, right, top, bottom
        )

        # 设置STRUT_PARTIAL,精确指定预留区域
        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
            ]
        )

        # 设置窗口状态,确保在i3wm中不浮动且始终置顶
        win.change_property(
            d.intern_atom('_NET_WM_STATE'),
            Xatom.ATOM, 32,
            [d.intern_atom('_NET_WM_STATE_STICKY'), d.intern_atom('_NET_WM_STATE_ABOVE')]
        )

        d.flush()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    bar = StatusBar()
    bar.show()
    sys.exit(app.exec())

内容的提问来源于stack exchange,提问作者fauve

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 23:50:58