Ubuntu – How to show (raise) all windows of an application

unitywindow-manager

I have an application using multiple windows. How can I quickly bring all the windows of that application to the foreground?

When I scroll through the applications with the scroll-wheel it only shows one window. When going to the next window, the last window is brought to the background again.

When I click on the application icon, I get a full-screen overview of all the windows. I have to select each window manually and move my mouse across half the screen several times.

My best solution so far is minimizing all windows (Ctrl+Super+D) and then show the windows of my application using the scroll-wheel.

Is there a better solution?

Best Answer

EDIT -new answer-

The answer(s) below is/are still totally valid, and so the suggested options. Ongoing insight however made me add this option to use the indicator below, which is probably the most elegant solution.

As such, it should probably replace option 5 (using a .desktop file).

Simply choose the application from the list, and all windows of the corresponding application (present on the current viewport) will raise:

enter image description here

How to use

from ppa:

sudo add-apt-repository ppa:vlijm/upfront
sudo apt-get update
sudo apt-get install upfront

...or manually:

#!/usr/bin/env python3
import signal
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('AppIndicator3', '0.1')
from gi.repository import Gtk, AppIndicator3, GObject
import time
from threading import Thread
import os
import subprocess
import getpass

currpath = os.path.dirname(os.path.realpath(__file__))

class Indicator():
    def __init__(self):
        self.app = 'raise_apps'
        iconpath = os.path.join(currpath, "raise.png")
        self.indicator = AppIndicator3.Indicator.new(
            self.app, iconpath,
            AppIndicator3.IndicatorCategory.OTHER)
        self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)       
        self.indicator.set_menu(self.create_menu())
        # the thread:
        self.update = Thread(target=self.check_recent)
        # daemonize the thread to make the indicator stopable
        self.update.setDaemon(True)
        self.update.start()

    def create_menu(self):
        # creates the (initial) menu
        self.menu = Gtk.Menu()
        # separator
        initial = Gtk.MenuItem("Fetching list...")
        menu_sep = Gtk.SeparatorMenuItem()
        self.menu.append(initial)
        self.menu.append(menu_sep)
        # item_quit.show() 
        self.menu.show_all()
        return self.menu

    def raise_wins(self, *args):
        index = self.menu.get_children().index(self.menu.get_active())
        selection = self.menu_items2[index][1]
        for w in selection:
            execute(["wmctrl", "-ia", w])

    def set_new(self):
        # update the list, appearing in the menu
        for i in self.menu.get_children():
            self.menu.remove(i)
        for app in self.menu_items2:

            sub = Gtk.MenuItem(app[0])
            self.menu.append(sub)
            sub.connect('activate', self.raise_wins)
        # separator
        menu_sep = Gtk.SeparatorMenuItem()
        self.menu.append(menu_sep)
        # quit
        item_quit = Gtk.MenuItem('Quit')
        item_quit.connect('activate', self.stop)
        self.menu.append(item_quit)
        self.menu.show_all()

    def get_apps(self):
        # calculate screen resolution
        res_output = get("xrandr").split(); idf = res_output.index("current")
        res = (int(res_output[idf+1]), int(res_output[idf+3].replace(",", "")))
        # creating window list on current viewport / id's / application names
        w_data = [l.split() for l in get(["wmctrl", "-lpG"]).splitlines()]
        # windows on current viewport
        relevant = [w for w in w_data if 0 < int(w[3]) < res[0] and 0 < int(w[4]) < res[1]]
        # pids
        pids = [l.split() for l in get(["ps", "-u", getpass.getuser()]).splitlines()]
        matches = [[p[-1], [w[0] for w in relevant if w[2] == p[0]]] for p in pids]
        return [m for m in matches if m[1]]

    def check_recent(self):
        self.menu_items1 = []
        while True:
            time.sleep(4)
            self.menu_items2 = self.get_apps()
            for app in self.menu_items2:
                app[0] = "gnome-terminal" if "gnome-terminal" in app[0] else app[0]
            if self.menu_items2 != self.menu_items1:
                GObject.idle_add(
                    self.set_new, 
                    priority=GObject.PRIORITY_DEFAULT
                    )
            self.menu_items1 = self.menu_items2

    def stop(self, source):
        Gtk.main_quit()

