Multiple Desktops for Pharo
Speed coding a desktop manager
Pharo is a fascinating lively object oriented environment that allows you to turn programming ideas very quickly into real usable programs and tools. While it took years for Microsoft to finally provide multiple desktop support in recent Windows version 10 it only took a few hours for the author of this article to implement such a feature in the Pharo environment.
This article explains how Pharo was extended with support for multiple desktops — from basic experiments up to the final code. It also gives an overview on how to use this new “DesktopManager” goodie.
The image concept
In the tradition of Smalltalk systems Pharo is based on an image concept — a single file including the whole object world that could be run easily on different devices and operating systems using a virtual machine. The native virtual machine adapts the Pharo environment to the current runtime (Windows, Linux, Mac, Android or others) and allows you to easily develop on one system and run on another very easily.
You can develop on a Windows, Linux or Mac laptop and later run the Pharo program 1:1 on a completely different device like the Raspberry Pi. It is even possible to debug a program failure on one machine and continue debugging on another.
This allows also for fascinating possibilities like developing web application locally and just pushing the image with the running program later onto a server to quickly publish into a cloud. This comes with increased productivity but also allows for easy maintenance — if you want to find and kill a bug in an application you can even save, download and restore a copy of the whole environment while the failure happens.
So the whole Pharo object world could be freezed and saved at any time into one or more image files — allowing you to quickly make snapshots, copy them around and continue work at any point in time. All this is an old Smalltalk tradition that now gets new attention also in modern virtualization technologies.
So Pharo is a like an easy transferable operating system moveable between systems and devices.
Single window and a UI with own windows
Also if you start the Pharo environment from an image file you will notice that the system runs in a single native window:
Within this single native window (that is hosted by the underlying OS) our Pharo environment provides its own UI implementation — including its own windowing and graphic system. This way the implementation can be kept independent from the current runtime. So Pharo looks and behaves like an own desktop operating system itself.
There is even a setting in the default settings browser to run the system full screen and a WorldMenu > Windows > Toggle full screen mode item.
If you like you can trigger this “manually” by evaluating the following code in a Pharo code workspace:
Display fullscreen: true
To explain: the Smalltalk programming language itself consists of objects and messages and with its pure object oriented nature it should be easy to understand this simple piece of code that is very close to english language.
When evaluated this snippet sends the keyword message #fullscreen: with the argument object true to a globally known object called “Display”.
If you set Pharo to full screen using this expression you can forget about the underlying Windows, Linux or iOS completely and make Pharo your own custom operating system.
The Pharo VM itself runs in a single native thread of a native process of the underlying operating system — this is sometimes called a “green threads” architecture. But similar to an operating system Pharo includes multiple internal “processes” running concurrently within the Smalltalk image / running VM instance. Like nearly all Smalltalk systems Pharo includes its own process scheduler that is referenced by the global variable “Processor” allowing you to inspect or script also the processing system easily:
Processor activeProcess inspect
If you like you can watch, track/trace, kill or debug the running Smalltalk processes from a built in Pharo tool called Process browser:
You can play around by running a built in simple webserver
ZnServer startDefaultOn: 8080.
and pointing your browser to http://localhost:8080/ — you will see that some new worker processes/worker threads will be created.
If you like to know more just read about concurrent programming in Pharo.
With an own process implementation Pharo implements an object oriented multi-tasking operating system and while developing you stay independent from native process or thread implementations as much as possible.
“An operating system is a collection of things that don’t fit into a language. There shouldn’t be one.’’
As the previous examples showed Pharo has very much in common with an operating system. The difference is that it is more a lively kernel and scriptable object system that one can easily persist and transfer and that is easily extendable using the Smalltalk language.
But if Pharo is some kind of modernized operating system then something is missing: Pharo has its own windows system with themable user interface but Pharo does (at the time of the writing) by default not support the concept of multiple desktops.
What would it require to implement multiple desktop support in Pharo ?
Tinkering before coding:
One has to know that Pharo is still based on a UI system called Morphic. Morphic itself is based on some nice ideas — but it also had some design flaws and years ago when Pharo forked as its own open source project from the Squeak Smalltalk system the morphic code was in a horrible state.
Fortunately the Pharo community was able to clean the dark sides as much as possible to make it less painful and also a replacement for Morphic called “Block” and “Brick” is already in the pipe.
Before any line of code could be written the author played around in the Pharo system. The Pharo playground is an excellent tool for that and one can use it to inspect another global variable called “World” by running:
This global variable “World” points to an object representing the current world/screen that is displayed in front of the user. The inspector tells us that the world is an instance of WorldMorph. In an object oriented system like Smalltalk we can ask an object for its class and inspect that as well:
World class inspect
If we want to have a code browser showing us the methods this class supports we can do by simply sending the message #browse to an object.
Side note: usually one opens the tools like browsers using menu or shortcuts. But using the #browse method is also very handy if you do not yet know about the class of an object you work on.
Reading a little bit the methods on the class and instance side of WorldMorph there were two methods that got my special attention: one was an instance method called #install to install an existing world object and one was a class side method called #installNewWorld. So it is already possible to have new worlds and install them. Yay — lets move on!
In Morphic any UI related object is a morph and can have submorphs. Lets inspect the submorphs of our current world:
World submorphs inspect
Nice: I got a list of several morphs including the Pharo windows that are currently open. There is even a TaskBarMorph instance as a submorph in our world representing the task bar at the bottom. Using the inspector it was easy to dive deeper into the world structure and using the class browser understand more about their implementation.
Let’s get back to our idea of multiple desktops/multiple worlds. Another check revealed that in a fresh started Pharo image there is only one instance of WorldMorph:
WorldMorph allInstances size
That is the instance of WorldMorph the global variable “World” is pointing to. Next experiment was:
WorldMorph allInstances size
revealing that it was possible to create a second instance or WorldMorph. By using:
WorldMorph allInstances first install
WorldMorph allInstances last install
I was able to switch between the two worlds already. Nice!
Mmmmh … let’s summarize what we found out:
- Initially there is only one “world(morph) object” in the system representing the desktop and holding the visible windows
- it is an instance of class WorldMorph
- There is the possibility to create new worlds and switch to
them using #install
Unfortunately when switching the worlds the windows did not fully redraw leaving ugly rectangles on the screen. We need to redraw and restore the display. Wait, there already is a global variable “Display” that we used when switching fullscreen. And Nautilus the class browser quickly revealed that there is a restore method:
That means we can restore the display after “world switching” easily:
WorldMorph allInstances last install.
Still cumbersome — but it works. Having this knowledge we can now move on in finding a design for a goodie to create and manage multiple worlds/desktops in Pharo.
Desktops and a desktop manager
Initial experiments finished —time to write some code. The author started to create a new code package called “DesktopManager-Core” in the system browser and a category/tag “Base” for the basic classes we need:
The first class implemented in our design is a new class called “Desktop” for representing the different desktops:
Let’s foresee an attribute (instance variable) “world” to hold the world the desktop represents:
In programming languages like C++, C# or Java a class usually would be defined in a source code. A class definition file (Desktop.cpp/ Desktop.cs/ Desktop.java) in these languages would be a dumb text definition file fed into a compiler to verify and translate.
In an interactive and lively system like Pharo a class could be created like any other object by sending instance creation methods. The reason is simple: in a pure OO environment anything is an object, so even a class is an object. Remember: there are only objects and messages.
So in Smalltalk a new (sub)class is typically created by sending a message to the superclass. We want to inherit from the base class Object so we can quickly fill out the template provided in the Nautilus system browser:
and create a new Desktop class. Do not forget to add a class comment! Using the Refactoring menu of the Nautilus browser we can quickly generate accessor methods (getter/setter) for our world attribute. Additionally we define some convinience methods to be able to write code like this:
Desktop world: World
I will not go into all the details of the implementation. The full source code can be found on SmalltalkHub:
To manage the multiple desktops we need some kind of desktop manager holding an ordered collection of the multiple desktop instances. As there is always at least one desktop for the initial world in this relationship collection the cardinality of this relationship is not 0…n (“zero to n desktops”) but 1…n (“one to n desktops”). Let’s sketch this with an UML class diagram:
followed by creating the class in the Pharo environment:
When providing multiple desktops we want the user to be able to navigate by moving to the next or to the previous desktop. For this we need to know the index of the current active desktop in our collection of desktops. We honor this with an attribute to keep this info:
To initialize new instances of our new manager class we implement an instance side #initialize method:
desktops := OrderedCollection with: (Desktop world: World).
currentDesktopIndex := 1
So initially we have a collection of one desktop referencing the current World. As this one is also the active desktop our index needs to be 1.
Another design decision is that there should only be one desktop manager in the system. So we implement a class side #soleInstance method returning the singleton.
Now one could write
to access it. Additionally we add another convinience unary method called #manager — this time in class Desktop to be able to write:
This is by far more easy to remember than the previous expression.
Now code was written for handling:
- Creation of new desktops (see #createNewDesktop method)
- Adding a new desktop to the collection (see #addNewDesktop method)
- Switching to a desktop (see #switchToDesktop: method)
- Navigation (see #switchToNextDesktop and #switchToPreviousDesktop methods)
This new code allowed to navigate already using Smalltalk expressions:
Desktop manager addNewDesktop; switchToNextDesktop
or check the desktop collection:
Desktop manager desktops size
Even working with the desktops is possible:
Desktop manager desktops first isActive
Please check the full source code for the details.
Goodies for the goodie
Adding to the world menu
The global world menu of Pharo not only allows you to access the various tools available - it also allows to deal with “Windows”. This lead to the idea to also have a custom menu showing the various active/inactive desktop(s) and allowing you to add new.
Fortunately this could be easily provided by implementing methods including the <worldMenu> pragma.
Adding keyboard shortcuts
The entry in the world menu is nice but one has to use the mouse. Usually one also wants to navigate using the keyboard. To keep it easy to remember for the user the following key combinations were invented:
CTRL + D followed by CTRL + A — “Desktop Add”
CTRL + D followed by CTRL + N — “Desktop Next”
CTRL + D followed by CTRL + P — “Desktop Previous”
A custom class DesktopKeymapCategory was implemented quickly with a few methods using a predefined system pragma <shortcut> definition.
Adding a spotter preview
Navigation using the keyboard helps — but often you can not see how many desktops you have. A preview would be nice as well. How to do that?
Pharo again comes to the rescue — thankfully the more recent versions (Pharo 4 onwards) now include so called “moldable tools” — that means UI related tools you can easily (re)use within your own application.
One of these tools is spotter — a user interface component used for searching and previewing of data. So with a single class called DesktopSpotterModel and primarily a single methods #spotterDesktopsFor: the author was able to reuse this tool for the purpose of navigating the desktops:
^ aStep listProcessor
candidatesLimit: Float infinity;
allCandidates: [ Desktop manager desktops ];
itemName: [:item | item name ];
itemIcon: [:item | item icon ];
actLogic: [:each | Desktop manager switchToDesktop: each ];
This method uses the <spotterOrder:> pragma and provide a list of desktops for searching/navigating in the spotter tool.
To implement a small “preview picture” on the selected desktop instance another method was necessary:
spotterPreviewDesktopIn: aComposite <spotterPreview: 15> |form newWidth newHeight| newWidth := 300. form := self world imageForm. newHeight := newWidth / form width * form height. aComposite custom: ((self world imageForm scaledToSize: newWidth @ newHeight) asAlphaImageMorph asBrick margin: 20)
This basically creates a form/ image representation of the world the desktop points to. The calculation is only done to scale the preview image keeping the original image ratio.
Additionally another keyboard shortcut was added:
CTRL + D followed by CTRL + D —”Desktop Desktop”
so you have to hold down CTRL and keep pressing D two times to open the newly implemented tool.
The result is fantastic — a view to navigate and preview the desktop collection:
Adding a custom inspector tool
Even the inspector (the tool where one inspects an object) in Pharo is moldable. By providing #gtInspectorDesktopsIn: in class DesktopManager and #gtInspectorMorphIn: in class Desktop the following custom view was very quickly realized:
To get this view just evaluate
Desktop manager inspect
in a playground or workspace.
The whole desktop manager goodie took only a few hours to implement. For sure it could be even more improved. But actually this article took more time to write than the current code and the result is fantastic: one can have multiple desktops in Pharo and can navigate them using keyboard, menu or the custom spotter. This again brings Pharo closer to be used as an independent but yet powerful object oriented operating and runtime system.