Application scripting: AmigaOS & KDE
Malaga has naturally fired supertankers worth of discussion about what we want to do for KDE 4. I want to add some more fuel, this time about application scripting and bindings. :-)
I thought I would explain how AmigaOS handled macros, scripting and IPC back in the day. I have yet to come across a desktop platform that offers better, more powerful or easier to use application scripting and inter-application scripting than what AmigaOS had in early 90s. Then I'll have a look at what we have now in KDE 3.
For people who already know about ARexx, or just have short attention spans, you people can skip ahead towards the end of this article.
Application scripting in AmigaOS 2.0
In version 2.0 AmigaOS (then AmigaDOS) gained a very powerful scripting ability based around an implementation of the Rexx scripting language called ARexx (=Amiga+Rexx). It was aimed at power users who wanted to automate and combine the functionality of their GUI applications. More background info from wikipedia http://en.wikipedia.org/wiki/REXX and http://en.wikipedia.org/wiki/AREXX.
One of the main goals of ARexx was scripting running applications using interprocess communication. ARexx has the concept of a "command environment", which is where commands where sent to. The command environment is typically another running application. Conside this example ARexx script:
ADDRESS 'IMAGEFX.1'
Screen2Front
When the Rexx interpretter runs this small script it sees ADDRESS. ADDRESS is a Rexx keyword, and in this case it specifies that the program "IMAGEFX.1" should be the target for commands. "IMAGEFX.1" is the name of the ARexx port (read: IPC port) for the currently running instance of the image processing program ImageFX. The next line in the script is not a Rexx keyword or statement, and here is the trick. When Rexx encounters a line that it doesn't understand it sends it to the command environment, in this case ImageFX. ImageFX then gets its chance to execute the line if it can. "Screen2Front" is a simple command to move the ImageFX screen (read: window) to the front of all other windows. ImageFX then sends a return code back to Rexx saying whether the line was successfully executed or not.
The ADDRESS keyword is your way of "dialing up" another program that you want to talk to. It is naturally possible to switch between different running applications and control them together in concert. It is also possible to execute AmigaDOS commands such as "delete" and "list", basically the same way that a unix style shell works.
This way it is very simple to write ARexx scripts and mix them with commands aimed at another application.
Adding Rexx support to an application
For a program to expose its functionality to Rexx, it needs to provide something called an ARexx port. An ARexx port is simply an IPC message port for receiving and handling Rexx commands. Messages containing Rexx commands are sent to an application's ARexx port where the application can then handle it. The event loop of an application is typically based around waiting for messages (read: events) to appear on message ports. Every GUI application also has a message port for receiving GUI related events.
The next thing an application needs to do is define and document a set of commands for Rexx scripts to use. Here is the (short) list of commands for a small audio utility:
http://www.audiolabs.it/docs/refman/arexx.html
The commands were aimed at power users and defined in terms of the GUI. Most commands corresponded to menu items and actions that the user could do using the application's GUI. The command set was never in terms of how the application worked "under the covers", and was not seen as a way of just executing C functions remotely.
The most important part about adding an ARexx port to an application is that it is dead easy for the programmer and everyone did it. And shortly after AmigaOS 2.0 appeared everyone demanded that your application had an ARexx port. Applications that lacked ARexx ports were not taken seriously.
Macros
The scenario above describes an ARexx script being run directly from the shell, but ARexx was also extensively used for adding macro support inside applications. Applications could easily start Rexx scripts by sending a message with the filename of the macro/script to a daemon call Rexxmast which listened on its own message port. Rexxmast would then start the Rexx interpreter running on the script and also set the command environment to point to the application that requested the script. The script could then directly access and control the calling application without having to mess with correctly using the ADDRESS command.
It is not uncommon for applications to add macros directly to their menus. macros. This naturally permitted a great deal of customisation.
Back to KDE 3
ARexx ports are more or less equivalent to DCOP interfaces in KDE. The main difference between is that Rexx would throw commands, basically strings, at an application's ARexx port and let the application try to do something with it. While DCOP interfaces expose an explicit C++ style API consisting of methods with signatures and data types and so forth.
#!/usr/bin/python
# GPL. (C) 2005 Simon Edwards.
import sys
import dcopext
from kdecore import *
#
aboutdata = KAboutData("dcop_test","dcop_test","1.0", "A test", KAboutData.License_GPL, "Copyright (C) 2005 Simon Edwards")
KCmdLineArgs.init(sys.argv,aboutdata)
app = Kapplication()
#
dcop = app.dcopClient()
kwin = dcopext.DCOPApp("kicker", dcop) # Similar to ARexx ADDRESS
ok,size = kwin.Panel.panelSize()
print "Panel size is:",size
This example above shows how to query kicker for its size. Despite all of the boilerplate code to set it up, the code that actually talks to kicker is very small and straight forward. The example here uses Python instead of Rexx, but what is happening here is more or less the same. (I'm sure this could also be done using Ruby or Javascript. Both languages are dynamic enough to support this kind of thing. Java would probably be messy though). An application is being controlled by script running out-of-process.
It is worth pointing out that the example above doesn't use any bindings or IDL built for talking to kicker. This is all runtime.
The primary difference between KDE now and AmigaOS is that KDE applications don't provide a complete and _documented_ API for use by power users, power scripters. DCOP interfaces are mostly viewed as a way of exposing internal C++ methods to the world. This is not the same as providing an API aimed at scripting.
Conclusion
What I'm trying to point out with all this is that KDE already has the technical capability to offer this level of kick-ass scripting and inter-application scripting. What appears to be lacking is a clear vision about what we want achieve and what kind of platform we want to provide in KDE.
My second point is that technically it doesn't have to be all that difficult. We can already go a long way with what we have in KDE right now.
Looking forward to KDE 4. :)