如何将应用程序(及其所有新窗口)锁定到特定的工作区?

如何将应用程序(及其所有新窗口)锁定到特定的工作区?

我在工作区 1。这会生成几个图。同时我切换到工作区 2并在那里工作。我的问题是情节突然出现在工作区 2

是否可以将软件锁定在特定的工作区上,这样当 MATLAB 生成工作区 1,我可以工作工作区 2不会受到突然出现的情节的干扰吗?

答案1

重要编辑

下面是第一个答案中脚本的重写版本(如下)。区别如下:

  • 现在脚本的资源极低(就像后台脚本一样)。现在安排操作在需要时(且仅在需要时)执行。循环实际上什么也不做,只是检查是否有新窗口出现。
  • 机器人WM_CLASS和目标工作区现在参数运行脚本。仅使用第一部分或第二部分(标识部分)WM_CLASS(请参阅下文:如何使用)
  • 脚本现在将焦点放在当前活动窗口上(实际上在一瞬间重新聚焦)
  • 当脚本启动时,它会显示一条通知(示例gedit):

    在此处输入图片描述

剧本

#!/usr/bin/env python3
import subprocess
import sys
import time
import math

app_class = sys.argv[1]
ws_lock = [int(n)-1 for n in sys.argv[2].split(",")]

def check_wlist():
    # get the current list of windows
    try:
        raw_list = [
            l.split() for l in subprocess.check_output(
                ["wmctrl", "-lG"]
                ).decode("utf-8").splitlines()
            ]
        ids = [l[0] for l in raw_list]
        return (raw_list, ids)
    except subprocess.CalledProcessError:
        pass

def get_wssize():
    # get workspace size
    resdata = subprocess.check_output(["xrandr"]).decode("utf-8").split()
    i = resdata.index("current")
    return [int(n) for n in [resdata[i+1], resdata[i+3].replace(",", "")]]

def get_current(ws_size):
    # vector of the current workspace to origin of the spanning desktop
    dt_data = subprocess.check_output(
        ["wmctrl", "-d"]
        ).decode("utf-8").split()
    curr = [int(n) for n in dt_data[5].split(",")]
    return (int(curr[0]/ws_size[0]), int(curr[1]/ws_size[1]))

def get_relativewinpos(ws_size, w_data):
    # vector to the application window, relative to the current workspace
    xpos = int(w_data[2]); ypos = int(w_data[3])
    xw = ws_size[0]; yw = ws_size[1]
    return (math.ceil((xpos-xw)/xw), math.ceil((ypos-yw)/yw))

def get_abswindowpos(ws_size, w_data):
    # vector from the origin to the current window's workspace (flipped y-axis)
    curr_pos = get_current(ws_size)
    w_pos = get_relativewinpos(ws_size, w_data)
    return (curr_pos[0]+w_pos[0], curr_pos[1]+w_pos[1])

def wm_class(w_id):
    # get the WM_CLASS of new windows
    return subprocess.check_output(
        ["xprop", "-id", w_id.strip(), "WM_CLASS"]
        ).decode("utf-8").split("=")[-1].strip()

ws_size = get_wssize()
wlist1 = []
subprocess.Popen(["notify-send", 'workspace lock is running for '+app_class])

while True:
    # check focussed window ('except' for errors during "wild" workspace change)
    try:
        focus = subprocess.check_output(
            ["xdotool", "getwindowfocus"]
            ).decode("utf-8")
    except subprocess.CalledProcessError:
        pass
    time.sleep(1)
    wdata = check_wlist() 
    if wdata !=  None:
        # compare existing window- ids, checking for new ones
        wlist2 = wdata[1]
        if wlist2 != wlist1:
            # if so, check the new window's class
            newlist = [[w, wm_class(w)] for w in wlist2 if not w in wlist1]
            valids = sum([[l for l in wdata[0] if l[0] == w[0]] \
                          for w in newlist if app_class in w[1]], [])
            # for matching windows, check if they need to be moved (check workspace)
            for w in valids:
                abspos = list(get_abswindowpos(ws_size, w))
                if not abspos == ws_lock:
                    current = get_current(ws_size)
                    move = (
                        (ws_lock[0]-current[0])*ws_size[0],
                            (ws_lock[1]-current[1])*ws_size[1]-56
                        )
                    new_w = "wmctrl -ir "+w[0]+" -e "+(",").join(
                        ["0", str(int(w[2])+move[0]),
                         str(int(w[2])+move[1]), w[4], w[5]]
                        )
                    subprocess.call(["/bin/bash", "-c", new_w])
                    # re- focus on the window that was focussed
                    if not app_class in wm_class(focus):
                        subprocess.Popen(["wmctrl", "-ia", focus])
        wlist1 = wlist2

