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 includingenabled,trayWindow,menu, andmarginsettings insrc/main/utils/Constants.ts(lines 58-65).MainRunner: Merges user-defined options with defaults, instantiates the mainBrowserWindow, and conditionally invokescreateTray()whentrayOptions.enabledis true (lines 24-27 and 77-84 ofsrc/main/MainRunner.ts).createTray(window, options): Core factory function insrc/main/tray.ts(lines 7-66) that constructs theTrayinstance, attaches event handlers, and optionally builds the context menu.- Window management helpers: Utility functions
hideWindow,showWindow,toggleWindow, andalignWindow(lines 69-121 ofsrc/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.icondefined in the constants module. - Debounced handlers: Click events use
debounce()to prevent rapid-fire toggling. - Mode exclusivity: When
trayWindowis true, the code explicitly setstrayOptions.menu = falseto 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:
- Obtain the primary display's work area dimensions.
- Read the tray icon's bounds via
tray.getBounds(). - Determine if the icon resides in the lower screen half to adjust vertical placement.
- Apply optional
marginoffsets fromTrayOptions. - 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
TrayOptionsinsrc/main/utils/Constants.tsare merged with user settings inMainRunnerbefore passing tocreateTray(). - 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 usingMathclamps. - Event architecture: All click handlers use debouncing and delegate to
toggleWindow(), which coordinates betweenshowWindow(triggering re-alignment) andhideWindow.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →