window Stable WINMACLNX
Control the app window, open secondary windows, communicate between them, and manage the application lifecycle.
Import
import { glyxWindow, ipc, input } from '@glyx-dev/react'glyxWindow
Window state
| Method | Returns | Description |
|---|---|---|
glyxWindow.setFullscreen(bool) | void | Enter / exit fullscreen (covers taskbar) |
glyxWindow.setMaximized(bool) | void | Maximize / restore |
glyxWindow.setMinimized() | void | Minimize to taskbar |
glyxWindow.isFullscreen() | boolean | Current fullscreen state |
glyxWindow.isMaximized() | boolean | Current maximized state |
Size and position
| Method | Returns | Description |
|---|---|---|
glyxWindow.getWindowSize() | {width, height} | Current window size in physical pixels |
glyxWindow.getScreenSize() | {width, height} | Primary monitor resolution |
Window properties
| Method | Returns | Description |
|---|---|---|
glyxWindow.setTitle(title) | void | Change the window title bar text |
glyxWindow.setAlwaysOnTop(bool) | void | Pin window above others |
glyxWindow.platform() | 'windows' | 'macos' | 'linux' | Host OS (cached, never re-queried) |
Lifecycle
| Method | Returns | Description |
|---|---|---|
glyxWindow.close() | void | Close this window (main window also exits the app) |
glyxWindow.quit() | void | Exit the entire application |
glyxWindow.restart() | void | Quit and re-launch the same executable |
glyxWindow.hideSplash() | void | Hide the splash screen overlay |
External links
glyxWindow.openExternal(url: string): voidOpens a URL in the system default browser, or a mailto: link in the default mail client. Does not open arbitrary executables — web and mailto URIs only. Throws at runtime unless the shell capability is declared.
glyxWindow.openExternal('https://glyx.dev/docs')
glyxWindow.openExternal('mailto:support@example.com')Memory
glyxWindow.collectMemory(): voidTriggers a V8 GC hint and returns freed memory pages to the OS via mimalloc. Useful at logical transition points (level load complete, navigating away from a heavy view, after closing a modal with large data):
// After clearing a large dataset:
setRows([])
glyxWindow.collectMemory()The runtime already calls this automatically on focus loss and on a background timer — manual use is optional.
Multi-window
glyxWindow.create(opts?) → Promise<{ id: number, send(msg: string): void }>Opens a secondary window running the same app bundle. Returns a handle for IPC messaging.
const win = await glyxWindow.create({
title: 'Inspector',
width: 400,
height: 600,
})
// Send a message to that window
win.send(JSON.stringify({ type: 'init', payload: 42 }))Options
| Option | Type | Description |
|---|---|---|
title | string | Window title |
width | number | Initial width in physical pixels |
height | number | Initial height in physical pixels |
key | string | Dedupe key — at most one window per key (see below) |
allowDuplicate | boolean | Opt out of config-level title dedupe for this call |
Preventing duplicate windows
By default, calling create() twice opens two windows. To make repeat calls
focus the existing window instead, enable the config flag:
window: {
preventDuplicateWindows: true,
}With the flag on, create({ title: 'Settings' }) while a "Settings" window is
already open focuses that window (restoring it if minimized) and resolves
with the existing window's handle — so win.send(...) still reaches it.
Nothing is deduped unless titles collide, so ordinary child windows with
distinct titles are unaffected.
Fine-grained control per call:
// Dedupe on an explicit key (works even without the config flag) —
// e.g. one preview window per document:
await glyxWindow.create({ title: doc.name, key: 'preview:' + doc.id })
// Intentionally open a twin despite the config flag:
await glyxWindow.create({ title: 'Untitled', allowDuplicate: true })An explicit key always wins over title-based dedupe; allowDuplicate: true
bypasses the config flag (it does not bypass an explicit key).
ipc — inter-window messaging
Send strings between windows. Both sender and receiver must import ipc.
import { ipc } from '@glyx-dev/react'ipc.send(targetHandle, message)
Send a message to another window. Use the id from glyxWindow.create().
ipc.send(win.id, JSON.stringify({ type: 'update', data: newValue }))ipc.on('message', callback)
Listen for messages in the receiving window. Returns an unsubscribe function.
const unsub = ipc.on('message', (raw) => {
const msg = JSON.parse(raw)
if (msg.type === 'update') {
setData(msg.data)
}
})
// Cleanup:
useEffect(() => unsub, [])Splash screen
Configure in glyx.config.json:
{
"splash": {
"image": "assets/splash.png",
"background": "#0D0D14",
"minimumMs": 1200
}
}Hide programmatically once your app is ready:
// After loading initial data:
await db.open('app.db')
await runMigrations()
glyxWindow.hideSplash()minimumMs guarantees the splash stays visible for at least that long, even if hideSplash() is called immediately. The splash auto-hides after 30 seconds as a safety net.
Custom title bar
Remove the native title bar with decorations: false in config:
{
"window": {
"decorations": false
}
}Then build your own using the WindowControls component (built into Glyx):
import { WindowControls } from '@glyx-dev/react'
function TitleBar() {
return (
<View
glyxDraggable // drag this region to move the window
style={{
height: 38,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 12,
backgroundColor: '#0D0D14',
}}
>
<Text style={{ fontSize: 13, color: '#9999cc' }}>My App</Text>
<WindowControls />
</View>
)
}WindowControls automatically renders the correct buttons for each platform (traffic lights on macOS, minimize / maximize / close on Windows).
The glyxDraggable prop marks the view as a drag region. Buttons and inputs inside the region work normally — only bare View areas drag the window.
See the Custom Title Bar guide for a full walkthrough.
Keyboard shortcuts — input.shortcut and input.globalShortcut
Glyx has two shortcut APIs with different scope:
| API | Scope | OS registration |
|---|---|---|
input.shortcut | Fires only when the app window is focused | No — pure JS event listener |
input.globalShortcut | Fires even when the app is in the background | Yes — registered with the OS |
Use input.shortcut for in-app commands (save, navigate, open palette). Use input.globalShortcut for
system-wide triggers (screenshot tool, clipboard manager, always-on hotkeys).
Accelerator format
Modifiers joined with +, key last. Case-insensitive.
"ctrl+s" → Ctrl + S
"ctrl+shift+z" → Ctrl + Shift + Z (redo)
"alt+f4" → Alt + F4
"meta+k" → Cmd+K on macOS, Win+K on Windows
"f11" → F11 (no modifier)
"escape" → Escape keySupported modifiers: ctrl / control, shift, alt, meta / cmd / win.
input.shortcut — focused shortcuts
const id = input.shortcut.register(accelerator: string, cb: () => void): number
input.shortcut.unregister(id: number): voidRegister any modifier + key combination. Returns a numeric ID for cleanup.
import { input } from '@glyx-dev/react'
import { useEffect } from 'react'
function NotesApp() {
useEffect(() => {
const ids = [
input.shortcut.register('ctrl+s', () => saveNote()),
input.shortcut.register('ctrl+n', () => newNote()),
input.shortcut.register('ctrl+z', () => undo()),
input.shortcut.register('ctrl+shift+z', () => redo()),
input.shortcut.register('ctrl+shift+e', () => exportPdf()),
input.shortcut.register('f11', () => glyxWindow.setFullscreen(!glyxWindow.isFullscreen())),
]
return () => ids.forEach(id => input.shortcut.unregister(id))
}, [])
}Multiple components can register shortcuts independently — they don't conflict as long as the accelerator strings are different. Registering the same accelerator twice fires both callbacks.
Shortcuts can run any async work — save to the local database, call an API, update state. Use a ref to capture the latest prop or state value so the callback always sees current data, not the stale closure from when the effect ran:
import { input, db } from '@glyx-dev/react'
import { useEffect, useRef, useState } from 'react'
function NoteEditor({ noteId }) {
const [content, setContent] = useState('')
const contentRef = useRef(content)
useEffect(() => { contentRef.current = content }, [content])
useEffect(() => {
const id = input.shortcut.register('ctrl+s', async () => {
// 1 — persist to local DB
await db.run(
'UPDATE notes SET content = ?, updated_at = ? WHERE id = ?',
[contentRef.current, Date.now(), noteId]
)
// 2 — sync to remote API
await fetch('https://api.example.com/notes/' + noteId, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content: contentRef.current }),
})
})
return () => input.shortcut.unregister(id)
}, [noteId])
return <TextInput value={content} onChangeText={setContent} multiline />
}contentRef is the key detail: noteId is stable so the effect only runs once per note,
but content changes on every keystroke. Without the ref, ctrl+s would always save the
initial empty string.
input.globalShortcut — system-wide shortcuts
const id = input.globalShortcut.register(accelerator: string, cb: () => void): string | null
input.globalShortcut.unregister(id: string): voidSame accelerator format, but the shortcut is registered with the OS via a native binding. Fires even when your app is minimized or behind other windows.
useEffect(() => {
// Bring the app to front from anywhere on the system
const id = input.globalShortcut.register('ctrl+shift+g', () => {
glyxWindow.setMinimized(false)
glyxWindow.setFullscreen(false)
})
return () => input.globalShortcut.unregister(id)
}, [])Global shortcuts hold an OS-level registration. Always unregister in the useEffect cleanup
to release the binding when the component unmounts. If the app crashes without unregistering,
the OS cleans up automatically on process exit.
Returns null if the binding is unavailable (e.g. the OS denied the registration because
another process already holds that combination).
input.gamepads — gamepad input
const unsub = input.gamepads.onInput(cb: (event: GamepadEvent) => void): () => voidFires for every gamepad event polled each frame. Returns an unsubscribe function.
useEffect(() => {
return input.gamepads.onInput(({ id, name, event }) => {
if (event.type === 'ButtonPressed' && event.button === 'South') jump()
})
}, [])Examples
Toggle fullscreen on F11
import { glyxWindow } from '@glyx-dev/react'
import { addKeyListener } from '@glyx-dev/react'
import { useEffect } from 'react'
function App() {
useEffect(() => {
const unsub = addKeyListener(({ key, pressed }) => {
if (pressed && key === 'F11') {
glyxWindow.setFullscreen(!glyxWindow.isFullscreen())
}
})
return unsub
}, [])
// ...
}Restart after applying an update
// After writing a new binary to disk:
await applyUpdate(binaryBlob)
glyxWindow.restart()Inspector window (dev tool pattern)
// Main window:
const inspectorWin = await glyxWindow.create({
title: 'Dev Inspector',
width: 500,
height: 700,
})
// Push state changes to the inspector:
function onStateChange(state) {
inspectorWin.send(JSON.stringify({ type: 'state', state }))
}
// Inspector window:
ipc.on('message', (raw) => {
const { type, state } = JSON.parse(raw)
if (type === 'state') setInspectedState(state)
})glyxWindow.platform() is determined at compile time and cached after the first call — it never triggers a native round-trip on subsequent reads.