17

On Irix's 4Dwm, there was an ability to minimise windows into a box (contrary to the task bar used by modern window managers). I have seen this also on an old HPUX.

See the "console" square in the linked image:

enter image description here

Is it possible to accomplish on Ubuntu, either with a plugin or maybe some window manager other than Unity?

  • Weird, but interesting question :) I could think of something like this. Would the icon matter, or could it be a generic one? How does it behave? like an icon on the desktop or a minimal "window". – Jacob Vlijm Dec 18 '15 at 17:22
  • @JacobVlijm The icon is not generic. Each application has it's own icon as the icons of minimised windows in Unity) – Artium Dec 18 '15 at 18:01
  • Could be done within Unity as well, but the window would be iconized on the desktop (with the corresponding application's icon and the window's name). Would you fancy a shot? (would be an interesting, but challenging job, better ask before I start :) ) – Jacob Vlijm Dec 18 '15 at 18:53
  • 1
    Yes this can be helpful to me. I have to work with a lot of open windows and this way of organising them is better in my opinion. I know nothings about Unity, so I can help only with the testing. – Artium Dec 18 '15 at 19:11
18
+50

To my own surprise, it works quite nicely, As long as you do not have too many other things on your desktop.

I worked with it for a while, and it seems a weird, but strangely enough nice alternative to frequent workspace switches. Refreshing for its simplicity.

In practice

The solution is actually pretty much what you describe:

  • Pressing a key combination will "box" the window on your desktop, from a window:

    enter image description here

    into an icon, with the application's appearance:

    enter image description here

  • Double click the icon, and the window will re- appear and the icon will vanish.

How it works

