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.