Honestly, I was never a friend of Apples OS, but working for some time now with Unity I found myself in a bit of trouble.

The launcher panel is quite nice, but I don't want to have it in my way, and more worse, there is no task bar anymore. So, having a lot of applications running in one session, I need to find a simple way to get them back, very fast.

I mean, I minimize them when I don't need them, but I'm a bad guy with tabbing around the window list.

So, I was thinking then: I miss my applications, I want them in a menu in my panel...why don't we have such a thing like Apples Finder app? You know that's the menu entry in the main menu and the menu entries are showing all running apps. When clicking on one of the menu entries, it will bring the application to the foreground.

"But we do have that with the launcher in Unity" you will say, but I don't like to rely on icons. Icons are good, but sometimes I really like a textual representation.

So, I sat down now, for 15 minutes or so, and played around with python dbus and the python appindicator library and hacked something together.

It's just a proof of concept, and far from usable...but I think you get the idea.

The AppFinder would consist of two services:


  1. AppFinder-Service
    This is a dbus session service or dbus system service running in the backround and listening to dbus calls like "AddApp" or "AddAppDocument"
  2. AppFinderIndicator Service
    This service daemon runs in your desktop session (unity, plasma whatever) and does all the UI work.
So, having both running, and playing around with dbus-send:

$ dbus-send --session --print-reply --dest="com.sourcecode.AppFinder" /com/sourcecode/AppFinder com.sourcecode.AppFinder.AddApp string:"gnome-terminal" boolean:false

This call will add an menu entry for an application which is not document oriented. Gnome-Terminal in this example, doesn't have "real" documents (or you could see the open terminal tabs as documents, but I don't).
Sending this dbus call to the AppFinder Service will result in another dbus call (inside AppFinder Service) to the AppFinderIndicator service, which then results in something like this:

Now, let's imagine you open OpenOffice or LibreOffice or KWord, and those apps are mostly document oriented, which means, one app can have several documents opened.
So when one of the app starts, it could send this dbus call:

$ dbus-send --session --print-reply --dest="com.sourcecode.AppFinder" /com/sourcecode/AppFinder com.sourcecode.AppFinder.AddApp string:"OpenOffice Writer" boolean:true

Now it works just the like call as above, but indicates the AppFinder service and the AppFinderIndicator service, that we will have documents attached to this app.

It'll look like this:

Imagine further, OpenOffice opens up, and the first task it does is to create a new writer document. During this task it could send out a dbus call like this:

$ dbus-send --session --print-reply --dest="com.sourcecode.AppFinder" /com/sourcecode/AppFinder com.sourcecode.AppFinder.AddAppDocument string:"OpenOffice Writer" string:"New Document"

This will result UI wise in this:


Now, eventually when you click on one of the app entries, it will unminimize the app window and brings it to the foreground.

Anyways, I'm not an UI expert, and it looks like that I have a different workflow regarding my desktop as others, but I find this idea nifty.

If someone wants to take this idea to a production ready app, please do so.

And for others interested in the underlaying python code, here we go:
AppFinder-Service:

#!/usr/bin/python

import gobject
from dbus.mainloop.glib import DBusGMainLoop
import dbus
import dbus.service


class AppFinder(dbus.service.Object):
def init(self):
self.sessionbus=dbus.SessionBus()
busName=dbus.service.BusName("com.sourcecode.AppFinder",bus=self.session
bus)

dbus.service.Object.init(self,busName,'/com/sourcecode/AppFinder')
@dbus.service.method(dbusinterface='com.sourcecode.AppFinder', insignature="sb",outsignature="s")
def AddApp(self,app
name,hasdocuments=False):
proxy=self.session
bus.getobject("com.sourcecode.AppFinderIndicator","/com/sourcecode/AppFinderIndicator")
proxy.AddAppMenu(app
name,hasdocuments)
return app
name
@dbus.service.method(dbusinterface='com.sourcecode.AppFinder',insignature="ss",outsignature="ss")
def AddAppDocument(self,app
name,documentname):
proxy=self.session
bus.getobject("com.sourcecode.AppFinderIndicator","/com/sourcecode/AppFinderIndicator")
proxy.AddAppDocumentMenu(app
name,documentname)
return (app
name,documentname)

if name=="main":
DBusGMainLoop(set
as_default=True)
appfinder=AppFinder()
loop=gobject.MainLoop()
loop.run()


AppFinderIndicator-Service:

#!/usr/bin/python

import gobject
import gtk
import appindicator
from dbus.mainloop.glib import DBusGMainLoop
import dbus
import dbus.service

class AppFinderClient(dbus.service.Object):
def init(self,menu):
self.menu=menu
self.menuitems={}
busName=dbus.service.BusName("com.sourcecode.AppFinderIndicator",bus=dbus.SessionBus())
dbus.service.Object.init(self,busName,'/com/sourcecode/AppFinderIndicator')
@dbus.service.method(dbus
interface="com.sourcecode.AppFinderIndicator",insignature="sb",outsignature="")
def AddAppMenu(self,appname,hasdocuments):
if not self.menuitems.haskey(appname):
if not has
documents:
self.menuitems[appname]={}
self.menuitems[appname]["apptitle"]=gtk.MenuItem(appname)
self.menu.append(self.menuitems[appname]["apptitle"])
self.menu
items[appname]["apptitle"].show()
else:
self.menuitems[appname]={}
self.menuitems[appname]["apptitle"]=gtk.MenuItem(appname)
self.menuitems[appname]["apptitle"].show()
self.menu
items[appname]["documentmenu"]=gtk.Menu()
self.menuitems[appname]["documentitems"]={}
self.menu.append(self.menu
items[appname]["apptitle"])
self.menuitems[appname]["apptitle"].setsubmenu(self.menuitems[appname]["documentmenu"])
@dbus.service.method(dbus
interface="com.sourcecode.AppFinderIndicator",insignature="ss",outsignature="")
def AddAppDocumentMenu(self,appname,documentname):
if self.menuitems.haskey(appname):
if self.menu
items[appname].haskey("documentmenu"):
if not self.menu
items[appname]["documentitems"].haskey(documentname):
self.menuitems[appname]["documentitems"][documentname]=gtk.MenuItem(documentname)
self.menu
items[appname]["documentitems"][documentname].show()
self.menu
items[appname]["documentmenu"].append(self.menuitems[appname]["documentitems"][documentname])





if name=="main":
DBusGMainLoop(setasdefault=True)
ind=appindicator.Indicator("appfinder-client", "indicator-messages", appindicator.CATEGORYAPPLICATIONSTATUS)
ind.setstatus(appindicator.STATUSACTIVE)
ind.setattentionicon("indicator-messages-new")

menu=gtk.Menu()
ind.set_menu(menu)
appfinderclient=AppFinderClient(menu)
gtk.main()



Honestly, this is all a 20 minutes hack, and I know you can do it better, but just a mockup idea, not bad.

Oh and btw..;)