What happens internally when I click "Lock to launcher" in Unity?

In the Unity desktop, when I start a GUI application, its icon appears in the launcher (if it isn't already there).

Now when I right-click this icon, I either get the option Lock to Launcher or Unlock from Launcher, depending on whether the application is already locked to the launcher or not.

My question is:
What happens under the hood, when I click one of those two options if no .desktop file exists?

Can it automatically create simple .desktop files if it can't find one, under which conditions may that happen, and where do the pinned launcher items get saved?


What happens if you lock/unlock an application to/from the launcher

Not sure if this answer is deep enough "under the hood", but this is what happens:

You can get the current content of the Unity Launcher by the command:

gsettings get com.canonical.Unity.Launcher favorites

It will produce a list, looking like:

['application://extras-qlequicklisteditor.desktop', 'application://gedit.desktop', 'application://gnome-terminal.desktop', 'application://nautilus.desktop', 'application://firefox.desktop', 'application://thunderbird.desktop', 'application://gnome-screenshot.desktop', 'application://dconf-editor.desktop', 'application://virtualbox.desktop', 'application://gnome-tweak-tool.desktop', 'unity://running-apps', 'unity://devices', 'unity://expo-icon']

The mentions in the list are obviously based on the names of the corresponding .desktop files.

Now when you run a GUI application, when you right- click on its icon in the launcher and choose Lock to Launcher, The currently chosen item is added to the list, while Unlock from Launcher will remove the item from the list.

Editing the Unity Launcher programmatically

Re- reading your (first) comment below your question: You can, as mentioned, get the current Launcher items by the command:

 gsettings get com.canonical.Unity.Launcher favorites

and set a possible altered list by the command:

 gsettings set com.canonical.Unity.Launcher favorites "[item1, item2, etc]"

You can then of course edit the Unity Launcher's content programmatically, as is done here.

If the application has no .desktop file

If You run a GUI application without an existing .desktop file, Unity creates a basic one locally (in ~/.local/share/applications), named after the executable (application.desktop). In the Exec= line, you will find the command you ran, to call the application.

If you would look into a .desktop file, created this way, it includes the line:

X-UnityGenerated=true

Note

As mentioned by @muru (thanks!), in a few (exceptional, as it seems) situations, Unity does not succeed to create a "missing" .desktop file of an executable. The only example I could find however was in case of Tkinter windows, which are owned by pid 0 in the output of wmctrl -lp.


What happens when you click Lock To Launcher option, is that Unity will change specific dconf schema for the launcher favorites and call couple of dbus methods. The key for programmers and application developers is the change in dconf schema. ( Jacob's answer relies on gsettings, however the idea is essentially the same as gsettings is just front end with sanity check for dconf ). Here, I just want to present a few observations made.

Side note: here, I'm testing everything with a custom python app which has no .desktop file

Dconf changes

Running dconf watch / will reveal that this is what gets changed:

$ dconf watch /                                                                           # Lock to launcher
/com/canonical/unity/launcher/favorites
  ['application://gnome-terminal.desktop', 'application://firefox.desktop', 'application://gedit.desktop', 'application://sakura.desktop', 'application://mplab.desktop', 'unity://running-apps', 'application://pyqt_clock_py.desktop', 'unity://devices']
# Unlock from launcher

/com/canonical/unity/launcher/favorites
  ['application://gnome-terminal.desktop', 'application://firefox.desktop', 'application://gedit.desktop', 'application://sakura.desktop', 'application://mplab.desktop', 'unity://running-apps', 'unity://devices']

Creation of .desktop file for the app

Initially, there is check whether or not the .desktop file exists for the app. If the file exists - good. If not - Unity will issue a dbus call to org.ayatana.bamf.control.CreateLocalDesktopFile method on org.ayatana.bamf service. This can be used to automate .desktop file creation. Although , this doesn't show-up in the dbus-monitor output, I believe that is one of the methods that may be used Unity.

Here's a small demo:

# start custom app in background, app appears on the launcher
$> python /home/xieerqi/bin/python/pyqt_clock.py &                                                                    
[1] 16768
# confirm that there is no .desktop file for that app
$> qdbus org.ayatana.bamf /org/ayatana/bamf/matcher org.ayatana.bamf.matcher.RunningApplicationsDesktopFiles 
/usr/share/applications/compiz.desktop
/usr/share/applications/firefox.desktop
/usr/share/applications/x-terminal-emulator.desktop
$> ls .local/share/applications/pyqt_clock_py.desktop                                                                 
ls: cannot access .local/share/applications/pyqt_clock_py.desktop: No such file or directory
# I use custom function to find list of running apps by their dbus path
$> typeset -f running_apps
running_apps() {
    qdbus org.ayatana.bamf /org/ayatana/bamf/matcher org.ayatana.bamf.matcher.RunningApplications | xargs -I {} bash -c "echo {}; qdbus org.ayatana.bamf {} org.ayatana.bamf.view.Name" 
} 
$> running_apps                                                                                                       
/org/ayatana/bamf/application/0x146bb90
Clock
/org/ayatana/bamf/application/1932146384 # that's what we want
Firefox Web Browser
/org/ayatana/bamf/application/1060483892
MY CUSTOM TERMINAL
/org/ayatana/bamf/application/885622223
Compiz
/org/ayatana/bamf/application/0x146b8f0
 # Use  the dbus method to create desktop file
$> qdbus org.ayatana.bamf /org/ayatana/bamf/control \                                                                 
> org.ayatana.bamf.control.CreateLocalDesktopFile  /org/ayatana/bamf/application/0x146bb90                            
# Verify its creation
$> ls .local/share/applications/pyqt*                                                                                 
.local/share/applications/pyqt_clock_py.desktop
# This doesn't however pin the program to launcher
# Different call to dbus will be issued
$ gsettings get com.canonical.Unity.Launcher favorites                                                                
['application://gnome-terminal.desktop', 'application://firefox.desktop', 'application://gedit.desktop', 'application://sakura.desktop', 'application://mplab.desktop', 'unity://running-apps', 'unity://devices']

There is a different dbus method, that destroys the file:

dbus-monitor revelations

I've performed locking and unlocking action with dbus-monitor --profile command running. Bellow you can see several calls to methods ( designated by mc ) to ca.desrt.dconf.Writer interface and Zeitgeist.

mc  1461904751  317156  3474    :1.32   /ca/desrt/dconf/Writer/user ca.desrt.dconf.Writer   Change
mr  1461904751  317976  4520    3473    :1.32
mc  1461904751  320331  3475    :1.32   /org/gnome/zeitgeist/log/activity   org.gnome.zeitgeist.Log InsertEvents
mc  1461904751  341474  118 :1.93   /org/gnome/zeitgeist/monitor/special    org.gnome.zeitgeist.Monitor NotifyInsert
mr  1461904751  341576  119 3475    :1.32
mr  1461904751  341927  39  118 :1.93
mr  1461904751  356896  114 3474    :1.32
sig 1461904751  357892  115 /ca/desrt/dconf/Writer/user ca.desrt.dconf.Writer   Notify

If perform more detailed view with dconf-monitor you will see that calls to dconf writes sequence of bytes and zeitgeist logs the entry added. I've tested this several times, and these are the same actions performed in each case.

Sample output form Zeitgeist.

method call sender=:1.93 -> dest=org.gnome.zeitgeist.SimpleIndexer serial=104 path=/org/gnome/zeitgeist/monitor/special; interface=org.gnome.zeitgeist.Monitor; member=NotifyInsert
   struct {
      int64 1461904249994
      int64 1461904249994
   }
   array [
      struct {
         array [
            string "14288"
            string "1461904249994"
            string "http://www.zeitgeist-project.com/ontologies/2010/01/27/zg#AccessEvent"
            string "http://www.zeitgeist-project.com/ontologies/2010/01/27/zg#UserActivity"
            string "application://compiz.desktop"
            string ""
         ]
         array [
            array [
               string "application://pyqt_clock_py.desktop"
               string "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Software"
               string "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#SoftwareItem"
               string ""
               string "application/x-desktop"
               string "Clock"
               string "unknown"
               string "application://pyqt_clock_py.desktop"
               string ""
            ]
         ]
         array [
         ]
      }
   ]

Unity Source Code:

The specific code that handles that is defined in launcher/ApplicationLauncherIcon.cpp of Unity source code

/* (Un)Stick to Launcher */
  glib::Object<DbusmenuMenuitem> menu_item(dbusmenu_menuitem_new());
  const char* label = !IsSticky() ? _("Lock to Launcher") : _("Unlock from Launcher");
  dbusmenu_menuitem_property_set(menu_item, DBUSMENU_MENUITEM_PROP_LABEL, label);
  dbusmenu_menuitem_property_set_bool(menu_item, DBUSMENU_MENUITEM_PROP_ENABLED, true);
  dbusmenu_menuitem_property_set_bool(menu_item, DBUSMENU_MENUITEM_PROP_VISIBLE, true);

But the actual job is performed by unity-shared/BamfApplicationManager.cpp

bool Application::SetSticky(bool const& param)
{
  bool is_sticky = GetSticky();
  if (param == is_sticky)
    return false; // unchanged

  bamf_view_set_sticky(bamf_view_, param);
  return true; // value updated
}

Where does that leave us ?

Knowing the changes being made to dconf and the specific behavior of the launcher can help us extend its functionality. The examples of that from both me and Jacob include:

  • Setting up Launcher form an input file
  • Reordering to make active app top or bottom item
  • Cloning launcher setup from user to user
  • Creating unique launcher per workspace

The particular usefulness of the dbus method for creating .desktop files allows automating shortcut creating for the custom-written apps , which later can be locked to the launcher using gsettings method Jacob has described.