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

Added some support for positioning misbehaving windows

This commit is contained in:
2017-01-13 18:49:59 +01:00
parent 0894af3ba6
commit a5fb065b21

219
moveto.py Normal file → Executable file
View File

@@ -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 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:
+---------------------+-----------------------------+--+ +---------------------+-----------------------------+--+
| | | | | | | |
| +--------+ | +--+ | +--------+ | +--+
@@ -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: Possible moves of the depicted window would be:
1. without resizing: - 'move left' will move window to to the left half on screen 0
- move to the left edge of the screen 0 - 'move right' will move window to to the right half on screen 0
- move to the right edge of the screen 0
- move to the left edge of the screen 1 Let's assume that we chose the latter, so the new layout would be as follow:
- move to the right edge of the screen 1
- move to the screen 1 (don't cross boundary of the +----------+----------+-----------------------------+--+
screen) | | window | | |
- 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
Date: 2015-10-12 added debug option, figured out wmaker decorations Date: 2015-10-12 added debug option, figured out wmaker decorations
calculation method 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 from subprocess import Popen, PIPE, call
import logging import logging
import os import os
import re import re
import sys import sys
from docopt import docopt
from gtk import gdk from gtk import gdk
@@ -72,6 +139,7 @@ from gtk import gdk
COVER_MINIWINDOWS = True COVER_MINIWINDOWS = True
COVER_DOCK = False COVER_DOCK = False
def get_magic_number(): def get_magic_number():
"""Get the numbers for window shift and position""" """Get the numbers for window shift and position"""
@@ -92,6 +160,16 @@ def get_magic_number():
MAGIC_NO, DECORATIONS_HEIGHT = 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(): def get_monitors():
"""Get monitors information: """Get monitors information:
name name
@@ -145,19 +223,17 @@ class Screens(object):
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'
if window == scr.right_half: if window == scr.right_half:
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
@@ -167,6 +243,8 @@ 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)
@@ -190,12 +268,13 @@ class Screen(object):
"size_x": 0, "size_x": 0,
"size_y": 0} "size_y": 0}
def calculate_columns(self): def calculate_columns(self, window_name):
""" """
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
@@ -208,7 +287,7 @@ 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("calculate_columns %s", COVER_MINIWINDOWS) logging.debug('Covering miniwindows: %s', COVER_MINIWINDOWS)
if not COVER_MINIWINDOWS: if not COVER_MINIWINDOWS:
self.y = sy = sy - (64 + 2) self.y = sy = sy - (64 + 2)
@@ -223,6 +302,17 @@ class Screen(object):
self.maximized['size_y'] = self.right_half['size_y'] = \ self.maximized['size_y'] = self.right_half['size_y'] = \
self.left_half['size_y'] = sy - DECORATIONS_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):
""" """
@@ -245,6 +335,7 @@ 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()
@@ -282,7 +373,7 @@ class WMWindow(object):
winner = int(winner) winner = int(winner)
logging.debug("predicted x position of the dock: %d", 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: for screen in self.screens.screens:
logging.debug("screen: %s", str(screen)) logging.debug("screen: %s", str(screen))
if winner in range(screen.x_shift, screen.x + screen.x_shift + 1): 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 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:
print "Cannot get window size and position" logging.warning('Cannot get window size and position for %s',
self.name)
return return
pos, size = out[1:] pos, size = out[1:]
@@ -313,8 +405,12 @@ 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) - MAGIC_NO 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):
@@ -328,8 +424,10 @@ 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):
print "Not enough data for calculate window placement" logging.warning('Not enough data for calculate window placement. '
print self.x, self.y, self.pos_x, self.pos_y '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: else:
self.guess_dimensions() self.guess_dimensions()
@@ -366,8 +464,7 @@ class WMWindow(object):
self._detect_dock_position() self._detect_dock_position()
for screen in self.screens.screens: for screen in self.screens.screens:
screen.calculate_columns() screen.calculate_columns(self.name)
def get_data(self): def get_data(self):
"""Return current window coordinates and size""" """Return current window coordinates and size"""
@@ -399,7 +496,7 @@ class WMWindow(object):
return True return True
def get_coords(self, which): def get_coords(self, which):
"""Return screen coordinates""" """Return window in 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,
@@ -435,16 +532,16 @@ 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)
@@ -455,22 +552,22 @@ def show_monitors(monitors):
print "Available monitors:" print "Available monitors:"
for name, data in monitors.items(): for name, data in monitors.items():
mon = data.copy() mon = data.copy()
mon.update({"name": name}) mon.update({'name': name})
print "%(name)s at %(sx)sx%(sy)s with dimensions %(x)sx%(y)s" % mon logging.debug('%(name)s at %(sx)sx%(sy)s with dimensions %(x)sx%(y)s', mon)
def move_mouse(monitors, name): 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""" the name screen"""
mon = monitors.get(name) mon = monitors.get(name)
if not mon: if not mon:
print "No such monitor: %s" % name logging.warning('No such monitor: %s', name)
return return
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)
@@ -495,26 +592,26 @@ Options:
-v --version Show version. -v --version Show version.
-d --debug Show debug messages. -d --debug Show debug messages.
""" % {"prog": sys.argv[0]} """ % {'prog': sys.argv[0]}
opts = docopt(arguments, version=1.4) opts = docopt(arguments, version=1.5)
level = logging.DEBUG if opts['--debug'] else logging.WARNING 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) level=level)
monitors = get_monitors() monitors = get_monitors()
if opts["showmonitors"]: if opts['showmonitors']:
show_monitors(monitors) show_monitors(monitors)
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']:
set_cover() 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() main()