aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Héda <ivan.heda@gmail.com>2018-02-12 23:15:59 +0100
committerIvan Héda <ivan.heda@gmail.com>2018-02-12 23:19:30 +0100
commitdb1a4d981d8191e2e3bc15fa4667f7bb29276aad (patch)
tree7ef5582fd140d0b99b474731c19b89fa4aad85a4
parentMerge branch 'shellcheck' (diff)
downloadmove-to-next-monitor-db1a4d981d8191e2e3bc15fa4667f7bb29276aad.tar.gz
move-to-next-monitor-db1a4d981d8191e2e3bc15fa4667f7bb29276aad.tar.bz2
move-to-next-monitor-db1a4d981d8191e2e3bc15fa4667f7bb29276aad.zip
Rewritten to python. Displays are arranged into list
-rw-r--r--.gitignore110
-rw-r--r--README.md21
-rwxr-xr-xmove-to-next-monitor200
3 files changed, 247 insertions, 84 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b3f845d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,110 @@
+.idea
+.ipynb_checkpoints
+
+# Created by .ignore support plugin (hsz.mobi)
+### Python template
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+.static_storage/
+.media/
+local_settings.py
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+
diff --git a/README.md b/README.md
index 3bdf339..3b4105e 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,22 @@
# move-to-next-monitor
-Script to move windows from one monitor to the next in Xubuntu
+Originally, this is fork of [jc00ke](https://github.com/jc00ke/move-to-next-monitor)'s `move-to-next-monitor`.
-Even though I don't use Xubuntu anymore, people are still interested
-in this script. Sweet! Glad it can help others.
+The logic has been rewritten to Python and changed slightly. Now, it cycles window through the monitors, which don't
+need to be same sized to work properly. The order of cycling could be controlled with `--horizontal-first`
+and `--reverse` parameters.
-I mapped `ctrl+alt+n` to exectute this script.
+Script requires to work properly:
+ * `xrandr`
+ * `xdotool`
+ * `wmctrl`
+ * `xprop`
+ * `python`
## Usage
```
-wget https://raw.githubusercontent.com/jc00ke/move-to-next-monitor/master/move-to-next-monitor
+wget https://raw.githubusercontent.com/vanaoff/move-to-next-monitor/master/move-to-next-monitor
chmod +x move-to-next-monitor
mv move-to-next-monitor /somewhere/in/your/$PATH
```
@@ -19,11 +25,8 @@ mv move-to-next-monitor /somewhere/in/your/$PATH
[We have one](code_of_conduct.md), and you're expected to follow it.
-## Support
-
-Since I don't use Xubuntu and therefore don't have a way to test changes, I'm going to rely on others to QA and give me feedback.
-
## Thanks
* [icyrock](http://icyrock.com/blog/2012/05/xubuntu-moving-windows-between-monitors/) post for initial development
* [@jordansissel](https://github.com/jordansissel) for his excellent [xdotool](https://github.com/jordansissel/xdotool)
+* [jc00ke](https://github.com/jc00ke/move-to-next-monitor)
diff --git a/move-to-next-monitor b/move-to-next-monitor
index 07a6ff0..33affce 100755
--- a/move-to-next-monitor
+++ b/move-to-next-monitor
@@ -1,75 +1,125 @@
-#!/bin/sh
-#
-# Move the current window to the next monitor.
-#
-# Also works only on one X screen (which is the most common case).
-#
-# Props to
-# http://icyrock.com/blog/2012/05/xubuntu-moving-windows-between-monitors/
-#
-# Unfortunately, both "xdotool getwindowgeometry --shell $window_id" and
-# checking "-geometry" of "xwininfo -id $window_id" are not sufficient, as
-# the first command does not respect panel/decoration offsets and the second
-# will sometimes give a "-0-0" geometry. This is why we resort to "xwininfo".
-
-screen_width=$(xdpyinfo | awk '/dimensions:/ { print $2; exit }' | cut -d"x" -f1)
-screen_height=$(xdpyinfo | awk '/dimensions:/ { print $2; exit }' | cut -d"x" -f2)
-display_width=$(xdotool getdisplaygeometry | cut -d" " -f1)
-display_height=$(xdotool getdisplaygeometry | cut -d" " -f2)
-window_id=$(xdotool getactivewindow)
-
-# Remember if it was maximized.
-window_horz_maxed=$(xprop -id "$window_id" _NET_WM_STATE | grep '_NET_WM_STATE_MAXIMIZED_HORZ')
-window_vert_maxed=$(xprop -id "$window_id" _NET_WM_STATE | grep '_NET_WM_STATE_MAXIMIZED_VERT')
-
-# Un-maximize current window so that we can move it
-wmctrl -ir "$window_id" -b remove,maximized_vert,maximized_horz
-
-# Read window position
-x=$(xwininfo -id "$window_id" | awk '/Absolute upper-left X:/ { print $4 }')
-y=$(xwininfo -id "$window_id" | awk '/Absolute upper-left Y:/ { print $4 }')
-
-# Subtract any offsets caused by panels or window decorations
-x_offset=$(xwininfo -id "$window_id" | awk '/Relative upper-left X:/ { print $4 }')
-y_offset=$(xwininfo -id "$window_id" | awk '/Relative upper-left Y:/ { print $4 }')
-x=$(( x - x_offset))
-y=$(( y - y_offset))
-
-# Compute new X position
-new_x=$((x + display_width))
-# Compute new Y position
-new_y=$((y + display_height))
-
-# If we would move off the right-most monitor, we set it to the left one.
-# We also respect the window's width here: moving a window off more than half its width won't happen.
-width=$(xdotool getwindowgeometry "$window_id" | awk '/Geometry:/ { print $2 }'|cut -d"x" -f1)
-if [ "$(( new_x + width / 2))" -gt "$screen_width" ]; then
- new_x=$((new_x - screen_width))
-fi
-
-height=$(xdotool getwindowgeometry "$window_id" | awk '/Geometry:/ { print $2 }'|cut -d"x" -f2)
-if [ "$((new_y + height / 2))" -gt "$screen_height" ]; then
- new_y=$((new_y - screen_height))
-fi
-
-# Don't move off the left side.
-if [ "$new_x" -lt 0 ]; then
- new_x=0
-fi
-
-# Don't move off the bottom
-if [ "$new_y" -lt 0 ]; then
- new_y=0
-fi
-
-# Move the window
-xdotool windowmove "$window_id" "$new_x" "$new_y"
-
-# Maximize window again, if it was before
-if [ -n "${window_horz_maxed}" ] && [ -n "${window_vert_maxed}" ]; then
- wmctrl -ir "$window_id" -b add,maximized_vert,maximized_horz
-elif [ -n "${window_horz_maxed}" ]; then
- wmctrl -ir "$window_id" -b add,maximized_horz
-elif [ -n "${window_vert_maxed}" ]; then
- wmctrl -ir "$window_id" -b add,maximized_vert
-fi
+#!/bin/env python
+
+import subprocess
+import re
+
+import sys
+
+
+def execute(command):
+ return subprocess.run(command.split(), stdout=subprocess.PIPE).stdout.decode('utf8').strip().split('\n')
+
+
+HORIZONTAL_FIRST = (2, 3)
+VERTICAL_FIRST = (3, 2)
+display_coordinates_split = re.compile('[x+]')
+
+
+def get_display_positions():
+ for x in execute('xrandr'):
+ split = x.split(' ')
+ if 'connected' in split:
+ position = [display_coordinates_split.split(t) for t in split if t != 'primary'][2]
+ yield [int(x) for x in position]
+
+
+def get_active_window_id():
+ return int(execute('xdotool getactivewindow')[0])
+
+
+def get_window_state(window_id):
+ window_state = execute('xprop -id %s _NET_WM_STATE' % window_id)[0]
+ return dict(hmaximized='_NET_WM_STATE_MAXIMIZED_HORZ' in window_state,
+ vmaximized='_NET_WM_STATE_MAXIMIZED_VERT' in window_state,
+ focused='_NET_WM_STATE_FOCUSED' in window_state)
+
+
+def set_window_state(window_id, state):
+ if state['hmaximized'] or state['vmaximized']:
+ props = ['add']
+ if state['hmaximized']:
+ props.append('maximized_horz')
+ if state['vmaximized']:
+ props.append('maximized_vert')
+ execute('wmctrl -ir %s -b %s' % (window_id, ','.join(props)))
+
+
+def unmaximize(window_id):
+ execute('wmctrl -ir %s -b remove,maximized_vert,maximized_horz' % window_id)
+
+
+def get_window_position(window_id):
+ abs_x, abs_y, rel_x, rel_y, width, height = [0] * 6
+ for row in execute('xwininfo -id %s' % window_id):
+ if 'Absolute upper-left X:' in row:
+ abs_x = int(row.split()[-1])
+ elif 'Absolute upper-left Y:' in row:
+ abs_y = int(row.split()[-1])
+ elif 'Relative upper-left X:' in row:
+ rel_x = int(row.split()[-1])
+ elif 'Relative upper-left Y:' in row:
+ rel_y = int(row.split()[-1])
+ elif 'Width:' in row:
+ width = int(row.split()[-1])
+ elif 'Height:' in row:
+ height = int(row.split()[-1])
+ return abs_x - rel_x, abs_y - rel_y, width + rel_x, height + rel_y
+
+
+def point_seq(window_id):
+ x, y, x_size, y_size = get_window_position(window_id)
+ for y in range(y, y_size + 1, int(y_size / 2)):
+ yield x + int(x_size / 2), y
+ yield x, y
+ yield x + x_size, y
+
+
+def get_display_index(point, displays):
+ x, y = point
+ for i, display in enumerate(displays):
+ width, height, woffset, hoffset = display
+ if woffset <= x < woffset + width and hoffset <= y < hoffset + height:
+ return i
+
+
+def get_display_index_of_window(window_id, displays):
+ for point in point_seq(window_id):
+ return get_display_index(point, displays)
+
+
+def next_display_index(index, displays, step=1):
+ return (index + step) % len(displays)
+
+
+def new_window_position(window_id, displays, step=1):
+ window_pos = wx, wy = get_window_position(window_id)
+ display_index = get_display_index(window_pos, displays)
+ owidth, oheight, owoffset, ohoffset = displays[display_index]
+ xrel = (wx - owoffset) / owidth
+ yrel = (wy - ohoffset) / oheight
+ nwidth, nheight, nwoffset, nhoffset = displays[next_display_index(display_index, displays, step)]
+ return xrel * nwidth + nwoffset, yrel * nheight + nhoffset
+
+
+def move_to_next_monitor(window_id, displays, step=1):
+ state = get_window_state(window_id)
+ unmaximize(window_id)
+ xnew, ynew = new_window_position(window_id, displays, step)
+ execute('xdotool windowmove %s %s %s' % (window_id, xnew, ynew))
+ set_window_state(window_id, state)
+
+
+if __name__ == '__main__':
+ if '--horizontal-first' in sys.argv:
+ sorting = HORIZONTAL_FIRST
+ else:
+ sorting = VERTICAL_FIRST
+
+ displays = sorted(get_display_positions(), key=lambda x: [x[i] for i in sorting])
+
+ if len(displays) > 1:
+ if '--reverse' in sys.argv:
+ step = -1
+ else:
+ step = 1
+ move_to_next_monitor(get_active_window_id(), displays, step)
bgstack15