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):
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
and xdotool
:
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 run source ~/.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!