def get(command):
    return subprocess.check_output(command).decode("utf-8")

def execute(command):
    subprocess.Popen(command)

Indicator()
GObject.threads_init()
signal.signal(signal.SIGINT, signal.SIG_DFL)
Gtk.main()
  • The indicator needs wmctrl

    sudo apt-get wmctrl
    
  • Copy the indicator into an empty file, save it as raise_apps.py

  • Copy the image below, save it exactly named raise.png in one and the same directory as the indicator.

    enter image description here

  • Then simply run it by the command:

    python3 /path/to/raise_apps.py

  • Add if you want to Startup Applications:

    /bin/bash -c "sleep 10 && python3 /path/to/raise_apps.py" 
    

OLD ANSWER:

About the question

With the right tools, it is not very complicated to "just" raise all windows of an application. It is a bit more complicated to make sure only the windows of the current viewport are raised. The real challenge however is to find a convenient way to make the action available to the user.

Below five options to take care of that, to show how it can be done. All options are ready to be used. The last option however is kind of experimental; it works fine but has a few minor cosmetic downsides, as explained in the description of the option. I added it nevertheless as a concept.

Spreading the windows automatically in a non- overlapping way, as suggested in a comment, seems not a practical idea to me; if you work in an (application-wise) grouped window setup, the script would possibly unwantedly rearrange windows.

How to use

For all options you need to:

  • install wmctrl if it is not yet on your system:

    sudo apt-get install wmctrl
    
  • create, if it does not exist yet, the directory:

    ~/bin
    

    (explanation: the directory ~/bin is in $PATH, so you can run executables by their name)

  • Copy the script, corresponding to the option, paste it into an empty file, save it as raise_app (no extension) in ~/bin and make it executable

In the separate options, possible additional steps will be explained.

Option 1: choose the application by entering one or more characters

  • Press a key combination, a zenity window will appear
  • Enter one or more characters of the application's name in the entry box
  • Press enter

This will make all windows of the matching application (on the current viewport) come to front.

raise all gnome-terminal windows on the current viewport:

enter image description here

enter image description here

How to use:

  • Do the set up as described in "How to use"
  • Test-run it by the command:

    raise_app
    
  • If all works fine, add it to a shortcut key combination of your choice: Choose: System Settings > "Keyboard" > "Shortcuts" > "Custom Shortcuts". Click the "+" and add the command

The script:

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

def get(command):
    return subprocess.check_output(["/bin/bash", "-c", command]).decode("utf-8")

def execute(command):
    subprocess.Popen(["/bin/bash", "-c", command])
# calculate screen resolution
res_output = get("xrandr").split(); idf = res_output.index("current")
res = (int(res_output[idf+1]), int(res_output[idf+3].replace(",", "")))
# creating window list on current viewport / id's / application names
w_data = [l.split()[0:7] for l in get("wmctrl -lpG").splitlines()]
windows = [[get("ps -u "+getpass.getuser()+" | grep "+w[2]).split()[-1], w[0]]
           for w in w_data if 0 < int(w[3]) < res[0] and 0 < int(w[4]) < res[1]]
# ask user for first characters
try:
    arg = get('zenity --entry --text "first characters" --title "application"').strip()
except subprocess.CalledProcessError:
    pass
# raise matching windows
try:
    [execute("wmctrl -ia "+item[1]) for item in windows if item[0].startswith(arg)]
except (subprocess.CalledProcessError, NameError):
    pass



Option 2: cycle through applications and raise their windows with a key combination:

Let's say I have the script below under a key combination Alt+1. I have several windows open of:

  • firefox
  • gnome-terminal
  • nautilus

The current state:

enter image description here

I press once Alt+1, all nautilus windows are raised:

<image>

I press again Alt+1, all firefox windows are raised:

<image>

I press again Alt+1, all gnome-terminal windows are raised again, the cycle starts over:

<image>

How to use

  • Do the set up as described in "How to use"
  • Add it to a shortcut key combination of your choice: Choose: System Settings > "Keyboard" > "Shortcuts" > "Custom Shortcuts". Click the "+" and add the command

    raise_app
    

Then cycle through your applications with grouped application windows with your key combination.

