From f7b59a027060697d2a1b1c9a319f19823a140041 Mon Sep 17 00:00:00 2001 From: Lukas Rysavy Date: Wed, 27 May 2020 08:46:30 +0200 Subject: [PATCH 01/11] fixed multi screen x offset for dual monitor mode --- snaptile.py | 8 ++++++-- window.py | 7 +++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/snaptile.py b/snaptile.py index e74586b..1545f1a 100755 --- a/snaptile.py +++ b/snaptile.py @@ -97,7 +97,7 @@ def global_inital_states(): get_posmap(keymap, displ) ) -global disp, root, lastkey_state, posmap; +global disp, root, lastkey_state, posmap, isDualMonitor; def run(): @@ -105,6 +105,9 @@ def run(): opts, args = getopt.getopt(sys.argv[1:], "hdWk:") keyboardLayout = autodetectKeyboard() + + global isDualMonitor + isDualMonitor = False for opt in opts: @@ -175,7 +178,8 @@ def checkevt(_, __, handle=None): def handleevt(startkey, endkey): position( posmap[startkey], - posmap[endkey] + posmap[endkey], + isDualMonitor, ) if __name__ == '__main__': diff --git a/window.py b/window.py index a53faed..c959af6 100644 --- a/window.py +++ b/window.py @@ -1,6 +1,6 @@ from gi.repository import Gdk -def position(startpos, endpos): +def position(startpos, endpos, dualMonitor): window, screen = active_window() window.unmaximize() window.set_shadow_width(0, 0, 0, 0) @@ -19,7 +19,10 @@ def position(startpos, endpos): ) - multiscreen_offset = get_multi_screen_offset(screen, window) + if dualMonitor: + multiscreen_offset = 0 + else: + multiscreen_offset = get_multi_screen_offset(screen, window) window.move_resize( pos[1] * w + multiscreen_offset, From 9c1bd6c6577fae7d665e0da84b49aee03adde7e0 Mon Sep 17 00:00:00 2001 From: Lukas Rysavy Date: Wed, 24 Jun 2020 18:28:05 +0200 Subject: [PATCH 02/11] fixed multi screen offset handling for misaligned screens --- window.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/window.py b/window.py index c959af6..8eb7a8a 100644 --- a/window.py +++ b/window.py @@ -5,6 +5,7 @@ def position(startpos, endpos, dualMonitor): window.unmaximize() window.set_shadow_width(0, 0, 0, 0) workarea = screen.get_monitor_workarea(screen.get_monitor_at_window(window)) + display = Gdk.Display.get_default() offx, offy = offsets(window) w, h = (workarea.width / 4, workarea.height / 3) @@ -24,11 +25,19 @@ def position(startpos, endpos, dualMonitor): else: multiscreen_offset = get_multi_screen_offset(screen, window) + if dualMonitor: + screen_y_offset = [ + monitor_y_offset(display, min(startpos, endpos, key=lambda x: x[0]*10 + x[1])[1]), + monitor_y_offset(display, max(startpos, endpos, key=lambda x: x[0]*10 + x[1])[1]), + ] + else: + screen_y_offset = [0, 0] + window.move_resize( pos[1] * w + multiscreen_offset, - pos[0] * h, + pos[0] * h + screen_y_offset[0], w * dims[1] - (offx * 2), - h * dims[0]- (offx + offy) + h * dims[0]- (offx + offy) + screen_y_offset[1] - screen_y_offset[0] ) def active_window(): @@ -61,3 +70,11 @@ def no_window(screen, window): ) or window.get_type_hint().value_name == 'GDK_WINDOW_TYPE_HINT_DESKTOP' ) + + +def monitor_y_offset(display, x): + left_monitor = display.get_monitor_at_point(0, 0) + right_monitor = display.get_monitor(1) \ + if left_monitor == display.get_monitor(0) \ + else display.get_monitor(0) + return [left_monitor, right_monitor][x // 4].get_workarea().y From 1da7b25cd076628b91745d1b718a863c18161528 Mon Sep 17 00:00:00 2001 From: Lukas Rysavy Date: Wed, 24 Jun 2020 18:28:32 +0200 Subject: [PATCH 03/11] fixed crash on shortcut without window focus --- window.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/window.py b/window.py index 8eb7a8a..ecd5ae9 100644 --- a/window.py +++ b/window.py @@ -2,6 +2,8 @@ def position(startpos, endpos, dualMonitor): window, screen = active_window() + if window is None: + return window.unmaximize() window.set_shadow_width(0, 0, 0, 0) workarea = screen.get_monitor_workarea(screen.get_monitor_at_window(window)) @@ -45,7 +47,7 @@ def active_window(): window = screen.get_active_window() if no_window(screen, window): - return None + return None, None return (window, screen) From bbb952e9d4b23626597daa9102f759f97d2912c7 Mon Sep 17 00:00:00 2001 From: Lukas Rysavy Date: Mon, 29 Jun 2020 11:10:57 +0200 Subject: [PATCH 04/11] reworked geometry calculation to be able to handle differently sized monitors --- window.py | 77 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/window.py b/window.py index ecd5ae9..aeaf4d1 100644 --- a/window.py +++ b/window.py @@ -6,42 +6,54 @@ def position(startpos, endpos, dualMonitor): return window.unmaximize() window.set_shadow_width(0, 0, 0, 0) - workarea = screen.get_monitor_workarea(screen.get_monitor_at_window(window)) + display = Gdk.Display.get_default() + if dualMonitor: + monitor = get_target_monitor(display, startpos[1]) + workarea = monitor.get_workarea() + end_monitor = get_target_monitor(display, endpos[1]) + end_workarea = end_monitor.get_workarea() + else: + monitor = screen.get_monitor_at_window(window) + workarea = screen.get_monitor_workarea(monitor) + # same screen -> same workarea on both corners + end_workarea = workarea - offx, offy = offsets(window) w, h = (workarea.width / 4, workarea.height / 3) - - pos = ( - min(startpos[0], endpos[0]), - min(startpos[1], endpos[1]) + end_w, end_h = (end_workarea.width / 4, end_workarea.height / 3) + + # each contain top left and bottom right position of the respective cell + first_corner = ( + (startpos[1] % 4) * w + workarea.x, + startpos[0] * h + workarea.y, + (startpos[1] % 4 + 1) * w + workarea.x, + (startpos[0] + 1) * h + workarea.y, ) - dims = ( - max(abs(endpos[0] - startpos[0]) + 1, 1), - max(abs(endpos[1] - startpos[1]) + 1, 1) + second_corner = ( + (endpos[1] % 4) * end_w + end_workarea.x, + endpos[0] * end_h + end_workarea.y, + (endpos[1] % 4 + 1) * end_w + end_workarea.x, + (endpos[0] + 1) * end_h + end_workarea.y, ) + top_left, bottom_right = ( + # use top left corner of cells (0 & 1) + (min(first_corner[0], second_corner[0]), min(first_corner[1], second_corner[1])), + # use bottom right corner of cells (2 & 3) + (max(first_corner[2], second_corner[2]), max(first_corner[3], second_corner[3])), + ) - if dualMonitor: - multiscreen_offset = 0 - else: - multiscreen_offset = get_multi_screen_offset(screen, window) - - if dualMonitor: - screen_y_offset = [ - monitor_y_offset(display, min(startpos, endpos, key=lambda x: x[0]*10 + x[1])[1]), - monitor_y_offset(display, max(startpos, endpos, key=lambda x: x[0]*10 + x[1])[1]), - ] - else: - screen_y_offset = [0, 0] + dims = ( + bottom_right[0] - top_left[0], + bottom_right[1] - top_left[1], + ) window.move_resize( - pos[1] * w + multiscreen_offset, - pos[0] * h + screen_y_offset[0], - w * dims[1] - (offx * 2), - h * dims[0]- (offx + offy) + screen_y_offset[1] - screen_y_offset[0] + *top_left, + *dims, ) + def active_window(): screen = Gdk.Screen.get_default() window = screen.get_active_window() @@ -51,16 +63,6 @@ def active_window(): return (window, screen) -def get_multi_screen_offset(screen,window): - monitor = screen.get_monitor_at_window(window) - monitor_geometry = screen.get_monitor_geometry(monitor) - return monitor_geometry.x - -def offsets(window): - origin = window.get_origin() - root = window.get_root_origin() - return (origin.x - root.x, origin.y - root.y) - def no_window(screen, window): return ( @@ -74,9 +76,10 @@ def no_window(screen, window): ) -def monitor_y_offset(display, x): +def get_target_monitor(display, x): + # NOTE: only works for up to 2 monitors!! left_monitor = display.get_monitor_at_point(0, 0) right_monitor = display.get_monitor(1) \ if left_monitor == display.get_monitor(0) \ else display.get_monitor(0) - return [left_monitor, right_monitor][x // 4].get_workarea().y + return [left_monitor, right_monitor][x // 4] From 190b92bdb954946c3e55f0d9dde0a64defd54d40 Mon Sep 17 00:00:00 2001 From: Lukas Rysavy Date: Wed, 1 Jul 2020 11:15:10 +0200 Subject: [PATCH 05/11] allow termination via kill --- snaptile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/snaptile.py b/snaptile.py index 1545f1a..4278195 100755 --- a/snaptile.py +++ b/snaptile.py @@ -144,6 +144,7 @@ def run(): GObject.io_add_watch(root.display, GObject.IO_IN, checkevt) print('Snaptile running. Press CTRL+C to quit.') signal.signal(signal.SIGINT, signal.SIG_DFL) + signal.signal(signal.SIGTERM, signal.SIG_DFL) Gtk.main() def checkevt(_, __, handle=None): From 4c93be80135663442986418782655bd007d6f53e Mon Sep 17 00:00:00 2001 From: Lukas Rysavy Date: Tue, 18 Aug 2020 09:48:30 +0200 Subject: [PATCH 06/11] replaced deprecated GObject method call with call to newer GLib method --- snaptile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snaptile.py b/snaptile.py index 4278195..65a1468 100755 --- a/snaptile.py +++ b/snaptile.py @@ -9,7 +9,7 @@ import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, GObject +from gi.repository import Gtk, GLib from window import position from keyutil import get_posmap, initkeys @@ -141,7 +141,7 @@ def run(): initkeys(keymap, disp, root, mask) for _ in range(0, root.display.pending_events()): root.display.next_event() - GObject.io_add_watch(root.display, GObject.IO_IN, checkevt) + GLib.io_add_watch(root.display, GLib.IO_IN, checkevt) print('Snaptile running. Press CTRL+C to quit.') signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGTERM, signal.SIG_DFL) From 6847fd50b850c56fe3be0b298a26a6c60d63eb10 Mon Sep 17 00:00:00 2001 From: Lukas Rysavy Date: Tue, 18 Aug 2020 11:59:21 +0200 Subject: [PATCH 07/11] Fixed 'bug' in Xlib ...which ate key release events (apparently, this is intentional), see https://bugs.freedesktop.org/show_bug.cgi?id=99280 --- snaptile.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/snaptile.py b/snaptile.py index 65a1468..988f8ae 100755 --- a/snaptile.py +++ b/snaptile.py @@ -109,7 +109,7 @@ def run(): global isDualMonitor isDualMonitor = False - + for opt in opts: if opt[0] == '-h': print ('Snaptile.py') @@ -147,18 +147,24 @@ def run(): signal.signal(signal.SIGTERM, signal.SIG_DFL) Gtk.main() +# counter for how many keys are currently pressed to fix xlib 'bug' +keys_pressed = 0 + def checkevt(_, __, handle=None): - global lastkey_state + global lastkey_state, keys_pressed handle = handle or root.display for _ in range(0, handle.pending_events()): event = handle.next_event() if event.type == X.KeyPress: - + keys_pressed += 1 if event.detail not in posmap: break + # prevent loosing double press release events + root.grab_keyboard(1, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime) + if not lastkey_state['pressed']: handleevt(event.detail, event.detail) @@ -171,6 +177,17 @@ def checkevt(_, __, handle=None): } if event.type == X.KeyRelease: + # prevent going under 0 since we get + # one last release event from the modifier key + keys_pressed = max(keys_pressed-1, 0) + if keys_pressed == 0: + # no more keys pressed, so ungrab keyboard + disp.flush() + disp.ungrab_keyboard(X.CurrentTime) + else: + # grab keyboard to avoid losing KeyRelease event + root.grab_keyboard(1, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime) + if event.detail == lastkey_state['code']: lastkey_state['pressed'] = False From 8403289175e81f2b2377c2f8658af4e9e3519b63 Mon Sep 17 00:00:00 2001 From: Lukas Rysavy Date: Wed, 1 Jul 2020 16:58:08 +0200 Subject: [PATCH 08/11] added fill shortcut enable with the -f switch on double press on a single tile, greedily fills as many tiles as possible around this one without intersecting with other windows. windows which intersect with the given tile are excluded (or no results would be possible) --- snaptile.py | 31 +++++++++++++++++---- window.py | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 6 deletions(-) diff --git a/snaptile.py b/snaptile.py index 988f8ae..904e49d 100755 --- a/snaptile.py +++ b/snaptile.py @@ -10,9 +10,11 @@ import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GLib -from window import position +from window import position, fill from keyutil import get_posmap, initkeys +import time + keymaps = { "qwerty": (['Q', 'W', 'E', 'R'], @@ -59,6 +61,9 @@ ['semicolon', 'Q', 'J', 'K', 'X', 'B', 'M', 'W']), } +# delay for double presses +fill_delay = 0.5 + def autodetectKeyboard(): @@ -97,19 +102,21 @@ def global_inital_states(): get_posmap(keymap, displ) ) -global disp, root, lastkey_state, posmap, isDualMonitor; +global disp, root, lastkey_state, posmap, isDualMonitor, fillEnabled; def run(): mask = None - opts, args = getopt.getopt(sys.argv[1:], "hdWk:") + opts, args = getopt.getopt(sys.argv[1:], "hdWk:f") keyboardLayout = autodetectKeyboard() - global isDualMonitor + global isDualMonitor, fillEnabled isDualMonitor = False + fillEnabled = False + for opt in opts: if opt[0] == '-h': print ('Snaptile.py') @@ -117,6 +124,7 @@ def run(): print ('-W use Windows key') print ('-h this help text') print ('-k to specify a keyboard layout (eg. qwerty)') + print ('-f enable filling available space on double press') sys.exit() elif opt[0] == '-d': isDualMonitor = True @@ -124,6 +132,8 @@ def run(): mask = 'Windows' elif opt[0] == '-k': keyboardLayout = opt[1] + elif opt[0] == '-f': + fillEnabled = True global keymap; keymapSource = keymaps @@ -166,14 +176,20 @@ def checkevt(_, __, handle=None): root.grab_keyboard(1, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime) if not lastkey_state['pressed']: - handleevt(event.detail, event.detail) + if fillEnabled and \ + lastkey_state['code'] == event.detail and \ + time.time() - lastkey_state['time'] < fill_delay: + handle_fill(event.detail) + else: + handleevt(event.detail, event.detail) else: handleevt(lastkey_state['code'], event.detail) lastkey_state = { 'code': event.detail, - 'pressed': True + 'pressed': True, + 'time': time.time(), } if event.type == X.KeyRelease: @@ -200,5 +216,8 @@ def handleevt(startkey, endkey): isDualMonitor, ) +def handle_fill(key): + fill(posmap[key], isDualMonitor) + if __name__ == '__main__': run() diff --git a/window.py b/window.py index aeaf4d1..c2d7ff0 100644 --- a/window.py +++ b/window.py @@ -1,4 +1,5 @@ from gi.repository import Gdk +from itertools import product def position(startpos, endpos, dualMonitor): window, screen = active_window() @@ -54,6 +55,85 @@ def position(startpos, endpos, dualMonitor): ) +def fill(pos, dualMonitor): + screen = Gdk.Screen.get_default() + display = Gdk.Display.get_default() + window = screen.get_active_window() + if dualMonitor: + monitor = get_target_monitor(display, pos[1]) + else: + monitor = display.get_monitor_at_window(window) + other_wins = [ + win for win in screen.get_window_stack() + if win.get_desktop() == window.get_desktop() + and win != window + ] + win_geometries = [win.get_frame_extents() for win in other_wins] + initial = grid_to_xywh(pos, monitor) + # filter out windows which overlap no matter what + win_geometries = [win for win in win_geometries if not overlaps(initial, geom_to_tuple(win))] + max_tiles = 0 + best_pos = () + # try all possibilities and choose the one with the most tiles + # at least one is valid (the single tile), since we filter out windows + # which conflict this tile above + for ylow, yhigh, xlow, xhigh in product( + range(0, pos[0] + 1), + range(pos[0], 3), + range(0, (pos[1] % 4) + 1), + range((pos[1] % 4), 4), + ): + tiles = (xhigh-xlow+1) * (yhigh-ylow+1) + if tiles < max_tiles: + continue + top_left = (ylow, xlow) + bot_right = (yhigh, xhigh) + abs_top_left = grid_to_coords(top_left, monitor)[:2] + abs_bot_right = grid_to_coords(bot_right, monitor)[2:] + abs_pos = ( + *abs_top_left, + abs_bot_right[0] - abs_top_left[0], + abs_bot_right[1] - abs_top_left[1], + ) + if not any(overlaps(abs_pos, geom_to_tuple(geom)) for geom in win_geometries): + max_tiles = tiles + best_pos = abs_pos + + window.unmaximize() + window.set_shadow_width(0, 0, 0, 0) + window.move_resize(*best_pos) + + +def grid_to_coords(pos, monitor): + geom = monitor.get_geometry() + x = geom.x + (pos[1] % 4) * geom.width // 4 + y = geom.y + (pos[0] % 3) * geom.height // 3 + return (x, y, x + geom.width // 4, y + geom.height // 3) + + +def grid_to_xywh(pos, monitor): + coords = grid_to_coords(pos, monitor) + return ( + coords[0], + coords[1], + coords[2] - coords[0], + coords[3] - coords[1], + ) + + +def geom_to_tuple(geom): + return (geom.x, geom.y, geom.width, geom.height) + + +def overlaps(geom1, geom2): + return not ( + geom1[0] >= geom2[0] + geom2[2] or + geom1[0] + geom1[2] <= geom2[0] or + geom1[1] >= geom2[1] + geom2[3] or + geom1[1] + geom1[3] <= geom2[1] + ) + + def active_window(): screen = Gdk.Screen.get_default() window = screen.get_active_window() From e7250f4d9ef2536ddd24cd5fd14619a035463560 Mon Sep 17 00:00:00 2001 From: Lukas Rysavy Date: Mon, 6 Jul 2020 16:34:53 +0200 Subject: [PATCH 09/11] amended readme to contain new features --- README.md | 15 +++++++++++++++ window.py | 1 + 2 files changed, 16 insertions(+) diff --git a/README.md b/README.md index 1571906..2d2fa82 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,13 @@ The grid system divides your screen into 12 sections | A| S| D| F| | Z| X| C| V| +or, in dual screen mode (`-d` switch), into 12 sections each, with extended keybindings for the second screen: + +| T| Y| U| I| +|--|--|--|--| +| G| H| J| K| +| B| N| M| ,| + You can snap your window to any rectangle, of any arbitrary size, on this grid by specifying 2 corners. For example: ctl + alt + E + D @@ -43,6 +50,14 @@ which looks like ![screenshot from 2017-06-07 22-55-56](https://user-images.githubusercontent.com/5866348/26910417-b381baca-4bd4-11e7-9ff7-fff9262743e8.png) +### Fill +With the `-f` switch, filling is activated. On double press of any of the shortcuts, snaptile will try to find as many sections +around the specified one as possible without intersecting with other windows, and fill the largest match. This will still align to the 4x3 grid. +Windows which obstruct the initial section are excluded from intersection, so they might be partially or even completely occluded. + +For example, if there is a window between tiles Q and S, double pressing ctl + alt + X +will have the same outcome as Z and V. Double C, on the other hand, will result in E and V. + ## Requirements * Python3 diff --git a/window.py b/window.py index c2d7ff0..545cfab 100644 --- a/window.py +++ b/window.py @@ -104,6 +104,7 @@ def fill(pos, dualMonitor): window.move_resize(*best_pos) +# TODO move to geom_utils def grid_to_coords(pos, monitor): geom = monitor.get_geometry() x = geom.x + (pos[1] % 4) * geom.width // 4 From 45b410ee16bcb7e4fc41d25b56f73c16398ee8b1 Mon Sep 17 00:00:00 2001 From: Lukas Rysavy Date: Tue, 7 Jul 2020 09:55:42 +0200 Subject: [PATCH 10/11] split up window.py --- geom_utils.py | 28 ++++++++++++++++++++++++++++ window.py | 32 +------------------------------- 2 files changed, 29 insertions(+), 31 deletions(-) create mode 100644 geom_utils.py diff --git a/geom_utils.py b/geom_utils.py new file mode 100644 index 0000000..1496807 --- /dev/null +++ b/geom_utils.py @@ -0,0 +1,28 @@ +def grid_to_coords(pos, monitor): + geom = monitor.get_geometry() + x = geom.x + (pos[1] % 4) * geom.width // 4 + y = geom.y + (pos[0] % 3) * geom.height // 3 + return (x, y, x + geom.width // 4, y + geom.height // 3) + + +def grid_to_xywh(pos, monitor): + coords = grid_to_coords(pos, monitor) + return ( + coords[0], + coords[1], + coords[2] - coords[0], + coords[3] - coords[1], + ) + + +def geom_to_tuple(geom): + return (geom.x, geom.y, geom.width, geom.height) + + +def overlaps(geom1, geom2): + return not ( + geom1[0] >= geom2[0] + geom2[2] or + geom1[0] + geom1[2] <= geom2[0] or + geom1[1] >= geom2[1] + geom2[3] or + geom1[1] + geom1[3] <= geom2[1] + ) diff --git a/window.py b/window.py index 545cfab..0dbd2b8 100644 --- a/window.py +++ b/window.py @@ -1,5 +1,6 @@ from gi.repository import Gdk from itertools import product +from geom_utils import grid_to_coords, grid_to_xywh, geom_to_tuple, overlaps def position(startpos, endpos, dualMonitor): window, screen = active_window() @@ -104,37 +105,6 @@ def fill(pos, dualMonitor): window.move_resize(*best_pos) -# TODO move to geom_utils -def grid_to_coords(pos, monitor): - geom = monitor.get_geometry() - x = geom.x + (pos[1] % 4) * geom.width // 4 - y = geom.y + (pos[0] % 3) * geom.height // 3 - return (x, y, x + geom.width // 4, y + geom.height // 3) - - -def grid_to_xywh(pos, monitor): - coords = grid_to_coords(pos, monitor) - return ( - coords[0], - coords[1], - coords[2] - coords[0], - coords[3] - coords[1], - ) - - -def geom_to_tuple(geom): - return (geom.x, geom.y, geom.width, geom.height) - - -def overlaps(geom1, geom2): - return not ( - geom1[0] >= geom2[0] + geom2[2] or - geom1[0] + geom1[2] <= geom2[0] or - geom1[1] >= geom2[1] + geom2[3] or - geom1[1] + geom1[3] <= geom2[1] - ) - - def active_window(): screen = Gdk.Screen.get_default() window = screen.get_active_window() From 71d51b2574fd04284611e298e1425a99cef996c7 Mon Sep 17 00:00:00 2001 From: Lukas Rysavy Date: Mon, 17 Aug 2020 14:25:54 +0200 Subject: [PATCH 11/11] prevent focus stealing --- snaptile.py | 29 ++++++++++++++++++++--------- window.py | 14 ++++++++++---- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/snaptile.py b/snaptile.py index 904e49d..5ee9ff6 100755 --- a/snaptile.py +++ b/snaptile.py @@ -97,7 +97,8 @@ def global_inital_states(): rt, { 'code': 0, - 'pressed': False + 'pressed': False, + 'window': None, }, get_posmap(keymap, displ) ) @@ -116,7 +117,7 @@ def run(): isDualMonitor = False fillEnabled = False - + for opt in opts: if opt[0] == '-h': print ('Snaptile.py') @@ -175,21 +176,30 @@ def checkevt(_, __, handle=None): # prevent loosing double press release events root.grab_keyboard(1, X.GrabModeAsync, X.GrabModeAsync, X.CurrentTime) + win = None if not lastkey_state['pressed']: if fillEnabled and \ lastkey_state['code'] == event.detail and \ time.time() - lastkey_state['time'] < fill_delay: - handle_fill(event.detail) + win = handle_fill( + event.detail, + lastkey_state['window'], + ) else: - handleevt(event.detail, event.detail) + win = handleevt(event.detail, event.detail) else: - handleevt(lastkey_state['code'], event.detail) + win = handleevt( + lastkey_state['code'], + event.detail, + lastkey_state['window'], + ) lastkey_state = { 'code': event.detail, 'pressed': True, 'time': time.time(), + 'window': win, } if event.type == X.KeyRelease: @@ -209,15 +219,16 @@ def checkevt(_, __, handle=None): return True -def handleevt(startkey, endkey): - position( +def handleevt(startkey, endkey, window=None): + return position( posmap[startkey], posmap[endkey], isDualMonitor, + window, ) -def handle_fill(key): - fill(posmap[key], isDualMonitor) +def handle_fill(key, window=None): + return fill(posmap[key], isDualMonitor, window) if __name__ == '__main__': run() diff --git a/window.py b/window.py index 0dbd2b8..5b4a1da 100644 --- a/window.py +++ b/window.py @@ -2,8 +2,11 @@ from itertools import product from geom_utils import grid_to_coords, grid_to_xywh, geom_to_tuple, overlaps -def position(startpos, endpos, dualMonitor): - window, screen = active_window() +def position(startpos, endpos, dualMonitor, window=None): + if window is None: + window, screen = active_window() + else: + screen = Gdk.Screen.get_default() if window is None: return window.unmaximize() @@ -54,12 +57,14 @@ def position(startpos, endpos, dualMonitor): *top_left, *dims, ) + return window -def fill(pos, dualMonitor): +def fill(pos, dualMonitor, window=None): screen = Gdk.Screen.get_default() display = Gdk.Display.get_default() - window = screen.get_active_window() + if window is None: + window = screen.get_active_window() if dualMonitor: monitor = get_target_monitor(display, pos[1]) else: @@ -103,6 +108,7 @@ def fill(pos, dualMonitor): window.unmaximize() window.set_shadow_width(0, 0, 0, 0) window.move_resize(*best_pos) + return window def active_window():