Skip to content

Instantly share code, notes, and snippets.

@rkmax
Created August 20, 2023 20:32
Show Gist options
  • Save rkmax/3516f77c9d9703b46862e81e0600a0c4 to your computer and use it in GitHub Desktop.
Save rkmax/3516f77c9d9703b46862e81e0600a0c4 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
import subprocess
import sys
SCREEN_LAYOUT = {
'horizontal': {
'full-size': '0,0,{width},{height}',
'half-left': '0,0,{width}/2,{height}',
'half-right': '{width}/2,0,{width}/2,{height}',
'top-left': '0,0,{width}/2,{height}/2',
'top-right': '{width}/2,0,{width}/2,{height}/2',
'bottom-left': '0,{height}/2,{width}/2,{height}/2',
'bottom-right': '{width}/2,{height}/2,{width}/2,{height}/2',
'two-thirds-left': '0,0,{width}*2/3,{height}',
'two-thirds-right': '{width}/3,0,{width}*2/3,{height}',
'one-third-left': '0,0,{width}/3,{height}',
'one-third-right': '{width}*2/3,0,{width}/3,{height}',
'one-third-top-right': '{width}*2/3,0,{width}/3,{height}/2',
'one-third-bottom-right': '{width}*2/3,{height}/2,{width}/3,{height}/2',
'centered-left': '50,{height}*1/6,{width}*3/5,{height}*2/3',
},
'vertical': {
'full-size': '0,0,{width},{height}',
'half-top': '0,0,{width},{height}/2',
'half-bottom': '0,{height}/2,{width},{height}/2',
'first-quarter': '0,0,{width},{height}/4',
'second-quarter': '0,{height}/4,{width},{height}/4',
'third-quarter': '0,{height}/2,{width},{height}/4',
'fourth-quarter': '0,{height}*3/4,{width},{height}/4',
},
}
# Some screens have a bar at the top or bottom
# define the screen name and where to put space for the bar
BAR_HEIGHT = {
'DP-0': { # ✋ Change this to match your screen name
'top': 0,
'bottom': 44, # ✋ Change this t match you bar height
'left': 0,
'right': 0,
}
}
def calculate_layout_screen(screen_data, layout_name):
"""Return the layout of the screen."""
if screen_data['name'] in BAR_HEIGHT:
bar = BAR_HEIGHT[screen_data['name']]
screen_data['x'] += bar['left']
screen_data['y'] += bar['top']
screen_data['width'] -= bar['left'] + bar['right']
screen_data['height'] -= bar['top'] + bar['bottom']
calc_layout = SCREEN_LAYOUT[screen_data['layout']][layout_name].format(**screen_data)
return {
'x': eval(calc_layout.split(',')[0]) + screen_data['x'],
'y': eval(calc_layout.split(',')[1]) + screen_data['y'],
'width': eval(calc_layout.split(',')[2]),
'height': eval(calc_layout.split(',')[3]),
}
def get_screen_data_map(monitoring_data):
"""Return a dictionary of screen data."""
info = {
'name': monitoring_data[3],
'x': int(monitoring_data[2].split('+')[1]),
'y': int(monitoring_data[2].split('+')[2]),
'width': int(monitoring_data[2].split('+')[0].split('x')[0].split('/')[0]),
'height': int(monitoring_data[2].split('+')[0].split('x')[1].split('/')[0]),
}
info['layout'] = get_screen_layout(info)
return info
def get_screens_data():
"""Return screen data using xrandr."""
xrandr = subprocess.Popen(['xrandr', '--listactivemonitors'],
stdout=subprocess.PIPE)
monitors = xrandr.stdout.read().decode('utf-8').split('\n')[1:-1]
return [get_screen_data_map(monitor.split()) for monitor in monitors]
def get_active_window_data():
"""Return active window data using xdotool."""
xdotool = subprocess.Popen(['xdotool', 'getactivewindow', 'getwindowgeometry'],
stdout=subprocess.PIPE)
window_info = xdotool.stdout.read().decode('utf-8').split('\n')
name = window_info[0].split(' ')[1]
x = int(window_info[1].split()[1].split(',')[0])
y = int(window_info[1].split()[1].split(',')[1])
width = int(window_info[2].split()[1].split('x')[0])
height = int(window_info[2].split()[1].split('x')[1])
return {
'name': name,
'x': x,
'y': y,
'width': width,
'height': height,
}
def get_screen_layout(screen_data):
"""Return if the screen is horizontal or vertical oriented."""
return 'horizontal' if screen_data['width'] > screen_data['height'] else 'vertical'
def get_screen_of_window(screens_data, window_data):
"""Return the screen where the window is located using x,y of the window."""
for screen in screens_data:
screen_width = screen['width'] + screen['x']
screen_height = screen['height'] + screen['y']
window_is_x_in_screen = screen['x'] < window_data['x'] + 1 < screen_width
window_is_y_in_screen = screen['y'] < window_data['y'] + 1 < screen_height
if window_is_y_in_screen and window_is_x_in_screen:
return screen
def resize_reposition_window(window_data, layout_data):
"""Resize and reposition the window."""
subprocess.call(['xdotool', 'windowsize', window_data['name'],
str(layout_data['width']), str(layout_data['height'])])
subprocess.call(['xdotool', 'windowmove', window_data['name'],
str(layout_data['x']), str(layout_data['y'])])
if __name__ == '__main__':
layout_name = sys.argv[1] if len(sys.argv) > 1 else 'full-size'
screen_name = sys.argv[2] if len(sys.argv) > 2 else None
active_window = get_active_window_data()
screens = get_screens_data()
if screen_name:
screen_of_window = [screen for screen in screens if screen['name'] == screen_name][0]
else:
screen_of_window = get_screen_of_window(screens, active_window)
layout = calculate_layout_screen(screen_of_window, layout_name)
resize_reposition_window(active_window, layout)
print('screens: {}'.format(screens))
print('window: {}'.format(active_window))
print('screen: {}'.format(screen_of_window))
print('layout: {}'.format(layout))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment