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:
219
moveto.py
Normal file → Executable file
219
moveto.py
Normal file → Executable 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()
|
||||||
|
|||||||
Reference in New Issue
Block a user