18

I would like to mimic the use of Alfred on Mac OS X, where if you try to open an app after searching for it, it will only open a new window if the program is not already running, otherwise it will set the focus on the currently running instance of that application. Is there anyway to change the default behavior of the launcher to check for this before opening a new window?

6
  • Also @pidge To do this would not be difficult, but it would also effect the right-click "open with" behavior. I guess that is an unacceptable side effect. Apr 1, 2015 at 22:10
  • 1
    I think you should be able to create a script which can check whether a specific process is already running and decide on either launching a new process or giving the focus to the existing window. Unfortunately, I am not very good in scripting yet... But @JacobVlijm is known as a guy with scripts for every purpose ;) You would have to replace all original launchers by the respective script though. Not sure if you want to/can do that - I would not...
    – Byte Commander
    Apr 2, 2015 at 22:10
  • 2
    If you go with the script route you could use this script as a starting point. I wrote it for LXDE/Openbox initially, but it should work in Unity, too. More information on the script and its usage here. Apr 2, 2015 at 22:48
  • 1
    @ByteCommander exactly that was what I had in mind. You could even script- replace the commands in the .desktop files. If you replace the command in the .desktop file however, the right-click open with option is broken. Apr 3, 2015 at 5:38
  • 1
    For which Desktop Environment?
    – j0h
    Apr 6, 2015 at 13:57

3 Answers 3

7

Update April 7: A different version added and found Albert, see update and Bonus bellow !!!

Concerning dash functionality: You have asked " Is there anyway to change the default behavior of the launcher to check for this before opening a new window". Basic answer is, no, as a regular user you have no way of adding that behavior to dash. However, if there would be a unity scope developer who'd be willing to implement that, you might approach them or develop one yourself if you have resolve and willing to learn. My coding skills are very modest, hence I use shell scripting and the available graphical front-end for the scripts as a workaround.

Related information

Original post:

I have written a script that uses zenity dialogue and wmctrl to achieve what you asked for. Notice that this is a graphical script, meaning it will only work with windows, in GUI, and won't work if you try to launch something in tty. Besides, from what I understand Alfred does exactly the same thing. You can create a desktop shortcut to it or launcher shortcut to it, as described here and here.

The script:

#!/bin/bash
# Author: Serg Kolo
# Description: A launcher script that checks whether
#       or not a window of a particular program already exists
#       If a window of such program is open, bring it to focus
#       Otherwise - launch a new window
#       Written for https://askubuntu.com/q/440142/295286
# Date: April 6 , 2015
#


MYPROG=$( zenity --entry --title='MY LAUNCHER' --text='Type the name of application to run' )
sleep 0.5
wmctrl -lx | awk '{print $3}' | grep -i "$MYPROG"

if [ $? -eq 0 ]; then
    sleep 1         
    wmctrl -xa $MYPROG
   #as an alternative try the line bellow
   #wmctrl -a $MYPROG
    exit 1
else 
    $MYPROG &
    exit 0
fi

Side notes: in the previous version , script used echo $?, to test if previous expressions exited successfully. As per muru's suggestion ( from the edit ), I changed the code to somewhat more compact version, so i suggest you take a look at the previous version and the current.

Also , previously wmctrl -a $MYPROG didn't work with testing google-chrome or chromium-browser; for some stupid reason some programs have WM_CLASS property of the window capitalized , while the program as listed by dpkg --get-selections is lowercase (just read man wmctrl and run wmctrl -lx, you'll know). Adding that -ax should take care of this. The script brings up the already open chromium window as it should

Another thing - wmctlr is somewhat weird in that it needs a delay sometimes (had experience with it in another script), so i had to add sleep 1 line. Previously it would be kind of on and off with firefox, but now works swimmingly.

The script in action

In the animation bellow you can see that on the first run of the script, there is one instance of firefox open, and the script switches focus to that window; on the second test, I open new instance of google-chrome, which hasn't been open previously. (Side note: If you are currious about the desktop, by the way, that is openbox with cairo dock)

Per suggestion in the comments, embedded animation removed, posted link only. Report if it is broken please ! https://i.stack.imgur.com/puuPZ.gif

Update, April 7

I improved the script somewhat to make all the programs listed in zenity's drop-down entry box. Now the user doesn't have to memorize each program, but can just scroll through a list of them using arrow keys or just open the drop down menu. Also, this improved version raises windows not by name, but by window id, which gives much better performance. Note, the way i go through .desktop files is kind of redundant, using cut command twice, but since my script-fu isn't that good so far, this is all i can do. Suggestions for improvement are welcome!

#!/bin/bash
# Author: Serg Kolo
# Description: Second version of a launcher script that checks whether
#       or not a window of a particular program already exists
#       If a window of such program is open, bring it to focus
#       Otherwise - launch a new window
#       Written for https://askubuntu.com/q/440142/295286
# Date: April 7 , 2015
#

set -x