The script:

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

include_single = True # set to False if you only want to cycle through apps with multiple windows

def get(command):
    return subprocess.check_output(["/bin/bash", "-c", command]).decode("utf-8")

def execute(command):
    subprocess.Popen(["/bin/bash", "-c", command])

def get_frontmost():
    cmd = "xprop -root"
    frontmost = [l for l in get(cmd).splitlines() if\
                 "ACTIVE_WINDOW(WINDOW)" in l][0].split()[-1]
    return frontmost[:2]+"0"+frontmost[2:]
# calculate screen resolution
res_output = get("xrandr").split(); idf = res_output.index("current")
res = (int(res_output[idf+1]), int(res_output[idf+3].replace(",", "")))
# creating window list on current viewport / id's / application names
w_data = [l.split()[0:7] for l in get("wmctrl -lpG").splitlines()]
windows = [[get("ps -u "+getpass.getuser()+" | grep "+w[2]).split()[-1], w[0]]
           for w in w_data if 0 < int(w[3]) < res[0] and 0 < int(w[4]) < res[1]]
# create application list to cycle through
if include_single == False:
    pre = [it[0] for it in windows]
    apps = sorted(list(set([it for it in pre if pre.count(it) > 1])))
else:
    apps = sorted(list(set([it[0] for it in windows])))
if len(apps) == 0:
    pass
else:
    # get the frontmost window as a last itm in the cycle
    front = get_frontmost()
    front_pid = [l.split()[2] for l in get("wmctrl -lp").splitlines() if front in l][0]
    last_infront = get("ps -u "+getpass.getuser()+" | grep "+front_pid).split()[-1]
    # determine next apllication to raise
    if not last_infront in apps or last_infront == apps[-1]:
        arg = apps[0]
        print(arg)
    else:
        arg = apps[apps.index(last_infront)+1]
    # raise matching windows
    try:
        [execute("wmctrl -ia "+item[1]) for item in windows if item[0] == arg]
    except (subprocess.CalledProcessError, NameError):
        pass



Option 3: press key combination + click on launcher icon -or- application window to raise all windows on the current viewport

This is probably the option that is closest to what is described in the question / comment.

Let's say I have a messy desktop with three nautilus windows buried under other windows.

<image>

To raise all nautilus windows (example shortcut: Alt+1):

  • Press Alt+1, release (!)
  • Within 3 seconds, either:

    click on the application's icon in the launcher

    <image>

    or:

    click on one of the application's windows

    <image>

    result:

    <image>


How to use:

  • Do the set up as described in "How to use"
  • Test-run it by the command:

    raise_app
    
  • If all works fine, add it to a shortcut key combination of your choice: Choose: System Settings > "Keyboard" > "Shortcuts" > "Custom Shortcuts". Click the "+" and add the command

Then:

  • Press your key combination and within 3 seconds, either:

    • click on the application's icon in the launcher
    • click on one of the application's windows

The script

#!/usr/bin/env python3
import subprocess
import getpass
import time

def get(command):
    return subprocess.check_output(["/bin/bash", "-c", command]).decode("utf-8")

def execute(command):
    subprocess.Popen(["/bin/bash", "-c", command])

def get_frontmost():
    cmd = "xprop -root"
    frontmost = [l for l in get(cmd).splitlines() if\
                 "ACTIVE_WINDOW(WINDOW)" in l][0].split()[-1]
    return frontmost[:2]+"0"+frontmost[2:]

# calculate screen resolution
res_output = get("xrandr").split(); idf = res_output.index("current")
res = (int(res_output[idf+1]), int(res_output[idf+3].replace(",", "")))
# get window data for various purposes
w_data = get("wmctrl -lpG").splitlines()
non_windows = sum([[l.split()[0] for l in w_data if it in l]\
               for it in ("unity-launcher", "unity-panel", "unity-dash", "Hud")], [])
# get id of current window
curr_window = get_frontmost()
# user gets 3 seconds to pick an application window (or launcher icon)
t = 0
while t < 4:
    w_id1 = get_frontmost()
    time.sleep(1)
    w_id2 = get_frontmost()
    if w_id1 == w_id2 or w_id2 in non_windows+[curr_window]:
        t = t+1
    else:
        new_frontmost = w_id2
        break
