Rohan Verma



Commit Diff


commit - b7bda67bcb33cd457e8311d404262aa47155d71d
commit + 275afb190ec44547db433d466f8a84a7ff1cee19
blob - d5dd479a976603498bcca512cc386d34ae6f47df
blob + 094bae55f5826a8c9b45e5bdcb654550169298f0
Binary files .DS_Store and .DS_Store differ
blob - f5d5903678900c6e3877a59af8e9f3546bfe684d
blob + deb55e8d233fa1c43d40c1ae0e750c382a8ea62c
--- build.sh
+++ build.sh
@@ -1,8 +1,9 @@
 #!/bin/sh
 set -e
 INC="-Ilib -Isrc"
-CORE="src/ui.c src/audio.c src/aproc.c src/net.c src/update.c lib/microui.c"
-CFLAGS="-Os -Wall -ffunction-sections -fdata-sections"
+CORE="src/audio.c src/aproc.c src/net.c src/update.c"
+CFLAGS="-Os -Wall"
+GC="-ffunction-sections -fdata-sections"
 
 SIGNDIR="${SIGNDIR:-$HOME/.signify}"
 SIGNKEYNAME="${SIGNKEYNAME:-rverma-2026}"
@@ -41,6 +42,7 @@ if [ -f "$SIGNPUB" ]; then
 	fi
 fi
 CFLAGS="$CFLAGS $PUBDEF"
+CXXFLAGS="$CFLAGS -std=c++11 -fno-exceptions -fno-rtti -D_THREAD_SAFE -D_REENTRANT"
 
 sign_artifact() {
 	[ -f "$SIGNKEY" ] || return 0
@@ -73,6 +75,9 @@ if [ "$1" = "win" ]; then
 	PFX=${WINPFX:-./deps-win-$WARCH}
 	[ -d "$PFX" ] || PFX=/tmp/w64
 	[ -d "$PFX" ] || { echo "deps missing: run ./win-deps.sh $WARCH"; exit 1; }
+	[ -f "$PFX/lib/libfltk.a" ] || { echo "fltk missing: run ./win-deps.sh $WARCH"; exit 1; }
+	WCXX=${WINCXX:-$(echo "$WCC" | sed 's/clang$/clang++/; s/gcc-posix$/g++-posix/; s/gcc$/g++/')}
+	command -v "$WCXX" >/dev/null 2>&1 || { echo "no c++ compiler for $TRIPLE ($WCXX)"; exit 1; }
 
 	ICON_OBJ=""
 	if [ -f assets/milna.png ]; then
@@ -107,13 +112,18 @@ if [ "$1" = "win" ]; then
 		echo "icon: no assets/milna.png — exe has default icon"
 	fi
 
+	UIOBJ=/tmp/milna_ui_win.o
+	$WCXX $CXXFLAGS $INC -I"$PFX/include" -D_WIN32_WINNT=0x0A00 \
+		-c src/ui.cpp -o "$UIOBJ"
 	$WCC $CFLAGS $INC -I"$PFX/include" -D_WIN32_WINNT=0x0A00 \
-		src/plat_win.c src/vid_win.c src/cap_win.c src/cam_win.c $CORE \
+		src/vid_win.c src/cap_win.c src/cam_win.c $CORE "$UIOBJ" \
 		$ICON_OBJ \
 		-o milna.exe \
+		"$PFX/lib/libfltk.a" \
 		"$PFX/lib/libopus.a" "$PFX/lib/libspeexdsp.a" "$PFX/lib/libsodium.a" \
 		-lws2_32 -lmfplat -lmfreadwrite -lmf -lmfuuid -lwmcodecdspuuid -lstrmiids -lole32 -loleaut32 \
 		-lgdi32 -luser32 -lwinmm -lavrt \
+		-lcomctl32 -lgdiplus -luuid -lwinspool -lstdc++ \
 		-static -mwindows -Wl,--gc-sections
 	$TRIPLE-strip milna.exe 2>/dev/null || true
 	echo "built ./milna.exe ($(du -h milna.exe | cut -f1))"
@@ -128,15 +138,23 @@ Darwin)
 	OPUS=$(brew --prefix opus 2>/dev/null || echo /opt/homebrew/opt/opus)
 	SPX=$(brew --prefix speexdsp 2>/dev/null || echo /opt/homebrew/opt/speexdsp)
 	SODIUM=$(brew --prefix libsodium 2>/dev/null || echo /opt/homebrew/opt/libsodium)
-	$CC $CFLAGS $INC -I"$OPUS/include" -I"$SPX/include" -I"$SODIUM/include" \
-		src/plat_mac.m src/vid_mac.m src/cap_mac.m src/cam_mac.m $CORE -o $OUT \
+	FLTK=$(brew --prefix fltk 2>/dev/null || echo /opt/homebrew/opt/fltk)
+	[ -f "$FLTK/lib/libfltk.a" ] || { echo "fltk missing: brew install fltk"; exit 1; }
+	CXX=${CXX:-c++}
+	UIOBJ=/tmp/milna_ui.o
+	$CXX $CXXFLAGS $INC -I"$FLTK/include" -c src/ui.cpp -o "$UIOBJ"
+	$CC $CFLAGS $GC $INC -I"$OPUS/include" -I"$SPX/include" -I"$SODIUM/include" \
+		src/vid_mac.m src/cap_mac.m src/cam_mac.m $CORE "$UIOBJ" -o $OUT \
+		"$FLTK/lib/libfltk.a" \
 		"$OPUS/lib/libopus.a" \
 		"$SPX/lib/libspeexdsp.a" \
 		"$SODIUM/lib/libsodium.a" \
+		-lc++ \
 		-framework Cocoa -framework CoreGraphics \
 		-framework CoreAudio -framework AudioToolbox -framework AudioUnit \
 		-framework VideoToolbox -framework CoreMedia -framework CoreVideo \
 		-framework ScreenCaptureKit -framework AVFoundation \
+		-weak_framework UniformTypeIdentifiers \
 		-Wl,-dead_strip
 	strip "$OUT" 2>/dev/null || true
 	echo "built ./$OUT ($(du -h $OUT | cut -f1))"
@@ -204,10 +222,15 @@ PLIST
 	exit 0
 	;;
 Linux)
-	$CC $CFLAGS $INC $(pkg-config --cflags opus speexdsp vpx libsodium) \
-		src/plat_x11.c src/vid.c src/cap_stub.c src/cam_stub.c $CORE -o $OUT \
+	command -v fltk-config >/dev/null 2>&1 || { echo "fltk missing (need fltk-config)"; exit 1; }
+	CXX=${CXX:-g++}
+	UIOBJ=/tmp/milna_ui.o
+	$CXX $CXXFLAGS $INC $(fltk-config --cxxflags) -c src/ui.cpp -o "$UIOBJ"
+	$CC $CFLAGS $GC $INC $(pkg-config --cflags opus speexdsp vpx libsodium) \
+		src/vid.c src/cap_stub.c src/cam_stub.c $CORE "$UIOBJ" -o $OUT \
 		$(pkg-config --libs opus speexdsp vpx libsodium) \
-		-lX11 -lm -lpthread -ldl -Wl,--gc-sections
+		$(fltk-config --ldstaticflags) -lstdc++ \
+		-lm -lpthread -ldl -Wl,--gc-sections
 	;;
 *)
 	echo "unknown platform"; exit 1
blob - 8e9e5d3348746dacfa997357d4164ab152cbd10e (mode 644)
blob + /dev/null
--- lib/atlas.inl
+++ /dev/null
@@ -1,995 +0,0 @@
-
-enum { ATLAS_WHITE = MU_ICON_MAX, ATLAS_FONT };
-enum { ATLAS_WIDTH = 128, ATLAS_HEIGHT = 128 };
-
-
-static unsigned char atlas_texture[ATLAS_WIDTH * ATLAS_HEIGHT] = {
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xc0, 0x20,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x32, 0x0b, 0x00, 0x00, 0x00,
-  0x00, 0x31, 0x35, 0x01, 0x00, 0x00, 0x00, 0x15, 0x35, 0x1d, 0x30, 0x19,
-  0x00, 0x00, 0x0f, 0x35, 0x06, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x2d, 0x00, 0x00, 0x00,
-  0x14, 0x26, 0x00, 0x00, 0x31, 0x2a, 0x00, 0x00, 0x00, 0x10, 0x31, 0x00,
-  0x00, 0x00, 0x00, 0x06, 0x3e, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x06, 0x3e, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x40, 0x0d, 0x00, 0x00, 0x00, 0x00,
-  0x31, 0x35, 0x2f, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x41,
-  0x37, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x21, 0xe0, 0xea, 0x2c, 0x00, 0x00, 0x00, 0x00,
-  0x36, 0xc4, 0xdb, 0xb2, 0xd9, 0xc1, 0x1a, 0x00, 0x00, 0xea, 0xff, 0x39,
-  0x00, 0x00, 0x00, 0x9e, 0xff, 0x88, 0xbe, 0x9c, 0x00, 0x00, 0x72, 0xff,
-  0x48, 0x00, 0x00, 0xbb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x1c, 0xe4, 0xce, 0x8d, 0x00, 0x00, 0xb5, 0x60, 0x00, 0x00,
-  0xea, 0xfa, 0x2c, 0x00, 0x00, 0x4e, 0xeb, 0x00, 0x00, 0x1c, 0x8f, 0xea,
-  0xea, 0xee, 0x92, 0x1f, 0x00, 0x00, 0x1c, 0x8f, 0xea, 0xea, 0xee, 0x92,
-  0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x42, 0xf3, 0xcd, 0xf5, 0x3a, 0x00, 0x00, 0x00, 0xea, 0xf2, 0xef, 0xe5,
-  0x8f, 0x2f, 0x00, 0x00, 0x0f, 0xa0, 0xfe, 0xf2, 0xf1, 0xfa, 0x33, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21,
-  0xe0, 0xea, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x35, 0xdc, 0x33, 0x00, 0x06,
-  0x00, 0x5a, 0xd7, 0x13, 0x00, 0xea, 0xd8, 0x92, 0x00, 0x00, 0x09, 0xf0,
-  0xd9, 0x88, 0x7b, 0xda, 0x00, 0x00, 0xb9, 0xe9, 0x91, 0x00, 0x05, 0xf4,
-  0x03, 0x35, 0x02, 0x32, 0x1f, 0x00, 0x04, 0x37, 0x24, 0x00, 0x5b, 0xa9,
-  0x1b, 0xe7, 0x01, 0x44, 0xd0, 0x02, 0x00, 0x00, 0xea, 0xe4, 0xc3, 0x01,
-  0x00, 0x4e, 0xeb, 0x00, 0x00, 0x8f, 0xd8, 0x42, 0x01, 0x3a, 0xd0, 0x9b,
-  0x00, 0x00, 0x8f, 0xd8, 0x42, 0x01, 0x3a, 0xd0, 0x9b, 0x00, 0x2e, 0x1a,
-  0x00, 0x03, 0x36, 0x19, 0x00, 0x04, 0x36, 0x00, 0xa3, 0xa0, 0x00, 0xb5,
-  0x8d, 0x00, 0x00, 0x00, 0xea, 0x6d, 0x01, 0x3d, 0xac, 0xe5, 0x03, 0x00,
-  0xa1, 0xeb, 0x63, 0x0c, 0x03, 0x2e, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0xe1, 0xeb, 0x2d, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0xd6, 0x38, 0x37, 0xb4, 0xd6, 0xe9, 0x35, 0x9e, 0x5c,
-  0x00, 0xea, 0x87, 0xe8, 0x03, 0x00, 0x56, 0xcc, 0xba, 0x88, 0x38, 0xff,
-  0x1a, 0x09, 0xf7, 0x84, 0xd9, 0x00, 0x39, 0xfe, 0x0e, 0xff, 0xb7, 0xe1,
-  0xf3, 0x94, 0xbc, 0xde, 0xfb, 0x97, 0x73, 0x9b, 0x0b, 0xfb, 0x0b, 0xcf,
-  0x44, 0x01, 0x00, 0x00, 0xea, 0x65, 0xf7, 0x63, 0x00, 0x4e, 0xeb, 0x00,
-  0x01, 0xe0, 0x79, 0x00, 0x00, 0x00, 0x6b, 0xea, 0x03, 0x01, 0xe0, 0x79,
-  0x00, 0x00, 0x00, 0x6b, 0xea, 0x03, 0xb0, 0xa4, 0x00, 0x39, 0xfe, 0xa0,
-  0x00, 0x3d, 0xfd, 0x00, 0x6e, 0xe0, 0x5b, 0xef, 0x41, 0x00, 0x00, 0x00,
-  0xea, 0x6d, 0x00, 0x00, 0x17, 0xfd, 0x47, 0x18, 0xff, 0x8f, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x21, 0xe1, 0xeb, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0xe3,
-  0x00, 0xc5, 0x51, 0x00, 0xd4, 0x37, 0x61, 0x99, 0x00, 0xea, 0x4d, 0xe9,
-  0x45, 0x00, 0xb2, 0x70, 0xbf, 0x88, 0x04, 0xf0, 0x59, 0x48, 0xe9, 0x18,
-  0xfd, 0x23, 0x77, 0xd4, 0x0e, 0xff, 0x9f, 0x02, 0x54, 0xff, 0x86, 0x00,
-  0x69, 0xf0, 0x39, 0xd1, 0x61, 0xca, 0x60, 0xb6, 0x6f, 0xe3, 0x98, 0x00,
-  0xea, 0x4a, 0x7f, 0xec, 0x15, 0x4e, 0xeb, 0x00, 0x32, 0xff, 0x36, 0x00,
-  0x00, 0x00, 0x29, 0xff, 0x3e, 0x32, 0xff, 0x36, 0x00, 0x00, 0x00, 0x29,
-  0xff, 0x3f, 0x68, 0xe6, 0x00, 0x83, 0xaf, 0xe7, 0x01, 0x80, 0xc9, 0x00,
-  0x18, 0xf4, 0xff, 0x53, 0x00, 0x15, 0x3b, 0x00, 0xea, 0x6d, 0x00, 0x00,
-  0x00, 0xc5, 0xa2, 0x41, 0xff, 0x39, 0x00, 0x40, 0x73, 0x73, 0x35, 0x00,
-  0x00, 0x00, 0x3d, 0xed, 0x45, 0x00, 0x00, 0x00, 0x22, 0xe1, 0xeb, 0x2d,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4f, 0xb3, 0x12, 0xf9, 0x04, 0x03,
-  0xef, 0x2c, 0x6a, 0x94, 0x00, 0xea, 0x4d, 0x95, 0x9e, 0x14, 0xf6, 0x18,
-  0xc1, 0x88, 0x00, 0xb2, 0x96, 0x8e, 0xa4, 0x00, 0xcb, 0x6a, 0xb5, 0x92,
-  0x0e, 0xff, 0x59, 0x00, 0x27, 0xff, 0x3d, 0x00, 0x3a, 0xff, 0x04, 0x71,
-  0xae, 0x40, 0xe0, 0x2e, 0xee, 0x1e, 0xd5, 0x00, 0xea, 0x4d, 0x08, 0xd9,
-  0xa0, 0x4b, 0xeb, 0x00, 0x20, 0xfe, 0x45, 0x00, 0x00, 0x00, 0x37, 0xff,
-  0x2c, 0x20, 0xfe, 0x45, 0x00, 0x00, 0x00, 0x37, 0xff, 0x3b, 0x21, 0xff,
-  0x29, 0xcc, 0x4a, 0xe9, 0x30, 0xc3, 0x81, 0x14, 0xdf, 0xab, 0xbd, 0xcd,
-  0x14, 0x9c, 0xb5, 0x00, 0xea, 0x6d, 0x00, 0x00, 0x00, 0xdc, 0xa6, 0x20,
-  0xfe, 0x42, 0x00, 0x63, 0xb2, 0xf4, 0x76, 0x00, 0x00, 0x00, 0x13, 0xd0,
-  0xf6, 0x45, 0x00, 0x22, 0xe1, 0xeb, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x3f, 0xe7, 0x03, 0xd7, 0x5e, 0x75, 0xf7, 0x8d, 0xc7, 0x4a,
-  0x00, 0xea, 0x4d, 0x3c, 0xf0, 0x71, 0xb7, 0x00, 0xc1, 0x88, 0x00, 0x70,
-  0xc9, 0xcb, 0x5d, 0x00, 0x83, 0xa2, 0xe9, 0x4f, 0x0e, 0xff, 0x43, 0x00,
-  0x26, 0xff, 0x2d, 0x00, 0x39, 0xff, 0x00, 0x00, 0x00, 0x7c, 0x9a, 0x1f,
-  0xf1, 0x00, 0xaf, 0x00, 0xea, 0x4d, 0x00, 0x44, 0xfe, 0x83, 0xeb, 0x00,
-  0x00, 0xcd, 0x89, 0x00, 0x00, 0x00, 0x7b, 0xd9, 0x00, 0x00, 0xcd, 0x89,
-  0x00, 0x00, 0x00, 0x7b, 0xf8, 0x08, 0x00, 0xd8, 0x75, 0xf8, 0x0d, 0xa7,
-  0x79, 0xf7, 0x39, 0x5b, 0xfc, 0x0a, 0x09, 0xba, 0xd4, 0xf6, 0x39, 0x00,
-  0xea, 0x6d, 0x00, 0x00, 0x31, 0xff, 0x75, 0x00, 0xcc, 0x8f, 0x00, 0x00,
-  0x00, 0xdb, 0x76, 0x00, 0x00, 0x00, 0x00, 0x13, 0xd0, 0xf6, 0x63, 0xe1,
-  0xeb, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0xf3,
-  0x44, 0x2c, 0x96, 0x87, 0x29, 0xa3, 0x64, 0x00, 0x00, 0xea, 0x4d, 0x01,
-  0xe1, 0xf3, 0x5b, 0x00, 0xc1, 0x88, 0x00, 0x2e, 0xf7, 0xf5, 0x17, 0x00,
-  0x3a, 0xec, 0xfc, 0x10, 0x0e, 0xff, 0x43, 0x00, 0x26, 0xff, 0x2d, 0x00,
-  0x39, 0xff, 0x00, 0x00, 0x17, 0xe6, 0x19, 0x02, 0xee, 0x13, 0xd0, 0x00,
-  0xea, 0x4d, 0x00, 0x00, 0xa5, 0xf6, 0xeb, 0x00, 0x00, 0x7d, 0xec, 0x7a,
-  0x24, 0x73, 0xe7, 0x87, 0x00, 0x00, 0x7d, 0xec, 0x7a, 0x24, 0x73, 0xe7,
-  0x9c, 0x00, 0x00, 0x91, 0xda, 0xbd, 0x00, 0x61, 0xd9, 0xed, 0x03, 0x2b,
-  0xfe, 0x67, 0x1d, 0x70, 0xfc, 0xf0, 0x1a, 0x00, 0xea, 0x7f, 0x31, 0x81,
-  0xdc, 0xdc, 0x0a, 0x00, 0x79, 0xef, 0x83, 0x23, 0x1c, 0xe1, 0x76, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x13, 0xd0, 0xff, 0xec, 0x2e, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0xdc, 0x78, 0x20, 0x0d,
-  0x3c, 0x3b, 0x00, 0x00, 0x00, 0xea, 0x4d, 0x00, 0x8a, 0xf3, 0x0b, 0x00,
-  0xc1, 0x88, 0x00, 0x01, 0xea, 0xce, 0x00, 0x00, 0x03, 0xed, 0xc9, 0x00,
-  0x0e, 0xff, 0x43, 0x00, 0x26, 0xff, 0x2d, 0x00, 0x39, 0xff, 0x00, 0x00,
-  0x98, 0x7e, 0x00, 0x00, 0x78, 0xe7, 0xb0, 0x00, 0xea, 0x4d, 0x00, 0x00,
-  0x18, 0xef, 0xeb, 0x00, 0x00, 0x06, 0x54, 0xb6, 0xfb, 0xbb, 0x57, 0x07,
-  0x00, 0x00, 0x06, 0x54, 0xb6, 0xfc, 0xff, 0x8d, 0x01, 0x00, 0x00, 0x49,
-  0xff, 0x74, 0x00, 0x1b, 0xfe, 0xa8, 0x00, 0x00, 0x7f, 0xde, 0xff, 0xe7,
-  0x72, 0xb1, 0xdb, 0x00, 0xea, 0xff, 0xf8, 0xd6, 0x92, 0x13, 0x00, 0x00,
-  0x06, 0x52, 0xb4, 0xfb, 0xff, 0xe5, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x13, 0xbc, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x44, 0xb7, 0xde, 0xdb, 0xad, 0x50, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x1b, 0xda, 0xbb, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f,
-  0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0xe6,
-  0xa0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x17, 0x00, 0x00, 0x00,
-  0x2a, 0x1e, 0x00, 0x35, 0x13, 0x00, 0x00, 0x00, 0x2c, 0x1c, 0x00, 0x00,
-  0x0c, 0x27, 0x00, 0x29, 0x09, 0x00, 0x00, 0x00, 0x3c, 0x24, 0x00, 0x00,
-  0x00, 0x31, 0x35, 0x30, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x44,
-  0x1f, 0x00, 0x00, 0x31, 0x17, 0x00, 0x00, 0x13, 0x35, 0x00, 0x31, 0x35,
-  0x2b, 0x02, 0x00, 0x00, 0x00, 0x31, 0x35, 0x2a, 0x02, 0x00, 0x00, 0x33,
-  0x16, 0x00, 0x00, 0x00, 0x0f, 0x35, 0x27, 0x28, 0x00, 0x00, 0x00, 0x1f,
-  0x2e, 0x0b, 0xbc, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x13, 0xbc, 0x2a, 0x0b, 0xbc, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0xea, 0x6d, 0x00, 0x00, 0x00, 0xc9, 0x8e, 0x00, 0xfc,
-  0x5b, 0x00, 0x00, 0x00, 0xd2, 0x84, 0x00, 0x00, 0x59, 0xa9, 0x00, 0xea,
-  0x0f, 0x00, 0x00, 0x34, 0xfe, 0xce, 0x00, 0x00, 0x00, 0xea, 0xf2, 0xed,
-  0xfa, 0xce, 0x1a, 0x00, 0x09, 0xb5, 0xfd, 0xe7, 0xf5, 0xaf, 0x00, 0xea,
-  0x6d, 0x00, 0x0c, 0xcf, 0xa7, 0x00, 0xea, 0xf2, 0xf3, 0xea, 0x94, 0x00,
-  0x00, 0xea, 0xf4, 0xf6, 0xec, 0x9d, 0x00, 0xc0, 0x9e, 0x00, 0x00, 0x00,
-  0x7a, 0xe5, 0x58, 0xf8, 0x26, 0x00, 0x0d, 0xe4, 0x7a, 0x0e, 0xff, 0x43,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0xff, 0x39, 0x0e,
-  0xff, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea,
-  0x6d, 0x00, 0x00, 0x00, 0xc9, 0x8e, 0x00, 0xfc, 0x5b, 0x00, 0x00, 0x00,
-  0xd2, 0x84, 0x00, 0x00, 0x89, 0x79, 0x1c, 0xdd, 0x00, 0x00, 0x00, 0x93,
-  0xad, 0xf9, 0x2e, 0x00, 0x00, 0xea, 0x6d, 0x00, 0x19, 0xe3, 0x8c, 0x00,
-  0xab, 0xe1, 0x4f, 0x02, 0x07, 0x1f, 0x00, 0xea, 0x6d, 0x03, 0xb2, 0xc6,
-  0x08, 0x00, 0xea, 0x6d, 0x03, 0x4d, 0xff, 0x33, 0x00, 0xea, 0x6d, 0x03,
-  0x49, 0xff, 0x3a, 0x67, 0xee, 0x06, 0x00, 0x00, 0xd0, 0x8e, 0x00, 0xb3,
-  0xbb, 0x00, 0x8e, 0xcf, 0x05, 0x0e, 0xff, 0x41, 0x12, 0x25, 0x00, 0x00,
-  0x00, 0x00, 0x18, 0x20, 0x18, 0xff, 0x39, 0x0e, 0xff, 0x41, 0x2e, 0x33,
-  0x01, 0x00, 0x03, 0x35, 0x01, 0x2e, 0x34, 0x01, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x04, 0x39, 0x05, 0x00, 0x00, 0x03, 0x36, 0x02, 0x11, 0x25,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x6d, 0x00, 0x00, 0x00,
-  0xc9, 0x8e, 0x00, 0xfc, 0x5b, 0x00, 0x00, 0x00, 0xd2, 0x84, 0x41, 0xd3,
-  0xf0, 0xe3, 0xdd, 0xf4, 0xd3, 0x00, 0x06, 0xec, 0x52, 0xb4, 0x8d, 0x00,
-  0x00, 0xea, 0x6d, 0x00, 0x06, 0xdd, 0x86, 0x0b, 0xfc, 0x81, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0xea, 0x6d, 0x8d, 0xdd, 0x15, 0x00, 0x00, 0xea, 0x6d,
-  0x00, 0x07, 0xf3, 0x64, 0x00, 0xea, 0x6d, 0x00, 0x0a, 0xf4, 0x69, 0x13,
-  0xfa, 0x4a, 0x00, 0x27, 0xff, 0x34, 0x00, 0x1c, 0xf1, 0x86, 0xf7, 0x33,
-  0x00, 0x0e, 0xff, 0xaa, 0xd3, 0xf8, 0xab, 0x01, 0x00, 0x87, 0xf6, 0xda,
-  0xa5, 0xff, 0x39, 0x0e, 0xff, 0xc0, 0xe5, 0xf2, 0xd5, 0x03, 0x0e, 0xff,
-  0xb4, 0xe5, 0xf2, 0xd6, 0x04, 0x00, 0x00, 0x00, 0x00, 0x6d, 0xe7, 0xdd,
-  0xe7, 0x6d, 0x00, 0x0e, 0xff, 0x98, 0xd2, 0xf8, 0xab, 0x01, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0xea, 0xe9, 0xd9, 0xd9, 0xd9, 0xf7, 0x8e, 0x00, 0xfc,
-  0x5b, 0x00, 0x00, 0x00, 0xd2, 0x84, 0x09, 0x1d, 0xec, 0x2e, 0x93, 0x87,
-  0x1d, 0x00, 0x52, 0xf1, 0x09, 0x5d, 0xe8, 0x04, 0x00, 0xea, 0xe4, 0xd3,
-  0xf6, 0xb1, 0x0d, 0x3c, 0xff, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea,
-  0xc9, 0xff, 0x60, 0x00, 0x00, 0x00, 0xea, 0x8a, 0x49, 0x9e, 0xf1, 0x0d,
-  0x00, 0xea, 0xa6, 0x71, 0xbb, 0xe1, 0x1a, 0x00, 0xb4, 0xa0, 0x00, 0x7d,
-  0xda, 0x00, 0x00, 0x00, 0x74, 0xff, 0x93, 0x00, 0x00, 0x0e, 0xff, 0x9c,
-  0x02, 0x2a, 0xff, 0x32, 0x0e, 0xfa, 0x5a, 0x00, 0x6f, 0xff, 0x39, 0x0e,
-  0xff, 0xa6, 0x04, 0x1e, 0xff, 0x42, 0x0e, 0xff, 0xa5, 0x04, 0x1e, 0xff,
-  0x42, 0x00, 0x00, 0x00, 0x07, 0xf1, 0x59, 0x00, 0x55, 0xf2, 0x09, 0x0e,
-  0xff, 0x99, 0x01, 0x2d, 0xff, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea,
-  0x98, 0x4c, 0x4c, 0x4c, 0xd9, 0x8e, 0x00, 0xfc, 0x5b, 0x00, 0x00, 0x00,
-  0xd2, 0x84, 0x37, 0x58, 0xef, 0x4a, 0xc6, 0x83, 0x38, 0x00, 0xb1, 0xd5,
-  0x6f, 0x7d, 0xff, 0x4c, 0x00, 0xea, 0x94, 0x47, 0x70, 0xe7, 0x83, 0x22,
-  0xff, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0xd2, 0xaf, 0xe2, 0x10,
-  0x00, 0x00, 0xea, 0xf5, 0xda, 0x94, 0x3e, 0x00, 0x00, 0xea, 0xd6, 0xc4,
-  0xfd, 0x27, 0x00, 0x00, 0x5b, 0xef, 0x06, 0xd3, 0x81, 0x00, 0x00, 0x01,
-  0xbc, 0xeb, 0xd2, 0x06, 0x00, 0x0e, 0xff, 0x56, 0x00, 0x00, 0xdd, 0x77,
-  0x50, 0xfb, 0x0c, 0x00, 0x23, 0xff, 0x39, 0x0e, 0xff, 0x5c, 0x00, 0x00,
-  0xeb, 0x68, 0x0e, 0xff, 0x5c, 0x00, 0x00, 0xeb, 0x68, 0x00, 0x00, 0x00,
-  0x4c, 0xfb, 0x0b, 0x00, 0x08, 0xf8, 0x51, 0x0e, 0xff, 0x52, 0x00, 0x00,
-  0xde, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x6d, 0x00, 0x00, 0x00,
-  0xc9, 0x8e, 0x00, 0xd3, 0x85, 0x00, 0x00, 0x09, 0xf4, 0x6b, 0x7b, 0xc7,
-  0xdf, 0xa7, 0xfd, 0xa9, 0x7f, 0x16, 0xfa, 0xc4, 0xb8, 0xb8, 0xe2, 0xab,
-  0x00, 0xea, 0x6d, 0x00, 0x00, 0x8e, 0xdf, 0x00, 0xd2, 0x89, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0xea, 0x6d, 0x0f, 0xe0, 0xa1, 0x00, 0x00, 0xea, 0x6d,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x6d, 0x01, 0xc6, 0xa5, 0x00, 0x00,
-  0x0c, 0xf5, 0x69, 0xff, 0x28, 0x00, 0x00, 0x60, 0xec, 0x21, 0xe4, 0x80,
-  0x00, 0x0e, 0xff, 0x65, 0x00, 0x01, 0xea, 0x67, 0x43, 0xfe, 0x12, 0x00,
-  0x25, 0xff, 0x39, 0x0e, 0xff, 0x43, 0x00, 0x00, 0xea, 0x68, 0x0e, 0xff,
-  0x43, 0x00, 0x00, 0xea, 0x68, 0x00, 0x00, 0x00, 0x4c, 0xfe, 0x16, 0x00,
-  0x11, 0xfd, 0x40, 0x0e, 0xff, 0x65, 0x00, 0x01, 0xeb, 0x67, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0xea, 0x6d, 0x00, 0x00, 0x00, 0xc9, 0x8e, 0x00, 0x72,
-  0xdf, 0x4b, 0x23, 0x8c, 0xfa, 0x28, 0x00, 0x7c, 0x81, 0x1a, 0xe5, 0x00,
-  0x00, 0x70, 0xeb, 0x05, 0x00, 0x00, 0x5c, 0xf8, 0x00, 0xea, 0x7f, 0x22,
-  0x4a, 0xdd, 0x95, 0x00, 0x84, 0xed, 0x7c, 0x1d, 0x24, 0x32, 0x00, 0xea,
-  0x6d, 0x00, 0x40, 0xfd, 0x52, 0x00, 0xea, 0x6d, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0xea, 0x6d, 0x00, 0x31, 0xfc, 0x49, 0x00, 0x00, 0xa8, 0xe3, 0xcd,
-  0x00, 0x00, 0x17, 0xed, 0x62, 0x00, 0x54, 0xf9, 0x2d, 0x0e, 0xff, 0xbc,
-  0x1d, 0x5f, 0xff, 0x21, 0x07, 0xf5, 0x7e, 0x11, 0x8d, 0xff, 0x39, 0x0e,
-  0xff, 0x43, 0x00, 0x00, 0xea, 0x68, 0x0e, 0xff, 0x43, 0x00, 0x00, 0xea,
-  0x68, 0x00, 0x00, 0x00, 0x14, 0xf2, 0x88, 0x15, 0x82, 0xe8, 0x03, 0x0e,
-  0xff, 0xbc, 0x1e, 0x61, 0xff, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea,
-  0x6d, 0x00, 0x00, 0x00, 0xc9, 0x8e, 0x00, 0x10, 0x85, 0xdc, 0xfe, 0xd9,
-  0x4c, 0x00, 0x00, 0xaa, 0x50, 0x4b, 0xb4, 0x00, 0x00, 0xcf, 0x97, 0x00,
-  0x00, 0x00, 0x0c, 0xf4, 0x00, 0xea, 0xff, 0xff, 0xdd, 0x93, 0x1c, 0x00,
-  0x08, 0x5c, 0xc0, 0xfe, 0xed, 0x7b, 0x00, 0xea, 0x6d, 0x00, 0x00, 0x8d,
-  0xea, 0x00, 0xea, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x6d, 0x00,
-  0x00, 0x95, 0xe0, 0x00, 0x00, 0x4f, 0xff, 0x74, 0x00, 0x00, 0xa8, 0xc3,
-  0x01, 0x00, 0x00, 0xb7, 0xca, 0x0e, 0xfd, 0x6f, 0xd4, 0xe5, 0x72, 0x00,
-  0x00, 0x5c, 0xd9, 0xdb, 0x63, 0xef, 0x39, 0x0e, 0xff, 0x43, 0x00, 0x00,
-  0xea, 0x68, 0x0e, 0xff, 0x43, 0x00, 0x00, 0xea, 0x68, 0x00, 0x00, 0x00,
-  0x00, 0x43, 0xdb, 0xfc, 0xb7, 0x46, 0x00, 0x0e, 0xff, 0x8c, 0xd4, 0xe4,
-  0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x02, 0x04,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
-  0x00, 0x00, 0x00, 0x0e, 0xff, 0x43, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e,
-  0xff, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xc2, 0x33, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x79,
-  0x00, 0x00, 0x00, 0x00, 0x58, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x0a, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2e,
-  0x14, 0x00, 0x00, 0x04, 0x32, 0x3b, 0x03, 0x00, 0x00, 0x00, 0x21, 0x38,
-  0x04, 0x00, 0x00, 0x00, 0x00, 0x07, 0x3b, 0x0f, 0x00, 0x2e, 0x35, 0x35,
-  0x35, 0x16, 0x00, 0x00, 0x03, 0x26, 0x49, 0x1f, 0x1e, 0x33, 0x33, 0x33,
-  0x33, 0x33, 0x00, 0x00, 0x18, 0x3a, 0x05, 0x00, 0x00, 0x00, 0x14, 0x37,
-  0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f,
-  0x00, 0x31, 0x35, 0x35, 0x35, 0x27, 0x00, 0x31, 0x35, 0x35, 0x35, 0x26,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0xa6, 0xea, 0x9e, 0x53, 0x0f, 0x03,
-  0x5f, 0xa3, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6d,
-  0xe9, 0xea, 0xb4, 0x23, 0x00, 0x07, 0x98, 0xff, 0x61, 0x00, 0x27, 0xd4,
-  0xee, 0xe8, 0xe8, 0x2f, 0x32, 0xcf, 0xdd, 0xe5, 0xef, 0x33, 0x00, 0x00,
-  0x00, 0x89, 0xff, 0x41, 0x00, 0xea, 0xf9, 0xf6, 0xf6, 0x64, 0x00, 0x25,
-  0xcf, 0xfe, 0xd1, 0x6c, 0x90, 0xf6, 0xf6, 0xf6, 0xf9, 0xff, 0x00, 0xa0,
-  0xea, 0xca, 0xee, 0x3e, 0x00, 0x9e, 0xf3, 0xe2, 0xe3, 0x1c, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xfc, 0x00, 0xea, 0xf4, 0xec,
-  0xec, 0xac, 0x00, 0xea, 0xf4, 0xec, 0xec, 0xa9, 0x00, 0x00, 0x00, 0x00,
-  0x17, 0x3a, 0x05, 0x30, 0x0c, 0x06, 0x36, 0x0c, 0x00, 0x00, 0x34, 0x13,
-  0x16, 0xf6, 0x91, 0xc3, 0x79, 0x5b, 0x80, 0xed, 0xc9, 0xd9, 0xdb, 0xcb,
-  0x00, 0x00, 0x31, 0xa9, 0x00, 0x00, 0x06, 0xf7, 0x57, 0x0c, 0xbd, 0x88,
-  0x05, 0xc6, 0xb6, 0xed, 0x61, 0x00, 0x08, 0x64, 0x05, 0x02, 0xbf, 0x9d,
-  0x04, 0x3a, 0x00, 0x01, 0xbe, 0xa2, 0x00, 0x00, 0x3d, 0xd1, 0xfc, 0x41,
-  0x02, 0xfc, 0x41, 0x00, 0x00, 0x00, 0x00, 0xc9, 0xa9, 0x1f, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x90, 0xbf, 0x23, 0xfe, 0x27, 0x00, 0x9b, 0xaf,
-  0x2b, 0xfc, 0x21, 0x00, 0xa9, 0xb1, 0x00, 0x00, 0x00, 0x01, 0x50, 0xce,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0x98, 0x1e, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x7c, 0xc1, 0x00, 0xea, 0x6d, 0x00, 0x00, 0x00, 0x00, 0xea,
-  0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0xf6, 0xdf, 0xbf, 0xf9,
-  0x39, 0x1d, 0xff, 0x38, 0x00, 0x00, 0xf6, 0x5c, 0x30, 0xff, 0x55, 0xa7,
-  0x00, 0x00, 0x06, 0x21, 0xdd, 0xeb, 0x45, 0x0c, 0x00, 0x00, 0x3a, 0xc8,
-  0x00, 0x00, 0x35, 0xfd, 0x09, 0x00, 0x77, 0xc1, 0x00, 0x44, 0x03, 0xee,
-  0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0xa9, 0x00, 0x00, 0x00, 0x09,
-  0xdb, 0x79, 0x00, 0x0d, 0xdc, 0x43, 0xff, 0x41, 0x13, 0xff, 0x38, 0x0f,
-  0x00, 0x00, 0x36, 0xff, 0x34, 0x26, 0x05, 0x00, 0x00, 0x00, 0x00, 0x12,
-  0xf3, 0x4e, 0x0a, 0xf1, 0x6b, 0x16, 0xce, 0x80, 0x6e, 0xda, 0x00, 0x00,
-  0x58, 0xe9, 0x00, 0x01, 0x50, 0xce, 0xbc, 0x44, 0x49, 0xab, 0xab, 0xab,
-  0xab, 0xa8, 0x0f, 0x78, 0xe1, 0x98, 0x1f, 0x00, 0x00, 0x01, 0xdb, 0x62,
-  0x00, 0xea, 0x6d, 0x00, 0x00, 0x00, 0x00, 0xea, 0x6a, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x0e, 0xfa, 0x58, 0x00, 0x6f, 0xff, 0x39, 0x1d, 0xff, 0x38,
-  0x00, 0x00, 0xf6, 0x5c, 0x00, 0x90, 0xfc, 0xdd, 0x46, 0x00, 0x00, 0x8b,
-  0xbb, 0x75, 0xce, 0x06, 0x41, 0x8d, 0xa7, 0xe7, 0x8d, 0x8d, 0x6b, 0xe3,
-  0x00, 0x00, 0x52, 0xf5, 0x00, 0x00, 0x00, 0xf0, 0x61, 0x00, 0x00, 0x00,
-  0x00, 0x31, 0xf8, 0x3f, 0x00, 0x33, 0xcb, 0xf8, 0x85, 0x05, 0x00, 0x9c,
-  0x8e, 0x08, 0xff, 0x41, 0x1f, 0xfd, 0xfe, 0xfa, 0xb8, 0x2d, 0x5b, 0xf2,
-  0xc9, 0xe6, 0xe7, 0x5a, 0x00, 0x00, 0x00, 0x7c, 0xd9, 0x02, 0x00, 0x37,
-  0xfb, 0xf8, 0xa4, 0x02, 0x2e, 0xfd, 0x49, 0x26, 0xbd, 0xfe, 0x35, 0xce,
-  0xac, 0x34, 0x00, 0x00, 0x24, 0x55, 0x55, 0x55, 0x55, 0x53, 0x00, 0x00,
-  0x08, 0x68, 0xd8, 0x98, 0x00, 0x3b, 0xf4, 0x0d, 0x00, 0xea, 0xe8, 0xd6,
-  0xd6, 0x6f, 0x00, 0xea, 0xb6, 0x82, 0x82, 0x42, 0x00, 0x00, 0x4f, 0xfb,
-  0x0c, 0x00, 0x23, 0xff, 0x39, 0x1d, 0xff, 0x38, 0x00, 0x00, 0xf6, 0x5c,
-  0x00, 0x00, 0x3a, 0xe4, 0xf4, 0x89, 0x00, 0x24, 0x30, 0x0c, 0x48, 0x00,
-  0x34, 0x72, 0x92, 0xe1, 0x72, 0x72, 0x5e, 0xea, 0x00, 0x00, 0x59, 0xee,
-  0x00, 0x00, 0x00, 0xf0, 0x61, 0x00, 0x00, 0x00, 0x22, 0xe6, 0x76, 0x00,
-  0x00, 0x15, 0x57, 0x82, 0xe7, 0x79, 0x4d, 0xd5, 0x09, 0x08, 0xff, 0x41,
-  0x00, 0x13, 0x03, 0x2c, 0xcd, 0xa5, 0x6f, 0xfc, 0x30, 0x01, 0x81, 0xd3,
-  0x00, 0x00, 0x09, 0xe9, 0x6a, 0x00, 0x0f, 0xc5, 0xb2, 0x78, 0xed, 0x63,
-  0x00, 0x7f, 0xe4, 0xe4, 0xa5, 0xeb, 0x34, 0xc4, 0xcd, 0x64, 0x0a, 0x00,
-  0x2e, 0x6c, 0x6c, 0x6c, 0x6c, 0x6a, 0x00, 0x00, 0x29, 0x92, 0xe4, 0x93,
-  0x00, 0x9a, 0xa3, 0x00, 0x00, 0xea, 0x96, 0x48, 0x48, 0x25, 0x00, 0xea,
-  0xc8, 0xa1, 0xa1, 0x52, 0x00, 0x00, 0x43, 0xfe, 0x12, 0x00, 0x23, 0xff,
-  0x39, 0x1c, 0xff, 0x3d, 0x00, 0x1c, 0xff, 0x5c, 0x06, 0x00, 0x17, 0xa7,
-  0x8a, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0xc8,
-  0x00, 0x00, 0x25, 0xff, 0x0f, 0x00, 0x7f, 0xb8, 0x00, 0x00, 0x00, 0xf0,
-  0x61, 0x00, 0x00, 0x20, 0xe0, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x84, 0xe4, 0xc4, 0xf0, 0xe2, 0xe3, 0xff, 0xe9, 0x00, 0x00, 0x00, 0x00,
-  0x7b, 0xdd, 0x4b, 0xf3, 0x07, 0x00, 0x47, 0xf8, 0x00, 0x00, 0x68, 0xed,
-  0x0b, 0x00, 0x5f, 0xe3, 0x02, 0x00, 0x5d, 0xeb, 0x00, 0x00, 0x01, 0x00,
-  0xaa, 0xbd, 0x00, 0x00, 0x35, 0xa5, 0xea, 0x90, 0x3f, 0x92, 0x92, 0x92,
-  0x92, 0x90, 0x34, 0xbe, 0xe0, 0x74, 0x10, 0x00, 0x09, 0xf0, 0x44, 0x00,
-  0x00, 0xea, 0x6d, 0x00, 0x00, 0x00, 0x00, 0xea, 0x6a, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x07, 0xf5, 0x7b, 0x10, 0x8b, 0xff, 0x39, 0x02, 0xe4, 0x8c,
-  0x10, 0x86, 0xff, 0x5c, 0x60, 0xd2, 0xa9, 0xea, 0xfd, 0x77, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x91, 0x00, 0x00, 0x01, 0xea,
-  0x87, 0x2f, 0xd5, 0x81, 0x00, 0x00, 0x00, 0xf0, 0x61, 0x00, 0x1f, 0xdf,
-  0x9f, 0x29, 0x29, 0x29, 0x36, 0x28, 0x04, 0x29, 0xcd, 0x9c, 0x2a, 0x35,
-  0x35, 0x3b, 0xff, 0x69, 0x28, 0x37, 0x09, 0x32, 0xd3, 0x81, 0x16, 0xef,
-  0x7f, 0x18, 0xa7, 0xaf, 0x00, 0x03, 0xdb, 0x87, 0x00, 0x00, 0x3e, 0xf5,
-  0x26, 0x05, 0x88, 0xc7, 0x00, 0x06, 0x1b, 0x75, 0xf4, 0x48, 0x00, 0x00,
-  0x00, 0x00, 0x1b, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4d, 0x56,
-  0x04, 0x00, 0x00, 0x00, 0x5a, 0xe2, 0x02, 0x00, 0x00, 0xea, 0x81, 0x24,
-  0x24, 0x1a, 0x00, 0xea, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c,
-  0xda, 0xda, 0x71, 0xff, 0x39, 0x00, 0x68, 0xe4, 0xfa, 0xa6, 0xd0, 0x5c,
-  0x09, 0x38, 0x74, 0xc9, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0xb5, 0xef, 0x82, 0x10,
-  0x00, 0x00, 0x00, 0xf0, 0x61, 0x00, 0x7e, 0xff, 0xff, 0xff, 0xff, 0xff,
-  0x5f, 0xdc, 0xfc, 0xe9, 0xa6, 0x22, 0x00, 0x00, 0x00, 0x08, 0xff, 0x41,
-  0x3e, 0xd7, 0xfd, 0xe4, 0x95, 0x17, 0x00, 0x47, 0xdc, 0xfb, 0xb2, 0x30,
-  0x00, 0x55, 0xfa, 0x1b, 0x00, 0x00, 0x00, 0x90, 0xe3, 0xf2, 0xbf, 0x3c,
-  0x00, 0xec, 0xfc, 0xde, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0xb9, 0x85, 0x00, 0x00, 0x00, 0xea, 0xff, 0xff, 0xff, 0xba, 0x00, 0xea,
-  0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x04, 0x1a, 0xff,
-  0x39, 0x00, 0x00, 0x02, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x70,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x01,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x01,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0f, 0x0d, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0xff, 0x39, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x14, 0xc2, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x17, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x15, 0x43, 0x14, 0x00, 0x2d, 0x35, 0x35, 0x35,
-  0x35, 0x35, 0x32, 0x1b, 0x00, 0x00, 0x00, 0x26, 0x1c, 0x35, 0x35, 0x35,
-  0x35, 0x35, 0x00, 0x00, 0x1d, 0x17, 0x00, 0x00, 0x00, 0x00, 0x8c, 0xa9,
-  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xbc, 0x2f, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x7a,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x35, 0x35,
-  0x35, 0x2b, 0x0d, 0x35, 0x04, 0x35, 0x0e, 0x00, 0x12, 0x41, 0x0e, 0x00,
-  0x2f, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x6d, 0x00, 0x00, 0x00, 0x00, 0xa1,
-  0xf9, 0xe4, 0xfe, 0x7c, 0xc9, 0xf0, 0xf6, 0xfe, 0xf0, 0xf0, 0x9e, 0xd1,
-  0x02, 0x00, 0x14, 0xf1, 0x7c, 0xf0, 0xf0, 0xf0, 0xf8, 0xff, 0x00, 0x00,
-  0xbd, 0xae, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xb2, 0x74, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x0e, 0xff, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xaa, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x60, 0x60, 0x60, 0xaa, 0x33, 0xfc,
-  0x02, 0xf9, 0x36, 0x9f, 0xeb, 0xd6, 0xf7, 0x5d, 0xa5, 0x99, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0xea, 0x6d, 0x00, 0x00, 0x00, 0x2b, 0xfe, 0x2d, 0x00, 0x1a, 0x15,
-  0x00, 0x00, 0x6c, 0xea, 0x00, 0x00, 0x1d, 0xf7, 0x58, 0x00, 0x8a, 0xda,
-  0x00, 0x00, 0x00, 0x08, 0xd5, 0x9c, 0x00, 0x35, 0xc9, 0xd2, 0x34, 0x00,
-  0x00, 0x00, 0x00, 0x01, 0x06, 0x00, 0x00, 0x03, 0x35, 0x42, 0x09, 0x00,
-  0x00, 0x00, 0x07, 0x38, 0x03, 0x00, 0x00, 0x00, 0x28, 0x48, 0x36, 0x36,
-  0x0e, 0xff, 0x40, 0x00, 0x0e, 0x36, 0x34, 0x17, 0x00, 0x00, 0x0f, 0x36,
-  0x25, 0x2c, 0x00, 0x00, 0x1a, 0x35, 0x33, 0x1a, 0x00, 0x00, 0x10, 0x36,
-  0x00, 0x00, 0x58, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x95, 0x00, 0x00, 0x00, 0x95, 0x1f, 0xea, 0x00, 0xe6, 0x22, 0x1c,
-  0x04, 0x00, 0x7a, 0xcf, 0x46, 0xef, 0x09, 0x00, 0x00, 0x00, 0x04, 0x3e,
-  0x21, 0x00, 0x04, 0x36, 0x34, 0x06, 0x00, 0x00, 0x00, 0xea, 0x6d, 0x00,
-  0x00, 0x00, 0x24, 0xff, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0xea,
-  0x00, 0x00, 0x00, 0x8b, 0xd9, 0x1d, 0xf5, 0x54, 0x00, 0x00, 0x00, 0x8b,
-  0xe0, 0x0e, 0x00, 0xac, 0x58, 0x54, 0xb7, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0xa7, 0xd5, 0xc6, 0xf3, 0x42, 0x00, 0x6d, 0xe1, 0xc7,
-  0xe1, 0x49, 0x02, 0xca, 0xbd, 0xb6, 0xff, 0xb2, 0x0e, 0xff, 0x40, 0x0c,
-  0xc7, 0xae, 0xba, 0xa4, 0x00, 0x00, 0x7f, 0xdf, 0x46, 0xfb, 0x38, 0x08,
-  0xd8, 0x97, 0xb6, 0xb1, 0x00, 0x00, 0x81, 0xe2, 0x00, 0x00, 0x58, 0xaa,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00,
-  0x00, 0x95, 0x0a, 0xa3, 0x00, 0xa1, 0x0d, 0x00, 0x00, 0x02, 0xa9, 0xb9,
-  0x03, 0xe3, 0x58, 0x00, 0x00, 0x70, 0xe7, 0xdd, 0xf7, 0x11, 0xe9, 0xe3,
-  0xd8, 0xcf, 0x00, 0x00, 0x00, 0xea, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x84,
-  0xfb, 0xb6, 0x33, 0x00, 0x00, 0x00, 0x6c, 0xea, 0x00, 0x00, 0x00, 0x12,
-  0xef, 0xd9, 0xc8, 0x01, 0x00, 0x00, 0x3b, 0xfb, 0x42, 0x00, 0x26, 0xdf,
-  0x04, 0x02, 0xd1, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
-  0x00, 0x00, 0xa7, 0xa5, 0x07, 0xf1, 0x3f, 0x00, 0x77, 0xba, 0x45, 0xf4,
-  0x06, 0x01, 0xe9, 0x66, 0x0e, 0xff, 0x45, 0xbc, 0xbb, 0x07, 0x59, 0xf3,
-  0x0a, 0x00, 0xd8, 0x80, 0x00, 0x95, 0xd5, 0x8f, 0xdb, 0x0b, 0x50, 0xf9,
-  0x13, 0x00, 0xd8, 0x85, 0x00, 0x00, 0x58, 0xaa, 0x00, 0x00, 0x2c, 0xb1,
-  0xcf, 0x88, 0x4c, 0x89, 0x00, 0x95, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x01, 0x9a, 0xd6, 0x1d, 0x00, 0x86, 0xb7, 0x00,
-  0x07, 0xf1, 0x62, 0x00, 0x06, 0x58, 0xf8, 0x20, 0x00, 0x08, 0x00, 0x00,
-  0x00, 0xea, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0xbe, 0xfe, 0x5a,
-  0x00, 0x00, 0x6c, 0xea, 0x00, 0x00, 0x00, 0x00, 0x79, 0xff, 0x40, 0x00,
-  0x00, 0x0a, 0xda, 0x92, 0x00, 0x00, 0x9a, 0x75, 0x00, 0x00, 0x53, 0xc0,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x8b, 0xbf, 0xe6, 0xc5,
-  0x49, 0xfe, 0xc3, 0xc2, 0xd2, 0xfc, 0x36, 0xfe, 0x39, 0x2b, 0xf8, 0x29,
-  0x0e, 0xff, 0xc7, 0xf9, 0x18, 0x00, 0x09, 0xef, 0x57, 0x32, 0xfd, 0x20,
-  0x00, 0x0b, 0xe0, 0xff, 0x3d, 0x00, 0x04, 0xe5, 0x69, 0x31, 0xfe, 0x25,
-  0x00, 0x00, 0x58, 0xaa, 0x00, 0x00, 0x49, 0x3a, 0x47, 0x91, 0xbd, 0x67,
-  0x00, 0x95, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x43, 0xe6, 0x15, 0x00, 0x00, 0x28, 0xfb, 0x1a, 0x4a, 0xfb, 0x0d, 0x00,
-  0x00, 0x11, 0xc2, 0xf4, 0x90, 0x19, 0x00, 0x00, 0x00, 0xea, 0x6d, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaf, 0xbe, 0x00, 0x00, 0x6c, 0xea,
-  0x00, 0x00, 0x00, 0x00, 0x47, 0xff, 0x12, 0x00, 0x00, 0x91, 0xda, 0x0a,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x27, 0xfc, 0x6a, 0x1e, 0x9a, 0xc5, 0x3b, 0xfd, 0x42, 0x31,
-  0x31, 0x31, 0x00, 0x9f, 0xe5, 0xb3, 0x5d, 0x00, 0x0e, 0xff, 0xac, 0xd2,
-  0xa4, 0x00, 0x00, 0x97, 0xac, 0x88, 0xbc, 0x00, 0x00, 0x2b, 0xf7, 0xee,
-  0x75, 0x00, 0x00, 0x82, 0xbf, 0x86, 0xc3, 0x00, 0x00, 0x00, 0x58, 0xaa,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00,
-  0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x5b, 0x00, 0x00,
-  0x00, 0x00, 0xc7, 0x76, 0x40, 0xfe, 0x15, 0x00, 0x00, 0x00, 0x01, 0x4f,
-  0xd1, 0xdf, 0x00, 0x00, 0x00, 0xea, 0x83, 0x27, 0x27, 0x23, 0x39, 0x44,
-  0x14, 0x2b, 0xd1, 0x7b, 0x00, 0x00, 0x6c, 0xea, 0x00, 0x00, 0x00, 0x00,
-  0x47, 0xff, 0x12, 0x00, 0x40, 0xfd, 0x60, 0x27, 0x27, 0x27, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0xf6,
-  0x0f, 0x1f, 0xe4, 0xc5, 0x01, 0xe0, 0x89, 0x0e, 0x07, 0x31, 0x08, 0xfa,
-  0x4e, 0x24, 0x0a, 0x00, 0x0e, 0xff, 0x40, 0x2b, 0xf5, 0x65, 0x00, 0x36,
-  0xef, 0xd1, 0x5b, 0x00, 0x05, 0xcd, 0x9e, 0x4b, 0xf8, 0x2d, 0x00, 0x1e,
-  0xf6, 0xda, 0x63, 0x00, 0x00, 0x00, 0x58, 0xaa, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x10, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x68, 0xd4,
-  0x04, 0xec, 0x8a, 0x14, 0x3f, 0x27, 0x2c, 0x01, 0x54, 0xee, 0x00, 0x00,
-  0x00, 0xea, 0xff, 0xff, 0xff, 0xe6, 0x51, 0xdb, 0xfd, 0xea, 0xa3, 0x11,
-  0x00, 0x00, 0x6c, 0xea, 0x00, 0x00, 0x00, 0x00, 0x47, 0xff, 0x12, 0x00,
-  0xa0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xc8, 0xf7, 0xdf, 0x83, 0xc5,
-  0x00, 0x3f, 0xae, 0xf4, 0xfc, 0x9f, 0x0a, 0xd2, 0xfc, 0xfb, 0xf7, 0x90,
-  0x0e, 0xff, 0x40, 0x00, 0x65, 0xf6, 0x00, 0x00, 0xd4, 0xf0, 0x09, 0x00,
-  0x83, 0xe5, 0x10, 0x00, 0xa2, 0xd0, 0x00, 0x00, 0xb4, 0xf4, 0x0d, 0x00,
-  0x00, 0x00, 0x58, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0xc6, 0x95, 0x95, 0x95, 0xb6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x82, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x11, 0xf7, 0x00, 0x4f, 0xbf, 0xfe,
-  0xf1, 0x57, 0xe9, 0xf3, 0xde, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x0c, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d,
-  0x0a, 0x00, 0xaa, 0x94, 0x08, 0x02, 0x4e, 0xf8, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x06, 0xbf, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x58, 0xaa,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0f, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, 0x0f,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xae,
-  0x37, 0x49, 0xae, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0xc3,
-  0xf1, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x58, 0xaa, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x79, 0xb0, 0x96, 0x5c, 0x10,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x9f, 0x3a, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x3d, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0xff, 0x5a, 0xff, 0x8f, 0x00,
-  0x00, 0x00, 0xff, 0x8f, 0x00, 0x00, 0x00, 0x8f, 0xff, 0xff, 0xff, 0xff,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x3c, 0xaa, 0x3c, 0x8f, 0xff, 0x8f, 0x00, 0x00, 0x8f, 0xff,
-  0x8f, 0x00, 0x8f, 0xff, 0x8f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x8f, 0xff, 0x8f, 0x00, 0x00, 0x8f, 0xff, 0x8f, 0xff, 0x8f,
-  0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
-  0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x14, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x6e, 0xaa, 0x00, 0x3c, 0xaa, 0x3c, 0x00, 0x00, 0x8f,
-  0xff, 0x8f, 0x00, 0x00, 0x8f, 0xff, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x86, 0xbd, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x17,
-  0x00, 0x00, 0x00, 0x0a, 0x35, 0x13, 0x00, 0x00, 0x30, 0x20, 0x1d, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x31, 0x17, 0x00, 0x33, 0x15, 0x07, 0x35, 0x35,
-  0x26, 0x35, 0x31, 0x03, 0x7f, 0x18, 0x03, 0x7f, 0x18, 0x0b, 0xbc, 0x31,
-  0x00, 0x00, 0x0d, 0x35, 0x00, 0x00, 0x00, 0x5e, 0xff, 0x70, 0x00, 0x00,
-  0x00, 0x00, 0x70, 0xff, 0x5e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5,
-  0xff, 0x00, 0x5a, 0xff, 0x5a, 0x00, 0x8f, 0xff, 0x8f, 0x00, 0x00, 0x00,
-  0x00, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x81, 0xd8, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x4f, 0x3b, 0x00, 0x00, 0x00, 0xb5, 0xf5, 0xb0, 0xe7, 0x28, 0x00, 0x26,
-  0xff, 0x55, 0x00, 0x3e, 0xe7, 0x4c, 0xdc, 0x06, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0xea, 0x6d, 0x00, 0xf4, 0x64, 0x22, 0xff, 0xcb, 0x8e, 0xd6, 0xeb, 0x0b,
-  0xc5, 0x2f, 0x0b, 0xc5, 0x2f, 0x0e, 0xff, 0x43, 0x00, 0x00, 0x33, 0xfc,
-  0x00, 0x00, 0x00, 0x00, 0x70, 0xff, 0x70, 0x00, 0x00, 0x70, 0xff, 0x70,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x8f, 0xff, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x17, 0x36, 0x36, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x07,
-  0xcd, 0xa8, 0x36, 0x03, 0x35, 0x00, 0x24, 0x0b, 0xcf, 0x85, 0x36, 0x00,
-  0x32, 0xfd, 0x1b, 0x01, 0xbb, 0x85, 0x00, 0x19, 0xff, 0x48, 0x01, 0xca,
-  0x70, 0x01, 0xca, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x2c, 0x09, 0x00, 0x2c, 0x08, 0x00, 0xea, 0x6d, 0x00, 0xf4,
-  0x64, 0x22, 0xff, 0x12, 0x00, 0x46, 0xeb, 0x03, 0x36, 0x0e, 0x03, 0x36,
-  0x0e, 0x0e, 0xff, 0x43, 0x00, 0x00, 0x1f, 0xea, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x70, 0xff, 0x70, 0x70, 0xff, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x55, 0x37, 0x00, 0x37, 0x55, 0x00, 0x1e, 0x55, 0x1e, 0xff, 0x8f, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, 0xcc,
-  0xcc, 0xe8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x98, 0xf3, 0xe6, 0xc9, 0x0e,
-  0xff, 0x85, 0xfc, 0x93, 0xfb, 0xde, 0xc9, 0x00, 0x41, 0xf6, 0x00, 0x00,
-  0x9e, 0x99, 0x00, 0x0c, 0xff, 0x3c, 0x2c, 0xff, 0x0f, 0x00, 0x69, 0xd0,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xf8, 0x54,
-  0x20, 0xf8, 0x51, 0x00, 0xea, 0x6d, 0x00, 0xf4, 0x64, 0x22, 0xff, 0x12,
-  0x00, 0x46, 0xeb, 0x0e, 0xff, 0x43, 0x0e, 0xff, 0x43, 0x0e, 0xff, 0x43,
-  0x00, 0x00, 0x0a, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xff,
-  0xff, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xa5, 0x00, 0xa5,
-  0xff, 0x00, 0x5a, 0xff, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0xe6, 0x6f, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0xc7, 0x8b, 0x00, 0x0e, 0xff, 0xc7, 0x1f, 0x00,
-  0xee, 0x64, 0x00, 0x00, 0x64, 0xe7, 0x00, 0x00, 0x8f, 0xba, 0x00, 0x01,
-  0xfd, 0x2f, 0x54, 0xe5, 0x00, 0x00, 0x41, 0xf6, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x45, 0x09, 0x03, 0x46, 0x09, 0x00,
-  0xea, 0x6d, 0x00, 0xf4, 0x64, 0x22, 0xff, 0x12, 0x00, 0x46, 0xeb, 0x0e,
-  0xff, 0x43, 0x0e, 0xff, 0x43, 0x0e, 0xff, 0x43, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xff, 0xff, 0x70, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x55, 0x37, 0x00, 0x37, 0x55, 0x00, 0x1e, 0x55,
-  0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x01, 0xb1, 0xb6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0xc7, 0x8b, 0x00, 0x0e, 0xff, 0x5d, 0x00, 0x00, 0xee, 0x64, 0x00, 0x9b,
-  0xe8, 0x53, 0x00, 0x00, 0x23, 0xc4, 0xd7, 0x00, 0xf1, 0x22, 0x7b, 0xbe,
-  0x00, 0x00, 0x1a, 0xff, 0x00, 0x00, 0x00, 0x62, 0xa4, 0xa4, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x6d, 0x00, 0xf4,
-  0x64, 0x22, 0xff, 0x12, 0x00, 0x46, 0xeb, 0x0e, 0xff, 0x43, 0x0e, 0xff,
-  0x43, 0x0e, 0xff, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x70, 0xff, 0x70, 0x70, 0xff, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69,
-  0xe8, 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x8b, 0x00, 0x0e,
-  0xff, 0x43, 0x00, 0x00, 0xee, 0x64, 0x00, 0x52, 0xcc, 0xa7, 0x00, 0x00,
-  0x4e, 0xea, 0x76, 0x00, 0x75, 0x0c, 0x6f, 0xca, 0x00, 0x00, 0x26, 0xff,
-  0x00, 0x00, 0x00, 0x49, 0x7a, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0xea, 0x6d, 0x00, 0xf4, 0x64, 0x22, 0xff, 0x12,
-  0x00, 0x46, 0xeb, 0x0e, 0xff, 0x43, 0x0e, 0xff, 0x43, 0x0e, 0xff, 0x43,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0xff, 0x70, 0x00,
-  0x00, 0x70, 0xff, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xf5, 0x4c, 0x03, 0x03, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0xc7, 0x8b, 0x00, 0x0e, 0xff, 0x43, 0x00, 0x00,
-  0xd0, 0x8f, 0x05, 0x00, 0x4a, 0xf5, 0x00, 0x00, 0x99, 0xa2, 0x00, 0x08,
-  0x75, 0x17, 0x47, 0xf2, 0x01, 0x00, 0x4e, 0xeb, 0x09, 0x56, 0x17, 0x00,
-  0x00, 0x00, 0x08, 0x75, 0x17, 0x08, 0x75, 0x17, 0x09, 0x56, 0x17, 0x00,
-  0xea, 0x6d, 0x00, 0xf4, 0x64, 0x22, 0xff, 0x12, 0x00, 0x46, 0xeb, 0x0e,
-  0xff, 0x43, 0x0e, 0xff, 0x43, 0x0e, 0xff, 0x43, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x5e, 0xff, 0x70, 0x00, 0x00, 0x00, 0x00, 0x70, 0xff,
-  0x5e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x9a, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0xc7, 0x8b, 0x00, 0x0e, 0xff, 0x43, 0x00, 0x00, 0x62, 0xee, 0xfc, 0x00,
-  0x41, 0xf7, 0x00, 0x00, 0x9f, 0x99, 0x00, 0x27, 0xfe, 0x4f, 0x1a, 0xfc,
-  0x27, 0x00, 0x82, 0xba, 0x3d, 0xf9, 0x14, 0x00, 0x00, 0x00, 0x27, 0xfe,
-  0x4f, 0x27, 0xfe, 0x4f, 0x3d, 0xf9, 0x14, 0x00, 0xea, 0x6d, 0x00, 0xf6,
-  0x60, 0x22, 0xff, 0x12, 0x00, 0x46, 0xeb, 0x0e, 0xff, 0x43, 0x0e, 0xff,
-  0x43, 0x0e, 0xff, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
-  0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x14, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0e, 0x00, 0x13, 0xf7, 0x74, 0x3a,
-  0xe1, 0x6d, 0x00, 0x00, 0x12, 0x00, 0x00, 0x97, 0x9e, 0x0d, 0xeb, 0x3d,
-  0x71, 0xaf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x12, 0x00,
-  0x71, 0xaf, 0x00, 0x00, 0x00, 0x00, 0x33, 0xff, 0x25, 0x22, 0xff, 0x3c,
-  0x20, 0x67, 0xeb, 0x00, 0x00, 0x00, 0x17, 0xff, 0x42, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc1,
-  0xc1, 0xc1, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xa8, 0x8b, 0x86, 0x0d, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x16, 0xc8, 0x5c, 0x97, 0x00, 0x49, 0x33, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x33, 0x00, 0x00,
-  0x00, 0x00, 0xf9, 0xad, 0x00, 0x1c, 0xd1, 0xd1, 0x95, 0xd1, 0xc0, 0x00,
-  0x00, 0x00, 0xba, 0xf7, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-};
-
-
-static mu_Rect atlas[] = {
-  [ MU_ICON_CLOSE ] = { 88, 68, 16, 16 },
-  [ MU_ICON_CHECK ] = { 0, 0, 18, 18 },
-  [ MU_ICON_EXPANDED ] = { 118, 68, 7, 5 },
-  [ MU_ICON_COLLAPSED ] = { 113, 68, 5, 7 },
-  [ ATLAS_WHITE ] = { 125, 68, 3, 3 },
-  [ ATLAS_FONT+32 ] = { 84, 68, 2, 17 },
-  [ ATLAS_FONT+33 ] = { 39, 68, 3, 17 },
-  [ ATLAS_FONT+34 ] = { 114, 51, 5, 17 },
-  [ ATLAS_FONT+35 ] = { 34, 17, 7, 17 },
-  [ ATLAS_FONT+36 ] = { 28, 34, 6, 17 },
-  [ ATLAS_FONT+37 ] = { 58, 0, 9, 17 },
-  [ ATLAS_FONT+38 ] = { 103, 0, 8, 17 },
-  [ ATLAS_FONT+39 ] = { 86, 68, 2, 17 },
-  [ ATLAS_FONT+40 ] = { 42, 68, 3, 17 },
-  [ ATLAS_FONT+41 ] = { 45, 68, 3, 17 },
-  [ ATLAS_FONT+42 ] = { 34, 34, 6, 17 },
-  [ ATLAS_FONT+43 ] = { 40, 34, 6, 17 },
-  [ ATLAS_FONT+44 ] = { 48, 68, 3, 17 },
-  [ ATLAS_FONT+45 ] = { 51, 68, 3, 17 },
-  [ ATLAS_FONT+46 ] = { 54, 68, 3, 17 },
-  [ ATLAS_FONT+47 ] = { 124, 34, 4, 17 },
-  [ ATLAS_FONT+48 ] = { 46, 34, 6, 17 },
-  [ ATLAS_FONT+49 ] = { 52, 34, 6, 17 },
-  [ ATLAS_FONT+50 ] = { 58, 34, 6, 17 },
-  [ ATLAS_FONT+51 ] = { 64, 34, 6, 17 },
-  [ ATLAS_FONT+52 ] = { 70, 34, 6, 17 },
-  [ ATLAS_FONT+53 ] = { 76, 34, 6, 17 },
-  [ ATLAS_FONT+54 ] = { 82, 34, 6, 17 },
-  [ ATLAS_FONT+55 ] = { 88, 34, 6, 17 },
-  [ ATLAS_FONT+56 ] = { 94, 34, 6, 17 },
-  [ ATLAS_FONT+57 ] = { 100, 34, 6, 17 },
-  [ ATLAS_FONT+58 ] = { 57, 68, 3, 17 },
-  [ ATLAS_FONT+59 ] = { 60, 68, 3, 17 },
-  [ ATLAS_FONT+60 ] = { 106, 34, 6, 17 },
-  [ ATLAS_FONT+61 ] = { 112, 34, 6, 17 },
-  [ ATLAS_FONT+62 ] = { 118, 34, 6, 17 },
-  [ ATLAS_FONT+63 ] = { 119, 51, 5, 17 },
-  [ ATLAS_FONT+64 ] = { 18, 0, 10, 17 },
-  [ ATLAS_FONT+65 ] = { 41, 17, 7, 17 },
-  [ ATLAS_FONT+66 ] = { 48, 17, 7, 17 },
-  [ ATLAS_FONT+67 ] = { 55, 17, 7, 17 },
-  [ ATLAS_FONT+68 ] = { 111, 0, 8, 17 },
-  [ ATLAS_FONT+69 ] = { 0, 35, 6, 17 },
-  [ ATLAS_FONT+70 ] = { 6, 35, 6, 17 },
-  [ ATLAS_FONT+71 ] = { 119, 0, 8, 17 },
-  [ ATLAS_FONT+72 ] = { 18, 17, 8, 17 },
-  [ ATLAS_FONT+73 ] = { 63, 68, 3, 17 },
-  [ ATLAS_FONT+74 ] = { 66, 68, 3, 17 },
-  [ ATLAS_FONT+75 ] = { 62, 17, 7, 17 },
-  [ ATLAS_FONT+76 ] = { 12, 51, 6, 17 },
-  [ ATLAS_FONT+77 ] = { 28, 0, 10, 17 },
-  [ ATLAS_FONT+78 ] = { 67, 0, 9, 17 },
-  [ ATLAS_FONT+79 ] = { 76, 0, 9, 17 },
-  [ ATLAS_FONT+80 ] = { 69, 17, 7, 17 },
-  [ ATLAS_FONT+81 ] = { 85, 0, 9, 17 },
-  [ ATLAS_FONT+82 ] = { 76, 17, 7, 17 },
-  [ ATLAS_FONT+83 ] = { 18, 51, 6, 17 },
-  [ ATLAS_FONT+84 ] = { 24, 51, 6, 17 },
-  [ ATLAS_FONT+85 ] = { 26, 17, 8, 17 },
-  [ ATLAS_FONT+86 ] = { 83, 17, 7, 17 },
-  [ ATLAS_FONT+87 ] = { 38, 0, 10, 17 },
-  [ ATLAS_FONT+88 ] = { 90, 17, 7, 17 },
-  [ ATLAS_FONT+89 ] = { 30, 51, 6, 17 },
-  [ ATLAS_FONT+90 ] = { 36, 51, 6, 17 },
-  [ ATLAS_FONT+91 ] = { 69, 68, 3, 17 },
-  [ ATLAS_FONT+92 ] = { 124, 51, 4, 17 },
-  [ ATLAS_FONT+93 ] = { 72, 68, 3, 17 },
-  [ ATLAS_FONT+94 ] = { 42, 51, 6, 17 },
-  [ ATLAS_FONT+95 ] = { 15, 68, 4, 17 },
-  [ ATLAS_FONT+96 ] = { 48, 51, 6, 17 },
-  [ ATLAS_FONT+97 ] = { 54, 51, 6, 17 },
-  [ ATLAS_FONT+98 ] = { 97, 17, 7, 17 },
-  [ ATLAS_FONT+99 ] = { 0, 52, 5, 17 },
-  [ ATLAS_FONT+100 ] = { 104, 17, 7, 17 },
-  [ ATLAS_FONT+101 ] = { 60, 51, 6, 17 },
-  [ ATLAS_FONT+102 ] = { 19, 68, 4, 17 },
-  [ ATLAS_FONT+103 ] = { 66, 51, 6, 17 },
-  [ ATLAS_FONT+104 ] = { 111, 17, 7, 17 },
-  [ ATLAS_FONT+105 ] = { 75, 68, 3, 17 },
-  [ ATLAS_FONT+106 ] = { 78, 68, 3, 17 },
-  [ ATLAS_FONT+107 ] = { 72, 51, 6, 17 },
-  [ ATLAS_FONT+108 ] = { 81, 68, 3, 17 },
-  [ ATLAS_FONT+109 ] = { 48, 0, 10, 17 },
-  [ ATLAS_FONT+110 ] = { 118, 17, 7, 17 },
-  [ ATLAS_FONT+111 ] = { 0, 18, 7, 17 },
-  [ ATLAS_FONT+112 ] = { 7, 18, 7, 17 },
-  [ ATLAS_FONT+113 ] = { 14, 34, 7, 17 },
-  [ ATLAS_FONT+114 ] = { 23, 68, 4, 17 },
-  [ ATLAS_FONT+115 ] = { 5, 52, 5, 17 },
-  [ ATLAS_FONT+116 ] = { 27, 68, 4, 17 },
-  [ ATLAS_FONT+117 ] = { 21, 34, 7, 17 },
-  [ ATLAS_FONT+118 ] = { 78, 51, 6, 17 },
-  [ ATLAS_FONT+119 ] = { 94, 0, 9, 17 },
-  [ ATLAS_FONT+120 ] = { 84, 51, 6, 17 },
-  [ ATLAS_FONT+121 ] = { 90, 51, 6, 17 },
-  [ ATLAS_FONT+122 ] = { 10, 68, 5, 17 },
-  [ ATLAS_FONT+123 ] = { 31, 68, 4, 17 },
-  [ ATLAS_FONT+124 ] = { 96, 51, 6, 17 },
-  [ ATLAS_FONT+125 ] = { 35, 68, 4, 17 },
-  [ ATLAS_FONT+126 ] = { 102, 51, 6, 17 },
-  [ ATLAS_FONT+127 ] = { 108, 51, 6, 17 },
-};
-
blob - 0415e45b105484fcf06fe1d9f4f01875d98cd128 (mode 644)
blob + /dev/null
--- lib/microui.c
+++ /dev/null
@@ -1,1208 +0,0 @@
-/*
-** Copyright (c) 2024 rxi
-**
-** Permission is hereby granted, free of charge, to any person obtaining a copy
-** of this software and associated documentation files (the "Software"), to
-** deal in the Software without restriction, including without limitation the
-** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-** sell copies of the Software, and to permit persons to whom the Software is
-** furnished to do so, subject to the following conditions:
-**
-** The above copyright notice and this permission notice shall be included in
-** all copies or substantial portions of the Software.
-**
-** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-** IN THE SOFTWARE.
-*/
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include "microui.h"
-
-#define unused(x) ((void) (x))
-
-#define expect(x) do {                                               \
-    if (!(x)) {                                                      \
-      fprintf(stderr, "Fatal error: %s:%d: assertion '%s' failed\n", \
-        __FILE__, __LINE__, #x);                                     \
-      abort();                                                       \
-    }                                                                \
-  } while (0)
-
-#define push(stk, val) do {                                                 \
-    expect((stk).idx < (int) (sizeof((stk).items) / sizeof(*(stk).items))); \
-    (stk).items[(stk).idx] = (val);                                         \
-    (stk).idx++; /* incremented after incase `val` uses this value */       \
-  } while (0)
-
-#define pop(stk) do {      \
-    expect((stk).idx > 0); \
-    (stk).idx--;           \
-  } while (0)
-
-
-static mu_Rect unclipped_rect = { 0, 0, 0x1000000, 0x1000000 };
-
-static mu_Style default_style = {
-  /* font | size | padding | spacing | indent */
-  NULL, { 68, 10 }, 5, 4, 24,
-  /* title_height | scrollbar_size | thumb_size */
-  24, 12, 8,
-  {
-    { 230, 230, 230, 255 }, /* MU_COLOR_TEXT */
-    { 25,  25,  25,  255 }, /* MU_COLOR_BORDER */
-    { 50,  50,  50,  255 }, /* MU_COLOR_WINDOWBG */
-    { 25,  25,  25,  255 }, /* MU_COLOR_TITLEBG */
-    { 240, 240, 240, 255 }, /* MU_COLOR_TITLETEXT */
-    { 0,   0,   0,   0   }, /* MU_COLOR_PANELBG */
-    { 75,  75,  75,  255 }, /* MU_COLOR_BUTTON */
-    { 95,  95,  95,  255 }, /* MU_COLOR_BUTTONHOVER */
-    { 115, 115, 115, 255 }, /* MU_COLOR_BUTTONFOCUS */
-    { 30,  30,  30,  255 }, /* MU_COLOR_BASE */
-    { 35,  35,  35,  255 }, /* MU_COLOR_BASEHOVER */
-    { 40,  40,  40,  255 }, /* MU_COLOR_BASEFOCUS */
-    { 43,  43,  43,  255 }, /* MU_COLOR_SCROLLBASE */
-    { 30,  30,  30,  255 }  /* MU_COLOR_SCROLLTHUMB */
-  }
-};
-
-
-mu_Vec2 mu_vec2(int x, int y) {
-  mu_Vec2 res;
-  res.x = x; res.y = y;
-  return res;
-}
-
-
-mu_Rect mu_rect(int x, int y, int w, int h) {
-  mu_Rect res;
-  res.x = x; res.y = y; res.w = w; res.h = h;
-  return res;
-}
-
-
-mu_Color mu_color(int r, int g, int b, int a) {
-  mu_Color res;
-  res.r = r; res.g = g; res.b = b; res.a = a;
-  return res;
-}
-
-
-static mu_Rect expand_rect(mu_Rect rect, int n) {
-  return mu_rect(rect.x - n, rect.y - n, rect.w + n * 2, rect.h + n * 2);
-}
-
-
-static mu_Rect intersect_rects(mu_Rect r1, mu_Rect r2) {
-  int x1 = mu_max(r1.x, r2.x);
-  int y1 = mu_max(r1.y, r2.y);
-  int x2 = mu_min(r1.x + r1.w, r2.x + r2.w);
-  int y2 = mu_min(r1.y + r1.h, r2.y + r2.h);
-  if (x2 < x1) { x2 = x1; }
-  if (y2 < y1) { y2 = y1; }
-  return mu_rect(x1, y1, x2 - x1, y2 - y1);
-}
-
-
-static int rect_overlaps_vec2(mu_Rect r, mu_Vec2 p) {
-  return p.x >= r.x && p.x < r.x + r.w && p.y >= r.y && p.y < r.y + r.h;
-}
-
-
-static void draw_frame(mu_Context *ctx, mu_Rect rect, int colorid) {
-  mu_draw_rect(ctx, rect, ctx->style->colors[colorid]);
-  if (colorid == MU_COLOR_SCROLLBASE  ||
-      colorid == MU_COLOR_SCROLLTHUMB ||
-      colorid == MU_COLOR_TITLEBG) { return; }
-  /* draw border */
-  if (ctx->style->colors[MU_COLOR_BORDER].a) {
-    mu_draw_box(ctx, expand_rect(rect, 1), ctx->style->colors[MU_COLOR_BORDER]);
-  }
-}
-
-
-void mu_init(mu_Context *ctx) {
-  memset(ctx, 0, sizeof(*ctx));
-  ctx->draw_frame = draw_frame;
-  ctx->_style = default_style;
-  ctx->style = &ctx->_style;
-}
-
-
-void mu_begin(mu_Context *ctx) {
-  expect(ctx->text_width && ctx->text_height);
-  ctx->command_list.idx = 0;
-  ctx->root_list.idx = 0;
-  ctx->scroll_target = NULL;
-  ctx->hover_root = ctx->next_hover_root;
-  ctx->next_hover_root = NULL;
-  ctx->mouse_delta.x = ctx->mouse_pos.x - ctx->last_mouse_pos.x;
-  ctx->mouse_delta.y = ctx->mouse_pos.y - ctx->last_mouse_pos.y;
-  ctx->frame++;
-}
-
-
-static int compare_zindex(const void *a, const void *b) {
-  return (*(mu_Container**) a)->zindex - (*(mu_Container**) b)->zindex;
-}
-
-
-void mu_end(mu_Context *ctx) {
-  int i, n;
-  /* check stacks */
-  expect(ctx->container_stack.idx == 0);
-  expect(ctx->clip_stack.idx      == 0);
-  expect(ctx->id_stack.idx        == 0);
-  expect(ctx->layout_stack.idx    == 0);
-
-  /* handle scroll input */
-  if (ctx->scroll_target) {
-    ctx->scroll_target->scroll.x += ctx->scroll_delta.x;
-    ctx->scroll_target->scroll.y += ctx->scroll_delta.y;
-  }
-
-  /* unset focus if focus id was not touched this frame */
-  if (!ctx->updated_focus) { ctx->focus = 0; }
-  ctx->updated_focus = 0;
-
-  /* bring hover root to front if mouse was pressed */
-  if (ctx->mouse_pressed && ctx->next_hover_root &&
-      ctx->next_hover_root->zindex < ctx->last_zindex &&
-      ctx->next_hover_root->zindex >= 0
-  ) {
-    mu_bring_to_front(ctx, ctx->next_hover_root);
-  }
-
-  /* reset input state */
-  ctx->key_pressed = 0;
-  ctx->input_text[0] = '\0';
-  ctx->mouse_pressed = 0;
-  ctx->scroll_delta = mu_vec2(0, 0);
-  ctx->last_mouse_pos = ctx->mouse_pos;
-
-  /* sort root containers by zindex */
-  n = ctx->root_list.idx;
-  qsort(ctx->root_list.items, n, sizeof(mu_Container*), compare_zindex);
-
-  /* set root container jump commands */
-  for (i = 0; i < n; i++) {
-    mu_Container *cnt = ctx->root_list.items[i];
-    /* if this is the first container then make the first command jump to it.
-    ** otherwise set the previous container's tail to jump to this one */
-    if (i == 0) {
-      mu_Command *cmd = (mu_Command*) ctx->command_list.items;
-      cmd->jump.dst = (char*) cnt->head + sizeof(mu_JumpCommand);
-    } else {
-      mu_Container *prev = ctx->root_list.items[i - 1];
-      prev->tail->jump.dst = (char*) cnt->head + sizeof(mu_JumpCommand);
-    }
-    /* make the last container's tail jump to the end of command list */
-    if (i == n - 1) {
-      cnt->tail->jump.dst = ctx->command_list.items + ctx->command_list.idx;
-    }
-  }
-}
-
-
-void mu_set_focus(mu_Context *ctx, mu_Id id) {
-  ctx->focus = id;
-  ctx->updated_focus = 1;
-}
-
-
-/* 32bit fnv-1a hash */
-#define HASH_INITIAL 2166136261
-
-static void hash(mu_Id *hash, const void *data, int size) {
-  const unsigned char *p = data;
-  while (size--) {
-    *hash = (*hash ^ *p++) * 16777619;
-  }
-}
-
-
-mu_Id mu_get_id(mu_Context *ctx, const void *data, int size) {
-  int idx = ctx->id_stack.idx;
-  mu_Id res = (idx > 0) ? ctx->id_stack.items[idx - 1] : HASH_INITIAL;
-  hash(&res, data, size);
-  ctx->last_id = res;
-  return res;
-}
-
-
-void mu_push_id(mu_Context *ctx, const void *data, int size) {
-  push(ctx->id_stack, mu_get_id(ctx, data, size));
-}
-
-
-void mu_pop_id(mu_Context *ctx) {
-  pop(ctx->id_stack);
-}
-
-
-void mu_push_clip_rect(mu_Context *ctx, mu_Rect rect) {
-  mu_Rect last = mu_get_clip_rect(ctx);
-  push(ctx->clip_stack, intersect_rects(rect, last));
-}
-
-
-void mu_pop_clip_rect(mu_Context *ctx) {
-  pop(ctx->clip_stack);
-}
-
-
-mu_Rect mu_get_clip_rect(mu_Context *ctx) {
-  expect(ctx->clip_stack.idx > 0);
-  return ctx->clip_stack.items[ctx->clip_stack.idx - 1];
-}
-
-
-int mu_check_clip(mu_Context *ctx, mu_Rect r) {
-  mu_Rect cr = mu_get_clip_rect(ctx);
-  if (r.x > cr.x + cr.w || r.x + r.w < cr.x ||
-      r.y > cr.y + cr.h || r.y + r.h < cr.y   ) { return MU_CLIP_ALL; }
-  if (r.x >= cr.x && r.x + r.w <= cr.x + cr.w &&
-      r.y >= cr.y && r.y + r.h <= cr.y + cr.h ) { return 0; }
-  return MU_CLIP_PART;
-}
-
-
-static void push_layout(mu_Context *ctx, mu_Rect body, mu_Vec2 scroll) {
-  mu_Layout layout;
-  int width = 0;
-  memset(&layout, 0, sizeof(layout));
-  layout.body = mu_rect(body.x - scroll.x, body.y - scroll.y, body.w, body.h);
-  layout.max = mu_vec2(-0x1000000, -0x1000000);
-  push(ctx->layout_stack, layout);
-  mu_layout_row(ctx, 1, &width, 0);
-}
-
-
-static mu_Layout* get_layout(mu_Context *ctx) {
-  return &ctx->layout_stack.items[ctx->layout_stack.idx - 1];
-}
-
-
-static void pop_container(mu_Context *ctx) {
-  mu_Container *cnt = mu_get_current_container(ctx);
-  mu_Layout *layout = get_layout(ctx);
-  cnt->content_size.x = layout->max.x - layout->body.x;
-  cnt->content_size.y = layout->max.y - layout->body.y;
-  /* pop container, layout and id */
-  pop(ctx->container_stack);
-  pop(ctx->layout_stack);
-  mu_pop_id(ctx);
-}
-
-
-mu_Container* mu_get_current_container(mu_Context *ctx) {
-  expect(ctx->container_stack.idx > 0);
-  return ctx->container_stack.items[ ctx->container_stack.idx - 1 ];
-}
-
-
-static mu_Container* get_container(mu_Context *ctx, mu_Id id, int opt) {
-  mu_Container *cnt;
-  /* try to get existing container from pool */
-  int idx = mu_pool_get(ctx, ctx->container_pool, MU_CONTAINERPOOL_SIZE, id);
-  if (idx >= 0) {
-    if (ctx->containers[idx].open || ~opt & MU_OPT_CLOSED) {
-      mu_pool_update(ctx, ctx->container_pool, idx);
-    }
-    return &ctx->containers[idx];
-  }
-  if (opt & MU_OPT_CLOSED) { return NULL; }
-  /* container not found in pool: init new container */
-  idx = mu_pool_init(ctx, ctx->container_pool, MU_CONTAINERPOOL_SIZE, id);
-  cnt = &ctx->containers[idx];
-  memset(cnt, 0, sizeof(*cnt));
-  cnt->open = 1;
-  mu_bring_to_front(ctx, cnt);
-  return cnt;
-}
-
-
-mu_Container* mu_get_container(mu_Context *ctx, const char *name) {
-  mu_Id id = mu_get_id(ctx, name, strlen(name));
-  return get_container(ctx, id, 0);
-}
-
-
-void mu_bring_to_front(mu_Context *ctx, mu_Container *cnt) {
-  cnt->zindex = ++ctx->last_zindex;
-}
-
-
-/*============================================================================
-** pool
-**============================================================================*/
-
-int mu_pool_init(mu_Context *ctx, mu_PoolItem *items, int len, mu_Id id) {
-  int i, n = -1, f = ctx->frame;
-  for (i = 0; i < len; i++) {
-    if (items[i].last_update < f) {
-      f = items[i].last_update;
-      n = i;
-    }
-  }
-  expect(n > -1);
-  items[n].id = id;
-  mu_pool_update(ctx, items, n);
-  return n;
-}
-
-
-int mu_pool_get(mu_Context *ctx, mu_PoolItem *items, int len, mu_Id id) {
-  int i;
-  unused(ctx);
-  for (i = 0; i < len; i++) {
-    if (items[i].id == id) { return i; }
-  }
-  return -1;
-}
-
-
-void mu_pool_update(mu_Context *ctx, mu_PoolItem *items, int idx) {
-  items[idx].last_update = ctx->frame;
-}
-
-
-/*============================================================================
-** input handlers
-**============================================================================*/
-
-void mu_input_mousemove(mu_Context *ctx, int x, int y) {
-  ctx->mouse_pos = mu_vec2(x, y);
-}
-
-
-void mu_input_mousedown(mu_Context *ctx, int x, int y, int btn) {
-  mu_input_mousemove(ctx, x, y);
-  ctx->mouse_down |= btn;
-  ctx->mouse_pressed |= btn;
-}
-
-
-void mu_input_mouseup(mu_Context *ctx, int x, int y, int btn) {
-  mu_input_mousemove(ctx, x, y);
-  ctx->mouse_down &= ~btn;
-}
-
-
-void mu_input_scroll(mu_Context *ctx, int x, int y) {
-  ctx->scroll_delta.x += x;
-  ctx->scroll_delta.y += y;
-}
-
-
-void mu_input_keydown(mu_Context *ctx, int key) {
-  ctx->key_pressed |= key;
-  ctx->key_down |= key;
-}
-
-
-void mu_input_keyup(mu_Context *ctx, int key) {
-  ctx->key_down &= ~key;
-}
-
-
-void mu_input_text(mu_Context *ctx, const char *text) {
-  int len = strlen(ctx->input_text);
-  int size = strlen(text) + 1;
-  expect(len + size <= (int) sizeof(ctx->input_text));
-  memcpy(ctx->input_text + len, text, size);
-}
-
-
-/*============================================================================
-** commandlist
-**============================================================================*/
-
-mu_Command* mu_push_command(mu_Context *ctx, int type, int size) {
-  mu_Command *cmd = (mu_Command*) (ctx->command_list.items + ctx->command_list.idx);
-  expect(ctx->command_list.idx + size < MU_COMMANDLIST_SIZE);
-  cmd->base.type = type;
-  cmd->base.size = size;
-  ctx->command_list.idx += size;
-  return cmd;
-}
-
-
-int mu_next_command(mu_Context *ctx, mu_Command **cmd) {
-  if (*cmd) {
-    *cmd = (mu_Command*) (((char*) *cmd) + (*cmd)->base.size);
-  } else {
-    *cmd = (mu_Command*) ctx->command_list.items;
-  }
-  while ((char*) *cmd != ctx->command_list.items + ctx->command_list.idx) {
-    if ((*cmd)->type != MU_COMMAND_JUMP) { return 1; }
-    *cmd = (*cmd)->jump.dst;
-  }
-  return 0;
-}
-
-
-static mu_Command* push_jump(mu_Context *ctx, mu_Command *dst) {
-  mu_Command *cmd;
-  cmd = mu_push_command(ctx, MU_COMMAND_JUMP, sizeof(mu_JumpCommand));
-  cmd->jump.dst = dst;
-  return cmd;
-}
-
-
-void mu_set_clip(mu_Context *ctx, mu_Rect rect) {
-  mu_Command *cmd;
-  cmd = mu_push_command(ctx, MU_COMMAND_CLIP, sizeof(mu_ClipCommand));
-  cmd->clip.rect = rect;
-}
-
-
-void mu_draw_rect(mu_Context *ctx, mu_Rect rect, mu_Color color) {
-  mu_Command *cmd;
-  rect = intersect_rects(rect, mu_get_clip_rect(ctx));
-  if (rect.w > 0 && rect.h > 0) {
-    cmd = mu_push_command(ctx, MU_COMMAND_RECT, sizeof(mu_RectCommand));
-    cmd->rect.rect = rect;
-    cmd->rect.color = color;
-  }
-}
-
-
-void mu_draw_box(mu_Context *ctx, mu_Rect rect, mu_Color color) {
-  mu_draw_rect(ctx, mu_rect(rect.x + 1, rect.y, rect.w - 2, 1), color);
-  mu_draw_rect(ctx, mu_rect(rect.x + 1, rect.y + rect.h - 1, rect.w - 2, 1), color);
-  mu_draw_rect(ctx, mu_rect(rect.x, rect.y, 1, rect.h), color);
-  mu_draw_rect(ctx, mu_rect(rect.x + rect.w - 1, rect.y, 1, rect.h), color);
-}
-
-
-void mu_draw_text(mu_Context *ctx, mu_Font font, const char *str, int len,
-  mu_Vec2 pos, mu_Color color)
-{
-  mu_Command *cmd;
-  mu_Rect rect = mu_rect(
-    pos.x, pos.y, ctx->text_width(font, str, len), ctx->text_height(font));
-  int clipped = mu_check_clip(ctx, rect);
-  if (clipped == MU_CLIP_ALL ) { return; }
-  if (clipped == MU_CLIP_PART) { mu_set_clip(ctx, mu_get_clip_rect(ctx)); }
-  /* add command */
-  if (len < 0) { len = strlen(str); }
-  cmd = mu_push_command(ctx, MU_COMMAND_TEXT, sizeof(mu_TextCommand) + len);
-  memcpy(cmd->text.str, str, len);
-  cmd->text.str[len] = '\0';
-  cmd->text.pos = pos;
-  cmd->text.color = color;
-  cmd->text.font = font;
-  /* reset clipping if it was set */
-  if (clipped) { mu_set_clip(ctx, unclipped_rect); }
-}
-
-
-void mu_draw_icon(mu_Context *ctx, int id, mu_Rect rect, mu_Color color) {
-  mu_Command *cmd;
-  /* do clip command if the rect isn't fully contained within the cliprect */
-  int clipped = mu_check_clip(ctx, rect);
-  if (clipped == MU_CLIP_ALL ) { return; }
-  if (clipped == MU_CLIP_PART) { mu_set_clip(ctx, mu_get_clip_rect(ctx)); }
-  /* do icon command */
-  cmd = mu_push_command(ctx, MU_COMMAND_ICON, sizeof(mu_IconCommand));
-  cmd->icon.id = id;
-  cmd->icon.rect = rect;
-  cmd->icon.color = color;
-  /* reset clipping if it was set */
-  if (clipped) { mu_set_clip(ctx, unclipped_rect); }
-}
-
-
-/*============================================================================
-** layout
-**============================================================================*/
-
-enum { RELATIVE = 1, ABSOLUTE = 2 };
-
-
-void mu_layout_begin_column(mu_Context *ctx) {
-  push_layout(ctx, mu_layout_next(ctx), mu_vec2(0, 0));
-}
-
-
-void mu_layout_end_column(mu_Context *ctx) {
-  mu_Layout *a, *b;
-  b = get_layout(ctx);
-  pop(ctx->layout_stack);
-  /* inherit position/next_row/max from child layout if they are greater */
-  a = get_layout(ctx);
-  a->position.x = mu_max(a->position.x, b->position.x + b->body.x - a->body.x);
-  a->next_row = mu_max(a->next_row, b->next_row + b->body.y - a->body.y);
-  a->max.x = mu_max(a->max.x, b->max.x);
-  a->max.y = mu_max(a->max.y, b->max.y);
-}
-
-
-void mu_layout_row(mu_Context *ctx, int items, const int *widths, int height) {
-  mu_Layout *layout = get_layout(ctx);
-  if (widths) {
-    expect(items <= MU_MAX_WIDTHS);
-    memcpy(layout->widths, widths, items * sizeof(widths[0]));
-  }
-  layout->items = items;
-  layout->position = mu_vec2(layout->indent, layout->next_row);
-  layout->size.y = height;
-  layout->item_index = 0;
-}
-
-
-void mu_layout_width(mu_Context *ctx, int width) {
-  get_layout(ctx)->size.x = width;
-}
-
-
-void mu_layout_height(mu_Context *ctx, int height) {
-  get_layout(ctx)->size.y = height;
-}
-
-
-void mu_layout_set_next(mu_Context *ctx, mu_Rect r, int relative) {
-  mu_Layout *layout = get_layout(ctx);
-  layout->next = r;
-  layout->next_type = relative ? RELATIVE : ABSOLUTE;
-}
-
-
-mu_Rect mu_layout_next(mu_Context *ctx) {
-  mu_Layout *layout = get_layout(ctx);
-  mu_Style *style = ctx->style;
-  mu_Rect res;
-
-  if (layout->next_type) {
-    /* handle rect set by `mu_layout_set_next` */
-    int type = layout->next_type;
-    layout->next_type = 0;
-    res = layout->next;
-    if (type == ABSOLUTE) { return (ctx->last_rect = res); }
-
-  } else {
-    /* handle next row */
-    if (layout->item_index == layout->items) {
-      mu_layout_row(ctx, layout->items, NULL, layout->size.y);
-    }
-
-    /* position */
-    res.x = layout->position.x;
-    res.y = layout->position.y;
-
-    /* size */
-    res.w = layout->items > 0 ? layout->widths[layout->item_index] : layout->size.x;
-    res.h = layout->size.y;
-    if (res.w == 0) { res.w = style->size.x + style->padding * 2; }
-    if (res.h == 0) { res.h = style->size.y + style->padding * 2; }
-    if (res.w <  0) { res.w += layout->body.w - res.x + 1; }
-    if (res.h <  0) { res.h += layout->body.h - res.y + 1; }
-
-    layout->item_index++;
-  }
-
-  /* update position */
-  layout->position.x += res.w + style->spacing;
-  layout->next_row = mu_max(layout->next_row, res.y + res.h + style->spacing);
-
-  /* apply body offset */
-  res.x += layout->body.x;
-  res.y += layout->body.y;
-
-  /* update max position */
-  layout->max.x = mu_max(layout->max.x, res.x + res.w);
-  layout->max.y = mu_max(layout->max.y, res.y + res.h);
-
-  return (ctx->last_rect = res);
-}
-
-
-/*============================================================================
-** controls
-**============================================================================*/
-
-static int in_hover_root(mu_Context *ctx) {
-  int i = ctx->container_stack.idx;
-  while (i--) {
-    if (ctx->container_stack.items[i] == ctx->hover_root) { return 1; }
-    /* only root containers have their `head` field set; stop searching if we've
-    ** reached the current root container */
-    if (ctx->container_stack.items[i]->head) { break; }
-  }
-  return 0;
-}
-
-
-void mu_draw_control_frame(mu_Context *ctx, mu_Id id, mu_Rect rect,
-  int colorid, int opt)
-{
-  if (opt & MU_OPT_NOFRAME) { return; }
-  colorid += (ctx->focus == id) ? 2 : (ctx->hover == id) ? 1 : 0;
-  ctx->draw_frame(ctx, rect, colorid);
-}
-
-
-void mu_draw_control_text(mu_Context *ctx, const char *str, mu_Rect rect,
-  int colorid, int opt)
-{
-  mu_Vec2 pos;
-  mu_Font font = ctx->style->font;
-  int tw = ctx->text_width(font, str, -1);
-  mu_push_clip_rect(ctx, rect);
-  pos.y = rect.y + (rect.h - ctx->text_height(font)) / 2;
-  if (opt & MU_OPT_ALIGNCENTER) {
-    pos.x = rect.x + (rect.w - tw) / 2;
-  } else if (opt & MU_OPT_ALIGNRIGHT) {
-    pos.x = rect.x + rect.w - tw - ctx->style->padding;
-  } else {
-    pos.x = rect.x + ctx->style->padding;
-  }
-  mu_draw_text(ctx, font, str, -1, pos, ctx->style->colors[colorid]);
-  mu_pop_clip_rect(ctx);
-}
-
-
-int mu_mouse_over(mu_Context *ctx, mu_Rect rect) {
-  return rect_overlaps_vec2(rect, ctx->mouse_pos) &&
-    rect_overlaps_vec2(mu_get_clip_rect(ctx), ctx->mouse_pos) &&
-    in_hover_root(ctx);
-}
-
-
-void mu_update_control(mu_Context *ctx, mu_Id id, mu_Rect rect, int opt) {
-  int mouseover = mu_mouse_over(ctx, rect);
-
-  if (ctx->focus == id) { ctx->updated_focus = 1; }
-  if (opt & MU_OPT_NOINTERACT) { return; }
-  if (mouseover && !ctx->mouse_down) { ctx->hover = id; }
-
-  if (ctx->focus == id) {
-    if (ctx->mouse_pressed && !mouseover) { mu_set_focus(ctx, 0); }
-    if (!ctx->mouse_down && ~opt & MU_OPT_HOLDFOCUS) { mu_set_focus(ctx, 0); }
-  }
-
-  if (ctx->hover == id) {
-    if (ctx->mouse_pressed) {
-      mu_set_focus(ctx, id);
-    } else if (!mouseover) {
-      ctx->hover = 0;
-    }
-  }
-}
-
-
-void mu_text(mu_Context *ctx, const char *text) {
-  const char *start, *end, *p = text;
-  int width = -1;
-  mu_Font font = ctx->style->font;
-  mu_Color color = ctx->style->colors[MU_COLOR_TEXT];
-  mu_layout_begin_column(ctx);
-  mu_layout_row(ctx, 1, &width, ctx->text_height(font));
-  do {
-    mu_Rect r = mu_layout_next(ctx);
-    int w = 0;
-    start = end = p;
-    do {
-      const char* word = p;
-      while (*p && *p != ' ' && *p != '\n') { p++; }
-      w += ctx->text_width(font, word, p - word);
-      if (w > r.w && end != start) { break; }
-      w += ctx->text_width(font, p, 1);
-      end = p++;
-    } while (*end && *end != '\n');
-    mu_draw_text(ctx, font, start, end - start, mu_vec2(r.x, r.y), color);
-    p = end + 1;
-  } while (*end);
-  mu_layout_end_column(ctx);
-}
-
-
-void mu_label(mu_Context *ctx, const char *text) {
-  mu_draw_control_text(ctx, text, mu_layout_next(ctx), MU_COLOR_TEXT, 0);
-}
-
-
-int mu_button_ex(mu_Context *ctx, const char *label, int icon, int opt) {
-  int res = 0;
-  mu_Id id = label ? mu_get_id(ctx, label, strlen(label))
-                   : mu_get_id(ctx, &icon, sizeof(icon));
-  mu_Rect r = mu_layout_next(ctx);
-  mu_update_control(ctx, id, r, opt);
-  /* handle click */
-  if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) {
-    res |= MU_RES_SUBMIT;
-  }
-  /* draw */
-  mu_draw_control_frame(ctx, id, r, MU_COLOR_BUTTON, opt);
-  if (label) { mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, opt); }
-  if (icon) { mu_draw_icon(ctx, icon, r, ctx->style->colors[MU_COLOR_TEXT]); }
-  return res;
-}
-
-
-int mu_checkbox(mu_Context *ctx, const char *label, int *state) {
-  int res = 0;
-  mu_Id id = mu_get_id(ctx, &state, sizeof(state));
-  mu_Rect r = mu_layout_next(ctx);
-  mu_Rect box = mu_rect(r.x, r.y, r.h, r.h);
-  mu_update_control(ctx, id, r, 0);
-  /* handle click */
-  if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id) {
-    res |= MU_RES_CHANGE;
-    *state = !*state;
-  }
-  /* draw */
-  mu_draw_control_frame(ctx, id, box, MU_COLOR_BASE, 0);
-  if (*state) {
-    mu_draw_icon(ctx, MU_ICON_CHECK, box, ctx->style->colors[MU_COLOR_TEXT]);
-  }
-  r = mu_rect(r.x + box.w, r.y, r.w - box.w, r.h);
-  mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, 0);
-  return res;
-}
-
-
-int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, mu_Id id, mu_Rect r,
-  int opt)
-{
-  int res = 0;
-  mu_update_control(ctx, id, r, opt | MU_OPT_HOLDFOCUS);
-
-  if (ctx->focus == id) {
-    /* handle text input */
-    int len = strlen(buf);
-    int n = mu_min(bufsz - len - 1, (int) strlen(ctx->input_text));
-    if (n > 0) {
-      memcpy(buf + len, ctx->input_text, n);
-      len += n;
-      buf[len] = '\0';
-      res |= MU_RES_CHANGE;
-    }
-    /* handle backspace */
-    if (ctx->key_pressed & MU_KEY_BACKSPACE && len > 0) {
-      /* skip utf-8 continuation bytes */
-      while ((buf[--len] & 0xc0) == 0x80 && len > 0);
-      buf[len] = '\0';
-      res |= MU_RES_CHANGE;
-    }
-    /* handle return */
-    if (ctx->key_pressed & MU_KEY_RETURN) {
-      mu_set_focus(ctx, 0);
-      res |= MU_RES_SUBMIT;
-    }
-  }
-
-  /* draw */
-  mu_draw_control_frame(ctx, id, r, MU_COLOR_BASE, opt);
-  if (ctx->focus == id) {
-    mu_Color color = ctx->style->colors[MU_COLOR_TEXT];
-    mu_Font font = ctx->style->font;
-    int textw = ctx->text_width(font, buf, -1);
-    int texth = ctx->text_height(font);
-    int ofx = r.w - ctx->style->padding - textw - 1;
-    int textx = r.x + mu_min(ofx, ctx->style->padding);
-    int texty = r.y + (r.h - texth) / 2;
-    mu_push_clip_rect(ctx, r);
-    mu_draw_text(ctx, font, buf, -1, mu_vec2(textx, texty), color);
-    mu_draw_rect(ctx, mu_rect(textx + textw, texty, 1, texth), color);
-    mu_pop_clip_rect(ctx);
-  } else {
-    mu_draw_control_text(ctx, buf, r, MU_COLOR_TEXT, opt);
-  }
-
-  return res;
-}
-
-
-static int number_textbox(mu_Context *ctx, mu_Real *value, mu_Rect r, mu_Id id) {
-  if (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->key_down & MU_KEY_SHIFT &&
-      ctx->hover == id
-  ) {
-    ctx->number_edit = id;
-    sprintf(ctx->number_edit_buf, MU_REAL_FMT, *value);
-  }
-  if (ctx->number_edit == id) {
-    int res = mu_textbox_raw(
-      ctx, ctx->number_edit_buf, sizeof(ctx->number_edit_buf), id, r, 0);
-    if (res & MU_RES_SUBMIT || ctx->focus != id) {
-      *value = strtod(ctx->number_edit_buf, NULL);
-      ctx->number_edit = 0;
-    } else {
-      return 1;
-    }
-  }
-  return 0;
-}
-
-
-int mu_textbox_ex(mu_Context *ctx, char *buf, int bufsz, int opt) {
-  mu_Id id = mu_get_id(ctx, &buf, sizeof(buf));
-  mu_Rect r = mu_layout_next(ctx);
-  return mu_textbox_raw(ctx, buf, bufsz, id, r, opt);
-}
-
-
-int mu_slider_ex(mu_Context *ctx, mu_Real *value, mu_Real low, mu_Real high,
-  mu_Real step, const char *fmt, int opt)
-{
-  char buf[MU_MAX_FMT + 1];
-  mu_Rect thumb;
-  int x, w, res = 0;
-  mu_Real last = *value, v = last;
-  mu_Id id = mu_get_id(ctx, &value, sizeof(value));
-  mu_Rect base = mu_layout_next(ctx);
-
-  /* handle text input mode */
-  if (number_textbox(ctx, &v, base, id)) { return res; }
-
-  /* handle normal mode */
-  mu_update_control(ctx, id, base, opt);
-
-  /* handle input */
-  if (ctx->focus == id &&
-      (ctx->mouse_down | ctx->mouse_pressed) == MU_MOUSE_LEFT)
-  {
-    v = low + (ctx->mouse_pos.x - base.x) * (high - low) / base.w;
-    if (step) { v = ((long long)((v + step / 2) / step)) * step; }
-  }
-  /* clamp and store value, update res */
-  *value = v = mu_clamp(v, low, high);
-  if (last != v) { res |= MU_RES_CHANGE; }
-
-  /* draw base */
-  mu_draw_control_frame(ctx, id, base, MU_COLOR_BASE, opt);
-  /* draw thumb */
-  w = ctx->style->thumb_size;
-  x = (v - low) * (base.w - w) / (high - low);
-  thumb = mu_rect(base.x + x, base.y, w, base.h);
-  mu_draw_control_frame(ctx, id, thumb, MU_COLOR_BUTTON, opt);
-  /* draw text  */
-  sprintf(buf, fmt, v);
-  mu_draw_control_text(ctx, buf, base, MU_COLOR_TEXT, opt);
-
-  return res;
-}
-
-
-int mu_number_ex(mu_Context *ctx, mu_Real *value, mu_Real step,
-  const char *fmt, int opt)
-{
-  char buf[MU_MAX_FMT + 1];
-  int res = 0;
-  mu_Id id = mu_get_id(ctx, &value, sizeof(value));
-  mu_Rect base = mu_layout_next(ctx);
-  mu_Real last = *value;
-
-  /* handle text input mode */
-  if (number_textbox(ctx, value, base, id)) { return res; }
-
-  /* handle normal mode */
-  mu_update_control(ctx, id, base, opt);
-
-  /* handle input */
-  if (ctx->focus == id && ctx->mouse_down == MU_MOUSE_LEFT) {
-    *value += ctx->mouse_delta.x * step;
-  }
-  /* set flag if value changed */
-  if (*value != last) { res |= MU_RES_CHANGE; }
-
-  /* draw base */
-  mu_draw_control_frame(ctx, id, base, MU_COLOR_BASE, opt);
-  /* draw text  */
-  sprintf(buf, fmt, *value);
-  mu_draw_control_text(ctx, buf, base, MU_COLOR_TEXT, opt);
-
-  return res;
-}
-
-
-static int header(mu_Context *ctx, const char *label, int istreenode, int opt) {
-  mu_Rect r;
-  int active, expanded;
-  mu_Id id = mu_get_id(ctx, label, strlen(label));
-  int idx = mu_pool_get(ctx, ctx->treenode_pool, MU_TREENODEPOOL_SIZE, id);
-  int width = -1;
-  mu_layout_row(ctx, 1, &width, 0);
-
-  active = (idx >= 0);
-  expanded = (opt & MU_OPT_EXPANDED) ? !active : active;
-  r = mu_layout_next(ctx);
-  mu_update_control(ctx, id, r, 0);
-
-  /* handle click */
-  active ^= (ctx->mouse_pressed == MU_MOUSE_LEFT && ctx->focus == id);
-
-  /* update pool ref */
-  if (idx >= 0) {
-    if (active) { mu_pool_update(ctx, ctx->treenode_pool, idx); }
-           else { memset(&ctx->treenode_pool[idx], 0, sizeof(mu_PoolItem)); }
-  } else if (active) {
-    mu_pool_init(ctx, ctx->treenode_pool, MU_TREENODEPOOL_SIZE, id);
-  }
-
-  /* draw */
-  if (istreenode) {
-    if (ctx->hover == id) { ctx->draw_frame(ctx, r, MU_COLOR_BUTTONHOVER); }
-  } else {
-    mu_draw_control_frame(ctx, id, r, MU_COLOR_BUTTON, 0);
-  }
-  mu_draw_icon(
-    ctx, expanded ? MU_ICON_EXPANDED : MU_ICON_COLLAPSED,
-    mu_rect(r.x, r.y, r.h, r.h), ctx->style->colors[MU_COLOR_TEXT]);
-  r.x += r.h - ctx->style->padding;
-  r.w -= r.h - ctx->style->padding;
-  mu_draw_control_text(ctx, label, r, MU_COLOR_TEXT, 0);
-
-  return expanded ? MU_RES_ACTIVE : 0;
-}
-
-
-int mu_header_ex(mu_Context *ctx, const char *label, int opt) {
-  return header(ctx, label, 0, opt);
-}
-
-
-int mu_begin_treenode_ex(mu_Context *ctx, const char *label, int opt) {
-  int res = header(ctx, label, 1, opt);
-  if (res & MU_RES_ACTIVE) {
-    get_layout(ctx)->indent += ctx->style->indent;
-    push(ctx->id_stack, ctx->last_id);
-  }
-  return res;
-}
-
-
-void mu_end_treenode(mu_Context *ctx) {
-  get_layout(ctx)->indent -= ctx->style->indent;
-  mu_pop_id(ctx);
-}
-
-
-#define scrollbar(ctx, cnt, b, cs, x, y, w, h)                              \
-  do {                                                                      \
-    /* only add scrollbar if content size is larger than body */            \
-    int maxscroll = cs.y - b->h;                                            \
-                                                                            \
-    if (maxscroll > 0 && b->h > 0) {                                        \
-      mu_Rect base, thumb;                                                  \
-      mu_Id id = mu_get_id(ctx, "!scrollbar" #y, 11);                       \
-                                                                            \
-      /* get sizing / positioning */                                        \
-      base = *b;                                                            \
-      base.x = b->x + b->w;                                                 \
-      base.w = ctx->style->scrollbar_size;                                  \
-                                                                            \
-      /* handle input */                                                    \
-      mu_update_control(ctx, id, base, 0);                                  \
-      if (ctx->focus == id && ctx->mouse_down == MU_MOUSE_LEFT) {           \
-        cnt->scroll.y += ctx->mouse_delta.y * cs.y / base.h;                \
-      }                                                                     \
-      /* clamp scroll to limits */                                          \
-      cnt->scroll.y = mu_clamp(cnt->scroll.y, 0, maxscroll);                \
-                                                                            \
-      /* draw base and thumb */                                             \
-      ctx->draw_frame(ctx, base, MU_COLOR_SCROLLBASE);                      \
-      thumb = base;                                                         \
-      thumb.h = mu_max(ctx->style->thumb_size, base.h * b->h / cs.y);       \
-      thumb.y += cnt->scroll.y * (base.h - thumb.h) / maxscroll;            \
-      ctx->draw_frame(ctx, thumb, MU_COLOR_SCROLLTHUMB);                    \
-                                                                            \
-      /* set this as the scroll_target (will get scrolled on mousewheel) */ \
-      /* if the mouse is over it */                                         \
-      if (mu_mouse_over(ctx, *b)) { ctx->scroll_target = cnt; }             \
-    } else {                                                                \
-      cnt->scroll.y = 0;                                                    \
-    }                                                                       \
-  } while (0)
-
-
-static void scrollbars(mu_Context *ctx, mu_Container *cnt, mu_Rect *body) {
-  int sz = ctx->style->scrollbar_size;
-  mu_Vec2 cs = cnt->content_size;
-  cs.x += ctx->style->padding * 2;
-  cs.y += ctx->style->padding * 2;
-  mu_push_clip_rect(ctx, *body);
-  /* resize body to make room for scrollbars */
-  if (cs.y > cnt->body.h) { body->w -= sz; }
-  if (cs.x > cnt->body.w) { body->h -= sz; }
-  /* to create a horizontal or vertical scrollbar almost-identical code is
-  ** used; only the references to `x|y` `w|h` need to be switched */
-  scrollbar(ctx, cnt, body, cs, x, y, w, h);
-  scrollbar(ctx, cnt, body, cs, y, x, h, w);
-  mu_pop_clip_rect(ctx);
-}
-
-
-static void push_container_body(
-  mu_Context *ctx, mu_Container *cnt, mu_Rect body, int opt
-) {
-  if (~opt & MU_OPT_NOSCROLL) { scrollbars(ctx, cnt, &body); }
-  push_layout(ctx, expand_rect(body, -ctx->style->padding), cnt->scroll);
-  cnt->body = body;
-}
-
-
-static void begin_root_container(mu_Context *ctx, mu_Container *cnt) {
-  push(ctx->container_stack, cnt);
-  /* push container to roots list and push head command */
-  push(ctx->root_list, cnt);
-  cnt->head = push_jump(ctx, NULL);
-  /* set as hover root if the mouse is overlapping this container and it has a
-  ** higher zindex than the current hover root */
-  if (rect_overlaps_vec2(cnt->rect, ctx->mouse_pos) &&
-      (!ctx->next_hover_root || cnt->zindex > ctx->next_hover_root->zindex)
-  ) {
-    ctx->next_hover_root = cnt;
-  }
-  /* clipping is reset here in case a root-container is made within
-  ** another root-containers's begin/end block; this prevents the inner
-  ** root-container being clipped to the outer */
-  push(ctx->clip_stack, unclipped_rect);
-}
-
-
-static void end_root_container(mu_Context *ctx) {
-  /* push tail 'goto' jump command and set head 'skip' command. the final steps
-  ** on initing these are done in mu_end() */
-  mu_Container *cnt = mu_get_current_container(ctx);
-  cnt->tail = push_jump(ctx, NULL);
-  cnt->head->jump.dst = ctx->command_list.items + ctx->command_list.idx;
-  /* pop base clip rect and container */
-  mu_pop_clip_rect(ctx);
-  pop_container(ctx);
-}
-
-
-int mu_begin_window_ex(mu_Context *ctx, const char *title, mu_Rect rect, int opt) {
-  mu_Rect body;
-  mu_Id id = mu_get_id(ctx, title, strlen(title));
-  mu_Container *cnt = get_container(ctx, id, opt);
-  if (!cnt || !cnt->open) { return 0; }
-  push(ctx->id_stack, id);
-
-  if (cnt->rect.w == 0) { cnt->rect = rect; }
-  begin_root_container(ctx, cnt);
-  rect = body = cnt->rect;
-
-  /* draw frame */
-  if (~opt & MU_OPT_NOFRAME) {
-    ctx->draw_frame(ctx, rect, MU_COLOR_WINDOWBG);
-  }
-
-  /* do title bar */
-  if (~opt & MU_OPT_NOTITLE) {
-    mu_Rect tr = rect;
-    tr.h = ctx->style->title_height;
-    ctx->draw_frame(ctx, tr, MU_COLOR_TITLEBG);
-
-    /* do title text */
-    if (~opt & MU_OPT_NOTITLE) {
-      mu_Id id = mu_get_id(ctx, "!title", 6);
-      mu_update_control(ctx, id, tr, opt);
-      mu_draw_control_text(ctx, title, tr, MU_COLOR_TITLETEXT, opt);
-      if (id == ctx->focus && ctx->mouse_down == MU_MOUSE_LEFT) {
-        cnt->rect.x += ctx->mouse_delta.x;
-        cnt->rect.y += ctx->mouse_delta.y;
-      }
-      body.y += tr.h;
-      body.h -= tr.h;
-    }
-
-    /* do `close` button */
-    if (~opt & MU_OPT_NOCLOSE) {
-      mu_Id id = mu_get_id(ctx, "!close", 6);
-      mu_Rect r = mu_rect(tr.x + tr.w - tr.h, tr.y, tr.h, tr.h);
-      tr.w -= r.w;
-      mu_draw_icon(ctx, MU_ICON_CLOSE, r, ctx->style->colors[MU_COLOR_TITLETEXT]);
-      mu_update_control(ctx, id, r, opt);
-      if (ctx->mouse_pressed == MU_MOUSE_LEFT && id == ctx->focus) {
-        cnt->open = 0;
-      }
-    }
-  }
-
-  push_container_body(ctx, cnt, body, opt);
-
-  /* do `resize` handle */
-  if (~opt & MU_OPT_NORESIZE) {
-    int sz = ctx->style->title_height;
-    mu_Id id = mu_get_id(ctx, "!resize", 7);
-    mu_Rect r = mu_rect(rect.x + rect.w - sz, rect.y + rect.h - sz, sz, sz);
-    mu_update_control(ctx, id, r, opt);
-    if (id == ctx->focus && ctx->mouse_down == MU_MOUSE_LEFT) {
-      cnt->rect.w = mu_max(96, cnt->rect.w + ctx->mouse_delta.x);
-      cnt->rect.h = mu_max(64, cnt->rect.h + ctx->mouse_delta.y);
-    }
-  }
-
-  /* resize to content size */
-  if (opt & MU_OPT_AUTOSIZE) {
-    mu_Rect r = get_layout(ctx)->body;
-    cnt->rect.w = cnt->content_size.x + (cnt->rect.w - r.w);
-    cnt->rect.h = cnt->content_size.y + (cnt->rect.h - r.h);
-  }
-
-  /* close if this is a popup window and elsewhere was clicked */
-  if (opt & MU_OPT_POPUP && ctx->mouse_pressed && ctx->hover_root != cnt) {
-    cnt->open = 0;
-  }
-
-  mu_push_clip_rect(ctx, cnt->body);
-  return MU_RES_ACTIVE;
-}
-
-
-void mu_end_window(mu_Context *ctx) {
-  mu_pop_clip_rect(ctx);
-  end_root_container(ctx);
-}
-
-
-void mu_open_popup(mu_Context *ctx, const char *name) {
-  mu_Container *cnt = mu_get_container(ctx, name);
-  /* set as hover root so popup isn't closed in begin_window_ex()  */
-  ctx->hover_root = ctx->next_hover_root = cnt;
-  /* position at mouse cursor, open and bring-to-front */
-  cnt->rect = mu_rect(ctx->mouse_pos.x, ctx->mouse_pos.y, 1, 1);
-  cnt->open = 1;
-  mu_bring_to_front(ctx, cnt);
-}
-
-
-int mu_begin_popup(mu_Context *ctx, const char *name) {
-  int opt = MU_OPT_POPUP | MU_OPT_AUTOSIZE | MU_OPT_NORESIZE |
-            MU_OPT_NOSCROLL | MU_OPT_NOTITLE | MU_OPT_CLOSED;
-  return mu_begin_window_ex(ctx, name, mu_rect(0, 0, 0, 0), opt);
-}
-
-
-void mu_end_popup(mu_Context *ctx) {
-  mu_end_window(ctx);
-}
-
-
-void mu_begin_panel_ex(mu_Context *ctx, const char *name, int opt) {
-  mu_Container *cnt;
-  mu_push_id(ctx, name, strlen(name));
-  cnt = get_container(ctx, ctx->last_id, opt);
-  cnt->rect = mu_layout_next(ctx);
-  if (~opt & MU_OPT_NOFRAME) {
-    ctx->draw_frame(ctx, cnt->rect, MU_COLOR_PANELBG);
-  }
-  push(ctx->container_stack, cnt);
-  push_container_body(ctx, cnt, cnt->rect, opt);
-  mu_push_clip_rect(ctx, cnt->body);
-}
-
-
-void mu_end_panel(mu_Context *ctx) {
-  mu_pop_clip_rect(ctx);
-  pop_container(ctx);
-}
blob - 269087cb4d884cf48fdd9510e2446c0282b6248b (mode 644)
blob + /dev/null
--- lib/microui.h
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
-** Copyright (c) 2024 rxi
-**
-** This library is free software; you can redistribute it and/or modify it
-** under the terms of the MIT license. See `microui.c` for details.
-*/
-
-#ifndef MICROUI_H
-#define MICROUI_H
-
-#define MU_VERSION "2.02"
-
-#define MU_COMMANDLIST_SIZE     (256 * 1024)
-#define MU_ROOTLIST_SIZE        32
-#define MU_CONTAINERSTACK_SIZE  32
-#define MU_CLIPSTACK_SIZE       32
-#define MU_IDSTACK_SIZE         32
-#define MU_LAYOUTSTACK_SIZE     16
-#define MU_CONTAINERPOOL_SIZE   48
-#define MU_TREENODEPOOL_SIZE    48
-#define MU_MAX_WIDTHS           16
-#define MU_REAL                 float
-#define MU_REAL_FMT             "%.3g"
-#define MU_SLIDER_FMT           "%.2f"
-#define MU_MAX_FMT              127
-
-#define mu_stack(T, n)          struct { int idx; T items[n]; }
-#define mu_min(a, b)            ((a) < (b) ? (a) : (b))
-#define mu_max(a, b)            ((a) > (b) ? (a) : (b))
-#define mu_clamp(x, a, b)       mu_min(b, mu_max(a, x))
-
-enum {
-  MU_CLIP_PART = 1,
-  MU_CLIP_ALL
-};
-
-enum {
-  MU_COMMAND_JUMP = 1,
-  MU_COMMAND_CLIP,
-  MU_COMMAND_RECT,
-  MU_COMMAND_TEXT,
-  MU_COMMAND_ICON,
-  MU_COMMAND_MAX
-};
-
-enum {
-  MU_COLOR_TEXT,
-  MU_COLOR_BORDER,
-  MU_COLOR_WINDOWBG,
-  MU_COLOR_TITLEBG,
-  MU_COLOR_TITLETEXT,
-  MU_COLOR_PANELBG,
-  MU_COLOR_BUTTON,
-  MU_COLOR_BUTTONHOVER,
-  MU_COLOR_BUTTONFOCUS,
-  MU_COLOR_BASE,
-  MU_COLOR_BASEHOVER,
-  MU_COLOR_BASEFOCUS,
-  MU_COLOR_SCROLLBASE,
-  MU_COLOR_SCROLLTHUMB,
-  MU_COLOR_MAX
-};
-
-enum {
-  MU_ICON_CLOSE = 1,
-  MU_ICON_CHECK,
-  MU_ICON_COLLAPSED,
-  MU_ICON_EXPANDED,
-  MU_ICON_MAX
-};
-
-enum {
-  MU_RES_ACTIVE       = (1 << 0),
-  MU_RES_SUBMIT       = (1 << 1),
-  MU_RES_CHANGE       = (1 << 2)
-};
-
-enum {
-  MU_OPT_ALIGNCENTER  = (1 << 0),
-  MU_OPT_ALIGNRIGHT   = (1 << 1),
-  MU_OPT_NOINTERACT   = (1 << 2),
-  MU_OPT_NOFRAME      = (1 << 3),
-  MU_OPT_NORESIZE     = (1 << 4),
-  MU_OPT_NOSCROLL     = (1 << 5),
-  MU_OPT_NOCLOSE      = (1 << 6),
-  MU_OPT_NOTITLE      = (1 << 7),
-  MU_OPT_HOLDFOCUS    = (1 << 8),
-  MU_OPT_AUTOSIZE     = (1 << 9),
-  MU_OPT_POPUP        = (1 << 10),
-  MU_OPT_CLOSED       = (1 << 11),
-  MU_OPT_EXPANDED     = (1 << 12)
-};
-
-enum {
-  MU_MOUSE_LEFT       = (1 << 0),
-  MU_MOUSE_RIGHT      = (1 << 1),
-  MU_MOUSE_MIDDLE     = (1 << 2)
-};
-
-enum {
-  MU_KEY_SHIFT        = (1 << 0),
-  MU_KEY_CTRL         = (1 << 1),
-  MU_KEY_ALT          = (1 << 2),
-  MU_KEY_BACKSPACE    = (1 << 3),
-  MU_KEY_RETURN       = (1 << 4)
-};
-
-
-typedef struct mu_Context mu_Context;
-typedef unsigned mu_Id;
-typedef MU_REAL mu_Real;
-typedef void* mu_Font;
-
-typedef struct { int x, y; } mu_Vec2;
-typedef struct { int x, y, w, h; } mu_Rect;
-typedef struct { unsigned char r, g, b, a; } mu_Color;
-typedef struct { mu_Id id; int last_update; } mu_PoolItem;
-
-typedef struct { int type, size; } mu_BaseCommand;
-typedef struct { mu_BaseCommand base; void *dst; } mu_JumpCommand;
-typedef struct { mu_BaseCommand base; mu_Rect rect; } mu_ClipCommand;
-typedef struct { mu_BaseCommand base; mu_Rect rect; mu_Color color; } mu_RectCommand;
-typedef struct { mu_BaseCommand base; mu_Font font; mu_Vec2 pos; mu_Color color; char str[1]; } mu_TextCommand;
-typedef struct { mu_BaseCommand base; mu_Rect rect; int id; mu_Color color; } mu_IconCommand;
-
-typedef union {
-  int type;
-  mu_BaseCommand base;
-  mu_JumpCommand jump;
-  mu_ClipCommand clip;
-  mu_RectCommand rect;
-  mu_TextCommand text;
-  mu_IconCommand icon;
-} mu_Command;
-
-typedef struct {
-  mu_Rect body;
-  mu_Rect next;
-  mu_Vec2 position;
-  mu_Vec2 size;
-  mu_Vec2 max;
-  int widths[MU_MAX_WIDTHS];
-  int items;
-  int item_index;
-  int next_row;
-  int next_type;
-  int indent;
-} mu_Layout;
-
-typedef struct {
-  mu_Command *head, *tail;
-  mu_Rect rect;
-  mu_Rect body;
-  mu_Vec2 content_size;
-  mu_Vec2 scroll;
-  int zindex;
-  int open;
-} mu_Container;
-
-typedef struct {
-  mu_Font font;
-  mu_Vec2 size;
-  int padding;
-  int spacing;
-  int indent;
-  int title_height;
-  int scrollbar_size;
-  int thumb_size;
-  mu_Color colors[MU_COLOR_MAX];
-} mu_Style;
-
-struct mu_Context {
-  /* callbacks */
-  int (*text_width)(mu_Font font, const char *str, int len);
-  int (*text_height)(mu_Font font);
-  void (*draw_frame)(mu_Context *ctx, mu_Rect rect, int colorid);
-  /* core state */
-  mu_Style _style;
-  mu_Style *style;
-  mu_Id hover;
-  mu_Id focus;
-  mu_Id last_id;
-  mu_Rect last_rect;
-  int last_zindex;
-  int updated_focus;
-  int frame;
-  mu_Container *hover_root;
-  mu_Container *next_hover_root;
-  mu_Container *scroll_target;
-  char number_edit_buf[MU_MAX_FMT];
-  mu_Id number_edit;
-  /* stacks */
-  mu_stack(char, MU_COMMANDLIST_SIZE) command_list;
-  mu_stack(mu_Container*, MU_ROOTLIST_SIZE) root_list;
-  mu_stack(mu_Container*, MU_CONTAINERSTACK_SIZE) container_stack;
-  mu_stack(mu_Rect, MU_CLIPSTACK_SIZE) clip_stack;
-  mu_stack(mu_Id, MU_IDSTACK_SIZE) id_stack;
-  mu_stack(mu_Layout, MU_LAYOUTSTACK_SIZE) layout_stack;
-  /* retained state pools */
-  mu_PoolItem container_pool[MU_CONTAINERPOOL_SIZE];
-  mu_Container containers[MU_CONTAINERPOOL_SIZE];
-  mu_PoolItem treenode_pool[MU_TREENODEPOOL_SIZE];
-  /* input state */
-  mu_Vec2 mouse_pos;
-  mu_Vec2 last_mouse_pos;
-  mu_Vec2 mouse_delta;
-  mu_Vec2 scroll_delta;
-  int mouse_down;
-  int mouse_pressed;
-  int key_down;
-  int key_pressed;
-  char input_text[256];
-};
-
-
-mu_Vec2 mu_vec2(int x, int y);
-mu_Rect mu_rect(int x, int y, int w, int h);
-mu_Color mu_color(int r, int g, int b, int a);
-
-void mu_init(mu_Context *ctx);
-void mu_begin(mu_Context *ctx);
-void mu_end(mu_Context *ctx);
-void mu_set_focus(mu_Context *ctx, mu_Id id);
-mu_Id mu_get_id(mu_Context *ctx, const void *data, int size);
-void mu_push_id(mu_Context *ctx, const void *data, int size);
-void mu_pop_id(mu_Context *ctx);
-void mu_push_clip_rect(mu_Context *ctx, mu_Rect rect);
-void mu_pop_clip_rect(mu_Context *ctx);
-mu_Rect mu_get_clip_rect(mu_Context *ctx);
-int mu_check_clip(mu_Context *ctx, mu_Rect r);
-mu_Container* mu_get_current_container(mu_Context *ctx);
-mu_Container* mu_get_container(mu_Context *ctx, const char *name);
-void mu_bring_to_front(mu_Context *ctx, mu_Container *cnt);
-
-int mu_pool_init(mu_Context *ctx, mu_PoolItem *items, int len, mu_Id id);
-int mu_pool_get(mu_Context *ctx, mu_PoolItem *items, int len, mu_Id id);
-void mu_pool_update(mu_Context *ctx, mu_PoolItem *items, int idx);
-
-void mu_input_mousemove(mu_Context *ctx, int x, int y);
-void mu_input_mousedown(mu_Context *ctx, int x, int y, int btn);
-void mu_input_mouseup(mu_Context *ctx, int x, int y, int btn);
-void mu_input_scroll(mu_Context *ctx, int x, int y);
-void mu_input_keydown(mu_Context *ctx, int key);
-void mu_input_keyup(mu_Context *ctx, int key);
-void mu_input_text(mu_Context *ctx, const char *text);
-
-mu_Command* mu_push_command(mu_Context *ctx, int type, int size);
-int mu_next_command(mu_Context *ctx, mu_Command **cmd);
-void mu_set_clip(mu_Context *ctx, mu_Rect rect);
-void mu_draw_rect(mu_Context *ctx, mu_Rect rect, mu_Color color);
-void mu_draw_box(mu_Context *ctx, mu_Rect rect, mu_Color color);
-void mu_draw_text(mu_Context *ctx, mu_Font font, const char *str, int len, mu_Vec2 pos, mu_Color color);
-void mu_draw_icon(mu_Context *ctx, int id, mu_Rect rect, mu_Color color);
-
-void mu_layout_row(mu_Context *ctx, int items, const int *widths, int height);
-void mu_layout_width(mu_Context *ctx, int width);
-void mu_layout_height(mu_Context *ctx, int height);
-void mu_layout_begin_column(mu_Context *ctx);
-void mu_layout_end_column(mu_Context *ctx);
-void mu_layout_set_next(mu_Context *ctx, mu_Rect r, int relative);
-mu_Rect mu_layout_next(mu_Context *ctx);
-
-void mu_draw_control_frame(mu_Context *ctx, mu_Id id, mu_Rect rect, int colorid, int opt);
-void mu_draw_control_text(mu_Context *ctx, const char *str, mu_Rect rect, int colorid, int opt);
-int mu_mouse_over(mu_Context *ctx, mu_Rect rect);
-void mu_update_control(mu_Context *ctx, mu_Id id, mu_Rect rect, int opt);
-
-#define mu_button(ctx, label)             mu_button_ex(ctx, label, 0, MU_OPT_ALIGNCENTER)
-#define mu_textbox(ctx, buf, bufsz)       mu_textbox_ex(ctx, buf, bufsz, 0)
-#define mu_slider(ctx, value, lo, hi)     mu_slider_ex(ctx, value, lo, hi, 0, MU_SLIDER_FMT, MU_OPT_ALIGNCENTER)
-#define mu_number(ctx, value, step)       mu_number_ex(ctx, value, step, MU_SLIDER_FMT, MU_OPT_ALIGNCENTER)
-#define mu_header(ctx, label)             mu_header_ex(ctx, label, 0)
-#define mu_begin_treenode(ctx, label)     mu_begin_treenode_ex(ctx, label, 0)
-#define mu_begin_window(ctx, title, rect) mu_begin_window_ex(ctx, title, rect, 0)
-#define mu_begin_panel(ctx, name)         mu_begin_panel_ex(ctx, name, 0)
-
-void mu_text(mu_Context *ctx, const char *text);
-void mu_label(mu_Context *ctx, const char *text);
-int mu_button_ex(mu_Context *ctx, const char *label, int icon, int opt);
-int mu_checkbox(mu_Context *ctx, const char *label, int *state);
-int mu_textbox_raw(mu_Context *ctx, char *buf, int bufsz, mu_Id id, mu_Rect r, int opt);
-int mu_textbox_ex(mu_Context *ctx, char *buf, int bufsz, int opt);
-int mu_slider_ex(mu_Context *ctx, mu_Real *value, mu_Real low, mu_Real high, mu_Real step, const char *fmt, int opt);
-int mu_number_ex(mu_Context *ctx, mu_Real *value, mu_Real step, const char *fmt, int opt);
-int mu_header_ex(mu_Context *ctx, const char *label, int opt);
-int mu_begin_treenode_ex(mu_Context *ctx, const char *label, int opt);
-void mu_end_treenode(mu_Context *ctx);
-int mu_begin_window_ex(mu_Context *ctx, const char *title, mu_Rect rect, int opt);
-void mu_end_window(mu_Context *ctx);
-void mu_open_popup(mu_Context *ctx, const char *name);
-int mu_begin_popup(mu_Context *ctx, const char *name);
-void mu_end_popup(mu_Context *ctx);
-void mu_begin_panel_ex(mu_Context *ctx, const char *name, int opt);
-void mu_end_panel(mu_Context *ctx);
-
-#endif
blob - 77bc7524f26559f5bbcff3aebdc67c26b54a85fc
blob + 29c6ef524419d9497cd8d3aba9f62a8d70c174d9
--- src/aproc.c
+++ src/aproc.c
@@ -7,24 +7,27 @@
 #include "aproc.h"
 
 #define SR 48000
-#define TAIL (SR / 10) 
+#define TAIL (SR / 5)
 #define REF_RING (APROC_FRAME * 32)
+#define REF_MAX    (APROC_FRAME * 8)
+#define REF_TARGET (APROC_FRAME * 2)
 
 static struct {
 	SpeexEchoState *echo;
 	SpeexPreprocessState *pre;
 	int16_t ring[REF_RING];
 	atomic_size_t w, r;
+	int bypass;
 } P;
 
 static size_t ref_avail(void) {
 	return atomic_load_explicit(&P.w, memory_order_acquire)
-	     - atomic_load_explicit(&P.r, memory_order_relaxed);
+		- atomic_load_explicit(&P.r, memory_order_relaxed);
 }
 static void ref_write(const int16_t *src, size_t n) {
 	size_t w = atomic_load_explicit(&P.w, memory_order_relaxed);
 	size_t r = atomic_load_explicit(&P.r, memory_order_acquire);
-	if (REF_RING - (w - r) < n) return;          /* full: drop, silently */
+	if (REF_RING - (w - r) < n) return;
 	for (size_t i = 0; i < n; i++) P.ring[(w + i) % REF_RING] = src[i];
 	atomic_store_explicit(&P.w, w + n, memory_order_release);
 }
@@ -38,6 +41,7 @@ static size_t ref_read(int16_t *dst, size_t n) {
 
 int aproc_init(void) {
 	atomic_store(&P.w, 0); atomic_store(&P.r, 0);
+	P.bypass = getenv("MILNA_NO_APROC") != NULL;
 	P.echo = speex_echo_state_init(APROC_FRAME, TAIL);
 	if (!P.echo) return -1;
 	int sr = SR;
@@ -47,10 +51,10 @@ int aproc_init(void) {
 	if (!P.pre) return -2;
 	speex_preprocess_ctl(P.pre, SPEEX_PREPROCESS_SET_ECHO_STATE, P.echo);
 
-	int on = 1, off = 0;
-	float agc_target = 8000.0f;       
+	int on = 1;
+	float agc_target = 8000.0f;
 	int   agc_max_gain = 20;
-	int   agc_incr = 6, agc_decr = -10; 
+	int   agc_incr = 6, agc_decr = -10;
 	speex_preprocess_ctl(P.pre, SPEEX_PREPROCESS_SET_AGC, &on);
 	speex_preprocess_ctl(P.pre, SPEEX_PREPROCESS_SET_AGC_LEVEL, &agc_target);
 	speex_preprocess_ctl(P.pre, SPEEX_PREPROCESS_SET_AGC_MAX_GAIN, &agc_max_gain);
@@ -60,18 +64,24 @@ int aproc_init(void) {
 	int sup = -25, sup_active = -10;
 	speex_preprocess_ctl(P.pre, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS, &sup);
 	speex_preprocess_ctl(P.pre, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS_ACTIVE,
-	                     &sup_active);
+		&sup_active);
 	speex_preprocess_ctl(P.pre, SPEEX_PREPROCESS_SET_DENOISE, &on);
-	(void)off;
 	return 0;
 }
 
 void aproc_capture(int16_t *pcm) {
-	if (getenv("MILNA_NO_APROC")) return;
+	if (P.bypass) return;
 
-	int16_t ref[APROC_FRAME], clean[APROC_FRAME];
-	size_t got = ref_read(ref, APROC_FRAME);
-	if (got == APROC_FRAME) {
+	size_t avail = ref_avail();
+	if (avail > REF_MAX) {
+		size_t r = atomic_load_explicit(&P.r, memory_order_relaxed);
+		atomic_store_explicit(&P.r, r + (avail - REF_TARGET),
+			memory_order_release);
+		avail = REF_TARGET;
+	}
+	if (avail >= APROC_FRAME) {
+		int16_t ref[APROC_FRAME], clean[APROC_FRAME];
+		ref_read(ref, APROC_FRAME);
 		speex_echo_cancellation(P.echo, pcm, ref, clean);
 		memcpy(pcm, clean, sizeof(clean));
 	}
@@ -80,7 +90,7 @@ void aproc_capture(int16_t *pcm) {
 }
 
 void aproc_playback(const int16_t *pcm) {
-	if (getenv("MILNA_NO_APROC")) return;
+	if (P.bypass) return;
 	ref_write(pcm, APROC_FRAME);
 }
 
blob - 0996740ab5069ded85622c29466c8b130b642c12
blob + 78091faeb1fa6a86b408a1fc6b7c021884a48752
--- src/aproc.h
+++ src/aproc.h
@@ -2,7 +2,7 @@
 #define APROC_H
 #include <stdint.h>
 
-#define APROC_FRAME 480   /* 10ms @ 48k — process in these units */
+#define APROC_FRAME 480
 
 int  aproc_init(void);
 void aproc_free(void);
blob - e14898a920965090469701ef6952ca3b63a750fa
blob + 49aeb5976a05c74d30a9ce9e303bd3f81fac83ff
--- src/audio.c
+++ src/audio.c
@@ -11,160 +11,163 @@
 
 #define SR        48000
 #define CH        1
-#define FRAME     480            /* 10ms @ 48k — opus frame */
+#define FRAME     480
 #define MAX_PKT   4000
-#define RING_SAMP (FRAME * 64)   /* ~1.3s of buffered playback max */
+#define RING_SAMP (FRAME * 64)
 
 static struct {
-	ma_device cap_dev;   /* capture-only device */
-	ma_device play_dev;  /* playback-only device */
+	ma_device cap_dev;
+	ma_device play_dev;
 	OpusEncoder *enc;
-	OpusDecoder *dec[16];          /* one decoder per sender slot */
+	OpusDecoder *dec[16];
 
-	int last_seq[16], expect_seq[16], have_seq[16];   /* FEC loss detect */
+	int expect_seq[16], have_seq[16];
 	audio_sink_fn sink;
-	audio_sink_fn share_sink;      /* screenshare/system audio send path */
+	audio_sink_fn share_sink;
 	void *user;
 	atomic_int muted;
-	atomic_int deafened;   /* silence ALL incoming audio */
-	atomic_int reset_slot[16];  /* undeafen: clear slot state on udp thread */
-	atomic_int gain[32];   /* 0-15 voice vol, 16-31 screenshare vol (percent) */
+	atomic_int deafened;
+	atomic_int reset_slot[32];
+
+	atomic_int gain[32];
 	int gain_init[32];
 
 	_Atomic int mix[RING_SAMP];
-	atomic_size_t r;               /* global read cursor (samples) */
-	size_t wcur[32];               /* per-slot write cursors (0-15 voice, 16-31 share) */
+	atomic_size_t r;
+	size_t wcur[32];
 
-	/* device selection (guarded by being main/ui-thread only) */
 	ma_context ctx;
 	int ctx_up;
 	ma_device_id cap_id, play_id;
 	int have_cap_id, have_play_id;
 	char cap_name[64], play_name[64];
 
-	/* capture accumulator */
 	int16_t cap[FRAME];
 	int cap_n;
 
-	int started;
+	atomic_int started;
 } A;
 
-static int jb_min, jb_def, jb_max, jb_shrink_margin, jb_win;
-static void jb_init_profile(void) {
-	jb_min = FRAME * 4;     /* 40ms floor  — never thinner than this   */
-	jb_def = FRAME * 7;     /* 70ms start  — comfortable opening cushion */
-	jb_max = FRAME * 20;    /* 200ms ceiling under heavy bufferbloat    */
-	jb_shrink_margin = 5;   /* need 50ms consistent surplus to shrink   */
-	jb_win = 750;           /* ~7.5s adaptation: calm, shrinks rarely   */
-}
+enum {
+	JB_MIN = FRAME * 4,
+	JB_DEF = FRAME * 7,
+	JB_MAX = FRAME * 20,
+	JB_SHRINK_MARGIN = 5,
+	JB_WIN = 750,
+	JB_MAXAHEAD = JB_MAX + FRAME * 5
+};
 
-#define MIX_LEAD_MIN (FRAME * 2)   /* 20ms floor (was 10 — too thin for
-                                    * bursty links; caused resync glitch) */
-#define MIX_LEAD_DEF (FRAME * 4)   /* 40ms starting lead per sender */
-#define MIX_LEAD_MAX (FRAME * 10)  /* 100ms ceiling under heavy jitter */
-#define MIX_MAXAHEAD (FRAME * 10)  /* hard backlog cap */
-#define MIX_WIN      500           /* adaptation window: ~5s (slower, calmer) */
-
 static struct {
-	size_t lead[32];               /* current target lead, samples */
-	long minsl[32];                /* min slack seen this window */
-	int cnt[32];                   /* packets this window */
-	int repos[32];                 /* repositions (audible clicks) */
+	size_t lead[32];
+	long minsl[32];
+	int cnt[32];
+	int repos[32];
+	int shrink[32];
+	int drain[32];
 } J;
 
 static void mix_add(int slot, const int16_t *pcm, size_t n) {
 	size_t r = atomic_load_explicit(&A.r, memory_order_acquire);
 	size_t w = A.wcur[slot];
 	int fresh = 0;
-	if (J.lead[slot] == 0) { J.lead[slot] = jb_def;
-	                         J.minsl[slot] = (long)RING_SAMP; fresh = 1; }
+	if (J.lead[slot] == 0) {
+		J.lead[slot] = JB_DEF;
+		J.minsl[slot] = (long)RING_SAMP;
+		fresh = 1;
+	}
 
 	long slack = (long)(w - r);
-	int reposition = fresh || slack < (long)FRAME       /* underran (late) */
-	              || slack > (long)MIX_MAXAHEAD;          /* ran away */
+
+	if (!fresh) {
+		if (J.drain[slot]) {
+			if (slack > (long)J.lead[slot]) return;
+			J.drain[slot] = 0;
+		} else if (slack > JB_MAXAHEAD) {
+			J.drain[slot] = 1;
+			J.repos[slot]++;
+			return;
+		}
+	}
+
+	int reposition = fresh || slack < (long)(FRAME * 2);
 	if (reposition) {
-		if (!fresh && slack < (long)FRAME && J.lead[slot] < jb_max) {
-			J.lead[slot] += FRAME;        /* late: ask for more cushion */
+		if (!fresh && J.lead[slot] < (size_t)JB_MAX) {
+			J.lead[slot] += FRAME;
 			J.repos[slot]++;
 		}
 		w = r + J.lead[slot];
 		slack = (long)J.lead[slot];
 		J.minsl[slot] = (long)RING_SAMP; J.cnt[slot] = 0;
+		J.shrink[slot] = 0;
 	}
 
 	if (slack < J.minsl[slot]) J.minsl[slot] = slack;
-	if (++J.cnt[slot] >= jb_win) {
+	if (++J.cnt[slot] >= JB_WIN) {
 		J.cnt[slot] = 0;
-		if (J.minsl[slot] > (long)(J.lead[slot] + jb_shrink_margin * FRAME) &&
-		    J.lead[slot] > jb_min) {
+		if (J.minsl[slot] > (long)(J.lead[slot] + JB_SHRINK_MARGIN * FRAME) &&
+			J.lead[slot] > (size_t)JB_MIN)
+			J.shrink[slot] = 1;
+		J.minsl[slot] = (long)RING_SAMP;
+	}
+
+	if (J.shrink[slot] && J.lead[slot] > (size_t)JB_MIN) {
+		int quiet = 1;
+		for (size_t i = 0; i < n; i++)
+			if (pcm[i] > 300 || pcm[i] < -300) { quiet = 0; break; }
+		if (quiet) {
+			J.shrink[slot] = 0;
 			J.lead[slot] -= FRAME;
-			J.minsl[slot] = (long)RING_SAMP;
-			return;        /* frame intentionally not written */
+			return;
 		}
-		J.minsl[slot] = (long)RING_SAMP;
 	}
 
 	int g = atomic_load_explicit(&A.gain[slot], memory_order_relaxed);
-	if (g == 0 && !A.gain_init[slot]) g = 100;   /* default unity */
+	if (g == 0 && !A.gain_init[slot]) g = 100;
 	for (size_t i = 0; i < n; i++) {
 		int v = pcm[i] * g / 100;
 		atomic_fetch_add_explicit(&A.mix[(w + i) % RING_SAMP], v,
-		                          memory_order_relaxed);
+			memory_order_relaxed);
 	}
 	A.wcur[slot] = w + n;
 }
 
 static void mix_read(int16_t *dst, size_t n) {
 	size_t r = atomic_load_explicit(&A.r, memory_order_relaxed);
-	int loud = 0;
 	for (size_t i = 0; i < n; i++) {
 		int v = atomic_exchange_explicit(&A.mix[(r + i) % RING_SAMP], 0,
-		                                 memory_order_relaxed);
-		if (v > 500 || v < -500) loud = 1;
+			memory_order_relaxed);
 		dst[i] = (int16_t)(v > 32767 ? 32767 : v < -32768 ? -32768 : v);
 	}
 	atomic_store_explicit(&A.r, r + n, memory_order_release);
-	static int announced;
-	if (loud && !announced) {
-		fprintf(stderr, "[milna] playing remote audio\n");
-		announced = 1;
-	}
 }
 
 static void on_frame(int16_t *pcm) {
+	aproc_capture(pcm);
 	if (atomic_load_explicit(&A.muted, memory_order_relaxed)) return;
-	aproc_capture(pcm);   /* FRAME == APROC_FRAME == 10ms now */
-	{
-		static int mic_live;
-		if (!mic_live)
-			for (int i = 0; i < FRAME; i++)
-				if (pcm[i] > 500 || pcm[i] < -500) {
-					fprintf(stderr, "[milna] mic capturing (signal "
-					        "detected)\n");
-					mic_live = 1;
-					break;
-				}
-	}
 	uint8_t pkt[MAX_PKT];
 	int n = opus_encode(A.enc, pcm, FRAME, pkt, sizeof(pkt));
 	if (n > 0 && A.sink) A.sink(pkt, n, A.user);
 }
 
 static OpusEncoder *share_enc;
-static OpusDecoder *share_dec[16];   /* per-slot stereo music decoders */
+static OpusDecoder *share_dec[16];
+static int share_expect_seq[16], share_have_seq[16];
 static _Atomic int share_audio_on;
 
 int audio_share_start(void) {
-	if (share_enc) return 0;
-	int err;
-	share_enc = opus_encoder_create(SR, 1, OPUS_APPLICATION_AUDIO, &err);
-	if (err != OPUS_OK) { share_enc = NULL; return -1; }
-	opus_encoder_ctl(share_enc, OPUS_SET_BITRATE(32000));
-	opus_encoder_ctl(share_enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC));
-	opus_encoder_ctl(share_enc, OPUS_SET_INBAND_FEC(1));
-	opus_encoder_ctl(share_enc, OPUS_SET_PACKET_LOSS_PERC(10));
+	if (!share_enc) {
+		int err;
+		share_enc = opus_encoder_create(SR, 1, OPUS_APPLICATION_AUDIO, &err);
+		if (err != OPUS_OK) { share_enc = NULL; return -1; }
+		opus_encoder_ctl(share_enc, OPUS_SET_BITRATE(32000));
+		opus_encoder_ctl(share_enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC));
+		opus_encoder_ctl(share_enc, OPUS_SET_INBAND_FEC(1));
+		opus_encoder_ctl(share_enc, OPUS_SET_PACKET_LOSS_PERC(10));
+		fprintf(stderr, "[milna] share audio: encoder up (mono 32k music)\n");
+	} else {
+		opus_encoder_ctl(share_enc, OPUS_RESET_STATE);
+	}
 	atomic_store(&share_audio_on, 1);
-	fprintf(stderr, "[milna] share audio: encoder up (mono 32k music)\n");
 	return 0;
 }
 
@@ -183,28 +186,18 @@ int audio_share_get_gain(int slot) {
 
 void audio_share_stop(void) {
 	atomic_store(&share_audio_on, 0);
-	if (share_enc) { opus_encoder_destroy(share_enc); share_enc = NULL; }
-	for (int i = 0; i < 16; i++)
-		if (share_dec[i]) { opus_decoder_destroy(share_dec[i]); share_dec[i] = NULL; }
 }
 
 void audio_share_feed(const int16_t *stereo, int frames) {
 	if (!atomic_load_explicit(&share_audio_on, memory_order_relaxed)) return;
 	if (!share_enc || frames != FRAME) return;
-	/* downmix interleaved stereo capture -> mono for the 32k mono encoder */
+
 	int16_t mono[FRAME];
 	for (int i = 0; i < FRAME; i++)
 		mono[i] = (int16_t)(((int)stereo[2*i] + stereo[2*i+1]) / 2);
 	uint8_t pkt[MAX_PKT];
 	int n = opus_encode(share_enc, mono, FRAME, pkt, sizeof(pkt));
-	if (n > 0 && A.share_sink) {
-		static int sent;
-		if (sent == 0)
-			fprintf(stderr, "[milna] share audio: first frame SENT (%d b)\n", n);
-		if ((++sent % 500) == 0)   /* ~every 5s */
-			fprintf(stderr, "[milna] share audio: sent %d frames\n", sent);
-		A.share_sink(pkt, n, A.user);
-	}
+	if (n > 0 && A.share_sink) A.share_sink(pkt, n, A.user);
 }
 
 static void cap_cb(ma_device *d, void *out, const void *in, ma_uint32 nframes) {
@@ -215,29 +208,23 @@ static void cap_cb(ma_device *d, void *out, const void
 		if (A.cap_n == FRAME) { on_frame(A.cap); A.cap_n = 0; }
 	}
 }
+static int16_t play_ref[APROC_FRAME];
+static int play_ref_n;
+
 static void play_cb(ma_device *d, void *out, const void *in, ma_uint32 nframes) {
 	(void)d; (void)in;
 	int16_t *spk = (int16_t *)out;
-	static int logged;
-	if (!logged) {
-		fprintf(stderr, "[milna] playback callback: %u frames/call\n",
-		        (unsigned)nframes);
-		logged = 1;
-	}
-	mix_read(spk, nframes);   /* drains nframes samples; grid-independent */
+	mix_read(spk, nframes);
 
 	if (atomic_load_explicit(&A.deafened, memory_order_relaxed))
 		memset(spk, 0, (size_t)nframes * sizeof(int16_t));
 
-	static int16_t ref[APROC_FRAME];
-	static int ref_n = 0;
 	for (ma_uint32 i = 0; i < nframes; i++) {
-		ref[ref_n++] = spk[i];
-		if (ref_n == APROC_FRAME) { aproc_playback(ref); ref_n = 0; }
+		play_ref[play_ref_n++] = spk[i];
+		if (play_ref_n == APROC_FRAME) { aproc_playback(play_ref); play_ref_n = 0; }
 	}
 }
 
-/* ---- public api ---- */
 static int ensure_ctx(void) {
 	if (A.ctx_up) return 0;
 	if (ma_context_init(NULL, 0, NULL, &A.ctx) != MA_SUCCESS) return -1;
@@ -264,14 +251,14 @@ static int select_apply(int capture, const ma_device_i
 		return 0;
 	if (capture) {
 		if (info) { A.cap_id = info->id; A.have_cap_id = 1;
-		            snprintf(A.cap_name, 64, "%s", info->name); }
+			snprintf(A.cap_name, 64, "%s", info->name); }
 		else      { A.have_cap_id = 0; A.cap_name[0] = 0; }
 	} else {
 		if (info) { A.play_id = info->id; A.have_play_id = 1;
-		            snprintf(A.play_name, 64, "%s", info->name); }
+			snprintf(A.play_name, 64, "%s", info->name); }
 		else      { A.have_play_id = 0; A.play_name[0] = 0; }
 	}
-	if (A.started) {                 /* live switch: restart on new device */
+	if (atomic_load_explicit(&A.started, memory_order_relaxed)) {
 		audio_sink_fn s = A.sink; void *u = A.user;
 		int muted = atomic_load(&A.muted);
 		audio_stop();
@@ -279,7 +266,7 @@ static int select_apply(int capture, const ma_device_i
 		atomic_store(&A.muted, muted);
 		if (r != 0)
 			fprintf(stderr, "[milna] AUDIO RESTART FAILED (%d) — "
-			        "no devices running\n", r);
+				"no devices running\n", r);
 		return r;
 	}
 	return 0;
@@ -308,7 +295,7 @@ int audio_select_by_name(int capture, const char *name
 	for (int i = 0; i < n; i++)
 		if (!strcmp(src[i].name, name))
 			return select_apply(capture, &src[i]);
-	return -1;   /* device not present right now; default stays */
+	return -1;
 }
 
 const char *audio_sel_name(int capture) {
@@ -317,17 +304,18 @@ const char *audio_sel_name(int capture) {
 
 int audio_start(audio_sink_fn sink, void *user) {
 	int err;
-	jb_init_profile();   /* fixed stability-leaning jitter bounds */
 	memset(&A.cap, 0, sizeof(A.cap));
+	memset(play_ref, 0, sizeof(play_ref)); play_ref_n = 0;
 	A.sink = sink; A.user = user; A.cap_n = 0;
 	atomic_store(&A.muted, 0);
 
 	A.enc = opus_encoder_create(SR, CH, OPUS_APPLICATION_VOIP, &err);
 	if (err != OPUS_OK) return -1;
-	opus_encoder_ctl(A.enc, OPUS_SET_BITRATE(32000));   /* fuller voice */
+	opus_encoder_ctl(A.enc, OPUS_SET_BITRATE(32000));
 	opus_encoder_ctl(A.enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE));
 	opus_encoder_ctl(A.enc, OPUS_SET_INBAND_FEC(1));
-	opus_encoder_ctl(A.enc, OPUS_SET_PACKET_LOSS_PERC(25));
+	opus_encoder_ctl(A.enc, OPUS_SET_PACKET_LOSS_PERC(15));
+
 	opus_encoder_ctl(A.enc, OPUS_SET_DTX(0));
 	if (aproc_init() != 0) return -5;
 	atomic_store(&A.r, 0);
@@ -336,6 +324,9 @@ int audio_start(audio_sink_fn sink, void *user) {
 	memset(&J, 0, sizeof(J));
 	for (int i = 0; i < 32; i++) { A.gain[i] = 100; A.gain_init[i] = 1; }
 
+	for (int i = 0; i < 32; i++)
+		atomic_store_explicit(&A.reset_slot[i], 1, memory_order_relaxed);
+
 	if (ensure_ctx() != 0) return -7;
 	ma_device_config cc = ma_device_config_init(ma_device_type_capture);
 	if (A.have_cap_id) cc.capture.pDeviceID = &A.cap_id;
@@ -360,76 +351,76 @@ int audio_start(audio_sink_fn sink, void *user) {
 
 	if (ma_device_start(&A.cap_dev)  != MA_SUCCESS) return -4;
 	if (ma_device_start(&A.play_dev) != MA_SUCCESS) return -4;
-	A.started = 1;
+	atomic_store_explicit(&A.started, 1, memory_order_release);
 	return 0;
 }
 
-/* incoming screenshare/system audio (wire type 4): decode stereo music and
- * mix into playback. separate decoders from voice; downmix to mono into the
- * existing mono mix ring (the mixer is mono). */
-void audio_share_recv(int slot, const uint8_t *opus, int len) {
-	if (slot < 0 || slot >= 16) return;
-	if (!share_dec[slot]) {
-		int err;
-		share_dec[slot] = opus_decoder_create(SR, 1, &err);
-		if (err != OPUS_OK) { share_dec[slot] = NULL; return; }
-	}
-	int16_t mono[FRAME];
-	int n = opus_decode(share_dec[slot], opus, len, mono, FRAME, 0);
-	if (n <= 0) return;
-	{
-		static int rcvd;
-		if (rcvd == 0)
-			fprintf(stderr, "[milna] share audio: first frame RECEIVED "
-			        "from slot %d (%d samples)\n", slot, n);
-		if ((++rcvd % 500) == 0)
-			fprintf(stderr, "[milna] share audio: received %d frames\n", rcvd);
-	}
-	/* mix into a SEPARATE slot (slot+16) so screenshare audio never shares
-	 * a write cursor with the sender's voice (that collision = static). */
-	mix_add(slot + 16, mono, (size_t)n);
-}
+#define MAXDEC 5760
 
-void audio_recv_packet(int slot, int seq, const uint8_t *opus, int len) {
-	if (slot < 0 || slot >= 16) return;
-	/* pending reset from an undeafen: clear this slot's jitter + decoder
-	 * here on the udp thread so we never reset a decoder mid-use. */
-	if (atomic_exchange_explicit(&A.reset_slot[slot], 0, memory_order_relaxed)) {
-		J.lead[slot] = 0; J.minsl[slot] = 0; J.cnt[slot] = 0;
-		A.wcur[slot] = 0; A.have_seq[slot] = 0;
-		if (A.dec[slot]) opus_decoder_ctl(A.dec[slot], OPUS_RESET_STATE);
+static int recv_decode(OpusDecoder **dp, int mixslot, int *have, int *expect,
+		int seq, const uint8_t *opus, int len, int16_t *pcm, int maxdec) {
+	if (atomic_exchange_explicit(&A.reset_slot[mixslot], 0, memory_order_relaxed)) {
+		J.lead[mixslot] = 0; J.minsl[mixslot] = 0; J.cnt[mixslot] = 0;
+		J.shrink[mixslot] = 0; J.drain[mixslot] = 0;
+		A.wcur[mixslot] = 0; *have = 0;
+		if (*dp) opus_decoder_ctl(*dp, OPUS_RESET_STATE);
 	}
-	if (!A.dec[slot]) {
+	if (!*dp) {
 		int err;
-		A.dec[slot] = opus_decoder_create(SR, CH, &err);
-		if (err != OPUS_OK) { A.dec[slot] = NULL; return; }
+		*dp = opus_decoder_create(SR, 1, &err);
+		if (err != OPUS_OK) { *dp = NULL; return 0; }
 	}
-	enum { MAXDEC = 5760 };           /* 120ms @ 48k, opus maximum */
-	static int16_t pcm[MAXDEC];       /* udp thread only */
-
-	if (A.have_seq[slot] && seq >= 0) {
-		int gap = seq - A.expect_seq[slot];
+	if (*have && seq >= 0) {
+		int gap = seq - *expect;
 		if (gap >= 2 && gap <= 5) {
 			for (int g = 0; g < gap - 1; g++) {
-				int cn = opus_decode(A.dec[slot], NULL, 0, pcm, FRAME, 0);
-				if (cn > 0) mix_add(slot, pcm, (size_t)cn);
+				int cn = opus_decode(*dp, NULL, 0, pcm, FRAME, 0);
+				if (cn > 0) mix_add(mixslot, pcm, (size_t)cn);
 			}
 		}
 		if (gap == 1 || (gap >= 2 && gap <= 5)) {
-			int fn = opus_decode(A.dec[slot], opus, len, pcm, MAXDEC, 1);
-			if (fn > 0) mix_add(slot, pcm, (size_t)fn);
+			int fn = opus_decode(*dp, opus, len, pcm, maxdec, 1);
+			if (fn > 0) mix_add(mixslot, pcm, (size_t)fn);
 		}
 	}
+	int n = opus_decode(*dp, opus, len, pcm, maxdec, 0);
+	if (n <= 0) return 0;
+	*expect = seq + 1;
+	*have = 1;
+	mix_add(mixslot, pcm, (size_t)n);
+	return n;
+}
 
-	int n = opus_decode(A.dec[slot], opus, len, pcm, MAXDEC, 0);
-	if (n <= 0) return;
-	A.expect_seq[slot] = seq + 1;
-	A.have_seq[slot] = 1;
-	mix_add(slot, pcm, (size_t)n);
+void audio_share_recv(int slot, int seq, const uint8_t *opus, int len) {
+	int16_t mono[FRAME];
+	if (slot < 0 || slot >= 16) return;
+	if (!atomic_load_explicit(&A.started, memory_order_acquire)) return;
+	int n = recv_decode(&share_dec[slot], slot + 16, &share_have_seq[slot],
+		&share_expect_seq[slot], seq, opus, len, mono, FRAME);
+	if (n > 0) {
+		static int rcvd;
+		if (!rcvd++)
+			fprintf(stderr, "[milna] share audio: first frame RECEIVED "
+				"from slot %d (%d samples)\n", slot, n);
+	}
 }
 
+void audio_recv_packet(int slot, int seq, const uint8_t *opus, int len) {
+	static int16_t pcm[MAXDEC];
+	if (slot < 0 || slot >= 16) return;
+	if (!atomic_load_explicit(&A.started, memory_order_acquire)) return;
+	recv_decode(&A.dec[slot], slot, &A.have_seq[slot], &A.expect_seq[slot],
+		seq, opus, len, pcm, MAXDEC);
+}
+
 void audio_set_muted(int m) { atomic_store_explicit(&A.muted, m, memory_order_relaxed); }
 
+void audio_reset_slot(int slot) {
+	if (slot < 0 || slot >= 16) return;
+	atomic_store_explicit(&A.reset_slot[slot], 1, memory_order_relaxed);
+	atomic_store_explicit(&A.reset_slot[slot + 16], 1, memory_order_relaxed);
+}
+
 void audio_set_deafened(int d) {
 	int was = atomic_load_explicit(&A.deafened, memory_order_relaxed);
 	atomic_store_explicit(&A.deafened, d, memory_order_relaxed);
@@ -448,17 +439,15 @@ void audio_set_gain(int slot, int percent) {
 int audio_get_gain(int slot) {
 	if (slot < 0 || slot >= 16) return 100;
 	return A.gain_init[slot] ? atomic_load_explicit(&A.gain[slot],
-	                              memory_order_relaxed) : 100;
+		memory_order_relaxed) : 100;
 }
 
 void audio_stop(void) {
-	if (!A.started) return;
-	A.started = 0;
+	if (!atomic_exchange_explicit(&A.started, 0, memory_order_acq_rel)) return;
 	ma_device_uninit(&A.cap_dev);
 	ma_device_uninit(&A.play_dev);
 	if (A.enc) opus_encoder_destroy(A.enc);
 	A.enc = NULL;
-	for (int i = 0; i < 16; i++)
-		if (A.dec[i]) { opus_decoder_destroy(A.dec[i]); A.dec[i] = NULL; }
+
 	aproc_free();
 }
blob - 408a48c76823efb76b1017247cb02e474b87196d
blob + f8a46ba8d7c85a978d8025637f2faa8051766b69
--- src/audio.h
+++ src/audio.h
@@ -9,17 +9,18 @@ int  audio_start(audio_sink_fn sink, void *user);
 void audio_stop(void);
 
 void audio_recv_packet(int slot, int seq, const uint8_t *opus, int len);
+void audio_reset_slot(int slot);
 
 int  audio_share_start(void);
 void audio_share_stop(void);
 void audio_share_feed(const int16_t *stereo, int frames);
-void audio_share_recv(int slot, const uint8_t *opus, int len);
+void audio_share_recv(int slot, int seq, const uint8_t *opus, int len);
 void audio_share_set_sink(audio_sink_fn fn);
-void audio_share_set_gain(int slot, int pct); /* change screenshare volume */
+void audio_share_set_gain(int slot, int pct);
 int  audio_share_get_gain(int slot);
 
 void audio_set_muted(int muted);
-void audio_set_deafened(int deaf); 
+void audio_set_deafened(int deaf);
 
 void audio_set_gain(int slot, int percent);
 int  audio_get_gain(int slot);
blob - eaee93dda687494f6417bef4e99b09e14b3af610
blob + 8d6e89c79f0348ce16286b5f78c4a793a2569d2b
--- src/cam.h
+++ src/cam.h
@@ -1,6 +1,6 @@
 #ifndef CAM_H
 #define CAM_H
-int  cam_start(void);    /* 0 ok; may prompt for camera permission */
+int  cam_start(void);
 void cam_stop(void);
 int  cam_active(void);
 #endif
blob - 2c94a2f60fba43f3fba947f1334629d09c2951ac
blob + 8a00e30f47107ac44b8f265d14e2cd4727f6aafe
--- src/cam_mac.m
+++ src/cam_mac.m
@@ -31,8 +31,8 @@ static struct {
 
 @implementation MilnaCamSink
 - (void)captureOutput:(AVCaptureOutput *)output
-    didOutputSampleBuffer:(CMSampleBufferRef)sb
-           fromConnection:(AVCaptureConnection *)conn {
+	didOutputSampleBuffer:(CMSampleBufferRef)sb
+	fromConnection:(AVCaptureConnection *)conn {
 	(void)output; (void)conn;
 	if (!C.active) return;
 	CVImageBufferRef pb = CMSampleBufferGetImageBuffer(sb);
@@ -43,8 +43,8 @@ static struct {
 	if (w < 16 || h < 16) return;
 
 	if (!C.enc_up) {
-		C.hi = vid_enc_new(w, h, CAM_FPS, milna_vbitrate(CAM_KBPS));
-		C.lo = vid_enc_new(w / 2, h / 2, CAM_FPS, milna_vbitrate(CAM_KBPS_LO));
+		C.hi = vid_enc_new(w, h, CAM_FPS, CAM_KBPS);
+		C.lo = vid_enc_new(w / 2, h / 2, CAM_FPS, CAM_KBPS_LO);
 		C.frame = malloc((size_t)w * h * 4);
 		C.frame_lo = malloc((size_t)(w / 2) * (h / 2) * 4);
 		C.enc = malloc(1024 * 1024);
@@ -56,52 +56,41 @@ static struct {
 		C.w = w; C.h = h; C.enc_up = 1; C.first = 1;
 		CLOG("camera up: %dx%d@%d", w, h, CAM_FPS);
 	}
-	if (w != C.w || h != C.h) return;   /* mode changed mid-flight: skip */
+	if (w != C.w || h != C.h) return;
 
 	CVPixelBufferLockBaseAddress(pb, kCVPixelBufferLock_ReadOnly);
 	const uint8_t *src = CVPixelBufferGetBaseAddress(pb);
 	size_t stride = CVPixelBufferGetBytesPerRow(pb);
 	for (int y = 0; y < C.h; y++)
 		memcpy(C.frame + (size_t)y * C.w * 4, src + y * stride,
-		       (size_t)C.w * 4);
+			(size_t)C.w * 4);
 	CVPixelBufferUnlockBaseAddress(pb, kCVPixelBufferLock_ReadOnly);
 
-	ui_local_video(C.frame, C.w, C.h, 0);   /* own tile shows own camera */
+	ui_local_video(C.frame, C.w, C.h, 0);
 
 	int key = 0;
 	int force = C.first || net_keyframe_wanted();
 	C.first = 0;
 	int n = vid_enc_frame(C.hi, C.frame, force, C.enc, 1024 * 1024, &key);
-	if (n > 0) net_send_video(C.enc, n, key, 0, 1);   /* camera, high */
+	if (n > 0) net_send_video(C.enc, n, key, 0, 1);
 
-	int lw = C.w / 2, lh = C.h / 2;
-	for (int y = 0; y < lh; y++) {
-		const uint8_t *r0 = C.frame + (size_t)(y * 2) * C.w * 4;
-		const uint8_t *r1 = r0 + (size_t)C.w * 4;
-		uint8_t *d = C.frame_lo + (size_t)y * lw * 4;
-		for (int x = 0; x < lw; x++) {
-			const uint8_t *a = r0 + x * 8, *b = a + 4;
-			const uint8_t *c2 = r1 + x * 8, *e = c2 + 4;
-			for (int k = 0; k < 4; k++)
-				d[x * 4 + k] = (uint8_t)((a[k] + b[k] + c2[k] + e[k]) >> 2);
-		}
-	}
+	bgra_half(C.frame, C.w, C.h, C.frame_lo);
 	n = vid_enc_frame(C.lo, C.frame_lo, force, C.enc, 1024 * 1024, &key);
-	if (n > 0) net_send_video(C.enc, n, key, 0, 0);   /* camera, low */
+	if (n > 0) net_send_video(C.enc, n, key, 0, 0);
 }
 @end
 
 static void cam_session_start(void) {
 	AVCaptureDevice *dev =
-	    [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
+		[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
 	if (!dev) { CLOG("no camera found"); C.active = 0; return; }
 
 	NSError *err = nil;
 	AVCaptureDeviceInput *in =
-	    [AVCaptureDeviceInput deviceInputWithDevice:dev error:&err];
+		[AVCaptureDeviceInput deviceInputWithDevice:dev error:&err];
 	if (!in) {
 		CLOG("camera input: %s",
-		     err ? err.localizedDescription.UTF8String : "failed");
+			err ? err.localizedDescription.UTF8String : "failed");
 		C.active = 0;
 		return;
 	}
@@ -120,7 +109,7 @@ static void cam_session_start(void) {
 	[s addOutput:out];
 	[out release];
 
-	if ([dev lockForConfiguration:NULL]) {   /* cap to CAM_FPS if allowed */
+	if ([dev lockForConfiguration:NULL]) {
 		@try {
 			dev.activeVideoMinFrameDuration = CMTimeMake(1, CAM_FPS);
 			dev.activeVideoMaxFrameDuration = CMTimeMake(1, CAM_FPS);
@@ -138,7 +127,7 @@ int cam_start(void) {
 	if (!C.sink) C.sink = [MilnaCamSink new];
 	C.active = 1;
 	[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
-	                         completionHandler:^(BOOL ok) {
+		completionHandler:^(BOOL ok) {
 		if (!ok) {
 			CLOG("camera permission denied");
 			C.active = 0;
@@ -157,7 +146,7 @@ void cam_stop(void) {
 		[s stopRunning];
 		[s release];
 	}
-	if (C.q) dispatch_sync(C.q, ^{});   /* drain in-flight frame handlers */
+	if (C.q) dispatch_sync(C.q, ^{});
 	if (C.enc_up) {
 		vid_enc_del(C.hi); C.hi = NULL;
 		vid_enc_del(C.lo); C.lo = NULL;
blob - ec9ccd602bc5424fddfdd176ad968371d1343c8c
blob + f1e76f864fc5cfe3e3b6ea16c9668641cc912f94
--- src/cam_win.c
+++ src/cam_win.c
@@ -24,33 +24,11 @@ static struct {
 	volatile int run, active;
 } C;
 
-/* NV12 (camera native) -> BGRA. cameras commonly deliver NV12 or YUY2;
- * we request NV12 from the reader so this one path covers it. */
-static void nv12_to_bgra(const uint8_t *Y, const uint8_t *UV,
-                         int w, int h, int stride, uint8_t *dst) {
-	for (int y = 0; y < h; y++) {
-		const uint8_t *yr = Y + (size_t)y * stride;
-		const uint8_t *uv = UV + (size_t)(y / 2) * stride;
-		uint8_t *d = dst + (size_t)y * w * 4;
-		for (int x = 0; x < w; x++) {
-			int c = yr[x] - 16;
-			int u = uv[(x & ~1)] - 128, v = uv[(x & ~1) + 1] - 128;
-			int r = (298 * c + 409 * v + 128) >> 8;
-			int g = (298 * c - 100 * u - 208 * v + 128) >> 8;
-			int b = (298 * c + 516 * u + 128) >> 8;
-			d[x*4]   = (uint8_t)(b<0?0:b>255?255:b);
-			d[x*4+1] = (uint8_t)(g<0?0:g>255?255:g);
-			d[x*4+2] = (uint8_t)(r<0?0:r>255?255:r);
-			d[x*4+3] = 0xff;
-		}
-	}
-}
-
 static IMFSourceReader *open_camera(int *w, int *h) {
 	IMFAttributes *cfg = NULL;
 	MFCreateAttributes(&cfg, 1);
 	IMFAttributes_SetGUID(cfg, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
-	    &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
+		&MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
 
 	IMFActivate **devs = NULL; UINT32 ndev = 0;
 	if (FAILED(MFEnumDeviceSources(cfg, &devs, &ndev)) || ndev == 0) {
@@ -75,21 +53,20 @@ static IMFSourceReader *open_camera(int *w, int *h) {
 	}
 	IMFMediaSource_Release(src);
 
-	/* request NV12 output (the reader inserts a converter if needed) */
 	IMFMediaType *mt = NULL;
 	MFCreateMediaType(&mt);
 	IMFMediaType_SetGUID(mt, &MF_MT_MAJOR_TYPE, &MFMediaType_Video);
 	IMFMediaType_SetGUID(mt, &MF_MT_SUBTYPE, &MFVideoFormat_NV12);
 	HRESULT hr = IMFSourceReader_SetCurrentMediaType(rd,
-	    (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, mt);
+		(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, mt);
 	IMFMediaType_Release(mt);
 	if (FAILED(hr)) { CLOG("camera NV12 not accepted: 0x%lx", hr);
-	                  IMFSourceReader_Release(rd); return NULL; }
+		IMFSourceReader_Release(rd); return NULL; }
 
 	IMFMediaType *cur = NULL;
 	UINT32 cw = 640, ch = 480;
 	if (SUCCEEDED(IMFSourceReader_GetCurrentMediaType(rd,
-	        (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, &cur))) {
+		(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, &cur))) {
 		UINT64 sz = 0;
 		IMFMediaType_GetUINT64(cur, &MF_MT_FRAME_SIZE, &sz);
 		cw = (UINT32)(sz >> 32); ch = (UINT32)sz;
@@ -108,8 +85,8 @@ static DWORD WINAPI cam_main(LPVOID arg) {
 	uint8_t *frame = malloc((size_t)w * h * 4);
 	uint8_t *frame_lo = malloc((size_t)(w / 2) * (h / 2) * 4);
 	uint8_t *enc = malloc(1024 * 1024);
-	vid_enc *hi = vid_enc_new(w, h, CAM_FPS, milna_vbitrate(CAM_KBPS));
-	vid_enc *lo = vid_enc_new(w / 2, h / 2, CAM_FPS, milna_vbitrate(CAM_KBPS_LO));
+	vid_enc *hi = vid_enc_new(w, h, CAM_FPS, CAM_KBPS);
+	vid_enc *lo = vid_enc_new(w / 2, h / 2, CAM_FPS, CAM_KBPS_LO);
 	if (!frame || !frame_lo || !enc || !hi || !lo) { CLOG("cam init failed"); goto out; }
 	CLOG("camera up: %dx%d@%d", w, h, CAM_FPS);
 
@@ -118,7 +95,7 @@ static DWORD WINAPI cam_main(LPVOID arg) {
 		DWORD flags = 0; LONGLONG ts = 0; DWORD sidx = 0;
 		IMFSample *s = NULL;
 		HRESULT hr = IMFSourceReader_ReadSample(rd,
-		    (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, &sidx, &flags, &ts, &s);
+			(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, &sidx, &flags, &ts, &s);
 		if (FAILED(hr)) break;
 		if (!s) { if (flags & MF_SOURCE_READERF_ENDOFSTREAM) break; continue; }
 
@@ -126,10 +103,10 @@ static DWORD WINAPI cam_main(LPVOID arg) {
 		if (SUCCEEDED(IMFSample_ConvertToContiguousBuffer(s, &buf))) {
 			BYTE *p; DWORD max, len;
 			if (SUCCEEDED(IMFMediaBuffer_Lock(buf, &p, &max, &len))) {
-				nv12_to_bgra(p, p + (size_t)w * h, w, h, w, frame);
+				vid_nv12_to_bgra(p, w, p + (size_t)w * h, w, w, h, frame);
 				IMFMediaBuffer_Unlock(buf);
 
-				ui_local_video(frame, w, h, 0);   /* own tile preview */
+				ui_local_video(frame, w, h, 0);
 
 				int key = 0;
 				int force = first || net_keyframe_wanted();
@@ -137,17 +114,7 @@ static DWORD WINAPI cam_main(LPVOID arg) {
 				int n = vid_enc_frame(hi, frame, force, enc, 1024*1024, &key);
 				if (n > 0) net_send_video(enc, n, key, 0, 1);
 
-				int lw = w / 2, lh = h / 2;
-				for (int y = 0; y < lh; y++) {
-					const uint8_t *r0 = frame + (size_t)(y*2)*w*4;
-					const uint8_t *r1 = r0 + (size_t)w*4;
-					uint8_t *d = frame_lo + (size_t)y*lw*4;
-					for (int x = 0; x < lw; x++) {
-						const uint8_t *a=r0+x*8,*b=a+4,*c2=r1+x*8,*e2=c2+4;
-						for (int k=0;k<4;k++)
-							d[x*4+k]=(uint8_t)((a[k]+b[k]+c2[k]+e2[k])>>2);
-					}
-				}
+				bgra_half(frame, w, h, frame_lo);
 				n = vid_enc_frame(lo, frame_lo, force, enc, 1024*1024, &key);
 				if (n > 0) net_send_video(enc, n, key, 0, 0);
 			}
blob - d031ad1ea19beb67497e7cd2936cafd93922caf6
blob + e353ebbd2f13b745092c624556f2f0affebe8323
--- src/cap_mac.m
+++ src/cap_mac.m
@@ -15,7 +15,7 @@
 #define CAP_FPS  12
 #define CAP_KBPS    1200
 #define CAP_KBPS_LO 300
-#define CAP_MAXW 1280   /* scale capture down to at most this (gpu-side) */
+#define CAP_MAXW 1280
 
 @interface MilnaCap : NSObject <SCStreamOutput, SCStreamDelegate>
 @end
@@ -24,26 +24,26 @@ static struct {
 	SCStream *stream;
 	MilnaCap *sink;
 	dispatch_queue_t q;
-	dispatch_queue_t aq;   /* separate queue for audio output */
-	vid_enc *venc;           /* high layer: capture res */
-	vid_enc *venc_lo;        /* low layer: half res (simulcast) */
-	uint8_t *frame;          /* tight bgra staging */
-	uint8_t *frame_lo;       /* half-res staging */
-	uint8_t *enc;            /* encoded output staging */
+	dispatch_queue_t aq;
+	vid_enc *venc;
+	vid_enc *venc_lo;
+	uint8_t *frame;
+	uint8_t *frame_lo;
+	uint8_t *enc;
 	int w, h;
 	int enc_up;
 	int first;
 	volatile int active;
-	int16_t aud[480 * 2];   /* interleaved stereo accumulator (CAP_AFRAME) */
+	int16_t aud[480 * 2];
 	int aud_n;
 	int audio_on;
 } C;
-#define CAP_AFRAME 480 
+#define CAP_AFRAME 480
 
 @implementation MilnaCap
 - (void)stream:(SCStream *)stream
-    didOutputSampleBuffer:(CMSampleBufferRef)sb
-    ofType:(SCStreamOutputType)type {
+	didOutputSampleBuffer:(CMSampleBufferRef)sb
+	ofType:(SCStreamOutputType)type {
 	(void)stream;
 	if (type == SCStreamOutputTypeAudio) {
 		if (!C.active || !CMSampleBufferIsValid(sb)) return;
@@ -51,8 +51,8 @@ static struct {
 		AudioBufferList *ablp = &ablx.l;
 		CMBlockBufferRef block = NULL;
 		OSStatus s = CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(
-		    sb, NULL, ablp, sizeof(ablx), NULL, NULL,
-		    kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &block);
+			sb, NULL, ablp, sizeof(ablx), NULL, NULL,
+			kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment, &block);
 		if (s != noErr || !block) {
 			static int e1; if (!e1++) CLOG("audio buf extract failed: %d", (int)s);
 			return;
@@ -63,15 +63,15 @@ static struct {
 		int nch = (int)abl.mBuffers[0].mNumberChannels;
 		static int dbg;
 		if (!dbg++) CLOG("AUDIO ARRIVING: %d buffers, ch=%d, frames=%d "
-		                 "(buf0 bytes=%u)", (int)ablp->mNumberBuffers, nch,
-		                 nframes, (unsigned)abl.mBuffers[0].mDataByteSize);
+			"(buf0 bytes=%u)", (int)ablp->mNumberBuffers, nch,
+			nframes, (unsigned)abl.mBuffers[0].mDataByteSize);
 		if (f && nframes > 0) {
 			for (int i = 0; i < nframes; i++) {
 				float l, r;
-				if (ablp->mNumberBuffers >= 2) {         /* planar stereo */
+				if (ablp->mNumberBuffers >= 2) {
 					l = ((const float *)ablp->mBuffers[0].mData)[i];
 					r = ((const float *)ablp->mBuffers[1].mData)[i];
-				} else {                                  /* interleaved */
+				} else {
 					l = f[i * nch];
 					r = nch > 1 ? f[i * nch + 1] : l;
 				}
@@ -91,15 +91,15 @@ static struct {
 	if (type != SCStreamOutputTypeScreen || !C.active) return;
 	if (!CMSampleBufferIsValid(sb)) return;
 	CVPixelBufferRef pb = CMSampleBufferGetImageBuffer(sb);
-	if (!pb) return;   /* status-only buffers (idle screen) have no image */
+	if (!pb) return;
 
 	int w = (int)CVPixelBufferGetWidth(pb) & ~1;
 	int h = (int)CVPixelBufferGetHeight(pb) & ~1;
 	if (w < 16 || h < 16) return;
 
 	if (!C.enc_up) {
-		C.venc = vid_enc_new(w, h, CAP_FPS, milna_vbitrate(CAP_KBPS));
-		C.venc_lo = vid_enc_new(w / 2, h / 2, CAP_FPS, milna_vbitrate(CAP_KBPS_LO));
+		C.venc = vid_enc_new(w, h, CAP_FPS, CAP_KBPS);
+		C.venc_lo = vid_enc_new(w / 2, h / 2, CAP_FPS, CAP_KBPS_LO);
 		if (!C.venc || !C.venc_lo) {
 			CLOG("encoder init failed"); C.active = 0; return;
 		}
@@ -110,7 +110,7 @@ static struct {
 		if (!C.frame || !C.enc) { C.active = 0; return; }
 		CLOG("sharing %dx%d@%d", w, h, CAP_FPS);
 	}
-	if (w != C.w || h != C.h) return;   /* ignore size changes mid-stream */
+	if (w != C.w || h != C.h) return;
 
 	CVPixelBufferLockBaseAddress(pb, kCVPixelBufferLock_ReadOnly);
 	const uint8_t *s = CVPixelBufferGetBaseAddress(pb);
@@ -119,39 +119,26 @@ static struct {
 		memcpy(C.frame + (size_t)y * C.w * 4, s + y * stride, (size_t)C.w * 4);
 	CVPixelBufferUnlockBaseAddress(pb, kCVPixelBufferLock_ReadOnly);
 
-	ui_local_video(C.frame, C.w, C.h, 1);   /* sharer sees own share */
+	ui_local_video(C.frame, C.w, C.h, 1);
 
 	int key = 0;
 	int force = C.first || net_keyframe_wanted();
 	C.first = 0;
-	/* high layer */
+
 	int n = vid_enc_frame(C.venc, C.frame, force, C.enc, 2 * 1024 * 1024, &key);
 	if (n > 0) net_send_video(C.enc, n, key, 1, 1);
-	/* low layer: 2x2 box downsample, then encode */
 	if (C.frame_lo) {
-		int lw = C.w / 2, lh = C.h / 2;
-		for (int y = 0; y < lh; y++) {
-			const uint8_t *r0 = C.frame + (size_t)(y * 2) * C.w * 4;
-			const uint8_t *r1 = r0 + (size_t)C.w * 4;
-			uint8_t *d = C.frame_lo + (size_t)y * lw * 4;
-			for (int x = 0; x < lw; x++) {
-				const uint8_t *a = r0 + x * 8, *b = a + 4;
-				const uint8_t *c2 = r1 + x * 8, *e = c2 + 4;
-				for (int k = 0; k < 4; k++)
-					d[x * 4 + k] =
-					    (uint8_t)((a[k] + b[k] + c2[k] + e[k]) >> 2);
-			}
-		}
+		bgra_half(C.frame, C.w, C.h, C.frame_lo);
 		n = vid_enc_frame(C.venc_lo, C.frame_lo, force, C.enc,
-		                  2 * 1024 * 1024, &key);
+			2 * 1024 * 1024, &key);
 		if (n > 0) net_send_video(C.enc, n, key, 1, 0);
 	}
 }
 - (void)stream:(SCStream *)stream didStopWithError:(NSError *)error {
-	if (stream != C.stream) return;   
+	if (stream != C.stream) return;
 
 	CLOG("capture stopped: %s",
-	     error ? error.localizedDescription.UTF8String : "ok");
+		error ? error.localizedDescription.UTF8String : "ok");
 	C.active = 0;
 	[C.stream release];
 	C.stream = nil;
@@ -167,18 +154,18 @@ int cap_start(void) {
 	if (!C.sink) C.sink = [MilnaCap new];
 
 	[SCShareableContent getShareableContentWithCompletionHandler:
-	 ^(SCShareableContent *content, NSError *err) {
+		^(SCShareableContent *content, NSError *err) {
 		if (err || !content.displays.count) {
 			CLOG("no shareable content (screen recording permission?): %s",
-			     err ? err.localizedDescription.UTF8String : "no displays");
+				err ? err.localizedDescription.UTF8String : "no displays");
 			C.active = 0;
 			return;
 		}
 		SCDisplay *disp = content.displays.firstObject;
 
 		SCContentFilter *filter =
-		    [[SCContentFilter alloc] initWithDisplay:disp
-		                            excludingWindows:@[]];
+			[[SCContentFilter alloc] initWithDisplay:disp
+			excludingWindows:@[]];
 
 		int dw = (int)disp.width, dh = (int)disp.height;
 		if (dw > CAP_MAXW) { dh = dh * CAP_MAXW / dw; dw = CAP_MAXW; }
@@ -196,32 +183,32 @@ int cap_start(void) {
 		cfg.channelCount = 2;
 
 		SCStream *st = [[SCStream alloc] initWithFilter:filter
-		                                  configuration:cfg
-		                                       delegate:C.sink];
+			configuration:cfg
+			delegate:C.sink];
 		[filter release];
 		[cfg release];
 		NSError *aerr = nil;
 		[st addStreamOutput:C.sink
-		               type:SCStreamOutputTypeScreen
-		 sampleHandlerQueue:C.q
-		              error:&aerr];
+			type:SCStreamOutputTypeScreen
+			sampleHandlerQueue:C.q
+			error:&aerr];
 		if (aerr) { CLOG("addStreamOutput: %s", aerr.localizedDescription.UTF8String);
-		            C.active = 0; return; }
+			C.active = 0; return; }
 		@try {
 			NSError *auerr = nil;
 			[st addStreamOutput:C.sink
-			               type:SCStreamOutputTypeAudio
-			 sampleHandlerQueue:C.aq
-			              error:&auerr];
+				type:SCStreamOutputTypeAudio
+				sampleHandlerQueue:C.aq
+				error:&auerr];
 			if (auerr) {
 				CLOG("audio output (non-fatal): %s",
-				     auerr.localizedDescription.UTF8String);
+					auerr.localizedDescription.UTF8String);
 			} else {
 				audio_share_start(); C.audio_on = 1;
 			}
 		} @catch (NSException *ex) {
 			CLOG("audio output exception (non-fatal): %s",
-			     ex.reason.UTF8String);
+				ex.reason.UTF8String);
 		}
 		[st startCaptureWithCompletionHandler:^(NSError *serr) {
 			if (serr) {
@@ -246,7 +233,7 @@ void cap_stop(void) {
 			dispatch_semaphore_signal(sem);
 		}];
 		dispatch_semaphore_wait(sem,
-		    dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC));
+			dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC));
 		[st release];
 	}
 	if (C.q) dispatch_sync(C.q, ^{});
blob - a1ea5797d101220cedeee4aa00496c59c83762a4
blob + 5ff168c51b18e7c496f7858a61ba52236aa851bf
--- src/cap_stub.c
+++ src/cap_stub.c
@@ -1,4 +1,3 @@
-/* linux doesnt have compatability for screensharing yet, stub is left */
 #include "cap.h"
 int  cap_start(void) { return -1; }
 void cap_stop(void) {}
blob - 2234aa90c2625a0e46839d8903cf4676a82011db
blob + c4702418e9c97d68b5993a7e3628bd354b1dc99c
--- src/cap_win.c
+++ src/cap_win.c
@@ -20,11 +20,11 @@
 
 static struct {
 	HANDLE th;
-	HANDLE ath;            /* WASAPI loopback audio thread */
+	HANDLE ath;
 	volatile int run, active;
 } C;
 
-#define CAP_AFRAME 480   /* must equal audio FRAME (10ms @ 48k) */
+#define CAP_AFRAME 480
 
 static DWORD WINAPI cap_audio_main(LPVOID arg) {
 	(void)arg;
@@ -36,35 +36,35 @@ static DWORD WINAPI cap_audio_main(LPVOID arg) {
 	WAVEFORMATEX *wf = NULL;
 
 	const CLSID CLSID_MMDeviceEnumerator_ =
-	    {0xbcde0395,0xe52f,0x467c,{0x8e,0x3d,0xc4,0x57,0x92,0x91,0x69,0x2e}};
+		{0xbcde0395,0xe52f,0x467c,{0x8e,0x3d,0xc4,0x57,0x92,0x91,0x69,0x2e}};
 	const IID IID_IMMDeviceEnumerator_ =
-	    {0xa95664d2,0x9614,0x4f35,{0xa7,0x46,0xde,0x8d,0xb6,0x36,0x17,0xe6}};
+		{0xa95664d2,0x9614,0x4f35,{0xa7,0x46,0xde,0x8d,0xb6,0x36,0x17,0xe6}};
 	const IID IID_IAudioClient_ =
-	    {0x1cb9ad4c,0xdbfa,0x4c32,{0xb1,0x78,0xc2,0xf5,0x68,0xa7,0x03,0xb2}};
+		{0x1cb9ad4c,0xdbfa,0x4c32,{0xb1,0x78,0xc2,0xf5,0x68,0xa7,0x03,0xb2}};
 	const IID IID_IAudioCaptureClient_ =
-	    {0xc8adbd64,0xe71e,0x48a0,{0xa4,0xde,0x18,0x5c,0x39,0x5c,0xd3,0x17}};
+		{0xc8adbd64,0xe71e,0x48a0,{0xa4,0xde,0x18,0x5c,0x39,0x5c,0xd3,0x17}};
 
 	if (CoCreateInstance(&CLSID_MMDeviceEnumerator_, NULL, CLSCTX_ALL,
-	        &IID_IMMDeviceEnumerator_, (void **)&en) != S_OK) goto done;
-	/* eRender + eConsole = the default speakers; loopback captures its mix */
+		&IID_IMMDeviceEnumerator_, (void **)&en) != S_OK) goto done;
+
 	if (en->lpVtbl->GetDefaultAudioEndpoint(en, eRender, eConsole, &dev) != S_OK)
 		goto done;
 	if (dev->lpVtbl->Activate(dev, &IID_IAudioClient_, CLSCTX_ALL, NULL,
-	        (void **)&ac) != S_OK) goto done;
+		(void **)&ac) != S_OK) goto done;
 	if (ac->lpVtbl->GetMixFormat(ac, &wf) != S_OK) goto done;
-	/* loopback mode: AUDCLNT_STREAMFLAGS_LOOPBACK */
+
 	if (ac->lpVtbl->Initialize(ac, AUDCLNT_SHAREMODE_SHARED,
-	        0x00020000 /* LOOPBACK */, 10000000, 0, wf, NULL) != S_OK) goto done;
+		0x00020000  , 10000000, 0, wf, NULL) != S_OK) goto done;
 	if (ac->lpVtbl->GetService(ac, &IID_IAudioCaptureClient_,
-	        (void **)&cc) != S_OK) goto done;
+		(void **)&cc) != S_OK) goto done;
 	if (ac->lpVtbl->Start(ac) != S_OK) goto done;
 
 	int srcrate = wf->nSamplesPerSec;
 	int srcch   = wf->nChannels;
-	int isfloat = (wf->wFormatTag == 3 /* IEEE_FLOAT */) ||
-	    (wf->wFormatTag == 0xFFFE /* EXTENSIBLE */ && wf->wBitsPerSample == 32);
+	int isfloat = (wf->wFormatTag == 3  ) ||
+		(wf->wFormatTag == 0xFFFE   && wf->wBitsPerSample == 32);
 	CLOG("loopback: %d Hz, %d ch, %s", srcrate, srcch,
-	     isfloat ? "float" : "int16");
+		isfloat ? "float" : "int16");
 
 	static int16_t acc[CAP_AFRAME];
 	int accn = 0;
@@ -79,9 +79,8 @@ static DWORD WINAPI cap_audio_main(LPVOID arg) {
 		if (cc->lpVtbl->GetBuffer(cc, &data, &frames, &flags, NULL, NULL) != S_OK)
 			break;
 		for (UINT32 i = 0; i < frames; i++) {
-			/* downmix this source frame to one mono int16 sample */
 			int32_t m = 0;
-			if (flags & 2 /* SILENT */) {
+			if (flags & 2  ) {
 				m = 0;
 			} else if (isfloat) {
 				const float *fp = (const float *)data + (size_t)i * srcch;
@@ -93,7 +92,7 @@ static DWORD WINAPI cap_audio_main(LPVOID arg) {
 				int32_t s = 0; for (int c = 0; c < srcch; c++) s += sp[c];
 				m = s / srcch;
 			}
-			/* resample srcrate->48k by simple linear interpolation */
+
 			int16_t cur = (int16_t)m;
 			phase += 1.0;
 			while (phase >= step) {
@@ -134,7 +133,7 @@ static DWORD WINAPI cap_main(LPVOID arg) {
 	BITMAPINFO bi = {0};
 	bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
 	bi.bmiHeader.biWidth = sw;
-	bi.bmiHeader.biHeight = -sh;            /* top-down */
+	bi.bmiHeader.biHeight = -sh;
 	bi.bmiHeader.biPlanes = 1;
 	bi.bmiHeader.biBitCount = 32;
 	bi.bmiHeader.biCompression = BI_RGB;
@@ -145,8 +144,8 @@ static DWORD WINAPI cap_main(LPVOID arg) {
 	uint8_t *frame = malloc((size_t)dw * dh * 4);
 	uint8_t *frame_lo = malloc((size_t)(dw / 2) * (dh / 2) * 4);
 	uint8_t *enc = malloc(2 * 1024 * 1024);
-	vid_enc *hi = vid_enc_new(dw, dh, CAP_FPS, milna_vbitrate(CAP_KBPS));
-	vid_enc *lo = vid_enc_new(dw / 2, dh / 2, CAP_FPS, milna_vbitrate(CAP_KBPS_LO));
+	vid_enc *hi = vid_enc_new(dw, dh, CAP_FPS, CAP_KBPS);
+	vid_enc *lo = vid_enc_new(dw / 2, dh / 2, CAP_FPS, CAP_KBPS_LO);
 	if (!frame || !frame_lo || !enc || !hi || !lo || !shot) {
 		CLOG("capture init failed");
 		goto out;
@@ -161,9 +160,8 @@ static DWORD WINAPI cap_main(LPVOID arg) {
 		CURSORINFO ci = { sizeof(ci) };
 		if (GetCursorInfo(&ci) && (ci.flags & CURSOR_SHOWING))
 			DrawIconEx(mdc, ci.ptScreenPos.x, ci.ptScreenPos.y,
-			           ci.hCursor, 0, 0, 0, NULL, DI_NORMAL);
+				ci.hCursor, 0, 0, 0, NULL, DI_NORMAL);
 
-		/* nearest-neighbour scale screen -> frame */
 		const uint32_t *sp = shot;
 		for (int y = 0; y < dh; y++) {
 			const uint32_t *srow = sp + (size_t)(y * sh / dh) * sw;
@@ -172,7 +170,7 @@ static DWORD WINAPI cap_main(LPVOID arg) {
 				drow[x] = srow[x * sw / dw];
 		}
 
-		ui_local_video(frame, dw, dh, 1);   /* sharer sees own share */
+		ui_local_video(frame, dw, dh, 1);
 
 		int key = 0;
 		int force = first || net_keyframe_wanted();
@@ -180,18 +178,7 @@ static DWORD WINAPI cap_main(LPVOID arg) {
 		int n = vid_enc_frame(hi, frame, force, enc, 2 * 1024 * 1024, &key);
 		if (n > 0) net_send_video(enc, n, key, 1, 1);
 
-		for (int y = 0; y < dh / 2; y++) {       /* 2x2 box downsample */
-			const uint8_t *r0 = frame + (size_t)(y * 2) * dw * 4;
-			const uint8_t *r1 = r0 + (size_t)dw * 4;
-			uint8_t *d = frame_lo + (size_t)y * (dw / 2) * 4;
-			for (int x = 0; x < dw / 2; x++) {
-				const uint8_t *a = r0 + x * 8, *b = a + 4;
-				const uint8_t *c2 = r1 + x * 8, *e2 = c2 + 4;
-				for (int k = 0; k < 4; k++)
-					d[x * 4 + k] =
-					    (uint8_t)((a[k] + b[k] + c2[k] + e2[k]) >> 2);
-			}
-		}
+		bgra_half(frame, dw, dh, frame_lo);
 		n = vid_enc_frame(lo, frame_lo, force, enc, 2 * 1024 * 1024, &key);
 		if (n > 0) net_send_video(enc, n, key, 1, 0);
 
blob - f4be7e7ba70fcd8af0ccd731c37145d295c74eb6
blob + a5fe3d5e1a89347a0e543a502809e2ac6f811744
--- src/milna.h
+++ src/milna.h
@@ -1,34 +1,10 @@
 #ifndef MILNA_H
 #define MILNA_H
 
-#define MILNA_VERSION "1.9" 
+#define MILNA_VERSION "1.9"
 #include <stdint.h>
 
-typedef struct {
-	uint32_t *px; 
-	int w, h;
-} fb;
-
-enum { M_NONE, M_MOVE, M_DOWN, M_UP, M_TEXT, M_KEY_DOWN, M_KEY_UP };
-typedef struct {
-	int kind;
-	int x, y;
-	int btn;
-	int key;
-	char text[8];     
-} in_event;
-
-void ui_init(void);
-void ui_input(const in_event *e);
-void ui_frame(fb *f);
-void ui_mark_dirty(void);
-
 void ui_local_video(const uint8_t *bgra, int w, int h, int is_screen);
 void streams_drop_slot(int slot);
 
-int  milna_vbitrate(int base_kbps);
-int  ui_needs_redraw(void);
-
-void plat_invalidate(void);
-
 #endif
blob - 01fbc553eb4f576a0ac8584701a4d1142bdf174b
blob + b1ad8927b9ce2b1dda8e0ece4aef21c440719811
--- src/net.c
+++ src/net.c
@@ -26,6 +26,9 @@ static void net_os_init(void) {}
 #endif
 #include <pthread.h>
 #include <stdatomic.h>
+#ifndef MSG_DONTWAIT
+#define MSG_DONTWAIT 0
+#endif
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
@@ -38,7 +41,7 @@ static void net_os_init(void) {}
 #define LOG(...) do { fprintf(stderr, "[milna] " __VA_ARGS__); fputc('\n', stderr); } while (0)
 
 #define MAX_PEERS 16
-#define VCHUNK    1200            /* udp-safe video chunk payload */
+#define VCHUNK    1200
 #define VFRAME_MAX (768 * 1024)
 #define HDR 8
 
@@ -54,7 +57,7 @@ static int json_get(const char *json, const char *key,
 		if (*q == '\\' && q[1]) {
 			q++;
 			buf[o++] = (*q == 'n') ? '\n' : (*q == 'r') ? '\r' :
-			           (*q == 't') ? '\t' : *q;
+				(*q == 't') ? '\t' : *q;
 		} else buf[o++] = *q;
 		q++;
 	}
@@ -82,47 +85,76 @@ static void json_escape(const char *in, char *out, siz
 	out[o] = 0;
 }
 
-/* ---- peer table (slot-indexed; roster + crypto keys) ---- */
 static struct peer {
 	int used;
 	char id[32], name[32];
-	uint8_t key[crypto_secretbox_KEYBYTES];   /* their sender key */
+	uint8_t key[crypto_secretbox_KEYBYTES];
 	uint8_t nbase[16];
 	int have_key;
-	/* video reassembly, per source: [0]=camera [1]=screen */
 	struct vasm { uint8_t *buf; int seq, len, got, cnt, key; } va[2];
-	long last_kfreq;                          /* rate limit, seconds */
-	int muted, deafened;                      /* their announced a/v state */
+	long last_kfreq;
+	int muted, deafened;
 } P[MAX_PEERS];
 
 static struct {
-	int tcp, udp;                 /* sockets */
-	struct sockaddr_in raddr;     /* relay addr (for udp) */
+	int tcp, udp;
+	struct sockaddr_in raddr;
 	pthread_t t_tcp, t_udp, t_tick;
 	volatile int run;
-	pthread_mutex_t mu;           /* guards P[], roster ops, tcp writes */
+	pthread_mutex_t mu;
 
 	char self_id[32];
 	int self_slot;
 	uint8_t token[16];
-	int udp_ok;                   /* hello echoed back */
+	int udp_ok;
 
-	/* my keys */
 	uint8_t pk[crypto_box_PUBLICKEYBYTES], sk[crypto_box_SECRETKEYBYTES];
-	uint8_t skey[crypto_secretbox_KEYBYTES];  /* my sender key */
+	uint8_t skey[crypto_secretbox_KEYBYTES];
 	uint8_t snbase[16];
-	atomic_uint aseq, vseq, saseq;   /* per-type packet sequence (saseq = screenshare audio) */
+	atomic_uint aseq, vseq, saseq;
 	atomic_int kf_wanted;
-	atomic_int v_abandons;        /* incomplete video frames (loss signal) */
-	int want;                     /* simulcast layer we receive (1 = high) */
-	int want_high;                /* user/pin asked for high quality */
 } N = { .tcp = -1, .udp = -1, .self_slot = -1 };
 
 static net_video_fn v_handler;
 static void *v_user;
 
-/* ---- helpers ---- */
-static void tcp_line(const char *line) {       /* send one control line */
+#define VQ_CAP 8
+static struct vq_item { uint8_t *buf; int len, key, src, slot; } vq[VQ_CAP];
+static int vq_r, vq_n, vq_run;
+static pthread_mutex_t vq_mu = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t  vq_cv = PTHREAD_COND_INITIALIZER;
+static pthread_t t_dec;
+
+static void *dec_main(void *arg) {
+	(void)arg;
+	for (;;) {
+		pthread_mutex_lock(&vq_mu);
+		while (vq_run && vq_n == 0) pthread_cond_wait(&vq_cv, &vq_mu);
+		if (vq_n == 0) { pthread_mutex_unlock(&vq_mu); return NULL; }
+		struct vq_item it = vq[vq_r];
+		vq_r = (vq_r + 1) % VQ_CAP; vq_n--;
+		pthread_mutex_unlock(&vq_mu);
+		if (v_handler) v_handler(it.slot, it.buf, it.len, it.key, it.src, v_user);
+		free(it.buf);
+	}
+}
+
+static int vq_push(int slot, uint8_t *buf, int len, int key, int src) {
+	int queued = -1;
+	pthread_mutex_lock(&vq_mu);
+	if (vq_run && vq_n < VQ_CAP) {
+		struct vq_item *it = &vq[(vq_r + vq_n) % VQ_CAP];
+		it->buf = buf; it->len = len; it->key = key;
+		it->src = src; it->slot = slot;
+		vq_n++;
+		pthread_cond_signal(&vq_cv);
+		queued = 0;
+	}
+	pthread_mutex_unlock(&vq_mu);
+	return queued;
+}
+
+static void tcp_line(const char *line) {
 	pthread_mutex_lock(&N.mu);
 	if (N.tcp >= 0) {
 		size_t n = strlen(line);
@@ -133,29 +165,28 @@ static void tcp_line(const char *line) {       /* send
 }
 
 static void mk_nonce(uint8_t n24[24], const uint8_t nbase[16],
-                     uint32_t seq, uint8_t type) {
+	uint32_t seq, uint8_t type) {
 	memcpy(n24, nbase, 16);
 	n24[16] = seq; n24[17] = seq >> 8; n24[18] = seq >> 16; n24[19] = seq >> 24;
 	n24[20] = type; n24[21] = n24[22] = n24[23] = 0;
 }
 
 static void udp_send_media(uint8_t type, uint8_t flags,
-                           const uint8_t *pt, int len) {
+	const uint8_t *pt, int len) {
 	if (!N.udp_ok || N.self_slot < 0) return;
 	uint32_t seq = (type == 2) ? atomic_fetch_add(&N.aseq, 1)
-	             : (type == 4) ? atomic_fetch_add(&N.saseq, 1)
-	             :               atomic_fetch_add(&N.vseq, 1);
+		: (type == 4) ? atomic_fetch_add(&N.saseq, 1)
+		:               atomic_fetch_add(&N.vseq, 1);
 	uint8_t pkt[HDR + VCHUNK + 64 + crypto_secretbox_MACBYTES];
 	if (HDR + len + (int)crypto_secretbox_MACBYTES > (int)sizeof(pkt)) return;
 	pkt[0] = 1; pkt[1] = type; pkt[2] = (uint8_t)N.self_slot; pkt[3] = flags;
 	pkt[4] = seq; pkt[5] = seq >> 8; pkt[6] = seq >> 16; pkt[7] = seq >> 24;
 	uint8_t nonce[24]; mk_nonce(nonce, N.snbase, seq, type);
 	crypto_secretbox_easy(pkt + HDR, pt, (unsigned long long)len, nonce, N.skey);
-	sendto(N.udp, (const char *)pkt, HDR + len + crypto_secretbox_MACBYTES, 0,
-	       (struct sockaddr *)&N.raddr, sizeof(N.raddr));
+	sendto(N.udp, (const char *)pkt, HDR + len + crypto_secretbox_MACBYTES,
+		MSG_DONTWAIT, (struct sockaddr *)&N.raddr, sizeof(N.raddr));
 }
 
-/* the audio sink: one opus packet per call, from the audio thread */
 static void net_sink(const uint8_t *opus, int len, void *user) {
 	(void)user;
 	static int first = 1;
@@ -163,15 +194,13 @@ static void net_sink(const uint8_t *opus, int len, voi
 	udp_send_media(2, 0, opus, len);
 }
 
-/* screenshare/system audio sink: wire type 4 (music stream, separate from
- * voice type 2). uses its own sequence so it doesn't disturb voice FEC. */
 static void net_share_sink(const uint8_t *opus, int len, void *user) {
 	(void)user;
 	udp_send_media(4, 0, opus, len);
 }
 
 int net_send_video(const uint8_t *frame, int len, int is_key, int is_screen,
-                   int layer) {
+	int layer) {
 	if (!N.udp_ok) return -1;
 	if (layer < 0 || layer > 3) layer = 0;
 	static uint16_t fseq_[2][4];
@@ -188,7 +217,8 @@ int net_send_video(const uint8_t *frame, int len, int 
 		chunk[7] = (uint8_t)((is_screen ? 0x10 : 0) | layer);
 		memcpy(chunk + 8, frame + i * (VCHUNK - 8), n);
 		udp_send_media(3, (uint8_t)((is_key ? 1 : 0) | (layer << 1)),
-		               chunk, n + 8);
+			chunk, n + 8);
+		if ((i & 31) == 31) usleep(1000);
 	}
 	return 0;
 }
@@ -218,7 +248,7 @@ int net_peer_state(int slot) {
 	if (slot < 0 || slot >= MAX_PEERS) return 0;
 	pthread_mutex_lock(&N.mu);
 	int s = (P[slot].used ? (P[slot].muted ? 1 : 0) |
-	                        (P[slot].deafened ? 2 : 0) : 0);
+		(P[slot].deafened ? 2 : 0) : 0);
 	pthread_mutex_unlock(&N.mu);
 	return s;
 }
@@ -229,8 +259,8 @@ void net_announce_state(int muted, int deaf) {
 	if (N.tcp < 0) return;
 	char line[128];
 	snprintf(line, sizeof(line),
-	         "{\"type\":\"state\",\"muted\":%d,\"deaf\":%d}", muted ? 1 : 0,
-	         deaf ? 1 : 0);
+		"{\"type\":\"state\",\"muted\":%d,\"deaf\":%d}", muted ? 1 : 0,
+		deaf ? 1 : 0);
 	tcp_line(line);
 }
 
@@ -242,10 +272,10 @@ static void send_my_key(const char *peer_id, const uin
 	crypto_box_seal(sealed, pt, sizeof(pt), peer_pk);
 	char b64[256];
 	sodium_bin2base64(b64, sizeof(b64), sealed, sizeof(sealed),
-	                  sodium_base64_VARIANT_ORIGINAL);
+		sodium_base64_VARIANT_ORIGINAL);
 	char line[400];
 	snprintf(line, sizeof(line),
-	         "{\"type\":\"key\",\"to\":\"%s\",\"blob\":\"%s\"}", peer_id, b64);
+		"{\"type\":\"key\",\"to\":\"%s\",\"blob\":\"%s\"}", peer_id, b64);
 	tcp_line(line);
 }
 
@@ -261,7 +291,7 @@ static void peer_add(const char *json) {
 	uint8_t pk[crypto_box_PUBLICKEYBYTES];
 	size_t pkn;
 	if (sodium_base642bin(pk, sizeof(pk), pub, strlen(pub), NULL, &pkn,
-	        NULL, sodium_base64_VARIANT_ORIGINAL) != 0 || pkn != sizeof(pk))
+		NULL, sodium_base64_VARIANT_ORIGINAL) != 0 || pkn != sizeof(pk))
 		return;
 
 	pthread_mutex_lock(&N.mu);
@@ -273,8 +303,6 @@ static void peer_add(const char *json) {
 	pthread_mutex_unlock(&N.mu);
 
 	send_my_key(id, pk);
-	/* tell the newcomer my current mute/deafen state so their roster shows
-	 * the right badge immediately (state msgs are otherwise only on change) */
 	if (my_muted || my_deaf) net_announce_state(my_muted, my_deaf);
 	LOG("%s in slot %d", p->name, slot);
 }
@@ -287,7 +315,8 @@ static void peer_del(const char *json) {
 	free(P[slot].va[0].buf); free(P[slot].va[1].buf);
 	memset(&P[slot], 0, sizeof(P[slot]));
 	pthread_mutex_unlock(&N.mu);
-	streams_drop_slot(slot);   /* free this peer's video decoders (leak fix) */
+	streams_drop_slot(slot);
+	audio_reset_slot(slot);
 	LOG("slot %d left", slot);
 }
 
@@ -301,23 +330,19 @@ static void on_ctl(const char *json) {
 		if (json_get(json, "token", tok, sizeof(tok))) {
 			size_t tn;
 			sodium_base642bin(N.token, 16, tok, strlen(tok), NULL, &tn,
-			                  NULL, sodium_base64_VARIANT_ORIGINAL);
+				NULL, sodium_base64_VARIANT_ORIGINAL);
 		}
 		LOG("relay: id=%s", N.self_id);
-
 	} else if (!strcmp(type, "joined")) {
 		int slot;
 		if (json_get_int(json, "slot", &slot)) {
 			N.self_slot = slot;
 			LOG("joined, my slot=%d", slot);
 		}
-
 	} else if (!strcmp(type, "peer") || !strcmp(type, "peer-joined")) {
 		peer_add(json);
-
 	} else if (!strcmp(type, "peer-left")) {
 		peer_del(json);
-
 	} else if (!strcmp(type, "state")) {
 		char from[32]; int mu = 0, df = 0;
 		if (json_get(json, "from", from, sizeof(from))) {
@@ -330,7 +355,6 @@ static void on_ctl(const char *json) {
 				}
 			pthread_mutex_unlock(&N.mu);
 		}
-
 	} else if (!strcmp(type, "key")) {
 		char from[32], blob[256];
 		if (!json_get(json, "from", from, sizeof(from))) return;
@@ -338,8 +362,8 @@ static void on_ctl(const char *json) {
 		uint8_t sealed[48 + crypto_box_SEALBYTES];
 		size_t sn;
 		if (sodium_base642bin(sealed, sizeof(sealed), blob, strlen(blob),
-		        NULL, &sn, NULL, sodium_base64_VARIANT_ORIGINAL) != 0 ||
-		    sn != sizeof(sealed)) return;
+			NULL, &sn, NULL, sodium_base64_VARIANT_ORIGINAL) != 0 ||
+			sn != sizeof(sealed)) return;
 		uint8_t pt[48];
 		if (crypto_box_seal_open(pt, sealed, sizeof(sealed), N.pk, N.sk) != 0)
 			return;
@@ -353,19 +377,21 @@ static void on_ctl(const char *json) {
 				break;
 			}
 		pthread_mutex_unlock(&N.mu);
-
 	} else if (!strcmp(type, "kfreq")) {
 		atomic_store(&N.kf_wanted, 1);
 	}
 }
 
-static void request_keyframe(struct peer *p) {
+static int want_keyframe_locked(struct peer *p, char id_out[32]) {
 	long now = time(NULL);
-	if (now - p->last_kfreq < 1) return;
+	if (now - p->last_kfreq < 1) return 0;
 	p->last_kfreq = now;
+	snprintf(id_out, 32, "%s", p->id);
+	return 1;
+}
+static void send_kfreq(const char *id) {
 	char line[80];
-	snprintf(line, sizeof(line),
-	         "{\"type\":\"kfreq\",\"to\":\"%s\"}", p->id);
+	snprintf(line, sizeof(line), "{\"type\":\"kfreq\",\"to\":\"%s\"}", id);
 	tcp_line(line);
 }
 
@@ -382,27 +408,21 @@ static void on_media(const uint8_t *pkt, int n) {
 	int ptlen = n - HDR - crypto_secretbox_MACBYTES;
 	if (ptlen <= 0 || ptlen > (int)sizeof(pt)) return;
 	if (crypto_secretbox_open_easy(pt, pkt + HDR, n - HDR, nonce, p->key) != 0)
-		return;   /* bad mac / wrong key: drop */
+		return;
 
-	if (type == 2) {                 /* audio: one opus packet */
+	if (type == 2) {
 		audio_recv_packet(slot, (int)seq, pt, ptlen);
-
-	} else if (type == 4) {          /* screenshare/system audio (music) */
-		audio_share_recv(slot, pt, ptlen);
-
-	} else if (type == 3 && ptlen > 8) {   /* video chunk */
+	} else if (type == 4) {
+		audio_share_recv(slot, (int)seq, pt, ptlen);
+	} else if (type == 3 && ptlen > 8) {
 		int fseq = pt[0] | pt[1] << 8;
 		int cnt  = pt[4] | pt[5] << 8;
 		int key  = pt[6];
-		int src  = (pt[7] >> 4) & 1;       /* 0=camera 1=screen */
+		int src  = (pt[7] >> 4) & 1;
 		int dlen = ptlen - 8;
 		int idx  = pt[2] | pt[3] << 8;
-		/* reassembly buffers (p->va[].buf) are freed by peer_del on the TCP
-		 * thread. hold N.mu across the buffer ops and RE-CHECK p->used after
-		 * locking, so a peer leaving mid-frame can't free the buffer out
-		 * from under this memcpy (was a use-after-free / data race). the
-		 * critical section is just a memcpy, so it stays cheap. */
 		uint8_t *done_buf = NULL; int done_len = 0, done_key = 0;
+		char kfid[32]; int want_kf = 0;
 		pthread_mutex_lock(&N.mu);
 		if (!p->used) { pthread_mutex_unlock(&N.mu); return; }
 		struct vasm *a = &p->va[src];
@@ -412,10 +432,8 @@ static void on_media(const uint8_t *pkt, int n) {
 			a->seq = -1;
 		}
 		if (fseq != a->seq) {
-			if (a->seq >= 0 && a->got < a->cnt) {
-				atomic_fetch_add(&N.v_abandons, 1);
-				request_keyframe(p);     /* abandoned an incomplete frame */
-			}
+			if (a->seq >= 0 && a->got < a->cnt)
+				want_kf = want_keyframe_locked(p, kfid);
 			a->seq = fseq; a->len = 0; a->got = 0; a->cnt = cnt;
 			a->key = key;
 		}
@@ -427,10 +445,6 @@ static void on_media(const uint8_t *pkt, int n) {
 		if (a->got == a->cnt && v_handler) {
 			static int first = 1;
 			if (first) { LOG("first video frame RECEIVED (slot %d, %d bytes)", slot, a->len); first = 0; }
-			/* copy the completed frame into a private buffer UNDER the lock,
-			 * so peer_del freeing a->buf can't race the decode. decode the
-			 * copy after unlocking (the decoder can be slow and must not
-			 * hold the peer lock). */
 			done_buf = malloc((size_t)a->len);
 			if (done_buf) {
 				memcpy(done_buf, a->buf, (size_t)a->len);
@@ -438,14 +452,17 @@ static void on_media(const uint8_t *pkt, int n) {
 			}
 		}
 		pthread_mutex_unlock(&N.mu);
-		if (done_buf) {
-			v_handler(slot, done_buf, done_len, done_key, src, v_user);
+		if (want_kf) send_kfreq(kfid);
+		if (done_buf && vq_push(slot, done_buf, done_len, done_key, src) != 0) {
 			free(done_buf);
+			pthread_mutex_lock(&N.mu);
+			int kf = p->used ? want_keyframe_locked(p, kfid) : 0;
+			pthread_mutex_unlock(&N.mu);
+			if (kf) send_kfreq(kfid);
 		}
 	}
 }
 
-/* ---- threads ---- */
 static void *tcp_main(void *arg) {
 	(void)arg;
 	char buf[8192]; int fill = 0;
@@ -474,7 +491,7 @@ static void *udp_main(void *arg) {
 		long n = sock_read(N.udp, pkt, sizeof(pkt));
 		if (n <= 0) { if (!N.run) break; continue; }
 		if (n >= HDR && pkt[0] == 1) {
-			if (pkt[1] == 1) {           /* hello echo: udp path works */
+			if (pkt[1] == 1) {
 				if (!N.udp_ok) LOG("udp media path up");
 				N.udp_ok = 1;
 			} else on_media(pkt, (int)n);
@@ -488,52 +505,13 @@ static void send_hello(void) {
 	pkt[0] = 1; pkt[1] = 1;
 	memcpy(pkt + HDR, N.token, 16);
 	sendto(N.udp, (const char *)pkt, sizeof(pkt), 0,
-	       (struct sockaddr *)&N.raddr, sizeof(N.raddr));
+		(struct sockaddr *)&N.raddr, sizeof(N.raddr));
 }
 
-static void kfreq_all(void) {
-	pthread_mutex_lock(&N.mu);
-	char ids[MAX_PEERS][32]; int n = 0;
-	for (int s = 0; s < MAX_PEERS; s++)
-		if (P[s].used) snprintf(ids[n++], 32, "%s", P[s].id);
-	pthread_mutex_unlock(&N.mu);
-	for (int i = 0; i < n; i++) {
-		char line[80];
-		snprintf(line, sizeof(line),
-		         "{\"type\":\"kfreq\",\"to\":\"%s\"}", ids[i]);
-		tcp_line(line);
-	}
-}
-
-static void set_layer(int want);
-void net_want_high(int hi) {   /* ui: request high-quality video (pin/quality) */
-	N.want_high = hi;
-	set_layer(hi ? 1 : 0);
-}
-
-static void set_layer(int want) {
-	N.want = want;
-	char line[48];
-	snprintf(line, sizeof(line), "{\"type\":\"layer\",\"want\":%d}", want);
-	tcp_line(line);
-	kfreq_all();   /* start the new layer on a keyframe asap */
-}
-
 static void *tick_main(void *arg) {
 	(void)arg;
-	int win[5] = {0}, wi = 0, sec = 0, clean = 0;
 	while (N.run) {
-		send_hello();                /* bind + keepalive + nat refresh */
-		if (++sec >= 15) sec = 0;
-		win[wi] = atomic_exchange(&N.v_abandons, 0);
-		wi = (wi + 1) % 5;
-		int recent = win[0] + win[1] + win[2] + win[3] + win[4];
-		clean = win[(wi + 4) % 5] ? 0 : clean + 1;
-		if (N.want == 1 && recent >= 3) {
-			set_layer(0);   /* loss: drop to low layer (silent) */
-		} else if (N.want == 0 && clean >= 30 && N.want_high) {
-			set_layer(1);   /* recovered AND user wants high: climb back */
-		}
+		send_hello();
 		sleep(1);
 	}
 	return NULL;
@@ -565,9 +543,8 @@ int net_join(const char *server, const char *room, con
 		LOG("control connect failed"); sock_close(N.tcp); N.tcp = -1;
 		freeaddrinfo(res); return -3;
 	}
-	{ int one = 1;
-	  setsockopt(N.tcp, IPPROTO_TCP, TCP_NODELAY,
-	             (const char *)&one, sizeof(one)); }
+	int one = 1;
+	setsockopt(N.tcp, IPPROTO_TCP, TCP_NODELAY, (const char *)&one, sizeof(one));
 	memcpy(&N.raddr, res->ai_addr, sizeof(N.raddr));
 	N.raddr.sin_port = htons((uint16_t)port);
 	freeaddrinfo(res);
@@ -576,19 +553,20 @@ int net_join(const char *server, const char *room, con
 	if (N.udp >= 0) {
 		int tos = 0xB8;
 		setsockopt(N.udp, IPPROTO_IP, IP_TOS,
-		           (const char *)&tos, sizeof(tos));
-		int rcv = 1 << 20;   /* 1 MiB */
+			(const char *)&tos, sizeof(tos));
+		int rcv = 1 << 20;
 		setsockopt(N.udp, SOL_SOCKET, SO_RCVBUF,
-		           (const char *)&rcv, sizeof(rcv));
+			(const char *)&rcv, sizeof(rcv));
+		int snd = 1 << 20;
+		setsockopt(N.udp, SOL_SOCKET, SO_SNDBUF,
+			(const char *)&snd, sizeof(snd));
 	}
 
-	/* identity + sender key for this session */
 	crypto_box_keypair(N.pk, N.sk);
 	randombytes_buf(N.skey, sizeof(N.skey));
 	randombytes_buf(N.snbase, sizeof(N.snbase));
 	atomic_store(&N.aseq, 0); atomic_store(&N.vseq, 0); atomic_store(&N.saseq, 0);
-	N.udp_ok = 0; N.self_slot = -1; N.want = 0; N.want_high = 0;   /* LOCKED low */
-	atomic_store(&N.v_abandons, 0);
+	N.udp_ok = 0; N.self_slot = -1;
 	memset(P, 0, sizeof(P));
 
 	N.run = 1;
@@ -596,17 +574,21 @@ int net_join(const char *server, const char *room, con
 
 	char pub[64];
 	sodium_bin2base64(pub, sizeof(pub), N.pk, sizeof(N.pk),
-	                  sodium_base64_VARIANT_ORIGINAL);
+		sodium_base64_VARIANT_ORIGINAL);
 	char esc[64]; json_escape(name && name[0] ? name : "anon", esc, sizeof(esc));
 	char line[300];
 	snprintf(line, sizeof(line),
-	         "{\"type\":\"join\",\"room\":\"%s\",\"name\":\"%s\",\"pub\":\"%s\"}",
-	         room, esc, pub);
+		"{\"type\":\"join\",\"room\":\"%s\",\"name\":\"%s\",\"pub\":\"%s\"}",
+		room, esc, pub);
 	tcp_line(line);
 
 	for (int i = 0; i < 250 && N.self_slot < 0; i++)
 		usleep(20 * 1000);
 	if (N.self_slot < 0) { LOG("no welcome from relay"); }
+	pthread_mutex_lock(&vq_mu);
+	vq_r = 0; vq_n = 0; vq_run = 1;
+	pthread_mutex_unlock(&vq_mu);
+	pthread_create(&t_dec, NULL, dec_main, NULL);
 	pthread_create(&N.t_udp, NULL, udp_main, NULL);
 	pthread_create(&N.t_tick, NULL, tick_main, NULL);
 
@@ -627,6 +609,12 @@ void net_leave(void) {
 	pthread_join(N.t_tcp, NULL);
 	pthread_join(N.t_udp, NULL);
 	pthread_join(N.t_tick, NULL);
+
+	pthread_mutex_lock(&vq_mu);
+	vq_run = 0;
+	pthread_cond_broadcast(&vq_cv);
+	pthread_mutex_unlock(&vq_mu);
+	pthread_join(t_dec, NULL);
 	pthread_mutex_lock(&N.mu);
 	for (int s = 0; s < MAX_PEERS; s++) { free(P[s].va[0].buf); free(P[s].va[1].buf); }
 	memset(P, 0, sizeof(P));
blob - 5c5b6112e0c28e0ee1885d9fbd4388e76fd80b15
blob + 422b982fd353585e849a0cae6fd762065d9d64bf
--- src/net.h
+++ src/net.h
@@ -2,22 +2,20 @@
 #define NET_H
 #include <stdint.h>
 
-/* server: "host" or "host:port" (old ws:// urls tolerated). 0 on success. */
 int  net_join(const char *server, const char *room, const char *name);
 void net_leave(void);
 
-int  net_self_slot(void);   /* my media slot, -1 before joined */
+int  net_self_slot(void);
 int  net_peers(int slots[], char names[][32], int max);
 
 int  net_send_video(const uint8_t *frame, int len, int is_key, int is_screen,
-                    int layer);
-/* complete received frames, per sender slot + source (network thread!) */
+	int layer);
+
 typedef void (*net_video_fn)(int slot, const uint8_t *frame, int len,
-                             int is_key, int is_screen, void *user);
+	int is_key, int is_screen, void *user);
 void net_set_video_handler(net_video_fn fn, void *user);
 int  net_keyframe_wanted(void);
 
-void net_want_high(int hi);   /* request high-quality video layer */
-int  net_peer_state(int slot);          /* bit0=muted bit1=deafened */
+int  net_peer_state(int slot);
 void net_announce_state(int muted, int deaf);
 #endif
blob - 858f1aa19f9d4220927f323f4299ec5cb0e04406 (mode 644)
blob + /dev/null
--- src/plat_mac.m
+++ /dev/null
@@ -1,196 +0,0 @@
-#import <Cocoa/Cocoa.h>
-#import <AVFoundation/AVFoundation.h>
-#import <ScreenCaptureKit/ScreenCaptureKit.h>
-#include <stdlib.h>
-#include <string.h>
-#include "milna.h"
-#include "update.h"
-#include <dispatch/dispatch.h>
-#include "microui.h"
-
-static fb F;
-static CGColorSpaceRef CS;
-static NSView *g_view;
-
-void update_dispatch_bg(void) {
-	dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
-		@autoreleasepool {
-			update_run_blocking();
-		}
-		dispatch_async(dispatch_get_main_queue(), ^{
-			if (g_view) [g_view setNeedsDisplay:YES];
-		});
-	});
-}
-
-void plat_invalidate(void) {
-	NSView *v = g_view;
-	if (!v) return;
-	dispatch_async(dispatch_get_main_queue(), ^{
-		[v setNeedsDisplay:YES];
-	});
-}
-
-static int mu_key(unsigned short kc) {
-	switch (kc) {
-		case 56: case 60: return MU_KEY_SHIFT;
-		case 59: case 62: return MU_KEY_CTRL;
-		case 36: case 76: return MU_KEY_RETURN;
-		case 51: return MU_KEY_BACKSPACE;
-		default: return 0;
-	}
-}
-
-@interface MilnaView : NSView
-@end
-
-@implementation MilnaView
-- (BOOL)isFlipped { return YES; }
-- (BOOL)acceptsFirstResponder { return YES; }
-
-- (void)ensureBuf {
-	int w = (int)self.bounds.size.width, h = (int)self.bounds.size.height;
-	if (w <= 0 || h <= 0) return;
-	if (w == F.w && h == F.h && F.px) return;
-	free(F.px); F.w = w; F.h = h;
-	F.px = malloc((size_t)w * h * 4);
-}
-
-- (void)drawRect:(NSRect)dirty {
-	(void)dirty;
-	[self ensureBuf];
-	if (!F.px) return;
-	ui_frame(&F);
-	CGContextRef ctx = [[NSGraphicsContext currentContext] CGContext];
-
-	static CGDataProviderRef dp;
-	static void *dp_ptr; static size_t dp_sz;
-	size_t sz = (size_t)F.w * F.h * 4;
-	if (dp_ptr != F.px || dp_sz != sz) {
-		if (dp) CGDataProviderRelease(dp);
-		dp = CGDataProviderCreateWithData(NULL, F.px, sz, NULL);
-		dp_ptr = F.px; dp_sz = sz;
-	}
-	CGImageRef img = CGImageCreate(F.w, F.h, 8, 32, (size_t)F.w * 4, CS,
-	    kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little,
-	    dp, NULL, false, kCGRenderingIntentDefault);
-
-	CGContextSaveGState(ctx);
-	CGContextTranslateCTM(ctx, 0, F.h);
-	CGContextScaleCTM(ctx, 1.0, -1.0);
-	CGContextDrawImage(ctx, CGRectMake(0, 0, F.w, F.h), img);
-	CGContextRestoreGState(ctx);
-
-	CGImageRelease(img);
-}
-
-- (void)feed:(in_event)e { ui_input(&e); [self setNeedsDisplay:YES]; }
-
-- (void)feedChar:(unsigned char)c {
-	if (c >= 32 && c < 127) {
-		in_event e = {.kind=M_TEXT};
-		e.text[0] = (char)c; e.text[1] = 0;
-		[self feed:e];
-	}
-}
-
-- (mu_Vec2)pt:(NSEvent *)ev {
-	NSPoint p = [self convertPoint:ev.locationInWindow fromView:nil];
-	return mu_vec2((int)p.x, (int)p.y);
-}
-- (void)mouseMoved:(NSEvent *)ev      { mu_Vec2 p=[self pt:ev]; [self feed:(in_event){.kind=M_MOVE,.x=p.x,.y=p.y}]; }
-- (void)mouseDragged:(NSEvent *)ev    { [self mouseMoved:ev]; }
-- (void)mouseDown:(NSEvent *)ev       { mu_Vec2 p=[self pt:ev]; [self feed:(in_event){.kind=M_DOWN,.x=p.x,.y=p.y,.btn=MU_MOUSE_LEFT}]; }
-- (void)mouseUp:(NSEvent *)ev         { mu_Vec2 p=[self pt:ev]; [self feed:(in_event){.kind=M_UP,.x=p.x,.y=p.y,.btn=MU_MOUSE_LEFT}]; }
-- (void)rightMouseDown:(NSEvent *)ev  { mu_Vec2 p=[self pt:ev]; [self feed:(in_event){.kind=M_DOWN,.x=p.x,.y=p.y,.btn=MU_MOUSE_RIGHT}]; }
-- (void)rightMouseUp:(NSEvent *)ev    { mu_Vec2 p=[self pt:ev]; [self feed:(in_event){.kind=M_UP,.x=p.x,.y=p.y,.btn=MU_MOUSE_RIGHT}]; }
-
-- (void)keyDown:(NSEvent *)ev {
-	NSEventModifierFlags mods = ev.modifierFlags;
-
-	/* ---- cmd shortcuts (no menu bar, so handle them here) ---- */
-	if (mods & NSEventModifierFlagCommand) {
-		NSString *k = ev.charactersIgnoringModifiers.lowercaseString;
-		if ([k isEqualToString:@"q"]) { [NSApp terminate:nil]; return; }
-		if ([k isEqualToString:@"w"]) { [self.window close]; return; }
-		if ([k isEqualToString:@"m"]) { [self.window miniaturize:nil]; return; }
-		if ([k isEqualToString:@"v"]) {   
-			NSString *pb = [[NSPasteboard generalPasteboard]
-			                stringForType:NSPasteboardTypeString];
-			if (pb) {
-				const char *u = pb.UTF8String;
-				int fed = 0;
-				for (const char *p = u; *p && fed < 200; p++, fed++)
-					[self feedChar:(unsigned char)*p];
-			}
-			return;
-		}
-		return;
-	}
-
-	int mk = mu_key(ev.keyCode);
-	if (mk) { [self feed:(in_event){.kind=M_KEY_DOWN,.key=mk}]; return; }
-
-	const char *s = ev.characters.UTF8String;
-	if (s && s[0] && !s[1]) [self feedChar:(unsigned char)s[0]];
-}
-- (void)keyUp:(NSEvent *)ev {
-	if (ev.modifierFlags & NSEventModifierFlagCommand) return;
-	int mk = mu_key(ev.keyCode);
-	if (mk) [self feed:(in_event){.kind=M_KEY_UP,.key=mk}];
-}
-@end
-
-@interface App : NSObject <NSApplicationDelegate>
-@end
-@implementation App
-- (void)applicationDidFinishLaunching:(NSNotification *)n {
-	(void)n;
-	NSRect r = NSMakeRect(0, 0, 400, 280);
-	NSWindow *win = [[NSWindow alloc] initWithContentRect:r
-	    styleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
-	               NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable)
-	    backing:NSBackingStoreBuffered defer:NO];
-	win.title = @"milna";
-	win.acceptsMouseMovedEvents = YES;
-	MilnaView *v = [[MilnaView alloc] initWithFrame:r];
-	g_view = v;
-	win.contentView = v;
-	[win makeFirstResponder:v];
-	[win center];
-	[win makeKeyAndOrderFront:nil];
-	[NSApp activateIgnoringOtherApps:YES];
-
-	[AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio
-	                         completionHandler:^(BOOL granted) {
-		if (!granted) NSLog(@"[milna] microphone permission denied");
-	}];
-
-	NSTimer *ut = [NSTimer timerWithTimeInterval:2.0 repeats:YES
-	    block:^(NSTimer *t) {
-		static int kicked, shown;
-		if (!kicked) { update_check_async(); kicked = 1; }
-		if (!shown && update_ready()) {   
-			shown = 1;
-			ui_mark_dirty();              /* force build() to add the banner */
-			plat_invalidate();
-			[t invalidate];               /* banner is up; stop polling */
-		}
-	}];
-	[[NSRunLoop mainRunLoop] addTimer:ut forMode:NSRunLoopCommonModes];
-}
-- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)a { (void)a; return YES; }
-@end
-
-int main(void) {
-	CS = CGColorSpaceCreateDeviceRGB();
-	ui_init();
-	@autoreleasepool {
-		[NSApplication sharedApplication];
-		[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
-		App *d = [App new];
-		NSApp.delegate = d;
-		[NSApp run];   /* event-driven run loop: blocks until events */
-	}
-	return 0;
-}
blob - /dev/null
blob + 4854b14e81bb42fe4ca2cd451a43e16d5ed06a58 (mode 644)
--- /dev/null
+++ src/ui.cpp
@@ -0,0 +1,668 @@
+#include <FL/Fl.H>
+#include <FL/Fl_Double_Window.H>
+#include <FL/Fl_Input.H>
+#include <FL/Fl_Button.H>
+#include <FL/Fl_Choice.H>
+#include <FL/Fl_Box.H>
+#include <FL/fl_draw.H>
+#include <atomic>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#ifdef _WIN32
+#include <direct.h>
+#define mkdir(p, m) _mkdir(p)
+#else
+#include <sys/stat.h>
+#endif
+
+extern "C" {
+#include "milna.h"
+#include "audio.h"
+#include "net.h"
+#include "vid.h"
+#include "cap.h"
+#include "cam.h"
+#include "update.h"
+}
+
+static struct {
+	char server[96];
+	char room[64];
+	char name[32];
+	int in_call;
+	int muted;
+	int deaf;
+	int cam_preview;
+} A = { "rsap.sh", "lobby", "anon", 0, 0, 0, 0 };
+
+static Fl_Double_Window *W;
+static Fl_Input *in_server, *in_room, *in_name;
+static Fl_Button *b_join, *b_mute, *b_deaf, *b_share, *b_cam;
+static Fl_Box *status_box, *banner_box;
+static class CallView *call;
+
+static void conf_dir(char *out, size_t cap) {
+#ifdef _WIN32
+	const char *a = getenv("APPDATA");
+	snprintf(out, cap, "%s/milna", a ? a : ".");
+#else
+	const char *x = getenv("XDG_CONFIG_HOME");
+	if (x && x[0]) snprintf(out, cap, "%s/milna", x);
+	else snprintf(out, cap, "%s/.config/milna",
+		getenv("HOME") ? getenv("HOME") : ".");
+#endif
+}
+
+static void conf_path(char *out, size_t cap) {
+	char d[480];
+	conf_dir(d, sizeof(d));
+	snprintf(out, cap, "%s/rc", d);
+}
+
+static void conf_load(void) {
+	char p[512]; conf_path(p, sizeof(p));
+	FILE *f = fopen(p, "r");
+	if (!f) return;
+	char line[256];
+	while (fgets(line, sizeof(line), f)) {
+		char *nl = strchr(line, '\n'); if (nl) *nl = 0;
+		char *eq = strchr(line, '='); if (!eq) continue;
+		*eq = 0;
+		const char *k = line, *v = eq + 1;
+		if      (!strcmp(k, "server")) snprintf(A.server, sizeof(A.server), "%s", v);
+		else if (!strcmp(k, "room"))   snprintf(A.room, sizeof(A.room), "%s", v);
+		else if (!strcmp(k, "name"))   snprintf(A.name, sizeof(A.name), "%s", v);
+		else if (!strcmp(k, "in"))     audio_select_by_name(1, v);
+		else if (!strcmp(k, "out"))    audio_select_by_name(0, v);
+	}
+	fclose(f);
+}
+
+static void grab_inputs(void) {
+	if (!in_server) return;
+	snprintf(A.server, sizeof(A.server), "%s", in_server->value());
+	snprintf(A.room, sizeof(A.room), "%s", in_room->value());
+	snprintf(A.name, sizeof(A.name), "%s", in_name->value());
+}
+
+static void conf_save(void) {
+	char d[480];
+	conf_dir(d, sizeof(d));
+	char *sl = strrchr(d, '/');
+	if (sl) {
+		*sl = 0;
+		mkdir(d, 0755);
+		*sl = '/';
+	}
+	mkdir(d, 0755);
+	char p[512];
+	conf_path(p, sizeof(p));
+	FILE *f = fopen(p, "w");
+	if (!f) return;
+	fprintf(f, "server=%s\nroom=%s\nname=%s\nin=%s\nout=%s\n",
+		A.server, A.room, A.name,
+		audio_sel_name(1), audio_sel_name(0));
+	fclose(f);
+}
+
+static long long now_ms(void) {
+	struct timespec ts;
+	clock_gettime(CLOCK_MONOTONIC, &ts);
+	return ts.tv_sec * 1000LL + ts.tv_nsec / 1000000LL;
+}
+
+enum { VS_MAX = 16, PREVIEW_SLOT = VS_MAX - 1, STREAM_TTL = 3000 };
+
+typedef struct {
+	vid_dec *dec;
+	uint8_t *px;
+	int w, h, drop;
+	long long last, painted, started;
+} vstream;
+static vstream s_cam[VS_MAX], s_scr[VS_MAX];
+static pthread_mutex_t vs_mu = PTHREAD_MUTEX_INITIALIZER;
+
+static std::atomic<int> wake_pending;
+static std::atomic<long long> last_wake;
+static void frame_wake(void *);
+
+static void vs_store(vstream *s, const uint8_t *bgra, int w, int h) {
+	long long now = now_ms();
+	pthread_mutex_lock(&vs_mu);
+	int was_live = s->px && now - s->last < STREAM_TTL;
+	if (!s->px || s->w != w || s->h != h) {
+		free(s->px);
+		s->px = (uint8_t *)malloc((size_t)w * h * 4);
+		s->w = w; s->h = h;
+	}
+	if (s->px) {
+		memcpy(s->px, bgra, (size_t)w * h * 4);
+		s->last = now;
+		if (!was_live) s->started = now;
+	}
+	pthread_mutex_unlock(&vs_mu);
+
+	long long prev = last_wake.load(std::memory_order_relaxed);
+	if (now - prev >= 50 && last_wake.compare_exchange_strong(prev, now))
+		if (!wake_pending.exchange(1)) Fl::awake(frame_wake, NULL);
+}
+
+static void on_video(int slot, const uint8_t *frame, int len, int is_key,
+	int is_screen, void *user) {
+	(void)is_key; (void)user;
+	if (slot < 0 || slot >= VS_MAX) return;
+	enum { DW = 1920, DH = 1080 };
+	static uint8_t *dbuf;
+	if (!dbuf) dbuf = (uint8_t *)malloc((size_t)DW * DH * 4);
+	if (!dbuf) return;
+	vstream *s = is_screen ? &s_scr[slot] : &s_cam[slot];
+	pthread_mutex_lock(&vs_mu);
+	int dropped = s->drop; s->drop = 0;
+	pthread_mutex_unlock(&vs_mu);
+
+	if (dropped && s->dec) { vid_dec_del(s->dec); s->dec = NULL; }
+	if (!s->dec) { s->dec = vid_dec_new(); if (!s->dec) return; }
+	int w, h;
+	if (vid_dec_frame(s->dec, frame, len, dbuf, DW * DH * 4, &w, &h) != 1)
+		return;
+	vs_store(s, dbuf, w, h);
+}
+
+void ui_local_video(const uint8_t *bgra, int w, int h, int is_screen) {
+	int sl = net_self_slot();
+	if (sl < 0) sl = PREVIEW_SLOT;
+	if (sl >= VS_MAX || !bgra) return;
+	vs_store(is_screen ? &s_scr[sl] : &s_cam[sl], bgra, w, h);
+}
+
+static void stream_wipe(vstream *s) {
+	s->drop = 1;
+	free(s->px); s->px = NULL;
+	s->w = s->h = 0;
+	s->last = s->painted = s->started = 0;
+}
+
+void streams_drop_slot(int slot) {
+	if (slot < 0 || slot >= VS_MAX) return;
+	pthread_mutex_lock(&vs_mu);
+	stream_wipe(&s_cam[slot]);
+	stream_wipe(&s_scr[slot]);
+	pthread_mutex_unlock(&vs_mu);
+}
+
+static void streams_clear(void) {
+	pthread_mutex_lock(&vs_mu);
+	for (int i = 0; i < VS_MAX; i++) {
+		if (s_cam[i].dec) vid_dec_del(s_cam[i].dec);
+		if (s_scr[i].dec) vid_dec_del(s_scr[i].dec);
+		free(s_cam[i].px); free(s_scr[i].px);
+		memset(&s_cam[i], 0, sizeof(vstream));
+		memset(&s_scr[i], 0, sizeof(vstream));
+	}
+	pthread_mutex_unlock(&vs_mu);
+}
+
+struct R { int x, y, w, h; };
+static int inr(R r, int x, int y) {
+	return x >= r.x && y >= r.y && x < r.x + r.w && y < r.y + r.h;
+}
+static R bar_of(R t) { R b = { t.x + 8, t.y + t.h - 14, t.w - 16, 6 }; return b; }
+
+static uint8_t *scratch;
+static int scratch_cap;
+
+static void gray(int g) { fl_color(fl_rgb_color((uchar)g, (uchar)g, (uchar)g)); }
+
+static void blit_frame(const uint8_t *bgra, int sw, int sh, R r, int bg) {
+	gray(bg);
+	int dw = r.w, dh = 0;
+	if (r.w >= 8 && r.h >= 8 && sw > 0 && sh > 0) {
+		dh = sh * r.w / sw;
+		if (dh > r.h) { dh = r.h; dw = sw * r.h / sh; }
+		if (dw > 3840) dw = 3840;
+	}
+	if (dw < 1 || dh < 1) { fl_rectf(r.x, r.y, r.w, r.h); return; }
+	int ox = r.x + (r.w - dw) / 2, oy = r.y + (r.h - dh) / 2;
+	if (ox > r.x) fl_rectf(r.x, r.y, ox - r.x, r.h);
+	if (ox + dw < r.x + r.w) fl_rectf(ox + dw, r.y, r.x + r.w - ox - dw, r.h);
+	if (oy > r.y) fl_rectf(r.x, r.y, r.w, oy - r.y);
+	if (oy + dh < r.y + r.h) fl_rectf(r.x, oy + dh, r.w, r.y + r.h - oy - dh);
+	int need = dw * dh * 3;
+	if (need > scratch_cap) {
+		free(scratch);
+		scratch = (uint8_t *)malloc((size_t)need);
+		scratch_cap = scratch ? need : 0;
+	}
+	if (!scratch) return;
+	static int xmap[3840];
+	for (int i = 0; i < dw; i++) xmap[i] = i * sw / dw;
+	for (int y = 0; y < dh; y++) {
+		const uint8_t *srow = bgra + (size_t)(y * sh / dh) * sw * 4;
+		uint8_t *d = scratch + (size_t)y * dw * 3;
+		for (int x = 0; x < dw; x++) {
+			const uint8_t *p = srow + xmap[x] * 4;
+			d[0] = p[2]; d[1] = p[1]; d[2] = p[0];
+			d += 3;
+		}
+	}
+	fl_draw_image(scratch, ox, oy, dw, dh, 3, 0);
+}
+
+static void tile_label(R t, const char *s) {
+	gray(221);
+	fl_draw(s, t.x + 4, t.y + 14);
+}
+
+static void tile_status(R t, int muted, int deaf) {
+	const char *s = deaf ? "deafened" : muted ? "muted" : NULL;
+	if (!s) return;
+	fl_color(fl_rgb_color(230, 64, 64));
+	fl_draw(s, t.x + t.w - (int)fl_width(s) - 6, t.y + t.h - 6);
+}
+
+static void vol_bar(R t, int gain) {
+	R b = bar_of(t);
+	if (b.w < 20) return;
+	int fw = b.w * gain / 200;
+	gray(51);  fl_rectf(b.x, b.y, b.w, b.h);
+	gray(221); fl_rectf(b.x, b.y, fw, b.h);
+	gray(255); fl_rectf(b.x + fw - 1, b.y - 2, 3, b.h + 4);
+}
+
+static void hover_border(R t) {
+	gray(221);
+	fl_rect(t.x, t.y, t.w, t.h);
+	fl_rect(t.x + 1, t.y + 1, t.w - 2, t.h - 2);
+}
+
+class CallView : public Fl_Widget {
+	R big; int big_slot; int big_on; int big_remote;
+	R stile[VS_MAX]; int stile_slot[VS_MAX]; int nstile;
+	R ctile[VS_MAX + 1]; int ctile_slot[VS_MAX + 1]; int nctile;
+	int mx, my, hov, drag_slot;
+	R drag_bar;
+	int last_sig;
+
+	int hit_id(void) {
+		for (int i = 0; i < nstile; i++) if (inr(stile[i], mx, my)) return 200 + i;
+		for (int i = 0; i < nctile; i++) if (inr(ctile[i], mx, my)) return 300 + i;
+		if (big_on && inr(big, mx, my)) return 100;
+		return -1;
+	}
+
+	void set_gain_from_x(void) {
+		int pct = (Fl::event_x() - drag_bar.x) * 200 / (drag_bar.w > 0 ? drag_bar.w : 1);
+		if (pct < 0) pct = 0;
+		if (pct > 200) pct = 200;
+		if (drag_slot >= 100) audio_share_set_gain(drag_slot - 100, pct);
+		else audio_set_gain(drag_slot, pct);
+	}
+
+public:
+	int pin_slot;
+	CallView(int X, int Y, int Wd, int Hh) : Fl_Widget(X, Y, Wd, Hh),
+		big_slot(-1), big_on(0), big_remote(0), nstile(0), nctile(0),
+		mx(-1), my(-1), hov(-1), drag_slot(-1), last_sig(0), pin_slot(-1) {}
+
+	void draw() FL_OVERRIDE {
+		long long now = now_ms();
+		int slots[VS_MAX]; char names[VS_MAX][32]; int pst[VS_MAX];
+		int np = A.in_call ? net_peers(slots, names, VS_MAX) : 0;
+		int self_sl = A.in_call ? net_self_slot() : -1;
+		for (int i = 0; i < np; i++) pst[i] = net_peer_state(slots[i]);
+		fl_font(FL_HELVETICA, 12);
+
+		pthread_mutex_lock(&vs_mu);
+		int lv[VS_MAX], nlive = 0, freshest = -1; long long best = 0;
+		for (int i = 0; i < VS_MAX; i++)
+			if (s_scr[i].px && now - s_scr[i].last < STREAM_TTL) {
+				lv[nlive++] = i;
+				if (s_scr[i].started > best) { freshest = i; best = s_scr[i].started; }
+			}
+		int pin = freshest;
+		if (pin_slot >= 0 && s_scr[pin_slot].px &&
+			now - s_scr[pin_slot].last < STREAM_TTL) pin = pin_slot;
+
+		int sig = np * 31 + pin * 7 + nlive * 131 + w() * 3 + h() * 5
+			+ hov * 13 + A.muted + A.deaf * 2 + A.in_call * 4;
+		for (int i = 0; i < np; i++) sig = sig * 33 + slots[i] * 5 + pst[i];
+		for (int i = 0; i < np + 1; i++) {
+			int ssl = i == 0 ? (self_sl >= 0 ? self_sl : PREVIEW_SLOT) : slots[i - 1];
+			vstream *s = &s_cam[ssl];
+			sig = sig * 2 + (s->px && now - s->last < STREAM_TTL ? 1 : 0);
+		}
+		int full = (damage() & ~FL_DAMAGE_USER1) != 0;
+		if (sig != last_sig) { full = 1; last_sig = sig; }
+		if (full) { gray(29); fl_rectf(x(), y(), w(), h()); }
+
+		big_on = 0; nstile = 0; nctile = 0;
+		R area = { x(), y(), w(), h() };
+		R tiles = area;
+		if (pin >= 0) {
+			R sh = { area.x, area.y, area.w, area.h * 3 / 4 };
+			tiles.y += sh.h; tiles.h -= sh.h;
+			vstream *s = &s_scr[pin];
+			int hovd = inr(sh, mx, my);
+			if (full || s->painted != s->last) {
+				fl_push_clip(sh.x, sh.y, sh.w, sh.h);
+				blit_frame(s->px, s->w, s->h, sh, 20);
+				s->painted = s->last;
+				const char *lab = "share";
+				if (pin == self_sl) lab = "your share";
+				else for (int i = 0; i < np; i++)
+					if (slots[i] == pin) { lab = names[i]; break; }
+				tile_label(sh, lab);
+				if (hovd) {
+					hover_border(sh);
+					if (pin != self_sl) vol_bar(sh, audio_share_get_gain(pin));
+				}
+				fl_pop_clip();
+			}
+			big = sh; big_slot = pin; big_on = 1;
+			big_remote = pin != self_sl;
+		}
+
+		int nshare = pin >= 0 ? nlive - 1 : 0;
+		int n = np + 1 + nshare;
+		int cols = pin >= 0 ? n : (n <= 1 ? 1 : n <= 4 ? 2 : 3);
+		if (cols < 1) cols = 1;
+		int rows = (n + cols - 1) / cols;
+		int tw = tiles.w / cols, th = tiles.h / rows;
+		if (tw < 8 || th < 8) { pthread_mutex_unlock(&vs_mu); return; }
+
+		int ti = 0;
+		for (int i = 0; i < np + 1; i++, ti++) {
+			R t = { tiles.x + (ti % cols) * tw + 1,
+				tiles.y + (ti / cols) * th + 1, tw - 2, th - 2 };
+			vstream *s; const char *lab; char self[44];
+			int rslot = -1, muted, deaf;
+			if (i == 0) {
+				int ssl = self_sl >= 0 ? self_sl : PREVIEW_SLOT;
+				s = &s_cam[ssl];
+				snprintf(self, sizeof(self), "%s (you)", A.name);
+				lab = self; muted = A.muted; deaf = A.deaf;
+			} else {
+				rslot = slots[i - 1];
+				s = &s_cam[rslot];
+				lab = names[i - 1];
+				muted = pst[i - 1] & 1; deaf = pst[i - 1] & 2;
+			}
+			int live = s->px && now - s->last < STREAM_TTL;
+			if (full || (live && s->painted != s->last)) {
+				fl_push_clip(t.x, t.y, t.w, t.h);
+				if (full && !live) { gray(32); fl_rectf(t.x, t.y, t.w, t.h); }
+				if (live) { blit_frame(s->px, s->w, s->h, t, 32); s->painted = s->last; }
+				tile_label(t, lab);
+				tile_status(t, muted, deaf);
+				if (rslot >= 0 && inr(t, mx, my)) vol_bar(t, audio_get_gain(rslot));
+				fl_pop_clip();
+			}
+			ctile[nctile] = t; ctile_slot[nctile++] = rslot;
+		}
+		for (int k = 0; k < nlive && pin >= 0; k++) {
+			int sl = lv[k];
+			if (sl == pin) continue;
+			R t = { tiles.x + (ti % cols) * tw + 1,
+				tiles.y + (ti / cols) * th + 1, tw - 2, th - 2 };
+			ti++;
+			vstream *s = &s_scr[sl];
+			if (full || s->painted != s->last) {
+				fl_push_clip(t.x, t.y, t.w, t.h);
+				blit_frame(s->px, s->w, s->h, t, 20);
+				s->painted = s->last;
+				char lab[44] = "share";
+				if (sl == self_sl) snprintf(lab, sizeof(lab), "you (share)");
+				else for (int i = 0; i < np; i++)
+					if (slots[i] == sl) {
+						snprintf(lab, sizeof(lab), "%s (share)", names[i]);
+						break;
+					}
+				tile_label(t, lab);
+				if (inr(t, mx, my)) hover_border(t);
+				fl_pop_clip();
+			}
+			stile[nstile] = t; stile_slot[nstile++] = sl;
+		}
+		pthread_mutex_unlock(&vs_mu);
+	}
+
+	int handle(int e) FL_OVERRIDE {
+		switch (e) {
+		case FL_ENTER: return 1;
+		case FL_LEAVE:
+			mx = my = -1;
+			if (hov != -1) { hov = -1; redraw(); }
+			return 1;
+		case FL_MOVE: {
+			mx = Fl::event_x(); my = Fl::event_y();
+			int h2 = hit_id();
+			if (h2 != hov) { hov = h2; redraw(); }
+			return 1; }
+		case FL_PUSH: {
+			if (Fl::event_button() != FL_LEFT_MOUSE) return 1;
+			mx = Fl::event_x(); my = Fl::event_y();
+			for (int i = 0; i < nctile; i++)
+				if (ctile_slot[i] >= 0 && inr(bar_of(ctile[i]), mx, my)) {
+					drag_slot = ctile_slot[i]; drag_bar = bar_of(ctile[i]);
+					set_gain_from_x(); redraw(); return 1;
+				}
+			if (big_on && big_remote && inr(bar_of(big), mx, my)) {
+				drag_slot = big_slot + 100; drag_bar = bar_of(big);
+				set_gain_from_x(); redraw(); return 1;
+			}
+			for (int i = 0; i < nstile; i++)
+				if (inr(stile[i], mx, my)) { pin_slot = stile_slot[i]; redraw(); return 1; }
+			if (big_on && inr(big, mx, my)) { pin_slot = -1; redraw(); return 1; }
+			return 1; }
+		case FL_DRAG:
+			if (drag_slot >= 0) { set_gain_from_x(); redraw(); }
+			return 1;
+		case FL_RELEASE:
+			drag_slot = -1;
+			return 1;
+		}
+		return Fl_Widget::handle(e);
+	}
+};
+
+static void frame_wake(void *) {
+	wake_pending.store(0, std::memory_order_relaxed);
+	if (call) call->damage(FL_DAMAGE_USER1);
+}
+
+static void setlab(Fl_Widget *w, const char *s) {
+	if (w->label() != s) w->label(s);
+}
+
+static void sync_labels(void) {
+	setlab(status_box, A.in_call ? "status: in call" : "status: idle");
+	setlab(b_join, A.in_call ? "leave" : "join");
+	setlab(b_mute, A.muted ? "unmute" : "mute");
+	setlab(b_deaf, A.deaf ? "undeafen" : "deafen");
+	setlab(b_share, cap_active() ? "stop share" : "share screen");
+	setlab(b_cam, cam_active() ? "stop camera" : "camera");
+	if (A.in_call) b_share->activate(); else b_share->deactivate();
+}
+
+static void join_cb(Fl_Widget *, void *) {
+	if (!A.in_call) {
+		grab_inputs(); conf_save();
+		if (net_join(A.server, A.room, A.name) == 0) {
+			A.in_call = 1;
+
+			pthread_mutex_lock(&vs_mu);
+			stream_wipe(&s_cam[PREVIEW_SLOT]);
+			pthread_mutex_unlock(&vs_mu);
+			net_set_video_handler(on_video, NULL);
+			net_announce_state(A.muted, A.deaf);
+		}
+	} else {
+		grab_inputs(); conf_save();
+		cap_stop();
+		if (!A.cam_preview) cam_stop();
+		net_leave();
+		A.in_call = 0;
+		streams_clear();
+		call->pin_slot = -1;
+	}
+	sync_labels(); call->redraw();
+}
+
+static void mute_cb(Fl_Widget *, void *) {
+	A.muted = !A.muted;
+	if (!A.muted && A.deaf) { A.deaf = 0; audio_set_deafened(0); }
+	audio_set_muted(A.muted);
+	if (A.in_call) net_announce_state(A.muted, A.deaf);
+	sync_labels(); call->redraw();
+}
+
+static void deaf_cb(Fl_Widget *, void *) {
+	A.deaf = !A.deaf;
+	audio_set_deafened(A.deaf);
+	A.muted = A.deaf ? 1 : 0;
+	audio_set_muted(A.muted);
+	if (A.in_call) net_announce_state(A.muted, A.deaf);
+	sync_labels(); call->redraw();
+}
+
+static void share_cb(Fl_Widget *, void *) {
+	if (!cap_active()) {
+		if (cap_start() != 0)
+			fprintf(stderr, "[milna] share unavailable on this platform\n");
+	} else cap_stop();
+	sync_labels(); call->redraw();
+}
+
+static void cam_cb(Fl_Widget *, void *) {
+	if (!cam_active()) {
+		if (cam_start() != 0)
+			fprintf(stderr, "[milna] camera unavailable on this platform\n");
+		else if (!A.in_call) A.cam_preview = 1;
+	} else { cam_stop(); A.cam_preview = 0; }
+	sync_labels(); call->redraw();
+}
+
+struct DevChoice : Fl_Choice {
+	int cap;
+	char names[12][64];
+	DevChoice(int X, int Y, int Wd, int Hh, const char *L, int c)
+		: Fl_Choice(X, Y, Wd, Hh, L), cap(c) { callback(pick, NULL); }
+	void repop(void) {
+		int n = audio_list(cap, names, 12);
+		clear();
+
+		for (int i = 0; i < n; i++) { add("x"); replace(i, names[i]); }
+		value(-1);
+		const char *cur = audio_sel_name(cap);
+		for (int i = 0; i < n; i++)
+			if (!strcmp(names[i], cur)) { value(i); break; }
+	}
+	int handle(int e) FL_OVERRIDE {
+		if (e == FL_PUSH) repop();
+		return Fl_Choice::handle(e);
+	}
+	static void pick(Fl_Widget *w, void *) {
+		DevChoice *d = (DevChoice *)w;
+		if (d->value() < 0) return;
+		audio_select(d->cap, d->value());
+		grab_inputs(); conf_save();
+	}
+};
+static DevChoice *dv_in, *dv_out;
+
+static void tick_cb(void *) {
+	sync_labels();
+	if (call) call->damage(FL_DAMAGE_USER1);
+	Fl::repeat_timeout(1.0, tick_cb);
+}
+
+static void update_cb(void *) {
+	static int kicked;
+	if (!kicked) { kicked = 1; update_check_async(); }
+	if (update_ready()) {
+		static char b[96];
+		snprintf(b, sizeof(b), "update %s downloaded, quit and reopen to apply",
+			update_latest_version());
+		banner_box->label(b);
+		return;
+	}
+	Fl::repeat_timeout(2.0, update_cb);
+}
+
+static void win_cb(Fl_Widget *, void *) {
+	if (Fl::event() == FL_SHORTCUT && Fl::event_key() == FL_Escape) return;
+	if (A.in_call) { cap_stop(); cam_stop(); net_leave(); }
+	W->hide();
+}
+
+#ifdef __APPLE__
+extern "C" void update_dispatch_bg(void) {
+	pthread_t t;
+	if (pthread_create(&t, NULL,
+		[](void *) -> void * { update_run_blocking(); return NULL; },
+		NULL) == 0)
+		pthread_detach(t);
+}
+#endif
+
+int main(void) {
+	update_init(MILNA_VERSION);
+	conf_load();
+	Fl::lock();
+	FL_NORMAL_SIZE = 12;
+	Fl::background(29, 29, 29);
+	Fl::background2(45, 45, 45);
+	Fl::foreground(221, 221, 221);
+
+	const int CW = 640, CH = 560, TOP = 244;
+	int half = (CW - 24) / 2;
+	W = new Fl_Double_Window(CW, CH, "milna");
+
+	status_box = new Fl_Box(8, 4, 150, 20, "status: idle");
+	status_box->align(FL_ALIGN_INSIDE | FL_ALIGN_LEFT);
+	banner_box = new Fl_Box(158, 4, CW - 158 - 44, 20);
+	banner_box->align(FL_ALIGN_INSIDE | FL_ALIGN_LEFT);
+	banner_box->labelcolor(fl_rgb_color(240, 200, 100));
+	Fl_Box *ver = new Fl_Box(CW - 44, 4, 36, 20, "v" MILNA_VERSION);
+	ver->align(FL_ALIGN_INSIDE | FL_ALIGN_RIGHT);
+	ver->labelcolor(fl_rgb_color(120, 120, 120));
+
+	in_server = new Fl_Input(70, 28, CW - 78, 24, "server");
+	in_room   = new Fl_Input(70, 58, CW - 78, 24, "room");
+	in_name   = new Fl_Input(70, 88, CW - 78, 24, "name");
+	in_server->value(A.server);
+	in_room->value(A.room);
+	in_name->value(A.name);
+
+	b_join  = new Fl_Button(8, 120, CW - 16, 26, "join");
+	b_mute  = new Fl_Button(8, 152, half, 26, "mute");
+	b_deaf  = new Fl_Button(16 + half, 152, half, 26, "deafen");
+	b_share = new Fl_Button(8, 184, half, 26, "share screen");
+	b_cam   = new Fl_Button(16 + half, 184, half, 26, "camera");
+	b_join->callback(join_cb);
+	b_mute->callback(mute_cb);
+	b_deaf->callback(deaf_cb);
+	b_share->callback(share_cb);
+	b_cam->callback(cam_cb);
+
+	dv_in  = new DevChoice(38, 216, half - 30, 22, "mic", 1);
+	dv_out = new DevChoice(16 + half + 30, 216, half - 30, 22, "out", 0);
+
+	call = new CallView(0, TOP, CW, CH - TOP);
+	W->end();
+	W->resizable(call);
+	W->size_range(420, 380);
+	W->callback(win_cb);
+
+	sync_labels();
+	dv_in->repop();
+	dv_out->repop();
+
+	W->show();
+	Fl::add_timeout(1.0, tick_cb);
+	Fl::add_timeout(2.0, update_cb);
+	return Fl::run();
+}
blob - 0910d7c7f705124e44f91d3c3874fdc66226991d (mode 644)
blob + /dev/null
--- src/plat_win.c
+++ /dev/null
@@ -1,135 +0,0 @@
-#include <windows.h>
-#include <stdlib.h>
-#include <string.h>
-#include "milna.h"
-#include "update.h"
-#include "microui.h"
-
-static fb F;
-static BITMAPINFO BI;
-
-static int mu_key(WPARAM vk) {
-	switch (vk) {
-		case VK_SHIFT:   return MU_KEY_SHIFT;
-		case VK_CONTROL: return MU_KEY_CTRL;
-		case VK_RETURN:  return MU_KEY_RETURN;
-		case VK_BACK:    return MU_KEY_BACKSPACE;
-		default: return 0;
-	}
-}
-
-static void ensure_buf(int w, int h) {
-	if (w == F.w && h == F.h && F.px) return;
-	free(F.px);
-	F.w = w; F.h = h;
-	F.px = malloc((size_t)w * h * 4);
-	BI = (BITMAPINFO){0};
-	BI.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
-	BI.bmiHeader.biWidth = w;
-	BI.bmiHeader.biHeight = -h;        /* negative = top-down, no flip needed */
-	BI.bmiHeader.biPlanes = 1;
-	BI.bmiHeader.biBitCount = 32;
-	BI.bmiHeader.biCompression = BI_RGB;
-}
-
-static void redraw(HWND hwnd) { InvalidateRect(hwnd, NULL, FALSE); }
-
-static HWND g_hwnd;
-void plat_invalidate(void) { if (g_hwnd) InvalidateRect(g_hwnd, NULL, FALSE); }
-
-static LRESULT CALLBACK wndproc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
-	in_event e = {0};
-	switch (msg) {
-	case WM_SIZE:
-		ensure_buf(LOWORD(lp), HIWORD(lp));
-		redraw(hwnd);
-		return 0;
-	case WM_MOUSEMOVE:
-		e.kind = M_MOVE; e.x = (short)LOWORD(lp); e.y = (short)HIWORD(lp);
-		ui_input(&e); redraw(hwnd); return 0;
-	case WM_LBUTTONDOWN: case WM_LBUTTONUP:
-		e.kind = (msg == WM_LBUTTONDOWN) ? M_DOWN : M_UP;
-		e.x = (short)LOWORD(lp); e.y = (short)HIWORD(lp); e.btn = MU_MOUSE_LEFT;
-		ui_input(&e); redraw(hwnd); return 0;
-	case WM_RBUTTONDOWN: case WM_RBUTTONUP:
-		e.kind = (msg == WM_RBUTTONDOWN) ? M_DOWN : M_UP;
-		e.x = (short)LOWORD(lp); e.y = (short)HIWORD(lp); e.btn = MU_MOUSE_RIGHT;
-		ui_input(&e); redraw(hwnd); return 0;
-	case WM_TIMER:
-		if (wp == 1) {
-			static int kicked, shown;
-			if (!kicked) { update_check_async(); kicked = 1; }
-			if (!shown && update_ready()) {
-				shown = 1; ui_mark_dirty(); redraw(hwnd);
-				KillTimer(hwnd, 1);
-			}
-		}
-		return 0;
-	case WM_KEYDOWN: case WM_KEYUP: {
-		if (msg == WM_KEYDOWN && wp == 'V' && (GetKeyState(VK_CONTROL) & 0x8000)) {
-			if (OpenClipboard(hwnd)) {       /* paste, capped per frame */
-				HANDLE h = GetClipboardData(CF_TEXT);
-				char *t = h ? GlobalLock(h) : NULL;
-				if (t) {
-					int fed = 0;
-					for (const char *q = t; *q && fed < 200; q++, fed++)
-						if (*q >= 32 && *q < 127) {
-							in_event pe = {0};
-							pe.kind = M_TEXT; pe.text[0] = *q;
-							ui_input(&pe);
-						}
-					GlobalUnlock(h);
-				}
-				CloseClipboard();
-				redraw(hwnd);
-			}
-			return 0;
-		}
-		int mk = mu_key(wp);
-		if (mk) { e.kind = (msg == WM_KEYDOWN) ? M_KEY_DOWN : M_KEY_UP; e.key = mk;
-		          ui_input(&e); redraw(hwnd); }
-		return 0;
-	}
-	case WM_CHAR:
-		if (wp >= 32 && wp < 127) {
-			e.kind = M_TEXT; e.text[0] = (char)wp;
-			ui_input(&e); redraw(hwnd);
-		}
-		return 0;
-	case WM_PAINT: {
-		PAINTSTRUCT ps; HDC dc = BeginPaint(hwnd, &ps);
-		ui_frame(&F);
-		StretchDIBits(dc, 0, 0, F.w, F.h, 0, 0, F.w, F.h,
-		              F.px, &BI, DIB_RGB_COLORS, SRCCOPY);
-		EndPaint(hwnd, &ps);
-		return 0;
-	}
-	case WM_DESTROY: PostQuitMessage(0); return 0;
-	}
-	return DefWindowProc(hwnd, msg, wp, lp);
-}
-
-int main(void) {                 /* console subsystem: logs stay visible */
-	HINSTANCE inst = GetModuleHandle(NULL);
-	int show = SW_SHOW;
-	ui_init();
-	WNDCLASS wc = {0};
-	wc.lpfnWndProc = wndproc;
-	wc.hInstance = inst;
-	wc.lpszClassName = "milna";
-	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
-	RegisterClass(&wc);
-
-	HWND hwnd; g_hwnd = hwnd = CreateWindow("milna", "milna", WS_OVERLAPPEDWINDOW,
-	    CW_USEDEFAULT, CW_USEDEFAULT, 640, 560, NULL, NULL, inst, NULL);
-	ShowWindow(hwnd, show);
-	SetTimer(hwnd, 1, 2000, NULL);   /* one-shot-ish update check kick */
-
-	MSG msg;
-	while (GetMessage(&msg, NULL, 0, 0) > 0) {   /* BLOCKS — 0% cpu idle */
-		TranslateMessage(&msg);
-		DispatchMessage(&msg);
-	}
-	free(F.px);
-	return 0;
-}
blob - bd653e4df5c17574db5c6e8b087a55fc080478ca (mode 644)
blob + /dev/null
--- src/plat_x11.c
+++ /dev/null
@@ -1,110 +0,0 @@
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/keysym.h>
-#include <stdlib.h>
-#include <string.h>
-#include "milna.h"
-#include "microui.h"
-
-static int mu_key(KeySym k) {
-	switch (k) {
-		case XK_Shift_L: case XK_Shift_R: return MU_KEY_SHIFT;
-		case XK_Control_L: case XK_Control_R: return MU_KEY_CTRL;
-		case XK_Return: case XK_KP_Enter: return MU_KEY_RETURN;
-		case XK_BackSpace: return MU_KEY_BACKSPACE;
-		default: return 0;
-	}
-}
-
-static Window g_win;
-void plat_invalidate(void) {
-	if (!g_win) return;
-	Display *d = XOpenDisplay(NULL);
-	if (!d) return;
-	XEvent e = {0};
-	e.type = Expose;
-	e.xexpose.window = g_win;
-	XSendEvent(d, g_win, False, ExposureMask, &e);
-	XFlush(d);
-	XCloseDisplay(d);
-}
-
-int main(void) {
-	Display *dpy = XOpenDisplay(NULL);
-	if (!dpy) return 1;
-	int scr = DefaultScreen(dpy);
-	int w = 400, h = 280;
-	Window win; g_win = win = XCreateSimpleWindow(dpy, RootWindow(dpy, scr), 0, 0, w, h, 0,
-	                                 BlackPixel(dpy, scr), BlackPixel(dpy, scr));
-	XStoreName(dpy, win, "milna");
-	XSelectInput(dpy, win, ExposureMask | KeyPressMask | KeyReleaseMask |
-	             ButtonPressMask | ButtonReleaseMask | PointerMotionMask |
-	             StructureNotifyMask);
-	Atom wm_del = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
-	XSetWMProtocols(dpy, win, &wm_del, 1);
-	XMapWindow(dpy, win);
-
-	Visual *vis = DefaultVisual(dpy, scr);
-	GC gc = DefaultGC(dpy, scr);
-
-	fb f = { .w = w, .h = h };
-	f.px = malloc((size_t)w * h * 4);
-	XImage *img = XCreateImage(dpy, vis, DefaultDepth(dpy, scr), ZPixmap, 0,
-	                           (char *)f.px, w, h, 32, 0);
-
-	ui_init();
-
-	for (;;) {
-		XEvent ev;
-		XNextEvent(dpy, &ev);
-		in_event ie = {0};
-		int quit = 0, resized = 0;
-
-		switch (ev.type) {
-		case Expose: ie.kind = M_NONE; break;
-		case ConfigureNotify:
-			if (ev.xconfigure.width != f.w || ev.xconfigure.height != f.h) {
-				f.w = ev.xconfigure.width; f.h = ev.xconfigure.height;
-				img->data = NULL; XDestroyImage(img);
-				f.px = malloc((size_t)f.w * f.h * 4);
-				img = XCreateImage(dpy, vis, DefaultDepth(dpy, scr), ZPixmap, 0,
-				                   (char *)f.px, f.w, f.h, 32, 0);
-				resized = 1;
-			}
-			break;
-		case MotionNotify: ie.kind = M_MOVE; ie.x = ev.xmotion.x; ie.y = ev.xmotion.y; break;
-		case ButtonPress: case ButtonRelease:
-			ie.kind = (ev.type == ButtonPress) ? M_DOWN : M_UP;
-			ie.x = ev.xbutton.x; ie.y = ev.xbutton.y;
-			ie.btn = (ev.xbutton.button == Button1) ? MU_MOUSE_LEFT :
-			         (ev.xbutton.button == Button3) ? MU_MOUSE_RIGHT : MU_MOUSE_MIDDLE;
-			break;
-		case KeyPress: case KeyRelease: {
-			KeySym ks; char buf[8] = {0};
-			int n = XLookupString(&ev.xkey, buf, sizeof(buf) - 1, &ks, NULL);
-			int mk = mu_key(ks);
-			if (ev.type == KeyPress) {
-				if (mk) { ie.kind = M_KEY_DOWN; ie.key = mk; }
-				else if (n > 0 && (unsigned char)buf[0] >= 32) {
-					ie.kind = M_TEXT; memcpy(ie.text, buf, n);
-				}
-			} else if (mk) { ie.kind = M_KEY_UP; ie.key = mk; }
-			break;
-		}
-		case ClientMessage:
-			if ((Atom)ev.xclient.data.l[0] == wm_del) quit = 1;
-			break;
-		}
-		if (quit) break;
-		if (ie.kind != M_NONE) ui_input(&ie);
-
-		if (ie.kind != M_NONE || resized || ev.type == Expose || ui_needs_redraw()) {
-			ui_frame(&f);
-			XPutImage(dpy, win, gc, img, 0, 0, 0, 0, f.w, f.h);
-		}
-	}
-
-	img->data = NULL; XDestroyImage(img); free(f.px);
-	XCloseDisplay(dpy);
-	return 0;
-}
blob - 2e5a2cec883b4892d77017d77f4976df0d2c4481 (mode 644)
blob + /dev/null
--- src/ui.c
+++ /dev/null
@@ -1,719 +0,0 @@
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <pthread.h>
-#include <time.h>
-#include "milna.h"
-#include "microui.h"
-#include "audio.h"
-#include "aproc.h"
-#include "net.h"
-#include "vid.h"
-#include "cap.h"
-#include "cam.h"
-#include "update.h"
-
-int milna_vbitrate(int base) { return base; }
-#include "atlas.inl"   /* ATLAS_*, atlas_texture[], atlas[] */
-
-#define MAX_LIST 8     /* roster lines shown in the ui */
-
-static struct {
-	mu_Context mu;
-	char server[96];
-	char room[64];
-	char name[32];
-	int in_call;
-	int muted;
-	int deaf;                     /* deafened: mic off + all others silenced */
-	int cam_preview;              /* camera on before joining (landing preview) */
-	int dirty;
-	int show_dev;                 /* devices panel open */
-	char devs[2][12][64];         /* [capture][i] cached names */
-	int ndev[2];          /* set when state/input changed; drives redraw */
-} A = { .server = "rsap.sh", .room = "lobby",
-        .name = "anon", .dirty = 1 };
-
-
-#ifdef _WIN32
-#include <direct.h>
-#define mkdir(p, m) _mkdir(p)
-#else
-#include <sys/stat.h>
-#endif
-
-static void conf_path(char *out, size_t cap) {
-#ifdef _WIN32
-	const char *a = getenv("APPDATA");
-	snprintf(out, cap, "%s/milna/rc", a ? a : ".");
-#else
-	const char *x = getenv("XDG_CONFIG_HOME");
-	if (x && x[0]) snprintf(out, cap, "%s/milna/rc", x);
-	else snprintf(out, cap, "%s/.config/milna/rc",
-	              getenv("HOME") ? getenv("HOME") : ".");
-#endif
-}
-
-static void conf_load(void) {
-	char p[512]; conf_path(p, sizeof(p));
-	FILE *f = fopen(p, "r");
-	if (!f) { fprintf(stderr, "[milna] no config at %s (first run?)\n", p); return; }
-	fprintf(stderr, "[milna] config loaded from %s\n", p);
-	char line[256];
-	while (fgets(line, sizeof(line), f)) {
-		char *nl = strchr(line, '\n'); if (nl) *nl = 0;
-		char *eq = strchr(line, '='); if (!eq) continue;
-		*eq = 0;
-		const char *k = line, *v = eq + 1;
-		if      (!strcmp(k, "server")) snprintf(A.server, sizeof(A.server), "%s", v);
-		else if (!strcmp(k, "room"))   snprintf(A.room, sizeof(A.room), "%s", v);
-		else if (!strcmp(k, "name"))   snprintf(A.name, sizeof(A.name), "%s", v);
-		else if (!strcmp(k, "in"))     audio_select_by_name(1, v);
-		else if (!strcmp(k, "out"))    audio_select_by_name(0, v);
-	}
-	fclose(f);
-}
-
-static void conf_save(void) {
-	char p[512]; conf_path(p, sizeof(p));
-	char d[512];
-#ifdef _WIN32
-	snprintf(d, sizeof(d), "%s/milna", getenv("APPDATA") ? getenv("APPDATA") : ".");
-	mkdir(d, 0755);
-#else
-	const char *x = getenv("XDG_CONFIG_HOME");
-	if (x && x[0]) snprintf(d, sizeof(d), "%s", x);
-	else snprintf(d, sizeof(d), "%s/.config",
-	              getenv("HOME") ? getenv("HOME") : ".");
-	mkdir(d, 0755);
-	strncat(d, "/milna", sizeof(d) - strlen(d) - 1);
-	mkdir(d, 0755);
-#endif
-	FILE *f = fopen(p, "w");
-	if (!f) { fprintf(stderr, "[milna] cannot write config %s\n", p); return; }
-	fprintf(f, "server=%s\nroom=%s\nname=%s\nin=%s\nout=%s\n",
-	        A.server, A.room, A.name,
-	        audio_sel_name(1), audio_sel_name(0));
-	fclose(f);
-	fprintf(stderr, "[milna] config saved to %s\n", p);
-}
-
-typedef struct {
-	vid_dec *dec;
-	uint8_t *px;          /* tight BGRA, w*h*4 */
-	int w, h;
-	long last;            /* ms timestamp of the latest frame */
-	long painted;         /* last frame stamp actually blitted */
-	long started;        
-
-} vstream;
-#define VS_MAX 16
-#define PREVIEW_SLOT (VS_MAX - 1)   /* local self-preview before joining */
-#define STREAM_TTL 3000   /* hide a stream after 3s of silence */
-static vstream s_cam[VS_MAX], s_scr[VS_MAX];
-static pthread_mutex_t vs_mu = PTHREAD_MUTEX_INITIALIZER;
-
-static struct {
-	mu_Rect big; int big_live;
-	mu_Rect tile[VS_MAX]; int tile_slot[VS_MAX]; int ntile;
-} share_hits;
-
-static struct {
-	mu_Rect bar[VS_MAX]; int slot[VS_MAX]; int n;
-} vol_hits;
-static int vol_drag_slot = -1;   /* slot whose slider is being dragged */
-static int pin_slot = -1;        /* -1 = auto (freshest wins) */
-static int mouse_x = -1, mouse_y = -1;
-
-static int in_rect(mu_Rect r, int x, int y) {
-	return x >= r.x && y >= r.y && x < r.x + r.w && y < r.y + r.h;
-}
-
-static long now_ms(void) {
-	struct timespec ts;
-	clock_gettime(CLOCK_MONOTONIC, &ts);
-	return ts.tv_sec * 1000L + ts.tv_nsec / 1000000L;
-}
-
-static void vs_store(vstream *s, const uint8_t *bgra, int w, int h);
-
-static void on_video(int slot, const uint8_t *frame, int len, int is_key,
-                     int is_screen, void *user) {
-	(void)is_key; (void)user;
-	if (slot < 0 || slot >= VS_MAX) return;
-	enum { DW = 1920, DH = 1080 };
-	static uint8_t *scratch;           /* one net thread: shared is fine */
-	if (!scratch) scratch = malloc((size_t)DW * DH * 4);
-	if (!scratch) return;
-	vstream *s = is_screen ? &s_scr[slot] : &s_cam[slot];
-	if (!s->dec) { s->dec = vid_dec_new(); if (!s->dec) return; }
-	int w, h;
-	if (vid_dec_frame(s->dec, frame, len, scratch, DW * DH * 4, &w, &h) != 1)
-		return;
-	vs_store(s, scratch, w, h);
-}
-
-/* store one BGRA frame into a stream slot (decoded or local preview) */
-static void vs_store(vstream *s, const uint8_t *bgra, int w, int h) {
-	long now = now_ms();
-	pthread_mutex_lock(&vs_mu);
-	int was_live = s->px && (now - s->last < STREAM_TTL);
-	if (!s->px || s->w != w || s->h != h) {
-		free(s->px);
-		s->px = malloc((size_t)w * h * 4);
-		s->w = w; s->h = h;
-	}
-	if (s->px) {
-		memcpy(s->px, bgra, (size_t)w * h * 4);
-		s->last = now;
-		if (!was_live) s->started = now;
-	}
-	pthread_mutex_unlock(&vs_mu);
-	static long last_inv;
-	if (now - last_inv >= 50) { last_inv = now; plat_invalidate(); }
-}
-
-void ui_local_video(const uint8_t *bgra, int w, int h, int is_screen) {
-	int sl = net_self_slot();
-	if (sl < 0) sl = PREVIEW_SLOT;   /* pre-join: dedicated preview slot */
-	if (sl < 0 || sl >= VS_MAX || !bgra) return;
-	vs_store(is_screen ? &s_scr[sl] : &s_cam[sl], bgra, w, h);
-}
-
-void streams_drop_slot(int slot) {
-	if (slot < 0 || slot >= VS_MAX) return;
-	pthread_mutex_lock(&vs_mu);
-	if (s_cam[slot].dec) vid_dec_del(s_cam[slot].dec);
-	free(s_cam[slot].px);
-	if (s_scr[slot].dec) vid_dec_del(s_scr[slot].dec);
-	free(s_scr[slot].px);
-	memset(&s_cam[slot], 0, sizeof(vstream));
-	memset(&s_scr[slot], 0, sizeof(vstream));
-	pthread_mutex_unlock(&vs_mu);
-}
-
-static void streams_clear(void) {
-	pthread_mutex_lock(&vs_mu);
-	for (int i = 0; i < VS_MAX; i++) {
-		vid_dec_del(s_cam[i].dec); free(s_cam[i].px);
-		vid_dec_del(s_scr[i].dec); free(s_scr[i].px);
-		memset(&s_cam[i], 0, sizeof(vstream));
-		memset(&s_scr[i], 0, sizeof(vstream));
-	}
-	pthread_mutex_unlock(&vs_mu);
-}
-
-
-
-static int text_width(mu_Font font, const char *s, int len) {
-	(void)font; int w = 0;
-	if (len < 0) len = (int)strlen(s);
-	for (const char *p = s; *p && len--; p++) {
-		unsigned char c = (unsigned char)*p;
-		if (c < 32 || c > 126) continue;   /* skip non-ascii (no glyph) */
-		w += atlas[ATLAS_FONT + c].w;
-	}
-	return w;
-}
-static int text_height(mu_Font font) { (void)font; return 18; }
-
-static inline uint32_t blend(uint32_t dst, mu_Color c, unsigned cov) {
-	/* cov 0..255 modulates c.a; classic over operator, per channel */
-	unsigned a = (c.a * cov) / 255; if (!a) return dst;
-	unsigned dr = (dst >> 16) & 0xff, dg = (dst >> 8) & 0xff, db = dst & 0xff;
-	unsigned r = (c.r * a + dr * (255 - a)) / 255;
-	unsigned g = (c.g * a + dg * (255 - a)) / 255;
-	unsigned b = (c.b * a + db * (255 - a)) / 255;
-	return 0xff000000u | (r << 16) | (g << 8) | b;
-}
-
-static fb *FB;          /* current target during rasterize */
-static mu_Rect CLIP;    /* active clip rect */
-
-static inline int clip_test(int x, int y) {
-	return x >= CLIP.x && y >= CLIP.y &&
-	       x < CLIP.x + CLIP.w && y < CLIP.y + CLIP.h;
-}
-
-static void draw_rect(mu_Rect r, mu_Color c) {
-	int x0 = r.x, y0 = r.y, x1 = r.x + r.w, y1 = r.y + r.h;
-	if (x0 < CLIP.x) x0 = CLIP.x;
-	if (y0 < CLIP.y) y0 = CLIP.y;
-	if (x1 > CLIP.x + CLIP.w) x1 = CLIP.x + CLIP.w;
-	if (y1 > CLIP.y + CLIP.h) y1 = CLIP.y + CLIP.h;
-	if (x0 < 0) x0 = 0;
-	if (y0 < 0) y0 = 0;
-	if (x1 > FB->w) x1 = FB->w;
-	if (y1 > FB->h) y1 = FB->h;
-	for (int y = y0; y < y1; y++) {
-		uint32_t *row = FB->px + (size_t)y * FB->w;
-		for (int x = x0; x < x1; x++) row[x] = blend(row[x], c, 255);
-	}
-}
-
-static void draw_atlas(mu_Rect src, int dx, int dy, mu_Color c) {
-	for (int sy = 0; sy < src.h; sy++) {
-		int y = dy + sy; if (y < 0 || y >= FB->h) continue;
-		const unsigned char *srow = atlas_texture + (src.y + sy) * ATLAS_WIDTH + src.x;
-		uint32_t *drow = FB->px + (size_t)y * FB->w;
-		for (int sx = 0; sx < src.w; sx++) {
-			int x = dx + sx; if (x < 0 || x >= FB->w) continue;
-			if (!clip_test(x, y)) continue;
-			unsigned cov = srow[sx];
-			if (cov) drow[x] = blend(drow[x], c, cov);
-		}
-	}
-}
-
-static void draw_text(const char *s, mu_Vec2 pos, mu_Color c) {
-	int dx = pos.x;
-	for (const char *p = s; *p; p++) {
-		unsigned char ch = (unsigned char)*p;
-		if (ch < 32 || ch > 126) continue;   /* no glyph; skip (was OOB) */
-		mu_Rect src = atlas[ATLAS_FONT + ch];
-		draw_atlas(src, dx, pos.y, c);
-		dx += src.w;
-	}
-}
-
-static void draw_icon(int id, mu_Rect r, mu_Color c) {
-	mu_Rect src = atlas[id];
-	int dx = r.x + (r.w - src.w) / 2, dy = r.y + (r.h - src.h) / 2;
-	draw_atlas(src, dx, dy, c);
-}
-
-static void build(void) {
-	mu_Context *ctx = &A.mu;
-	mu_begin(ctx);
-	mu_Container *win = mu_get_container(ctx, "milna");
-	win->rect = mu_rect(0, 0, FB->w, FB->h);
-	if (mu_begin_window_ex(ctx, "milna", win->rect,
-	        MU_OPT_NOCLOSE | MU_OPT_NORESIZE | MU_OPT_NOTITLE | MU_OPT_NOFRAME)) {
-		mu_layout_row(ctx, 1, (int[]){ -1 }, 0);
-		mu_label(ctx, A.in_call ? "status: in call" : "status: idle");
-		if (update_ready()) {
-			char b[80];
-			snprintf(b, sizeof(b),
-			    "update %s downloaded - quit & reopen to apply",
-			    update_latest_version());
-			mu_layout_row(ctx, 1, (int[]){ -1 }, 0);
-			mu_label(ctx, b);
-		}
-		mu_layout_row(ctx, 2, (int[]){ 80, -1 }, 0);
-		mu_label(ctx, "server");
-		if (mu_textbox(ctx, A.server, sizeof(A.server)) & MU_RES_CHANGE) A.dirty = 1;
-		mu_label(ctx, "room");
-		if (mu_textbox(ctx, A.room, sizeof(A.room)) & MU_RES_CHANGE) A.dirty = 1;
-		mu_label(ctx, "name");
-		if (mu_textbox(ctx, A.name, sizeof(A.name)) & MU_RES_CHANGE) A.dirty = 1;
-		int half = (FB->w - 3 * ctx->style->spacing) / 2;
-		mu_layout_row(ctx, 2, (int[]){ half, -1 }, 0);
-		if (mu_button(ctx, A.in_call ? "leave" : "join")) {
-			if (!A.in_call) {
-				conf_save();
-				if (net_join(A.server, A.room, A.name) == 0) {
-					A.in_call = 1;
-					net_set_video_handler(on_video, NULL);
-					/* announce current mute/deafen on join */
-					net_announce_state(A.muted, A.deaf);
-				}
-			} else {
-				conf_save();   /* fields may have been edited mid-call */
-				cap_stop();
-				if (!A.cam_preview) cam_stop();
-				net_leave(); A.in_call = 0;
-				streams_clear();
-				pin_slot = -1;
-			}
-			A.dirty = 1;
-		}
-		if (mu_button(ctx, A.show_dev ? "devices: hide" : "devices")) {
-			A.show_dev = !A.show_dev;
-			if (A.show_dev) {       /* refresh lists on open */
-				A.ndev[1] = audio_list(1, A.devs[1], 12);
-				A.ndev[0] = audio_list(0, A.devs[0], 12);
-			}
-			A.dirty = 1;
-		}
-
-		/* ROW 2: mute + deafen */
-		mu_layout_row(ctx, 2, (int[]){ half, -1 }, 0);
-		if (mu_button(ctx, A.muted ? "unmute" : "mute")) {
-			A.muted = !A.muted;
-			if (!A.muted && A.deaf) { A.deaf = 0; audio_set_deafened(0); }
-			audio_set_muted(A.muted);
-			if (A.in_call) net_announce_state(A.muted, A.deaf);
-			A.dirty = 1;
-		}
-		if (mu_button(ctx, A.deaf ? "undeafen" : "deafen")) {
-			A.deaf = !A.deaf;
-			audio_set_deafened(A.deaf);
-			/* deafen implies mic muted too (hear nothing, say nothing) */
-			if (A.deaf) { A.muted = 1; }
-			else        { A.muted = 0; }
-			audio_set_muted(A.muted);
-			if (A.in_call) net_announce_state(A.muted, A.deaf);
-			A.dirty = 1;
-		}
-
-		/* ROW 3: share screen + camera */
-		mu_layout_row(ctx, 2, (int[]){ half, -1 }, 0);
-		if (A.in_call) {
-			if (mu_button(ctx, cap_active() ? "stop share" : "share screen")) {
-				if (!cap_active()) {
-					if (cap_start() != 0)
-						fprintf(stderr, "[milna] share unavailable on this platform\n");
-				} else cap_stop();
-				A.dirty = 1;
-			}
-		} else {
-			/* pre-join: screenshare disabled, labelled (not in call) */
-			if (mu_button(ctx, "screenshare (not in call)")) {
-			}
-		}
-		if (mu_button(ctx, cam_active() ? "stop camera" : "camera")) {
-			if (!cam_active()) {
-				if (cam_start() != 0)
-					fprintf(stderr, "[milna] camera unavailable on this platform\n");
-				else if (!A.in_call) A.cam_preview = 1;  /* landing preview */
-			} else { cam_stop(); A.cam_preview = 0; }
-			A.dirty = 1;
-		}
-
-		if (A.show_dev) {
-			for (int cap = 1; cap >= 0; cap--) {
-				char hdr[80];
-				const char *cur = audio_sel_name(cap);
-				snprintf(hdr, sizeof(hdr), "%s: %s",
-				         cap ? "input" : "output", cur[0] ? cur : "default");
-				mu_layout_row(ctx, 1, (int[]){ -1 }, 0);
-				mu_label(ctx, hdr);
-				for (int i = 0; i < A.ndev[cap]; i++) {
-					mu_push_id(ctx, &cap, sizeof(cap));
-					if (mu_button(ctx, A.devs[cap][i])) {
-						audio_select(cap, i);
-						conf_save();
-					}
-					mu_pop_id(ctx);
-				}
-			}
-		}
-		mu_end_window(ctx);
-	}
-	mu_end(ctx);
-}
-
-static void rasterize(void) {
-	uint32_t bg = 0xff1d1d1du;
-	for (int i = 0; i < FB->w * FB->h; i++) FB->px[i] = bg;
-	CLIP = mu_rect(0, 0, FB->w, FB->h);
-	mu_Command *cmd = NULL;
-	while (mu_next_command(&A.mu, &cmd)) {
-		switch (cmd->type) {
-			case MU_COMMAND_RECT: draw_rect(cmd->rect.rect, cmd->rect.color); break;
-			case MU_COMMAND_TEXT: draw_text(cmd->text.str, cmd->text.pos, cmd->text.color); break;
-			case MU_COMMAND_ICON: draw_icon(cmd->icon.id, cmd->icon.rect, cmd->icon.color); break;
-			case MU_COMMAND_CLIP: CLIP = cmd->clip.rect; break;
-		}
-	}
-	{
-		char ver[32];
-		snprintf(ver, sizeof(ver), "v%s", MILNA_VERSION);
-		int vw = text_width(NULL, ver, (int)strlen(ver));
-		CLIP = mu_rect(0, 0, FB->w, FB->h);
-		draw_text(ver, mu_vec2(FB->w - vw - 8, 6), mu_color(120, 120, 120, 255));
-	}
-}
-void ui_mark_dirty(void) { A.dirty = 1; }
-
-void ui_init(void) {
-	update_init(MILNA_VERSION);
-	mu_init(&A.mu);
-	A.mu.text_width = text_width;
-	A.mu.text_height = text_height;
-	A.mu.style->colors[MU_COLOR_TEXT]     = mu_color(0xdd, 0xdd, 0xdd, 0xff);
-	A.mu.style->colors[MU_COLOR_WINDOWBG] = mu_color(29, 29, 29, 0xff);
-	conf_load();   
-}
-
-void ui_input(const in_event *e) {
-	mu_Context *ctx = &A.mu;
-	if (e->kind == M_MOVE) { mouse_x = e->x; mouse_y = e->y; A.dirty = 1; }
-	if (e->kind == M_DOWN && e->btn == MU_MOUSE_LEFT && A.in_call) {
-		int on_slider = 0;
-		for (int i = 0; i < vol_hits.n; i++) {
-			if (in_rect(vol_hits.bar[i], e->x, e->y)) {
-				vol_drag_slot = vol_hits.slot[i];
-				int rel = e->x - vol_hits.bar[i].x;
-				int pct = rel * 200 / vol_hits.bar[i].w;  /* 0..200% */
-				if (vol_drag_slot >= 100)
-					audio_share_set_gain(vol_drag_slot - 100, pct);
-				else audio_set_gain(vol_drag_slot, pct);
-				on_slider = 1; A.dirty = 1;
-				break;
-			}
-		}
-		if (!on_slider) {       /* not a slider click -> pin logic */
-			if (share_hits.big_live && in_rect(share_hits.big, e->x, e->y))
-				pin_slot = -1;
-			for (int i = 0; i < share_hits.ntile; i++)
-				if (in_rect(share_hits.tile[i], e->x, e->y))
-					pin_slot = share_hits.tile_slot[i];
-		}
-	}
-	if (e->kind == M_MOVE && vol_drag_slot >= 0) {   /* dragging */
-		for (int i = 0; i < vol_hits.n; i++)
-			if (vol_hits.slot[i] == vol_drag_slot) {
-				int rel = e->x - vol_hits.bar[i].x;
-				int pct = rel * 200 / vol_hits.bar[i].w;
-				if (pct < 0) pct = 0;
-				if (pct > 200) pct = 200;
-				if (vol_drag_slot >= 100)
-					audio_share_set_gain(vol_drag_slot - 100, pct);
-				else audio_set_gain(vol_drag_slot, pct);
-				A.dirty = 1;
-				break;
-			}
-	}
-	if (e->kind == M_UP) vol_drag_slot = -1;
-	switch (e->kind) {
-		case M_MOVE: mu_input_mousemove(ctx, e->x, e->y); break;
-		case M_DOWN: mu_input_mousedown(ctx, e->x, e->y, e->btn); break;
-		case M_UP:   mu_input_mouseup(ctx, e->x, e->y, e->btn); break;
-		case M_TEXT: mu_input_text(ctx, e->text); break;
-		case M_KEY_DOWN: mu_input_keydown(ctx, e->key); break;
-		case M_KEY_UP:   mu_input_keyup(ctx, e->key); break;
-	}
-	A.dirty = 1;   /* any input may change the ui */
-}
-
-int ui_needs_redraw(void) { return A.dirty; }
-
-#define CONTROLS_H 230
-static int controls_h_now = CONTROLS_H;
-
-static void px_fill(mu_Rect r, uint32_t col) {
-	int x0 = r.x < 0 ? 0 : r.x, y0 = r.y < 0 ? 0 : r.y;
-	int x1 = r.x + r.w > FB->w ? FB->w : r.x + r.w;
-	int y1 = r.y + r.h > FB->h ? FB->h : r.y + r.h;
-	for (int y = y0; y < y1; y++) {
-		uint32_t *row = FB->px + (size_t)y * FB->w;
-		for (int x = x0; x < x1; x++) row[x] = col;
-	}
-}
-
-static void blit_scaled_bg(const uint8_t *src, int sw, int sh, mu_Rect r,
-                           uint32_t bg) {
-	static int xmap[3840];
-	if (r.w < 8 || r.h < 8 || sw <= 0 || sh <= 0) return;
-	int dw = r.w, dh = sh * r.w / sw;
-	if (dh > r.h) { dh = r.h; dw = sw * r.h / sh; }
-	if (dw > 3840) dw = 3840;
-	int ox = r.x + (r.w - dw) / 2, oy = r.y + (r.h - dh) / 2;
-	if (ox > r.x)  px_fill(mu_rect(r.x, r.y, ox - r.x, r.h), bg);
-	if (ox + dw < r.x + r.w)
-		px_fill(mu_rect(ox + dw, r.y, r.x + r.w - ox - dw, r.h), bg);
-	if (oy > r.y)  px_fill(mu_rect(r.x, r.y, r.w, oy - r.y), bg);
-	if (oy + dh < r.y + r.h)
-		px_fill(mu_rect(r.x, oy + dh, r.w, r.y + r.h - oy - dh), bg);
-	for (int x = 0; x < dw; x++) xmap[x] = x * sw / dw;
-	for (int y = 0; y < dh; y++) {
-		int dy = oy + y;
-		if (dy < 0 || dy >= FB->h) continue;
-		const uint32_t *srow = (const uint32_t *)src + (size_t)(y * sh / dh) * sw;
-		uint32_t *dst = FB->px + (size_t)dy * FB->w;
-		for (int x = 0; x < dw; x++) {
-			int dx = ox + x;
-			if (dx >= 0 && dx < FB->w) dst[dx] = srow[xmap[x]];
-		}
-	}
-}
-
-static void hover_border(mu_Rect t) {
-	if (!in_rect(t, mouse_x, mouse_y)) return;
-	uint32_t c = 0xffdddddd;
-	px_fill(mu_rect(t.x, t.y, t.w, 2), c);
-	px_fill(mu_rect(t.x, t.y + t.h - 2, t.w, 2), c);
-	px_fill(mu_rect(t.x, t.y, 2, t.h), c);
-	px_fill(mu_rect(t.x + t.w - 2, t.y, 2, t.h), c);
-}
-
-static void tile_volume_ex(mu_Rect t, int slot, int is_screen) {
-	if (slot < 0 || slot >= VS_MAX) return;
-	if (!in_rect(t, mouse_x, mouse_y)) return;     /* hover only */
-	int pad = 8, bh = 6;
-	mu_Rect bar = mu_rect(t.x + pad, t.y + t.h - pad - bh,
-	                      t.w - 2 * pad, bh);
-	if (bar.w < 20) return;
-	int g = is_screen ? audio_share_get_gain(slot) : audio_get_gain(slot);
-	int fillw = bar.w * g / 200;
-	px_fill(bar, 0xff333333);                       /* track */
-	px_fill(mu_rect(bar.x, bar.y, fillw, bh), 0xffdddddd);  /* level */
-	/* knob */
-	px_fill(mu_rect(bar.x + fillw - 1, bar.y - 2, 3, bh + 4), 0xffffffff);
-	if (vol_hits.n < VS_MAX) {
-		vol_hits.bar[vol_hits.n] = bar;
-		vol_hits.slot[vol_hits.n] = is_screen ? slot + 100 : slot;
-		vol_hits.n++;
-	}
-}
-
-static void tile_volume(mu_Rect t, int slot) { tile_volume_ex(t, slot, 0); }
-
-static void tile_label(mu_Rect t, const char *name) {
-	CLIP = t;
-	draw_text(name, mu_vec2(t.x + 4, t.y + 4), mu_color(221, 221, 221, 255));
-	CLIP = mu_rect(0, 0, FB->w, FB->h);
-}
-
-static void tile_status(mu_Rect t, int muted, int deaf) {
-	const char *s = deaf ? "deafened" : muted ? "muted" : NULL;
-	if (!s) return;
-	CLIP = t;
-	int tw = text_width(NULL, s, (int)strlen(s));
-	draw_text(s, mu_vec2(t.x + t.w - tw - 6, t.y + t.h - 18),
-	          mu_color(230, 64, 64, 255));   /* red */
-	CLIP = mu_rect(0, 0, FB->w, FB->h);
-}
-
-static void render_call_view(int all) {
-	long now = now_ms();
-	int slots[VS_MAX]; char names[VS_MAX][32];
-	int np = A.in_call ? net_peers(slots, names, VS_MAX) : 0;
-
-	mu_Rect area = mu_rect(0, controls_h_now, FB->w, FB->h - controls_h_now);
-	if (area.h < 40) return;
-
-	pthread_mutex_lock(&vs_mu);
-
-	int self_sl = net_self_slot();
-	int live[VS_MAX], nlive = 0, freshest = -1; long best = 0;
-	for (int i = 0; i < VS_MAX; i++)
-		if (s_scr[i].px && now - s_scr[i].last < STREAM_TTL) {
-			live[nlive++] = i;
-			if (s_scr[i].started > best) {   /* newest START pins */
-				freshest = i; best = s_scr[i].started;
-			}
-		}
-	int pin = -1;
-	if (pin_slot >= 0 && s_scr[pin_slot].px &&
-	    now - s_scr[pin_slot].last < STREAM_TTL) pin = pin_slot;
-	else pin = freshest;
-
-	share_hits.big_live = 0;
-	share_hits.ntile = 0;
-	vol_hits.n = 0;
-
-	{
-		int sig = np * 31 + pin * 7 + nlive * 131 + FB->w * 3 + FB->h * 5
-		        + controls_h_now * 17;
-		for (int i = 0; i < np; i++) sig = sig * 33 + slots[i];
-		static int last_sig;
-		if (sig != last_sig) { all = 1; last_sig = sig; }
-	}
-
-	mu_Rect tiles = area;
-	if (pin >= 0) {
-		mu_Rect share = mu_rect(area.x, area.y, area.w, area.h * 3 / 4);
-		tiles = mu_rect(area.x, area.y + share.h, area.w, area.h - share.h);
-		if (all || s_scr[pin].painted != s_scr[pin].last) {
-			if (all) px_fill(share, 0xff141414);
-			blit_scaled_bg(s_scr[pin].px, s_scr[pin].w, s_scr[pin].h, share, 0xff141414);
-			s_scr[pin].painted = s_scr[pin].last;
-			if (pin == self_sl) tile_label(share, "your share");
-			else for (int i = 0; i < np; i++)
-				if (slots[i] == pin) { tile_label(share, names[i]); break; }
-			hover_border(share);
-		}
-		share_hits.big = share;
-		share_hits.big_live = 1;
-
-		if (pin != self_sl)
-			tile_volume_ex(share, pin, 1);
-	}
-
-	int nshare = (pin >= 0) ? nlive - 1 : 0;
-	int n = np + 1 + nshare;
-	int cols = (pin >= 0) ? n : (n <= 1 ? 1 : n <= 4 ? 2 : 3);
-	if (cols < 1) cols = 1;
-	int rows = (n + cols - 1) / cols;
-	int tw = tiles.w / cols, th = tiles.h / rows;
-	if (tw < 8 || th < 8) { pthread_mutex_unlock(&vs_mu); return; }
-	int ti = 0;
-	for (int i = 0; i < np + 1; i++, ti++) {
-		mu_Rect t = mu_rect(tiles.x + (ti % cols) * tw + 1,
-		                    tiles.y + (ti / cols) * th + 1, tw - 2, th - 2);
-		vstream *s = NULL;
-		const char *lab = NULL;
-		char self[44];
-		if (i == 0) {                       /* self: local camera preview */
-			int ssl = self_sl >= 0 ? self_sl : PREVIEW_SLOT;
-			if (ssl >= 0 && ssl < VS_MAX) s = &s_cam[ssl];
-			snprintf(self, sizeof(self), "%s (you)", A.name);
-			lab = self;
-		} else {
-			s = &s_cam[slots[i - 1]];
-			lab = names[i - 1];
-		}
-		int live = s && s->px && now - s->last < STREAM_TTL;
-		int remote_slot = (i == 0) ? -1 : slots[i - 1];
-		int hov = in_rect(t, mouse_x, mouse_y);
-		if (all || (live && s->painted != s->last) || hov) {
-			if (all || hov) px_fill(t, 0xff202020);
-			if (live) { blit_scaled_bg(s->px, s->w, s->h, t, 0xff202020);
-			            s->painted = s->last; }
-			tile_label(t, lab);
-			if (remote_slot >= 0) tile_volume(t, remote_slot);  /* hover vol */
-			/* mute/deafen status badge (bottom-right, red) */
-			if (i == 0)
-				tile_status(t, A.muted, A.deaf);   /* my own state */
-			else {
-				int st = net_peer_state(remote_slot);
-				tile_status(t, st & 1, st & 2);
-			}
-		}
-	}
-	for (int k = 0; k < nlive && pin >= 0; k++) {
-		int sl = live[k];
-		if (sl == pin) continue;
-		mu_Rect t = mu_rect(tiles.x + (ti % cols) * tw + 1,
-		                    tiles.y + (ti / cols) * th + 1, tw - 2, th - 2);
-		ti++;
-		if (all || s_scr[sl].painted != s_scr[sl].last) {
-			blit_scaled_bg(s_scr[sl].px, s_scr[sl].w, s_scr[sl].h, t, 0xff141414);
-			s_scr[sl].painted = s_scr[sl].last;
-			char lab[44] = "share";
-			if (sl == self_sl) snprintf(lab, sizeof(lab), "you (share)");
-			else for (int i = 0; i < np; i++)
-				if (slots[i] == sl) {
-					snprintf(lab, sizeof(lab), "%s (share)", names[i]);
-					break;
-				}
-			tile_label(t, lab);
-			hover_border(t);
-		}
-		share_hits.tile[share_hits.ntile] = t;
-		share_hits.tile_slot[share_hits.ntile] = sl;
-		share_hits.ntile++;
-	}
-	pthread_mutex_unlock(&vs_mu);
-}
-
-void ui_frame(fb *f) {
-	controls_h_now = CONTROLS_H;
-	if (A.show_dev)
-		controls_h_now += 60 + 26 * (A.ndev[0] + A.ndev[1]);
-	static uint32_t *last_px;
-	static int last_w, last_h;
-	int fb_changed = (f->px != last_px || f->w != last_w || f->h != last_h);
-	FB = f;
-	if (A.dirty || fb_changed) {
-		build();
-		rasterize();          /* repaints the whole window background */
-		render_call_view(1);  /* so the call view must repaint fully */
-		last_px = f->px; last_w = f->w; last_h = f->h;
-		A.dirty = 0;
-	} else {
-		render_call_view(0);  /* only streams with new frames re-blit */
-	}
-}
blob - b46cf5fa3e8cb58fa688e9740ca1303b6e0ac826
blob + b82face40891902ee4ccb66ad5c2f73f15631609
--- src/update.c
+++ src/update.c
@@ -10,7 +10,7 @@ static int run_argv(char *const argv[]);
 
 #ifndef MILNA_PUBKEY
 #define MILNA_PUBKEY 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, \
-                     0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0
+	0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0
 #endif
 static const unsigned char SIGN_PUBKEY[32] = { MILNA_PUBKEY };
 
@@ -28,7 +28,7 @@ static int signify_sig_bytes(const char *path, unsigne
 		char *nl = strpbrk(line, "\r\n"); if (nl) *nl = 0;
 		unsigned char raw[128]; size_t rawlen = 0;
 		if (sodium_base642bin(raw, sizeof(raw), line, strlen(line),
-		        NULL, &rawlen, NULL, sodium_base64_VARIANT_ORIGINAL) != 0)
+			NULL, &rawlen, NULL, sodium_base64_VARIANT_ORIGINAL) != 0)
 			break;
 		if (rawlen != 2 + 8 + 64) break;
 		memcpy(sig, raw + 10, 64);
@@ -55,10 +55,10 @@ static int verify_file(const char *file, const char *s
 	fclose(f);
 	if (rd != (size_t)sz) { free(buf); return -1; }
 	int ok = crypto_sign_verify_detached(sig, buf, (size_t)sz,
-	                                      SIGN_PUBKEY) == 0;
+		SIGN_PUBKEY) == 0;
 	free(buf);
 	if (!ok) fprintf(stderr, "[milna] update: SIGNATURE VERIFY FAILED — "
-	                 "refusing to install\n");
+		"refusing to install\n");
 	return ok ? 0 : -1;
 }
 
@@ -68,10 +68,10 @@ static int fetch_and_verify(const char *url, const cha
 	snprintf(sigurl, sizeof(sigurl), "%s.sig", url);
 	snprintf(sigfile, sizeof(sigfile), "%s.sig", localfile);
 	char *sv[] = { (char*)"curl", (char*)"-fsSL", (char*)"--max-time",
-	               (char*)"30", sigurl, (char*)"-o", sigfile, NULL };
+		(char*)"30", sigurl, (char*)"-o", sigfile, NULL };
 	if (run_argv(sv) != 0) {
 		fprintf(stderr, "[milna] update: could not fetch signature %s\n",
-		        sigurl);
+			sigurl);
 		return -1;
 	}
 	int r = verify_file(localfile, sigfile);
@@ -108,8 +108,8 @@ static void *update_run_blocking_thunk(void *a);
 static char g_self[1024];
 static char g_new[1024];
 
+#ifdef __APPLE__
 static int bundle_path(char *out, size_t cap) {
-#ifdef __APPLE__
 	const char *marker = ".app/Contents/MacOS/";
 	char tmp[1024];
 	snprintf(tmp, sizeof(tmp), "%s", g_self);
@@ -118,10 +118,8 @@ static int bundle_path(char *out, size_t cap) {
 	p[4] = 0;
 	snprintf(out, cap, "%s", tmp);
 	return 1;
-#else
-	(void)out; (void)cap; return 0;
-#endif
 }
+#endif
 static volatile int g_ready;
 static char g_latest[32];
 static char g_localver[32];
@@ -148,7 +146,6 @@ static void self_path(char *out, size_t cap) {
 #elif defined(__APPLE__)
 	char raw[1024]; uint32_t sz = sizeof(raw);
 	if (_NSGetExecutablePath(raw, &sz) != 0) { out[0] = 0; return; }
-
 	if (!realpath(raw, out)) snprintf(out, cap, "%s", raw);
 #else
 	ssize_t n = readlink("/proc/self/exe", out, cap - 1);
@@ -159,7 +156,6 @@ static void self_path(char *out, size_t cap) {
 #ifdef _WIN32
 #include <process.h>
 static int run_argv(char *const argv[]) {
-
 	intptr_t r = _spawnvp(_P_WAIT, argv[0], (const char *const *)argv);
 	return r == 0 ? 0 : -1;
 }
@@ -180,7 +176,7 @@ static int run_argv(char *const argv[]) {
 static int fetch_version(char *buf, size_t cap) {
 	char vp[1100]; snprintf(vp, sizeof(vp), "%s.ver", g_self);
 	char *argv[] = { (char*)"curl", (char*)"-fsSL", (char*)"--max-time",
-	                 (char*)"8", (char*)VER_URL, (char*)"-o", vp, NULL };
+		(char*)"8", (char*)VER_URL, (char*)"-o", vp, NULL };
 	if (run_argv(argv) != 0) { remove(vp); return -1; }
 	FILE *f = fopen(vp, "r");
 	if (!f) return -1;
@@ -214,7 +210,7 @@ void update_run_blocking(void) {
 	char remote[32];
 	if (fetch_version(remote, sizeof(remote)) != 0) {
 		fprintf(stderr, "[milna] update: version check failed (no net / "
-		        "url unreachable)\n");
+			"url unreachable)\n");
 		return;
 	}
 	fprintf(stderr, "[milna] update: local=%s remote=%s\n", local, remote);
@@ -223,28 +219,25 @@ void update_run_blocking(void) {
 		return;
 	}
 	fprintf(stderr, "[milna] update: downloading %s%s%s ...\n",
-	        BIN_BASE, PLAT, EXE);
+		BIN_BASE, PLAT, EXE);
 	char url[1200];
 #ifdef __APPLE__
 	char bp[1024];
 	int in_bundle = bundle_path(bp, sizeof(bp));
 	if (in_bundle) {
-
 		snprintf(url, sizeof(url), "%smacos-app.zip", BIN_BASE);
 		char zip[1100]; snprintf(zip, sizeof(zip), "%s.zip", g_new);
 		char *dz[] = { (char*)"curl", (char*)"-fsSL", (char*)"--max-time",
-		               (char*)"180", url, (char*)"-o", zip, NULL };
+			(char*)"180", url, (char*)"-o", zip, NULL };
 		fprintf(stderr, "[milna] update: bundle mode, fetching %s\n", url);
 		if (run_argv(dz) != 0) {
 			fprintf(stderr, "[milna] update: DOWNLOAD FAILED — is "
-			        "milna-macos-app.zip on the vps?\n");
+				"milna-macos-app.zip on the vps?\n");
 			remove(zip); return;
 		}
-
 		char *xz[] = { (char*)"xattr", (char*)"-d",
-		               (char*)"com.apple.quarantine", zip, NULL };
+			(char*)"com.apple.quarantine", zip, NULL };
 		run_argv(xz);
-
 		if (fetch_and_verify(url, zip) != 0) {
 			remove(zip); return;
 		}
@@ -253,27 +246,25 @@ void update_run_blocking(void) {
 		g_ready = 1;
 		pthread_mutex_unlock(&g_mu);
 		fprintf(stderr, "[milna] update: %s (bundle) downloaded, banner "
-		        "will show\n", remote);
+			"will show\n", remote);
 		return;
 	}
 #endif
 	snprintf(url, sizeof(url), "%s%s%s", BIN_BASE, PLAT, EXE);
 	char *dargv[] = { (char*)"curl", (char*)"-fsSL", (char*)"--max-time",
-	                  (char*)"120", url, (char*)"-o", g_new, NULL };
+		(char*)"120", url, (char*)"-o", g_new, NULL };
 	if (run_argv(dargv) != 0) {
 		fprintf(stderr, "[milna] update: DOWNLOAD FAILED — is %s "
-		        "uploaded to the vps?\n", url);
+			"uploaded to the vps?\n", url);
 		remove(g_new); return;
 	}
-
 	if (fetch_and_verify(url, g_new) != 0) {
 		remove(g_new); return;
 	}
 #ifndef _WIN32
 	chmod(g_new, 0755);
-
 	char *xargv[] = { (char*)"xattr", (char*)"-d",
-	                  (char*)"com.apple.quarantine", g_new, NULL };
+		(char*)"com.apple.quarantine", g_new, NULL };
 	run_argv(xargv);
 #endif
 	pthread_mutex_lock(&g_mu);
@@ -291,7 +282,6 @@ void update_init(const char *local_version) {
 	fprintf(stderr, "[milna] update_init: self=%s\n", g_self);
 
 #ifdef __APPLE__
-
 	{
 		char bp[1024];
 		char zip[1100]; snprintf(zip, sizeof(zip), "%s.zip", g_new);
@@ -299,31 +289,28 @@ void update_init(const char *local_version) {
 		if (z && bundle_path(bp, sizeof(bp))) {
 			fclose(z);
 			fprintf(stderr, "[milna] update: staged bundle zip found, "
-			        "launching swap helper for %s\n", bp);
-
+				"launching swap helper for %s\n", bp);
 			char hp[] = "/tmp/milna_update.XXXXXX";
 			int hfd = mkstemp(hp);
 			FILE *h = (hfd >= 0) ? fdopen(hfd, "w") : NULL;
 			if (h) {
 				fchmod(hfd, 0700);
-
 				fprintf(h,
-				    "#!/bin/sh\n"
-				    "pid=\"$1\"; app=\"$2\"; zip=\"$3\"\n"
-				    "for i in $(seq 1 50); do kill -0 \"$pid\" 2>/dev/null || break; sleep 0.1; done\n"
-				    "tmp=$(mktemp -d /tmp/milna.XXXXXX) || exit 1\n"
-				    "if ! /usr/bin/unzip -oq \"$zip\" -d \"$tmp\"; then rm -rf \"$tmp\"; exit 1; fi\n"
-				    "newapp=$(find \"$tmp\" -maxdepth 2 -name '*.app' -type d | head -1)\n"
-				    "[ -n \"$newapp\" ] || { rm -rf \"$tmp\"; exit 1; }\n"
-				    "/usr/bin/xattr -cr \"$newapp\" 2>/dev/null\n"
-				    "rm -rf \"$app.old\"\n"
-				    "mv \"$app\" \"$app.old\" 2>/dev/null\n"
-				    "if ! mv \"$newapp\" \"$app\"; then mv \"$app.old\" \"$app\"; rm -rf \"$tmp\"; exit 1; fi\n"
-				    "/usr/bin/codesign --force --deep --sign - \"$app\" 2>/dev/null\n"
-				    "rm -rf \"$app.old\" \"$tmp\" \"$zip\"\n"
-				    "/usr/bin/open \"$app\"\n");
+					"#!/bin/sh\n"
+					"pid=\"$1\"; app=\"$2\"; zip=\"$3\"\n"
+					"for i in $(seq 1 50); do kill -0 \"$pid\" 2>/dev/null || break; sleep 0.1; done\n"
+					"tmp=$(mktemp -d /tmp/milna.XXXXXX) || exit 1\n"
+					"if ! /usr/bin/unzip -oq \"$zip\" -d \"$tmp\"; then rm -rf \"$tmp\"; exit 1; fi\n"
+					"newapp=$(find \"$tmp\" -maxdepth 2 -name '*.app' -type d | head -1)\n"
+					"[ -n \"$newapp\" ] || { rm -rf \"$tmp\"; exit 1; }\n"
+					"/usr/bin/xattr -cr \"$newapp\" 2>/dev/null\n"
+					"rm -rf \"$app.old\"\n"
+					"mv \"$app\" \"$app.old\" 2>/dev/null\n"
+					"if ! mv \"$newapp\" \"$app\"; then mv \"$app.old\" \"$app\"; rm -rf \"$tmp\"; exit 1; fi\n"
+					"/usr/bin/codesign --force --deep --sign - \"$app\" 2>/dev/null\n"
+					"rm -rf \"$app.old\" \"$tmp\" \"$zip\"\n"
+					"/usr/bin/open \"$app\"\n");
 				fclose(h);
-
 				char pidbuf[16];
 				snprintf(pidbuf, sizeof(pidbuf), "%d", (int)getpid());
 				char *hv[] = { (char*)"/bin/sh", hp, pidbuf, bp, zip, NULL };
@@ -334,7 +321,7 @@ void update_init(const char *local_version) {
 					_exit(127);
 				}
 				fprintf(stderr, "[milna] update: helper spawned, quitting "
-				        "for bundle swap\n");
+					"for bundle swap\n");
 				_exit(0);
 			}
 		} else if (z) fclose(z);
@@ -343,7 +330,7 @@ void update_init(const char *local_version) {
 
 	FILE *t = fopen(g_new, "rb");
 	fprintf(stderr, "[milna] update_init: pending .new %s\n",
-	        t ? "FOUND — promoting" : "none");
+		t ? "FOUND — promoting" : "none");
 	if (t) {
 		fclose(t);
 #ifdef _WIN32
@@ -352,38 +339,32 @@ void update_init(const char *local_version) {
 		MoveFileA(g_self, old);
 		if (MoveFileA(g_new, g_self) == 0)
 			MoveFileA(old, g_self);
-		else
-
-			;
 #else
 		if (rename(g_new, g_self) != 0) {
 			fprintf(stderr, "[milna] update: PROMOTE FAILED (%s) — is %s "
-			        "in a writable location (not /Applications)?\n",
-			        strerror(errno), g_self);
+				"in a writable location (not /Applications)?\n",
+				strerror(errno), g_self);
 		} else {
-
 			fprintf(stderr, "[milna] update: promoted, relaunching\n");
 #ifdef __APPLE__
-
 			{
 				char bundle[1024];
 				snprintf(bundle, sizeof(bundle), "%s", g_self);
 				char *p = strstr(bundle, ".app/Contents/MacOS/");
 				if (p) {
 					p[4] = 0;
-
 					char *cs[] = { (char*)"codesign", (char*)"--force",
-					               (char*)"--sign", (char*)"-", bundle, NULL };
+						(char*)"--sign", (char*)"-", bundle, NULL };
 					int rc = run_argv(cs);
 					fprintf(stderr, "[milna] update: re-signed bundle "
-					        "(%s, rc=%d)\n", bundle, rc);
+						"(%s, rc=%d)\n", bundle, rc);
 				}
 			}
 #endif
 			char *av[] = { g_self, NULL };
 			execv(g_self, av);
 			fprintf(stderr, "[milna] update: re-exec failed (%s)\n",
-			        strerror(errno));
+				strerror(errno));
 		}
 #endif
 	}
@@ -392,7 +373,6 @@ void update_init(const char *local_version) {
 }
 
 #ifdef __APPLE__
-
 void update_dispatch_bg(void);
 #endif
 
@@ -406,13 +386,12 @@ void update_check_async(void) {
 #else
 	pthread_t th;
 	if (pthread_create(&th, NULL,
-	        (void *(*)(void *))(void *)update_run_blocking_thunk, NULL) == 0)
+		(void *(*)(void *))(void *)update_run_blocking_thunk, NULL) == 0)
 		pthread_detach(th);
 #endif
 }
 
 #ifndef __APPLE__
-
 static void *update_run_blocking_thunk(void *a) {
 	(void)a; update_run_blocking(); return NULL;
 }
blob - 76573d1e8ea0472ddfd2f5e5d3193f61d3810e33
blob + ae03318d7a8d1c8911fefc54e607d63a3e0b03e7
--- src/update.h
+++ src/update.h
@@ -1,9 +1,9 @@
 #ifndef UPDATE_H
 #define UPDATE_H
 #include <stdint.h>
-void        update_init(const char *local_version);  
-void        update_check_async(void);   
-void        update_run_blocking(void);  
-int         update_ready(void);            
-const char *update_latest_version(void);   
+void        update_init(const char *local_version);
+void        update_check_async(void);
+void        update_run_blocking(void);
+int         update_ready(void);
+const char *update_latest_version(void);
 #endif
blob - f909cb9a60ee68b9734e30f6584f5dd8102a65bd
blob + e94fe83bb0a533ffb934c0c911a96165d27559fc
--- src/vid.c
+++ src/vid.c
@@ -8,7 +8,7 @@
 
 struct vid_enc {
 	vpx_codec_ctx_t ctx;
-	vpx_image_t img;          /* I420 staging */
+	vpx_image_t img;
 	int w, h, fps;
 	int64_t pts;
 };
@@ -21,7 +21,7 @@ static void bgra_to_i420(const uint8_t *s, int w, int 
 	uint8_t *U = im->planes[VPX_PLANE_U];
 	uint8_t *Vp = im->planes[VPX_PLANE_V];
 	int sy = im->stride[VPX_PLANE_Y], su = im->stride[VPX_PLANE_U],
-	    sv = im->stride[VPX_PLANE_V];
+		sv = im->stride[VPX_PLANE_V];
 	for (int y = 0; y < h; y++) {
 		const uint8_t *p = s + (size_t)y * w * 4;
 		for (int x = 0; x < w; x++) {
@@ -32,7 +32,7 @@ static void bgra_to_i420(const uint8_t *s, int w, int 
 	for (int y = 0; y < h / 2; y++) {
 		const uint8_t *p = s + (size_t)(y * 2) * w * 4;
 		for (int x = 0; x < w / 2; x++) {
-			int b = p[0], g = p[1], r = p[2];   /* sample top-left of 2x2 */
+			int b = p[0], g = p[1], r = p[2];
 			U[y * su + x]  = (uint8_t)(((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128);
 			Vp[y * sv + x] = (uint8_t)(((112 * r - 94 * g - 18 * b + 128) >> 8) + 128);
 			p += 8;
@@ -44,7 +44,7 @@ static void i420_to_bgra(const vpx_image_t *im, uint8_
 	const uint8_t *U = im->planes[VPX_PLANE_U];
 	const uint8_t *Vp = im->planes[VPX_PLANE_V];
 	int sy = im->stride[VPX_PLANE_Y], su = im->stride[VPX_PLANE_U],
-	    sv = im->stride[VPX_PLANE_V];
+		sv = im->stride[VPX_PLANE_V];
 	for (int y = 0; y < h; y++) {
 		uint8_t *p = d + (size_t)y * w * 4;
 		for (int x = 0; x < w; x++) {
@@ -63,9 +63,8 @@ static void i420_to_bgra(const vpx_image_t *im, uint8_
 	}
 }
 
-/* ---- encoder ---- */
 vid_enc *vid_enc_new(int w, int h, int fps, int kbps) {
-	w &= ~1; h &= ~1;          /* I420 needs even dims */
+	w &= ~1; h &= ~1;
 	vid_enc *e = calloc(1, sizeof(*e));
 	if (!e) return NULL;
 	vpx_codec_enc_cfg_t cfg;
@@ -75,13 +74,13 @@ vid_enc *vid_enc_new(int w, int h, int fps, int kbps) 
 	cfg.rc_target_bitrate = kbps;
 	cfg.rc_end_usage = VPX_CBR;
 	cfg.g_pass = VPX_RC_ONE_PASS;
-	cfg.g_lag_in_frames = 0;                /* realtime: no lookahead */
+	cfg.g_lag_in_frames = 0;
 	cfg.kf_mode = VPX_KF_AUTO;
-	cfg.kf_max_dist = fps * 5;              /* keyframe at least every 5s */
+	cfg.kf_max_dist = fps * 5;
 	cfg.g_threads = 4;
 	cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT;
 	if (vpx_codec_enc_init(&e->ctx, vpx_codec_vp8_cx(), &cfg, 0)) { free(e); return NULL; }
-	vpx_codec_control(&e->ctx, VP8E_SET_CPUUSED, 8);   /* fastest realtime */
+	vpx_codec_control(&e->ctx, VP8E_SET_CPUUSED, 8);
 	if (!vpx_img_alloc(&e->img, VPX_IMG_FMT_I420, w, h, 16)) {
 		vpx_codec_destroy(&e->ctx); free(e); return NULL;
 	}
@@ -90,7 +89,7 @@ vid_enc *vid_enc_new(int w, int h, int fps, int kbps) 
 }
 
 int vid_enc_frame(vid_enc *e, const uint8_t *bgra, int force_key,
-                  uint8_t *out, int cap, int *is_key) {
+	uint8_t *out, int cap, int *is_key) {
 	if (!e) return -1;
 	bgra_to_i420(bgra, e->w, e->h, &e->img);
 	vpx_enc_frame_flags_t fl = force_key ? VPX_EFLAG_FORCE_KF : 0;
@@ -126,14 +125,14 @@ vid_dec *vid_dec_new(void) {
 }
 
 int vid_dec_frame(vid_dec *d, const uint8_t *data, int len,
-                  uint8_t *out, int out_cap, int *w, int *h) {
+	uint8_t *out, int out_cap, int *w, int *h) {
 	if (!d) return -1;
 	if (vpx_codec_decode(&d->ctx, data, (unsigned)len, NULL, 0)) return -2;
 	vpx_codec_iter_t it = NULL;
 	vpx_image_t *im = vpx_codec_get_frame(&d->ctx, &it);
 	if (!im) return 0;
 	int iw = (int)im->d_w, ih = (int)im->d_h;
-	/* capacity check BEFORE any write: oversized stream cannot overflow */
+
 	if (iw > VID_MAX_W || ih > VID_MAX_H) return -3;
 	if ((int64_t)iw * ih * 4 > out_cap) return -3;
 	i420_to_bgra(im, out, iw, ih);
blob - 6dbe02c1700354922009422ef961a5fb54c0e88c
blob + ee069167bbcd4aca6b24b3ada12d2d3637b2a31d
--- src/vid.h
+++ src/vid.h
@@ -1,5 +1,6 @@
 #ifndef VID_H
 #define VID_H
+#include <stddef.h>
 #include <stdint.h>
 
 #define VID_MAX_W 3840
@@ -10,12 +11,29 @@ typedef struct vid_dec vid_dec;
 
 vid_enc *vid_enc_new(int w, int h, int fps, int kbps);
 int  vid_enc_frame(vid_enc *e, const uint8_t *bgra, int force_key,
-                   uint8_t *out, int cap, int *is_key);
+	uint8_t *out, int cap, int *is_key);
 void vid_enc_del(vid_enc *e);
 
 vid_dec *vid_dec_new(void);
 int  vid_dec_frame(vid_dec *d, const uint8_t *data, int len,
-                   uint8_t *out, int out_cap, int *w, int *h);
+	uint8_t *out, int out_cap, int *w, int *h);
 void vid_dec_del(vid_dec *d);
 
+void vid_nv12_to_bgra(const uint8_t *Y, int ystride, const uint8_t *UV,
+	int uvstride, int w, int h, uint8_t *dst);
+
+static inline void bgra_half(const uint8_t *src, int w, int h, uint8_t *dst) {
+	for (int y = 0; y < h / 2; y++) {
+		const uint8_t *r0 = src + (size_t)(y * 2) * w * 4;
+		const uint8_t *r1 = r0 + (size_t)w * 4;
+		uint8_t *d = dst + (size_t)y * (w / 2) * 4;
+		for (int x = 0; x < w / 2; x++) {
+			const uint8_t *a = r0 + x * 8, *b = a + 4;
+			const uint8_t *c = r1 + x * 8, *e = c + 4;
+			for (int k = 0; k < 4; k++)
+				d[x * 4 + k] = (uint8_t)((a[k] + b[k] + c[k] + e[k]) >> 2);
+		}
+	}
+}
+
 #endif
blob - 76e39dc4829367acea5670bdc4380121bc33da12
blob + 5df5e82c10d33afecf23b0bd23cb7bf83cd3bdf1
--- src/vid_mac.m
+++ src/vid_mac.m
@@ -13,7 +13,7 @@ struct vid_enc {
 	VTCompressionSessionRef sess;
 	int w, h, fps;
 	int64_t pts;
-	/* per-call output staging, filled by the compression callback */
+
 	uint8_t *out; int out_cap; int out_len; int out_key;
 };
 
@@ -27,7 +27,7 @@ static void put_nalu(struct vid_enc *e, const uint8_t 
 }
 
 static void enc_cb(void *refcon, void *src, OSStatus status,
-                   VTEncodeInfoFlags flags, CMSampleBufferRef sb) {
+	VTEncodeInfoFlags flags, CMSampleBufferRef sb) {
 	(void)src; (void)flags;
 	struct vid_enc *e = refcon;
 	if (status != noErr || !sb || !CMSampleBufferDataIsReady(sb)) return;
@@ -40,14 +40,14 @@ static void enc_cb(void *refcon, void *src, OSStatus s
 	} else key = 1;
 	e->out_key = key;
 
-	if (key) {   /* SPS+PPS in-band on keyframes */
+	if (key) {
 		CMFormatDescriptionRef fd = CMSampleBufferGetFormatDescription(sb);
 		const uint8_t *ps; size_t pn; size_t cnt = 0;
 		CMVideoFormatDescriptionGetH264ParameterSetAtIndex(fd, 0, NULL, NULL,
-		                                                   &cnt, NULL);
+			&cnt, NULL);
 		for (size_t i = 0; i < cnt; i++)
 			if (CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
-			        fd, i, &ps, &pn, NULL, NULL) == noErr)
+				fd, i, &ps, &pn, NULL, NULL) == noErr)
 				put_nalu(e, ps, pn);
 	}
 
@@ -56,9 +56,9 @@ static void enc_cb(void *refcon, void *src, OSStatus s
 	if (CMBlockBufferGetDataPointer(bb, 0, NULL, &total, &data) != noErr)
 		return;
 	size_t off = 0;
-	while (off + 4 <= total) {     /* AVCC -> annex-b */
+	while (off + 4 <= total) {
 		uint32_t n = ((uint8_t)data[off] << 24) | ((uint8_t)data[off+1] << 16)
-		           | ((uint8_t)data[off+2] << 8) | (uint8_t)data[off+3];
+			| ((uint8_t)data[off+2] << 8) | (uint8_t)data[off+3];
 		off += 4;
 		if (off + n > total) break;
 		put_nalu(e, (const uint8_t *)data + off, n);
@@ -72,14 +72,14 @@ vid_enc *vid_enc_new(int w, int h, int fps, int kbps) 
 	if (!e) return NULL;
 
 	CFMutableDictionaryRef src = CFDictionaryCreateMutable(NULL, 2,
-	    &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+		&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
 	int fmt = kCVPixelFormatType_32BGRA;
 	CFNumberRef nfmt = CFNumberCreate(NULL, kCFNumberIntType, &fmt);
 	CFDictionarySetValue(src, kCVPixelBufferPixelFormatTypeKey, nfmt);
 	CFRelease(nfmt);
 
 	OSStatus st = VTCompressionSessionCreate(NULL, w, h,
-	    kCMVideoCodecType_H264, NULL, src, NULL, enc_cb, e, &e->sess);
+		kCMVideoCodecType_H264, NULL, src, NULL, enc_cb, e, &e->sess);
 	CFRelease(src);
 	if (st != noErr) {
 		VLOG("VTCompressionSessionCreate: %d", (int)st);
@@ -90,7 +90,7 @@ vid_enc *vid_enc_new(int w, int h, int fps, int kbps) 
 	SETP(kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
 	SETP(kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse);
 	SETP(kVTCompressionPropertyKey_ProfileLevel,
-	     kVTProfileLevel_H264_High_AutoLevel);
+		kVTProfileLevel_H264_High_AutoLevel);
 	int br = kbps * 1000;
 	CFNumberRef nbr = CFNumberCreate(NULL, kCFNumberIntType, &br);
 	SETP(kVTCompressionPropertyKey_AverageBitRate, nbr); CFRelease(nbr);
@@ -115,7 +115,7 @@ vid_enc *vid_enc_new(int w, int h, int fps, int kbps) 
 }
 
 int vid_enc_frame(vid_enc *e, const uint8_t *bgra, int force_key,
-                  uint8_t *out, int cap, int *is_key) {
+	uint8_t *out, int cap, int *is_key) {
 	if (!e) return -1;
 
 	CVPixelBufferPoolRef pool = VTCompressionSessionGetPixelBufferPool(e->sess);
@@ -136,10 +136,10 @@ int vid_enc_frame(vid_enc *e, const uint8_t *bgra, int
 		const void *k[1] = { kVTEncodeFrameOptionKey_ForceKeyFrame };
 		const void *v[1] = { kCFBooleanTrue };
 		props = CFDictionaryCreate(NULL, k, v, 1,
-		    &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+			&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
 	}
 	OSStatus st = VTCompressionSessionEncodeFrame(e->sess, pb,
-	    CMTimeMake(e->pts++, e->fps), kCMTimeInvalid, props, NULL, NULL);
+		CMTimeMake(e->pts++, e->fps), kCMTimeInvalid, props, NULL, NULL);
 	if (props) CFRelease(props);
 	CVPixelBufferRelease(pb);
 	if (st != noErr) return -3;
@@ -165,8 +165,8 @@ struct vid_dec {
 };
 
 static void dec_cb(void *refcon, void *src, OSStatus status,
-                   VTDecodeInfoFlags flags, CVImageBufferRef img,
-                   CMTime pts, CMTime dur) {
+	VTDecodeInfoFlags flags, CVImageBufferRef img,
+	CMTime pts, CMTime dur) {
 	(void)src; (void)flags; (void)pts; (void)dur;
 	struct vid_dec *d = refcon;
 	if (status != noErr || !img) return;
@@ -192,11 +192,11 @@ static int dec_make_session(struct vid_dec *d) {
 	const uint8_t *ps[2] = { d->sps, d->pps };
 	size_t pn[2] = { d->sps_n, d->pps_n };
 	if (CMVideoFormatDescriptionCreateFromH264ParameterSets(NULL, 2, ps, pn,
-	        4, &d->fd) != noErr)
+		4, &d->fd) != noErr)
 		return -1;
 
 	CFMutableDictionaryRef attrs = CFDictionaryCreateMutable(NULL, 2,
-	    &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+		&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
 	int fmt = kCVPixelFormatType_32BGRA;
 	CFNumberRef nfmt = CFNumberCreate(NULL, kCFNumberIntType, &fmt);
 	CFDictionarySetValue(attrs, kCVPixelBufferPixelFormatTypeKey, nfmt);
@@ -204,7 +204,7 @@ static int dec_make_session(struct vid_dec *d) {
 
 	VTDecompressionOutputCallbackRecord cb = { dec_cb, d };
 	OSStatus st = VTDecompressionSessionCreate(NULL, d->fd, NULL, attrs,
-	                                           &cb, &d->sess);
+		&cb, &d->sess);
 	CFRelease(attrs);
 	if (st != noErr) { VLOG("VTDecompressionSessionCreate: %d", (int)st); return -2; }
 	return 0;
@@ -215,10 +215,9 @@ vid_dec *vid_dec_new(void) {
 }
 
 int vid_dec_frame(vid_dec *d, const uint8_t *data, int len,
-                  uint8_t *out, int out_cap, int *w, int *h) {
+	uint8_t *out, int out_cap, int *w, int *h) {
 	if (!d) return -1;
 
-	/* split annex-b; harvest sps/pps; collect vcl nalus */
 	const uint8_t *vcl[64]; size_t vcln[64]; int nvcl = 0;
 	int i = 0, need_new_session = 0;
 	while (i + 4 <= len) {
@@ -236,7 +235,7 @@ int vid_dec_frame(vid_dec *d, const uint8_t *data, int
 		int e = len;
 		for (int j = s; j + 3 <= len; j++) {
 			if (data[j] == 0 && data[j+1] == 0 &&
-			    (data[j+2] == 1 || (j + 4 <= len && data[j+2] == 0 && data[j+3] == 1))) {
+				(data[j+2] == 1 || (j + 4 <= len && data[j+2] == 0 && data[j+3] == 1))) {
 				e = j; break;
 			}
 		}
@@ -275,13 +274,13 @@ int vid_dec_frame(vid_dec *d, const uint8_t *data, int
 
 	CMBlockBufferRef bb = NULL;
 	if (CMBlockBufferCreateWithMemoryBlock(NULL, avcc, total, kCFAllocatorMalloc,
-	        NULL, 0, total, 0, &bb) != noErr) {
+		NULL, 0, total, 0, &bb) != noErr) {
 		free(avcc); return -3;
 	}
 	CMSampleBufferRef sb = NULL;
 	size_t ssz = total;
 	if (CMSampleBufferCreate(NULL, bb, true, NULL, NULL, d->fd, 1, 0, NULL, 1,
-	        &ssz, &sb) != noErr) {
+		&ssz, &sb) != noErr) {
 		CFRelease(bb); return -3;
 	}
 
blob - 58ad6a117543b270300d112ee55154ef79b4414a
blob + f9f5bc20cfb5e113fa4cfd175fa34a777ea0f0fa
--- src/vid_win.c
+++ src/vid_win.c
@@ -9,16 +9,16 @@
 #include <codecapi.h>
 
 DEFINE_GUID(MILNA_AVLowLatencyMode,
-    0x9c27891a, 0xed7a, 0x40e1, 0x88, 0xe8, 0xb2, 0x27, 0x27, 0xa0, 0x24, 0xee);
+	0x9c27891a, 0xed7a, 0x40e1, 0x88, 0xe8, 0xb2, 0x27, 0x27, 0xa0, 0x24, 0xee);
 DEFINE_GUID(MILNA_AVEncVideoForceKeyFrame,
-    0x2c0cf8be, 0x05ae, 0x4641, 0xa4, 0xf8, 0x3b, 0x53, 0xd8, 0xda, 0x59, 0xf5);
+	0x2c0cf8be, 0x05ae, 0x4641, 0xa4, 0xf8, 0x3b, 0x53, 0xd8, 0xda, 0x59, 0xf5);
 
 static HRESULT attr_set_2x32(IMFAttributes *a, const GUID *k,
-                             UINT32 hi, UINT32 lo) {
+	UINT32 hi, UINT32 lo) {
 	return IMFAttributes_SetUINT64(a, k, ((UINT64)hi << 32) | lo);
 }
 static void attr_get_2x32(IMFAttributes *a, const GUID *k,
-                          UINT32 *hi, UINT32 *lo) {
+	UINT32 *hi, UINT32 *lo) {
 	UINT64 v = 0;
 	IMFAttributes_GetUINT64(a, k, &v);
 	*hi = (UINT32)(v >> 32); *lo = (UINT32)v;
@@ -59,9 +59,9 @@ static void bgra_to_nv12(const uint8_t *src, int w, in
 	}
 }
 
-static void nv12_to_bgra(const uint8_t *Y, int ystride,
-                         const uint8_t *UV, int uvstride,
-                         int w, int h, uint8_t *dst) {
+void vid_nv12_to_bgra(const uint8_t *Y, int ystride,
+		const uint8_t *UV, int uvstride,
+		int w, int h, uint8_t *dst) {
 	for (int y = 0; y < h; y++) {
 		const uint8_t *yr = Y + (size_t)y * ystride;
 		const uint8_t *uv = UV + (size_t)(y / 2) * uvstride;
@@ -81,7 +81,7 @@ static void nv12_to_bgra(const uint8_t *Y, int ystride
 }
 
 static IMFSample *mk_sample(const uint8_t *data, DWORD len,
-                            LONGLONG t100ns, LONGLONG dur100ns) {
+	LONGLONG t100ns, LONGLONG dur100ns) {
 	IMFMediaBuffer *buf = NULL; IMFSample *s = NULL;
 	if (FAILED(MFCreateMemoryBuffer(len, &buf))) return NULL;
 	BYTE *p; DWORD max;
@@ -98,12 +98,28 @@ static IMFSample *mk_sample(const uint8_t *data, DWORD
 	return s;
 }
 
+static int ensure_out(IMFSample **s, IMFMediaBuffer **b, DWORD *cap, DWORD need) {
+	if (*cap >= need) return 0;
+	if (*b) IMFMediaBuffer_Release(*b);
+	if (*s) IMFSample_Release(*s);
+	*b = NULL; *s = NULL;
+	if (FAILED(MFCreateSample(s))) { *s = NULL; return -1; }
+	if (FAILED(MFCreateMemoryBuffer(need, b))) {
+		IMFSample_Release(*s);
+		*s = NULL; *b = NULL;
+		return -1;
+	}
+	IMFSample_AddBuffer(*s, *b);
+	*cap = need;
+	return 0;
+}
+
 struct vid_enc {
 	IMFTransform *mft;
 	ICodecAPI *capi;
 	int hw;
 	IMFMediaEventGenerator *evgen;
-	IMFSample *osample;        /* reused output sample (no per-frame alloc) */
+	IMFSample *osample;
 	IMFMediaBuffer *obuffer;
 	DWORD obuf_cap;
 	int w, h, fps;
@@ -122,33 +138,32 @@ vid_enc *vid_enc_new(int w, int h, int fps, int kbps) 
 		MFT_REGISTER_TYPE_INFO oti = { MFMediaType_Video, MFVideoFormat_H264 };
 		IMFActivate **acts = NULL; UINT32 nact = 0;
 		UINT32 flags = MFT_ENUM_FLAG_HARDWARE | MFT_ENUM_FLAG_ASYNCMFT |
-		               MFT_ENUM_FLAG_SORTANDFILTER;
+			MFT_ENUM_FLAG_SORTANDFILTER;
 		if (SUCCEEDED(MFTEnumEx(MFT_CATEGORY_VIDEO_ENCODER, flags,
-		        NULL, &oti, &acts, &nact)) && nact) {
+			NULL, &oti, &acts, &nact)) && nact) {
 			for (UINT32 i = 0; i < nact && !e->mft; i++) {
 				if (SUCCEEDED(IMFActivate_ActivateObject(acts[i],
-				        &IID_IMFTransform, (void **)&e->mft))) {
+					&IID_IMFTransform, (void **)&e->mft))) {
 					WCHAR nm[128] = {0}; UINT32 nl = 0;
 					IMFActivate_GetString(acts[i],
-					    &MFT_FRIENDLY_NAME_Attribute, nm, 128, &nl);
+						&MFT_FRIENDLY_NAME_Attribute, nm, 128, &nl);
 					VLOG("hw h264 encoder: %ls", nm);
 					e->hw = 1;
 				}
 				IMFActivate_Release(acts[i]);
 			}
-			for (UINT32 i = 0; i < nact; i++) ; /* released above on use */
 			CoTaskMemFree(acts);
 		}
 	}
-	if (!e->mft) {   /* software fallback (every windows, incl. VMs) */
+	if (!e->mft) {
 		if (FAILED(CoCreateInstance(&CLSID_CMSH264EncoderMFT, NULL,
-		        CLSCTX_INPROC_SERVER, &IID_IMFTransform, (void **)&e->mft))) {
+			CLSCTX_INPROC_SERVER, &IID_IMFTransform, (void **)&e->mft))) {
 			VLOG("h264 encoder mft unavailable");
 			free(e); return NULL;
 		}
 		VLOG("sw h264 encoder (no hardware mft found)");
 	}
-	/* an async hardware MFT must be unlocked before use */
+
 	if (e->hw) {
 		IMFAttributes *a = NULL;
 		if (SUCCEEDED(IMFTransform_GetAttributes(e->mft, &a))) {
@@ -170,13 +185,13 @@ vid_enc *vid_enc_new(int w, int h, int fps, int kbps) 
 	IMFMediaType_SetGUID(ot, &MF_MT_SUBTYPE, &MFVideoFormat_H264);
 	IMFMediaType_SetUINT32(ot, &MF_MT_AVG_BITRATE, (UINT32)(kbps * 1000));
 	attr_set_2x32((IMFAttributes *)ot, &MF_MT_FRAME_SIZE,
-	              (UINT32)w, (UINT32)h);
+		(UINT32)w, (UINT32)h);
 	attr_set_2x32((IMFAttributes *)ot, &MF_MT_FRAME_RATE, (UINT32)fps, 1);
 	attr_set_2x32((IMFAttributes *)ot, &MF_MT_PIXEL_ASPECT_RATIO, 1, 1);
 	IMFMediaType_SetUINT32(ot, &MF_MT_INTERLACE_MODE,
-	                       MFVideoInterlace_Progressive);
+		MFVideoInterlace_Progressive);
 	IMFMediaType_SetUINT32(ot, &MF_MT_MPEG2_PROFILE,
-	                       eAVEncH264VProfile_Main);
+		eAVEncH264VProfile_Main);
 	HRESULT hr = IMFTransform_SetOutputType(e->mft, 0, ot, 0);
 	IMFMediaType_Release(ot);
 	if (FAILED(hr)) { VLOG("enc SetOutputType: 0x%lx", hr); goto fail; }
@@ -186,10 +201,10 @@ vid_enc *vid_enc_new(int w, int h, int fps, int kbps) 
 	IMFMediaType_SetGUID(it, &MF_MT_MAJOR_TYPE, &MFMediaType_Video);
 	IMFMediaType_SetGUID(it, &MF_MT_SUBTYPE, &MFVideoFormat_NV12);
 	attr_set_2x32((IMFAttributes *)it, &MF_MT_FRAME_SIZE,
-	              (UINT32)w, (UINT32)h);
+		(UINT32)w, (UINT32)h);
 	attr_set_2x32((IMFAttributes *)it, &MF_MT_FRAME_RATE, (UINT32)fps, 1);
 	IMFMediaType_SetUINT32(it, &MF_MT_INTERLACE_MODE,
-	                       MFVideoInterlace_Progressive);
+		MFVideoInterlace_Progressive);
 	hr = IMFTransform_SetInputType(e->mft, 0, it, 0);
 	IMFMediaType_Release(it);
 	if (FAILED(hr)) { VLOG("enc SetInputType: 0x%lx", hr); goto fail; }
@@ -199,7 +214,7 @@ vid_enc *vid_enc_new(int w, int h, int fps, int kbps) 
 
 	if (e->hw)
 		IMFTransform_QueryInterface(e->mft, &IID_IMFMediaEventGenerator,
-		                            (void **)&e->evgen);
+			(void **)&e->evgen);
 	IMFTransform_ProcessMessage(e->mft, MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
 	IMFTransform_ProcessMessage(e->mft, MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
 	e->w = w; e->h = h; e->fps = fps;
@@ -219,18 +234,8 @@ static int enc_drain(struct vid_enc *e, uint8_t *out, 
 		IMFTransform_GetOutputStreamInfo(e->mft, 0, &osi);
 		DWORD need = osi.cbSize ? osi.cbSize : 1024 * 1024;
 
-		if (e->obuf_cap < need) {
-			if (e->obuffer) IMFMediaBuffer_Release(e->obuffer);
-			if (e->osample) IMFSample_Release(e->osample);
-			e->obuffer = NULL; e->osample = NULL;
-			if (FAILED(MFCreateMemoryBuffer(need, &e->obuffer))) return total;
-			if (FAILED(MFCreateSample(&e->osample))) {
-				IMFMediaBuffer_Release(e->obuffer); e->obuffer = NULL;
-				return total;
-			}
-			IMFSample_AddBuffer(e->osample, e->obuffer);
-			e->obuf_cap = need;
-		}
+		if (ensure_out(&e->osample, &e->obuffer, &e->obuf_cap, need))
+			return total;
 		IMFMediaBuffer_SetCurrentLength(e->obuffer, 0);
 		MFT_OUTPUT_DATA_BUFFER odb = {0};
 		odb.pSample = e->osample;
@@ -251,13 +256,12 @@ static int enc_drain(struct vid_enc *e, uint8_t *out, 
 			if (SUCCEEDED(IMFMediaBuffer_Lock(got, &p, &max, &len))) {
 				int has_sps = (len > 4 && (p[4] & 0x1f) == 7);
 				if (clean && !has_sps && !e->seqhdr) {
-					/* fetch SPS/PPS once from the negotiated type */
 					IMFMediaType *cur = NULL;
 					if (SUCCEEDED(IMFTransform_GetOutputCurrentType(
-					        e->mft, 0, &cur))) {
+						e->mft, 0, &cur))) {
 						UINT8 *blob = NULL; UINT32 bn = 0;
 						if (SUCCEEDED(IMFMediaType_GetAllocatedBlob(cur,
-						        &MF_MT_MPEG_SEQUENCE_HEADER, &blob, &bn)) && bn) {
+							&MF_MT_MPEG_SEQUENCE_HEADER, &blob, &bn)) && bn) {
 							e->seqhdr = malloc(bn);
 							if (e->seqhdr) {
 								memcpy(e->seqhdr, blob, bn);
@@ -269,7 +273,7 @@ static int enc_drain(struct vid_enc *e, uint8_t *out, 
 					}
 				}
 				if (clean && !has_sps && e->seqhdr &&
-				    total + (int)e->seqhdr_n <= cap) {
+					total + (int)e->seqhdr_n <= cap) {
 					memcpy(out + total, e->seqhdr, e->seqhdr_n);
 					total += (int)e->seqhdr_n;
 				}
@@ -286,7 +290,7 @@ static int enc_drain(struct vid_enc *e, uint8_t *out, 
 }
 
 int vid_enc_frame(vid_enc *e, const uint8_t *bgra, int force_key,
-                  uint8_t *out, int cap, int *is_key) {
+	uint8_t *out, int cap, int *is_key) {
 	if (!e) return -1;
 	if (is_key) *is_key = 0;
 	if (force_key && e->capi) {
@@ -297,11 +301,11 @@ int vid_enc_frame(vid_enc *e, const uint8_t *bgra, int
 	bgra_to_nv12(bgra, e->w, e->h, e->nv12);
 	LONGLONG dur = 10000000LL / e->fps;
 	IMFSample *s = mk_sample(e->nv12, (DWORD)((size_t)e->w * e->h * 3 / 2),
-	                         e->pts * dur, dur);
+		e->pts * dur, dur);
 	if (!s) return -2;
 	e->pts++;
 
-	if (e->evgen) {            /* async hardware MFT: event-driven */
+	if (e->evgen) {
 		int got_out = 0, fed = 0, total = 0, guard = 0;
 		while (!got_out && guard++ < 16) {
 			IMFMediaEvent *ev = NULL;
@@ -340,11 +344,11 @@ void vid_enc_del(vid_enc *e) {
 
 struct vid_dec {
 	IMFTransform *mft;
-	int out_w, out_h;        /* display size (aperture-cropped) */
-	int buf_w, buf_h;        /* coded size */
+	int out_w, out_h;
+	int buf_w, buf_h;
 	int64_t pts;
 	int started;
-	IMFSample *osample;      /* reused output sample */
+	IMFSample *osample;
 	IMFMediaBuffer *obuffer;
 	DWORD obuf_cap;
 };
@@ -352,10 +356,10 @@ struct vid_dec {
 static int dec_negotiate_output(struct vid_dec *d) {
 	IMFMediaType *ot = NULL;
 	for (DWORD i = 0; SUCCEEDED(IMFTransform_GetOutputAvailableType(
-	        d->mft, 0, i, &ot)); i++) {
+		d->mft, 0, i, &ot)); i++) {
 		GUID sub;
 		if (SUCCEEDED(IMFMediaType_GetGUID(ot, &MF_MT_SUBTYPE, &sub)) &&
-		    IsEqualGUID(&sub, &MFVideoFormat_NV12)) {
+			IsEqualGUID(&sub, &MFVideoFormat_NV12)) {
 			if (FAILED(IMFTransform_SetOutputType(d->mft, 0, ot, 0))) {
 				IMFMediaType_Release(ot); return -1;
 			}
@@ -365,8 +369,8 @@ static int dec_negotiate_output(struct vid_dec *d) {
 			d->out_w = (int)w; d->out_h = (int)h;
 			MFVideoArea area;
 			if (SUCCEEDED(IMFMediaType_GetBlob(ot,
-			        &MF_MT_MINIMUM_DISPLAY_APERTURE,
-			        (UINT8 *)&area, sizeof(area), NULL))) {
+				&MF_MT_MINIMUM_DISPLAY_APERTURE,
+				(UINT8 *)&area, sizeof(area), NULL))) {
 				d->out_w = area.Area.cx;
 				d->out_h = area.Area.cy;
 			}
@@ -384,7 +388,7 @@ vid_dec *vid_dec_new(void) {
 	struct vid_dec *d = calloc(1, sizeof(*d));
 	if (!d) return NULL;
 	if (FAILED(CoCreateInstance(&CLSID_CMSH264DecoderMFT, NULL,
-	        CLSCTX_INPROC_SERVER, &IID_IMFTransform, (void **)&d->mft))) {
+		CLSCTX_INPROC_SERVER, &IID_IMFTransform, (void **)&d->mft))) {
 		VLOG("h264 decoder mft unavailable");
 		free(d); return NULL;
 	}
@@ -403,18 +407,18 @@ vid_dec *vid_dec_new(void) {
 		VLOG("dec SetInputType: 0x%lx", hr);
 		IMFTransform_Release(d->mft); free(d); return NULL;
 	}
-	dec_negotiate_output(d);   /* may succeed only after first frames */
+	dec_negotiate_output(d);
 	return d;
 }
 
 int vid_dec_frame(vid_dec *d, const uint8_t *data, int len,
-                  uint8_t *out, int out_cap, int *w, int *h) {
+	uint8_t *out, int out_cap, int *w, int *h) {
 	if (!d) return -1;
 	if (!d->started) {
 		IMFTransform_ProcessMessage(d->mft,
-		    MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
+			MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
 		IMFTransform_ProcessMessage(d->mft,
-		    MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
+			MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
 		d->started = 1;
 	}
 	IMFSample *s = mk_sample(data, (DWORD)len, d->pts * 666666LL, 666666LL);
@@ -427,18 +431,8 @@ int vid_dec_frame(vid_dec *d, const uint8_t *data, int
 	for (;;) {
 		if (d->buf_w == 0 && dec_negotiate_output(d) != 0) return 0;
 		DWORD need = (DWORD)((size_t)d->buf_w * d->buf_h * 3 / 2);
-		if (d->obuf_cap < need) {
-			if (d->obuffer) IMFMediaBuffer_Release(d->obuffer);
-			if (d->osample) IMFSample_Release(d->osample);
-			d->obuffer = NULL; d->osample = NULL;
-			if (FAILED(MFCreateSample(&d->osample)) ||
-			    FAILED(MFCreateMemoryBuffer(need, &d->obuffer))) {
-				if (d->osample) { IMFSample_Release(d->osample); d->osample = NULL; }
-				return -2;
-			}
-			IMFSample_AddBuffer(d->osample, d->obuffer);
-			d->obuf_cap = need;
-		}
+		if (ensure_out(&d->osample, &d->obuffer, &d->obuf_cap, need))
+			return -2;
 		IMFMediaBuffer_SetCurrentLength(d->obuffer, 0);
 		MFT_OUTPUT_DATA_BUFFER odb = {0};
 		odb.pSample = d->osample;
@@ -453,7 +447,7 @@ int vid_dec_frame(vid_dec *d, const uint8_t *data, int
 			return 0;
 		if (FAILED(hr))
 			return -3;
-		/* got a picture */
+
 		int ok = 0;
 		if ((int64_t)d->out_w * d->out_h * 4 <= out_cap) {
 			IMFMediaBuffer *got = NULL;
@@ -463,8 +457,8 @@ int vid_dec_frame(vid_dec *d, const uint8_t *data, int
 				if (SUCCEEDED(IMFMediaBuffer_Lock(got, &p, &bmax, &blen))) {
 					const uint8_t *Y = p;
 					const uint8_t *UV = p + (size_t)d->buf_w * d->buf_h;
-					nv12_to_bgra(Y, d->buf_w, UV, d->buf_w,
-					             d->out_w, d->out_h, out);
+					vid_nv12_to_bgra(Y, d->buf_w, UV, d->buf_w,
+						d->out_w, d->out_h, out);
 					IMFMediaBuffer_Unlock(got);
 					*w = d->out_w; *h = d->out_h;
 					ok = 1;
blob - /dev/null
blob + 8c3eda4b65170556ee7ac5904e909af809683a5b (mode 755)
Binary files /dev/null and milna.new differ
blob - a7836ecd0f19d6fe340ed999d6e5ba54801ac466
blob + c15995a4b6ce0dffe0f7d9ff0c7313084b0ceb71
--- srv/relay.go
+++ srv/relay.go
@@ -38,33 +38,45 @@ type member struct {
 	pub   string
 	room  string
 	conn  net.Conn
-	wmu   sync.Mutex // serializes writes to conn
+	wmu   sync.Mutex
 	token [16]byte
-	udp   *net.UDPAddr // bound media address (nil until hello)
-	want  int          // simulcast: which video layer this member receives
+	udp   *net.UDPAddr
+	want  int
 }
 
 func (m *member) send(v msg) {
 	b, _ := json.Marshal(v)
 	m.wmu.Lock()
 	defer m.wmu.Unlock()
+
+	m.conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
 	m.conn.Write(append(b, '\n'))
 }
 
 type hub struct {
 	mu      sync.Mutex
-	members map[string]*member            // id -> member
-	rooms   map[string]map[string]*member // room -> id -> member
+	members map[string]*member
+	rooms   map[string]map[string]*member
 	byToken map[[16]byte]*member
-	byAddr  map[string]*member // udp addr string -> member
+	byAddr  map[string]*member
 	udp     *net.UDPConn
 	nextID  int
 }
 
+type delivery struct {
+	to *member
+	v  msg
+}
+
+func deliver(d []delivery) {
+	for _, x := range d {
+		x.to.send(x.v)
+	}
+}
+
 func (h *hub) join(m *member, room, name, pub string) {
 	h.mu.Lock()
-	defer h.mu.Unlock()
-	h.leaveLocked(m)
+	out := h.leaveLocked(m)
 
 	m.room, m.name, m.pub = room, name, pub
 	if h.rooms[room] == nil {
@@ -73,11 +85,13 @@ func (h *hub) join(m *member, room, name, pub string) 
 
 	const maxRoom = 16
 	if len(h.rooms[room]) >= maxRoom {
-		m.send(msg{Type: "full"})
 		m.room = ""
+		out = append(out, delivery{m, msg{Type: "full"}})
+		h.mu.Unlock()
+		deliver(out)
 		return
 	}
-	// pick the lowest free slot (u8, room-scoped)
+
 	used := map[int]bool{}
 	for _, o := range h.rooms[room] {
 		used[o.slot] = true
@@ -89,22 +103,26 @@ func (h *hub) join(m *member, room, name, pub string) 
 		}
 	}
 	for _, o := range h.rooms[room] {
-		m.send(msg{Type: "peer", ID: o.id, Slot: o.slot, Name: o.name, Pub: o.pub})
-		o.send(msg{Type: "peer-joined", ID: m.id, Slot: m.slot, Name: m.name, Pub: m.pub})
+		out = append(out, delivery{m, msg{Type: "peer", ID: o.id, Slot: o.slot, Name: o.name, Pub: o.pub}})
+		out = append(out, delivery{o, msg{Type: "peer-joined", ID: m.id, Slot: m.slot, Name: m.name, Pub: m.pub}})
 	}
 	h.rooms[room][m.id] = m
+	n := len(h.rooms[room])
+	h.mu.Unlock()
+	deliver(out)
 	log.Printf("%s (%s) joined %s slot %d (%d in room)",
-		m.id, m.name, room, m.slot, len(h.rooms[room]))
+		m.id, m.name, room, m.slot, n)
 }
 
-func (h *hub) leaveLocked(m *member) {
+func (h *hub) leaveLocked(m *member) []delivery {
 	if m.room == "" {
-		return
+		return nil
 	}
+	var out []delivery
 	if r := h.rooms[m.room]; r != nil {
 		delete(r, m.id)
 		for _, o := range r {
-			o.send(msg{Type: "peer-left", ID: m.id, Slot: m.slot})
+			out = append(out, delivery{o, msg{Type: "peer-left", ID: m.id, Slot: m.slot}})
 		}
 		if len(r) == 0 {
 			delete(h.rooms, m.room)
@@ -115,6 +133,7 @@ func (h *hub) leaveLocked(m *member) {
 		m.udp = nil
 	}
 	m.room = ""
+	return out
 }
 
 func (h *hub) relayCtl(from *member, to string, v msg) {
@@ -141,7 +160,7 @@ func (h *hub) udpLoop() {
 			continue
 		}
 		switch buf[1] {
-		case 1: // hello: bind addr by token, echo back
+		case 1:
 			if n < 8+16 {
 				continue
 			}
@@ -156,8 +175,8 @@ func (h *hub) udpLoop() {
 				h.byAddr[addr.String()] = m
 			}
 			h.mu.Unlock()
-			h.udp.WriteToUDP(buf[:n], addr) // confirm
-		case 2, 3, 4: 
+			h.udp.WriteToUDP(buf[:n], addr)
+		case 2, 3, 4:
 			layer := -1
 			if buf[1] == 3 {
 				layer = int(buf[3]>>1) & 3
@@ -208,22 +227,25 @@ func (h *hub) serve(conn net.Conn) {
 			m.send(msg{Type: "joined", Slot: m.slot})
 		case "leave":
 			h.mu.Lock()
-			h.leaveLocked(m)
+			out := h.leaveLocked(m)
 			h.mu.Unlock()
+			deliver(out)
 		case "key", "kfreq":
 			h.relayCtl(m, v.To, v)
-		case "state": // mute/deafen: broadcast to everyone else in the room
+		case "state":
+			var out []delivery
 			h.mu.Lock()
 			v.From = m.id
 			if r := h.rooms[m.room]; r != nil {
 				for _, o := range r {
 					if o != m {
-						o.send(v)
+						out = append(out, delivery{o, v})
 					}
 				}
 			}
 			h.mu.Unlock()
-		case "layer": // simulcast subscription: which layer to receive
+			deliver(out)
+		case "layer":
 			h.mu.Lock()
 			if v.Want >= 0 && v.Want <= 3 {
 				m.want = v.Want
@@ -233,10 +255,11 @@ func (h *hub) serve(conn net.Conn) {
 	}
 
 	h.mu.Lock()
-	h.leaveLocked(m)
+	out := h.leaveLocked(m)
 	delete(h.members, m.id)
 	delete(h.byToken, m.token)
 	h.mu.Unlock()
+	deliver(out)
 	conn.Close()
 	log.Printf("%s disconnected", m.id)
 }
@@ -257,6 +280,9 @@ func main() {
 	if err != nil {
 		log.Fatal(err)
 	}
+
+	h.udp.SetReadBuffer(1 << 20)
+	h.udp.SetWriteBuffer(1 << 20)
 	go h.udpLoop()
 
 	l, err := net.Listen("tcp", port)
blob - 6664f7f169ef0d88edfd5b643045a05c909ee98e
blob + b321518dbe333495f3c050144bb2253676dc80ab
--- win-deps.sh
+++ win-deps.sh
@@ -2,14 +2,18 @@
 set -e
 ARCH=${1:-x86_64}
 case "$ARCH" in
-	x86_64) HOST=x86_64-w64-mingw32 ;;
-	arm64)  HOST=aarch64-w64-mingw32 ;;
+	x86_64) HOST=x86_64-w64-mingw32; PROC=x86_64 ;;
+	arm64)  HOST=aarch64-w64-mingw32; PROC=aarch64 ;;
 	*) echo "arch: x86_64 | arm64"; exit 1 ;;
 esac
 command -v $HOST-clang >/dev/null 2>&1 || command -v $HOST-gcc >/dev/null 2>&1 || {
 	echo "no $HOST toolchain. on macos:  brew install llvm-mingw"
 	exit 1
 }
+command -v cmake >/dev/null 2>&1 || {
+	echo "cmake needed for fltk. on macos:  brew install cmake"
+	exit 1
+}
 PFX="$(pwd)/deps-win-$ARCH"
 BLD="$(pwd)/.win-deps-build"
 mkdir -p "$BLD" && cd "$BLD"
@@ -35,5 +39,27 @@ build opus-1.5.2 --disable-doc --disable-extra-program
 build speexdsp-SpeexDSP-1.2.1 --disable-examples
 build libsodium-1.0.20-RELEASE
 
+if [ ! -f "$PFX/lib/libfltk.a" ]; then
+	fetch https://github.com/fltk/fltk/releases/download/release-1.4.5/fltk-1.4.5-source.tar.gz fltk-1.4.5
+	CCBIN=$(command -v $HOST-clang || command -v $HOST-gcc)
+	CXXBIN=$(command -v $HOST-clang++ || command -v $HOST-g++)
+	RCFLAG=""
+	command -v $HOST-windres >/dev/null 2>&1 && RCFLAG="-DCMAKE_RC_COMPILER=$HOST-windres"
+	cmake -S fltk-1.4.5 -B fltk-build \
+		-DCMAKE_SYSTEM_NAME=Windows -DCMAKE_SYSTEM_PROCESSOR=$PROC \
+		-DCMAKE_C_COMPILER="$CCBIN" -DCMAKE_CXX_COMPILER="$CXXBIN" $RCFLAG \
+		-DCMAKE_FIND_ROOT_PATH_MODE_PROGRAM=NEVER \
+		-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY \
+		-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY \
+		-DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_INSTALL_PREFIX="$PFX" \
+		-DFLTK_BUILD_TEST=OFF -DFLTK_BUILD_EXAMPLES=OFF -DFLTK_BUILD_FLUID=OFF \
+		-DFLTK_BUILD_GL=OFF -DFLTK_BUILD_FORMS=OFF \
+		-DFLTK_USE_SYSTEM_ZLIB=OFF -DFLTK_USE_SYSTEM_LIBPNG=OFF \
+		-DFLTK_USE_SYSTEM_LIBJPEG=OFF >/dev/null
+	cmake --build fltk-build -j8 >/dev/null
+	cmake --install fltk-build >/dev/null
+	echo "fltk-1.4.5 -> ok"
+fi
+
 echo "deps in $PFX"
 echo "build:  WINARCH=$ARCH ./build.sh win"