如何使用

  1. 该脚本需要wmctrlxdotool

    sudo apt-get install wmctrl xdotool
    
  2. 将上述脚本复制到一个空文件中,另存为lock_towspace.py

  3. 在您的特定应用程序中,找出WM_CLASS:打开您的应用程序,在终端中运行:

    xprop WM_CLASS and click on the window of the application
    

    输出将如下所示(就您而言):

    WM_CLASS: WM_CLASS(STRING) = "sun-awt-X11-XFramePeer", "MATLAB R2015a - academic use"
    

    使用命令的第一部分或第二部分来运行脚本。

  4. 运行脚本的命令是:

    python3 /path/to/lock_towspace.py "sun-awt-X11-XFramePeer" 2,2
    

    在命令中,最后一部分;2,2是要锁定应用程序的工作区(不带空格:(!)列,行)以“人”的格式;第一列/行是1,1

  5. 通过运行脚本来测试它。运行时,打开应用程序并让它像往常一样生成窗口。所有窗口都应出现在目标工作区中,如命令中设置的那样。

过时的答案:

(第二个)测试版本

下面的脚本锁定具体应用到其初始工作区。如果脚本已启动,它将确定应用程序驻留在哪个工作区。应用程序生成的所有其他窗口将在瞬间移动到同一工作区。

通过自动重新聚焦在生成附加窗口之前已聚焦的窗口来解决焦点问题。

剧本

#!/usr/bin/env python3
import subprocess
import time
import math

app_class = '"gedit", "Gedit"'

def get_wssize():
    # get workspace size
    resdata = subprocess.check_output(["xrandr"]).decode("utf-8").split()
    i = resdata.index("current")
    return [int(n) for n in [resdata[i+1], resdata[i+3].replace(",", "")]]

def get_current(ws_size):
    # get vector of the current workspace to the origin of the spanning desktop (flipped y-axis)
    dt_data = subprocess.check_output(["wmctrl", "-d"]).decode("utf-8").split(); curr = [int(n) for n in dt_data[5].split(",")]
    return (int(curr[0]/ws_size[0]), int(curr[1]/ws_size[1]))

def get_relativewinpos(ws_size, w_data):
    # vector to the application window, relative to the current workspace
    xw = ws_size[0]; yw = ws_size[1]
    return (math.ceil((w_data[1]-xw)/xw), math.ceil((w_data[2]-yw)/yw))

def get_abswindowpos(ws_size, w_data):
    curr_pos = get_current(ws_size)
    w_pos = get_relativewinpos(ws_size, w_data)
    return (curr_pos[0]+w_pos[0], curr_pos[1]+w_pos[1])

def wm_class(w_id):
    return subprocess.check_output(["xprop", "-id", w_id, "WM_CLASS"]).decode("utf-8").split("=")[-1].strip()

def filter_windows(app_class):
    # find windows (id, x_pos, y_pos) of app_class
    try:
        raw_list = [l.split() for l in subprocess.check_output(["wmctrl", "-lG"]).decode("utf-8").splitlines()]
        return [(l[0], int(l[2]), int(l[3]), l[4], l[5]) for l in raw_list if wm_class(l[0]) == app_class]
    except subprocess.CalledProcessError:
        pass

ws_size = get_wssize()
init_window = get_abswindowpos(ws_size, filter_windows(app_class)[0])
valid_windows1 = filter_windows(app_class)

