How to get the list of running GUI applications in the Unity Launcher?

I need a list with only those apps that are currently open/running in Dash, the ones that have that small white arrow(s) on the left side of the icon.

Is there any way to get it?

dash with arrows screenshoot


Solution 1:

Interesting question.

As always, there are different ways to get a list of these applications, each of them with specific ad- and disadvantages.

Getting a list of processes, represented in the Launcher, using the window list

Since only applications with a (mapped) window appear in the Launcher, using:

wmctrl -lp 

(wmctrl is not installed by default), we can get a list of opened windows and the process- id the windows belong to. The format of the output is:

0x05204641  0 12618  jacob-System-Product-Name verhaal (~) - gedit

where for us most important information is in:

  • the first string (0x05204641); this is the window -id
  • the third string (12618); this is the process id (pid) the window belongs to, and,
  • the last section (verhaal (~) - gedit); this is the window name.

Once we have the pid, we can look up the corresponding process name by the command:

ps -p <pid> -o comm=

We can script the steps above, and list the output(s) for existing windows, looking like (using python):

{'gnome-terminal', 'nautilus', 'gedit', 'GuitarPro.exe', 'firefox', 'thunderbird', 'soffice.bin'}

Complications

This seems straightforward. However, as always, reality is a bit more complicated. There are a few exceptions and complications we need to take care of:

  1. Some windows will belong to pid 0, which will raise an error when trying to get their properties. Windows of Idle (python IDE) or tkinter are such windows.
  2. Some windows are no "real" or windows, like transient windows (windows, which are called from, and belonging to other windows) or for example the desktop itself. These windows are listed as windows in the output of wmctrl, but do not appear separately in Dash.
  3. In some cases, the application's name is quite different from the process name, for example in the case of LibreOffice where all modules have the process name of soffice.bin. At the same time, running the command soffice.bin will not work. In case you need to identify the modules (Calc, Writer etc.) separately, you'd need to get additional information, from the window's name for example.
  4. Another example is the process name of gnome-terminal, as it appears in the process list, as in the output of ps -e ww. In 14.04, gnome-terminal appears as gnome-terminal, however, in 15.04 / 15.10 it shows: /usr/lib/gnome-terminal/gnome-terminal-server.

What we need to fix at least

To fix the most important issues above, you need to:

  • add a check if the window is a "real" or "normal" window, checking with

    xprop -id <window_id>
    

    If the output includes the line:

    _NET_WM_WINDOW_TYPE(ATOM) = _NET_WM_WINDOW_TYPE_NORMAL
    

    The window is a valid window in the sense of the Unity Launcher

  • To fix the process name of gnome-terminal in 15.x (assuming you want the presented process name to be gnome-terminal) we need to add an exception, to rename the process name into gnome-terminal if it appears as /usr/lib/gnome-terminal/gnome-terminal-server

Script

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

try:
    listed = sys.argv[1]
except IndexError:
    listed = []

get = lambda cmd: subprocess.check_output(cmd).decode("utf-8").strip()

def check_wtype(w_id):
    # check the type of window; only list "NORMAL" windows
    return "_NET_WM_WINDOW_TYPE_NORMAL" in get(["xprop", "-id", w_id])

def get_process(w_id):
    # get the name of the process, owning the window
    proc = get(["ps", "-p", w_id, "-o", "comm="])
    proc = "gnome-terminal" if "gnome-terminal" in proc else proc
    return proc

wlist = [l.split() for l in subprocess.check_output(["wmctrl", "-lp"])\
         .decode("utf-8").splitlines()]

validprocs = set([get_process(w[2]) for w in wlist if check_wtype(w[0]) == True])

if listed == "-list":
    for p in validprocs:
        print(p)
else:
    print(validprocs)

How to use

  1. The script needs wmctrl:

    sudo apt-get install wmctrl
    
  2. copy the script above into an empty file, save it as get_running.py

  3. run it by the command:

    python3 /path/to/get_running.py
    

    It will output like:

    {'gnome-terminal', 'nautilus', 'gedit', 'GuitarPro.exe', 'firefox', 'thunderbird', 'soffice.bin'}
    

    or, run with the argument -list:

    thunderbird
    nautilus
    gnome-terminal
    firefox
    gedit
    GuitarPro.exe
    soffice.bin
    

Notes

From your question, it is not completely clear what is exactly the purpose of the found list. If you need to have the application's name, as it appears in the interface ("readable" names), a completely different approach might be suited:

  • All globally installed applications do have a .desktop file in /usr/share/applications. In far most cases, we can conclude the process name and the interface name of the application from its .desktop file. Using this information, we could relatively easy create a list of running GUI applications, presented by their "readable" name.

Also in this case however, reality is also more complicated then theory, as explained here.

Solution 2:

The way to do it with qdbus and org.ayatana.bamf interface.

List of open applications by .desktop file:

$ 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

Using org.ayatana.bamf.matcher.RunningApplications and org.ayatana.bamf.view.Name methods

$ qdbus org.ayatana.bamf /org/ayatana/bamf/matcher  \                          
> org.ayatana.bamf.matcher.RunningApplications | \                             
> xargs -I {} qdbus org.ayatana.bamf {} org.ayatana.bamf.view.Name
Firefox Web Browser
MY CUSTOM TERMINAL
Compiz