Ubuntu 16.04: How do I add/remove pinned apps to Unity Launcher via terminal?

Contents:

  1. General theory of Launcher operation
  2. Possible ways of removing and appending to Unity launcher
  3. launcherctl.py utility

1. General theory of launcher operation

The Unity launcher essentially is a list of .desktop files. They are essentially shortcuts that allow launching applications as well as performing custom actions. Typically they are stored in /usr/share/applications , but can be also located in ~/.local/share/applications, and anywhere else on the system. For the general case, I recommend storing such files in /usr/share/applications for all users or ~/.local/share/applications for each individual user.

The Dconf database of settings allows storing list of such apps for Unity launcher and can be viewed and altered with gsettings utility. For instance:

$ gsettings get  com.canonical.Unity.Launcher favorites
['application://wps-office-et.desktop', 'application://wps-office-wpp.desktop', 'application://wps-office-wps.desktop', 'unity://running-apps', 'unity://devices']
$ gsettings set  com.canonical.Unity.Launcher favorites  "['wechat.desktop']"                                                         
$ gsettings get  com.canonical.Unity.Launcher favorites                                                                               
['application://wechat.desktop', 'unity://running-apps', 'unity://devices']

As you can see all the .desktop files have application:// prefix on them, however it is not required when setting the launcher list. The items with unity:// prefix are not alterable and cannot be removed.

The gsettings get com.canonical.Unity.Launcher favorites and gsettings set com.canonical.Unity.Launcher favorites commands can be used to create functions in your ~/.bashrc , for instance:

get_launcher()
{
    gsettings get  com.canonical.Unity.Launcher favorites
}

set_launcher()
{
    # call this as set_launcher "['file1.desktop','file2.desktop']"
    gsettings set  com.canonical.Unity.Launcher favorites "$1"
}

Example:

$ set_launcher "['firefox.desktop','gnome-terminal.desktop']"                                                                         
$ get_launcher                                                                                                                        
['application://firefox.desktop', 'application://gnome-terminal.desktop', 'unity://running-apps', 'unity://devices']

2. Possible ways of removing and appending to Unity Launcher

The simplest example has already been shown - via gsettings utility. Removing and appending specific item requires parsing the gsettings output. This can be done via sed or awk utilities , and with effort even in bash. However, I find that python allows the easier approach and "path of least resistance". Thus, the examples provided here use gsettings jointly with python.

Here's a case of removal:

$ gsettings get com.canonical.Unity.Launcher favorites|                                                                               
> python -c 'import ast,sys; x =[]; x = [i for l in sys.stdin for i in ast.literal_eval(l)]; 
> x.pop(x.index("application://"+sys.argv[1])); print x' firefox.desktop
['application://gnome-terminal.desktop', 'unity://running-apps', 'unity://devices']

What is happening here ? We pass output of the gsettings get via pipe to python. Python then reads standard input stream and using ast library evaluates the text representation of the list, and converts it to actual list that python can recognize. This greatly simplifies the work - if this was awk or sed we would have to deal with removing and appending individual characters. Finally , we remove ( pop ) the second command-line argument ( indicated by sys.argv[1] ) by finding it's index in the list. We now have new list, which can be passed on further via pipe to gsettings set

The complete command is then this:

$ gsettings get com.canonical.Unity.Launcher favorites|
> python -c 'import ast,sys; x =[]; x = [i for l in sys.stdin for i in ast.literal_eval(l)]; 
> x.pop(x.index("application://"+sys.argv[1])); print "\""+repr(x)+"\""' firefox.desktop |
> xargs -I {} gsettings set  com.canonical.Unity.Launcher favorites {}

Which can be nicely put into a ~/.bashrc function like so:

remove_launcher_item()
{
  gsettings get com.canonical.Unity.Launcher favorites|
  python -c 'import ast,sys; x =[]; x = [i for l in sys.stdin for i in ast.literal_eval(l)];\
  x.pop(x.index("application://"+sys.argv[1])); print "\""+repr(x)+"\""' "$1" |
  xargs -I {} gsettings set  com.canonical.Unity.Launcher favorites {}
}

A few things to note here is that we need to print again "string" representation of list enclosed into quotes and pass it via xargs. The idea with appending is similar, except instead of pop we use append function:

append_launcher_item()
{
  gsettings get com.canonical.Unity.Launcher favorites|
  python -c 'import ast,sys; x =[]; x = [i for l in sys.stdin for i in ast.literal_eval(l)];\
  x.append("application://"+sys.argv[1]); print "\""+repr(x)+"\""' "$1" |
  xargs -I {} gsettings set  com.canonical.Unity.Launcher favorites {}

}

Sample run:

$ get_launcher                                                                                                                        
['unity://running-apps', 'unity://devices', 'application://firefox.desktop']
$ append_launcher_item gnome-terminal.desktop                                                                                         
$ get_launcher                                                                                                                        
['unity://running-apps', 'unity://devices', 'application://firefox.desktop', 'application://gnome-terminal.desktop']
$ 

These functions don't necessarily have to be part of ~/.bashrc. You can place them into a script as well

3. launcherctl.py utility

Over time, I've researched and build a set of functions in python that can effectively do the same as gsettings utility. Putting the power of python together with those functions, I've made launcherctl.py utility.

This is a work in progress and will be expanded to include more functions in future. For this specific question, I will leave the source code as it appears in first version. Further versions and improvements can be found on GitHub.

What are the advantages of this script compared to bash functions ? 1. This is a "centralized" utility with specific purpose. You don't have to have separate script/function for each action. 2. Simple to use, minimalist command-line options 3. When used in conjunction with other utilities, this provides for more readable code.

Usage:

As shown by the -h command line option:

$ ./launcherctl.py -h                                                                                                                 
usage: launcherctl.py [-h] [-f FILE] [-a] [-r] [-l] [-c]

Copyright 2016. Sergiy Kolodyazhnyy.
    This command line utility allows appending and removing items
    from Unity launcher, as well as listing and clearing the
    Launcher items.

    --file option is required for --append and --remove 


optional arguments:
  -h, --help            show this help message and exit
  -f FILE, --file FILE
  -a, --append
  -r, --remove
  -l, --list
  -c, --clear

Command line usage is simple.

Appending:

$ ./launcherctl.py -a -f wechat.desktop

Removal:

$ ./launcherctl.py -r -f wechat.desktop

Clearing launcher completely:

$ ./launcherctl.py -c 

Listing items on the launcher:

$ ./launcherctl.py -l                                                                                                                 
chromium-browser.desktop
firefox.desktop
opera.desktop
vivaldi-beta.desktop

As mentioned before, it can be used with other commands. For instance,adding from file:

$ cat new_list.txt                                                                                                                    
firefox.desktop
wechat.desktop
gnome-terminal.desktop    
$ cat new_list.txt | xargs -L 1 launcherctl.py -a -f

Same can be used with removal of items given from a text file

Removing a 3rd item from the dash button:

$ launcherctl.py  -l | awk 'NR==3' | xargs -L 1 launcherctl.py -r -f  

Obtaining source code and installation

Manual way:

  1. Create a directory ~/bin.
  2. Save source code from below into file ~/bin/launcherctl.py
  3. If you are bash user, you can source ~/.profile, or logout and login. The ~/bin directory will be added to your $PATH variable automatically. For those who don't use bash , add ~/bin to your $PATH variable inside your shell configuration file, like so: PATH="$PATH:$HOME/bin

As I've mentioned latest changes to the code go into the GitHub repository. If you have git installed, the steps are simpler:

  1. git clone https://github.com/SergKolo/sergrep.git ~/bin/sergrep
  2. echo "PATH=$PATH:$HOME/bin/sergrep" >> ~/.bashrc
  3. source ~/.bashrc. After this step, you can call launcherctl.py as any other command. Getting updates is as simple as cd ~/bin/sergrep;git pull

Obtaining code from GitHub without git:

  1. cd /tmp
  2. wget https://github.com/SergKolo/sergrep/archive/master.zip
  3. unzip master.zip
  4. If you don't have ~/bin , make it with mkdir ~/bin
  5. mv sergrep-master/launcherctl.py ~/bin/launcherctl.py

In all cases, the same rules apply - the script must live in a directory that is added to PATH variable and must have executable permissions set with chmod +x launcherctl.py


