战略

战略

所以我有两个显示器,主显示器在右侧(由于 HDMI 电缆长度),辅助显示器在左侧。我启用了“仅在主显示器上切换工作区”选项。

问题是,当我切换到另一个工作区时,辅助显示器上的窗口会窃取焦点,直到我手动 alt tab 到必要的窗口,然后再次切换到该工作区。

问题是:切换到任何工作区后,如何使 GNOME 焦点窗口(即左上角)位于主显示屏上?

答案1

先决条件

  • xdotool安装在您的系统中
    • 您可能需要在 Xorg (X11) 中运行您的 gnome 系统,因为此工具在 Wayland 中不起作用
  • python3安装在您的系统中
    • 我不需要安装任何软件包,pip因为这些模块大多是内置的,并且随 python 一起提供

战略

  1. 创建一个脚本activewindow每次桌面/工作区发生变化时都会强制进行新的操作。
  2. 自动运行此脚本以在启动时运行

Gnome Focus 自动化脚本

#!/usr/bin/env python3
import os
import time
import subprocess
import atexit
import signal
import sys

# DESCRIPTION:
#
# Checks out every "REFRESH_RATE" seconds if the workspace/desktop changed
# If so, correct the focus to the last screen you were focusing in that workspace/desktop
#
# In the case that we have no history of you focusing a screen in the current desktop,
# attempt to focus on the topmost window by searching and filtering all the windows
# in the current desktop
#

# TWEAK VARIABLES
REFRESH_RATE = 0.01  # seconds
LOCKFILE = "/tmp/gnome_focus_automation_lockfile"

history_desktop_last_active_window = {}


def main():
    global history_desktop_last_active_window

    # This SCRIPT can't run duplicated on our system
    # So let's create a lockfile to prevent that
    create_lockfile_or_quit()

    current_desktop = get_desktop()
    while True:
        time.sleep(REFRESH_RATE)

        update_window_history()

        previous_desktop = current_desktop
        current_desktop = get_desktop()
        did_desktop_change = current_desktop != previous_desktop

        if did_desktop_change:
            focus_best_window(current_desktop)


def update_window_history():
    global history_desktop_last_active_window

    active_window = get_active_window()
    active_w_desktop = get_desktop_for_window(active_window)

    if active_w_desktop != "":
        history_desktop_last_active_window[active_w_desktop] = active_window


def focus_best_window(current_desktop):
    global history_desktop_last_active_window

    if current_desktop in history_desktop_last_active_window:
        target_window = history_desktop_last_active_window[current_desktop]
        is_target_valid = get_desktop_for_window(target_window) == current_desktop

        if is_target_valid:
            did_history_focus_succeded = try_activate_window(target_window)
        else:
            did_history_focus_succeded = False
    else:
        did_history_focus_succeded = False

    if not did_history_focus_succeded:
        # Fallback Focus
        topmost_window = unreliably_get_topmost_window_of_desktop(current_desktop)
        if topmost_window != "":
            try_activate_window(topmost_window)


# ----------------------------------------
# LOCKFILE helper functions


# Make sure the lock file is removed when we exit and when we receive a signal
def cleanup():
    os.unlink(LOCKFILE)


# If lockfile exists, then exit
def create_lockfile_or_quit():
    if os.path.exists(LOCKFILE):
        pid = open(LOCKFILE).read()
        try:
            os.kill(int(pid), 0)
            print("Already running...")
            print("")
            print("If you are sure that's not the case you can manually delete")
            print(f"the lockfile located at {LOCKFILE} and try again.")
            sys.exit()
        except OSError:
            pass

    atexit.register(cleanup)
    signal.signal(signal.SIGINT, lambda signal, frame: sys.exit(0))
    signal.signal(signal.SIGTERM, lambda signal, frame: sys.exit(0))

    with open(LOCKFILE, "w") as f:
        f.write(str(os.getpid()))


# ---------------------------------------------
# xdotool helper FUNCTIONS


def run_command_that_might_fail(command, fail_return_value):
    try:
        process = subprocess.Popen(
            command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL
        )
        stdout, _ = process.communicate()
        return stdout.decode().strip()
    except subprocess.CalledProcessError:
        return fail_return_value


def get_desktop():
    return run_command_that_might_fail(["xdotool", "get_desktop"], fail_return_value=-1)


def get_active_window():
    return run_command_that_might_fail(
        ["xdotool", "getactivewindow"], fail_return_value=-1
    )


def get_desktop_for_window(window):
    return run_command_that_might_fail(
        ["xdotool", "get_desktop_for_window", window], fail_return_value=""
    )


def unreliably_get_topmost_window_of_desktop(desktop):
    # The output from "xdotool search" is reverse-ordered according to how windows appear on the screen
    # So getting the last window from the current desktop is the way to go
    # This sometimes doesn't work and a window that is on the bottom appears on the top
    return (
        run_command_that_might_fail(
            ["xdotool", "search", "--desktop", str(desktop), "."], fail_return_value=""
        )
        .strip()
        .split("\n")[-1]
    )


def try_activate_window(window):
    output = run_command_that_might_fail(
        ["xdotool", "windowactivate", window], fail_return_value=-1
    )
    # Boolean return
    return output != -1


if __name__ == "__main__":
    main()

将此脚本保存在系统中的某个位置,并使用以下命令使其可执行

chmod +x {path_to_your_script}

现在您只需让它在您登录时自动启动即可。为此,请创建一个gnome_focus_automation.desktop位于的文件,~/.config/autostart其内容如下:

[Desktop Entry]
Type=Application
Exec=/home/username/path/to/your/script.py
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
Name=GnomeFocusAutomation
Comment=Everytime the workspace changes, the focus is corrected to the topmost window

重要的/home/username/path/to/your/script.py用脚本的完整路径替换。不要将路径替换为~类似~/path/to/your/script.py

完毕!

您应该注销并重新登录才能查看效果。

改进空间

目前,我的脚本每 0.01 秒检查一次工作区是否发生变化并采取相应措施...在我的实验中,这个数字对我来说很好,但我知道按时间间隔做出反应并不理想,最好在系统中进行回调以触发操作。

归根结底,这是一个“黑客”解决方案。如果有人有更优雅的解决方案,请评论...

答案2

关于窗口焦点:“所有显示器上的工作区”将焦点设置在我通过键盘或鼠标切换到的工作区上。我使用gnome 42.4并且x11与您有相同的主要右侧设置。

“仅在主显示器上显示工作区”在不同显示器上的表现与我想象的不同。Multi-Monitor和的设置Application Switching相互依赖,经过反复尝试后,它才按预期工作。

设置...多任务...多显示器和应用程序切换

相关内容