Skip to content

Instantly share code, notes, and snippets.

@kckrinke
Last active September 23, 2017 21:05
Show Gist options
  • Save kckrinke/3c418cb2315475a2d381d6c68fa60b5d to your computer and use it in GitHub Desktop.
Save kckrinke/3c418cb2315475a2d381d6c68fa60b5d to your computer and use it in GitHub Desktop.
Switch the active window to an opposing monitor. Bind to keyboard combo for full usefulness (such as <Super>+<Enter>). Depends upon the following programs: wmctrl xdpyinfo xrandr xprop xwininfo xdotool. Works with Qubes-OS - copy to dom0 and then you can bind to a keyboard combo.
#!/usr/bin/env perl
###############################################################################
# switch-window-screen - Move active window to the opposite monitor.
#
# Inspired by:
# https://chaosprevails7707.wordpress.com/2015/07/15/xfce-move-window-to-next-
# monitor-with-vertical-and-horizontal-setup/
#
# The MIT License (MIT)
#
# Copyright (c) 2017 Kevin C. Krinke <kevin@krinke.ca>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to permit
# persons to whom the Software is furnished to do so, subject to the
# following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
# USE OR OTHER DEALINGS IN THE SOFTWARE.
#
###############################################################################
use strict;
use constant { true => 1, false => 0 };
$|=1;
requirements_report();
my $info = get_screen_info();
move_window($info);
exit 0;
#
# Helper Functions
#
sub requirements_report {
my @paths = split(m/:/,$ENV{PATH});
my $flag = false;
foreach my $bin (qw( wmctrl xdpyinfo xrandr xprop xwininfo xdotool )) {
my $flagged = false;
foreach my $path (@paths) {
if (-x $path .'/'. $bin) {
$flagged = false;
}
}
if ($flagged) {
print STDERR "Missing dependency: ".$bin."\n";
$flag = true;
}
}
if ($flag) {
print STDERR "Please install all missing dependencies and then try again.\n";
exit 1;
}
}
sub get_screen_info {
my $info =
{ desktop =>
{ x => 0,
y => 0,
orient => ''
},
screens => [],
window =>
{ id => 0,
x => 0,
y => 0,
max =>
{ horz => false,
vert => false
},
}
};
my $raw = `xdpyinfo 2> /dev/null`;
($info->{desktop}{x}) = $raw =~ m!^\s*dimensions:\s*(\d+)x\d+\s*pixels.*$!ms;
($info->{desktop}{y}) = $raw =~ m!^\s*dimensions:\s*\d+x(\d+)\s*pixels.*$!ms;
if ($info->{desktop}{x} > $info->{desktop}{y}) {
$info->{desktop}{orient} = 'horizontal';
} else {
$info->{desktop}{orient} = 'vertical';
}
$raw = `(xrandr --query 2>/dev/null) | egrep '\\bconnected\\b'`;
foreach my $line (split(m/\r??\n/,$raw)) {
if ($line =~ m!^(\w+) connected\s*(primary|)\s*(\d+)x(\d+)\+(\d+)\+(\d+)\s*.*$!) {
my %screen =
( name => $1,
primary => $2 ? true : false,
x => $3, #actual
y => $4,
xo => $5, #offset
yo => $6,
);
push(@{$info->{screens}},\%screen);
if ($screen{xo} > 0 || $screen{yo} > 0) {
$info->{desktop}{tw} = $screen{xo};
$info->{desktop}{th} = $screen{yo};
}
}
}
$raw = `xdotool getactivewindow`;
chomp($raw);
$info->{window}{id} = $raw;
my $wm_state = `xprop -id $info->{window}{id} 2> /dev/null`;
$info->{window}{max}{horz} = ($wm_state =~ m/^_NET_WM_STATE.*_MAXIMIZED_HORZ/ms) ? true : false;
$info->{window}{max}{vert} = ($wm_state =~ m/^_NET_WM_STATE.*_MAXIMIZED_VERT/ms) ? true : false;
return $info;
}
sub move_window {
my ($info) = @_;
# unmaximize to get actual window coordinates
system('wmctrl -ir '.$info->{window}{id}.' -b remove,maximized_vert,maximized_horz');
# read position
my $xwininfo = `xwininfo -id $info->{window}{id} 2>&1`;
my ($xp) = $xwininfo =~ m!^\s*Absolute upper-left X:\s*(\d+?)\s*$!ms;
my ($yp) = $xwininfo =~ m!^\s*Absolute upper-left Y:\s*(\d+?)\s*$!ms;
my ($xo) = $xwininfo =~ m!^\s*Relative upper-left X:\s*(\d+?)\s*$!ms;
my ($yo) = $xwininfo =~ m!^\s*Relative upper-left Y:\s*(\d+?)\s*$!ms;
my ($gw) = $xwininfo =~ m!^\s*Width:\s*(\d+?)\s*$!ms;
my ($gh) = $xwininfo =~ m!^\s*Height:\s*(\d+?)\s*$!ms;
# computed positions
my $xc = $xp - $xo;
my $yc = $yp - $yo;
# calculate movement
my ($new_x,$new_y) = ($xc,$yc);
if ($info->{desktop}{orient} eq 'horizontal') {
$new_x = $xc - $info->{desktop}{tw} if $xc >= $info->{desktop}{tw};
$new_x = $xc + $info->{desktop}{tw} if $xc < $info->{desktop}{tw};
} else {
$new_y = $yc - $info->{desktop}{th} if $yc >= $info->{desktop}{th};
$new_y = $yc + $info->{desktop}{th} if $yc < $info->{desktop}{th};
}
# stay within bounds
$new_x = $info->{desktop}{x} - $gw if $new_x >= $info->{desktop}{x};
$new_y = $info->{desktop}{y} - $gh if $new_y >= $info->{desktop}{y};
# actually move the window
system('xdotool windowmove '.$info->{window}{id}.' '.$new_x.' '.$new_y);
# restore maximied state
if ($info->{window}{max}{vert} || $info->{window}{max}{horz}) {
my $state = 'add';
$state .= ',maximized_vert' if $info->{window}{max}{vert};
$state .= ',maximized_horz' if $info->{window}{max}{horz};
system('wmctrl -ir '.$info->{window}{id}.' -b '.$state);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment