是否有一个脚本(或软件)可以在特定的视口和位置上打开应用程序窗口?

是否有一个脚本(或软件)可以在特定的视口和位置上打开应用程序窗口?

因此,我在 Unity 中拥有 8 个虚拟桌面(使用 Compiz),因为我同时在处理许多项目。

问题是,每次我需要重新启动或意外关闭 Chrome(它占据了我工作所需窗口的很大一部分)时,我都必须手动再次打开这些窗口,然后进行设置(打开文件、转到正确的 URL 等)。

你会如何编写一个脚本来帮我完成所有这些操作?即:1) 打开窗口 2) 将它们放入正确的虚拟屏幕的正确坐标中

(1)很明显,对于 Google Chrome,你只需运行“google-chrome”。但那么你如何将它放在正确的位置呢?(2)

或者是否存在现有的脚本/软件可以为我完成这项工作?

答案1

它可以做得很好,但你需要对 Unity/viewports 有所了解。我希望下面的故事可以理解,如果不理解,请发表评论。

下面的脚本可用于在任何视口、任何位置打开任何应用程序的窗口,如果使用正确的参数运行它。该脚本是这个,但现在准备把窗户放在跨越虚拟桌面

1. 了解视口和窗口坐标

Unity 中的工作区

在 Unity 中,与其他窗口管理器不同,您实际上只有一个跨度工作区,该工作区被划分为视口。在本例中,您的工作区被划分为八个视口。

如何定义窗口的位置

窗口位置,如命令的输出:

wmctrl -lG
(you need to have wmctrl installed to run the command)

被描述为位置,相对的到左上角当前视口


因此,如果您在视口上1
视口 2 上的窗口可以定位在例如 1700(x 方向)x 500(y 方向)
(我的屏幕是 1680x1050)

在此处输入图片描述


但是,如果您在视口 6 上:
同一窗口将位于 20 (x)、-550 (y) 在此处输入图片描述


正确使用这些坐标对于使用正确的参数运行脚本非常重要,如下所述:

2. 如何使用脚本

下面的脚本可用于将应用程序的新窗口放置在虚拟(跨度)工作区上。

  1. 确保wmctrl已安装:

    sudo apt-get install wmctrl
    
  2. 将下面的脚本复制到一个空文件中,将其另存为setwindow(无扩展名)~/bin。如果目录尚不存在,请创建该目录。使脚本可执行

  3. 如果您刚刚创建的~/bin,请运行命令source ~/.profile或注销/登录以使目录在中可用$PATH
  4. 测试运行命令:

    setwindow <application> <x_position> <y_position> <horizontal_size> <vertical_size>
    

    例如

    setwindow gedit 100 100 200 200
    

    gedit 窗口应出现在当前视口上。

笔记:

  • 请记住,并非所有应用程序都允许窗口尺寸低于特定宽度或高度。gedit我的系统上窗口的最小宽度例如约为 470 像素。
  • 脚本仅在以下情况下才能正常工作整体窗口适合目标视口,请相应地选择坐标/大小。还要注意 Unity Launcher 和面板使用一些空间 (!),这可能会影响窗口的位置。
  • 使用负片<x_position>将窗口放置在当前视口的左侧
  • 使用负片<y_position>将窗口放置在当前视口上方
  • 要同时在不同的视口上打开新窗口,只需链接命令即可。查看“长话短说”示例中的视口设置,如果我在视口 1 上,我可以使用以下命令在视口 1、2、3 和 4 上打开 gedit 窗口:

    setwindow gedit 100 100 200 200&&setwindow gedit 1780 100 200 200&&setwindow gedit 3460 100 200 200&&setwindow gedit 5140 100 200 200
    

剧本

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

app = sys.argv[1]

get = lambda x: subprocess.check_output(["/bin/bash", "-c", x]).decode("utf-8")
ws1 = get("wmctrl -lp"); t = 0
subprocess.Popen(["/bin/bash", "-c", app])
# fix exception for Chrome (command = google-chrome-stable, but processname = chrome)
app = "chrome" if "chrome" in app else app
while t < 30:      
    ws2 = [w.split()[0:3] for w in get("wmctrl -lp").splitlines() if not w in ws1]
    procs = [[(p, w[0]) for p in get("ps -e ww").splitlines() \
              if app in p and w[2] in p] for w in ws2]
    if len(procs) > 0:
        w_id = procs[0][0][1]
        cmd1 = "wmctrl -ir "+w_id+" -b remove,maximized_horz"
        cmd2 = "wmctrl -ir "+w_id+" -b remove,maximized_vert"
        cmd3 = "wmctrl -ir "+procs[0][0][1]+" -e 0,"+sys.argv[2]+","+sys.argv[3]+","+sys.argv[4]+","+sys.argv[5]
        for cmd in [cmd1, cmd2, cmd3]:   
            subprocess.call(["/bin/bash", "-c", cmd])
        break
    time.sleep(0.5)
    t = t+1



