【问题标题】:Place a Window behind desktop icons using PyQt on Ubuntu/GNOME在 Ubuntu/GNOME 上使用 PyQt 在桌面图标后面放置一个窗口
【发布时间】:2022-12-03 10:42:34
【问题描述】:

我正在尝试开发一个简单的跨平台壁纸管理器,但我无法找到任何方法使用 XLib 将我的 PyQt 窗口放置在当前壁纸和桌面图标之间(在 Windows 和 macOS 上它更容易并且工作完美) .

这可以在 Cinnamon 上正常工作(只需模拟点击一下即可),但在 GNOME 上不行。任何人都可以帮助或给我任何线索吗? (我提供所有这些代码只是为了提供最小的可执行部分,但我想关键部分就在“如果“GNOME”...”这句话之后)

import os
import time

import Xlib
import ewmh
import pywinctl
from pynput import mouse

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


def sendBehind(hWnd):

        w = DISP.create_resource_object('window', hWnd)
        w.change_property(DISP.intern_atom('_NET_WM_STATE', False), Xlib.Xatom.ATOM, 32, [DISP.intern_atom('_NET_WM_STATE_BELOW', False), ], Xlib.X.PropModeReplace)
        w.change_property(DISP.intern_atom('_NET_WM_STATE', False), Xlib.Xatom.ATOM, 32, [DISP.intern_atom('_NET_WM_STATE_SKIP_TASKBAR', False), ], Xlib.X.PropModeAppend)
        w.change_property(DISP.intern_atom('_NET_WM_STATE', False), Xlib.Xatom.ATOM, 32, [DISP.intern_atom('_NET_WM_STATE_SKIP_PAGER', False), ], Xlib.X.PropModeAppend)
        DISP.flush()

        # This sends window below all others, but not behind the desktop icons
        w.change_property(DISP.intern_atom('_NET_WM_WINDOW_TYPE', False), Xlib.Xatom.ATOM, 32, [DISP.intern_atom('_NET_WM_WINDOW_TYPE_DESKTOP', False), ],Xlib.X.PropModeReplace)
        DISP.flush()

        if "GNOME" in os.environ.get('XDG_CURRENT_DESKTOP', ""):
            # This sends the window "too far behind" (below all others, including Wallpaper, like unmapped)
            # Trying to figure out how to raise it on top of wallpaper but behind desktop icons
            desktop = _xlibGetAllWindows(title="gnome-shell")
            if desktop:
                w.reparent(desktop[-1], 0, 0)
                DISP.flush()
        else:
            # Mint/Cinnamon: just clicking on the desktop, it raises, sending the window/wallpaper to the bottom!
            m = mouse.Controller()
            m.move(SCREEN.width_in_pixels - 1, 100)
            m.click(mouse.Button.left, 1)

        return '_NET_WM_WINDOW_TYPE_DESKTOP' in EWMH.getWmWindowType(hWnd, str=True)


def bringBack(hWnd, parent):
    w = DISP.create_resource_object('window', hWnd)

    if parent:
        w.reparent(parent, 0, 0)
        DISP.flush()

    w.unmap()
    w.change_property(DISP.intern_atom('_NET_WM_WINDOW_TYPE', False), Xlib.Xatom.ATOM,
                      32, [DISP.intern_atom('_NET_WM_WINDOW_TYPE_NORMAL', False), ],
                      Xlib.X.PropModeReplace)
    DISP.flush()
    w.change_property(DISP.intern_atom('_NET_WM_STATE', False), Xlib.Xatom.ATOM,
                      32, [DISP.intern_atom('_NET_WM_STATE_FOCUSED', False), ],
                      Xlib.X.PropModeReplace)
    DISP.flush()
    w.map()
    EWMH.setActiveWindow(hWnd)
    EWMH.display.flush()
    return '_NET_WM_WINDOW_TYPE_NORMAL' in EWMH.getWmWindowType(hWnd, str=True)


def _xlibGetAllWindows(parent: int = None, title: str = ""):

    if not parent:
        parent = ROOT
    allWindows = [parent]

    def findit(hwnd):
        query = hwnd.query_tree()
        for child in query.children:
            allWindows.append(child)
            findit(child)

    findit(parent)
    if not title:
        matches = allWindows
    else:
        matches = []
        for w in allWindows:
            if w.get_wm_name() == title:
                matches.append(w)
    return matches


