siwin
Simple window maker.
Summary
| Latest Version | Unknown |
|---|---|
| License | MIT |
| CI Status | Failing |
| Downloads | 0 |
| Last Indexed | 2026-07-21 05:24 |
Tags
Installation
nimble install siwin
choosenim install siwin
git clone https://github.com/levovix0/siwin
OS Compatibility
| Platform | Linux | macOS | Windows | FreeBSD | OpenBSD | NetBSD | Android | iOS | WASM | Embedded |
|---|---|---|---|---|---|---|---|---|---|---|
| siwin | ✓ | ✓ | ✓ | - | - | - | ✓ | - | - | - |
Source
| Repository | https://github.com/levovix0/siwin |
|---|---|
| Homepage | https://github.com/levovix0/siwin |
| Registry Source | nimble_official |
README
Siwin

Cross-platform window creation and event handling library.
Can be used as an alternative to GLFW/GLUT/windy
Features
- works with: OpenGL, Vulkan, Metal (on MacOS), software rendering
- works on: Linux(X11 and Wayland), Windows, MacOS
- handles events from: mouse, keyboard
- and also supports: clipboard, offscreen rendering, interactive move/resize, etc.
Examples
simple window
import siwin, opengl
let win = newOpenglWindow()
opengl.loadExtensions() # load opengl functions
win.eventsHandler.onRender = proc(e: RenderEvent) =
glClearColor(0.1, 0.1, 0.1, 1)
glClear(GlColorBufferBit or GlDepthBufferBit)
run win
software-rendering window
import siwin, vmath
const color = [32'u8, 32, 32, 255]
run newSoftwareRenderingWindow(), WindowEventsHandler(
onRender: proc(e: RenderEvent) =
let pixelBuffer = e.window.pixelBuffer
for y in 0..<pixelBuffer.size.y:
for x in 0..<pixelBuffer.size.x:
cast[ptr UncheckedArray[array[4, uint8]]](pixelBuffer.data)[y * pixelBuffer.size.x + x] = color
convertPixelsInplace(pixelBuffer.data, pixelBuffer.size, PixelBufferFormat.bgrx_32bit, pixelBuffer.format)
,
onKey: proc(e: KeyEvent) =
if (not e.pressed) and e.key == Key.escape:
close e.window
)
OpenGL

import siwin, opengl, vmath
let siwinGlobals = newSiwinGlobals(
preferedPlatform = (when defined(linux): x11 else: defaultPreferedPlatform)
# note: glBegin and other non- OpenGL ES functions don't work on Wayland,
# so we should set preferedPlatform to x11 on linux when using regular OpenGL.
# see tests/t_opengl_es.nim for more complex, wayland-compatible opengl example
)
var window = siwinGlobals.newOpenglWindow(
title="OpenGL example",
)
loadExtensions() # init opengl
run window, WindowEventsHandler(
onResize: proc(e: ResizeEvent) =
glViewport 0, 0, e.size.x.GLsizei, e.size.y.GLsizei
glMatrixMode GlProjection
glLoadIdentity()
glOrtho -30, 30, -30, 30, -30, 30
glMatrixMode(GlModelView)
,
onRender: proc(e: RenderEvent) =
glClearColor 0.3, 0.3, 0.3, 1
glClear GlColorBufferBit or GlDepthBufferBit
glShadeModel GlSmooth
glLoadIdentity()
glTranslatef -15, -15, 0
glBegin GlTriangles
glColor3f 1, 0, 0
glVertex2f 0, 0
glColor3f 0, 1, 0
glVertex2f 30, 0
glColor3f 0, 0, 1
glVertex2f 0, 30
glEnd()
)
note: call redraw(window) every time you want window.render to be called. siwin will automatically call window.render only when window resizes.
note: opengl 1.x and 2.x functions (like glBegin), is not supported on Wayland, due to Wayland only beeng able to initialize with EGL
Vulkan
see t_vulkan.nim
import siwin, nimgl/vulkan, sequtils
doassert vkInit()
let exts = getRequiredVulkanExtensions()
var cexts = exts.mapit(it[0].addr)
var appInfo = newVkApplicationInfo(
pApplicationName = "siwin Vulkan example",
applicationVersion = vkMakeVersion(1, 0, 0),
pEngineName = "No Engine",
engineVersion = vkMakeVersion(1, 0, 0),
apiVersion = vkApiVersion1_1
)
var instanceCreateInfo = newVkInstanceCreateInfo(
pApplicationInfo = appInfo.addr,
enabledExtensionCount = exts.len,
ppEnabledExtensionNames = cast[cstringArray](cexts[0].addr),
enabledLayerCount = 0,
ppEnabledLayerNames = nil,
)
var instance: VkInstance
doassert vkCreateInstance(instanceCreateInfo.addr, nil, result.addr) == VKSuccess
let siwinGlobals = newSiwinGlobals()
let window = siwinGlobals.newVulkanWindow(cast[pointer](instance), title="Vulkan example")
let surface = cast[VkSurfaceKHR](window.vulkanSurface)
# do other initialization using instance and surface...
run window, WindowEventsHandler(
onRender: proc(e: RenderEvent) =
## do rendering...
,
onClose: proc(e: CloseEvent) =
## uninitialize before surface destruction
)
# surface already destroyed, continue uninitializing...
pixie

note: very slow, but useful if opengl not needed and if window is used to just display one single image
import siwin, pixie
var image: Image
let siwinGlobals = newSiwinGlobals()
run siwinGlobals.newSoftwareRenderingWindow(title="pixie example"), WindowEventsHandler(
onResize: proc(e: ResizeEvent) =
if e.size.x * e.size.y <= 0: return
image = newImage(e.size.x, e.size.y)
,
onRender: proc(e: RenderEvent) =
if e.window.size.x * e.window.size.y <= 0: return
image.fill(rgba(255, 255, 255, 255))
let ctx = image.newContext
ctx.fillStyle = rgba(0, 255, 0, 255)
let
wh = vec2(250, 250)
pos = vec2(image.width.float, image.height.float) / 2 - wh / 2
ctx.fillRoundedRect(rect(pos, wh), 25.0)
let pixelBuffer = e.window.pixelBuffer
copyMem(pixelBuffer.data, image.data[0].addr, pixelBuffer.size.x * pixelBuffer.size.y * Color32bit.sizeof)
convertPixelsInplace(pixelBuffer.data, pixelBuffer.size, PixelBufferFormat.rgbx_32bit, pixelBuffer.format)
,
onKey: proc(e: KeyEvent) =
if (not e.pressed) and e.key == Key.escape:
close e.window
)
popup windows
This api adds popup windows. On Wayland these are required to do popups, but on other platforms these will just be frameless windows.
import siwin, vmath
let globals = newSiwinGlobals()
let parent = globals.newSoftwareRenderingWindow()
let placement = PopupPlacement(
anchorRectPos: ivec2(100, 100),
anchorRectSize: ivec2(120, 40),
size: ivec2(320, 220),
anchor: Edge.bottomLeft,
gravity: Edge.topLeft,
offset: ivec2(0, 8),
constraintAdjustment: {PopupConstraintAdjustment.pcaSlideX, PopupConstraintAdjustment.pcaFlipY},
reactive: true,
)
let popup = globals.newPopupWindow(parent, placement)
clipboard
let clipboard = window.clipboard
echo clipboard.text
clipboard.text = "some text"
offscreen rendering
note: this will create invisible window. ctx mustn't be discarded as its destructor will close the window.
If you have multiple contexts, use makeCurrent to select.
import siwin/offscreen, opengl
let siwinGlobals = newSiwinGlobals()
let ctx {.used.} = siwinGlobals.newOpenglContext()
loadExtensions()
# do any opengl computing
manual main cycle
import siwin
let siwinGlobals = newSiwinGlobals()
let window = siwinGlobals.newOenglWindow()
loadExtensions()
let eventsHandler = WindowEventsHandler(
# ...
)
window.firstStep(eventsHandler, makeVisible=true)
while window.opened:
window.step(eventsHandler)
running multiple windows
import siwin
let siwinGlobals = newSiwinGlobals()
let win1 = siwinGlobals.newOpenglWindow()
let win2 = siwinGlobals.newOpenglWindow()
loadExtensions()
let win1_eventsHandler = WindowEventsHandler(
onResize: proc(e: ResizeEvent) =
makeCurrent e.window
#...
,
onRender: proc(e: RenderEvent) =
makeCurrent e.window
#...
)
let win2_eventsHandler = WindowEventsHandler(
onResize: proc(e: ResizeEvent) =
makeCurrent e.window
#...
,
onRender: proc(e: RenderEvent) =
makeCurrent e.window
#...
)
runMultiple(
(window: win1, eventsHandler: win1_eventsHandler, makeVisible: true),
(window: win2, eventsHandler: win2_eventsHandler, makeVisible: true),
)
client-side decorations
import siwin
let siwinGlobals = newSiwinGlobals()
let window = siwinGlobals.newOpenglWindow(transparent=true, frameless=true)
loadExtensions()
run window, WindowEventsHandler(
onMouseMove: proc(e: MouseMoveEvent) =
if MouseButton.left in e.window.mouse.pressed:
window.startInteractiveMove()
# see also: startInteractiveResize
)
transparent backdrop blur
Backdrop blur uses the same API on macOS, Windows, Wayland, and X11. It is available on macOS, Windows 11 build 22621 or newer, and KDE compositors that advertise the KWin blur extension. Create the window with an alpha-capable surface so the effect can show through transparent pixels.
import siwin
let window = newSoftwareRenderingWindow(transparent = true, frameless = true)
if window.supports(wvcBackdropBlur):
discard window.trySetBackdrop(initWindowBackdrop()) # whole-window blur
# Empty regions mean the whole window. Non-empty regions use Siwin window coordinates.
# Regional blur is currently available on macOS and KDE only.
# discard window.trySetBackdrop(initWindowBackdrop(regions))
window.clearBackdrop()
macOS also supports system materials such as wbmSidebar and wbmHud:
if window.supports(wvcBackdropMaterial):
window.setBackdrop(initWindowBackdrop(wbmSidebar))
See backdrop_blur_demo.nim for a runnable cross-platform example.
all methods and events
see siwin/platforms/any/window
I want to get system handle of window and do some magic, but it is private?
import std/importutils
import siwin/platforms/x11/window
privateAccess WindowX11Obj
# ...
window.handle
Contributions
If you want to support this project, here is some tasks to do: * See issues * Any bugfixes is always accepted, just describe somewhere what you fixed * Refactoring (my code is bad, i know it) * if you doing very big refactoring, first create issue to ask is all your changes needed, and if it is, refactor * Documentation * Optimization * MacOS support * Android/IOS support * Web support * copy/paste images * Make cool site that adverts siwin
Just fork levovix0/siwin to your account, make changes and submit a pull request.
Or if it requires new repository to be created, create it and add an "change dependency" issue.