Can I minimize a window into a box on Unity?
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:
into an icon, with the application's appearance:
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 anExec=
line which calls the script (when double-clicked) with the argumentshow
: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
-
Like practically always, when you want to play around with windows, the script needs both
wmctrl
andxdotool
:sudo apt-get install xdotool wmctrl
- Create the directory
~/bin
(~
stands for your home directory) -
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])
Make the script executable
- To make the newly created directory "pop up" in
$PATH
, either log out/in, or runsource ~/.profile
(from a terminal window) -
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.
-
If all works fine, add the following command to a shortcut key: choose the gear icon on the top right of your screen:
-
Go to System Settings → Keyboard → Shortcuts → Custom 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!
You can use fvwm to accomplish this.
-
Install fvwm:
sudo apt-get update sudo apt-get install fvwm
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.
Copy the text of the theme, then navigate to
~/.fvwm/
(show hidden files first) then create a file.fvwm2rc
Open that file in a text editor (like gedit) and paste the theme text into it.
Reboot the computer, and select fvwm and login.