8

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?

9
  • 1
    Use the source.
    – muru
    Jan 3, 2016 at 19:53
  • 1
    @muru I mainly want to know whether it creates a new .desktop file automatically if none exists yet and how it stores which items are locked to the launcher. Maybe I should edit my question a bit... And did you downvote it? I'm not really looking forward to download and crawl through MBs of Unity source code, I don't even know where to start in there.
    – Byte Commander
    Jan 3, 2016 at 20:10
  • Yes, I did downvote. When you ask "what happens exactly under the hood", you're asking others to download and crawl through MBs of Unity source code. Because what happens exactly under the hood has only one answer: what's in the code.
    – muru
    Jan 3, 2016 at 20:11
  • @ByteCommander you don't mean what happens in com.canonical.Unity.Launcher favorites do you? Jan 3, 2016 at 20:15
  • 1
    @muru You're probably right. Is my current version narrowed down enough?
    – Byte Commander
    Jan 3, 2016 at 20:18

2 Answers 2

10

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.

5
  • 1
    The latter is not necessarily true. I started a basic GUI from Python (with a sample code using tkinter). The Lock to Launcher is available, but it does nothing ... presumably because it can't figure out the command that started the window.
    – muru
    Jan 3, 2016 at 20:44
  • Great answer, but I would still be interested more in the additional note by @muru, because the fact that it sometimes seems to create a .desktop file and sometimes not made me very curious about what criteria have to be met to succeed.
    – Byte Commander
    Jan 7, 2016 at 15:12
  • @ByteCommander Well, at this stage, it is a guess, but looking at tkinter windows, they are owned by WM class tk, and have pid 0. I assume that is the issue, but further testing would be needed. Jan 7, 2016 at 18:44
  • 1
    @muru, I tried a lot of different GUI executables, the ones failing are all tkinter windows with pid 0. Jan 7, 2016 at 18:47
  • Great answer. For tkinter to put an icon in launcher (after python starts) I use this: pngimg = tk.Image("photo", file="mserve.png") followed by this: toplevel.tk.call('wm', 'iconphoto', toplevel._w, pngimg). Others might change toplevel to root or gui or app, etc. Dec 6, 2020 at 14:41
2

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:

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.

2
  • Are you going to answer every question that @JacobVlijm responded to? :D But a nice one indeed.
    – Byte Commander
    Apr 29, 2016 at 5:53
  • 1
    @ByteCommander Of course !!! Competition is one of the driving forces in the world :D Seriously speaking though , it just happens that these types of questions interest me as well. Apr 29, 2016 at 5:57

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .