21

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?

5
  • @Joschua Bringing all windows of an application to the front is not too difficult, but how would you like to define the application? would a key combination+clicking an application's window do? Jan 5 '15 at 20:24
  • @Joschua or mayby more elegant, a key combo + 1st character of application name? Jan 5 '15 at 20:51
  • I think the behavior ist the same with all applications. I most often miss this feature with terminal windows, where I often have two or more windows open side by side. Then I switch to a full screen window (e.g. Firefox) and when I want to switch back to the two terminal windows it is kinda difficult. The best way I found so far is a mouse-middle-click on the Firefox application bar which brings Firefox to the background so that I have the two terminals at the front again. However this only works well, when There are not too many applications piled on top :D Jan 5 '15 at 22:08
  • also @Joschua It would be possible to have a key combination to bring to front application window groups; press once -> all firefox windows show up, pres again -> all terminal windows show up etc. could be made really smooth. interesting. working on it. will take a little work though. Jan 5 '15 at 22:22
  • @JacobVlijm Sounds like the right direction.. :) What seems most important to me, is that a key combination plus clicking on the icon brings all windows of that application (for example, many terminals as peq mentioned) to the front, preferably spread out, so that they are non-overlapping.. (Maybe, something like this could become part of Unity?!)
    – Joschua
    Jan 6 '15 at 0:06
21
+50

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.

14
  • I agree, and thanks again for all your effort! :) || There's a weird thing that I didn't notice before. Sometimes after using the Option 2 script, when an application window is focused (that is not maximized) and I click on another window that is visible "below", the application below below doesn't get the focus.
    – Joschua
    Jan 8 '15 at 22:47
  • @Joschua the OP of this question: askubuntu.com/questions/575830/… attended me of a bug that was introduced at the latest "feature" update. True/False were mixed up, causing the script to crash when no applications have more then one window. If you use option2, please update to the latest version. Jan 21 '15 at 19:22
  • Option 1 not working for me on ubuntu xenial. something@something:~/bin$ ./raise_app Gtk-Message: GtkDialog mapped without a transient parent. This is discouraged. I was trying to open terminal windows. Nothing happened.
    – xtrinch
    Sep 17 '16 at 12:33
  • @Nirri what application name did you use? The message is quite normal if a zenity window runs without a Gtk parent. "Discouraged" is not an error. Sep 17 '16 at 12:39
  • First chars of terminal. It works - kind of - it raises a window of any app - but only one of them, not all of them as expected @user72216
    – xtrinch
    Sep 17 '16 at 12:42
1

There is Super+W shortcut which will show expo of all the currently open windows, though that will include other applications. This comes by default, and doesn't require any changes, so perhaps it's a simplest option available.

Among other things, you could position windows on right and left halves of the screen with Ctrl+Super+Left/Right buttons, and switch between them with Alt+~ (tilde, the one next to number one key).

1
  • That doesn't bring all the windows of an application to the top though. You can see them, but you can't use them without having to click a lot.
    – Joschua
    Jan 8 '15 at 11:49
1

If you press Alt+Tab to cycle through applications, and you get to one with multiple windows, just keep holding the alt key down and after about 1 full second the icon will be replaced with a view of all of the windows for that application.

That may or may not be what you're looking for, but it works for me and is a ton simpler, so I figured I'd share the option!

1
  • 1
    You can also press the down arrow key to have the application windows show up straight away.
    – Kris
    Aug 2 '16 at 16:12
1

I took @JacobVlijm's raise_apps.py script and made some enhancements to it, including making it more robust.

Specifically, I had found that after a day or two, @JacobVlijm's script would stop working, and I'd have to manually restart the script, to get it working again. In retrospect, my best guess is that the numerous calls to xrandr eventually cause issues.

Anyways, I adapted his code, increased the polling frequency from 5 seconds to every 1 second, as it doesn't use much CPU anyways, and made it more robust. I can typically have it running for days/weeks without issues.

One caveat is that I only call xrandr once during startup, to get screen resolution dimensions. So if you change your screen resolution (e.g. from 1920x1080 to some other resolution), you'll probably want to manually restart raise-apps.py so that it will pick up the new resolution. Personally, I never change my screen resolution, so this is a non-issue for me. Additionally, I have strong reason to believe that too many calls to xrandr were what was causing @JacobVlijm's version of the script to stop working after a day or two, so I would strongly recommend not simply putting the numerous calls to xrandr back in..

BTW, you need to place the raise.png image in the /usr/local/icons/ directory. Or if you want to put raise.png in a different directory make the appropriate change to the script, so that the script can find the image file.

Hopefully, Ubuntu will integrate this type of 'raise all windows' functionality into their system sooner than later as it's very useful:

#!/usr/bin/python2
#
# Note to self:
# You need to add raise.png to /usr/local/icons/ directory.
#
# This script was taken from: https://askubuntu.com/questions/446521/how-to-show-raise-all-windows-of-an-application, 
# (@JacobVlijm's answer), and then improved to fix some
# issues, that were causing it to stop working after a day or two.
#
#
from __future__ import print_function

from sys import stderr, exit
import signal
import gi

gi.require_version('Gtk', '3.0')
gi.require_version('AppIndicator3', '0.1')
from gi.repository import Gtk, AppIndicator3, GObject, GLib

import logging
import logging.handlers

import time
import os
import subprocess
import getpass

logger = logging.getLogger('MyLogger')
logger.setLevel(logging.DEBUG)

log_handler = logging.handlers.SysLogHandler(address='/dev/log')

logger.addHandler(log_handler)


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

class Indicator():
    def __init__(self):
        self.app = 'raise-apps'
        iconpath = '/usr/local/icons/raise.png'
        self.indicator = AppIndicator3.Indicator.new(
            self.app, iconpath,
            AppIndicator3.IndicatorCategory.OTHER)
        self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)

        self.prev_menu_item_names = []
        self.menu_items = []

        res_output = get("xrandr").split()
        if (len(res_output) == 0):
            logger.error("raise-apps.py: invocation of xrandr failed! Unable to continue..")
            exit(-1)

        idf = res_output.index("current")
        res = (int(res_output[idf+1]), int(res_output[idf+3].replace(",", "")))
        (self.screen_width, self.screen_height) = res
        logger.info("raise-apps.py: screen resolution is %s x %s" % (self.screen_width, self.screen_height))

        self.indicator.set_menu(self.create_menu())

        GLib.timeout_add_seconds(1.0, self.check_recent)

    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)

        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_items[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_items:

            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):
        # 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]) < self.screen_width and 0 < int(w[4]) < self.screen_height]
        # 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):
        # print("in check_recent()", file=stderr)
        self.menu_items = self.get_apps()
        for app in self.menu_items:
            app[0] = "gnome-terminal" if "gnome-terminal" in app[0] else app[0]
        # check if menu items have changed:
        has_changed = len(self.menu_items) != len(self.prev_menu_item_names)
        if (not has_changed):
            for i in range(len(self.menu_items)):
                if self.prev_menu_item_names[i] != self.menu_items[i][0]:
                    has_changed = True
                    break

        if has_changed:
            GObject.idle_add(
                self.set_new,
                priority=GObject.PRIORITY_DEFAULT)

            self.prev_menu_item_names = []
            for item in self.menu_items:
                self.prev_menu_item_names.append(item[0])

        GLib.timeout_add_seconds(1.0, self.check_recent)


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


    def recreate_menu(self, *args):
        logger.info("in recreate_menu()")
        self.prev_menu_item_names = []
        self.menu_items = []

        self.menu_items = self.get_apps()
        for app in self.menu_items:
            app[0] = "gnome-terminal" if "gnome-terminal" in app[0] else app[0]

        GObject.idle_add(
            self.set_new,
            priority=GObject.PRIORITY_DEFAULT)

        self.prev_menu_item_names = []
        for item in self.menu_items:
            self.prev_menu_item_names.append(item[0])


def get(command):
    # enable to get a feel for what this app is doing..
    # print("get", command, file=stderr)
    try:
        return subprocess.check_output(command).decode("utf-8")

    except subprocess.CalledProcessError as e:
        logger.error("raise-apps.py error: cmd=%s, error=%s" % (command, e))
        return ""

    except OSError as e:
        logger.error("raise-apps.py error: cmd=%s, error=%s" % (command, e))
        return ""

def execute(command):
    # enable to get a feel for what this app is doing..
    # print("exec", command, file=stderr)
    try:
        subprocess.call(command)

    except subprocess.CalledProcessError as e:
        logger.error("raise-apps.py error: cmd=%s, error=%s" % (command, e))
        return ""
    except OSError as e:
        logger.error("raise-apps.py error: cmd=%s, error=%s" % (command, e))
    return ""


logger.info("(raise-apps.py is starting up..)")
Indicator()
signal.signal(signal.SIGINT, signal.SIG_DFL)
Gtk.main()

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.