From a5fb065b21cf6523c696150beb33403000f8a50d Mon Sep 17 00:00:00 2001 From: gryf Date: Fri, 13 Jan 2017 18:49:59 +0100 Subject: [PATCH] Added some support for positioning misbehaving windows --- moveto.py | 219 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 158 insertions(+), 61 deletions(-) mode change 100644 => 100755 moveto.py diff --git a/moveto.py b/moveto.py old mode 100644 new mode 100755 index 70b3e50..bf96e81 --- a/moveto.py +++ b/moveto.py @@ -16,6 +16,9 @@ Calculate possible moves of the window against to the current size and position. Assuming we have screen layout (two physical monitors in twin view nvidia mode which makes one big screen available) +To illustrate the behaviour, lets analyze following layout. Note, the window +is not maximized: + +---------------------+-----------------------------+--+ | | | | | +--------+ | +--+ @@ -24,47 +27,111 @@ nvidia mode which makes one big screen available) | +--------+ | | | | | +--+ | | | | - | screen 0 | screen 1 +--+ + | | +--+ | | | +--+ +--+--+ | - | | | | | | + | | screen 0 | | | screen 1 | +--+------------------+--+--+--------------------------+ -possible moves of the depicted window would be: - 1. without resizing: - - move to the left edge of the screen 0 - - move to the right edge of the screen 0 - - 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 - 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 +Possible moves of the depicted window would be: + - 'move left' will move window to to the left half on screen 0 + - 'move right' will move window to to the right half on screen 0 + +Let's assume that we chose the latter, so the new layout would be as follow: + + +----------+----------+-----------------------------+--+ + | | window | | | + | | | +--+ + | | | | | + | | | +--+ + | | | | | + | | | +--+ + | | | | | + | | | +--+ + | | | | + +--+ +----------+--+--+ | + | | 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 -Author: Roman 'gryf' Dobosz +Author: Roman "gryf" Dobosz Date: 2013-01-06 Date: 2014-03-31 (used pygtk instead of xrandr, which is faster) Date: 2014-06-25 added docopt, corrections and simplify the process Date: 2015-10-12 added debug option, figured out wmaker decorations calculation method -Version: 1.4 +Date: 2015-12-13 Added simple detection of certain windows, which doesn't + behave nicely - mostly QT apps +Version: 1.5 """ -from docopt import docopt from subprocess import Popen, PIPE, call import logging import os import re import sys +from docopt import docopt from gtk import gdk @@ -72,6 +139,7 @@ from gtk import gdk COVER_MINIWINDOWS = True COVER_DOCK = False + def get_magic_number(): """Get the numbers for window shift and position""" @@ -92,6 +160,16 @@ def get_magic_number(): MAGIC_NO, DECORATIONS_HEIGHT = get_magic_number() +def get_window_name(): + """Return the current active window name""" + + name = Popen(["xdotool", "getactivewindow", "getwindowname"], + stdout=PIPE).communicate()[0] + name = name.strip() + logging.debug('window name: %s', name) + return name + + def get_monitors(): """Get monitors information: name @@ -145,19 +223,17 @@ class Screens(object): left-half maximized, right-half maximized. If so, return appropriate information, None otherwise """ - logging.debug("window - %s", str(window)) - for scr in self.screens: if window == scr.left_half: - return "left" + return 'left' if window == scr.right_half: - return "right" + return 'right' # check for maximized window (approximated) if window['pos_x'] == window['pos_y'] == 0: if window['size_x'] in range(scr.x - 32, scr.x + 32) and \ window['size_x'] in range(scr.x - 32, scr.x + 32): - return "maximized" + return 'maximized' return None @@ -167,6 +243,8 @@ class Screen(object): Holds separate display information. It can be separate X screen or just a display/monitor """ + misbehaving_windows = ["Oracle VM VirtualBox", "LibreOffice"] + def __init__(self, x=0, y=0, sx=0, sy=0): """Initialization""" self.x = int(x) @@ -190,12 +268,13 @@ class Screen(object): "size_x": 0, "size_y": 0} - def calculate_columns(self): + def calculate_columns(self, window_name): """ Calculate dimension grid, which two column windows could occupy, make it pixel exact. """ sx, sy = self.x, self.y + logging.debug('sx, and sy: %d, %d', sx, sy) if sx % 2 != 0: # it is rare, but hell, shit happens @@ -208,7 +287,7 @@ class Screen(object): self.x = sx = sx - 2 # miniwindows on bottom + 2px for border - logging.debug("calculate_columns %s", COVER_MINIWINDOWS) + logging.debug('Covering miniwindows: %s', COVER_MINIWINDOWS) if not COVER_MINIWINDOWS: self.y = sy = sy - (64 + 2) @@ -223,6 +302,17 @@ class Screen(object): self.maximized['size_y'] = self.right_half['size_y'] = \ 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): """ @@ -245,6 +335,7 @@ class WMWindow(object): self.current_screen = 0 self.state = None self._main = main + self.name = get_window_name() self._discover_screens(monitors) self._get_props() @@ -282,7 +373,7 @@ class WMWindow(object): winner = int(winner) logging.debug("predicted x position of the dock: %d", winner) - import ipdb; ipdb.set_trace() + import ripdb; ripdb.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): @@ -297,12 +388,13 @@ class WMWindow(object): """ 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] out = out.strip().split("\n") if len(out) != 3: - print "Cannot get window size and position" + logging.warning('Cannot get window size and position for %s', + self.name) return pos, size = out[1:] @@ -313,8 +405,12 @@ class WMWindow(object): # XXX: arbitrary correction of the window position. Don't know why # xdotool reports such strange data - maybe it is connected with # 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_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): if self.pos_x in range(scr.x_shift, scr.x + scr.x_shift): @@ -328,8 +424,10 @@ class WMWindow(object): self.y = int(self.y) if None in (self.x, self.y, self.pos_x, self.pos_y): - print "Not enough data for calculate window placement" - print self.x, self.y, self.pos_x, self.pos_y + logging.warning('Not enough data for calculate window placement. ' + 'Window name "%s", (x, y, pos_x, pos_y): ' + '%d, %d, %d, %d', self.name, self.x, self.y, + self.pos_x, self.pos_y) else: self.guess_dimensions() @@ -366,8 +464,7 @@ class WMWindow(object): self._detect_dock_position() for screen in self.screens.screens: - screen.calculate_columns() - + screen.calculate_columns(self.name) def get_data(self): """Return current window coordinates and size""" @@ -399,7 +496,7 @@ class WMWindow(object): return True def get_coords(self, which): - """Return screen coordinates""" + """Return window in screen coordinates""" scr = self.screens.screens[self.current_screen] coord_map = {"maximized": scr.maximized, @@ -435,16 +532,16 @@ def cycle(monitors, right=False, main=None): coords = wmwin.get_coords(key) if order: - cmd = ['xdotool', "getactivewindow", - "windowsize", str(coords['size_x']), str(coords['size_y']), - "windowmove", str(coords['pos_x']), str(coords['pos_y']), - "mousemove", str(coords['pos_x'] + coords['size_x'] / 2), + cmd = ['xdotool', 'getactivewindow', + 'windowsize', str(coords['size_x']), str(coords['size_y']), + 'windowmove', str(coords['pos_x']), str(coords['pos_y']), + 'mousemove', str(coords['pos_x'] + coords['size_x'] / 2), str(coords['pos_y'] + coords['size_y'] / 2)] else: - cmd = ['xdotool', "getactivewindow", - "windowmove", str(coords['pos_x']), str(coords['pos_y']), - "windowsize", str(coords['size_x']), str(coords['size_y']), - "mousemove", str(coords['pos_x'] + coords['size_x'] / 2), + cmd = ['xdotool', 'getactivewindow', + 'windowmove', str(coords['pos_x']), str(coords['pos_y']), + 'windowsize', str(coords['size_x']), str(coords['size_y']), + 'mousemove', str(coords['pos_x'] + coords['size_x'] / 2), str(coords['pos_y'] + coords['size_y'] / 2)] call(cmd) @@ -455,22 +552,22 @@ def show_monitors(monitors): print "Available monitors:" for name, data in monitors.items(): mon = data.copy() - mon.update({"name": name}) - print "%(name)s at %(sx)sx%(sy)s with dimensions %(x)sx%(y)s" % mon + mon.update({'name': name}) + logging.debug('%(name)s at %(sx)sx%(sy)s with dimensions %(x)sx%(y)s', mon) def move_mouse(monitors, name): - """Move the mouse pointer to the left upper corner oft the specified by + """Move the mosue pointer to the left upper corner oft the specified by the name screen""" mon = monitors.get(name) if not mon: - print "No such monitor: %s" % name + logging.warning('No such monitor: %s', name) return - posx = mon["sx"] + 15 - posy = mon["sy"] + 50 - cmd = ['xdotool', "mousemove", str(posx), str(posy)] + posx = mon['sx'] + 15 + posy = mon['sy'] + 50 + cmd = ['xdotool', 'mousemove', str(posx), str(posy)] call(cmd) @@ -495,26 +592,26 @@ Options: -v --version Show version. -d --debug Show debug messages. -""" % {"prog": sys.argv[0]} - opts = docopt(arguments, version=1.4) +""" % {'prog': sys.argv[0]} + opts = docopt(arguments, version=1.5) level = logging.DEBUG if opts['--debug'] else logging.WARNING - logging.basicConfig(format='%(lineno)s %(funcName)s(): %(message)s', + logging.basicConfig(filename=os.path.expanduser('~/moveto.log'), + format='%(funcName)s:%(lineno)d %(message)s', level=level) - monitors = get_monitors() - if opts["showmonitors"]: + if opts['showmonitors']: show_monitors(monitors) return - if opts["mousemove"]: + if opts['mousemove']: move_mouse(monitors, opts['--monitor-name']) return - if opts["move"]: + if opts['move']: set_cover() - cycle(monitors, bool(opts["right"]), main=opts["--monitor-name"]) + cycle(monitors, bool(opts['right']), main=opts['--monitor-name']) -if __name__ == "__main__": +if __name__ == '__main__': main()