# raise
try:
    pid = [l.split()[2] for l in w_data if new_frontmost in l]
    wl_data = [l.split() for l in w_data]
    raise_windows = [l[0] for l in wl_data if pid[0] == l[2] and\
                     0 < int(l[3]) < res[0] and 0 < int(l[4]) < res[1]]
    [execute("wmctrl -ia "+item) for item in raise_windows]
except NameError:
    pass


Option 4: a key combination calls an option list, showing the number of windows per application on the current viewport

This one turned out to be more convenient then I assumed:

Pressing the (again example-) key combination Alt+1 calls a zenity window, listing all applications and the number of their windows on the current viewport:

enter image description here

Simply pressing the or arrows will bring you to the right option. Press Enter and all windows of the chosen application are raised.

How to use:

  • Do the set up as described in "How to use"
  • Test-run it by the command:

    raise_app
    
  • If all works fine, add it to a shortcut key combination of your choice: Choose: System Settings > "Keyboard" > "Shortcuts" > "Custom Shortcuts". Click the "+" and add the command

The script

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

def get(command):
    return subprocess.check_output(["/bin/bash", "-c", command]).decode("utf-8")

def execute(command):
    subprocess.Popen(["/bin/bash", "-c", command])
# calculate screen resolution
res_output = get("xrandr").split(); idf = res_output.index("current")
res = (int(res_output[idf+1]), int(res_output[idf+3].replace(",", "")))
# creating window list on current viewport / id's / application names
w_data = [l.split()[0:7] for l in get("wmctrl -lpG").splitlines()]
windows = [[get("ps -u "+getpass.getuser()+" | grep "+w[2]).split()[-1], w[0]]
           for w in w_data if 0 < int(w[3]) < res[0] and 0 < int(w[4]) < res[1]]
# preparing zenity optionlist
apps = [item[0] for item in windows]
# prevent multiple zenity windows
if apps.count("zenity") > 1:
    pass
elif apps.count("zenity") > 0:
    execute('zenity --info --text "Another Zenity window is open already"')
# preventing empty windowlist
elif len(apps) > 0:
    applist = [[app, str(apps.count(app))] for app in set(apps)]
    applist.sort(key=lambda x: x[1])
    # calling zenity window
    try:
        arg = get('zenity  --list  --text "Choose an application" '+\
               '--title "Current windows" '+\
               '--column "application" '+\
               '--column "windows" '+\
               '--height 250 '+\
               '--width 250 '+\
               (" ").join(sum(applist, [])))
    except subprocess.CalledProcessError:
        pass
    # raise matching windows
    try:
        [execute("wmctrl -ia "+item[1]) \
         for item in windows if arg.startswith(item[0])]
    except (subprocess.CalledProcessError, NameError):
        pass
else:
    execute('zenity --info --text "No windows to list"')



Option 5: raise windows of running applications from a launcher icon

This option exists of a launcher icon, with the currently running applications in a quicklist. Choose one, and all windows of the applications will be raised.

enter image description here

The launcher is automatically updated when the list of running applications (on the current viewport) changes. The quicklist shows a different list on other viewports, where windows of other applications are opened (will take 1-2 seconds to adapt).

As mentioned, although fully functional, this option is a meant as a concept. It has a few minor cosmetic downsides as it is. The most important:

  • The cursor "wheel" keeps spinning for a few seconds after an action. Although it does not effect the functionality, it is a cosmetic downside.
  • It takes 1-2 seconds for the applicationlist in the launcher icon to be updated after the list of running applications changes.

Furthermore the setup is slightly more complicated (although explained in detail below):

How to use

Below you will find:

two scripts / an icon / a .desktop file

  1. Prepare the setup as in "How to use", save the first (main-) script as raise_app in ~/bin
  2. Save the icon below (right-click, save as) as raise.png

    <icon>

  3. Copy the .desktop file into an empty file, edit the line

        Icon=/path/to/raise.png
    

    to the real path to the icon (paths with spaces between quotes)
    Save it as raise.desktop in ~/.local/share/applications

  4. Drag the .desktop file to the launcher to add it

  5. copy the second script, paste it into an empty file, save it as update_apps in ~/bin, make it executable.
  6. Add the following command to your startup applications (Dash > Startup Applications > Add):

    update_apps
    
  7. Log out and back in to make it work.

