commit - b7bda67bcb33cd457e8311d404262aa47155d71d
commit + 275afb190ec44547db433d466f8a84a7ff1cee19
blob - d5dd479a976603498bcca512cc386d34ae6f47df
blob + 094bae55f5826a8c9b45e5bdcb654550169298f0
Binary files .DS_Store and .DS_Store differ
blob - f5d5903678900c6e3877a59af8e9f3546bfe684d
blob + deb55e8d233fa1c43d40c1ae0e750c382a8ea62c
--- build.sh
+++ build.sh
#!/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}"
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
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
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))"
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))"
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
-
-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
-/*
-** 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
-/*
-** 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
#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);
}
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;
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);
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));
}
}
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
#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
#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;
}
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) {
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;
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();
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;
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) {
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);
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;
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);
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
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
#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
@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);
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);
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;
}
[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);
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;
[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
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) {
}
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;
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);
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; }
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();
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
#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
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;
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;
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;
}
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;
}
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);
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;
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; }
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) {
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
-/* 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
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;
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;
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;
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) {
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;
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;
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;
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();
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
#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
#endif
#include <pthread.h>
#include <stdatomic.h>
+#ifndef MSG_DONTWAIT
+#define MSG_DONTWAIT 0
+#endif
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#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
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++;
}
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);
}
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;
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];
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;
}
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;
}
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);
}
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);
}
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);
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);
}
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);
}
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))) {
}
pthread_mutex_unlock(&N.mu);
}
-
} else if (!strcmp(type, "key")) {
char from[32], blob[256];
if (!json_get(json, "from", from, sizeof(from))) return;
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;
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);
}
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];
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;
}
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);
}
}
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;
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);
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;
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);
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;
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);
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
#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
-#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
+#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
-#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
-#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
-#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
#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 };
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);
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;
}
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);
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);
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];
#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);
#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;
}
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;
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);
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;
}
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);
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);
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 };
_exit(127);
}
fprintf(stderr, "[milna] update: helper spawned, quitting "
- "for bundle swap\n");
+ "for bundle swap\n");
_exit(0);
}
} else if (z) fclose(z);
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
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
}
}
#ifdef __APPLE__
-
void update_dispatch_bg(void);
#endif
#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
#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
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;
};
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++) {
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;
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++) {
}
}
-/* ---- 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;
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;
}
}
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;
}
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
#ifndef VID_H
#define VID_H
+#include <stddef.h>
#include <stdint.h>
#define VID_MAX_W 3840
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
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;
};
}
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;
} 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);
}
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);
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);
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);
}
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);
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;
};
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;
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);
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;
}
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) {
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;
}
}
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
#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;
}
}
-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;
}
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;
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;
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))) {
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; }
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; }
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;
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;
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);
}
}
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;
}
}
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) {
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;
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;
};
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;
}
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;
}
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;
}
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);
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;
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;
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
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 {
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
}
}
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)
m.udp = nil
}
m.room = ""
+ return out
}
func (h *hub) relayCtl(from *member, to string, v msg) {
continue
}
switch buf[1] {
- case 1: // hello: bind addr by token, echo back
+ case 1:
if n < 8+16 {
continue
}
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
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
}
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)
}
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
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"
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"