Skip to content

AWT Peer Implementation

opencode-agent[bot] edited this page Oct 4, 2026 · 6 revisions

AWT Peer Implementation

JNode implements AWT peers using Swing components as a lightweight simulation of a desktop operating system, bypassing the need for a host OS windowing system.

Overview

Unlike traditional JVMs that delegate UI rendering to the host OS (X11, Windows), JNode implements the entire peer layer from scratch. The peer implementation uses Swing components as an internal rendering backend, creating a simulated desktop environment that draws directly to the framebuffer.

Key Components

Class Location Purpose
JNodeToolkit gui/src/awt/org/jnode/awt/JNodeToolkit.java Base toolkit, bridges AWT to framebuffer
SwingToolkit gui/src/awt/org/jnode/awt/swingpeers/SwingToolkit.java Creates all peer instances using Swing
JNodeGraphicsEnvironment gui/src/awt/org/jnode/awt/JNodeGraphicsEnvironment.java Manages screen devices and fonts
AWTPlugin gui/src/awt/org/jnode/awt/AWTPlugin.java Plugin lifecycle management
DesktopFrame gui/src/awt/org/jnode/awt/swingpeers/DesktopFrame.java Root window (the desktop)
DesktopFramePeer gui/src/awt/org/jnode/awt/swingpeers/DesktopFramePeer.java Peer for desktop
JNodeEventQueue gui/src/awt/org/jnode/awt/JNodeEventQueue.java Event dispatch
KeyboardHandler gui/src/awt/org/jnode/awt/KeyboardHandler.java Raw keyboard to AWT events
MouseHandler gui/src/awt/org/jnode/awt/MouseHandler.java Mouse input handling

Architecture

Initialization Flow

Boot → AWTPlugin.startPlugin()
     → VMImageUtils.setAPI(new VMImageAPIImpl())
     → Application calls Toolkit.getDefaultToolkit()
     → JNodeToolkit.incRefCount() initializes graphics mode
     → JNodeToolkit.onInitialize() (abstract, implemented by SwingToolkit)
     → SwingToolkit creates DesktopFrame

Toolkit Hierarchy

java.awt.Toolkit
    └── gnu.java.awt.ClasspathToolkit (GNU Classpath)
            └── org.jnode.awt.JNodeToolkit (abstract base)
                    └── org.jnode.awt.swingpeers.SwingToolkit

Peer Creation Pattern

When an AWT component is created, the toolkit creates a peer:

// Example from SwingToolkit.java
protected ButtonPeer createButton(Button target) {
    return new SwingButtonPeer(this, target);
}

Each peer wraps a Swing component that handles rendering. The peer maps between AWT and Swing coordinate systems and event models.

How It Works

Framebuffer Integration

JNode connects to video hardware through the Video-Driver-Architecture:

  1. JNodeToolkit queries DeviceManager for FrameBufferAPI implementations
  2. Requests ownership of the framebuffer via api.requestOwnership(this)
  3. Opens a Surface for drawing: api.open(config.getConfig())
  4. All painting operations write directly to video memory

Desktop Frame

The DesktopFrame is the root of the component hierarchy:

// From SwingToolkit.onInitialize()
desktopFrame = new DesktopFrame(getScreenSize());
desktopFrame.show();
desktopFrame.getContentPane().repaint();

All other AWT Frames are rendered as JInternalFrames inside this desktop's JDesktopPane.

Event Handling

Input Hardware → KeyboardHandler/MouseHandler
              → JNodeEventQueue
              → EventQueue.dispatchEvent()
              → AWT Component event listeners

The KeyboardHandler and MouseHandler convert hardware interrupts into AWT events:

  • Keyboard scancodes → KeyEvent (KEY_PRESSED, KEY_RELEASED, KEY_TYPED)
  • Mouse movement/clicks → MouseEvent (MOUSE_MOVED, MOUSE_DRAGGED, MOUSE_CLICKED, etc.)

Peer Interfaces

Swing peers implement standard AWT peer interfaces:

Peer Interface Implementation
ButtonPeer SwingButtonPeer
LabelPeer SwingLabelPeer
FramePeer SwingFramePeer / DesktopFramePeer
WindowPeer SwingWindowPeer
ContainerPeer SwingContainerPeer
LightweightPeer SwingLightweightPeer
... ...

Each implementation wraps the corresponding JComponent and delegates painting, event handling, and layout.

Font Rendering

JNode includes a TrueType font subsystem:

  • TTFFontPeer — wraps a TrueType font file
  • FontManager — service managing available fonts
  • TextRenderer — renders glyphs to graphics context

Font rendering flows through the graphics pipeline to the framebuffer.

SwingFramePeer and DesktopFrame

The SwingFramePeer (lines 54-167 in SwingFramePeer.java) implements FramePeer and ISwingContainerPeer. It wraps a SwingFrame (an internal JInternalFrame subclass) and manages:

  • Window state (iconified, maximized, normal)
  • Menu bar integration via setMenuBar()
  • Addition to the desktop via addToDesktop()

The DesktopFrame (lines 45-165 in DesktopFrame.java) extends JFrame and implements JNodeAwtContext. It serves as:

  • The root window containing all other AWT windows
  • A JDesktopPane that manages internal frames
  • Background color persistence via Preferences
  • Size adjustment through adjustDesktopSize()

Threading Model

The peer implementation uses SwingToolkit.invokeNowOrLater() to ensure thread-safe operations:

// From SwingFramePeer.java
public void setMenuBar(final MenuBar mb) {
    SwingToolkit.invokeNowOrLater(new Runnable() {
        public void run() { /* ... */ }
    });
}

This ensures AWT operations execute on the Event Dispatch Thread (EDT) while handling cases where peers may be created from different AppContext instances.

Window Activation and Taskbar Stacking

JNode's SwingBaseWindow is a JInternalFrame (SwingBaseWindow.java:52-53), so isSelected() is frequently true for windows that are not actually on top. This is a live source of bugs — an attempted fix for the resulting "clicking a background window does not raise it" / "clicking the taskbar button of a background-but-selected window iconifies it" cluster was written, documented here, and then reverted (see the timeline below).

Timeline

Commit Date Effect
2a712bf8 2026-09-26 Explicit-raise rewrite of activateWindow, DesktopManagerImpl.dragFrame/activateFrame overrides, WindowBar.isTopFrame, FrameWrapper toFront() + repaint chain
50c7b00be 2026-10-01 Revert of the above (3 files, −57/+3). Only the rate-limited activation-failure logging survived

What survives today is the logging half, which was orthogonal to the stacking behaviour and is not part of the revert.

SwingToolkit.activateWindow (current state)

gui/src/awt/org/jnode/awt/swingpeers/SwingToolkit.java:585-615:

if (f.isShowing() && !f.isSelected()) {
    try {
        f.setSelected(true);
        activationWarningCount = 0;
    } catch (PropertyVetoException e) { ... } catch (RuntimeException e) { ... }
}

The !f.isSelected() guard is back, and there is no toFront(), no getDesktopPane().setSelectedFrame(f), and no repaint chain — so activating an already-"selected" background window is still a no-op. That is the pre-2a712bf8 behaviour, and it is deliberate: the revert restored it.

Rate-Limited Activation Failure Logging (survived the revert)

A JInternalFrame's setSelected(true) can throw a plain RuntimeException when the frame's UI/peer state is inconsistent. That is logged and swallowed, at most a few times:

  • private static final int MAX_ACTIVATION_WARNINGS = 3; and private int activationWarningCount; (:118-120)
  • PropertyVetoException → log.debug("Internal frame refused selection", e) (:598-601)
  • RuntimeException → up to 2 log.warn("Unable to activate internal frame", e), then one log.warn("Further internal-frame activation failures suppressed", e), then silence (only log.debug afterwards) (:602-611)
  • A successful activation resets the counter to 0 (:597)

The counter is incremented past the threshold (to MAX + 1) so the "further failures suppressed" message is emitted exactly once.

Desktop.DesktopManagerImpl (current state)

gui/src/desktop/org/jnode/desktop/classic/Desktop.java:267-293. The activateFrame override does not exist any more, and dragFrame is back to a bare bounds update:

@Override
public void dragFrame(JComponent frame, int newX, int newY) {   // :270-273
    setBoundsForFrame(frame, newX, newY, frame.getWidth(), frame.getHeight());
}

DefaultDesktopManager.dragFrame is a no-op, so this override exists at all purely to make dragging move the window; there is no toFront() and no repaint. Fields: taskBar (:69), desktopPane (:71), desktopFrame (:74, a JNodeAwtContext). Neighbouring pre-existing overrides: deiconifyFrame :278-284, iconifyFrame :289-295.

WindowBar (current state)

gui/src/desktop/org/jnode/desktop/classic/WindowBar.java. isTopFrame does not exist any more, and FrameWrapper has no toFront() and no repaint chain:

if (frame.isIcon()) {
    frame.setIcon(false);
    frame.setSelected(true);
} else if (frame.isSelected()) {          // was: frame.isSelected() && isTopFrame(frame)
    frame.setSelected(false);
    frame.setIcon(true);
    selectNextFrame(frame.getDesktopPane());
} else {
    frame.setSelected(true);
}

Consequence, unchanged from before the fix attempt: because isSelected() can be true for several windows at once, clicking the taskbar button of a background-but-"selected" window iconifies it instead of raising it. This is the open half of the bug the revert exposed.

selectNextFrame(JDesktopPane) (:111-131) is unchanged and still picks the topmost remaining frame — the one with the lowest non-negative getComponentZOrder among visible non-iconified frames (:114-124) — but it only calls sel.setSelected(true), with no toFront() or repaint.

See DesktopFrame for the wider window-manager picture and JNode-Graphics2D for the paint-clipping half of the same bug family.

Gotchas

  • No native windowing: AWT windows are simulated internally; they don't correspond to OS-level windows
  • Reference counting: GUI mode uses incRefCount()/decRefCount() to track active users; when count reaches zero, graphics are released
  • AppContext isolation: Peers must be created in the same AppContext as the desktop frame to ensure event dispatch works correctly
  • Look and feel: SwingToolkit sets MetalLookAndFeel with OceanTheme as default
  • Desktop ownership: The first Frame created becomes the desktop; subsequent frames become internal frames
  • isSelected() is not "frontmost" — because a JNode window is a JInternalFrame, several windows report selected at once. There is no z-order-aware "am I on top" helper in the tree any more: WindowBar.isTopFrame was added on 2026-09-26 and reverted on 2026-10-01. Use getComponentZOrder comparisons directly (lowest index wins).
  • The 2026-09-26 stacking fix was reverted — toFront(), DesktopManagerImpl.activateFrame and the isTopFrame iconify gate are all gone. If you are chasing "taskbar click iconifies the wrong window" or "background window does not come forward", you are looking at pre-existing behaviour, not a regression.
  • Swing z-order is inverted: lower getComponentZOrder index is on top. otherZ < frameZ means "in front of me".
  • Occluded frames still repaint in full — there is no damage tracking, so window paint clipping (JNode-Graphics2D) is what prevents lower windows from painting over higher ones.
  • GUI tests must be headless-safe — constructing a real java.awt.Frame (or SwingBaseWindow, whose constructor chain is super(title, true, true, true, true)) requires a display that a forked test JVM does not have. Use a plain JInternalFrame.

Related Pages

Clone this wiki locally