| name | zeros-gui-development |
| description | Guide for developing GUI (Graphical User Interface) programs in ZerOS. Use when creating GUI applications, managing windows with GUIManager, handling events with EventManager, developing graphical interfaces in ZerOS. Covers singleton pattern issues, event cleanup, responsive layout with ResizeObserver, touch support for mobile devices, window lifecycle, theme compatibility, and resource management. |
ZerOS GUI Program Development Guide
Overview
GUI programs in ZerOS are graphical applications that create windows managed by GUIManager, handle user interactions through EventManager, and integrate with the system's theme system. GUI programs run in the browser-based ZerOS environment and must follow specific patterns for window lifecycle, event handling, and resource cleanup.
Key Characteristics:
- GUI programs create windows that are managed by
GUIManager
- All event handling must go through
EventManager (mandatory requirement)
- Must use CSS theme variables for consistent styling
- Support background mode (optional but recommended)
- Must properly clean up resources in
__exit__()
File Location:
- GUI programs are located in
system/service/DISK/D/application/<app-name>/<app-name>.js
- Each application should have its own directory
Program Structure
Basic Template
(function(window) {
'use strict';
const MYAPP = {
pid: null,
window: null,
windowId: null,
_kernelAPI: null,
eventHandlers: [],
__info__: function() {
return {
name: 'My Application',
type: 'GUI',
version: '1.0.0',
description: 'Description of my application',
author: 'Your Name',
copyright: '© 2025 ZerOS',
permissions: typeof PermissionManager !== 'undefined' ? [
PermissionManager.PERMISSION.GUI_WINDOW_CREATE,
PermissionManager.PERMISSION.EVENT_LISTENER,
] : [],
metadata: {
allowMultipleInstances: false,
category: 'system',
showOnDesktop: true
}
};
},
__init__: async function(pid, initArgs) {
this.pid = null;
this.window = null;
this.windowId = null;
this._kernelAPI = null;
this.eventHandlers = [];
this._resizeObserver = null;
this.pid = pid;
this._kernelAPI = (initArgs && initArgs.kernelAPI) || null;
const guiContainer = (initArgs && initArgs.guiContainer) ||
document.getElementById('gui-container');
if (!guiContainer) {
if (typeof KernelLogger !== 'undefined') {
KernelLogger.warn('MYAPP', '未找到 gui-container');
}
return;
}
this.window = document.createElement('div');
this.window.className = 'myapp-window zos-gui-window';
this.window.dataset.pid = String(pid);
this.window.style.cssText = `
width: 600px;
height: 400px;
min-width: 400px;
min-height: 300px;
display: flex;
flex-direction: column;
overflow: hidden;
`;
if (typeof GUIManager !== 'undefined') {
let icon = null;
if (typeof ApplicationAssetManager !== 'undefined') {
icon = ApplicationAssetManager.getIcon('myapp');
}
const windowInfo = GUIManager.registerWindow(pid, this.window, {
title: 'My Application',
icon: icon,
onClose: () => this._onCloseRequest()
});
if (windowInfo && windowInfo.windowId) {
this.windowId = windowInfo.windowId;
}
}
this._buildUI();
guiContainer.appendChild(this.window);
this._registerEventHandlers();
},
__exit__: async function() {
if (typeof EventManager !== 'undefined') {
for (let i = 0; i < this.eventHandlers.length; i++) {
try {
EventManager.unregisterEventHandler(this.eventHandlers[i]);
} catch (e) {}
}
}
this.eventHandlers = [];
if (typeof GUIManager !== 'undefined' && this.windowId) {
GUIManager.unregisterWindow(this.windowId);
} else if (this.pid && typeof GUIManager !== 'undefined') {
GUIManager.unregisterWindow(this.pid);
}
if (this.window && this.window.parentElement) {
this.window.parentElement.removeChild(this.window);
}
this.window = null;
this.windowId = null;
this._kernelAPI = null;
},
_buildUI: function() {
const content = document.createElement('div');
content.className = 'myapp-content';
content.textContent = 'Hello, World!';
this.window.appendChild(content);
},
_registerEventHandlers: function() {
if (typeof EventManager === 'undefined') return;
const button = this.window.querySelector('.myapp-button');
if (button) {
const handlerId = EventManager.registerElementEvent(
this.pid,
button,
'click',
(e) => {
this._handleButtonClick(e);
}
);
this.eventHandlers.push(handlerId);
}
},
_handleButtonClick: function(e) {
},
_onCloseRequest: function() {
this._exit();
},
_exit: function() {
if (this._kernelAPI && typeof this._kernelAPI.call === 'function') {
this._kernelAPI.call('Process.requestSelfTermination', []).catch(e => {
if (typeof KernelLogger !== 'undefined') {
KernelLogger.warn('MYAPP', 'requestSelfTermination 失败: ' + (e && e.message));
}
});
}
}
};
if (typeof window !== 'undefined') {
window.MYAPP = MYAPP;
} else if (typeof globalThis !== 'undefined') {
globalThis.MYAPP = MYAPP;
}
})(typeof window !== 'undefined' ? window : globalThis);
Required Methods
__info__()
Returns program metadata including name, type, version, permissions, and metadata.
Required Fields:
name (string): Program name
type (string): Must be 'GUI'
version (string): Version number
description (string): Program description
author (string): Author name
copyright (string): Copyright information
Optional Fields:
permissions (Array): Required permissions (see Permissions section)
metadata (Object):
allowMultipleInstances (boolean): Whether to allow multiple instances (default: false)
category (string): Application category ('system', 'utility', 'entertainment', etc.)
showOnDesktop (boolean): Whether to show icon on desktop
Example:
__info__: function() {
return {
name: 'My Application',
type: 'GUI',
version: '1.0.0',
description: 'Description of my application',
author: 'Your Name',
copyright: '© 2025 ZerOS',
permissions: typeof PermissionManager !== 'undefined' ? [
PermissionManager.PERMISSION.GUI_WINDOW_CREATE,
PermissionManager.PERMISSION.EVENT_LISTENER,
PermissionManager.PERMISSION.FILE_READ,
PermissionManager.PERMISSION.FILE_WRITE
] : [],
metadata: {
allowMultipleInstances: false,
category: 'utility',
showOnDesktop: true
}
};
}
__init__(pid, initArgs)
Initializes the GUI program. Must create the window element, register it with GUIManager, build the UI, and register event handlers.
Parameters:
pid (number): Process ID assigned by ProcessManager
initArgs (Object): Initialization arguments
guiContainer (HTMLElement): Container element for GUI windows
kernelAPI (Object): Process-bound kernel API (recommended for kernel calls)
Key Steps:
- Store
pid and kernelAPI
- Get
guiContainer from initArgs.guiContainer or document.getElementById('gui-container')
- Create window element with class
zos-gui-window and dataset.pid
- Register window with
GUIManager.registerWindow()
- Build UI content
- Append window to
guiContainer
- Register event handlers using
EventManager
Example:
__init__: async function(pid, initArgs) {
this.pid = pid;
this._kernelAPI = (initArgs && initArgs.kernelAPI) || null;
const guiContainer = (initArgs && initArgs.guiContainer) ||
document.getElementById('gui-container');
if (!guiContainer) {
if (typeof KernelLogger !== 'undefined') {
KernelLogger.warn('MYAPP', '未找到 gui-container');
}
return;
}
this.window = document.createElement('div');
this.window.className = 'myapp-window zos-gui-window';
this.window.dataset.pid = String(pid);
if (typeof GUIManager !== 'undefined') {
const windowInfo = GUIManager.registerWindow(pid, this.window, {
title: 'My Application',
icon: ApplicationAssetManager?.getIcon('myapp'),
onClose: () => this._onCloseRequest()
});
if (windowInfo && windowInfo.windowId) {
this.windowId = windowInfo.windowId;
}
}
this._buildUI();
guiContainer.appendChild(this.window);
this._registerEventHandlers();
}
__exit__()
Cleans up resources when the program exits. Must unregister event handlers, unregister window, and remove DOM elements.
Key Steps:
- Unregister all event handlers using
EventManager.unregisterEventHandler()
- Unregister window using
GUIManager.unregisterWindow()
- Remove window from DOM
- Clear all references (set to
null)
Example:
__exit__: async function() {
if (typeof EventManager !== 'undefined') {
for (let i = 0; i < this.eventHandlers.length; i++) {
try {
EventManager.unregisterEventHandler(this.eventHandlers[i]);
} catch (e) {}
}
}
this.eventHandlers = [];
if (typeof GUIManager !== 'undefined' && this.windowId) {
GUIManager.unregisterWindow(this.windowId);
}
if (this.window && this.window.parentElement) {
this.window.parentElement.removeChild(this.window);
}
this.window = null;
this.windowId = null;
this._kernelAPI = null;
}
Window Management
GUIManager API
GUIManager manages all GUI windows, providing unified window controls (minimize, maximize, close), drag and resize functionality, focus management, and z-index management.
Registering a Window
const windowInfo = GUIManager.registerWindow(pid, windowElement, {
title: 'Window Title',
icon: 'application/myapp/myapp.svg',
onClose: () => {
},
onMinimize: () => {
},
onMaximize: (isMaximized) => {
},
windowId: 'custom-window-id'
});
Window Info Object:
{
windowId: string,
window: HTMLElement,
pid: number,
zIndex: number,
isFocused: boolean,
isMinimized: boolean,
isMaximized: boolean,
isMainWindow: boolean,
title: string,
icon: string|null,
createdAt: number
}
Window Operations
GUIManager.focusWindow(this.windowId);
GUIManager.minimizeWindow(this.windowId);
GUIManager.restoreWindow(this.windowId, true);
GUIManager.toggleMaximize(this.windowId);
const winInfo = GUIManager.getWindowInfo(this.windowId);
const windows = GUIManager.getWindowsByPid(this.pid);
GUIManager.unregisterWindow(this.windowId);
Window Close Flow
When a window is closed (user clicks close button or unregisterWindow is called), GUIManager executes:
-
Calls onClose callback (if exists):
- Callback executes before window close animation
GUIManager clears onClose reference to prevent recursion
- If callback already called
unregisterWindow, GUIManager skips subsequent steps
-
Executes close animation:
- Uses
AnimateManager for smooth close animation
- Waits for animation to complete before removing element
-
Unregisters window:
- Removes window from registry
- Cleans up event listeners (drag, resize, etc.)
- Updates taskbar visibility
-
Checks process termination:
- If PID has no other windows and is not Exploit program (PID 10000), automatically calls
ProcessManager.killProgram(pid)
Important:
onClose callback should only perform cleanup work
- Do NOT call
unregisterWindow() or _closeWindow() in onClose
- Window close flow is managed by
GUIManager to ensure proper resource cleanup
Background Mode Support
GUI programs can support background mode, allowing them to continue running when the window is closed, and be restored from the system tray.
Implementing Background Mode
_onCloseRequest: function() {
this._goToBackground();
},
_goToBackground: function() {
if (!this.windowId || !this.window) return;
const winInfo = typeof GUIManager !== 'undefined' ?
GUIManager.getWindowInfo(this.windowId) : null;
if (winInfo) {
winInfo._backgroundRequested = true;
}
if (this.window.style) {
this.window.style.display = 'none';
}
if (this._kernelAPI && typeof this._kernelAPI.call === 'function') {
this._kernelAPI.call('Process.requestBackground', []).catch(e => {
if (typeof KernelLogger !== 'undefined') {
KernelLogger.warn('MYAPP', 'requestBackground 失败: ' + (e && e.message));
}
});
}
},
_registerBackgroundTray: async function() {
if (!this._kernelAPI || typeof this._kernelAPI.call !== 'function') return;
const windowId = this.windowId;
const kernelAPI = this._kernelAPI;
try {
await this._kernelAPI.call('Process.registerBackgroundTrayClick', [
function() {
if (typeof GUIManager === 'undefined') return;
const winInfo = GUIManager.getWindowInfo(windowId);
if (winInfo && winInfo.window) {
winInfo.window.style.display = '';
GUIManager.focusWindow(windowId);
}
if (kernelAPI && typeof kernelAPI.call === 'function') {
kernelAPI.call('Process.requestForeground', []).catch(() => {});
}
}
]);
await this._kernelAPI.call('Process.registerBackgroundTrayContextMenu', [
function() {
return [
{ label: 'Restore', action: () => { } },
{ label: 'Exit', action: () => { } }
];
}
]);
} catch (e) {
if (typeof KernelLogger !== 'undefined') {
KernelLogger.warn('MYAPP', '注册后台托盘失败: ' + (e && e.message));
}
}
}
How Background Mode Works:
- When user clicks close button,
onClose callback is called
- Set
winInfo._backgroundRequested = true and hide window
- Call
Process.requestBackground via kernel API
GUIManager detects _backgroundRequested and only hides window (doesn't unregister or kill process)
- Program continues running in background
- User can click tray icon to restore window
- Call
Process.requestForeground to bring program back to foreground
Event Handling
EventManager API (Mandatory)
All event handling must go through EventManager. This is a mandatory requirement in ZerOS.
Why EventManager:
- Unified event management with priority and propagation control
- Automatic cleanup when process exits (prevents memory leaks)
- Supports multiple programs registering the same event (executed by priority)
- Provides unified event propagation control API
Registering Event Handlers
const handlerId = EventManager.registerEventHandler(
this.pid,
'click',
(event, eventContext) => {
},
{
priority: 100,
selector: '.my-button',
stopPropagation: false,
once: false,
passive: false,
useCapture: false
}
);
this.eventHandlers.push(handlerId);
Registering Element-Specific Events
For events that don't bubble (e.g., mouseenter, mouseleave, load, error):
const handlerId = EventManager.registerElementEvent(
this.pid,
element,
'mouseenter',
(event, eventContext) => {
element.style.backgroundColor = 'blue';
},
{
once: false,
passive: false
}
);
this.eventHandlers.push(handlerId);
Event Context API
EventManager.registerEventHandler(this.pid, 'click', (e, ctx) => {
ctx.stopPropagation();
ctx.stopImmediatePropagation();
ctx.preventDefault();
if (ctx.stopped) {
}
if (ctx.prevented) {
}
const handlerInfo = ctx.currentHandler;
});
Unregistering Event Handlers
EventManager.unregisterEventHandler(handlerId);
EventManager.unregisterAllHandlersForPid(this.pid);
Note: ProcessManager automatically calls unregisterAllHandlersForPid when a process exits, so manual cleanup in __exit__ is optional but recommended for clarity.
Event Priority
Events are executed by priority (lower number = higher priority):
- Priority 1-10: System core events (e.g., context menu, window controls)
- Priority 11-30: Window management events (e.g., drag, resize)
- Priority 31-50: Menu and popup events
- Priority 51-100: Application events (default priority)
- Priority 101+: Low priority events
Common Event Patterns
Button Click
const button = this.window.querySelector('.my-button');
if (button) {
const handlerId = EventManager.registerElementEvent(
this.pid,
button,
'click',
(e) => {
e.stopPropagation();
this._handleButtonClick();
}
);
this.eventHandlers.push(handlerId);
}
Form Submission
const form = this.window.querySelector('.my-form');
if (form) {
const handlerId = EventManager.registerEventHandler(
this.pid,
'submit',
(e, ctx) => {
ctx.preventDefault();
this._handleFormSubmit(e);
return false;
},
{
selector: '.my-form',
priority: 50
}
);
this.eventHandlers.push(handlerId);
}
Keyboard Shortcuts
const handlerId = EventManager.registerEventHandler(
this.pid,
'keydown',
(e, ctx) => {
if (e.ctrlKey && e.key === 's') {
ctx.preventDefault();
this._save();
return false;
}
if (e.key === 'Escape') {
this._onCloseRequest();
return false;
}
},
{
priority: 10
}
);
this.eventHandlers.push(handlerId);
Context Menu
const handlerId = EventManager.registerEventHandler(
this.pid,
'contextmenu',
(e, ctx) => {
ctx.preventDefault();
this._showContextMenu(e.clientX, e.clientY);
return false;
},
{
selector: '.my-content',
priority: 5
}
);
this.eventHandlers.push(handlerId);
Theme Compatibility
Using CSS Theme Variables
Always use CSS theme variables for colors and styles to ensure compatibility with system themes:
.myapp-window {
background: var(--theme-background-elevated, rgba(37, 43, 53, 0.98));
border: 1px solid var(--theme-border, rgba(139, 92, 246, 0.3));
color: var(--theme-text, #d7e0dd);
}
.myapp-button {
background: var(--theme-primary, #8b5cf6);
color: var(--theme-text-on-primary, #ffffff);
border: 1px solid var(--theme-primary-dark, #7c3aed);
}
.myapp-button:hover {
background: var(--theme-primary-hover, #7c3aed);
}
.myapp-button:active {
background: var(--theme-primary-dark, #6d28d9);
}
.myapp-success {
color: var(--theme-success, #10b981);
}
.myapp-warning {
color: var(--theme-warning, #f59e0b);
}
.myapp-error {
color: var(--theme-error, #ef4444);
}
Available Theme Variables
Background Colors:
--theme-background
--theme-background-secondary
--theme-background-tertiary
--theme-background-elevated
Text Colors:
--theme-text
--theme-text-secondary
--theme-text-muted
--theme-text-on-primary
Primary Colors:
--theme-primary
--theme-primary-light
--theme-primary-dark
--theme-primary-hover
--theme-secondary
Status Colors:
--theme-success
--theme-warning
--theme-error
--theme-info
Border:
Style Variables:
--style-window-border-radius
--style-window-backdrop-filter
--style-window-box-shadow-focused
Listening to Theme Changes
__init__: async function(pid, initArgs) {
if (typeof ThemeManager !== 'undefined') {
this._themeUnsubscribe = ThemeManager.onThemeChange((themeId, theme) => {
this._updateTheme(theme);
});
}
},
__exit__: function() {
if (this._themeUnsubscribe && typeof this._themeUnsubscribe === 'function') {
this._themeUnsubscribe();
this._themeUnsubscribe = null;
}
},
_updateTheme: function(theme) {
if (this.window) {
this.window.style.backgroundColor = theme.colors.backgroundElevated;
}
}
Kernel API Calls
Using Process-Bound Kernel API (Recommended)
The initArgs.kernelAPI provides a process-bound kernel API that prevents PID spoofing and is more secure:
__init__: async function(pid, initArgs) {
this.pid = pid;
this._kernelAPI = (initArgs && initArgs.kernelAPI) || null;
if (this._kernelAPI && typeof this._kernelAPI.call === 'function') {
try {
const result = await this._kernelAPI.call('FileSystem.readFile', [
'D:/path/to/file.txt'
]);
console.log('File content:', result);
} catch (e) {
if (typeof KernelLogger !== 'undefined') {
KernelLogger.error('MYAPP', '读取文件失败: ' + e.message);
}
}
}
}
Common Kernel APIs for GUI Programs
File System:
const content = await this._kernelAPI.call('FileSystem.readFile', ['D:/path/to/file.txt']);
await this._kernelAPI.call('FileSystem.writeFile', ['D:/path/to/file.txt', 'content']);
const files = await this._kernelAPI.call('FileSystem.listDirectory', ['D:/path/to/dir']);
Process Management:
await this._kernelAPI.call('Process.requestSelfTermination', []);
await this._kernelAPI.call('Process.requestBackground', []);
await this._kernelAPI.call('Process.requestForeground', []);
await this._kernelAPI.call('Process.registerBackgroundTrayClick', [callback]);
await this._kernelAPI.call('Process.registerBackgroundTrayContextMenu', [menuCallback]);
Notifications:
await this._kernelAPI.call('Notification.createNotification', [this.pid, {
title: 'Title',
content: 'Content',
type: 'info',
duration: 5000
}]);
Storage:
const value = await this._kernelAPI.call('LStorage.read', ['myapp.setting.key']);
await this._kernelAPI.call('LStorage.write', ['myapp.setting.key', value]);
async function _loadSettings(key) {
if (typeof LStorage !== 'undefined') {
if (!LStorage._initialized) {
await LStorage.init();
}
const settings = LStorage.getSystemStorage(key);
return settings || {};
}
return {};
}
async function _saveSettings(key, value) {
if (typeof LStorage !== 'undefined') {
if (!LStorage._initialized) {
await LStorage.init();
}
LStorage.setSystemStorage(key, value);
}
}
GUI Dialogs:
await this._kernelAPI.call('GUIManager.showAlert', ['Message', 'Title', 'info']);
const confirmed = await this._kernelAPI.call('GUIManager.showConfirm', [
'Are you sure?',
'Confirm',
'warning'
]);
const input = await this._kernelAPI.call('GUIManager.showPrompt', [
'Enter value:',
'Input',
'default value'
]);
Permissions
Required Permissions
GUI programs must declare required permissions in __info__():
permissions: typeof PermissionManager !== 'undefined' ? [
PermissionManager.PERMISSION.GUI_WINDOW_CREATE,
PermissionManager.PERMISSION.EVENT_LISTENER,
PermissionManager.PERMISSION.FILE_READ,
PermissionManager.PERMISSION.FILE_WRITE,
PermissionManager.PERMISSION.PROCESS_BACKGROUND,
PermissionManager.PERMISSION.NETWORK_REQUEST,
] : []
Permission Levels
- NORMAL: Auto-granted, no user confirmation
- SPECIAL: Requires user confirmation
- DANGEROUS: Requires admin approval
Common Permissions
GUI_WINDOW_CREATE: Create GUI windows (required for GUI programs)
EVENT_LISTENER: Register event handlers (required for GUI programs)
FILE_READ: Read files
FILE_WRITE: Write files
PROCESS_BACKGROUND: Run in background mode
NETWORK_REQUEST: Make network requests
LSTORAGE_READ: Read from LStorage
LSTORAGE_WRITE: Write to LStorage
NOTIFICATION_CREATE: Create notifications
Best Practices
1. Window Lifecycle
- Always register window with
GUIManager in __init__
- Always unregister window in
__exit__
- Store
windowId for later reference
- Handle
onClose callback properly (cleanup only, don't call unregisterWindow)
2. Window CSS - Fixed Height Required
IMPORTANT: GUI窗口必须使用固定高度值来防止标题栏问题:
this.window.style.cssText = `
width: 800px;
height: 600px; // 必需:固定像素值
min-width: 600px; // 可选:最小宽度
min-height: 600px; // 必需:必须等于height
display: flex;
flex-direction: column;
overflow: hidden;
`;
禁止使用:
- ❌
height: 100% (会导致标题栏问题)
- ❌
height: calc(100vh - xxx) (可能导致问题)
- ❌ 单独使用
min-height 没有 height (无效)
- ❌ 父元素没有固定高度时使用
flex: 1
窗口初始化顺序(关键!):
this.window.style.cssText = `...`;
if (typeof GUIManager !== 'undefined') {
GUIManager.registerWindow(pid, this.window, {...});
}
guiContainer.appendChild(this.window);
this._buildUI();
工具栏和状态栏必须有固定高度(必须使用min-height和max-height):
.toolbar {
height: 46px;
min-height: 46px;
max-height: 46px;
flex-shrink: 0;
}
.statusbar {
height: 28px;
min-height: 28px;
max-height: 28px;
flex-shrink: 0;
}
内容区域使用Flexbox布局:
- 使用
flex: 1 和 min-height: 0 让子容器可以滚动
.packetcap-main {
display: flex;
flex: 1;
overflow: hidden;
min-height: 0;
}
.toolbar {
flex-shrink: 0;
}
.scrollable-content {
flex: 1;
overflow-y: auto;
min-height: 0;
}
3. Event Handling
- Always use
EventManager for event handling (mandatory requirement)
- Store event handler IDs in an array for cleanup
- Unregister all event handlers in
__exit__
- Use appropriate priorities for event handlers
- Use
selector option to limit event scope when possible
4. Resource Cleanup
- Clean up all event handlers in
__exit__
- Unregister window from
GUIManager
- Remove window from DOM
- Clear all references (set to
null)
- Cancel any timers (
setInterval, setTimeout)
- Unsubscribe from theme changes, language changes, etc.
5. Singleton Pattern Issue (IMPORTANT)
GUI programs that export as singletons (e.g., window.MYAPP = MYAPP) have a critical issue: when the program is closed and reopened, the previous instance's properties may cause errors.
Problem:
window.MYAPP = MYAPP;
Solution: Re-initialize essential properties at the beginning of __init__:
__init__: async function(pid, initArgs) {
this.pid = null;
this.window = null;
this.windowId = null;
this._eventHandlers = [];
this._resizeObserver = null;
this._currentCellSize = 16;
this.pid = pid;
}
Key Points:
- Always re-initialize
_eventHandlers = [] at the start of __init__
- Re-initialize any other array/object properties that were set to
null in __exit__
- This ensures the program works correctly even after being closed and reopened
6. Error Handling
- Always wrap kernel API calls in
try-catch
- Use
KernelLogger for logging (never use console.log)
- Report errors using
Exception.report() if needed
- Handle missing dependencies gracefully
7. Theme Compatibility
- Always use CSS theme variables for colors
- Provide fallback values for theme variables
- Listen to theme changes if UI needs dynamic updates
- Test with different themes
8. Background Mode
- Consider supporting background mode for long-running applications
- Register tray click handler to restore window
- Provide context menu for background processes
- Handle window restoration properly
9. Performance
- Avoid blocking operations in event handlers
- Use
requestAnimationFrame for animations
- Debounce/throttle frequent events (scroll, resize, etc.)
- Lazy load heavy content
10. Responsive Layout & Mobile Support
GUI programs should support responsive layout and mobile touch events.
ResizeObserver for Responsive Layout
Use ResizeObserver to detect container size changes (e.g., window maximize/restore):
_setupResizeHandler: function() {
if (typeof ResizeObserver !== 'undefined') {
const container = this.window.querySelector('.myapp-content');
if (container) {
this._resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
this._handleResize();
break;
}
});
this._resizeObserver.observe(container);
}
}
if (this.window) {
this._addEventHandler(this.window, 'transitionend', (e) => {
if (e.propertyName === 'width' || e.propertyName === 'height') {
this._handleResize();
}
});
}
},
_handleResize: function() {
const container = this.window.querySelector('.myapp-content');
if (!container) return;
const newWidth = container.clientWidth;
const newHeight = container.clientHeight;
this._updateLayout(newWidth, newHeight);
},
Mobile Touch Support
For programs that need touch input (e.g., drawing apps):
_getTouchPos: function(e) {
const rect = this.canvas.getBoundingClientRect();
let clientX, clientY;
if (e.touches && e.touches.length > 0) {
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
} else if (e.changedTouches && e.changedTouches.length > 0) {
clientX = e.changedTouches[0].clientX;
clientY = e.changedTouches[0].clientY;
} else {
clientX = e.clientX;
clientY = e.clientY;
}
return {
x: clientX - rect.left,
y: clientY - rect.top
};
},
_bindEvents: function() {
this._addEventHandler(this.canvas, 'pointerdown', (e) => {
e.preventDefault();
this._handlePointerDown(e);
});
this._addEventHandler(this.canvas, 'pointermove', (e) => {
e.preventDefault();
this._handlePointerMove(e);
});
this._addEventHandler(this.canvas, 'pointerup', (e) => {
this._handlePointerUp(e);
});
this._addEventHandler(this.canvas, 'touchstart', (e) => e.preventDefault(), { passive: false });
this._addEventHandler(this.canvas, 'touchmove', (e) => e.preventDefault(), { passive: false });
this._addEventHandler(this.canvas, 'touchend', (e) => e.preventDefault(), { passive: false });
},
_addEventHandler: function(element, event, handler, options = {}) {
element.addEventListener(event, handler, options);
this._eventHandlers.push({ element, event, handler, options });
},
Cleanup in exit
__exit__: async function() {
if (this._eventHandlers && Array.isArray(this._eventHandlers)) {
this._eventHandlers.forEach(({ element, event, handler, options }) => {
if (element && typeof element.removeEventListener === 'function') {
element.removeEventListener(event, handler, options);
}
});
this._eventHandlers = null;
}
if (this._resizeObserver) {
this._resizeObserver.disconnect();
this._resizeObserver = null;
}
}
11. Accessibility
- Use semantic HTML elements
- Provide keyboard shortcuts
- Support keyboard navigation
- Use ARIA attributes when appropriate
Common Patterns
Pattern 1: Simple Window
__init__: async function(pid, initArgs) {
this.pid = pid;
this._kernelAPI = (initArgs && initArgs.kernelAPI) || null;
const guiContainer = initArgs.guiContainer || document.getElementById('gui-container');
this.window = document.createElement('div');
this.window.className = 'myapp-window zos-gui-window';
this.window.dataset.pid = String(pid);
if (typeof GUIManager !== 'undefined') {
const windowInfo = GUIManager.registerWindow(pid, this.window, {
title: 'My App',
onClose: () => this._exit()
});
if (windowInfo) this.windowId = windowInfo.windowId;
}
this.window.innerHTML = '<div>Hello, World!</div>';
guiContainer.appendChild(this.window);
}
Pattern 2: Window with Controls
_buildUI: function() {
const toolbar = document.createElement('div');
toolbar.className = 'myapp-toolbar';
const btnSave = document.createElement('button');
btnSave.textContent = 'Save';
btnSave.className = 'myapp-btn myapp-btn-save';
toolbar.appendChild(btnSave);
const content = document.createElement('div');
content.className = 'myapp-content';
content.textContent = 'Content here';
this.window.appendChild(toolbar);
this.window.appendChild(content);
if (typeof EventManager !== 'undefined') {
const handlerId = EventManager.registerElementEvent(
this.pid,
btnSave,
'click',
() => this._save()
);
this.eventHandlers.push(handlerId);
}
}
Pattern 3: Form Handling
_buildForm: function() {
const form = document.createElement('form');
form.className = 'myapp-form';
const input = document.createElement('input');
input.type = 'text';
input.name = 'username';
form.appendChild(input);
const submitBtn = document.createElement('button');
submitBtn.type = 'submit';
submitBtn.textContent = 'Submit';
form.appendChild(submitBtn);
this.window.appendChild(form);
if (typeof EventManager !== 'undefined') {
const handlerId = EventManager.registerEventHandler(
this.pid,
'submit',
(e, ctx) => {
ctx.preventDefault();
this._handleSubmit(new FormData(form));
return false;
},
{ selector: '.myapp-form' }
);
this.eventHandlers.push(handlerId);
}
}
Pattern 4: Loading State
_showLoading: function() {
const loading = document.createElement('div');
loading.className = 'myapp-loading';
loading.innerHTML = '<div class="spinner"></div><div>Loading...</div>';
this.window.appendChild(loading);
},
_hideLoading: function() {
const loading = this.window.querySelector('.myapp-loading');
if (loading) {
loading.remove();
}
},
_loadData: async function() {
this._showLoading();
try {
const data = await this._kernelAPI.call('FileSystem.readFile', ['D:/data.json']);
this._renderData(data);
} catch (e) {
this._showError(e.message);
} finally {
this._hideLoading();
}
}
File Location and Naming
Directory Structure
system/service/DISK/D/application/
├── myapp/
│ ├── myapp.js # Main program file
│ ├── myapp.css # Styles (optional)
│ └── assets/ # Assets (optional)
│ ├── icon.svg
│ └── images/
Naming Conventions
- Directory name: Lowercase, descriptive (e.g.,
myapp, filemanager)
- Main file: Same as directory name with
.js extension (e.g., myapp.js)
- CSS file: Same as directory name with
.css extension (e.g., myapp.css)
- Global object: Uppercase, same as directory name (e.g.,
MYAPP)
Testing
Manual Testing Checklist
-
Window Creation:
-
Event Handling:
-
Theme Compatibility:
-
Background Mode:
-
Resource Cleanup:
-
Error Handling:
Common Issues
Issue 1: Window Not Appearing
Symptoms: Window element is created but not visible.
Solutions:
- Check if
guiContainer exists: document.getElementById('gui-container')
- Ensure window element is appended to
guiContainer
- Check CSS: window element needs
position: fixed or position: absolute
- Verify
GUIManager.registerWindow() succeeded
Issue 2: Events Not Working
Symptoms: Click handlers, keyboard shortcuts, etc. don't work.
Solutions:
- Ensure using
EventManager (not addEventListener)
- Check if permissions include
EVENT_LISTENER
- Verify event handler IDs are stored for cleanup
- Check event priority (might be blocked by higher priority handler)
Issue 3: Memory Leaks
Symptoms: Memory usage increases over time, program slows down.
Solutions:
- Ensure all event handlers are unregistered in
__exit__
- Clear all timers (
setInterval, setTimeout)
- Remove all DOM references (set to
null)
- Unsubscribe from theme/language change listeners
Issue 4: Theme Not Applied
Symptoms: UI doesn't match system theme.
Solutions:
- Use CSS theme variables (
var(--theme-...))
- Provide fallback values for theme variables
- Listen to theme changes if UI needs dynamic updates
- Test with different themes
Issue 5: Background Mode Not Working
Symptoms: Window closes instead of going to background.
Solutions:
- Set
winInfo._backgroundRequested = true in onClose
- Hide window (
window.style.display = 'none')
- Call
Process.requestBackground via kernel API
- Register tray click handler with
Process.registerBackgroundTrayClick
Issue 6: Permission Denied
Symptoms: Kernel API calls fail with permission error.
Solutions:
- Check if permission is declared in
__info__().permissions
- Verify permission level (NORMAL/SPECIAL/DANGEROUS)
- Check if user granted permission (for SPECIAL/DANGEROUS)
- Use process-bound kernel API (
initArgs.kernelAPI) instead of direct calls
References
API Documentation
Developer Guides
Example Programs
system/service/DISK/D/application/servicemanager/servicemanager.js - Complex GUI application
system/service/DISK/D/application/hellogui/hellogui.js - Simple GUI with background mode
system/service/DISK/D/application/taskmanager/taskmanager.js - Task manager GUI
system/service/DISK/D/application/zeroide/zeroide.js - IDE GUI application