while True:
    focus = subprocess.check_output(["xdotool", "getwindowfocus"]).decode("utf-8")
    time.sleep(1)
    valid_windows2 = filter_windows(app_class)
    if all([valid_windows2 != None, valid_windows2 != valid_windows1]):
        for t in [t for t in valid_windows2 if not t[0] in [w[0] for w in valid_windows1]]:
            absolute = get_abswindowpos(ws_size, t)
            if not absolute == init_window:
                current = get_current(ws_size)
                move = ((init_window[0]-current[0])*ws_size[0], (init_window[1]-current[1])*ws_size[1]-56)
                new_w = "wmctrl -ir "+t[0]+" -e "+(",").join(["0", str(t[1]+move[0]), str(t[2]+move[1]), t[3], t[4]])
                subprocess.call(["/bin/bash", "-c", new_w])
            focus = str(hex(int(focus)))
            z = 10-len(focus); focus = focus[:2]+z*"0"+focus[2:]
            if not wm_class(focus) == app_class:
                subprocess.Popen(["wmctrl", "-ia", focus])
        valid_windows1 = valid_windows2

如何使用

  1. 脚本wmctrl需要xdotool

    sudo apt-get install wmctrl xdotool
    
  2. 将脚本复制到一个空文件中,另存为keep_workspace.py

  3. 通过打开应用程序确定应用程序的“WM_CLASS”,然后打开终端并运行命令:

    xprop WM_CLASS
    

    然后点击应用程序窗口。复制输出(如"sun-awt-X11-XFramePeer", "MATLAB R2015a - academic use"您的情况所示),并将其放在单身的脚本头部的引号,如图所示。

  4. 使用以下命令运行脚本:

    python3 /path/to/keep_workspace.py
    

如果效果如您所愿,我会添加一个切换功能。虽然它在我的系统上已经运行了几个小时,但可能首先需要进行一些调整。

笔记

虽然你不应该注意到它,但脚本为系统增加一些处理器负载。在我的旧系统上,我注意到负载增加了 3-10%。如果您喜欢它的工作方式,我可能会进一步调整它以减少负载。

脚本本身假设辅助窗口与主窗口属于同一类,就像您在评论中指出的那样。通过(非常)简单的更改,辅助窗口但属于另一个类别。

解释

虽然对于普通读者来说可能不是很有趣,但该脚本是通过向量计算来工作的。启动时,脚本会计算:

  • 从原点到当前工作空间的矢量,输出为wmctrl -d
  • 相对于当前工作区的应用程序窗口的向量,由输出wmctrl -lG
  • 根据这两个,脚本计算出绝对应用程序窗口在跨桌面上的位置(一个矩阵中的所有工作区)

从那时起,脚本将查找同一应用程序的新窗口,并使用的输出xprop WM_CLASS,以与上述相同的方式查找它们的位置并将它们移动到“原始”工作区。

由于新创建的窗口从用户上次使用的窗口中“窃取”了焦点,因此焦点随后被设置为之前具有焦点的窗口。

答案2

我相信这个问题几年前就解决了,但我还是忍不住有点讽刺。Jacob 的回答非常有帮助和慷慨;python 脚本非常有用,我真的很欣赏他在回答上所做的工作。但在问题中,这完全是小题大做,因为最简单的解决方案是告诉 Matlab 您想要创建绘图而不进行实际绘制。这是通过在 Matlab 中创建带有绘图的图形时将值图形对象的属性“Visible”设置为“off”来实现的……例如:

% this  simply opens a pop-up window with an empty figure
figure(); 
% then different stuff is done like plotting, setting axes, lines widths adjusting, setting colors, etc.

%%%%%
% In this case the Matlab Editor won't show the figure in a window. All graphical job is done virtually

figure('Visible', 'off');

% alternatively,
fig = figure();
fig.Visible = 'off';

% or by using a set() function
set(fig, 'Visible', 'off'); 

图形可以保存为常见类型(例如 jpeg)的图像。如果不需要保存,fig.Visible = 'on'; 当代码运行到最后时,可以将此 Visible 属性设置回默认,图形就会出现。但是当需要批量生成一堆图片时,这是在代码运行时使用计算机的唯一方法。

相关内容