概述

多显示器能够显著提高办公效率,在使用过程中偶尔需要将窗口移动到另外的显示器上,这当然可以用鼠标操作,但是如果现在正在打字,那用键盘如何操作呢?
如果使用的windows,可以通过Win+Shift+← /→来移动窗口,linux我使用的是xfce作为桌面环境,没有发现类似的热键,所以只能自己实现了。

操作流程

功能脚本的编写

#!/bin/bash
cmd=`basename "$0"`
function usage(){
    echo "usage:"
    echo "   ${cmd} -l|--left"
    echo "   把当前窗口移动到左侧显示器"
    echo "" 
    echo "   ${cmd} -r|--right"
    echo "   把当前窗口移动到右侧显示器"
}

## 解析脚本参数
ARGS=`getopt -q -o lr -l left,right -n "${cmd}" -- "$@"`
eval set -- "${ARGS}"

par_l=0 	#left
par_r=0 	#right
while [ -n "$1" ];do
    case "$1" in
        -l|--left)
            par_l=1
            shift
            ;;
        -r|--right) 
            par_r=1
            shift
            ;;
        --)
            shift
            break
            ;;
        *)
            usage
            exit 1
            ;;
    esac
done

# 必须传入一个合法参数
temp=0
((temp=$par_l + $par_r))
if [[ $temp != 1 ]];then
    usage
    exit 1
fi

# 获取当前窗口信息
WID=`xdotool getactivewindow`

# 获取当前窗口信息
info=`xwininfo -tree -stats -id ${WID}`
ax=`echo "$info" | grep "Absolute upper-left X:" | grep -P -o "\d+"`
ay=`echo "$info" | grep "Absolute upper-left Y:" | grep -P -o "\d+"`
rx=`echo "$info" | grep "Relative upper-left X:" | grep -P -o "\d+"`
ry=`echo "$info" | grep "Relative upper-left Y:" | grep -P -o "\d+"`
width=`echo "$info" | grep "Width:" | grep -P -o "\d+"`
height=`echo "$info" | grep "Height:" | grep -P -o "\d+"`

# 根据传入的参数调整窗口的x坐标
if [ ${par_l} == 1 ];then
    if (( ${ax} >= 1440 ));then
        ((ax=${ax} - 1440))
    fi
elif [ ${par_r} == 1 ];then
    if (( ${ax} < 1440 ));then
        ((ax=${ax} + 1440))
    fi
fi

# 是否全屏
isFullScreen=`xprop -id ${WID} | grep -P "^_NET_WM_STATE" | grep -Po "_NET_WM_STATE_FULLSCREEN"`
# 是否横向最大化
isMaxHorz=`xprop -id ${WID} | grep -P "^_NET_WM_STATE" | grep -Po "_NET_WM_STATE_MAXIMIZED_HORZ"`
# 是否纵向最大化
isMaxVert=`xprop -id ${WID} | grep -P "^_NET_WM_STATE" | grep -Po "_NET_WM_STATE_MAXIMIZED_VERT"`

# 根据窗口状态移动窗口
if [ ${isFullScreen} != "" ];then
    wmctrl -ir $WID -b toggle,fullscreen
    wmctrl -i -r $WID -e 0,${ax},${ay},800,600
    wmctrl -ir $WID -b toggle,fullscreen
elif [ ${isMaxHorz} != "" ] && [ ${isMaxVert} != "" ];then
    wmctrl -ir $WID -b remove,maximized_horz,maximized_vert
    ((ax=${ax} + 4))
    xdotool getactivewindow windowmove ${ax} ${ay}
    wmctrl -ir $WID -b add,maximized_horz,maximized_vert
elif [ ${isMaxHorz} != "" ];then
    wmctrl -ir $WID -b toggle,maximized_horz
    xdotool getactivewindow windowmove ${ax} ${ay}
    wmctrl -ir $WID -b toggle,maximized_horz
elif [ ${isMaxVert} != "" ];then
    wmctrl -ir $WID -b toggle,maximized_vert
    xdotool getactivewindow windowmove ${ax} ${ay}
    wmctrl -ir $WID -b toggle,maximized_vert
else
    # 修正偏移
    PID=`xdotool getactivewindow getwindowpid`
    binName=`readlink -f /proc/$PID/exe | xargs basename`
    case ${binName} in
        "nvim-qt" )
            ;;
        * )
            ((ax=${ax} - ${rx}))
            ((ay=${ay} - ${ry}))
            ;;
    esac
    #xdotool getactivewindow windowmove ${ax} ${ay}
    wmctrl -r ":ACTIVE:" -e 0,${ax},${ay},${width},${height}
fi

每个部分的含义已经在脚本中做了注解,其中比较无奈的地方是修正偏移问题。通过xwininfo获取当前窗口信息虽然可以确定绝对坐标,但是绝大部分窗口都存在2倍的相对坐标偏移,只有nvim-qt不存在2倍偏移的情况。个人猜测这个偏移问题就是窗口的边框和标题大小,目前系统只有nvim-qt这么唯一一个qt程序,其他都是使用gtk的程序。此前使用xdotool获取窗口坐标,由于chrome浏览器使用内置标题栏,计算偏移的时候又需要单独考虑chrome的情况,所以就改用xwininfo获取窗口坐标了。
目前使用的两个显示器都是1440x900的分辨率,这在两个显示器切换上轻松了不少,如果有人使用不同分辨率的显示器,甚至两个显示器有一个是纵向摆放的…(我想想都头大)

热键绑定

打开xfce4-keyboard-settings,点击Add添加新的热键绑定,Command输入/path/of/script.sh --left,然后按下绑定的热键(我选了与windows同样的热键)。然后如法炮制向右移动的热键绑定。至此,移动窗口的命令就完成了。

目前已知的BUG

在窗口最大化下移动窗口后,会丢失部分位置和大小信息。由于wmctrlxdotool无法在全屏或最大化模式下移动窗口,所以移动窗口必须在窗口模式进行。
全屏移动窗口的理想情况应该如下操作:

  1. 切换到窗口模式
  2. 重新获取窗口位置和大小
  3. 移动到另外一个窗口
  4. 切换会全屏模式
    但是在操作中发现,低频的窗口移动操作还好,在频繁切换窗口操作时,无法保证正确获取窗口信息,致使移动后的窗口尺寸变成了全屏尺寸。所以这里使用800x600尺寸,固定全屏后的窗口大小,窗口坐标也变成了显示器的左上角。类似的,最大化窗口也有类似的问题。

总结

已经基本实现了显示器间窗口移动的功能,其中使用了xprop,xdotool,wmctrl三个窗口相关的命令,基本上每个发行版都能在软件仓库中找到,没有的就自己安装一下吧,都是特别小的软件包。整个脚本的复杂度全在窗口坐标和各种窗口状态的检验上,确实是一个无奈的选择。