Skip to content

Workspace internals

Sergii Stoian edited this page Aug 11, 2020 · 6 revisions

Warning: this document is in "work in progress" state and will be changed as implementation of Workspace evolves.

Workspace and WindowMaker integration

The Task

This is the most challenging and important part of the whole project.

WindowMaker is X11 window manager, written in C that has:

  • NeXTSTEP-like look and mostly a feel;
  • it's own memory management implementation;
  • it's own implementation of reading/writing defaults in OpenStep defaults format;
  • WINGs - library resebmles some OpenStep controls for dialogs, panels;
  • WPrefs.app - application WINGs-based application for manipulating user defaults;
  • Dock - NeXTSTEP-like dock - number of windows representing application icons;
  • icons and app icons placed at bottom of the screen - Icon Yard;
  • virtual workspaces support - Workspaces;
  • number of third-party Dock applications written with X11 libraries (fast and lightweight). It is lightweight and fast.

The Problem

WindowMaker looks like superior window manager for NEXTSPACE project. WindowMaker (and every X11 window manager) main task is to listen to X11 events in it's own loop and react to them (pass events to applications windows, place windows on screen, draw icons and app icons, move windows with mouse and so on). Loop implementatin (event.c, EventLoop()) blocks execution of WindowMaker until some X11 events occured. This is fine for window manager but this is not good for Objective-C application implemented with GNUstep libraries like Workspace. GNUstep applications have it's own run loop and any method or function which blocks run loop freezes the whole application.

So the challenge was to use WindowMaker with modifications as little as possible (this unlocks the possibility to send code to upstream project) and establish communication with GNUstep-based Workspace application.

The Solution

There were a number of tries to reach that goal with different results. I don't remember all of them, but I can list a couple types:

  • Workspace and WindowMaker are two seperate applications which communicate through NSNotifications (NSNotificationCenter or NSDistributedNotificationCenter). WindowMaker must be converted to true Obj-C application and still needs to solve problem with 2 different type of run loops because sending NSNotification inside WindowMaker blocks reacting to X11 events.
  • Workspace and WindowMaker is one application with X11 event processing inside main NSApp run loop. X11 events can be received with help of NSFileHandle. The result of this experiment behaved unpredictably. Probably it's due to overlapping of X11 and AppKit events inside AppKit run loop.

Finally, I came to a conclusion: the best approach is to keep two worlds inside sandboxes with ability to touch each other in pointwise manner. This is where multithreading might be usefull. All I need is to come up with the idea how to establish communication between two different types of applications.

So for some time I've been searching for solution that gives me ability to create one application that has two separate parts (threads) which can call other side's code without negative side effects.

Further experiments lead me to the best working method - use libdispatch library to create 2 separate threads: one for WindowMaker and it's event loop and second for Workspace application. I've already tried to use NSThreads for it with unreliable success. With libdispatch it was quite simple, reliable and looks elegant. Moreover, I can call code from one thread in context (data structures) of other one. Great.

The next task is to establish communication between these two threads. It is necessary in a number of cases as adding information about started X11 application to "Procesess" panel and removing it on application quit. This is done by implementing C functions inside Workspace+WindowMaker.m which are called inside Workspace thread (queue in terms of libdispatch) to avoid crashes of Workspace due to lack of thread safety in GNUstep AppKit library (gnustep-gui).

Such approach allow to gain some positive effects:

  • WindowMaker remains pure C application, no need to convert it to Obj-C;

  • WindowMaker can be modified with minimum changes to call some code inside Workspace;

  • Workspace has acces to internal WindowMaker structures if executed inside WindowMaker libdispatch queue;

  • Workspace has execution priority over WindowMaker thread; Workspace thread created with

    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

    so WindowMaker can't block Workspace's run loop.

    As an example, here is the code executed inside WindowMaker (application.c) on X11 application start:

    dispatch_sync(workspace_q, ^{XWApplicationDidAddWindow(wapp, wwin);});

    Here,

    workspace_q is libdispatch global queue

    XWApplicationDidAddWindow is a function implemented at Workspace side

Of course, there might be some other more ellegant methods. But this method just works without any side effects like blocking run loops. Feel free to create issue/pull request if you have better idea.

Startup sequence

In this section I describe briefly what happens when Workspace starts touching the points where Workspace and WindowMaker intersects.

