Can I move/resize windows a pixel at a time with my keyboard?

Solution 1:

Moving/resizing windows by 1px

Assuming you are using Unity, the script below moves or resizes windows by 1 px. The script can be run with 8 different arguments. Depending on the move/re-size options you want to use, you can add the commands to a shortcut key combination. An overview of options and the corresponding commands below:

enter image description here


Exceptions/limitations

There are a few limitations:

  • gnome-terminal windows can only be re-sized in steps. As a result increasing/decreasing the window size by 1px does not work with gnome-terminal.
  • The window to be moved/re-sized needs to be at least a few px from both the Unity launcher and the top panel.

How to use

  • First install wmctrl, which is needed to get the window geometry and to move the window.

    sudo apt-get install wmctrl
    
  • Create a directory ~/bin (so in your home directory)

  • Copy the script below into an empty file, save it as move_window (no extension)
  • Make it executable (right-click on the file > Properties > Permissions (tab) , tick "allow execute")

    To test, open a terminal window and run subsequently:

    move_window l
    move_window r
    move_window u
    move_window d
    

    Since the terminal window is the front-most, it should move 1px to the left/right/up/down.

    (As mentioned, resizing does not work with gnome-terminal)

  • If it works correctly, add the commands to shortcut keys;
    choose: System Settings > "Keyboard" > "Shortcuts" > "Custom Shortcuts". Click the "+" and add the commands to four different shortcut key combinations. That might be tricky, since the commands you mentioned are probably occupied. What worked on my system:

    • Shift+Ctrl+arrow key left
    • Shift+Ctrl+arrow key right
    • Shift+Ctrl+arrow key up
    • Shift+Ctrl+arrow key down

    for the move actions.
    For the resize actions you'll have to try additional combinations.

The script

#!/usr/bin/env python3
import subprocess
import sys
# calibration
cal = 4
# direction, as argument from user input (l, r, u, d / h+, h-, v+, v-)
direction = sys.argv[1]
# move step size 
mv = -1 if  direction in ["l", "d", "h-", "v-"] else 1

def get(command):
    return subprocess.check_output(["/bin/bash", "-c", command])\
           .decode("utf-8")

def execute(command):
    subprocess.call(["/bin/bash", "-c", command])
# find the top shift (height of the panel = resolution - working area)
res_output = get("xrandr").split(); idf = res_output.index("current")
res = (int(res_output[idf+1]), int(res_output[idf+3].replace(",", "")))[-1]
topshift = int(res) - int(get("wmctrl -d").split()[8].split("x")[-1])+cal
# find frontmost window
def get_windowid():
    cmd = "xprop -root"
    frontmost = [l for l in get(cmd).splitlines() if\
                 "ACTIVE_WINDOW(WINDOW)" in l][0].split()[-1]
    return frontmost[:2]+"0"+frontmost[2:]
# get window geometry, create move command
set_w = [w.split()[0:6] for w in get("wmctrl -lG").splitlines()\
          if get_windowid() in w][0]
set_w[0] = "wmctrl -ir "+set_w[0]+" -e 0"
set_w.pop(1)

if direction in ["l", "r"]:
    set_w[1] = str(int(set_w[1])+mv); set_w[2] = str(int(set_w[2])-topshift)  
elif direction in ["u", "d"]:
    set_w[2] = str(int(set_w[2])-topshift-mv) 
elif direction in ["v-", "v+"]:
    set_w[2] = str(int(set_w[2])-topshift); set_w[4] = str(int(set_w[4])+mv)
elif direction in ["h-", "h+"]:
    set_w[2] = str(int(set_w[2])-topshift); set_w[3] = str(int(set_w[3])+mv)

execute((",").join(set_w))

Note

There is a little difference in the way wmctrl reports the window geometry, and the way wmctrl sets the window geometry. In the first case it calculates from the full screen (resolution), in the second case only from the working area (??). Even then, The script had to "calibrate" 4 px vertically, for which I found no satisfying explanation. The good news is that on different computers, I saw no difference in the deviation.

If in your case the window makes unexpected jumps, leave a comment.


Explanation

How it works

  1. The front-most window is looked up with the help of xprop:

    xprop -root
    

    Somewhere in the (extensive) output, there is a line like:

    _NET_ACTIVE_WINDOW(WINDOW): window id # 0x4600a8d
    

    from which our window-id can be parsed: 0x4600a8d. Since the format is a bit different from wmctrl, we need to add a zero on the third position: 0x04200085

  2. The window id is used to look up the window and its current geometry data, in the output of wmctrl -lG. Once we have the correct line, the data we have on the window looks like:

    0x04200085  0 322  52   823  998  <computer_name> <window_name> 
    

    where column 2, 3, 4, 5 are subsequently:

    • x coordinate of the top left corner of the window
    • y coordinate
    • width of the window
    • height of the window
  3. by manipulating these figures, we can move/resize the window with the command:
    (example to move the window to the right by 1 px, changing "322" into "323")

    wmctrl -ir 0x04200085 -e 0,323,52,823,998
    

There are a few complications to deal with, but that is basically how it works.

Solution 2:

Apparently that doesn't work in XFCE either. I have an indirect idea that might work though...

I'd suggest trying Accessibility options for controlling the mouse with the keyboard, setting it to move very slowly, and use that to get the window to move a little bit at a time.

Each desktop seems to put the Accessibility options somewhere different, in XFCE it's under the general Settings Manager, but it shouldn't be hard to find in Unity & others...