1
0
mirror of https://github.com/gryf/moveto.git synced 2025-12-17 19:40:26 +01:00

Added calculation for window decorations, detect dock position

This commit is contained in:
2017-01-13 18:47:56 +01:00
parent dda42126cd
commit 0894af3ba6

298
moveto.py Executable file → Normal file
View File

@@ -16,9 +16,6 @@ Calculate possible moves of the window against to the current size and
position. Assuming we have screen layout (two physical monitors in twin view position. Assuming we have screen layout (two physical monitors in twin view
nvidia mode which makes one big screen available) nvidia mode which makes one big screen available)
To illustrate the behaviour, lets analyze following layout. Note, the window
is not maximized:
+---------------------+-----------------------------+--+ +---------------------+-----------------------------+--+
| | | | | | | |
| +--------+ | +--+ | +--------+ | +--+
@@ -27,128 +24,72 @@ is not maximized:
| +--------+ | | | | +--------+ | | |
| | +--+ | | +--+
| | | | | | | |
| | +--+ | screen 0 | screen 1 +--+
| | | | | |
+--+ +--+--+ | +--+ +--+--+ |
| | screen 0 | | | screen 1 | | | | | | |
+--+------------------+--+--+--------------------------+ +--+------------------+--+--+--------------------------+
Possible moves of the depicted window would be: possible moves of the depicted window would be:
- 'move left' will move window to to the left half on screen 0 1. without resizing:
- 'move right' will move window to to the right half on screen 0 - move to the left edge of the screen 0
- move to the right edge of the screen 0
Let's assume that we chose the latter, so the new layout would be as follow: - move to the left edge of the screen 1
- move to the right edge of the screen 1
+----------+----------+-----------------------------+--+ - move to the screen 1 (don't cross boundary of the
| | window | | | screen)
| | | +--+ - move to the left edge of the screen 1
| | | | | - move to the right edge of the screen 1
| | | +--+ 2. with resizing:
| | | | | - move to the screen 1 (maximized)
| | | +--+ - maximize on current screen
| | | | | - move to screen 0 to the left half
| | | +--+ - move to screen 0 to the right half
| | | | - move to screen 1 to the left half
+--+ +----------+--+--+ | - move to screen 1 to the right half
| | screen 0 | | | screen 1 |
+--+------------------+--+--+--------------------------+
The possibilities are:
- 'move left' will maximize window on screen 0
- 'move right' will move window to to the left half on screen 1
Move right will end up with following layout. Note, that mouse cursor follow
the window, so possible child windows of the current window should appear on
the screen, where main window is.
+---------------------+--------------+--------------+--+
| | window | | |
| | | +--+
| | | | |
| | | +--+
| | | | |
| | | +--+
| | | | |
| | | +--+
| | | |
+--+ +--+--+--------+ |
| | screen 0 | | | screen 1 |
+--+------------------+--+--+--------------------------+
Again, the possibilities are:
- 'move left' will move window to to the right half on screen 0
- 'move right' will maximize window on screen 1
And, if user keeps pushing window to the right it will need just two more
steps to end up like this:
+---------------------+-----------------------------+--+
| | window | |
| | +--+
| | | |
| | +--+
| | | |
| | +--+
| | | |
| | +--+
| | | |
+--+ +--+--+-----------------------+ |
| | screen 0 | | | screen 1 |
+--+------------------+--+--+--------------------------+
+---------------------+--------------+--------------+--+
| | | window | |
| | | +--+
| | | | |
| | | +--+
| | | | |
| | | +--+
| | | | |
| | | +--+
| | | | |
+--+ +--+--+ +--------------+ |
| | screen 0 | | | screen 1 |
+--+------------------+--+--+--------------------------+
Further moving window to the right will have no effect.
TODO: Make it more flexible with different screen configurations TODO: Make it more flexible with different screen configurations
Author: Roman "gryf" Dobosz <gryf73@gmail.com> Author: Roman 'gryf' Dobosz <gryf73@gmail.com>
Date: 2013-01-06 Date: 2013-01-06
Date: 2014-03-31 (used pygtk instead of xrandr, which is faster) Date: 2014-03-31 (used pygtk instead of xrandr, which is faster)
Date: 2014-06-25 added docopt, corrections and simplify the process Date: 2014-06-25 added docopt, corrections and simplify the process
Version: 1.3 Date: 2015-10-12 added debug option, figured out wmaker decorations
calculation method
Version: 1.4
""" """
import sys from docopt import docopt
import os
import re
from subprocess import Popen, PIPE, call from subprocess import Popen, PIPE, call
import logging import logging
import os
import re
import sys
from docopt import docopt
from gtk import gdk from gtk import gdk
# TODO: Make it configurable (lots of options starting from ini file) # TODO: Make it configurable (lots of options starting from ini file)
COVER_MINIWINDOWS = True COVER_MINIWINDOWS = True
COVER_DOCK = False COVER_DOCK = False
DECOTATORS_HEIGHT = 29 # TODO: get it somehow from real window
logging.basicConfig(filename=os.path.expanduser('~/moveto.log'), def get_magic_number():
format='%(funcName)s:%(lineno)d %(message)s', """Get the numbers for window shift and position"""
level=logging.INFO)
out = Popen(['xdotool', 'getactivewindow'],
stdout=PIPE).communicate()[0]
out = out.strip()
out = Popen(['xwininfo', '-id', out],
stdout=PIPE).communicate()[0]
out.strip()
for line in out.split("\n"):
line = line.strip()
if line.startswith("Relative upper-left Y"):
magic = int(line.split(" ")[-1].strip())
break
def get_window_name(): return magic * 2 + 1, magic + 8
"""Return the current active window name"""
name = Popen(["xdotool", "getactivewindow", "getwindowname"], MAGIC_NO, DECORATIONS_HEIGHT = get_magic_number()
stdout=PIPE).communicate()[0]
name = name.strip()
logging.debug('window name: %s', name)
return name
def get_monitors(): def get_monitors():
@@ -170,7 +111,7 @@ def get_monitors():
def set_cover(): def set_cover():
"""read actual wmaker config and set apropriate globals""" """read actual wmaker config and set appropriate globals"""
global COVER_MINIWINDOWS, COVER_DOCK global COVER_MINIWINDOWS, COVER_DOCK
with open(os.path.expanduser("~/GNUstep/Defaults/WindowMaker")) as fobj: with open(os.path.expanduser("~/GNUstep/Defaults/WindowMaker")) as fobj:
@@ -200,10 +141,12 @@ class Screens(object):
def guess_dimensions(self, window): def guess_dimensions(self, window):
""" """
Check wheter current window is in one of three states: maximized, Check whether current window is in one of three states: maximized,
left-half maximized, right-half maximized. If so, return appropriate left-half maximized, right-half maximized. If so, return appropriate
information, None otherwise information, None otherwise
""" """
logging.debug("window - %s", str(window))
for scr in self.screens: for scr in self.screens:
if window == scr.left_half: if window == scr.left_half:
return "left" return "left"
@@ -211,9 +154,9 @@ class Screens(object):
return "right" return "right"
# check for maximized window (approximated) # check for maximized window (approximated)
if window["pos_x"] == window["pos_y"] == 0: if window['pos_x'] == window['pos_y'] == 0:
if window["size_x"] in range(scr.x - 32, scr.x + 32) and \ if window['size_x'] in range(scr.x - 32, scr.x + 32) and \
window["size_x"] in range(scr.x - 32, scr.x + 32): window['size_x'] in range(scr.x - 32, scr.x + 32):
return "maximized" return "maximized"
return None return None
@@ -224,8 +167,6 @@ class Screen(object):
Holds separate display information. It can be separate X screen or just a Holds separate display information. It can be separate X screen or just a
display/monitor display/monitor
""" """
misbehaving_windows = ["Oracle VM VirtualBox", "LibreOffice"]
def __init__(self, x=0, y=0, sx=0, sy=0): def __init__(self, x=0, y=0, sx=0, sy=0):
"""Initialization""" """Initialization"""
self.x = int(x) self.x = int(x)
@@ -249,13 +190,12 @@ class Screen(object):
"size_x": 0, "size_x": 0,
"size_y": 0} "size_y": 0}
def calculate_columns(self, window_name): def calculate_columns(self):
""" """
Calculate dimension grid, which two column windows could occupy, Calculate dimension grid, which two column windows could occupy,
make it pixel exact. make it pixel exact.
""" """
sx, sy = self.x, self.y sx, sy = self.x, self.y
logging.debug('sx, and sy: %d, %d', sx, sy)
if sx % 2 != 0: if sx % 2 != 0:
# it is rare, but hell, shit happens # it is rare, but hell, shit happens
@@ -268,32 +208,21 @@ class Screen(object):
self.x = sx = sx - 2 self.x = sx = sx - 2
# miniwindows on bottom + 2px for border # miniwindows on bottom + 2px for border
logging.debug("Covering miniwindows: %s", COVER_MINIWINDOWS) logging.debug("calculate_columns %s", COVER_MINIWINDOWS)
if not COVER_MINIWINDOWS: if not COVER_MINIWINDOWS:
self.y = sy = sy - (64 + 2) self.y = sy = sy - (64 + 2)
self.left_half["size_x"] = sx / 2 - 1 self.left_half['size_x'] = sx / 2 - 1
self.maximized["pos_x"] = self.left_half["pos_x"] = self.x_shift self.maximized['pos_x'] = self.left_half['pos_x'] = self.x_shift
self.right_half["size_x"] = sx / 2 self.right_half['size_x'] = sx / 2
self.right_half["pos_x"] = sx / 2 + self.x_shift self.right_half['pos_x'] = sx / 2 + self.x_shift
self.maximized["size_x"] = sx self.maximized['size_x'] = sx
self.maximized["size_y"] = self.right_half["size_y"] = \ self.maximized['size_y'] = self.right_half['size_y'] = \
self.left_half["size_y"] = sy - DECOTATORS_HEIGHT self.left_half['size_y'] = sy - DECORATIONS_HEIGHT
for name in self.misbehaving_windows:
if name in window_name:
logging.debug('Correcting position of window %s off 21 '
'pixels', window_name)
self.left_half["pos_y"] = 21
self.right_half["pos_y"] = 21
self.right_half["pos_y"] = 21
logging.debug('left half: %s', self.left_half)
logging.debug('right half: %s', self.right_half)
logging.debug('maximized: %s', self.maximized)
class WMWindow(object): class WMWindow(object):
""" """
@@ -316,24 +245,64 @@ class WMWindow(object):
self.current_screen = 0 self.current_screen = 0
self.state = None self.state = None
self._main = main self._main = main
self.name = get_window_name()
self._discover_screens(monitors) self._discover_screens(monitors)
self._get_props() self._get_props()
def _detect_dock_position(self):
"""Find out all 64x64 windows, which are in most cases not manageable
by windowmaker, and try to figure out where they are placed -
currently it's a majority for certain x coordinate, since dock is
arranged vertically. Note, that clip may or may not influence the
process, since it might be placed freely, and contains more dockapps
or appicons than dock itself."""
res = Popen("xwininfo -tree -root -children".split(),
stdout=PIPE).communicate()[0]
result = {}
for item in res.split("\n"):
if "64x64" not in item:
continue
item = item.split()
geometry = item[-2]
_, shift_x, shift_y = geometry.split("+")
try:
result[shift_x].append(shift_y)
except KeyError:
result[shift_x] = [shift_y]
length = 0
winner = 0
for key in result:
new_len = len(set(result[key]))
if new_len > length:
length = new_len
winner = key
winner = int(winner)
logging.debug("predicted x position of the dock: %d", winner)
import ipdb; ipdb.set_trace()
for screen in self.screens.screens:
logging.debug("screen: %s", str(screen))
if winner in range(screen.x_shift, screen.x + screen.x_shift + 1):
logging.debug("setting %s screen as a main", str(screen))
screen.main = True
else:
screen.main = False
def _get_props(self): def _get_props(self):
""" """
Update current window dimensions and position Update current window dimensions and position
""" """
self.x = self.y = self.pos_x = self.pos_y = None self.x = self.y = self.pos_x = self.pos_y = None
out = Popen(["xdotool", "getactivewindow", "getwindowgeometry"], out = Popen(['xdotool', 'getactivewindow', 'getwindowgeometry'],
stdout=PIPE).communicate()[0] stdout=PIPE).communicate()[0]
out = out.strip().split("\n") out = out.strip().split("\n")
if len(out) != 3: if len(out) != 3:
logging.warning('Cannot get window size and position for %s', print "Cannot get window size and position"
self.name)
return return
pos, size = out[1:] pos, size = out[1:]
@@ -344,12 +313,8 @@ class WMWindow(object):
# XXX: arbitrary correction of the window position. Don't know why # XXX: arbitrary correction of the window position. Don't know why
# xdotool reports such strange data - maybe it is connected with # xdotool reports such strange data - maybe it is connected with
# inner/outer dimensions and/or window manager decorations # inner/outer dimensions and/or window manager decorations
logging.debug('window position reported via xdotool: %s x %s',
self.pos_x, self.pos_y)
self.pos_x = int(self.pos_x) - 1 self.pos_x = int(self.pos_x) - 1
self.pos_y = int(self.pos_y) - 43 self.pos_y = int(self.pos_y) - MAGIC_NO
logging.debug('window position after corrections: %s x %s',
self.pos_x, self.pos_y)
for scr_no, scr in enumerate(self.screens.screens): for scr_no, scr in enumerate(self.screens.screens):
if self.pos_x in range(scr.x_shift, scr.x + scr.x_shift): if self.pos_x in range(scr.x_shift, scr.x + scr.x_shift):
@@ -363,10 +328,8 @@ class WMWindow(object):
self.y = int(self.y) self.y = int(self.y)
if None in (self.x, self.y, self.pos_x, self.pos_y): if None in (self.x, self.y, self.pos_x, self.pos_y):
logging.warning('Not enough data for calculate window placement. ' print "Not enough data for calculate window placement"
'Window name "%s", (x, y, pos_x, pos_y): ' print self.x, self.y, self.pos_x, self.pos_y
'%d, %d, %d, %d', self.name, self.x, self.y,
self.pos_x, self.pos_y)
else: else:
self.guess_dimensions() self.guess_dimensions()
@@ -388,7 +351,6 @@ class WMWindow(object):
elif not self.screens.screens: elif not self.screens.screens:
screen.main = True screen.main = True
screen.calculate_columns(self.name)
self.screens.append(screen) self.screens.append(screen)
# sort screens depending on the position (only horizontal order is # sort screens depending on the position (only horizontal order is
@@ -400,6 +362,13 @@ class WMWindow(object):
self.screens.screens = [screens[key] for key in sorted(screens.keys())] self.screens.screens = [screens[key] for key in sorted(screens.keys())]
if len(self.screens.screens) > 1 and not self._main:
self._detect_dock_position()
for screen in self.screens.screens:
screen.calculate_columns()
def get_data(self): def get_data(self):
"""Return current window coordinates and size""" """Return current window coordinates and size"""
return {"pos_x": self.pos_x, return {"pos_x": self.pos_x,
@@ -430,7 +399,7 @@ class WMWindow(object):
return True return True
def get_coords(self, which): def get_coords(self, which):
"""Return window in screen coordinates""" """Return screen coordinates"""
scr = self.screens.screens[self.current_screen] scr = self.screens.screens[self.current_screen]
coord_map = {"maximized": scr.maximized, coord_map = {"maximized": scr.maximized,
@@ -466,17 +435,17 @@ def cycle(monitors, right=False, main=None):
coords = wmwin.get_coords(key) coords = wmwin.get_coords(key)
if order: if order:
cmd = ["xdotool", "getactivewindow", cmd = ['xdotool', "getactivewindow",
"windowsize", str(coords["size_x"]), str(coords["size_y"]), "windowsize", str(coords['size_x']), str(coords['size_y']),
"windowmove", str(coords["pos_x"]), str(coords["pos_y"]), "windowmove", str(coords['pos_x']), str(coords['pos_y']),
"mousemove", str(coords["pos_x"] + coords["size_x"] / 2), "mousemove", str(coords['pos_x'] + coords['size_x'] / 2),
str(coords["pos_y"] + coords["size_y"] / 2)] str(coords['pos_y'] + coords['size_y'] / 2)]
else: else:
cmd = ["xdotool", "getactivewindow", cmd = ['xdotool', "getactivewindow",
"windowmove", str(coords["pos_x"]), str(coords["pos_y"]), "windowmove", str(coords['pos_x']), str(coords['pos_y']),
"windowsize", str(coords["size_x"]), str(coords["size_y"]), "windowsize", str(coords['size_x']), str(coords['size_y']),
"mousemove", str(coords["pos_x"] + coords["size_x"] / 2), "mousemove", str(coords['pos_x'] + coords['size_x'] / 2),
str(coords["pos_y"] + coords["size_y"] / 2)] str(coords['pos_y'] + coords['size_y'] / 2)]
call(cmd) call(cmd)
@@ -491,7 +460,7 @@ def show_monitors(monitors):
def move_mouse(monitors, name): def move_mouse(monitors, name):
"""Move the mosue pointer to the left upper corner oft the specified by """Move the mouse pointer to the left upper corner oft the specified by
the name screen""" the name screen"""
mon = monitors.get(name) mon = monitors.get(name)
@@ -501,7 +470,7 @@ def move_mouse(monitors, name):
posx = mon["sx"] + 15 posx = mon["sx"] + 15
posy = mon["sy"] + 50 posy = mon["sy"] + 50
cmd = ["xdotool", "mousemove", str(posx), str(posy)] cmd = ['xdotool', "mousemove", str(posx), str(posy)]
call(cmd) call(cmd)
@@ -510,23 +479,28 @@ def main():
arguments = """Move windows around mimicking Windows7 flag+arrows behaviour arguments = """Move windows around mimicking Windows7 flag+arrows behaviour
Usage: Usage:
%(prog)s move (left|right) [-r|-l] [-m NAME] %(prog)s move (left|right) [-r|-l] [-m NAME] [-d]
%(prog)s mousemove -m NAME %(prog)s mousemove -m NAME [-d]
%(prog)s showmonitors %(prog)s showmonitors [-d]
%(prog)s (-h | --help) %(prog)s (-h | --help)
%(prog)s --version %(prog)s --version
Options: Options:
-m NAME --monitor-name=NAME Name of the monitor to be treades as the main -m NAME --monitor-name=NAME Name of the monitor to be treated as the main
one (so the one containing dock) one (so the one containing dock)
-r --dock-right Dock is on the right edge of the rightmost -r --dock-right Dock is on the right edge of the rightmost
screen screen
-l --dock-left Dock is on the left edge of the leftmost screen -l --dock-left Dock is on the left edge of the leftmost screen
-h --help Show this screen. -h --help Show this screen.
-v --version Show version. -v --version Show version.
-d --debug Show debug messages.
""" % {"prog": sys.argv[0]} """ % {"prog": sys.argv[0]}
opts = docopt(arguments, version=0.1) opts = docopt(arguments, version=1.4)
level = logging.DEBUG if opts['--debug'] else logging.WARNING
logging.basicConfig(format='%(lineno)s %(funcName)s(): %(message)s',
level=level)
monitors = get_monitors() monitors = get_monitors()
if opts["showmonitors"]: if opts["showmonitors"]:
@@ -534,7 +508,7 @@ Options:
return return
if opts["mousemove"]: if opts["mousemove"]:
move_mouse(monitors, opts["--monitor-name"]) move_mouse(monitors, opts['--monitor-name'])
return return
if opts["move"]: if opts["move"]: