Is there a way to store the current desktop layout?
What I want to be able to save the current positions of my applications, so when I'm going to open the same ones and run something they will rearrange as they were.
For example if I'm going to open a sublime and three terminal windows I would like to be able to save that somehow.
I don't care if it's an app or a command line tool, as long as I can easily save the positions of my apps.
I'm a big fan of Moom, but unfortunately it works only on MacOS and I really miss it when on Ubuntu. It supports more features and if you know something close to it on top of my main problem that's also fine.
Solution 1:
Note
The script was patched/fixed on January 16 2017, fixing for a few applications of which the process name differs from the command to run the application. Possibly, this occurs occasionally on applications. If someone finds one, please leave a comment.
Script to remember and restore the window arrangement and their corresponding applications.
The script below can be run with two options. Let's say you have the window arrangement as below:
To read (remember) the current window arrangement and their applications, run the script with the option:
<script> -read
Then close all windows:
Then to set up the last remembered window arrangement, run it with the option:
<script> -run
and the last remembered window arrangement will be restored:
This will also work after a restart.
Putting the two commands under two different shortcut keys, you can "record" your window arrangement, shutdown your computer and recall the same window arrangement after (e.g.) a restart.
What the script does, and what it does not
Run with the option -read
- The script uses
wmctrl
to list all windows, across all workspaces, their positions, their sizes, the applications they belong to - The script then "converts" the window positions from relative (to the current workspace, as in the output of
wmctrl
) to absolute positions, on your spanning workspaces. Therefore it does not matter if the windows you want to remember are on only one workspace or spread over different workspaces. - The script then "remembers" the current window arrangement, writing it into an invisible file in your home directory.
Run with the option -run
- the script reads the last remembered window arrangement; it starts the corresponding applications, moves the windows to the remembered positions, also with the help of
wmctrl
The script does not remember the files that possibly might be opened in the windows, nor (e.g.) the websites that were opened in a browser window.
Issues
The combination of wmctrl
and Unity
has some bugs, a few examples:
- the window coordinates, as read by
wmctrl
differs slightly form the command to position the windows, as mentioned here. Therefore the recalled window positions might slightly differ from the original position. - The
wmctrl
commands works a bit unpredictable if the edge of the window is very near either theUnity Launcher
or the panel. - The "remembered" windows need to be completely inside a workspace borders for the
wmctrl
placement command to work well.
Some applications open new windows by default in the same window in a new tab (like gedit
). I fixed it for gedit
, but please mention it if you find more exceptions.
The script
#!/usr/bin/env python3
import subprocess
import os
import sys
import time
wfile = os.environ["HOME"]+"/.windowlist"
arg = sys.argv[1]
def get(command):
return subprocess.check_output(["/bin/bash", "-c", command]).decode("utf-8")
def check_window(w_id):
w_type = get("xprop -id "+w_id)
if " _NET_WM_WINDOW_TYPE_NORMAL" in w_type:
return True
else:
return False
def get_res():
# get resolution and the workspace correction (vector)
xr = subprocess.check_output(["xrandr"]).decode("utf-8").split()
pos = xr.index("current")
res = [int(xr[pos+1]), int(xr[pos+3].replace(",", "") )]
vp_data = subprocess.check_output(["wmctrl", "-d"]).decode("utf-8").split()
curr_vpdata = [int(n) for n in vp_data[5].split(",")]
return [res, curr_vpdata]
app = lambda pid: subprocess.check_output(["ps", "-p", pid, "-o", "comm="]).decode("utf-8").strip()
def read_windows():
res = get_res()
w_list = [l.split() for l in get("wmctrl -lpG").splitlines()]
relevant = [[w[2],[int(n) for n in w[3:7]]] for w in w_list if check_window(w[0]) == True]
for i, r in enumerate(relevant):
relevant[i] = app(r[0])+" "+str((" ").join([str(n) for n in r[1]]))
with open(wfile, "wt") as out:
for l in relevant:
out.write(l+"\n")
def open_appwindow(app, x, y, w, h):
ws1 = get("wmctrl -lp"); t = 0
# fix command for certain apps that open in new tab by default
if app == "gedit":
option = " --new-window"
else:
option = ""
# fix command if process name and command to run are different
if "gnome-terminal" in app:
app = "gnome-terminal"
elif "chrome" in app:
app = "/usr/bin/google-chrome-stable"
subprocess.Popen(["/bin/bash", "-c", app+option])
# fix exception for Chrome (command = google-chrome-stable, but processname = chrome)
app = "chrome" if "chrome" in app else app
while t < 30:
ws2 = [w.split()[0:3] for w in get("wmctrl -lp").splitlines() if not w in ws1]
procs = [[(p, w[0]) for p in get("ps -e ww").splitlines() \
if app in p and w[2] in p] for w in ws2]
if len(procs) > 0:
time.sleep(0.5)
w_id = procs[0][0][1]
cmd1 = "wmctrl -ir "+w_id+" -b remove,maximized_horz"
cmd2 = "wmctrl -ir "+w_id+" -b remove,maximized_vert"
cmd3 = "wmctrl -ir "+procs[0][0][1]+" -e 0,"+x+","+y+","+w+","+h
for cmd in [cmd1, cmd2, cmd3]:
subprocess.call(["/bin/bash", "-c", cmd])
break
time.sleep(0.5)
t = t+1
def run_remembered():
res = get_res()[1]
try:
lines = [l.split() for l in open(wfile).read().splitlines()]
for l in lines:
l[1] = str(int(l[1]) - res[0]); l[2] = str(int(l[2]) - res[1] - 24)
open_appwindow(l[0], l[1], l[2], l[3], l[4])
except FileNotFoundError:
pass
if arg == "-run":
run_remembered()
elif arg == "-read":
read_windows()
How to set up
Before you start, make sure wmctrl
is installed:
sudo apt-get install wmctrl
Then:
- Copy the script into an empty file, save it as
recall_windows
in~/bin
. Create the directory if necessary. If the directory didn't exist yet, run eithersource ~/.profile
or log out/in after you created the directory. It will now be in$PATH
- Make the script executable (!).
-
Now open a few windows,
gedit
,firefox
or whatever, and test-run the script in a terminal by running the command (no path prefix needed):recall_windows -read
-
close the windows. Now run in a terminal:
recall_windows -run
Your window setup should now be restored
If all works fine, add two commands to shortcut keys: Choose: System Settings > "Keyboard" > "Shortcuts" > "Custom Shortcuts". Click the "+" and add the commands:
recall_windows -read
and
recall_windows -run
to two different shortcut keys
Solution 2:
I wrote a little library/command line tool which allows saving and restoring sessions and has support for different monitors setups as well as virtual desktops.
Installation
npm install -g linux-window-session-manager
Usage
Save the current session to ~/.lwsm/sessionData/DEFAULT.json
lwsm save
Save the current session to ~/.lwsm/sessionData/my-session.json
lwsm save my-session
Restore the session from ~/.lwsm/sessionData/DEFAULT.json
lwsm restore
Restore the session from ~/.lwsm/sessionData/my-session.json
lwsm restore my-session
Gracefully close all running apps before starting the session
lwsm restore --closeAllOpenWindows
Check it out: https://github.com/johannesjo/linux-window-session-manager
Solution 3:
there is no such program. You may install compiz cub:
sudo apt-get install compiz compizconfig-settings-manager compiz-fusion-plugins-extra compiz-fusion-plugins-main compiz-plugins
and follow this how-to
the compiz is the most advanced desktop tool for unity/gnome
Solution 4:
I don't know of a simple way of achieving this.
However, I rarely need that for a very simple reason: suspend. Suspend and hibernation are your friends. Not only do you save window positions, but you also save the whole state of your system. I rarely switch off the computer completely, except to reload a new kernel version.
Solution 5:
I could not post it as a comment. This is a slightly updated version of the script above. The main change is that if the application exists, it will be simply re-positioned instead of re-launched. Otherwise it is a great script. I used it on Lubuntu 16.04 and now on 18.04. Thanks for writing it. This script should probably be posted some place on github or something similar so people could contribute.
#! /usr/bin/env python3
import subprocess
import os
import sys
import time
wfile = os.environ["HOME"]+"/.windowlist"
arg = sys.argv[1]
def get(command):
return subprocess.check_output(["/bin/bash", "-c", command]).decode("utf-8")
def check_window(w_id):
w_type = get("xprop -id "+w_id)
if " _NET_WM_WINDOW_TYPE_NORMAL" in w_type:
return True
elif "\"xterm\"" in w_type:
return True
else:
return False
def get_res():
# get resolution and the workspace correction (vector)
xr = subprocess.check_output(["xrandr"]).decode("utf-8").split()
pos = xr.index("current")
res = [int(xr[pos+1]), int(xr[pos+3].replace(",", "") )]
vp_data = subprocess.check_output(["wmctrl", "-d"]).decode("utf-8").split()
curr_vpdata = [int(n) for n in vp_data[5].split(",")]
return [res, curr_vpdata]
app = lambda pid: subprocess.check_output(["ps", "-q", pid, "-o", "comm="]).decode("utf-8").strip()
def read_windows():
res = get_res()
w_list = [l.split() for l in get("wmctrl -lpG").splitlines()]
relevant = [[w[2],[int(n) for n in w[3:7]]] for w in w_list if check_window(w[0]) == True]
for i, r in enumerate(relevant):
relevant[i] = app(r[0])+" "+str((" ").join([str(n) for n in r[1]]))
with open(wfile, "wt") as out:
for l in relevant:
out.write(l+"\n")
def read_window_ids():
w_list = [l.split() for l in get("wmctrl -lpG").splitlines()]
relevant = [[w[2], w[0]] for w in w_list if check_window(w[0]) == True]
for i, r in enumerate(relevant):
relevant[i][0] = app(r[0])
return relevant
def open_appwindow(app, x, y, w, h):
ws1 = get("wmctrl -lp"); t = 0
# fix command for certain apps that open in new tab by default
if app == "gedit":
option = " --new-window"
else:
option = ""
# fix command if process name and command to run are different
if "gnome-terminal" in app:
app = "gnome-terminal"
elif "chrome" in app:
app = "/usr/bin/google-chrome-stable"
subprocess.Popen(["/bin/bash", "-c", app+option])
# fix exception for Chrome (command = google-chrome-stable, but processname = chrome)
app = "chrome" if "chrome" in app else app
while t < 30:
ws2 = [w.split()[0:3] for w in get("wmctrl -lp").splitlines() if not w in ws1]
procs = [[(p, w[0]) for p in get("ps -e ww").splitlines() \
if app in p and w[2] in p] for w in ws2]
if len(procs) > 0:
time.sleep(0.5)
w_id = procs[0][0][1]
reposition_window(w_id, x, y, w, h)
break
time.sleep(0.5)
t = t+1
def reposition_window(w_id, x, y, w, h):
cmd1 = "wmctrl -ir "+w_id+" -b remove,maximized_horz"
cmd2 = "wmctrl -ir "+w_id+" -b remove,maximized_vert"
cmd3 = "wmctrl -ir "+w_id+" -e 0,"+x+","+y+","+w+","+h
for cmd in [cmd1, cmd2, cmd3]:
subprocess.call(["/bin/bash", "-c", cmd])
return
def run_remembered():
res = get_res()[1]
running = read_window_ids()
try:
lines = [l.split() for l in open(wfile).read().splitlines()]
for l in lines:
l[1] = str(int(l[1]) - res[0]); l[2] = str(int(l[2]) - res[1] - 24)
apps = [a[0] for a in running]
if l[0] in apps :
idx = apps.index(l[0])
reposition_window(running[idx][1], l[1], l[2], l[3], l[4])
running.pop(idx)
else :
open_appwindow(l[0], l[1], l[2], l[3], l[4])
except FileNotFoundError:
pass
def show_help():
print("usage: python3 save-restore-win.py -read|-run")
print(" -read : read window positions from wm")
print(" -run : restore window positions")
if arg == "-run":
run_remembered()
elif arg == "-read":
read_windows()
else :
show_help()