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
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 <gryf73@gmail.com>
Author: Roman "gryf" Dobosz <gryf73@gmail.com>
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()