But first, I describe some noteworthy changes I've made to the WindowMaker sources. Vanilla WindowMaker starts creating 2 processes: first is a process that monitors real WindowMaker process. Both of them implemented in WindowMaker's main.c file. Branching is started in main() into MonitorLoop() and real_main(). real_main() ends with call to EventLoop() - that is, main WindowMaker's run loop.

That said, to allow compilation of WindowMaker into Workspace sources and further communication between them, I've made some more modifications:

  • main() was completely disabled at compile time with #ifndef NEXTSPACE;
  • essential code from main() was copied into WWMInitializeWindowMaker();
  • real_main() was modified to disable redundant code (it's not quite neccessary, but simplifies some things a lot) plus commented out call to EventLoop().

So rough description of Workspace startup sequence is the following:

  1. Initialize WindowMaker in wmaker_q queue synchronously (call `WWMInitializeWindowMaker()1 in Workspace_main.m). More things are done at this step like: applying displays layout, signal handling overrides, some hacks.
  2. Call WindowMaker's EventLoop() in wmaker_q asynchronously. At this point WindowMaker is running, no Dock visible yet.
  3. Start GNUstep part of Workspace on workspace_q queue - NSApplicationMain() (Workspace_main.m).
  4. Prepare Workspace to run in -applicationDidFinishLaunching: (Controller.m).
  5. Show Dock: WWMDockShowIcons() (Controller.m).
  6. Autolaunch docked applications WWMDockAutoLaunch() (Workspace+Windowmaker.m).

You may notice some weird logic: some WindowMaker's functions (showing dock and autolaunching docked applications) are run inside workspace_q instead of wmaker_q. Actually this is intentional: after step #2 WindowMaker runs its code only as reaction to the X11 events. In other words, wmaker_q queue is blocked until some X11 events received. So the only active thread (queue) to run these functions in is Workspace queue - workspace_q. It works. The opposite is bad, runing Workspace's code inside wmaker_q, most probably, will lead to crash.

ICCCM method of Workspace and WindowMaker communication

GNUstep is designed and developed to work with different window managers. GNUstep applications need to communicate to window manager they are running with. The best way to this is use method described in section 4 of ICCCM document.

I've discovered how GNUstep and WindowMaker interconnects to earch other. I'll continue to check if this method would be applicable to integration task described in previous sections. Below is my notes on findings and unanswered questions.

Based on information from back/Documentation/Back/Standards.txt, GNUstep Back and WindowMaker source code I concluded that WindowMaker and application can communicate by several methods:

  1. Application set to owned window some attributes. Attributes can be set with some property name known to both: window manager and application. Window manager then checks for property value with XGetWindowProperty() Xlib function call. Property _GNUSTEP_WM_ATTR created to assign to window extra attributes not covered by ICCCM.
  2. Sending messages with XSendEevent() with event type XClientMessageEvent structure. message_type atom is set to known protocol or property name and data union field is set to some values known to both application (window) and window manager. This is a true ICCCM method of client responses to window manager actions.

Let's take a look at "WindowMaker Protocols and Client Messages" section of back/Documentation/Back/Standards.txt:

  • _GNUSTEP_WM_MINIATURIZE_WINDOW - tells to window manager that window, accepts requests to Miniaturize window with client message.

  • _GNUSTEP_TITLEBAR_STATE - tells to window manager that window, accepts requests to get or set titlebar appearnce of window.

  • _GNUSTEP_WM_ATTR - property for setting various attributes about the window. One of the fields in GNUstepWMAttributes structure is extra_flags. It takes OR'ed values of window attributes (style, level, pixmaps, etc). And now something unexpected: WMFHideOtherApplications and WMFHideApplication! What? Are they protocols? Are they message types? (rememeber this is value of field in structure called GNUstepWMAttributes). What these attributes should tell to window manager? Some variants are:

    WMFHideOtherApplications

    • window can hide other aplications;
    • window can be hidden while somebody hides applications;
    • window want to send request to hide other applications to window manager.

    WMFHideApplication

    • window accept requests to hide application - why it's not protocol (property) and not called _GNUSTEP_WM_HIDE_APP like protocol property for Miniaturize action?;

I've failed to find answer for these questions and decided to:

  • create new protocol property _GNUSTEP_WM_HIDE_APP - tells to window manager that window, accepts requests to Hide application with client message.
  • create new protocol property _GNUSTEP_WM_HIDE_OTHERS - tells to clients that window manager accepts message to hide all windows of applications except caller application.

To sum up, here are some interconnection agreement statements. AppKit (GNUstep) application must support:

  • _GNUSTEP_TITLEBAR_STATE - WM can manage titlebar state app window;
  • _GNUSTEP_WM_ATTR - WM can manage some specific atrributes of AppKit app;
  • _GNUSTEP_WM_MINIATURIZE_WINDOW - protocol, WM can request app window to iconify itself;
  • _GNUSTEP_WM_HIDE_APP - protocol, WM can request app window to hide application windows.

Window manager must support:

  • _GNUSTEP_WM_HIDE_OTHERS - application can request to hide windows of other applications.
  • _GNUSTEP_FRAME_OFFSETS - application can get information to calculate window geometry.

Automatic launch of applications at startup

Original WindowMaker manages automatic launch of docked applications which have AutoLaunch=Yes set in WMState file. Workspace also uses this attribute from file with two significant changes:

  1. Value of this attribute can be changed from Workspace's Dock Preferences panel.
  2. WindowMaker is not managing launching applications at startup - Workspace now do it.

The second part of this change is support of GNUstep applications to properly handle autolaunch. The patched GNUstep Back and GNUstep GUI make applications start smooth - without focus flickering from Workspace menu to autolaunched application's menu - but displaying appicon and its contents (for example, Preferences appicon with time and date view).

Applications have no distinct method to know if it autolaunched since NSApplication removes -NXAutoLaunch YES argument to omit flickering. But if application reaches it's -applicationDidFinishLaunching: methods and still is not in active state it's sure sign - application was autolaunched. So this code should work (and used in NEXTSPACE applications):

- (void)applicationDidFinishLaunching:(NSNotification *)aNotif
{
  if ([NSApp isActive] == NO) {
    // This is a place for code if application was autolaunched
  }
}

Events handling

Applications registration

To manage running applications through Tools->Processes panel Workspace must register applications as they appears on screen. This is handled for two types of applications:

  1. GNUstep applications: Workspace receives NSNotification while GNUstep applications start.
  2. X11 (Xlib, GTK, Qt, etc) applications: WindowMaker registers applications while its window(s) appear on screen. During registration process WindowMaker informs Workspace about it by calling Workspace's functions: XWApplicationDidCreate() and XWApplicationDidAddWindow(). Workspace then gets needed information from the suuplied WApplication structure and adds to the list of running applications. When application loses its windows WindowMaker informs Workspace with XWApplicationDidCloseWindow() call. When application loses its last window WindowMaker unregister application and call XWApplicationDidDestroy() Workspace's function.

Display resolution and layout changes

Display and resolution changes may be spawned from Preferences application as well as by xrandr tool. WindowMaker listens to XRandR events and call Workspace's XWUpdateScreenInfo() function. This function updates screen and display layout and adjusts placement of Dock and Icon Yard.

Look and feel

Despite the fact the WindowMaker looks very NeXTish I've made some changes to provide more native look and feel. These changes are:

  • the same look of miniwindow for GNUstep and X11 applications (tile image, title font);
  • mouse click on titlebar buttons changes buttons to highlighted state instead of pushed in;
  • click on inactive window titlebar now the same for GNUstep and X11 applications: application window raises and titlebar changed to focused state (for GNUstep applications application menu appears);

Configuration files

Some hardcoded paths in WindowMaker will add prefix path defined in GNUSTEP_USER_ROOT environment variable. Original WindowMaker's prefix is 'GNUstep' and path to main configuration file is ~/GNUstep/Defaults/WindowMaker.

In NEXTSPACE GNUSTEP_USER_ROOT is set to "~/Library" so configuration directories hierarchy looks like this:

  • ~/Library/Preferences - holds .plist files of GNUstep applications which use NSUserDefaults class;
  • ~/Library/Preferences/.NextSpace - holds configuration files for NEXTSPACE system applications (Login, Workspace);
  • ~/Library/Preferences/.WindowMaker - WindowMaker configuration files (WMGLOBAL, WMState, WMWindowAttributes...).

Finally if you want to start original WindowMaker, configuration files will not be overlapped. Original WindowMaker files are in ~/GNUstep, NEXTSPACE files - in ~/Library.

Logout and Power Off

Not implemented yet.