-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathfocus_changer.py
189 lines (162 loc) · 6.33 KB
/
focus_changer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import sys
from enum import Enum
from locale import atoi
from typing import List, Dict, Any
import subprocess
"""
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
"""
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())