How TUUI Integrates the System Tray: Menu Creation and Window Management

TL;DR: TUUI implements system tray integration in the main process through the createTray() function in src/main/tray.ts, which configures an Electron Tray instance with optional context menus, click handlers for window visibility, and dynamic positioning relative to the tray icon bounds.

TUUI, an Electron-based application framework maintained in the ai-ql/tuui repository, provides a flexible system tray integration that supports both traditional context menu behavior and floating window mode. The implementation centers on configurable TrayOptions that control menu creation, window alignment, and click interactions. This article examines the complete architecture from configuration defaults in src/main/utils/Constants.ts through the window positioning algorithms in src/main/tray.ts.

Architecture Overview

The system tray integration relies on four coordinated components:

  • Constants.DEFAULT_TRAY_OPTIONS: Defines baseline configuration including enabled, trayWindow, menu, and margin settings in src/main/utils/Constants.ts (lines 58-65).
  • MainRunner: Merges user-defined options with defaults, instantiates the main BrowserWindow, and conditionally invokes createTray() when trayOptions.enabled is true (lines 24-27 and 77-84 of src/main/MainRunner.ts).
  • createTray(window, options): Core factory function in src/main/tray.ts (lines 7-66) that constructs the Tray instance, attaches event handlers, and optionally builds the context menu.
  • Window management helpers: Utility functions hideWindow, showWindow, toggleWindow, and alignWindow (lines 69-121 of src/main/tray.ts) handle visibility state and geometric positioning.

The initialization flow proceeds sequentially: MainRunner.createMainWindow() constructs the BrowserWindow, checks trayOptions.enabled, and delegates tray setup to createTray(mainWindow, trayOptions). When trayWindow mode is active, alignWindow() calculates the window's screen position relative to the tray icon bounds.

Core Implementation Details

Default Tray Configuration

Default behavior is centralized in src/main/utils/Constants.ts:

static DEFAULT_TRAY_OPTIONS: TrayOptions = {
  enabled: false,
  trayWindow: false,
  menu: false,
  tooltip: Constants.APP_NAME,
  margin: { x: 0, y: 0 },
  showAtStartup: false
}

These values serve as fallbacks when MainRunner merges configuration objects (lines 77-84). The trayWindow property determines whether the tray operates in floating-window mode (click toggles visibility) or traditional menu mode (right-click displays options).

Tray Initialization and Event Wiring

The createTray() function in src/main/tray.ts implements the core logic:

export function createTray(window: BrowserWindow, options) {
  trayOptions = options || Constants.DEFAULT_TRAY_OPTIONS

  // Floating window mode disables context menus
  if (trayOptions.trayWindow) {
    trayOptions.menu = false
  }

  tray = new Tray(Constants.ASSETS_PATH.icon)
  tray.setToolTip(trayOptions.tooltip)

  if (trayOptions.menu) {
    // Classic mode: right-click menu with dev tools and exit options
    tray.on('click', () => debounce(() => toggleWindow(window), 100))

    const contextMenu = Menu.buildFromTemplate([
      { label: 'Open Dev Tools', click: () => window.webContents.openDevTools() },
      { label: 'Force Reload',   click: () => window.webContents.reloadIgnoringCache() },
      {
        label: 'Clear Storage',
        click: () => {
          const sess = session.fromPartition(Constants.PARTITION_NAME)
          sess.clearStorageData({ storages: ['cookies','cachestorage','localstorage','indexdb','serviceworkers'] })
          window.webContents.reloadIgnoringCache()
        }
      },
      { label: 'Exit', click: () => app.quit() }
    ])
    tray.setContextMenu(contextMenu)
  } else {
    // Tray-window mode: both click and right-click toggle visibility
    tray.on('right-click', () => debounce(() => toggleWindow(window)))
    tray.on('click',        () => debounce(() => toggleWindow(window)))
  }

  alignWindow(window)
  return tray
}

Key implementation details include:

  • Icon sourcing: Loaded from Constants.ASSETS_PATH.icon defined in the constants module.
  • Debounced handlers: Click events use debounce() to prevent rapid-fire toggling.
  • Mode exclusivity: When trayWindow is true, the code explicitly sets trayOptions.menu = false to prevent menu conflicts.

Window Visibility Management

Helper functions manage the floating window's visibility state (lines 69-92):

export function hideWindow(window) { 
  window.hide() 
}

export function toggleWindow(window) {
  if (window.isVisible()) hideWindow(window)
  else showWindow(window)
}

export function showWindow(window) {
  window.show()
  alignWindow(window)  // Recalculate position on every show
}

The showWindow function ensures the window re-aligns to the current tray position each time it becomes visible, accommodating changes in screen layout or tray location.

Dynamic Positioning Algorithm

Window positioning is handled by alignWindow() and calculateWindowPosition() (lines 94-121):

export function alignWindow(window) {
  if (!trayOptions.trayWindow) return
  const b = window.getBounds()
  const position = calculateWindowPosition(b)
  window.setBounds({ width: b.width, height: b.height, x: position.x, y: position.y })
}

The calculateWindowPosition function utilizes Electron's screen API to:

  1. Obtain the primary display's work area dimensions.
  2. Read the tray icon's bounds via tray.getBounds().
  3. Determine if the icon resides in the lower screen half to adjust vertical placement.
  4. Apply optional margin offsets from TrayOptions.
  5. Clamp coordinates to Math.max(0, Math.min(...)) constraints to keep the window within visible screen bounds.

Customizing TUUI's System Tray Behavior

To override default behavior, create a custom initialization module:

import { BrowserWindow } from 'electron'
import { createTray } from './tray'
import Constants from './utils/Constants'

const customOptions = {
  enabled: true,
  trayWindow: true,          // Enable floating window mode
  menu: false,
  tooltip: 'Custom TUUI Instance',
  margin: { x: 10, y: 12 }, // Offset from tray icon
  showAtStartup: true       // Display immediately on launch
}

export function initCustomTray(mainWin: BrowserWindow) {
  const tray = createTray(mainWin, customOptions)
  // Runtime modifications are supported:
  tray.setToolTip('Application Active')
}

When showAtStartup: true, MainRunner (lines 30-33) automatically invokes showWindow(mainWindow) after creation, displaying the aligned window immediately.

Summary

  • Configuration hierarchy: Default TrayOptions in src/main/utils/Constants.ts are merged with user settings in MainRunner before passing to createTray().
  • Dual mode support: The implementation supports context menu mode (menu: true) with right-click templates or tray-window mode (trayWindow: true) with click-to-toggle visibility.
  • Positioning safety: The alignWindow() function calculates geometry relative to tray icon bounds and constrains results to visible screen areas using Math clamps.
  • Event architecture: All click handlers use debouncing and delegate to toggleWindow(), which coordinates between showWindow (triggering re-alignment) and hideWindow.

Frequently Asked Questions

How do I switch from a context menu to a floating window in TUUI?

Set trayWindow: true in your TrayOptions configuration. According to the source code in src/main/tray.ts, this automatically disables the context menu (trayOptions.menu = false) and registers both left and right click events to call toggleWindow(), creating a floating window that appears near the tray icon.

Where does TUUI store the default system tray settings?

Default settings are defined as Constants.DEFAULT_TRAY_OPTIONS in src/main/utils/Constants.ts (lines 58-65). These include enabled: false, trayWindow: false, menu: false, and default margin values of { x: 0, y: 0 }.

How does TUUI prevent the tray window from appearing off-screen?

The calculateWindowPosition function (called within alignWindow in src/main/tray.ts) uses Electron's screen API to determine the primary display bounds. It compares the tray icon's position against screen dimensions, applies configurable margin offsets, and clamps the final X/Y coordinates using Math.max(0, Math.min(...)) to ensure the window remains within visible boundaries.

Can I modify the context menu items in TUUI's system tray?

Yes. When menu: true is set, the createTray() function builds the menu using Menu.buildFromTemplate() with hardcoded labels like "Open Dev Tools" and "Clear Storage". To customize these, you would need to fork or modify src/main/tray.ts and alter the array passed to Menu.buildFromTemplate() before tray.setContextMenu(contextMenu) is called.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →