🚧 Glyx is pre-release software. APIs may change before v1.0. Get started →
Documentation
APIs & Bindings
window

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

MethodReturnsDescription
glyxWindow.setFullscreen(bool)voidEnter / exit fullscreen (covers taskbar)
glyxWindow.setMaximized(bool)voidMaximize / restore
glyxWindow.setMinimized()voidMinimize to taskbar
glyxWindow.isFullscreen()booleanCurrent fullscreen state
glyxWindow.isMaximized()booleanCurrent maximized state

Size and position

MethodReturnsDescription
glyxWindow.getWindowSize(){width, height}Current window size in physical pixels
glyxWindow.getScreenSize(){width, height}Primary monitor resolution

Window properties

MethodReturnsDescription
glyxWindow.setTitle(title)voidChange the window title bar text
glyxWindow.setAlwaysOnTop(bool)voidPin window above others
glyxWindow.platform()'windows' | 'macos' | 'linux'Host OS (cached, never re-queried)

Lifecycle

MethodReturnsDescription
glyxWindow.close()voidClose this window (main window also exits the app)
glyxWindow.quit()voidExit the entire application
glyxWindow.restart()voidQuit and re-launch the same executable
glyxWindow.hideSplash()voidHide the splash screen overlay

External links

glyxWindow.openExternal(url: string): void

Opens 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(): void

Triggers 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

OptionTypeDescription
titlestringWindow title
widthnumberInitial width in physical pixels
heightnumberInitial height in physical pixels
keystringDedupe key — at most one window per key (see below)
allowDuplicatebooleanOpt 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:

glyx.config.ts
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:

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:

glyx.config.json
{
  "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:

APIScopeOS registration
input.shortcutFires only when the app window is focusedNo — pure JS event listener
input.globalShortcutFires even when the app is in the backgroundYes — 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 key

Supported 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): void

Register 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): void

Same 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): () => void

Fires 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.