478 lines
13 KiB
C
478 lines
13 KiB
C
/*
|
|
* Vinci
|
|
*
|
|
* Copyright (C) 2025 Orastron Srl unipersonale
|
|
*
|
|
* Vinci is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, version 3 of the License.
|
|
*
|
|
* Vinci is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Vinci. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* File author: Stefano D'Angelo, Paolo Marrone
|
|
*/
|
|
|
|
#include "vinci.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <xcb/xcb.h>
|
|
#include <poll.h>
|
|
#include <stdio.h>
|
|
|
|
struct window {
|
|
vinci *g;
|
|
window *next;
|
|
xcb_window_t window;
|
|
xcb_pixmap_t pixmap;
|
|
xcb_gcontext_t gc;
|
|
uint16_t x;
|
|
uint16_t y;
|
|
uint16_t width;
|
|
uint16_t height;
|
|
void *bgra;
|
|
void *data;
|
|
window_cbs cbs;
|
|
};
|
|
|
|
struct vinci {
|
|
xcb_connection_t *connection;
|
|
xcb_screen_t *screen;
|
|
const xcb_setup_t *setup;
|
|
xcb_visualtype_t *visual;
|
|
xcb_atom_t wm_protocols_atom;
|
|
xcb_atom_t wm_delete_atom;
|
|
xcb_atom_t xembed_info_atom;
|
|
window *windows;
|
|
char keep_running;
|
|
};
|
|
|
|
vinci* vinci_new(void) {
|
|
vinci* g = (vinci*) malloc(sizeof(vinci));
|
|
if (g == NULL)
|
|
return NULL;
|
|
|
|
g->connection = xcb_connect(NULL, NULL);
|
|
if (xcb_connection_has_error(g->connection)) {
|
|
xcb_disconnect(g->connection);
|
|
return NULL;
|
|
}
|
|
g->screen = xcb_setup_roots_iterator(xcb_get_setup(g->connection)).data;
|
|
g->setup = xcb_get_setup(g->connection);
|
|
|
|
xcb_visualtype_iterator_t visual_iter; xcb_intern_atom_reply_t* reply; // stupid C++ and its cross-initialization issues...
|
|
|
|
xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(g->screen);
|
|
xcb_depth_t *depth = NULL;
|
|
while (depth_iter.rem) {
|
|
if (depth_iter.data->depth == 24 && depth_iter.data->visuals_len) {
|
|
depth = depth_iter.data;
|
|
break;
|
|
}
|
|
xcb_depth_next(&depth_iter);
|
|
}
|
|
if (!depth)
|
|
goto err;
|
|
|
|
visual_iter = xcb_depth_visuals_iterator(depth);
|
|
g->visual = NULL;
|
|
while (visual_iter.rem) {
|
|
if (visual_iter.data->_class == XCB_VISUAL_CLASS_TRUE_COLOR) {
|
|
g->visual = visual_iter.data;
|
|
break;
|
|
}
|
|
xcb_visualtype_next(&visual_iter);
|
|
}
|
|
if (!g->visual)
|
|
goto err;
|
|
|
|
reply = xcb_intern_atom_reply(g->connection, xcb_intern_atom(g->connection, 1, 12, "WM_PROTOCOLS"), NULL);
|
|
if (!reply)
|
|
goto err;
|
|
g->wm_protocols_atom = reply->atom;
|
|
free(reply);
|
|
|
|
reply = xcb_intern_atom_reply(g->connection, xcb_intern_atom(g->connection, 0, 16, "WM_DELETE_WINDOW"), NULL);
|
|
if (!reply)
|
|
goto err;
|
|
g->wm_delete_atom = reply->atom;
|
|
free(reply);
|
|
|
|
reply = xcb_intern_atom_reply(g->connection, xcb_intern_atom(g->connection, 0, 12, "_XEMBED_INFO"), NULL);
|
|
if (!reply)
|
|
goto err;
|
|
g->xembed_info_atom = reply->atom;
|
|
free(reply);
|
|
|
|
g->windows = NULL;
|
|
|
|
return g;
|
|
|
|
err:
|
|
free(g);
|
|
return NULL;
|
|
}
|
|
|
|
void vinci_destroy(vinci* g) {
|
|
xcb_disconnect(g->connection);
|
|
free(g);
|
|
}
|
|
|
|
static window* get_window(vinci* g, xcb_window_t xw) {
|
|
window* w = g->windows;
|
|
while (w->window != xw)
|
|
w = (window*)w->next;
|
|
return w;
|
|
}
|
|
|
|
static uint32_t get_mouse_state(uint16_t state, xcb_button_t last) {
|
|
return (
|
|
(state & XCB_BUTTON_MASK_1) >> 8
|
|
| (state & XCB_BUTTON_MASK_2) >> 7
|
|
| (state & XCB_BUTTON_MASK_3) >> 9 )
|
|
^ (last == 1 ? 1 : (last == 2 ? 4 : (last == 3 ? 2 : 0)))
|
|
;
|
|
}
|
|
|
|
void vinci_run(vinci* g, char single) {
|
|
g->keep_running = 1;
|
|
xcb_generic_event_t *ev;
|
|
struct pollfd pfd;
|
|
pfd.fd = xcb_get_file_descriptor(g->connection);
|
|
pfd.events = POLLIN;
|
|
while (g->keep_running) {
|
|
int err = poll(&pfd, 1, single ? 0 : -1);
|
|
if (err == -1)
|
|
break;
|
|
else if (err == 0 && single)
|
|
break;
|
|
|
|
while (g->keep_running && (ev = xcb_poll_for_event(g->connection))) {
|
|
switch (ev->response_type & ~0x80) {
|
|
case XCB_EXPOSE:
|
|
{
|
|
xcb_expose_event_t *x = (xcb_expose_event_t *)ev;
|
|
window* w = get_window(g, x->window);
|
|
|
|
xcb_copy_area(g->connection, w->pixmap, w->window, w->gc, x->x, x->y, x->x, x->y, x->width, x->height);
|
|
xcb_flush(g->connection);
|
|
}
|
|
break;
|
|
|
|
case XCB_CONFIGURE_NOTIFY:
|
|
{
|
|
xcb_configure_notify_event_t *x = (xcb_configure_notify_event_t *)ev;
|
|
window* w = get_window(g, x->window);
|
|
|
|
xcb_translate_coordinates_reply_t *trans =
|
|
xcb_translate_coordinates_reply(g->connection, xcb_translate_coordinates(g->connection, w->window, g->screen->root, 0, 0), NULL);
|
|
if (trans) {
|
|
if (trans->dst_x != w->x || trans->dst_y != w->y) {
|
|
w->x = trans->dst_x;
|
|
w->y = trans->dst_y;
|
|
if (w->cbs.on_window_move)
|
|
w->cbs.on_window_move(w, trans->dst_x, trans->dst_y);
|
|
}
|
|
free(trans);
|
|
}
|
|
|
|
if (x->width != w->width || x->height != w->height) {
|
|
w->width = x->width;
|
|
w->height = x->height;
|
|
xcb_free_pixmap(g->connection, w->pixmap);
|
|
w->pixmap = xcb_generate_id(g->connection);
|
|
xcb_create_pixmap(g->connection, g->screen->root_depth, w->pixmap, w->window, x->width, x->height);
|
|
w->bgra = (uint8_t*) realloc(w->bgra, (w->width * w->height) << 2);
|
|
|
|
if (w->cbs.on_window_resize)
|
|
w->cbs.on_window_resize(w, x->width, x->height);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case XCB_CLIENT_MESSAGE:
|
|
{
|
|
xcb_client_message_event_t *x = (xcb_client_message_event_t *)ev;
|
|
window* w = get_window(g, x->window);
|
|
if (w->cbs.on_window_close) {
|
|
if (x->data.data32[0] == g->wm_delete_atom)
|
|
w->cbs.on_window_close(w);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case XCB_BUTTON_PRESS:
|
|
{
|
|
xcb_button_press_event_t *x = (xcb_button_press_event_t *)ev;
|
|
window* w = get_window(g, x->event);
|
|
// First three buttons
|
|
if (w->cbs.on_mouse_press) {
|
|
if (x->detail <= 3)
|
|
w->cbs.on_mouse_press(w, x->event_x, x->event_y, get_mouse_state(x->state, x->detail));
|
|
}
|
|
// wheel
|
|
if (w->cbs.on_mouse_wheel) {
|
|
if (x->detail == 4)
|
|
w->cbs.on_mouse_wheel(w, x->event_x, x->event_y, 57);
|
|
else if (x->detail == 5)
|
|
w->cbs.on_mouse_wheel(w, x->event_x, x->event_y, -57);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case XCB_BUTTON_RELEASE:
|
|
{
|
|
xcb_button_release_event_t *x = (xcb_button_release_event_t *)ev;
|
|
window* w = get_window(g, x->event);
|
|
|
|
// First three buttons
|
|
if (x->detail <= 3) {
|
|
if (w->cbs.on_mouse_release)
|
|
w->cbs.on_mouse_release(w, x->event_x, x->event_y, get_mouse_state(x->state, x->detail));
|
|
}
|
|
// Mouse wheel
|
|
else {
|
|
// Nothing to do
|
|
}
|
|
}
|
|
break;
|
|
|
|
case XCB_MOTION_NOTIFY:
|
|
{
|
|
xcb_motion_notify_event_t *x = (xcb_motion_notify_event_t *)ev;
|
|
window* w = get_window(g, x->event);
|
|
if (w->cbs.on_mouse_move)
|
|
w->cbs.on_mouse_move(w, x->event_x, x->event_y, get_mouse_state(x->state, 0));
|
|
}
|
|
break;
|
|
|
|
case XCB_ENTER_NOTIFY:
|
|
{
|
|
xcb_enter_notify_event_t *x = (xcb_enter_notify_event_t *)ev;
|
|
window* w = get_window(g, x->event);
|
|
if (w->cbs.on_mouse_enter)
|
|
w->cbs.on_mouse_enter(w, x->event_x, x->event_y, get_mouse_state(x->state, 0));
|
|
}
|
|
break;
|
|
|
|
case XCB_LEAVE_NOTIFY:
|
|
{
|
|
xcb_leave_notify_event_t *x = (xcb_leave_notify_event_t *)ev;
|
|
window* w = get_window(g, x->event);
|
|
if (w->cbs.on_mouse_leave)
|
|
w->cbs.on_mouse_leave(w, x->event_x, x->event_y, get_mouse_state(x->state, 0));
|
|
}
|
|
break;
|
|
|
|
case XCB_KEY_PRESS:
|
|
{
|
|
xcb_key_press_event_t *x = (xcb_key_press_event_t *)ev;
|
|
window* w = get_window(g, x->event);
|
|
if (w->cbs.on_key_press)
|
|
w->cbs.on_key_press(w, x->detail, x->state);
|
|
}
|
|
break;
|
|
|
|
case XCB_KEY_RELEASE:
|
|
{
|
|
xcb_key_release_event_t *x = (xcb_key_release_event_t *)ev;
|
|
window* w = get_window(g, x->event);
|
|
if (w->cbs.on_key_release)
|
|
w->cbs.on_key_release(w, x->detail, x->state);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
free(ev);
|
|
}
|
|
}
|
|
}
|
|
|
|
void vinci_stop(vinci* g) {
|
|
g->keep_running = 0;
|
|
}
|
|
|
|
#define XEMBED_MAPPED (1 << 0)
|
|
|
|
window* window_new(vinci* g, void* p, uint32_t width, uint32_t height, window_cbs *cbs) {
|
|
xcb_window_t *parent = (xcb_window_t *) p;
|
|
|
|
window* ret = (window*) malloc(sizeof(window));
|
|
if (ret == NULL)
|
|
return NULL;
|
|
ret->bgra = malloc((width * height) << 2);
|
|
if (!ret->bgra) {
|
|
free(ret);
|
|
return NULL;
|
|
}
|
|
|
|
ret->window = xcb_generate_id(g->connection);
|
|
uint32_t mask = XCB_CW_EVENT_MASK;
|
|
uint32_t values[] = { XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY
|
|
| XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE
|
|
| XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_BUTTON_MOTION
|
|
| XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW
|
|
| XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE };
|
|
xcb_create_window(g->connection, 24, ret->window, parent ? *parent : g->screen->root,
|
|
0, 0, width, height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
|
g->visual->visual_id, mask, values);
|
|
|
|
ret->pixmap = xcb_generate_id(g->connection);
|
|
xcb_create_pixmap(g->connection, g->screen->root_depth, ret->pixmap, ret->window, width, height);
|
|
ret->gc = xcb_generate_id(g->connection);
|
|
xcb_create_gc(g->connection, ret->gc, ret->pixmap, 0, NULL);
|
|
|
|
uint32_t xembed_info[] = { 0, 0 };
|
|
xcb_change_property(g->connection, XCB_PROP_MODE_REPLACE, ret->window, g->xembed_info_atom, g->xembed_info_atom, 32, 2, xembed_info);
|
|
|
|
// Subscribe to window close events
|
|
xcb_change_property(g->connection, XCB_PROP_MODE_REPLACE, ret->window, g->wm_protocols_atom, XCB_ATOM_ATOM, 32, 1, &(g->wm_delete_atom));
|
|
|
|
xcb_flush(g->connection);
|
|
|
|
ret->next = NULL;
|
|
if (g->windows == NULL)
|
|
g->windows = ret;
|
|
else {
|
|
window* n = g->windows;
|
|
while (n->next)
|
|
n = (window*) n->next;
|
|
n->next = ret;
|
|
}
|
|
|
|
ret->g = g;
|
|
ret->x = 0;
|
|
ret->y = 0;
|
|
ret->width = width;
|
|
ret->height = height;
|
|
ret->data = NULL;
|
|
ret->cbs = *cbs;
|
|
|
|
return ret;
|
|
}
|
|
|
|
void window_free(window* w) {
|
|
if (w->g->windows == w)
|
|
w->g->windows = w->next;
|
|
else {
|
|
window* n = w->g->windows;
|
|
while ((window*) n->next != w)
|
|
n = (window*) n->next;
|
|
n->next = w->next;
|
|
}
|
|
|
|
xcb_free_gc(w->g->connection, w->gc);
|
|
xcb_free_pixmap(w->g->connection, w->pixmap);
|
|
xcb_destroy_window(w->g->connection, w->window);
|
|
xcb_flush(w->g->connection);
|
|
free(w->bgra);
|
|
free(w);
|
|
}
|
|
|
|
void window_draw(window* w, unsigned char *data, int32_t dx, int32_t dy, int32_t dw, int32_t dh, int32_t wx, int32_t wy, int32_t width, int32_t height) {
|
|
|
|
const int32_t wx_ = wx < 0 ? 0 : wx;
|
|
const int32_t wy_ = wy < 0 ? 0 : wy;
|
|
width = width - (wx_ - wx);
|
|
height = height - (wy_ - wy);
|
|
if (width <= 0 || height <= 0 )
|
|
return;
|
|
dx = dx + (wx_ - wx);
|
|
dy = dy + (wy_ - wy);
|
|
wx = wx_;
|
|
wy = wy_;
|
|
|
|
uint8_t *bgra = (uint8_t*) w->bgra;
|
|
|
|
int32_t i = 0;
|
|
int32_t o = (dw * dy + dx) << 2;
|
|
int32_t p = (dw - width) << 2;
|
|
|
|
if (w->g->setup->image_byte_order == XCB_IMAGE_ORDER_LSB_FIRST) {
|
|
for (int32_t y = dy; y < dy + height && y < dh; y++) {
|
|
for (int32_t x = dx; x < dx + width; x++, o += 4) {
|
|
bgra[i++] = data[o + 2];
|
|
bgra[i++] = data[o + 1];
|
|
bgra[i++] = data[o ];
|
|
i++;
|
|
}
|
|
o += p;
|
|
}
|
|
} else {
|
|
for (int32_t y = dy; y < dy + height; y++) {
|
|
for (int32_t x = dx; x < dx + width; x++, o += 4) {
|
|
bgra[i++] = data[o ];
|
|
bgra[i++] = data[o + 1];
|
|
bgra[i++] = data[o + 2];
|
|
i++;
|
|
}
|
|
o += p;
|
|
}
|
|
}
|
|
|
|
const uint32_t bytes = (width * height) << 2;
|
|
xcb_put_image(w->g->connection, XCB_IMAGE_FORMAT_Z_PIXMAP, w->pixmap, w->gc, width, height, wx, wy, 0, w->g->screen->root_depth, bytes, bgra);
|
|
xcb_copy_area(w->g->connection, w->pixmap, w->window, w->gc, wx, wy, wx, wy, width, height);
|
|
xcb_flush(w->g->connection);
|
|
}
|
|
|
|
void *window_get_handle(window* w) {
|
|
return &w->window;
|
|
}
|
|
|
|
uint32_t window_get_width(window* w) {
|
|
return w->width;
|
|
}
|
|
uint32_t window_get_height(window* w) {
|
|
return w->height;
|
|
}
|
|
/*
|
|
// In case we need to get w and h from x:
|
|
xcb_get_geometry_cookie_t geom_cookie = xcb_get_geometry(w->g->connection, w->window);
|
|
xcb_get_geometry_reply_t *cr = xcb_get_geometry_reply(w->g->connection, geom_cookie, NULL);
|
|
cr->width; cr->height;
|
|
*/
|
|
|
|
void window_resize(window* w, uint32_t width, uint32_t height) {
|
|
const uint32_t values[] = { width, height };
|
|
xcb_configure_window(w->g->connection, w->window, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
|
|
xcb_clear_area(w->g->connection, 1, w->window, 0, 0, width, height);
|
|
xcb_flush(w->g->connection);
|
|
}
|
|
|
|
void window_move (window* w, uint32_t x, uint32_t y) {
|
|
(void) x;
|
|
(void) y;
|
|
const uint32_t values[] = { w->width, w->height };
|
|
xcb_configure_window(w->g->connection, w->window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values);
|
|
xcb_flush(w->g->connection);
|
|
}
|
|
|
|
void window_show(window* w) {
|
|
uint32_t xembed_info[] = { 0, XEMBED_MAPPED };
|
|
xcb_change_property(w->g->connection, XCB_PROP_MODE_REPLACE, w->window, w->g->xembed_info_atom, w->g->xembed_info_atom, 32, 2, xembed_info);
|
|
xcb_map_window(w->g->connection, w->window);
|
|
xcb_flush(w->g->connection);
|
|
}
|
|
|
|
void window_hide(window* w) {
|
|
uint32_t xembed_info[] = { 0, 0 };
|
|
xcb_change_property(w->g->connection, XCB_PROP_MODE_REPLACE, w->window, w->g->xembed_info_atom, w->g->xembed_info_atom, 32, 2, xembed_info);
|
|
xcb_unmap_window(w->g->connection, w->window);
|
|
xcb_flush(w->g->connection);
|
|
}
|
|
|
|
void window_set_data(window* w, void *data) {
|
|
w->data = data;
|
|
}
|
|
|
|
void *window_get_data(window* w) {
|
|
return w->data;
|
|
} |