编辑:懒惰版本

如果您只想输入坐标和大小,就像在当前视口上打开一个窗口并将目标视口作为参数(无需计算任何内容)一样,然后使用下面的版本...

如果你像脚本的第一个版本那样进行设置,则可以使用以下命令运行它:

setwindow <application> <x_position> <y_position> <horizontal_size> <vertical_size> <targeted_viewport>

一个例子:在视口上打开一个Google-Chrome位置为20, 20、大小为的窗口:300x3005

setwindow google-chrome 20 20 300 300 5

设置与脚本的第一个版本基本相同。
请注意,只有当定义的窗口(位置/大小)完全适合目标视口时,此脚本才能正常工作。

剧本:

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

app = sys.argv[1]
target_vp = int(sys.argv[6])

def get_res():
    # get resolution
    xr = subprocess.check_output(["xrandr"]).decode("utf-8").split()
    pos = xr.index("current")
    return [int(xr[pos+1]), int(xr[pos+3].replace(",", "") )]

res = get_res()

def current(set_vp):
    # get the current viewport
    vp_data = subprocess.check_output(
        ["wmctrl", "-d"]
        ).decode("utf-8").split()
    dt = [int(n) for n in vp_data[3].split("x")]
    cols = int(dt[0]/res[0])
    rows = int(dt[1]/res[1])    
    curr_vpdata = [int(n) for n in vp_data[5].split(",")]
    curr_col = int(curr_vpdata[0]/res[0])
    curr_row = int(curr_vpdata[1]/res[1])
    curr_vp = curr_col+curr_row*cols+1
    # calculate the vector to the origin from the current viewport (in resolution units)
    vec_curr = vector(curr_vp, cols)
    # calculate the vector to the origin from the targeted viewport
    vec_set = vector(set_vp, cols)
    # calculate the vector between current and targeted viewport
    vec_relative = [vec_set[0] - vec_curr[0],
                    vec_set[1] - vec_curr[1]]
    # calculate needed correction (absolute)
    relative = [vec_relative[0]*res[0],
                vec_relative[1]*res[1]]
    return relative

def vector(vp, cols):
    rem = vp%cols
    vec_x = rem-1 if rem != 0 else cols-1
    vec_y = int((vp-1)/cols)
    return [vec_x, vec_y]

res = get_res() # nieuw
get = lambda x: subprocess.check_output(["/bin/bash", "-c", x]).decode("utf-8")
ws1 = get("wmctrl -lp"); t = 0
# check for additional arguments to run the application
try:
    subprocess.Popen(["/bin/bash", "-c", app+" "+sys.argv[7]])  
except IndexError:
    subprocess.Popen(["/bin/bash", "-c", app])

# fix exception for Chrome (command = google-chrome-stable, but processname = chrome)
app = "chrome" if "chrome" in app else app
while t < 30:      
    ws2 = [w.split()[0:3] for w in get("wmctrl -lp").splitlines() if not w in ws1]
    procs = [[(p, w[0]) for p in get("ps -e ww").splitlines() \
              if app in p and w[2] in p] for w in ws2]
    if len(procs) > 0:
        w_id = procs[0][0][1]
        cmd1 = "wmctrl -ir "+w_id+" -b remove,maximized_horz"
        cmd2 = "wmctrl -ir "+w_id+" -b remove,maximized_vert"
        # calculate the correction, related to the current workspace, marge for launcher and panel
        pos_x = int(sys.argv[2]); pos_y = int(sys.argv[3]); x_marge = 65; y_marge = 35
        pos_x = pos_x if pos_x > x_marge else x_marge; pos_y = pos_y if pos_y > y_marge else y_marge
        x_relative = pos_x+current(target_vp)[0]
        y_relative = pos_y+current(target_vp)[1]
        # correct possible inaccurately set width / height
        x_size = res[0]; y_size = res[1]
        set_width = int(sys.argv[4]); set_height = int(sys.argv[5])
        width = set_width if set_width+x_marge+pos_x < x_size else x_size - pos_x - x_marge
        height = set_height if set_height+y_marge+pos_y < y_size else y_size - pos_y - y_marge
        cmd3 = "wmctrl -ir "+w_id+" -e 0,"+str(x_relative)+","+str(y_relative)+","+str(width)+","+str(height)
        for cmd in [cmd1, cmd2, cmd3]:   
            subprocess.call(["/bin/bash", "-c", cmd])
        break
    time.sleep(0.5)
    t = t+1


