I use i3 window manager. If you switch to another workspace, mouse pointer will move to the center of the active window. It doesn’t matter for my work but sometimes (mainly on workspace where I have web browser) the centered mouse pointer obstructs the view and I’m forced give it away.
I created small utility which moves the mouse pointer to the last position for each workspace. It works with changing focus too.
Here is the part of my ~/.config/i3/config:
bindsym $mod+h exec "~/bin/i3-change.py focus --due left"
bindsym $mod+j exec "~/bin/i3-change.py focus --due down"
bindsym $mod+k exec "~/bin/i3-change.py focus --due up"
bindsym $mod+l exec "~/bin/i3-change.py focus --due right"
bindsym $mod+1 exec "~/bin/i3-change.py workspace 1"
bindsym $mod+2 exec "~/bin/i3-change.py workspace 2"
bindsym $mod+3 exec "~/bin/i3-change.py workspace 3"
bindsym $mod+4 exec "~/bin/i3-change.py workspace 4"
bindsym $mod+5 exec "~/bin/i3-change.py workspace 5"
bindsym $mod+6 exec "~/bin/i3-change.py workspace 6"
bindsym $mod+7 exec "~/bin/i3-change.py workspace 7"
bindsym $mod+8 exec "~/bin/i3-change.py workspace 8"
bindsym $mod+9 exec "~/bin/i3-change.py workspace 9"
bindsym $mod+0 exec "~/bin/i3-change.py workspace 10"
Here is the i3-change.py utility:
#!/usr/bin/python3
import click
import json
import logging
import os
import re
import subprocess
log = logging.getLogger(__name__)
def get_current_workspace():
process = subprocess.run([
'i3-msg',
'-t',
'get_workspaces',
], check=True, stdout=subprocess.PIPE)
return [workspace['name'] for workspace in json.loads(process.stdout) if workspace['focused']][0] # noqa: E501
def get_current_window_id():
ps = subprocess.Popen([
'i3-msg',
'-t',
'get_tree',
], stdout=subprocess.PIPE)
process = subprocess.run([
'jq',
'-r',
'.. | select(.focused? and .focused == true).id'
], check=True, stdin=ps.stdout, stdout=subprocess.PIPE)
ps.wait()
return process.stdout.decode('utf-8').strip()
def get_current_window_id2():
process = subprocess.run([
'i3-msg',
'-t',
'get_tree',
], check=True, stdout=subprocess.PIPE)
def find(element, results=[]):
if 'focused' in element and element['focused']:
results.append(element)
if 'nodes' in element:
for node in element['nodes']:
find(node, results)
return results
return find(json.loads(process.stdout))[0]['id']
def get_current_mouse():
process = subprocess.run([
'xdotool',
'getmouselocation',
], check=True, stdout=subprocess.PIPE)
match = re.match('x:(\\d+) y:(\\d+) screen:(\\d+) window:(\\d+)', process.stdout.decode('utf-8')) # noqa: E501
return match[1], match[2]
def communicate(data: str):
fifo = '/tmp/in-memory-storage'
if not os.path.exists(fifo):
return None
with open(fifo, 'w') as f:
f.write(data)
with open(fifo, 'r') as f:
return f.read()
@click.group()
def cli():
pass
@cli.command()
@click.option('--due', type=click.Choice(['left', 'up', 'down', 'right']))
def focus(due):
old = get_current_workspace()
mouse_x, mouse_y = get_current_mouse()
communicate(f'i3-workspace-{old}={mouse_x} {mouse_y}')
subprocess.run([
'i3-msg',
'focus',
due,
], check=True)
new = get_current_workspace()
if old == new:
return
match = re.match('(\\d+) (\\d+)', communicate(f'i3-workspace-{new}'))
if match:
window_id = get_current_window_id2()
subprocess.run([
'xdotool',
'mousemove',
match[1],
match[2],
], check=True)
subprocess.run([
'i3-msg',
f'[con_id="{window_id}"] focus',
], check=True, stdout=subprocess.PIPE)
@cli.command()
@click.argument('name')
def workspace(name):
mouse_x, mouse_y = get_current_mouse()
communicate(f'i3-workspace-{get_current_workspace()}={mouse_x} {mouse_y}')
subprocess.run([
'i3-msg',
'workspace',
name,
], check=True)
match = re.match('(\\d+) (\\d+)', communicate(f'i3-workspace-{name}'))
if match:
subprocess.run([
'xdotool',
'mousemove',
match[1],
match[2],
], check=True)
if __name__ == '__main__':
cli()
It will be published on my Github repo https://github.com/petrposvic/scripts.