![通过监视器旋转焦点](https://linux22.com/image/877142/%E9%80%9A%E8%BF%87%E7%9B%91%E8%A7%86%E5%99%A8%E6%97%8B%E8%BD%AC%E7%84%A6%E7%82%B9.png)
我的开发机器上有 3 个显示器,运行的是 Ubuntu + Unity。我通常将一个显示器用于网页浏览和 IM,将另外两个显示器用于运行 vim 或输入命令的终端。我正在寻找一个可以随意在它们之间切换的单键序列。
我想知道如何将焦点切换到每个显示器上的(顶部)窗口。我正在寻找的行为有点像 ALT+TAB,除了在应用程序之间旋转(这只会使最近使用的应用程序实例在旋转中可用)之外,我可以在显示器之间旋转焦点。
作为折衷,如果我可以将每个窗口都包含在列表中,我可以忍受使用 ALT+TAB 机制。不过,我仍然认为这会很烦人。
答案1
通过监视器旋转焦点的脚本
在下面的设置中,二涉及脚本:一个后台脚本用于跟踪聚焦窗口的历史记录(请参阅底部的解释以了解为什么需要这样做),以及一个放置在快捷键下的“操作”脚本,用于将焦点设置在下一个屏幕上。如果下一个屏幕当前没有要设置焦点的窗口,则会显示一条消息:
脚本
脚本 1;背景脚本,将其(精确)保存为focus_track.py
#!/usr/bin/env python3
import subprocess
import time
import os
rootdata = os.environ["HOME"]+"/.focus_history"
def get_screendata():
return sorted([int(s.split("+")[-2]) for s in subprocess.check_output(["xrandr"]).decode("utf-8").split() if s.count("+") == 2])
def current_windows():
try:
return subprocess.check_output(["wmctrl", "-lG"]).decode("utf-8")
except subprocess.CalledProcessError:
pass
def convert_format(w_id):
return w_id[:2]+(10-len(w_id))*"0"+w_id[2:]
def read_data():
return open(rootdata).read().splitlines()
def get_top(wlist):
top = convert_format([l.split("#")[-1].strip() for l in \
subprocess.check_output(["xprop", "-root"]).decode("utf-8").splitlines() \
if "_NET_ACTIVE_WINDOW(WINDOW)" in l][0])
return [l for l in wlist if top in l][0]
if __name__ == "__main__":
open(rootdata, "wt").write("This is an empty line")
while True:
time.sleep(1)
wdata = current_windows()
if wdata != None:
wlist = wdata.splitlines()
# get frontmost window (as in wmctrl -lG)
top = get_top(wlist)
oldlist = read_data()
if not top == oldlist[0]:
# clean up closed windows
[oldlist.remove(l) for l in oldlist if not l.split()[0] in wdata]
# remove possible other mentions of the active window
[oldlist.remove(l) for l in oldlist if l.startswith(top.split()[0])]
open(rootdata, "wt").write(("\n").join([top]+oldlist))
脚本2;动作脚本,另存为next_focus.py
在同一目录中 脚本 1。
#!/usr/bin/env python3
import subprocess
import focus_track
# read existing windows and their order (x-wise) from file
windows = [w.split() for w in focus_track.read_data()]
w_data = [[w[0], int(w[2])] for w in windows]
# get position of currently focussed window
currfocus = focus_track.get_top(focus_track.current_windows().splitlines()).split()[2]
# get screendata
screens = focus_track.get_screendata()
def screen_pos(x):
return [(int(x) > n) for n in screens].count(True)
scr_position = screen_pos(currfocus)
next_screen = 1 if scr_position == len(screens) else scr_position+1
try:
next_focus = [w for w in w_data if screen_pos(w[1]) == next_screen][0]
subprocess.Popen(["wmctrl", "-ia", next_focus[0]])
except IndexError:
subprocess.Popen(["notify-send", "No window to focus on next screen"])
如何使用
wmctrl
需要安装脚本sudo apt-get install wmctrl
- 将脚本 1 复制到一个空文件中,将其另存为 (exactly)
focus_track.py
。名称很重要,因为两个脚本共享功能;脚本 1 是进口进入脚本 2。 - 将脚本 2 复制到一个空文件中,保存为(准确)
next_focus.py
与脚本 1 位于同一目录中。 测试运行设置:注意:在打开(并因此聚焦)窗口之前启动后台脚本。在后台脚本启动之前打开的窗口在获得焦点之前不会被“记录”
使用以下命令启动后台脚本(例如从终端):
python3 /path/to/focus_track.py
在不同的屏幕上打开窗口。
使用以下命令运行脚本 2:
python3 /path/to/next_focus.py
焦点应切换到下一个屏幕。如果当前屏幕是行中的最后一个屏幕,则焦点将切换到第一个屏幕。
如果一切正常,将脚本 1 添加到启动应用程序:Dash > 启动应用程序 > 添加命令:
python3 /path/to/focus_track.py
并将脚本 2 添加到键盘快捷键:选择:系统设置 > “键盘” > “快捷键” > “自定义快捷键”。单击“+”并添加命令:
python3 /path/to/next_focus.py
更改为您喜欢的快捷键。
笔记
- 由于后台脚本会跟踪焦点历史,为了立即正常运行,最好启动前您开始使用计算机工作,例如登录(使用启动应用程序)。
- 脚本假设屏幕从左到右设置,不重叠。但它们的垂直对齐无关紧要。
- 为了“节省燃料”,并使脚本的负载可以忽略不计,脚本每秒仅更新一次焦点历史记录。因此,窗口定义为专注如果它关注的是至少0.5至1秒之间。
- 虽然我是在双屏设置上测试的,但它应该(并且我很确定它可以)在 3 (+) 个屏幕的设置上正常工作。
解释:
需要什么
要将焦点从一个屏幕切换到另一个屏幕,需要确定哪个是最前面的窗口每屏幕。然而,主要问题是,分布在多个屏幕上的窗口实际上都在同一个堆栈中,因此以相同的顺序排列(z 方向)。我们拥有的工具(wmctrl
、xdotool
等xprop
)在最好的情况下只能确定目前活跃窗口。它们没有给我们任何有关其他屏幕上窗口顺序的信息,因为窗口是活动窗口下方。
因此,乍一看,将焦点从一个屏幕切换到另一个屏幕似乎是不可能的。
然而:
如何获取信息
然而,有一个解决方法:如果我们让后台脚本跟踪当前聚焦的窗口,并维护更改的历史记录(只要窗口存在),我们实际上可以得出结论当前打开的窗口的 z 顺序是什么。如果我们还跟踪它们的几何形状和位置,我们就拥有了所需的所有信息。
一个例子:
我们目前有五个窗口:A、B、C、D、E。如果它们的焦点通过 D、E、A、C、B 改变,我们知道窗口的 z 顺序是:B、C、A、E、D(从前到后)
结合它们的位置(x 方向)和屏幕数据(屏幕的 x 分辨率),我们就拥有了所需的所有信息。要将焦点切换到下一个屏幕,我们只需查找下一个屏幕上的第一个窗口即可。