使用参数打开应用程序窗口

为了完成这项工作,完整回答你的问题:

如果您运行脚本如下:

setwindow google-chrome 20 20 300 300 5

它将打开一个默认目标桌面上的窗口。
但是,使用最新版本的脚本,您添加额外的打开应用程序窗口的参数,例如url

setwindow <application> <x_position> <y_position> <horizontal_size> <vertical_size> <targeted_viewport> <(optional)_argument>

例如:

setwindow google-chrome 0 0 600 600 3 "--new-window http://askubuntu.com"

如果(额外)参数包含空格,请使用引号。上面的示例将google-chrome在视口 3 上打开一个窗口,打开url http://askubuntu.com

您可以链接命令以在一个命令中打开不同工作区上的多个窗口/url,例如:

setwindow google-chrome 0 0 600 600 8 "--new-window http://askubuntu.com"&&setwindow google-chrome 0 0 600 600 7 "--new-window www.google.com"

答案2

这扩展了@Jacob Vlijim 的上述回答非常好稍微修改一下setwindow脚本:

#!/usr/bin/env python

import time
import argparse
import subprocess

DEFAULT_WIDTH = '1920'
DEFAULT_HEIGHT = '1080'


def get_window_list():
    window_list = subprocess.check_output(['/bin/bash', '-c', 'wmctrl -l'])
    parsed_list = []
    for line in window_list.splitlines():
        window_info = line.split()
        if window_info[1] != '-1':
            parsed_list.append(window_info[0])
    return parsed_list


def main(params):
    old_list = get_window_list()
    subprocess.Popen(['/bin/bash', '-c', params.command])

    def get_diff(old):
        new_list = get_window_list()
        return list(set(new_list) - set(old))

    diff = get_diff(old_list)
    x = 0
    while not diff:
        if x == 10:
            print 'window not found'
            return
        x += 1
        diff = get_diff(old_list)
        time.sleep(1)
    if len(diff) > 1:
        raise Exception(diff)
    window_id = diff[0]
    command_list = []
    command_list.append('wmctrl -ir %s -t %s' % (window_id, params.desktop))
    command_list.append('wmctrl -ir %s -b remove,maximized_horz,maximized_vert'
        % window_id)
    command_list.append('wmctrl -ir %s -e 0,%s,%s,%s,%s' %
        (window_id, params.x_pos, params.y_pos, params.width, params.height))
    for command in command_list:
        subprocess.call(['/bin/bash', '-c', command])

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('command', type=str)
    parser.add_argument('-d', '--desktop', default='0', type=str)
    parser.add_argument('-x', '--x-pos', default='0', type=str)
    parser.add_argument('-y', '--y-pos', default='0', type=str)
    parser.add_argument('-w', '--width', default=DEFAULT_WIDTH, type=str)
    parser.add_argument('-t', '--height', default=DEFAULT_HEIGHT, type=str)
    args = parser.parse_args()
    main(args)

变更描述:

  1. python3(仅代表python个人喜好)
  2. sys.argv为了argparse更好的命令行界面
  3. 严格的窗口 ID(而不是进程 ID)窗口解析
    • 有些程序对多个窗口使用单个进程 ID
  4. while循环 0.5 秒至 1 秒的睡眠时间
  5. 更详细/可读的变量名称和pep8 依从性
  6. 屏幕尺寸的全局常量变量而不是xrandr依赖

注意:这只是我为 Debian Jessie LXDE 个人使用而编写的略微改进的版本。您的结果可能会有所不同。

答案3

对于那些感兴趣的人,我已经实现了 Desktopen:https://github.com/snitko/desktopen

它允许您以非常友好的方式编写脚本以在不同的视口和显示器上打开窗口。

相关内容