Last active
April 30, 2020 10:21
-
-
Save rezwan-hossain/8f9dbabca6716ee09b8e474ecec0b818 to your computer and use it in GitHub Desktop.
Shortcut to switch displays focus and cursor on ubuntu
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
install: | |
sudo apt install xdotool x11-xserver-utils or sudo apt-get install xdotool | |
usage: add to this keybord shortcut | |
To set focus to left screen pass "left" as arg | |
python3 ./focus_changer.py left | |
To set focus to right screen pass "right" as arg | |
usage python3 ./focus_changer.py right | |
import sys | |
from enum import Enum | |
from locale import atoi | |
from typing import List, Dict, Any | |
import subprocess | |
import sys | |
""" | |
usage: | |
To set focus to left screen pass "left" as arg | |
python3 ./focus_changer.py left | |
To set focus to right screen pass "right" as arg | |
usage python3 ./focus_changer.py right | |
To focus on the other side of the current monitor you must | |
usage python3 ./focus_changer.py right_inter | |
or | |
usage python3 ./focus_changer.py left_inter | |
""" | |
arg = sys.argv[1] | |
screeninfo = [ | |
s for s in subprocess.check_output("xrandr").decode("utf-8").split()\ | |
if s.count("+") == 2 | |
] | |
if arg == "left": | |
match = [s for s in screeninfo if s.endswith("+0+0")][0] | |
elif arg == "right": | |
match = [s for s in screeninfo if not s.endswith("+0+0")][0] | |
data = [item.split("x") for item in match.split("+")] | |
numbers = [int(n) for n in [item for sublist in data for item in sublist]] | |
coord = [str(int(n)) for n in [(numbers[0]/2)+numbers[2], (numbers[1]/2)+numbers[3]]] | |
subprocess.Popen(["xdotool", "mousemove", coord[0], coord[1]]) | |
class Direction(Enum): | |
""" | |
Monitor change direction | |
""" | |
SWITCH = "switch" | |
LEFT = "left" | |
RIGHT = "right" | |
RIGHT_INTER = "right_inter" | |
LEFT_INTER = "left_inter" | |
def get_current_windows_position() -> Dict[str, int]: | |
""" | |
:return: The current active windows position. i.e: { "x": 140, "y": 940, "side": MovementDirection.LEFT } | |
The "side" field is related to the inter screen position | |
i.e: | |
Imagine a monitor with this res: 1920x1080 | |
"(x)" is the position of the mouse at the moment of the script execution. For example (x=400,y=300) | |
Since x is located on the left side of the screen then the "side" field will have the value LEFT. | |
LEFT Otherwise it will have the value RIGHT. | |
960 (the middle of screen) | |
| | |
v | |
_________________________ | |
| | | |
| (x) | | |
| | | |
| | | |
------------------------- | |
""" | |
active_windows = subprocess.getoutput("xdotool getactivewindow") | |
lines = subprocess.getoutput("xdotool getwindowgeometry " + active_windows).split("\n")[1:] | |
x, y = [atoi(x.replace(" (screen", "")) for x in lines[0].split(": ")[1].split(",")] | |
x_resolution = int(lines[1].replace(" Geometry:", "").split("x")[0]) | |
return { | |
"x": x, | |
"y": y, | |
"side": Direction.LEFT if x < (x_resolution / 2) else Direction.RIGHT | |
} | |
def get_all_monitors() -> List[Dict[str, Any]]: | |
""" | |
:return: all monitors array list sorted from left to right. | |
i.e: [ | |
{'hr': 1366, 'vr': 768, 'ho': 0, 'vo': 914, 'name': 'eDP-1-1'}, | |
{'hr': 2560, 'vr': 1440, 'ho': 1366, 'vo': 0, 'name': 'HDMI-1-1'}, | |
] | |
hr: Horizontal resolution | |
vr: Vertical resolution | |
ho: Horizontal offset | |
vo: Vertical offset | |
name: The screen name | |
""" | |
# all_monitors_xrand_resp_ is string like this: | |
# Monitors: 2 | |
# 0: +*HDMI-1-1 2560/621x1440/341+1366+0 HDMI-1-1 | |
# 1: +eDP-1-1 1366/309x768/174+0+45 eDP-1-1 | |
all_monitors_xrand_resp_ = subprocess.getoutput("xrandr --listmonitors") | |
monitors_ = [] | |
for line_ in all_monitors_xrand_resp_.split(": ")[2:]: | |
monitor = { | |
# Horizontal resolution. i.e 2560 | |
"hr": atoi(line_.split(" ")[1].split("/")[0]), | |
# Vertical resolution. i.e 1440 | |
"vr": atoi(line_.split(" ")[1].split("/")[1].split("x")[1].split("/")[0]), | |
# Horizontal offset. i.e 1366 | |
"ho": atoi(line_.split(" ")[1].split("+")[1]), | |
# Vertical offset. i.e 0 | |
"vo": atoi(line_.split(" ")[1].split("+")[2]), | |
# Monitor name. i.e HDMI-1-1 | |
"name": line_.replace(" ", " ").rsplit(" ")[0].replace("+", "").replace("*", ""), | |
} | |
monitors_.append(monitor) | |
return sorted(monitors_, key=lambda i: i['ho']) | |
def get_current_monitor_pos(monitors: List[Dict[str, Any]], pos: Dict[str, int]) -> int: | |
""" | |
Return the current monitor position | |
Monitors are represented as arrays with base index 0, where the left-most | |
monitor has the lowest index. | |
i.e: If the screen focus in the screen 0 then return 0 | |
Screen 0 Screen 1 | |
| | | |
v v | |
____________ _____________ | |
| | | | | |
| (x) | | | | |
| | | | | |
------------- -------------- | |
""" | |
for i in range(len(monitors)): | |
if monitors[i]["ho"] <= pos["x"] < monitors[i]["hr"] + monitors[i]["ho"]: | |
return i | |
def determine_monitor_to_move(mov: Direction, current_pos: int, n_monitors: int) -> int: | |
""" | |
:param mov: Current mov | |
:param current_pos: Current pos | |
:param n_monitors: Number of monitors in the monitors array | |
""" | |
if n_monitors == 1: | |
return 0 | |
if mov == Direction.LEFT: | |
return current_pos - 1 if current_pos > 0 else 0 | |
if mov == Direction.RIGHT: | |
return current_pos + 1 if current_pos < n_monitors - 1 else n_monitors - 1 | |
return 0 if current_pos == 1 else 1 | |
def get_center_of_monitor(monitor: Dict[str, Any], mov: Direction) -> Dict[str, int]: | |
""" | |
Return the center of the screen. | |
If the movement is "inter" then add an positive or offset for left or right respectively | |
:param monitor: The monitor object dictionary | |
:param mov: The desired movement | |
:return: | |
""" | |
if 'inter' in mov.value: | |
offset_x = -25 if mov == Direction.LEFT_INTER else 25 | |
else: | |
offset_x = 0 | |
return { | |
"x": (monitor["hr"] / 2) + monitor["ho"] + offset_x, | |
"y": (monitor["vr"] / 2) + monitor["vo"], | |
} | |
def change_monitor_focus(mov: Direction) -> int: | |
pos = get_current_windows_position() | |
monitors = get_all_monitors() | |
current_monitor_pos = get_current_monitor_pos(monitors, pos) | |
if mov in [Direction.LEFT_INTER, Direction.RIGHT_INTER]: | |
new_monitor_pos = current_monitor_pos | |
else: | |
new_monitor_pos = determine_monitor_to_move(mov, current_monitor_pos, len(monitors)) | |
if new_monitor_pos == current_monitor_pos: | |
mov = Direction.RIGHT_INTER if pos['side'] == Direction.LEFT else Direction.LEFT_INTER | |
center_of_screen = get_center_of_monitor(monitors[new_monitor_pos], mov) | |
query_get_window_at = "xdotool mousemove {} {} getmouselocation --shell mousemove restore & echo $WINDOW ".format( | |
center_of_screen["x"], | |
center_of_screen["y"] | |
) | |
window_id = subprocess.getoutput(query_get_window_at).split("WINDOW=")[1] | |
subprocess.getoutput("xdotool windowactivate {}".format(window_id)) | |
return new_monitor_pos | |
def get_args() -> Direction: | |
if len(sys.argv) == 1: | |
return Direction.SWITCH | |
mov = str(sys.argv[1]).upper() | |
try: | |
return Direction[mov] | |
except KeyError as e: | |
print("ERROR: invalid mov: {}".format(mov)) | |
if __name__ == '__main__': | |
change_monitor_focus(get_args()) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment