How can I preserve the desktop's icon positions after reboot, on a dual monitor setup (Unity)?

The cause

Most likely, the issue is the result of a bug (related to the video driver). I assume the second screen is not remembered somehow, and "virtually" re- connected in a rather late stage in the start up (log in) process.

Work around

As often, even if it is a bug, it doesn't mean we cannot find a work around. If we:

  • can make some kind of a snapshot of the current icon positions on log out
  • restore the icon positions after the second screen appears correctly after restart (log in)

effectively the bug is worked around.

How to read the icon position on the desktop

You can get the position on the desktop of an icon by the command:

gvfs-info -a 'metadata::nautilus-icon-position' '<file>'

This will a.o. output the coordinates of the icon on your desktop, which we can save into a file. If we make a snapshot this way of your desktop before shutting down the computer, we can subsequently restore the icon's position afterwards, with the command:

gvfs-set-attribute -t string '<file>' 'metadata::nautilus-icon-position' '500,500'

(for example)

A script to take such a snapshot, and restore the icon layout (depending on the argument) would be:

#!/usr/bin/env python3
import subprocess
import os
import sys

# --- set your localized desktop name below:
dt_name = "Bureaublad"
# ---


# used strings & definitions
val = " 'metadata::nautilus-icon-position' "
search = "gvfs-info -a"+val
set_val = "gvfs-set-attribute -t string "
home = os.environ["HOME"]
dt_dir = home+"/"+dt_name
datafile = home+"/.desktop_items.txt"

arg = sys.argv[1]

get = lambda cmd: subprocess.check_output(["/bin/bash", "-c", cmd]).decode("utf-8")

def snapshot():
    try:
        os.remove(datafile)
    except FileNotFoundError:
        pass
    dt_items = os.listdir(dt_dir)
    dt_data = []
    for f in [f for f in dt_items if all([not f.startswith("."), not f.endswith("~")])]:
        f = '"'+f+'"' if f.count(" ") > 0 else f
        file = dt_dir+"/"+f
        cmd = search+file
        try:
            loc = [int(n) for n in get(cmd).split()[-1].split(",")]
            # loc[0] = loc[0] - screen_pos[0]; loc[1] = loc[1] - screen_pos[1]
            loc = (",").join([str(n) for n in loc])
            open(datafile, "a+").write(file+"|||"+loc+"\n")
        except:
            pass
    subprocess.Popen(["/bin/bash", "-c", "notify-send 'A snapshot was taken'"])

def restore():
    items = [l.strip().split("|||") for l in open(datafile).readlines()]
    for item in items:
        try:
            xy = [int(n) for n in item[1].split(",")]
            move_to = (",").join(str(n) for n in [xy[0], xy[1]])
            command = set_val+item[0]+val+move_to
            subprocess.Popen(["/bin/bash", "-c", command])
        except:
            pass
    subprocess.Popen(["/bin/bash", "-c", "notify-send 'Click on the desktop and press F5'"])

if arg == "snapshot":
    snapshot()
elif arg == "restore":
    restore()

To use it:

  • Copy the script into an empty file, save it as restore_desktop.py
  • In the head section of the script, in the line:

    # --- set your localized desktop name below:
    dt_name = "Desktop"
    # ---
    

    set the localized name of your desktop folder ("Bureaublad" in Dutch)

  • To take a snapshot of the current icon layout, run the command:

    python3 /path/to/restore_desktop.py snapshot
    
  • To restore a previously taken snapshot:

    python3 /path/to/restore_desktop.py restore
    

    then click on the desktop and press F5 to refresh the desktop.

To use the script in our situation would however take a few additions

  • The desktop still needs to be refreshed from the command line to actually apply the restore of the icons. This can be done by either restarting nautilus, or press F5 with the desktop in front. Since we use it on startup, the first option might be the most elegant one.

  • We need to take a snapshot on log out as the current user to make sure the last icon layout is recorded into a snapshot. So far, I could not find a way to do that. However, as always, there is a workaround. If we include the command

    gnome-session-quit --poweroff
    

    in our script, we can simply call the script (with the right arguments) to include the shutdown procedure and take the snapshot.

  • Then to make sure the restored snapshot is applied we can include the restart of nautilus in the script.

The solution

The final script then becomes:

#!/usr/bin/env python3
import subprocess
import os
import sys
import time

# --- set your localized desktop name below:
dt_name = "Desktop"
# ---


# used strings & definitions
val = " 'metadata::nautilus-icon-position' "
search = "gvfs-info -a"+val
set_val = "gvfs-set-attribute -t string "
home = os.environ["HOME"]
dt_dir = home+"/"+dt_name
datafile = home+"/.desktop_items.txt"

arg = sys.argv[1]

get = lambda cmd: subprocess.check_output(["/bin/bash", "-c", cmd]).decode("utf-8")

def snapshot():
    # remove the previous snapshot
    try:
        os.remove(datafile)
    except FileNotFoundError:
        pass
    # list the items on the desktop, look up their positions
    dt_items = os.listdir(dt_dir)
    dt_data = []
    for f in [f for f in dt_items if all([not f.startswith("."), not f.endswith("~")])]:
        f = '"'+f+'"' if f.count(" ") > 0 else f
        file = dt_dir+"/"+f
        cmd = search+file
        try:
            loc = [int(n) for n in get(cmd).split()[-1].split(",")]
            loc = (",").join([str(n) for n in loc])
            open(datafile, "a+").write(file+"|||"+loc+"\n")
        except:
            pass
    # notify that a snapshot was taken
    subprocess.call(["/bin/bash", "-c", "notify-send 'A snapshot was taken'"])
    # send the command to shutdown
    subprocess.Popen(["gnome-session-quit", "--power-off"])

def restore():
    # wait for 20 seconds for the desktop to be fully loaded and the screen to be found
    time.sleep(20)
    # read the snapshot file
    items = [l.strip().split("|||") for l in open(datafile).readlines()]
    for item in items:
        try:
            # place the icon(s) acoording to the remembered position(s)
            xy = [int(n) for n in item[1].split(",")]
            move_to = (",").join(str(n) for n in [xy[0], xy[1]])
            command = set_val+item[0]+val+move_to
            subprocess.Popen(["/bin/bash", "-c", command])
        except:
            pass
    # restart nautilus
    subprocess.call(["killall", "nautilus"])
    subprocess.Popen(["nautilus"])
    # send notification
    subprocess.Popen(["/bin/bash", "-c", "notify-send 'Icon positions were restored'"])

if arg == "snapshot":
    snapshot()
elif arg == "restore":
    restore()

How to use

  1. Copy the script into an empty file, save it as restore_icons.py
  2. In the head section of the script, set the appropriate (localized) name of the Desktop folder:

    # --- set your localized desktop name below:
    dt_name = "Desktop"
    # ---
    
  3. Test- run the script by running the command:

    python3 /path/to/restore_icons.py snapshot
    

    A snapshot of the current layout will be take. De shut down menu appears on your desktop, but cancel it by clicking outside the menu on your desktop.

    <image_1>

    Then deliberately mess around with your icons (positions) on your desktop.
    Finaly, run the script again with another argument:

     python3 /path/to/restore_icons.py restore
    

    Wait for 20 seconds (see Note at the bottom why that is included in the script) and your desktop icons will be restored to how it was before you took the snapshot.

  4. If all works fine, add a shortut key: Choose: System Settings > "Keyboard" > "Shortcuts" > "Custom Shortcuts". Click the "+" and add the command:

    python3 /path/to/restore_icons.py snapshot
    

    This will be the shortcut you need to use, to shut down your computer.

  5. Add the restore command to Startup Applications: Dash > Startup Applications > Add. Add the command:

    python3 /path/to/restore_icons.py restore
    

    Now 20 seconds after startup (log in), your icons will be exactly like you left them on shut down.

Note

The 20 seconds break is to make sure the second monitor is found and reconnected before the restore command runs.


The Script works on Linux Mint 18.3 Cinnamon too, when i did the following little changes:

replace:

nautilus-icon-position -> nemo-icon-position gnome-session-quit ->cinnamon-session-quit "killall","nautilus" ->"killall","nemo-desktop" subprocess.Popen(["nautilus"]) -> subprocess.POpen(["nemo-desktop"])

thanks a lot for that great script

cinnamon


I wrote iconic to solve this problem by letting you save and load desktop icon settings. Additionally it will:

  • Let you move icons to any one of three monitors
  • Define a grid size to spread icons evenly across desktop as close or far apart as you prefer
  • Not suffer the "lost icon syndrome" that occurs when monitors of multiple resolutions are used
  • Sort icons alphabetically, alphabetically with "Link to" prefix ignored, sort by modified date ascending or date descending
  • Allow different grid size (columns x rows) depending on monitor, EG more on 4K monitor than 2K monitor
  • Instant Test button for quick experimentation on column x row changes or reserved space changes for monitor left, top, right or bottom areas
  • Test button will last for x seconds defined by you, clear all windows before test and restore them after test
  • Bash script for easy modifications

You can get the script on github.

Here's the main screen:

iconic main menu.png

Visit the github page for iconic to see all the other screens, explanations and a copy of the script.