From 0384176de628a1a5ffedde0575ed388b5e8f94d9 Mon Sep 17 00:00:00 2001 From: mid <> Date: Sun, 5 Oct 2025 23:26:33 +0300 Subject: [PATCH] Replace X11-based screen capture with cross-platform library Unfortunately this brings back the C++ requirement, but later on this will happen anyway, what with me wanting some CV funsies. --- Makefile | 13 +-- hi/node.h | 8 +- hi/window.c | 218 -------------------------------------------------- hi/window.cpp | 130 ++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 228 deletions(-) create mode 100644 hi/window.cpp diff --git a/Makefile b/Makefile index ece78b5..6c118a6 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -CXXFLAGS := -D_POSIX_C_SOURCE=200809L -Wno-narrowing -march=native -flto -Wall -fvisibility=hidden -fPIC -msse4 -I./ -I/usr/local/include/sail `pkg-config --cflags pango opus libv4l2` '-Wl,-rpath,$$ORIGIN' -Wno-multichar -LDFLAGS := -lwebm -lpng -lvpx -lsail -lsail-manip `pkg-config --libs pango opus libv4l2` -lportaudio -lXtst -lrtmp -lfdk-aac +CXXFLAGS := -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L -Wno-narrowing -march=native -flto -Wall -fvisibility=hidden -fPIC -msse4 -I./ -I/usr/local/include/sail -L/usr/local/lib `pkg-config --cflags pango opus libv4l2` '-Wl,-rpath,$$ORIGIN' -Wno-multichar +LDFLAGS := -lwebm -lpng -lvpx -lsail -lsail-manip `pkg-config --libs pango opus libv4l2` -lportaudio -lXtst -lrtmp -lfdk-aac -leebie -lscreen_capture_lite_shared ifneq ($(RELEASE),0) CXXFLAGS := $(CXXFLAGS) -O0 -gdwarf-2 -DMTR_ENABLED @@ -10,12 +10,12 @@ endif all: $(CC) $(CXXFLAGS) -std=c99 -shared -c -o node.o hi/node.c $(LDFLAGS) - $(CC) $(CXXFLAGS) -std=c99 -shared -c -o window.o hi/window.c $(LDFLAGS) + $(CXX) $(CXXFLAGS) -std=c++17 -shared -c -o window.o hi/window.cpp $(LDFLAGS) $(CC) $(CXXFLAGS) -std=c99 -shared -c -o microphone.o hi/microphone.c $(LDFLAGS) $(CC) $(CXXFLAGS) -std=c99 -shared -c -o mode.o hi/mode.c $(LDFLAGS) $(CC) $(CXXFLAGS) -std=c99 -shared -c -o img.o hi/img.c $(LDFLAGS) - $(CXX) $(CXXFLAGS) -std=c++11 -shared -c -o webmdec.o hi/webmdec.cpp $(LDFLAGS) - $(CXX) $(CXXFLAGS) -std=c++11 -shared -c -o webmenc.o hi/webmenc.cpp $(LDFLAGS) + $(CXX) $(CXXFLAGS) -std=c++17 -shared -c -o webmdec.o hi/webmdec.cpp $(LDFLAGS) + $(CXX) $(CXXFLAGS) -std=c++17 -shared -c -o webmenc.o hi/webmenc.cpp $(LDFLAGS) $(CC) $(CXXFLAGS) -std=c99 -shared -c -o vpxenc.o hi/vpxenc.c $(LDFLAGS) $(CC) $(CXXFLAGS) -std=c99 -shared -c -o opus.o hi/opus.c $(LDFLAGS) $(CC) $(CXXFLAGS) -std=c99 -shared -c -o webcam.o hi/webcam.c $(LDFLAGS) @@ -25,6 +25,7 @@ all: $(CC) $(CXXFLAGS) -std=c99 -shared -c -o rtmp.o hi/rtmp.c $(LDFLAGS) $(CC) $(CXXFLAGS) -std=c99 -shared -c -o aaclc.o hi/aaclc.c $(LDFLAGS) $(CC) $(CXXFLAGS) -std=c99 -shared -c -o mkv.o hi/mkv.c $(LDFLAGS) - $(CC) $(CXXFLAGS) -shared -o libcutihi.so -shared node.o webmdec.o webmenc.o window.o microphone.o mode.o img.o opus.o webcam.o scale.o minitrace.o h264enc.o rtmp.o aaclc.o vpxenc.o mkv.o $(LDFLAGS) + $(CC) $(CXXFLAGS) -std=c99 -shared -c -o serialize.o hi/serialize.c $(LDFLAGS) + $(CC) $(CXXFLAGS) -shared -o libcutihi.so -shared node.o webmdec.o webmenc.o window.o microphone.o mode.o img.o opus.o webcam.o scale.o minitrace.o h264enc.o rtmp.o aaclc.o vpxenc.o mkv.o serialize.o $(LDFLAGS) $(CXX) $(CXXFLAGS) -std=c++11 `wx-config --cflags base,adv,core,aui` -o cuticle ui/main.cpp ui/frame.cpp ui/textctrl.cpp ui/timeline.cpp -L./ -lcutihi $(LDFLAGS) `wx-config --libs base,adv,core,aui` diff --git a/hi/node.h b/hi/node.h index 0050b09..d2461e3 100644 --- a/hi/node.h +++ b/hi/node.h @@ -246,10 +246,10 @@ CUTIVIS int CHi_MuxWebm_Stop(CHiPubNode*); #define CUTIHI_WINDOW_OUT_SAMPLE 1 CUTIVIS CHiPubNode *CHi_Window(); -CUTIVIS size_t CHi_Window_GetSourceCount(); -CUTIVIS const char *CHi_Window_GetSourceName(size_t); -CUTIVIS uintptr_t CHi_Window_GetSourceData(size_t); -CUTIVIS size_t CHi_Window_GetNextSource(size_t); +CUTIVIS size_t CHi_Window_GetList(void **buf); +CUTIVIS const char *CHi_Window_GetName(void *buf, size_t i); +CUTIVIS size_t CHi_Window_GetHandle(void *buf, size_t i); +CUTIVIS void CHi_Window_FreeList(void *buf); #define CUTIHI_MICROPHONE_IN_NAME 0 #define CUTIHI_MICROPHONE_OUT_AUDIO 1 diff --git a/hi/window.c b/hi/window.c index c7504e5..e69de29 100644 --- a/hi/window.c +++ b/hi/window.c @@ -1,218 +0,0 @@ -#include"node.h" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include"img.h" - -#include"linearity.h" - -#include"minitrace.h" - -static Display *d; -static Window root; - -static int find_window(Display *d, Window *w, const char *contains) { - if(contains) { - int found = 0; - Atom atom = XInternAtom(d, "_NET_CLIENT_LIST", 1); - Atom actualType; - int format; - unsigned long numItems, bytesAfter; - - Window *list; - XTextProperty windowName; - - int status = XGetWindowProperty(d, root, atom, 0L, ~0L, 0, AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, (unsigned char**) &list); - - if(status >= Success) { - for(int i = 0; i < numItems; i++) { - status = XGetWMName(d, list[i], &windowName); - if(status >= Success) { - if(windowName.value && strstr(windowName.value, contains)) { - *w = list[i]; - found = 1; - break; - } - } - } - } - - XFree(list); - - return found; - } else { - *w = root; - return 1; - } -} - -typedef struct { - CHiPubNode pub; - - Window xcache; - XImage *ximg; - XShmSegmentInfo shminfo; - - CHiImage *vcache; -} CHiWindowNode; - -static int window_perform(CHiPubNode *n) { - CHiWindowNode *w = (CHiWindowNode*) n; - - MTR_BEGIN("CHi", "window_perform"); - - Window toshoot; - if(!find_window(d, &toshoot, CHi_Crawl(&n->sinks[0])->data.text)) return 0; - - size_t stride; - if(!w->xcache || w->xcache != toshoot) { - w->xcache = toshoot; - - XWindowAttributes attrs; - XGetWindowAttributes(d, toshoot, &attrs); - - w->ximg = XShmCreateImage(d, attrs.visual, 32, ZPixmap, NULL, &w->shminfo, attrs.width, attrs.height); - stride = ((w->ximg->bytes_per_line + 15) & ~15); - w->shminfo.shmid = shmget(IPC_PRIVATE, stride * w->ximg->height, IPC_CREAT | 0777); - w->shminfo.shmaddr = w->ximg->data = shmat(w->shminfo.shmid, 0, 0); - w->shminfo.readOnly = False; - XShmAttach(d, &w->shminfo); - - w->vcache = CHi_Image_New(2, 4, 8 * attrs.width, attrs.width, attrs.height, NULL); - } else { - stride = ((w->ximg->bytes_per_line + 15) & ~15); - } - - XWindowAttributes toshootattrs; - XGetWindowAttributes(d, w->xcache, &toshootattrs); - - XShmGetImage(d, w->xcache, w->ximg, 0, 0, AllPlanes); - - // Turn u8 image to u16 - #pragma omp parallel for - for(size_t y = 0; y < w->vcache->height; y++) { - for(size_t x = 0; x < w->vcache->width; x += 2) { - __m128i c = _mm_loadu_si128((__m128i*) ((uintptr_t) w->ximg->data + y * w->ximg->bytes_per_line + x * 4)); - c = _mm_shuffle_epi8(c, _mm_set_epi8(7, -128, 6, -128, 5, -128, 4, -128, 3, -128, 2, -128, 1, -128, 0, -128)); - c = apply_gamma_epi16(c, _mm_set_ps(1, 2.2f, 2.2f, 2.2f)); - _mm_storeu_si128((__m128i*) ((uintptr_t) w->vcache->data16 + y * w->vcache->stride + x * 8), c); - } - } - - n->sources[0].type = CUTIHI_VAL_SAMPLE; - n->sources[0].data.sample = w->vcache; - - MTR_END("CHi", "window_perform"); - - return 1; -} - -static void window_destroy(CHiPubNode *pubn) { - CHiWindowNode *n = (void*) pubn; - - if(n->vcache) { - XShmDetach(d, &n->shminfo); - shmdt(n->shminfo.shmaddr); - XDestroyImage(n->ximg); - } - - free(pubn); -} - -CUTIVIS CHiPubNode *CHi_Window() { - if(!d) { - d = XOpenDisplay(NULL); - root = RootWindow(d, DefaultScreen(d)); - } - - CHiWindowNode *n = calloc(1, sizeof(*n)); - n->pub.type = CUTIHI_T('CWin','dow '); - n->pub.Start = n->pub.Stop = NULL; - n->pub.Perform = window_perform; - n->pub.Destroy = window_destroy; - n->pub.sinkCount = 1; - n->pub.sinks = calloc(sizeof(*n->pub.sinks), 1); - n->pub.sourceCount = 1; - n->pub.sources = calloc(sizeof(*n->pub.sources), 1); - - n->xcache = 0; - n->vcache = NULL; - - return &n->pub; -} - -// All of the following are ews - -CUTIVIS size_t CHi_Window_GetSourceCount() { - Atom atom = XInternAtom(d, "_NET_CLIENT_LIST", 1); - Atom actualType; - int format; - unsigned long numItems, bytesAfter; - - Window *list; - - int status = XGetWindowProperty(d, root, atom, 0L, ~0L, 0, AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, (unsigned char**) &list); - - //XFree(list); - - return status >= Success ? numItems : 0; -} - -CUTIVIS const char *CHi_Window_GetSourceName(size_t idx) { - int found = 0; - Atom atom = XInternAtom(d, "_NET_CLIENT_LIST", 1); - Atom actualType; - int format; - unsigned long numItems, bytesAfter; - - Window *list; - XTextProperty windowName; - - int status = XGetWindowProperty(d, root, atom, 0L, ~0L, 0, AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, (unsigned char**) &list); - - if(status >= Success) { - //XFree(list); - - status = XGetWMName(d, list[idx], &windowName); - if(status >= Success) { - found = 1; - } - } - - return found ? strdup(windowName.value ? windowName.value : "") : NULL; -} - -CUTIVIS uintptr_t CHi_Window_GetSourceData(size_t idx) { - Atom atom = XInternAtom(d, "_NET_CLIENT_LIST", 1); - Atom actualType; - int format; - unsigned long numItems, bytesAfter; - - Window *list; - - int status = XGetWindowProperty(d, root, atom, 0L, ~0L, 0, AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, (unsigned char**) &list); - - if(status >= Success) { - Window ret = list[idx]; - //XFree(list); - return ret; - } - - return 0; -} - -CUTIVIS size_t CHi_Window_GetNextSource(size_t i) { - return i + 1; -} diff --git a/hi/window.cpp b/hi/window.cpp new file mode 100644 index 0000000..ad1d5b2 --- /dev/null +++ b/hi/window.cpp @@ -0,0 +1,130 @@ +#include"node.h" + +#include + +#include +#include + +#include +#include + +#include + +#include + +#include"img.h" + +#include"linearity.h" + +#include"minitrace.h" + +typedef struct { + CHiPubNode pub; + + char* lastWindowString; + std::shared_ptr config; + + std::mutex mut; + std::vector images; +} CHiWindowNode; + +static int window_perform(CHiPubNode *n) { + CHiWindowNode *w = (CHiWindowNode*) n; + + MTR_BEGIN("CHi", "window_perform"); + + const char *expectedTitle = CHi_Crawl(&w->pub.sinks[0])->data.text; + + if(w->lastWindowString == nullptr || strcmp(w->lastWindowString, expectedTitle)) { + if(w->lastWindowString) { + free(w->lastWindowString); + } + w->lastWindowString = strdup(expectedTitle); + + w->config = SL::Screen_Capture::CreateCaptureConfiguration([=](){ + for(SL::Screen_Capture::Window& window : SL::Screen_Capture::GetWindows()) { + if(!strcmp(window.Name, w->lastWindowString)) { + return std::vector{window}; + } + } + return std::vector{}; + })->onNewFrame([=](const SL::Screen_Capture::Image& img, const SL::Screen_Capture::Window& window){ + CHiImage *new_image = CHi_Image_New(2, 4, (window.Size.x * 8 + 15) & ~15, window.Size.x, window.Size.y, nullptr); + memset(new_image->data8, 0, new_image->stride * new_image->height); + + #pragma omp parallel for + for(size_t y = 0; y < new_image->height; y++) { + uint8_t buf[16] = {}; + for(size_t x = 0; x < new_image->width; x += 2) { + memcpy(buf, &img.Data[y * new_image->width + x], 8); + + __m128i c = _mm_loadu_si128((__m128i*) buf); + c = _mm_shuffle_epi8(c, _mm_set_epi8(7, -128, 6, -128, 5, -128, 4, -128, 3, -128, 2, -128, 1, -128, 0, -128)); + c = apply_gamma_epi16(c, _mm_set_ps(1, 2.2f, 2.2f, 2.2f)); + _mm_store_si128((__m128i*) ((uintptr_t) new_image->data8 + y * new_image->stride + x * 8), c); + } + } + + std::unique_lock lock{w->mut}; + while(w->images.size() > 0) { + CHi_Image_Free(w->images.front()); + w->images.erase(w->images.begin()); + } + w->images.push_back(new_image); + })->start_capturing(); + } + + std::unique_lock lock{w->mut}; + if(w->images.size() > 0) { + if(n->sources[0].data.sample) { + CHi_Image_Free(n->sources[0].data.sample); + } + + n->sources[0].type = CUTIHI_VAL_SAMPLE; + n->sources[0].data.sample = w->images.front(); + + w->images.erase(w->images.begin()); + } + + MTR_END("CHi", "window_perform"); + + return 1; +} + +static void window_destroy(CHiPubNode *pubn) { + CHiWindowNode *n = (CHiWindowNode*) pubn; + delete n; +} + +CUTIVIS CHiPubNode *CHi_Window() { + auto *n = new CHiWindowNode(); + n->pub.type = CUTIHI_T('CWin','dow '); + n->pub.Start = n->pub.Stop = NULL; + n->pub.Perform = window_perform; + n->pub.Destroy = window_destroy; + n->pub.sinkCount = 1; + n->pub.sinks = (CHiValue*) calloc(sizeof(*n->pub.sinks), 1); + n->pub.sourceCount = 1; + n->pub.sources = (CHiValue*) calloc(sizeof(*n->pub.sources), 1); + + return &n->pub; +} + +CUTIVIS size_t CHi_Window_GetList(void **buf) { + auto vec = SL::Screen_Capture::GetWindows(); + + *buf = calloc(vec.size(), sizeof(vec[0])); + memcpy(*buf, &vec[0], sizeof(vec[0]) * vec.size()); + + return vec.size(); +} + +CUTIVIS const char *CHi_Window_GetName(void *buf, size_t i) { + return ((SL::Screen_Capture::Window*) buf)[i].Name; +} +CUTIVIS size_t CHi_Window_GetHandle(void *buf, size_t i) { + return ((SL::Screen_Capture::Window*) buf)[i].Handle; +} +CUTIVIS void CHi_Window_FreeList(void *buf) { + free(buf); +}