The short story (explanation):

  • When pressing the shortcut key, the script is called with the argument box:

    windowbox box
    
  • The script then:

    • reads the window id of the frontmost window
    • checks if it is a "normal" window (you wouldn't want to unmap your desktop for example)
    • Looks up the process name of the application, owning the window.
    • Looks up the corresponding icon in the corresponding application's .desktop file in /usr/share/applications
    • creates a uniquely named .desktop file, with an Exec= line which calls the script (when double-clicked) with the argument show:

      windowbox show
      

The .desktop file will add a number of additional arguments arguments, such as the window id, the (file-) name of the .desktop file.

Subsequently:

  • The .desktop file is then made executable, to make it a double- clickable object.

  • When the .desktop file is double clicked, the window is (re-) mapped, the .desktop file is removed from your desktop.

How to set up

  1. Like practically always, when you want to play around with windows, the script needs both wmctrl and xdotool:

    sudo apt-get install xdotool wmctrl
    
  2. Create the directory ~/bin (~ stands for your home directory)
  3. Copy the script below into an empty file, save it as windowbox (no extension) in ~/bin.

    #!/usr/bin/env python3
    import subprocess
    import sys
    import os
    
    # --- On Unity, there is a (y-wise) deviation in window placement
    # set to zero for other window managers
    deviation = 28
    # ---
    
    args = sys.argv[1:]
    
    get = lambda cmd: subprocess.check_output(cmd).decode("utf-8").strip()
    
    def find_dtop():
        # get the localized path to the Desktop folder
        home = os.environ["HOME"]
        dr_file = home+"/.config/user-dirs.dirs"
        return [home+"/"+ l.split("/")[-1].strip() \
                for l in open(dr_file).readlines() \
                if l.startswith("XDG_DESKTOP_DIR=")][0].replace('"', "")
    
    def check_windowtype(w_id):
        # check the type of window; only unmap "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 and window x/y position
        w_list = get(["wmctrl", "-lpG"]).splitlines()
        pid = [l for l in w_list if w_id in l][0].split()
        proc = get(["ps", "-p", pid[2], "-o", "comm="])
        xy = (" ").join(pid[3:5])
        return (proc, xy)
    
    def read_f(f, string, proc):
        # search for a possible match in a targeted .desktop file
        try:
            with open(f) as read:
                for l in read:
                    if all([l.startswith(string), proc in l]):
                        in_f = True
                        break
                    else:
                        in_f = False
        except:
            in_f = False
        return in_f
    
    def get_icon(proc, w_name):
        # search appropriate icon in /usr/share/applications
        exceptions = [item for item in [
            ["soffice", "libreoffice-main"],
            ["gnome-terminal", "utilities-terminal"],
            ["nautilus", "folder"],
            ] if item[0] in proc]
        if exceptions:
            if exceptions == [["soffice", "libreoffice-main"]]:
                loffice = [
                    ["Calc", "libreoffice-calc"],
                    ["Writer", "libreoffice-writer"],
                    ["Base", "libreoffice-base"],
                    ["Draw", "libreoffice-draw"],
                    ["Impress", "libreoffice-impress"],
                    ]
                match = [m[1] for m in loffice if m[0] in w_name]
                if match:
                    return match[0]
                else:
                    return exceptions[0][1]
            else:      
                return exceptions[0][1]
        else:
            default = "/usr/share/applications"
            dtfiles = [default+"/"+f for f in os.listdir(default)]
            for f in dtfiles:
                if read_f(f, "Exec=", proc) == True:   
                    for l in open(f).readlines():
                        if l.startswith("Icon="):
                            icon = l.replace("Icon=", "").strip()
                            print(f)
                            break
                    break
            return icon
    
    def create_name():
        # create unique (file-) name for boxed window
        n = 1
        while True:
            name = dtop+"/"+"boxed_"+str(n)+".desktop"
            if os.path.exists(name):
                n += 1
            else:
                break
        return name
    
    def convert_wid(w_id):
        # convert window- id, xdotool format, into wmctrl format
        w_id = hex(int(w_id))
        return w_id[:2]+(10-len(w_id))*"0"+w_id[2:]
    
    def create_icon(w_id, w_name, icon, pos):
        # create the launcher, representing the boxed window
        boxedwindow = create_name()
        f_content =[
                "[Desktop Entry]",
                "Name=[WINDOW] "+w_name,
                "Exec=windowbox show "+w_id+" '"+boxedwindow+"' "+pos,
                "Icon="+icon,
                "Type=Application",
                ]
        if icon == "generic":
            f_content.pop(3)
        with open(boxedwindow, "wt") as boxed:
            for l in f_content:
                boxed.write(l+"\n")
        command = "chmod +x "+"'"+boxedwindow+"'"
        subprocess.call(["/bin/bash", "-c", command])
    
    if args[0] == "box":
        dtop = find_dtop()
        w_id = convert_wid(get(["xdotool", "getactivewindow"]))
        w_name = get(["xdotool", "getwindowname", w_id])
        if check_windowtype(w_id) == True:
            procdata = get_process(w_id)
            procname = procdata[0]
            icon = get_icon(procname, w_name); icon = icon if icon != None else "generic"
            create_icon(w_id, w_name, icon, procdata[1])
            subprocess.call(["xdotool", "windowunmap", w_id])
    
    elif args[0] == "show":
        w_id = args[1]
        subprocess.call(["xdotool", "windowmap", w_id])    
        subprocess.call(["xdotool", "windowmove", "--sync", w_id, args[3], str(int(args[4])-deviation)])
        os.remove(args[2])
    
  4. Make the script executable

  5. To make the newly created directory "pop up" in $PATH, either log out/in, or run source ~/.profile (from a terminal window)
  6. Test- run the script from a terminal window by the command:

    windowbox box
    

    The window should disappear, the "boxed" window should appear on your desktop.

  7. If all works fine, add the following command to a shortcut key: choose the gear icon on the top right of your screen:

    Gear icon

  8. Go to System SettingsKeyboardShortcutsCustom Shortcuts. Click the + and add the command:

    windowbox box
    

That should do it.

Important note

The script uses xdotool's windowunmap to make the window invisible. The created "box" (icon) on your desktop is the only "gate" to the hidden window. In other words: don't remove the desktop file(s) manually. The window will be lost for good if you do.

Work to do [edit 20-12: done]

The script still could use some refinement:

  • The window geometry is not restored by definition. Can be fixed very well, but I thought I'd show you the first result.
  • In most cases, the boxed window has its correct icon. The function get_process(w_id) could use some improvement however. If the process is not found as a command in /usr/share/applications, the file has a generic icon.

Giving the boxed-window icons a different size than the other icons

The script names the created .desktop files always boxed_1.desktop, boxed_2.desktop etc, depending on the "available" name at the moment of creation (filenames, not the displayed name).

You can resize the files (in general), by right- click > icon size. The good news is that if you remove the file and recreate it, the size is remembered. Even if you create the file again after a restart. That means that if you ever resized the boxed windows (e.g.) 1-5, they will always have the same size when you (the script) create them again!

enter image description here

  • 2
    I can't resist myself without leaving a comment, very nice answer from you :) – Ravan Dec 19 '15 at 12:46
  • Nice! Few comments: 1. I replaced the line dtop = "/home/jacob/Bureaublad" with a path to my desktop (dtop = "/home/" + user + "/Desktop") 2. Restoring by double clicking did not work. I suspect that source ~/.profile is not enough will log-in/out promptly to test test this. 3. In unity it is possible to resize icons manually (right click -> resize icon), is it possible to add some parameter in f_content to set icon's size? – Artium Dec 19 '15 at 12:46
  • 4
    When I started to read the first paragraph of this answer, I just knew there would be an orange icon at the bottom! ;-) :P – Fabby Dec 19 '15 at 18:12
  • 1
    Hi @Artium I updated the script, it has a few important improvements now, including better icon- finding and geometry- restoring, Have fun! – Jacob Vlijm Dec 20 '15 at 12:39
  • 1
    @Artium I know, but try the one in the script, it is the usual folder icon, similar to your image. probably just a link to the same icon. – Jacob Vlijm Dec 20 '15 at 21:15
7

You can use fvwm to accomplish this.

  1. Install fvwm:

    sudo apt-get update
    sudo apt-get install fvwm
    
  2. Find a them that uses the iconify function - there are several here: http://www.jmcunx.com/fvwm_theme.html Several look like the screen-shot you show.

  3. Copy the text of the theme, then navigate to ~/.fvwm/ (show hidden files first) then create a file .fvwm2rc

  4. Open that file in a text editor (like gedit) and paste the theme text into it.

  5. Reboot the computer, and select fvwm and login.

enter image description here

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.