The first script

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

arg = sys.argv[1]

def get(command):
    return subprocess.check_output(["/bin/bash", "-c", command]).decode("utf-8")

def execute(command):
    subprocess.Popen(["/bin/bash", "-c", command])
# calculate screen resolution
res_output = get("xrandr").split(); idf = res_output.index("current")
res = (int(res_output[idf+1]), int(res_output[idf+3].replace(",", "")))
# creating window list on current viewport / id's / application names
w_data = [l.split()[0:7] for l in get("wmctrl -lpG").splitlines()]
windows = [[get("ps -u "+getpass.getuser()+" | grep "+w[2]).split()[-1], w[0]]
           for w in w_data if 0 < int(w[3]) < res[0] and 0 < int(w[4]) < res[1]]
try:
    [execute("wmctrl -ia "+item[1]) for item in windows if item[0].startswith(arg)]
except (subprocess.CalledProcessError, NameError):
    pass

The second script

#!/usr/bin/env python3
import subprocess
import getpass
import time
import os

dtfile = os.environ["HOME"]+"/.local/share/applications/raise.desktop"

def get(command):
    return subprocess.check_output(["/bin/bash", "-c", command]).decode("utf-8")

def execute(command):
    subprocess.Popen(["/bin/bash", "-c", command])
# calculate screen resolution
res_output = get("xrandr").split(); idf = res_output.index("current")
res = (int(res_output[idf+1]), int(res_output[idf+3].replace(",", "")))
# creating window list on current viewport / id's / application names
def applist():
    try:
        w_data = [l.split()[0:7] for l in get("wmctrl -lpG").splitlines()]
        windows = [[get("ps -u "+getpass.getuser()+" | grep "+w[2]).split()[-1], w[0]]
                   for w in w_data if 0 < int(w[3]) < res[0] and 0 < int(w[4]) < res[1]]
    except subprocess.CalledProcessError:
        return []
    else:
        return set([app[0] for app in windows])

def update_dtfile(applications, text):
    actionline = "Actions="+(";").join(applications)+";\n"
    with open(dtfile) as src:
        lines = src.readlines()
    lines = lines[:[i for i in range(len(lines)) \
                 if lines[i].startswith("Actions=")][0]]+[actionline]
    for item in text:
        for it in item:
            lines.append(it)
    with open(dtfile, "wt") as out:
        for line in lines:
            out.write(line)

while True:
    apps1 = applist()
    time.sleep(1)
    apps2 = applist()
    if apps1 != apps2: 
        text = [["[Desktop Action "+it+"]\n", "Name="+it+"\n",
            "Exec=raise_app "+it+"\n", "OnlyShowIn=Unity;\n\n",
            ]for it in apps2]
        update_dtfile(apps2, text)

The .desktop file

[Desktop Entry]
Name=Raise application windows
Comment=Raise groups of windows
Icon=/path/to/raise.png
Terminal=false
Type=Application
Version=1.0

Actions=



Brief Explanation

All solutions above use wmctrl to create a window list, using the wmctrl -lpG command. This command produces lines, looking like:

0x044000b3  0 3429   65   24   1615 1026 jacob-System-Product-Name unity - How to show all windows of an application? - Ask Ubuntu - Mozilla Firefox

These lines include:

  • 1st column: the window's id (that we can use to raise it)
  • 3rd column: the pid that owns the window.
  • 4th / 5th column: the window's geometry x-y (that we use to see if the window is on the current viewport, i.c.w xrandr)

The pid is looked up in the output of ps -u <username> to get a "user-readable" identification (name) of the application.
Thus we can allocate windows to applications. Subsequently we can raise the windows of a given application in a for loop with the command wmctrl -ia.

In option 3
the script starts a 3- second "waiting" loop, using the xprop -root command repeatedly to see if there is any change in what is the frontmost window; this will happen if the user either clicks on a launcher icon to raise an application's window, or clicks on a window directly. If so, the while- loop breaks and looks up the "new" frontmost application, and subsequently raises all other windows of that application.