hWnd = pywinctl.getActiveWindow()
parent = hWnd._hWnd.query_tree().parent
sendBehind(hWnd._hWnd)
time.sleep(3)
bringBack(hWnd._hWnd, parent)

【问题讨论】:

  • 所以你是说你可以在壁纸和 Windows 中的桌面图标之间放置一个窗口?
  • 确切地。在 windows、macOs 和 cinnamon 上,但在 GNOME 中不行。有兴趣的可以看看:github.com/Kalmat/PyWinCtl
  • 在我的 Ubuntu 上,这会立即关闭我关注的窗口;您可能已经警告过我们 :-)。另外,你忘了import os
  • @Kalma 太酷了!感谢您的链接 :)
  • 所以,为了澄清起见,您希望您的窗口看起来像this,它在桌面上的图标后面,但在墙纸前面? (该图像经过编辑以进行演示;不幸的是我没有让您的代码正常工作)。

标签: python ubuntu pyqt5 gnome xlib


【解决方案1】:

尤里卡!!!最新的 Ubuntu 版本 (22.04) 似乎已经自行解决了这个问题。它现在有一个可以与之交互的桌面图标“层”。这也为我提供了在 Mint/Cinnamon 上找到更智能解决方案的线索(在其他操作系统中的测试仍在进行中)。对于那些有同样问题的人来说,这是似乎工作正常的代码:

import time

import Xlib
import ewmh
import pywinctl

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


def sendBehind(hWnd):
    w = DISP.create_resource_object('window', hWnd.id)
    w.unmap()
    w.change_property(DISP.intern_atom('_NET_WM_WINDOW_TYPE', False), Xlib.Xatom.ATOM,
                      32, [DISP.intern_atom('_NET_WM_WINDOW_TYPE_DESKTOP', False), ],
                      Xlib.X.PropModeReplace)
    DISP.flush()
    w.map()

    # This will try to raise the desktop icons layer on top of the window
    # Ubuntu: "@!0,0;BDHF" is the new desktop icons NG extension
    # Mint: "Desktop" name is language-dependent. Using its class (nemo-desktop)
    desktop = _xlibGetAllWindows(title="@!0,0;BDHF", klass=('nemo-desktop', 'Nemo-desktop'))
    for d in desktop:
        w = DISP.create_resource_object('window', d)
        w.raise_window()

    return '_NET_WM_WINDOW_TYPE_DESKTOP' in EWMH.getWmWindowType(hWnd, str=True)


def bringBack(hWnd):

    w = DISP.create_resource_object('window', hWnd.id)

    w.unmap()
    w.change_property(DISP.intern_atom('_NET_WM_WINDOW_TYPE', False), Xlib.Xatom.ATOM,
                      32, [DISP.intern_atom('_NET_WM_WINDOW_TYPE_NORMAL', False), ],
                      Xlib.X.PropModeReplace)
    DISP.flush()
    w.change_property(DISP.intern_atom('_NET_WM_STATE', False), Xlib.Xatom.ATOM,
                      32, [DISP.intern_atom('_NET_WM_STATE_FOCUSED', False), ],
                      Xlib.X.PropModeReplace)
    DISP.flush()
    w.map()
    EWMH.setActiveWindow(hWnd)
    EWMH.display.flush()
    return '_NET_WM_WINDOW_TYPE_NORMAL' in EWMH.getWmWindowType(hWnd, str=True)


def _xlibGetAllWindows(parent=None, title: str = "", klass=None):

    parent = parent or ROOT
    allWindows = [parent]

    def findit(hwnd):
        query = hwnd.query_tree()
        for child in query.children:
            allWindows.append(child)
            findit(child)

    findit(parent)
    if not title and not klass:
        return allWindows
    else:
        return [window for window in allWindows if ((title and window.get_wm_name() == title) or
                                                    (klass and window.get_wm_class() == klass))]


hWnd = pywinctl.getActiveWindow()
sendBehind(hWnd._hWnd)
time.sleep(3)
bringBack(hWnd._hWnd)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-16
    相关资源
    最近更新 更多