feat: add screenshot and ocr functionality with web interface
build: update makefile with new dependencies for tesseract and libpng chore: reorganize example files into examples directory
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
#include "notifications.h"
|
||||
#include "layout.h"
|
||||
#include "panel.h"
|
||||
#include "api.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@@ -234,6 +235,8 @@ Client *client_manage(Window window)
|
||||
client_sync_log("client_manage: focusing");
|
||||
client_focus(client, true);
|
||||
|
||||
api_emit_window_created(client->window, client->title, client->class, client->workspace);
|
||||
|
||||
client_sync_log("client_manage: DONE");
|
||||
return client;
|
||||
}
|
||||
@@ -247,6 +250,8 @@ void client_unmanage(Client *client)
|
||||
return;
|
||||
}
|
||||
|
||||
api_emit_window_destroyed(client->window, client->title, client->workspace);
|
||||
|
||||
LOG_DEBUG("Unmanaging window: %lu", client->window);
|
||||
|
||||
if (dwn->alt_tab_client == client) {
|
||||
@@ -345,7 +350,9 @@ void client_focus(Client *client, bool update_mru)
|
||||
client_sync_log("client_focus: unfocusing previous");
|
||||
|
||||
Workspace *ws = workspace_get(client->workspace);
|
||||
Window prev_window = None;
|
||||
if (ws != NULL && ws->focused != NULL && ws->focused != client) {
|
||||
prev_window = ws->focused->window;
|
||||
client_unfocus(ws->focused);
|
||||
}
|
||||
|
||||
@@ -378,6 +385,8 @@ void client_focus(Client *client, bool update_mru)
|
||||
|
||||
atoms_set_active_window(client->window);
|
||||
|
||||
api_emit_window_focused(client->window, client->title, prev_window);
|
||||
|
||||
client_sync_log("client_focus: DONE");
|
||||
LOG_DEBUG("Focused window: %s", client->title);
|
||||
}
|
||||
@@ -395,6 +404,8 @@ void client_unfocus(Client *client)
|
||||
}
|
||||
|
||||
decorations_render(client, false);
|
||||
|
||||
api_emit_window_unfocused(client->window, client->title);
|
||||
}
|
||||
|
||||
void client_raise(Client *client)
|
||||
@@ -414,6 +425,8 @@ void client_raise(Client *client)
|
||||
|
||||
XRaiseWindow(dwn->display, win);
|
||||
|
||||
api_emit_window_raised(client->window);
|
||||
|
||||
notifications_raise_all();
|
||||
panel_raise_all();
|
||||
}
|
||||
@@ -433,6 +446,8 @@ void client_lower(Client *client)
|
||||
return;
|
||||
}
|
||||
XLowerWindow(dwn->display, win);
|
||||
|
||||
api_emit_window_lowered(client->window);
|
||||
}
|
||||
|
||||
void client_minimize(Client *client)
|
||||
@@ -444,6 +459,8 @@ void client_minimize(Client *client)
|
||||
client->flags |= CLIENT_MINIMIZED;
|
||||
client_hide(client);
|
||||
|
||||
api_emit_window_minimized(client->window);
|
||||
|
||||
Workspace *ws = workspace_get(client->workspace);
|
||||
if (ws != NULL && ws->focused == client) {
|
||||
Client *next = workspace_mru_get_previous(client->workspace, client);
|
||||
@@ -466,6 +483,8 @@ void client_restore(Client *client)
|
||||
client->flags &= ~CLIENT_MINIMIZED;
|
||||
client_show(client);
|
||||
client_focus(client, true);
|
||||
|
||||
api_emit_window_restored(client->window);
|
||||
}
|
||||
|
||||
|
||||
@@ -475,6 +494,9 @@ void client_move(Client *client, int x, int y)
|
||||
return;
|
||||
}
|
||||
|
||||
int old_x = client->x;
|
||||
int old_y = client->y;
|
||||
|
||||
int area_x, area_y, area_w, area_h;
|
||||
layout_get_usable_area(&area_x, &area_y, &area_w, &area_h);
|
||||
|
||||
@@ -505,6 +527,10 @@ void client_move(Client *client, int x, int y)
|
||||
client->x = x;
|
||||
client->y = y;
|
||||
client_configure(client);
|
||||
|
||||
if (old_x != client->x || old_y != client->y) {
|
||||
api_emit_window_moved(client->window, old_x, old_y, client->x, client->y);
|
||||
}
|
||||
}
|
||||
|
||||
void client_resize(Client *client, int width, int height)
|
||||
@@ -513,6 +539,9 @@ void client_resize(Client *client, int width, int height)
|
||||
return;
|
||||
}
|
||||
|
||||
int old_w = client->width;
|
||||
int old_h = client->height;
|
||||
|
||||
int area_x, area_y, area_w, area_h;
|
||||
layout_get_usable_area(&area_x, &area_y, &area_w, &area_h);
|
||||
|
||||
@@ -535,6 +564,10 @@ void client_resize(Client *client, int width, int height)
|
||||
client->width = width;
|
||||
client->height = height;
|
||||
client_configure(client);
|
||||
|
||||
if (old_w != client->width || old_h != client->height) {
|
||||
api_emit_window_resized(client->window, old_w, old_h, client->width, client->height);
|
||||
}
|
||||
}
|
||||
|
||||
void client_move_resize(Client *client, int x, int y, int width, int height)
|
||||
@@ -682,6 +715,10 @@ void client_update_title(Client *client)
|
||||
return;
|
||||
}
|
||||
|
||||
char old_title[256];
|
||||
strncpy(old_title, client->title, sizeof(old_title) - 1);
|
||||
old_title[sizeof(old_title) - 1] = '\0';
|
||||
|
||||
char *name = atoms_get_window_name(client->window);
|
||||
if (name != NULL) {
|
||||
strncpy(client->title, name, sizeof(client->title) - 1);
|
||||
@@ -691,6 +728,10 @@ void client_update_title(Client *client)
|
||||
strncpy(client->title, "Untitled", sizeof(client->title) - 1);
|
||||
}
|
||||
|
||||
if (strcmp(old_title, client->title) != 0) {
|
||||
api_emit_window_title_changed(client->window, old_title, client->title);
|
||||
}
|
||||
|
||||
if (client->frame != None) {
|
||||
Workspace *ws = workspace_get(client->workspace);
|
||||
bool focused = (ws != NULL && ws->focused == client);
|
||||
@@ -733,6 +774,7 @@ void client_set_fullscreen(Client *client, bool fullscreen)
|
||||
|
||||
client->flags |= CLIENT_FULLSCREEN;
|
||||
atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_FULLSCREEN, true);
|
||||
api_emit_window_fullscreen(client->window, true);
|
||||
|
||||
client->x = 0;
|
||||
client->y = 0;
|
||||
@@ -753,6 +795,7 @@ void client_set_fullscreen(Client *client, bool fullscreen)
|
||||
} else {
|
||||
client->flags &= ~CLIENT_FULLSCREEN;
|
||||
atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_FULLSCREEN, false);
|
||||
api_emit_window_fullscreen(client->window, false);
|
||||
|
||||
client->x = client->old_x;
|
||||
client->y = client->old_y;
|
||||
@@ -794,6 +837,8 @@ void client_set_floating(Client *client, bool floating)
|
||||
} else {
|
||||
client->flags &= ~CLIENT_FLOATING;
|
||||
}
|
||||
|
||||
api_emit_window_floating(client->window, floating);
|
||||
}
|
||||
|
||||
void client_toggle_floating(Client *client)
|
||||
@@ -838,6 +883,7 @@ void client_set_maximize(Client *client, bool maximized)
|
||||
|
||||
atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_MAXIMIZED_VERT, true);
|
||||
atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_MAXIMIZED_HORZ, true);
|
||||
api_emit_window_maximized(client->window, true);
|
||||
|
||||
int area_x, area_y, area_width, area_height;
|
||||
layout_get_usable_area(&area_x, &area_y, &area_width, &area_height);
|
||||
@@ -860,6 +906,7 @@ void client_set_maximize(Client *client, bool maximized)
|
||||
|
||||
atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_MAXIMIZED_VERT, false);
|
||||
atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_MAXIMIZED_HORZ, false);
|
||||
api_emit_window_maximized(client->window, false);
|
||||
|
||||
client->x = client->old_x;
|
||||
client->y = client->old_y;
|
||||
|
||||
+12
@@ -16,6 +16,7 @@
|
||||
#include "decorations.h"
|
||||
#include "demo.h"
|
||||
#include "layout.h"
|
||||
#include "api.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@@ -456,11 +457,17 @@ void keys_handle_press(XKeyEvent *ev)
|
||||
bindings[i].modifiers == clean_mask) {
|
||||
if (bindings[i].callback != NULL) {
|
||||
LOG_DEBUG("Key binding triggered: %s", bindings[i].description);
|
||||
|
||||
const char *name = XKeysymToString(keysym);
|
||||
api_emit_shortcut_triggered(name ? name : "unknown", bindings[i].description);
|
||||
|
||||
bindings[i].callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
api_emit_key_pressed(ev->keycode, keysym, clean_mask);
|
||||
}
|
||||
|
||||
void keys_handle_release(XKeyEvent *ev)
|
||||
@@ -470,6 +477,9 @@ void keys_handle_release(XKeyEvent *ev)
|
||||
}
|
||||
|
||||
KeySym keysym = XLookupKeysym(ev, 0);
|
||||
unsigned int clean_mask = ev->state & ~(Mod2Mask | LockMask);
|
||||
|
||||
api_emit_key_released(ev->keycode, keysym, clean_mask);
|
||||
|
||||
if (keysym == XK_Super_L || keysym == XK_Super_R) {
|
||||
if (super_pressed && !super_used_in_combo) {
|
||||
@@ -767,6 +777,7 @@ void key_show_desktop(void)
|
||||
}
|
||||
}
|
||||
dwn->desktop_shown = true;
|
||||
api_emit_show_desktop_toggled(true);
|
||||
} else {
|
||||
for (int i = 0; i < dwn->desktop_minimized_count; i++) {
|
||||
Client *c = client_find_by_window(dwn->desktop_minimized[i]);
|
||||
@@ -776,6 +787,7 @@ void key_show_desktop(void)
|
||||
}
|
||||
dwn->desktop_minimized_count = 0;
|
||||
dwn->desktop_shown = false;
|
||||
api_emit_show_desktop_toggled(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-2
@@ -22,6 +22,8 @@
|
||||
#include "services.h"
|
||||
#include "api.h"
|
||||
#include "demo.h"
|
||||
#include "screenshot.h"
|
||||
#include "ocr.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -466,6 +468,8 @@ static void handle_button_press(XButtonEvent *ev)
|
||||
dwn->drag_orig_y = c->y;
|
||||
dwn->resizing = false;
|
||||
|
||||
api_emit_drag_started(c->window, false);
|
||||
|
||||
XGrabPointer(dwn->display, c->frame, True,
|
||||
PointerMotionMask | ButtonReleaseMask,
|
||||
GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
|
||||
@@ -485,12 +489,16 @@ static void handle_button_press(XButtonEvent *ev)
|
||||
dwn->drag_orig_h = c->height;
|
||||
dwn->resizing = true;
|
||||
|
||||
api_emit_drag_started(c->window, true);
|
||||
|
||||
XGrabPointer(dwn->display, c->frame, True,
|
||||
PointerMotionMask | ButtonReleaseMask,
|
||||
GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
api_emit_mouse_button_pressed(ev->button, ev->x_root, ev->y_root);
|
||||
}
|
||||
|
||||
static void handle_button_release(XButtonEvent *ev)
|
||||
@@ -503,12 +511,13 @@ static void handle_button_release(XButtonEvent *ev)
|
||||
volume_slider_handle_release(volume_slider);
|
||||
}
|
||||
|
||||
(void)ev;
|
||||
|
||||
if (dwn->drag_client != NULL) {
|
||||
api_emit_drag_ended(dwn->drag_client->window, dwn->resizing);
|
||||
XUngrabPointer(dwn->display, CurrentTime);
|
||||
dwn->drag_client = NULL;
|
||||
}
|
||||
|
||||
api_emit_mouse_button_released(ev->button, ev->x_root, ev->y_root);
|
||||
}
|
||||
|
||||
static void handle_motion_notify(XMotionEvent *ev)
|
||||
@@ -774,6 +783,10 @@ int dwn_init(void)
|
||||
|
||||
ai_init();
|
||||
|
||||
screenshot_init();
|
||||
|
||||
ocr_init();
|
||||
|
||||
api_init();
|
||||
|
||||
demo_init();
|
||||
@@ -807,6 +820,8 @@ void dwn_cleanup(void)
|
||||
|
||||
demo_cleanup();
|
||||
api_cleanup();
|
||||
ocr_cleanup();
|
||||
screenshot_cleanup();
|
||||
ai_cleanup();
|
||||
notifications_cleanup();
|
||||
news_cleanup();
|
||||
@@ -873,6 +888,10 @@ void dwn_run(void)
|
||||
|
||||
exa_process_pending();
|
||||
|
||||
screenshot_process_pending();
|
||||
|
||||
ocr_process_pending();
|
||||
|
||||
demo_update();
|
||||
|
||||
notifications_update();
|
||||
@@ -966,6 +985,8 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
|
||||
XInitThreads();
|
||||
|
||||
signal(SIGTERM, signal_handler);
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGHUP, signal_handler);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "notifications.h"
|
||||
#include "config.h"
|
||||
#include "util.h"
|
||||
#include "api.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
@@ -520,6 +521,8 @@ uint32_t notification_show(const char *app_name, const char *summary,
|
||||
LOG_DEBUG("Notification %u (%dx%d): %s - %s", notif->id,
|
||||
notif->width, notif->height, notif->summary, notif->body);
|
||||
|
||||
api_emit_notification_shown(notif->id, notif->summary, notif->body);
|
||||
|
||||
return notif->id;
|
||||
}
|
||||
|
||||
@@ -545,6 +548,8 @@ void notification_close(uint32_t id)
|
||||
notif->body = NULL;
|
||||
}
|
||||
|
||||
api_emit_notification_closed(id);
|
||||
|
||||
dwn_free(notif);
|
||||
|
||||
notifications_position();
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* retoor <retoor@molodetz.nl>
|
||||
* OCR text extraction using Tesseract
|
||||
*/
|
||||
|
||||
#include "ocr.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdatomic.h>
|
||||
#include <tesseract/capi.h>
|
||||
#include <leptonica/allheaders.h>
|
||||
|
||||
static TessBaseAPI *tess_api = NULL;
|
||||
static bool initialized = false;
|
||||
static pthread_mutex_t ocr_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static OcrRequest *ocr_queue = NULL;
|
||||
static atomic_int ocr_shutting_down = 0;
|
||||
|
||||
bool ocr_init(void)
|
||||
{
|
||||
if (initialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
atomic_store(&ocr_shutting_down, 0);
|
||||
|
||||
tess_api = TessBaseAPICreate();
|
||||
if (tess_api == NULL) {
|
||||
LOG_ERROR("Failed to create Tesseract API");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TessBaseAPIInit3(tess_api, NULL, "eng") != 0) {
|
||||
LOG_WARN("Tesseract initialization failed, OCR unavailable");
|
||||
TessBaseAPIDelete(tess_api);
|
||||
tess_api = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
LOG_INFO("OCR module initialized with Tesseract");
|
||||
return true;
|
||||
}
|
||||
|
||||
void ocr_cleanup(void)
|
||||
{
|
||||
atomic_store(&ocr_shutting_down, 1);
|
||||
|
||||
pthread_mutex_lock(&ocr_mutex);
|
||||
while (ocr_queue != NULL) {
|
||||
OcrRequest *req = ocr_queue;
|
||||
ocr_queue = req->next;
|
||||
pthread_mutex_unlock(&ocr_mutex);
|
||||
|
||||
pthread_join(req->thread, NULL);
|
||||
if (req->png_data != NULL) {
|
||||
dwn_free(req->png_data);
|
||||
}
|
||||
ocr_result_free(&req->result);
|
||||
dwn_free(req);
|
||||
|
||||
pthread_mutex_lock(&ocr_mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&ocr_mutex);
|
||||
|
||||
if (tess_api != NULL) {
|
||||
TessBaseAPIEnd(tess_api);
|
||||
TessBaseAPIDelete(tess_api);
|
||||
tess_api = NULL;
|
||||
}
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
bool ocr_is_available(void)
|
||||
{
|
||||
return initialized && tess_api != NULL;
|
||||
}
|
||||
|
||||
const char *ocr_status_string(OcrStatus status)
|
||||
{
|
||||
switch (status) {
|
||||
case OCR_OK:
|
||||
return "OK";
|
||||
case OCR_ERROR_INVALID_ARG:
|
||||
return "Invalid argument";
|
||||
case OCR_ERROR_INIT:
|
||||
return "Initialization error";
|
||||
case OCR_ERROR_IMAGE:
|
||||
return "Image processing error";
|
||||
case OCR_ERROR_NO_MEMORY:
|
||||
return "Out of memory";
|
||||
case OCR_ERROR_NOT_AVAILABLE:
|
||||
return "OCR not available";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
void ocr_result_free(OcrResult *result)
|
||||
{
|
||||
if (result == NULL) {
|
||||
return;
|
||||
}
|
||||
if (result->text != NULL) {
|
||||
dwn_free(result->text);
|
||||
result->text = NULL;
|
||||
}
|
||||
result->length = 0;
|
||||
result->confidence = 0.0f;
|
||||
}
|
||||
|
||||
OcrStatus ocr_extract_from_png(const unsigned char *png_data, size_t size,
|
||||
const char *language, OcrResult *result)
|
||||
{
|
||||
if (result == NULL) {
|
||||
return OCR_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
memset(result, 0, sizeof(OcrResult));
|
||||
|
||||
if (png_data == NULL || size == 0) {
|
||||
return OCR_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (!ocr_is_available()) {
|
||||
return OCR_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
const char *lang = (language != NULL && language[0] != '\0') ? language : "eng";
|
||||
|
||||
if (TessBaseAPIInit3(tess_api, NULL, lang) != 0) {
|
||||
LOG_WARN("Failed to set language '%s', falling back to 'eng'", lang);
|
||||
if (TessBaseAPIInit3(tess_api, NULL, "eng") != 0) {
|
||||
return OCR_ERROR_INIT;
|
||||
}
|
||||
}
|
||||
|
||||
PIX *image = pixReadMem(png_data, size);
|
||||
if (image == NULL) {
|
||||
LOG_ERROR("Failed to read PNG image for OCR");
|
||||
return OCR_ERROR_IMAGE;
|
||||
}
|
||||
|
||||
TessBaseAPISetImage2(tess_api, image);
|
||||
|
||||
char *text = TessBaseAPIGetUTF8Text(tess_api);
|
||||
if (text == NULL) {
|
||||
pixDestroy(&image);
|
||||
return OCR_ERROR_IMAGE;
|
||||
}
|
||||
|
||||
int conf = TessBaseAPIMeanTextConf(tess_api);
|
||||
float confidence = (float)conf / 100.0f;
|
||||
|
||||
size_t text_len = strlen(text);
|
||||
result->text = dwn_malloc(text_len + 1);
|
||||
if (result->text == NULL) {
|
||||
TessDeleteText(text);
|
||||
pixDestroy(&image);
|
||||
return OCR_ERROR_NO_MEMORY;
|
||||
}
|
||||
|
||||
memcpy(result->text, text, text_len + 1);
|
||||
result->length = text_len;
|
||||
result->confidence = confidence;
|
||||
|
||||
TessDeleteText(text);
|
||||
pixDestroy(&image);
|
||||
|
||||
return OCR_OK;
|
||||
}
|
||||
|
||||
static void *ocr_worker(void *arg)
|
||||
{
|
||||
OcrRequest *req = (OcrRequest *)arg;
|
||||
|
||||
if (atomic_load(&ocr_shutting_down)) {
|
||||
req->state = OCR_STATE_ERROR;
|
||||
req->status = OCR_ERROR_NOT_AVAILABLE;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TessBaseAPI *local_tess = TessBaseAPICreate();
|
||||
if (local_tess == NULL) {
|
||||
req->status = OCR_ERROR_INIT;
|
||||
req->state = OCR_STATE_ERROR;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *lang = req->language[0] ? req->language : "eng";
|
||||
if (TessBaseAPIInit3(local_tess, NULL, lang) != 0) {
|
||||
if (TessBaseAPIInit3(local_tess, NULL, "eng") != 0) {
|
||||
TessBaseAPIDelete(local_tess);
|
||||
req->status = OCR_ERROR_INIT;
|
||||
req->state = OCR_STATE_ERROR;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
PIX *image = pixReadMem(req->png_data, req->png_size);
|
||||
if (image == NULL) {
|
||||
TessBaseAPIEnd(local_tess);
|
||||
TessBaseAPIDelete(local_tess);
|
||||
req->status = OCR_ERROR_IMAGE;
|
||||
req->state = OCR_STATE_ERROR;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TessBaseAPISetImage2(local_tess, image);
|
||||
char *text = TessBaseAPIGetUTF8Text(local_tess);
|
||||
int conf = TessBaseAPIMeanTextConf(local_tess);
|
||||
|
||||
if (text != NULL) {
|
||||
size_t len = strlen(text);
|
||||
req->result.text = dwn_malloc(len + 1);
|
||||
if (req->result.text) {
|
||||
memcpy(req->result.text, text, len + 1);
|
||||
req->result.length = len;
|
||||
req->result.confidence = (float)conf / 100.0f;
|
||||
req->status = OCR_OK;
|
||||
req->state = OCR_STATE_COMPLETED;
|
||||
} else {
|
||||
req->status = OCR_ERROR_NO_MEMORY;
|
||||
req->state = OCR_STATE_ERROR;
|
||||
}
|
||||
TessDeleteText(text);
|
||||
} else {
|
||||
req->status = OCR_ERROR_IMAGE;
|
||||
req->state = OCR_STATE_ERROR;
|
||||
}
|
||||
|
||||
pixDestroy(&image);
|
||||
TessBaseAPIEnd(local_tess);
|
||||
TessBaseAPIDelete(local_tess);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
OcrRequest *ocr_extract_async(const unsigned char *png_data, size_t size,
|
||||
const char *language,
|
||||
void (*callback)(OcrRequest *))
|
||||
{
|
||||
if (!initialized || atomic_load(&ocr_shutting_down)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (png_data == NULL || size == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
OcrRequest *req = dwn_calloc(1, sizeof(OcrRequest));
|
||||
if (req == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
req->png_data = dwn_malloc(size);
|
||||
if (req->png_data == NULL) {
|
||||
dwn_free(req);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(req->png_data, png_data, size);
|
||||
req->png_size = size;
|
||||
|
||||
if (language != NULL && language[0] != '\0') {
|
||||
strncpy(req->language, language, sizeof(req->language) - 1);
|
||||
req->language[sizeof(req->language) - 1] = '\0';
|
||||
}
|
||||
|
||||
req->state = OCR_STATE_PENDING;
|
||||
req->callback = callback;
|
||||
|
||||
pthread_mutex_lock(&ocr_mutex);
|
||||
req->next = ocr_queue;
|
||||
ocr_queue = req;
|
||||
pthread_mutex_unlock(&ocr_mutex);
|
||||
|
||||
if (pthread_create(&req->thread, NULL, ocr_worker, req) != 0) {
|
||||
pthread_mutex_lock(&ocr_mutex);
|
||||
OcrRequest **pp = &ocr_queue;
|
||||
while (*pp != NULL) {
|
||||
if (*pp == req) {
|
||||
*pp = req->next;
|
||||
break;
|
||||
}
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
pthread_mutex_unlock(&ocr_mutex);
|
||||
dwn_free(req->png_data);
|
||||
dwn_free(req);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LOG_DEBUG("OCR async request started");
|
||||
return req;
|
||||
}
|
||||
|
||||
void ocr_process_pending(void)
|
||||
{
|
||||
if (atomic_load(&ocr_shutting_down)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&ocr_mutex);
|
||||
OcrRequest **pp = &ocr_queue;
|
||||
|
||||
while (*pp != NULL) {
|
||||
OcrRequest *req = *pp;
|
||||
|
||||
if (req->state == OCR_STATE_COMPLETED ||
|
||||
req->state == OCR_STATE_ERROR) {
|
||||
|
||||
pthread_join(req->thread, NULL);
|
||||
|
||||
*pp = req->next;
|
||||
pthread_mutex_unlock(&ocr_mutex);
|
||||
|
||||
if (req->callback != NULL) {
|
||||
req->callback(req);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&ocr_mutex);
|
||||
} else {
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ocr_mutex);
|
||||
}
|
||||
|
||||
void ocr_cancel(OcrRequest *req)
|
||||
{
|
||||
if (req == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&ocr_mutex);
|
||||
OcrRequest **pp = &ocr_queue;
|
||||
while (*pp != NULL) {
|
||||
if (*pp == req) {
|
||||
*pp = req->next;
|
||||
break;
|
||||
}
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
pthread_mutex_unlock(&ocr_mutex);
|
||||
|
||||
pthread_cancel(req->thread);
|
||||
pthread_join(req->thread, NULL);
|
||||
|
||||
if (req->png_data != NULL) {
|
||||
dwn_free(req->png_data);
|
||||
}
|
||||
ocr_result_free(&req->result);
|
||||
dwn_free(req);
|
||||
}
|
||||
@@ -0,0 +1,628 @@
|
||||
/*
|
||||
* DWN - Desktop Window Manager
|
||||
* retoor <retoor@molodetz.nl>
|
||||
* Screenshot capture implementation using X11 and libpng
|
||||
*/
|
||||
|
||||
#include "screenshot.h"
|
||||
#include "dwn.h"
|
||||
#include "client.h"
|
||||
#include "workspace.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdatomic.h>
|
||||
#include <png.h>
|
||||
|
||||
static bool initialized = false;
|
||||
static pthread_mutex_t screenshot_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static ScreenshotRequest *screenshot_queue = NULL;
|
||||
static atomic_int screenshot_shutting_down = 0;
|
||||
|
||||
static const char base64_chars[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
bool screenshot_init(void)
|
||||
{
|
||||
atomic_store(&screenshot_shutting_down, 0);
|
||||
initialized = true;
|
||||
LOG_INFO("Screenshot module initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
void screenshot_cleanup(void)
|
||||
{
|
||||
atomic_store(&screenshot_shutting_down, 1);
|
||||
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
while (screenshot_queue != NULL) {
|
||||
ScreenshotRequest *req = screenshot_queue;
|
||||
screenshot_queue = req->next;
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
|
||||
pthread_join(req->thread, NULL);
|
||||
screenshot_result_free(&req->result);
|
||||
dwn_free(req);
|
||||
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
static size_t base64_encoded_size(size_t input_size)
|
||||
{
|
||||
return 4 * ((input_size + 2) / 3);
|
||||
}
|
||||
|
||||
char *screenshot_to_base64(const ScreenshotResult *result, size_t *out_len)
|
||||
{
|
||||
if (result == NULL || result->data == NULL || result->size == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t encoded_size = base64_encoded_size(result->size);
|
||||
char *encoded = dwn_malloc(encoded_size + 1);
|
||||
if (encoded == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const unsigned char *input = result->data;
|
||||
size_t input_len = result->size;
|
||||
char *output = encoded;
|
||||
|
||||
for (size_t i = 0; i < input_len; i += 3) {
|
||||
unsigned int n = ((unsigned int)input[i]) << 16;
|
||||
if (i + 1 < input_len) {
|
||||
n |= ((unsigned int)input[i + 1]) << 8;
|
||||
}
|
||||
if (i + 2 < input_len) {
|
||||
n |= (unsigned int)input[i + 2];
|
||||
}
|
||||
|
||||
*output++ = base64_chars[(n >> 18) & 0x3F];
|
||||
*output++ = base64_chars[(n >> 12) & 0x3F];
|
||||
*output++ = (i + 1 < input_len) ? base64_chars[(n >> 6) & 0x3F] : '=';
|
||||
*output++ = (i + 2 < input_len) ? base64_chars[n & 0x3F] : '=';
|
||||
}
|
||||
|
||||
*output = '\0';
|
||||
if (out_len != NULL) {
|
||||
*out_len = output - encoded;
|
||||
}
|
||||
|
||||
return encoded;
|
||||
}
|
||||
|
||||
void screenshot_result_free(ScreenshotResult *result)
|
||||
{
|
||||
if (result == NULL) {
|
||||
return;
|
||||
}
|
||||
if (result->data != NULL) {
|
||||
dwn_free(result->data);
|
||||
result->data = NULL;
|
||||
}
|
||||
result->size = 0;
|
||||
result->width = 0;
|
||||
result->height = 0;
|
||||
}
|
||||
|
||||
const char *screenshot_status_string(ScreenshotStatus status)
|
||||
{
|
||||
switch (status) {
|
||||
case SCREENSHOT_OK:
|
||||
return "OK";
|
||||
case SCREENSHOT_ERROR_INVALID_ARG:
|
||||
return "Invalid argument";
|
||||
case SCREENSHOT_ERROR_X11:
|
||||
return "X11 error";
|
||||
case SCREENSHOT_ERROR_PNG:
|
||||
return "PNG encoding error";
|
||||
case SCREENSHOT_ERROR_NO_MEMORY:
|
||||
return "Out of memory";
|
||||
case SCREENSHOT_ERROR_WINDOW_NOT_FOUND:
|
||||
return "Window not found";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
unsigned char *data;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
} PngBuffer;
|
||||
|
||||
static void png_write_callback(png_structp png, png_bytep data, png_size_t length)
|
||||
{
|
||||
PngBuffer *buf = (PngBuffer *)png_get_io_ptr(png);
|
||||
if (buf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t new_size = buf->size + length;
|
||||
if (new_size > buf->capacity) {
|
||||
size_t new_capacity = buf->capacity * 2;
|
||||
if (new_capacity < new_size) {
|
||||
new_capacity = new_size + 1024;
|
||||
}
|
||||
unsigned char *new_data = dwn_realloc(buf->data, new_capacity);
|
||||
if (new_data == NULL) {
|
||||
png_error(png, "Memory allocation failed");
|
||||
return;
|
||||
}
|
||||
buf->data = new_data;
|
||||
buf->capacity = new_capacity;
|
||||
}
|
||||
|
||||
memcpy(buf->data + buf->size, data, length);
|
||||
buf->size = new_size;
|
||||
}
|
||||
|
||||
static void png_flush_callback(png_structp png)
|
||||
{
|
||||
(void)png;
|
||||
}
|
||||
|
||||
static ScreenshotStatus ximage_to_png(XImage *img, ScreenshotResult *result)
|
||||
{
|
||||
if (img == NULL || result == NULL) {
|
||||
return SCREENSHOT_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
if (png == NULL) {
|
||||
return SCREENSHOT_ERROR_PNG;
|
||||
}
|
||||
|
||||
png_infop info = png_create_info_struct(png);
|
||||
if (info == NULL) {
|
||||
png_destroy_write_struct(&png, NULL);
|
||||
return SCREENSHOT_ERROR_PNG;
|
||||
}
|
||||
|
||||
if (setjmp(png_jmpbuf(png))) {
|
||||
png_destroy_write_struct(&png, &info);
|
||||
return SCREENSHOT_ERROR_PNG;
|
||||
}
|
||||
|
||||
PngBuffer buf = {0};
|
||||
buf.capacity = (size_t)(img->width * img->height * 3 / 2 + 1024);
|
||||
buf.data = dwn_malloc(buf.capacity);
|
||||
if (buf.data == NULL) {
|
||||
png_destroy_write_struct(&png, &info);
|
||||
return SCREENSHOT_ERROR_NO_MEMORY;
|
||||
}
|
||||
|
||||
png_set_write_fn(png, &buf, png_write_callback, png_flush_callback);
|
||||
|
||||
png_set_IHDR(png, info, img->width, img->height, 8,
|
||||
PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
|
||||
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||
|
||||
png_write_info(png, info);
|
||||
|
||||
unsigned char *row = dwn_malloc(img->width * 3);
|
||||
if (row == NULL) {
|
||||
dwn_free(buf.data);
|
||||
png_destroy_write_struct(&png, &info);
|
||||
return SCREENSHOT_ERROR_NO_MEMORY;
|
||||
}
|
||||
|
||||
unsigned long red_mask = img->red_mask;
|
||||
unsigned long green_mask = img->green_mask;
|
||||
unsigned long blue_mask = img->blue_mask;
|
||||
|
||||
int red_shift = 0;
|
||||
int green_shift = 0;
|
||||
int blue_shift = 0;
|
||||
|
||||
while ((red_mask & 1) == 0 && red_shift < 32) {
|
||||
red_shift++;
|
||||
red_mask >>= 1;
|
||||
}
|
||||
while ((green_mask & 1) == 0 && green_shift < 32) {
|
||||
green_shift++;
|
||||
green_mask >>= 1;
|
||||
}
|
||||
while ((blue_mask & 1) == 0 && blue_shift < 32) {
|
||||
blue_shift++;
|
||||
blue_mask >>= 1;
|
||||
}
|
||||
|
||||
for (int y = 0; y < img->height; y++) {
|
||||
for (int x = 0; x < img->width; x++) {
|
||||
unsigned long pixel = XGetPixel(img, x, y);
|
||||
row[x * 3 + 0] = (pixel >> red_shift) & 0xFF;
|
||||
row[x * 3 + 1] = (pixel >> green_shift) & 0xFF;
|
||||
row[x * 3 + 2] = (pixel >> blue_shift) & 0xFF;
|
||||
}
|
||||
png_write_row(png, row);
|
||||
}
|
||||
|
||||
png_write_end(png, info);
|
||||
|
||||
dwn_free(row);
|
||||
png_destroy_write_struct(&png, &info);
|
||||
|
||||
result->data = buf.data;
|
||||
result->size = buf.size;
|
||||
result->width = img->width;
|
||||
result->height = img->height;
|
||||
|
||||
return SCREENSHOT_OK;
|
||||
}
|
||||
|
||||
ScreenshotStatus screenshot_capture_fullscreen(ScreenshotResult *result)
|
||||
{
|
||||
if (result == NULL) {
|
||||
return SCREENSHOT_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (dwn == NULL || dwn->display == NULL) {
|
||||
return SCREENSHOT_ERROR_X11;
|
||||
}
|
||||
|
||||
memset(result, 0, sizeof(ScreenshotResult));
|
||||
|
||||
XImage *img = XGetImage(dwn->display, dwn->root,
|
||||
0, 0, dwn->screen_width, dwn->screen_height,
|
||||
AllPlanes, ZPixmap);
|
||||
if (img == NULL) {
|
||||
LOG_ERROR("XGetImage failed for fullscreen capture");
|
||||
return SCREENSHOT_ERROR_X11;
|
||||
}
|
||||
|
||||
ScreenshotStatus status = ximage_to_png(img, result);
|
||||
XDestroyImage(img);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
ScreenshotStatus screenshot_capture_window(Window window, ScreenshotResult *result)
|
||||
{
|
||||
if (result == NULL) {
|
||||
return SCREENSHOT_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (dwn == NULL || dwn->display == NULL) {
|
||||
return SCREENSHOT_ERROR_X11;
|
||||
}
|
||||
|
||||
if (window == None) {
|
||||
return SCREENSHOT_ERROR_WINDOW_NOT_FOUND;
|
||||
}
|
||||
|
||||
memset(result, 0, sizeof(ScreenshotResult));
|
||||
|
||||
XWindowAttributes wa;
|
||||
if (!XGetWindowAttributes(dwn->display, window, &wa)) {
|
||||
return SCREENSHOT_ERROR_WINDOW_NOT_FOUND;
|
||||
}
|
||||
|
||||
int x, y;
|
||||
Window child;
|
||||
XTranslateCoordinates(dwn->display, window, dwn->root, 0, 0, &x, &y, &child);
|
||||
|
||||
int width = wa.width;
|
||||
int height = wa.height;
|
||||
|
||||
if (x < 0) {
|
||||
width += x;
|
||||
x = 0;
|
||||
}
|
||||
if (y < 0) {
|
||||
height += y;
|
||||
y = 0;
|
||||
}
|
||||
if (x + width > dwn->screen_width) {
|
||||
width = dwn->screen_width - x;
|
||||
}
|
||||
if (y + height > dwn->screen_height) {
|
||||
height = dwn->screen_height - y;
|
||||
}
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
return SCREENSHOT_ERROR_X11;
|
||||
}
|
||||
|
||||
XImage *img = XGetImage(dwn->display, dwn->root, x, y, width, height,
|
||||
AllPlanes, ZPixmap);
|
||||
if (img == NULL) {
|
||||
LOG_ERROR("XGetImage failed for window capture");
|
||||
return SCREENSHOT_ERROR_X11;
|
||||
}
|
||||
|
||||
ScreenshotStatus status = ximage_to_png(img, result);
|
||||
XDestroyImage(img);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
ScreenshotStatus screenshot_capture_active(ScreenshotResult *result)
|
||||
{
|
||||
if (result == NULL) {
|
||||
return SCREENSHOT_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (dwn == NULL) {
|
||||
return SCREENSHOT_ERROR_X11;
|
||||
}
|
||||
|
||||
Workspace *ws = workspace_get_current();
|
||||
if (ws == NULL || ws->focused == NULL) {
|
||||
return SCREENSHOT_ERROR_WINDOW_NOT_FOUND;
|
||||
}
|
||||
|
||||
Client *c = ws->focused;
|
||||
Window target = (c->frame != None) ? c->frame : c->window;
|
||||
|
||||
return screenshot_capture_window(target, result);
|
||||
}
|
||||
|
||||
ScreenshotStatus screenshot_capture_area(int x, int y, int width, int height, ScreenshotResult *result)
|
||||
{
|
||||
if (result == NULL) {
|
||||
return SCREENSHOT_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
return SCREENSHOT_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (dwn == NULL || dwn->display == NULL) {
|
||||
return SCREENSHOT_ERROR_X11;
|
||||
}
|
||||
|
||||
memset(result, 0, sizeof(ScreenshotResult));
|
||||
|
||||
if (x < 0) {
|
||||
width += x;
|
||||
x = 0;
|
||||
}
|
||||
if (y < 0) {
|
||||
height += y;
|
||||
y = 0;
|
||||
}
|
||||
if (x + width > dwn->screen_width) {
|
||||
width = dwn->screen_width - x;
|
||||
}
|
||||
if (y + height > dwn->screen_height) {
|
||||
height = dwn->screen_height - y;
|
||||
}
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
return SCREENSHOT_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
XImage *img = XGetImage(dwn->display, dwn->root, x, y, width, height,
|
||||
AllPlanes, ZPixmap);
|
||||
if (img == NULL) {
|
||||
LOG_ERROR("XGetImage failed for area capture");
|
||||
return SCREENSHOT_ERROR_X11;
|
||||
}
|
||||
|
||||
ScreenshotStatus status = ximage_to_png(img, result);
|
||||
XDestroyImage(img);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static void *screenshot_worker(void *arg)
|
||||
{
|
||||
ScreenshotRequest *req = (ScreenshotRequest *)arg;
|
||||
|
||||
if (atomic_load(&screenshot_shutting_down)) {
|
||||
req->state = SCREENSHOT_STATE_ERROR;
|
||||
req->status = SCREENSHOT_ERROR_X11;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (req->mode) {
|
||||
case SCREENSHOT_MODE_FULLSCREEN:
|
||||
req->status = screenshot_capture_fullscreen(&req->result);
|
||||
break;
|
||||
case SCREENSHOT_MODE_WINDOW:
|
||||
req->status = screenshot_capture_window(req->window, &req->result);
|
||||
break;
|
||||
case SCREENSHOT_MODE_ACTIVE:
|
||||
req->status = screenshot_capture_active(&req->result);
|
||||
break;
|
||||
case SCREENSHOT_MODE_AREA:
|
||||
req->status = screenshot_capture_area(req->x, req->y,
|
||||
req->width, req->height,
|
||||
&req->result);
|
||||
break;
|
||||
}
|
||||
|
||||
req->state = (req->status == SCREENSHOT_OK)
|
||||
? SCREENSHOT_STATE_COMPLETED
|
||||
: SCREENSHOT_STATE_ERROR;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ScreenshotRequest *screenshot_capture_async(ScreenshotMode mode,
|
||||
void (*callback)(ScreenshotRequest *))
|
||||
{
|
||||
if (!initialized || atomic_load(&screenshot_shutting_down)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ScreenshotRequest *req = dwn_calloc(1, sizeof(ScreenshotRequest));
|
||||
if (req == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
req->mode = mode;
|
||||
req->state = SCREENSHOT_STATE_PENDING;
|
||||
req->callback = callback;
|
||||
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
req->next = screenshot_queue;
|
||||
screenshot_queue = req;
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
|
||||
if (pthread_create(&req->thread, NULL, screenshot_worker, req) != 0) {
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
ScreenshotRequest **pp = &screenshot_queue;
|
||||
while (*pp != NULL) {
|
||||
if (*pp == req) {
|
||||
*pp = req->next;
|
||||
break;
|
||||
}
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
dwn_free(req);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Screenshot async request started (mode=%d)", mode);
|
||||
return req;
|
||||
}
|
||||
|
||||
ScreenshotRequest *screenshot_capture_window_async(Window window,
|
||||
void (*callback)(ScreenshotRequest *))
|
||||
{
|
||||
if (!initialized || atomic_load(&screenshot_shutting_down)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ScreenshotRequest *req = dwn_calloc(1, sizeof(ScreenshotRequest));
|
||||
if (req == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
req->mode = SCREENSHOT_MODE_WINDOW;
|
||||
req->window = window;
|
||||
req->state = SCREENSHOT_STATE_PENDING;
|
||||
req->callback = callback;
|
||||
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
req->next = screenshot_queue;
|
||||
screenshot_queue = req;
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
|
||||
if (pthread_create(&req->thread, NULL, screenshot_worker, req) != 0) {
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
ScreenshotRequest **pp = &screenshot_queue;
|
||||
while (*pp != NULL) {
|
||||
if (*pp == req) {
|
||||
*pp = req->next;
|
||||
break;
|
||||
}
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
dwn_free(req);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Screenshot async window request started");
|
||||
return req;
|
||||
}
|
||||
|
||||
ScreenshotRequest *screenshot_capture_area_async(int x, int y, int w, int h,
|
||||
void (*callback)(ScreenshotRequest *))
|
||||
{
|
||||
if (!initialized || atomic_load(&screenshot_shutting_down)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ScreenshotRequest *req = dwn_calloc(1, sizeof(ScreenshotRequest));
|
||||
if (req == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
req->mode = SCREENSHOT_MODE_AREA;
|
||||
req->x = x;
|
||||
req->y = y;
|
||||
req->width = w;
|
||||
req->height = h;
|
||||
req->state = SCREENSHOT_STATE_PENDING;
|
||||
req->callback = callback;
|
||||
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
req->next = screenshot_queue;
|
||||
screenshot_queue = req;
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
|
||||
if (pthread_create(&req->thread, NULL, screenshot_worker, req) != 0) {
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
ScreenshotRequest **pp = &screenshot_queue;
|
||||
while (*pp != NULL) {
|
||||
if (*pp == req) {
|
||||
*pp = req->next;
|
||||
break;
|
||||
}
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
dwn_free(req);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
LOG_DEBUG("Screenshot async area request started");
|
||||
return req;
|
||||
}
|
||||
|
||||
void screenshot_process_pending(void)
|
||||
{
|
||||
if (atomic_load(&screenshot_shutting_down)) {
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
ScreenshotRequest **pp = &screenshot_queue;
|
||||
|
||||
while (*pp != NULL) {
|
||||
ScreenshotRequest *req = *pp;
|
||||
|
||||
if (req->state == SCREENSHOT_STATE_COMPLETED ||
|
||||
req->state == SCREENSHOT_STATE_ERROR) {
|
||||
|
||||
pthread_join(req->thread, NULL);
|
||||
|
||||
*pp = req->next;
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
|
||||
if (req->callback != NULL) {
|
||||
req->callback(req);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
} else {
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
}
|
||||
|
||||
void screenshot_cancel(ScreenshotRequest *req)
|
||||
{
|
||||
if (req == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&screenshot_mutex);
|
||||
ScreenshotRequest **pp = &screenshot_queue;
|
||||
while (*pp != NULL) {
|
||||
if (*pp == req) {
|
||||
*pp = req->next;
|
||||
break;
|
||||
}
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
pthread_mutex_unlock(&screenshot_mutex);
|
||||
|
||||
pthread_cancel(req->thread);
|
||||
pthread_join(req->thread, NULL);
|
||||
|
||||
screenshot_result_free(&req->result);
|
||||
dwn_free(req);
|
||||
}
|
||||
+30
-8
@@ -11,6 +11,7 @@
|
||||
#include "util.h"
|
||||
#include "config.h"
|
||||
#include "panel.h"
|
||||
#include "api.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
@@ -114,7 +115,7 @@ void workspace_switch(int index)
|
||||
XUngrabServer(dwn->display);
|
||||
XSync(dwn->display, False);
|
||||
|
||||
(void)old_workspace;
|
||||
api_emit_workspace_switched(old_workspace, index);
|
||||
}
|
||||
|
||||
void workspace_switch_next(void)
|
||||
@@ -143,6 +144,7 @@ void workspace_add_client(int workspace, Client *client)
|
||||
|
||||
client->workspace = workspace;
|
||||
|
||||
api_emit_workspace_window_added(workspace, client->window);
|
||||
}
|
||||
|
||||
void workspace_remove_client(int workspace, Client *client)
|
||||
@@ -159,6 +161,8 @@ void workspace_remove_client(int workspace, Client *client)
|
||||
if (ws->focused == client) {
|
||||
ws->focused = NULL;
|
||||
}
|
||||
|
||||
api_emit_workspace_window_removed(workspace, client->window);
|
||||
}
|
||||
|
||||
void workspace_move_client(Client *client, int new_workspace)
|
||||
@@ -179,22 +183,20 @@ void workspace_move_client(Client *client, int new_workspace)
|
||||
XGrabServer(dwn->display);
|
||||
|
||||
workspace_remove_client(old_workspace, client);
|
||||
|
||||
workspace_add_client(new_workspace, client);
|
||||
|
||||
atoms_set_window_desktop(client->window, new_workspace);
|
||||
|
||||
if (new_workspace != dwn->current_workspace) {
|
||||
client_hide(client);
|
||||
} else {
|
||||
client_show(client);
|
||||
Workspace *target_ws = workspace_get(new_workspace);
|
||||
if (target_ws != NULL) {
|
||||
target_ws->focused = client;
|
||||
}
|
||||
|
||||
workspace_arrange(old_workspace);
|
||||
workspace_arrange(new_workspace);
|
||||
|
||||
XUngrabServer(dwn->display);
|
||||
XSync(dwn->display, False);
|
||||
|
||||
workspace_switch(new_workspace);
|
||||
}
|
||||
|
||||
Client *workspace_get_first_client(int workspace)
|
||||
@@ -226,6 +228,7 @@ void workspace_set_layout(int workspace, LayoutType layout)
|
||||
return;
|
||||
}
|
||||
|
||||
int old_layout = ws->layout;
|
||||
ws->layout = layout;
|
||||
|
||||
XGrabServer(dwn->display);
|
||||
@@ -233,6 +236,8 @@ void workspace_set_layout(int workspace, LayoutType layout)
|
||||
XUngrabServer(dwn->display);
|
||||
XSync(dwn->display, False);
|
||||
|
||||
api_emit_workspace_layout_changed(workspace, old_layout, layout);
|
||||
|
||||
LOG_DEBUG("Workspace %d layout set to %d", workspace + 1, layout);
|
||||
}
|
||||
|
||||
@@ -249,6 +254,7 @@ void workspace_cycle_layout(int workspace)
|
||||
return;
|
||||
}
|
||||
|
||||
int old_layout = ws->layout;
|
||||
ws->layout = (ws->layout + 1) % LAYOUT_COUNT;
|
||||
|
||||
XGrabServer(dwn->display);
|
||||
@@ -256,6 +262,8 @@ void workspace_cycle_layout(int workspace)
|
||||
XUngrabServer(dwn->display);
|
||||
XSync(dwn->display, False);
|
||||
|
||||
api_emit_workspace_layout_changed(workspace, old_layout, ws->layout);
|
||||
|
||||
const char *layout_names[] = { "Tiling", "Floating", "Monocle" };
|
||||
LOG_INFO("Workspace %d: %s layout", workspace + 1, layout_names[ws->layout]);
|
||||
}
|
||||
@@ -267,6 +275,8 @@ void workspace_set_master_ratio(int workspace, float ratio)
|
||||
return;
|
||||
}
|
||||
|
||||
float old_ratio = ws->master_ratio;
|
||||
|
||||
if (ratio < 0.1f) ratio = 0.1f;
|
||||
if (ratio > 0.9f) ratio = 0.9f;
|
||||
|
||||
@@ -276,6 +286,10 @@ void workspace_set_master_ratio(int workspace, float ratio)
|
||||
workspace_arrange(workspace);
|
||||
XUngrabServer(dwn->display);
|
||||
XSync(dwn->display, False);
|
||||
|
||||
if (old_ratio != ws->master_ratio) {
|
||||
api_emit_workspace_master_ratio_changed(workspace, old_ratio, ws->master_ratio);
|
||||
}
|
||||
}
|
||||
|
||||
void workspace_adjust_master_ratio(int workspace, float delta)
|
||||
@@ -293,6 +307,8 @@ void workspace_set_master_count(int workspace, int count)
|
||||
return;
|
||||
}
|
||||
|
||||
int old_count = ws->master_count;
|
||||
|
||||
if (count < 1) count = 1;
|
||||
ws->master_count = count;
|
||||
|
||||
@@ -300,6 +316,10 @@ void workspace_set_master_count(int workspace, int count)
|
||||
workspace_arrange(workspace);
|
||||
XUngrabServer(dwn->display);
|
||||
XSync(dwn->display, False);
|
||||
|
||||
if (old_count != ws->master_count) {
|
||||
api_emit_workspace_master_count_changed(workspace, old_count, ws->master_count);
|
||||
}
|
||||
}
|
||||
|
||||
void workspace_adjust_master_count(int workspace, int delta)
|
||||
@@ -323,6 +343,8 @@ void workspace_arrange(int workspace)
|
||||
}
|
||||
|
||||
layout_arrange(workspace);
|
||||
|
||||
api_emit_workspace_arranged(workspace, ws->layout);
|
||||
}
|
||||
|
||||
void workspace_arrange_current(void)
|
||||
|
||||
Reference in New Issue
Block a user