Original source code:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

#
# Author: Serg Kolo , contact: [email protected]
# Date: Sept 24, 2016
# Purpose: command-line utility for controling the launcher
#          settings
# Tested on: Ubuntu 16.04 LTS
#
#
# Licensed under The MIT License (MIT).
# See included LICENSE file or the notice below.
#
# Copyright © 2016 Sergiy Kolodyazhnyy
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import gi
from gi.repository import Gio
import argparse
import sys

def gsettings_get(schema, path, key):
    """Get value of gsettings schema"""
    if path is None:
        gsettings = Gio.Settings.new(schema)
    else:
        gsettings = Gio.Settings.new_with_path(schema, path)
    return gsettings.get_value(key)

def gsettings_set(schema, path, key, value):
    """Set value of gsettings schema"""
    if path is None:
        gsettings = Gio.Settings.new(schema)
    else:
        gsettings = Gio.Settings.new_with_path(schema, path)
    if isinstance(value,list ):
        return gsettings.set_strv(key, value)
    if isinstance(value,int):
        return gsettings.set_int(key, value)

def puts_error(string):
    sys.stderr.write(string+"\n")
    sys.exit(1)

def list_items():
    """ lists all applications pinned to launcher """
    schema = 'com.canonical.Unity.Launcher'
    path = None
    key = 'favorites'
    items = list(gsettings_get(schema,path,key))
    for item in items:
        if 'application://' in item:
            print(item.replace("application://","").lstrip())

def append_item(item):
    """ appends specific item to launcher """
    schema = 'com.canonical.Unity.Launcher'
    path = None
    key = 'favorites'
    items = list(gsettings_get(schema,path,key))

    if not item.endswith(".desktop"):
        puts_error( ">>> Bad file.Must have .desktop extension!!!")

    items.append('application://' + item)
    gsettings_set(schema,path,key,items)

def remove_item(item):
    """ removes specific item from launcher """
    schema = 'com.canonical.Unity.Launcher'
    path = None
    key = 'favorites'
    items = list(gsettings_get(schema,path,key))

    if not item.endswith(".desktop"):
       puts_error(">>> Bad file. Must have .desktop extension!!!")
    items.pop(items.index('application://'+item))
    gsettings_set(schema,path,key,items)

def clear_all():
    """ clears the launcher completely """
    schema = 'com.canonical.Unity.Launcher'
    path = None
    key = 'favorites'

    gsettings_set(schema,path,key,[])

def parse_args():
    """parse command line arguments"""

    info="""Copyright 2016. Sergiy Kolodyazhnyy.
    This command line utility allows appending and removing items
    from Unity launcher, as well as listing and clearing the
    Launcher items.

    --file option is required for --append and --remove 
    """
    arg_parser = argparse.ArgumentParser(
                 description=info,
                 formatter_class=argparse.RawTextHelpFormatter)
    arg_parser.add_argument('-f','--file',action='store',
                            type=str,required=False)
    arg_parser.add_argument('-a','--append',
                            action='store_true',required=False)

    arg_parser.add_argument('-r','--remove',
                            action='store_true',required=False)
    arg_parser.add_argument('-l','--list',
                            action='store_true',required=False)

    arg_parser.add_argument('-c','--clear',
                            action='store_true',required=False)
    return arg_parser.parse_args()

def main():
    """ Defines program entry point """
    args = parse_args()

    if args.list:
       list_items()
       sys.exit(0)

    if args.append:
       if not args.file:
          puts_error(">>>Specify .desktop file with --file option")

       append_item(args.file)
       sys.exit(0)

    if args.remove:
       if not args.file:
          puts_error(">>>Specify .desktop file with --file option")

       remove_item(args.file)
       sys.exit(0)

    if args.clear:
       clear_all()
       sys.exit(0)

    sys.exit(0)

if __name__ == '__main__':
    main()

Additional notes:

  • in the past I've created an answer that allows setting the launcher list from file: https://askubuntu.com/a/761021/295286
  • I've also created a Unity indicator for switching between multiple lists. See instructions here: http://www.omgubuntu.co.uk/2016/09/launcher-list-indicator-update-ppa-workspaces