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:
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 withgnome-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
-
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 fromwmctrl
, we need to add a zero on the third position:0x04200085
-
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
-
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...