MYPROG=$(zenity --entry --text 'Select program from list' --entry-text $(ls /usr/share/applications/*.desktop | cut -d'/' -f5 | cut -d'.' -f1 | xargs echo))
sleep 0.5
# Do we have a window of such program ?
wmctrl -lx| awk '{print $3}'  | grep -i $MYPROG

if [ $? -eq 0 ]; then
    sleep 0.5 # if yes, find that window id, and raise it
    WINID=$(wmctrl -lx | grep -i $MYPROG | awk 'NR==1{print $1}')
    wmctrl -ia $WINID &
 #  exit 0  
else
    echo $MYPROG | grep -i libreoffice
    if [ $? -eq 0  ]
    then
        MYPROG=$(echo $MYPROG | sed 's/-/ --/g')
    fi
    $MYPROG &

#  exit 0 
fi

enter image description here

Bonus:

I have actually found Albert , which is Linux version of Alfred, but have not tried it myself. Worth checking out though. However, as Jacob noted already, it is still buggy.

There is an app called Gnome-Do, which graphically looks similar to Alfred, however it does not have the same functionality as this script.

enter image description here

Let me know if you like this script, if there's anything needs fixing, and don't forget to upvote the answer if you find it useful

18
  • By the way, notice how i type the names of the programs - exactly as listed in dpkg --get-selectons . Launching libreoffice writer by typing "writer" wont work, but you could make a symlink to it in your ~/bin, /bin or /usr/bin folder or use alias in .bashrc or .profile. Apr 6, 2015 at 20:41
  • Notice also, that you will need wmctl installed, it doesn't come by default, but pretty handy. I've also used it to make this Apr 6, 2015 at 21:44
  • Could you replace the annimation by an image, linking to the annimation? My browser keeps "loading" the page, so I cannot refresh it. (and the annimation does not run :) ) Apr 7, 2015 at 8:59
  • Thanks! The link works fine in Chrome, not in Firefox. Apr 7, 2015 at 13:03
  • @JacobVlijm Ugh, so it does. Not sure why firefox is refusing to play it. It's just imgur link to what i originally uploaded Apr 7, 2015 at 13:26
5
+100

1. Dash the Second

Below a script that can be used as an alternative to Dash, when it comes to running applications as described in your question.

It exists of a window with the same functionality as Dash; if type one or more characters of the application, the application will appear in the list. Press Enter to either start or raise the application, depending on if it is already running or not.

You can call it from a shortcut key combination, or set an icon in the launcher to use it similarly to Dash (see further below), or both.

enter image description here

The script

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

user = getpass.getuser()
get = lambda x: subprocess.check_output(["/bin/bash", "-c", x]).decode("utf-8")
skip = ["%F", "%U", "%f", "%u"]; trim = ["chrome", "chromium", "nautilus"]

def apply(command):
    if "libreoffice" in command:
        proc = [l.split()[0] for l in get("ps -u "+user).splitlines() if "soffice.bin" in l]
        module = command.split("--")[-1]
        time.sleep(0.1)
        try:
            ws = sum([[w.split()[0] for w in get("wmctrl -lp").splitlines() if process in w and module in w.lower()] for process in proc], [])[0]
            subprocess.call(["wmctrl", "-ia", ws])
        except IndexError:
            subprocess.Popen(["/bin/bash", "-c", command+"&"])
    else:
        check = command.split("/")[-1][:14]
        proc = [p.split()[0] for p in get("ps -u "+user).splitlines() if check in p]
        time.sleep(0.5)
        try:
            ws = sum([[w.split()[0] for w in get("wmctrl -lp").splitlines() if process in w] for process in proc], [])
            if command == "nautilus":
                real_window = [w for w in ws if "_NET_WM_WINDOW_TYPE_NORMAL" in get("xprop -id "+w)][0]
            else:
                real_window = ws[0]
            subprocess.call(["wmctrl", "-ia", real_window])
        except IndexError:
            subprocess.Popen(["/bin/bash", "-c", command+"&"])
# default directories of .desktop files; globally, locally, LibreOffice- specific when separately installed
globally = "/usr/share/applications"; locally = os.environ["HOME"]+"/.local/share/applications"; lo_dir = "/opt/libreoffice4.4/share/xdg"
# create list of .desktop files; local ones have preference
local_files = [it for it in os.listdir(locally) if it.endswith(".desktop")]
global_files = [it for it in os.listdir(globally) if it.endswith(".desktop")]
lo_spec = [it for it in os.listdir(lo_dir) if it.endswith(".desktop")] if os.path.exists(lo_dir) else []
for f in [f for f in local_files if f in global_files]:
    global_files.remove(f)
for f in [f for f in local_files if f in lo_spec]:
    lo_spec.remove(f)
dtfiles = [globally+"/"+f for f in global_files]+[locally+"/"+f for f in local_files]+[lo_dir+"/"+f for f in lo_spec]
# create list of application names / commands
valid = []
for f in dtfiles:
    content = open(f).read()
    if all(["NoDisplay=true" not in content,"Exec=" in content]):
        lines = content.splitlines()
        name = [l.replace("Name=", "") for l in lines if "Name=" in l][0]
        command = [l.replace("Exec=", "") for l in lines if all(["Exec=" in l, not "TryExec=" in l])][0]
        valid.append((name, command))
valid.sort(key=lambda x: x[0])
# create zenity list + window
list_items = '"'+'" "'.join([f[0] for f in valid])+'"'
proposed = 'zenity --list --text "Type one or more characters... " --column="Application List" '+\
           '--title="Dash the Second" --height 450 --width 300 '+list_items
try:
    choice = subprocess.check_output(["/bin/bash", "-c", proposed]).decode("utf-8").strip().split("|")[0]
    command = [r[1] for r in valid if r[0] == choice][0]
    # command fixes:
    for s in skip:
        command = command.replace(" "+s, "")
    for t in trim:
        if t in command:
            command = t
    apply(command)
except subprocess.CalledProcessError:
    pass

How to use

The script needs wmctrl installed:

sudo apt-get install wmctrl

Then:

  1. Paste the script above into an empty file, save it as dash_alternative.py
  2. Add it to a shortcut key combination: Choose: System Settings > "Keyboard" > "Shortcuts" > "Custom Shortcuts". Click the "+" and add the command:

    python3 /path/to/dash_alternative.py
    

Explanation

When the script is run, It lists all applications, represented in /usr/share/applications. It searches the .dektop files, creating a list of all application names (from the first "Name=" line) and the command to run the application (from the first "Exec=" line).

Subsequently, a Zenity list is created, presenting all applications in a sorted manner.

Whenever an application is selected, the script looks in the list of running processes if the application is running. If so, the corresponding window is raised. If not, a new instance is opened.

Notes

  1. To run the script on 12.04 (since the original question was tagged 12.04 simply change the shebang to #!/usr/bin/env python and run it by the command

    python /path/to/dash_alternative.py
    
  2. As far as I tested it, the script works fine. Commands and their (not-) corresponding process names (e.g. LibreOffice <> soffice.bin), different window types (nautilus has several different window types, besides "real" windows) , multiple pids per application (Chromium, Google-chrome) can cause exceptions, that I fixed in the examples above. If anyone runs into an issue, please mention it.

2. Additional: setting it as an alternative to the "real" Dash for running applications

  1. Copy and safe the script as mentioned above
  2. Save the icon below (right-click > safe as) as dash_alternative.png

    enter image description here

  3. Copy the code below into an empty file, save it in ~/.local/share/applications as dash_thesecond.desktop. Set the correct paths for /path/to/dash_alternative.py (the script) and /path/to/dash_alternative.png (the icon)

    [Desktop Entry]
    Name=Dash the Second
    Exec=python3 /path/to/dash_alternative.py
    Icon=/path/to/dash_alternative.png
    Type=Application
    Hidden=false
    
  4. Drag the .desktop file on to the launcher:

4
  • 1
    Nice to know there's a whole folder of .desktop files ! I was wondering how do the native and apt-installed applications get listed by name instead of commands. Good work there ! Apr 7, 2015 at 13:22
  • @Serg Thanks! and the same to you :). There is also a local directory for .desktop files: ~/.local/share/applications. I thought I'd limit the search to globally installed applications. Apr 7, 2015 at 13:27
  • Jacob, I don't know python, but maybe this will be useful to you in improving your script by turning it into a unity scope. My understanding is that this is the only way to alter dash functionality exactly how OP wants Apr 8, 2015 at 14:27
  • @Serg Thanks! that is an interesting post, will definitely take a good look at it! Apr 8, 2015 at 14:31
0

For the launcher (the vertical panel on the left side of the screen), this is already the default behavior, as it is the task switching interface.

For the dash (the large bit that opens when you click the Ubuntu logo), there is no way to change the behavior in this manner, without a presumably significant modification to the source code itself.

Some apps however, may already do this, as they are designed to behave that way. All apps are not, and should not necessarily be, implemented in such a manner, though.

As another feature though, if you open the window spread with Super+W, and start typing an application name, the windows for that application will be the only ones shown.

4
  • I actually have found the Linux version of what OP wanted, see my post , the bonus section. Apparently someone else had an idea to bring Alfred to Linux Apr 8, 2015 at 6:46
  • 1
    @Serg please try out Albert; Albert still has the "LibreOffice bug", the "Chromium bug" and the "Chrome bug". Even the "Files bug"... Albert simply always opens a new instance of those applications. LibreOffice simply does not work at all. Also, in the comments below your link you can find a number of issues. Apr 8, 2015 at 7:03
  • @Serg No, you found a workaround, by adding an additional piece of software running in the background, which provides a wholly different user experience, and requires retraining how to find applications. What the question asked was how to have this work in the Unity dash. You may be able to get a similar feature outside of Unity itself, but the only way to change Unity to do this, is to change the source code.
    – dobey
    Apr 8, 2015 at 13:57
  • @dobey Well, that's true; altering dash functionality is beyond our reach, so . . .gotta do with what we have a vailable, right ? Unless there's a developer who'd be willing to code a unity scope with such functionality . . . Apr 8, 2015 at 14:07

You must log in to answer this question.

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