Ready. Set. Go!

This commit is contained in:
apio 2024-03-31 12:56:50 +02:00
commit 65d4da1d1f
Signed by: apio
GPG Key ID: B8A7D06E42258954
7 changed files with 674 additions and 0 deletions

14
.clang-format Normal file
View File

@ -0,0 +1,14 @@
---
BasedOnStyle: Microsoft
CompactNamespaces: 'false'
FixNamespaceComments: 'false'
NamespaceIndentation: All
AllowShortBlocksOnASingleLine: 'true'
AllowShortCaseLabelsOnASingleLine: 'true'
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: Always
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: 'true'
PointerAlignment: Left
Cpp11BracedListStyle: 'false'
SpaceBeforeCpp11BracedList: 'true'

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
**/*.o
libwind.a

25
LICENSE Normal file
View File

@ -0,0 +1,25 @@
BSD 2-Clause License
Copyright (c) 2024, apio.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

16
Makefile Normal file
View File

@ -0,0 +1,16 @@
libwind.a: src/wind.o
ar rcs libwind.a src/wind.o
src/wind.o: src/wind.c
$(CC) -Wall -Wextra -Werror -O2 -c -o src/wind.o src/wind.c
DESTDIR := $(if $(DESTDIR),$(DESTDIR),/usr/local)
install:
mkdir -p $(DESTDIR)/lib
mkdir -p $(DESTDIR)/include
cp ./libwind.a $(DESTDIR)/lib/
cp src/wind.h $(DESTDIR)/include/
clean:
rm libwind.a src/wind.o

27
README.md Normal file
View File

@ -0,0 +1,27 @@
# libwind
A standalone implementation of the [wind](https://git.cloudapio.eu/apio/Luna/src/branch/main/wind) client protocol in C
## Why?
`wind` is the windowing system for the Luna operating system. While Luna already has a native library that implements the `wind` client protocol (`libui`), reimplementing this protocol in a standalone library has a number of advantages, especially for third-party programs:
- Increased compatibility: `libui` is written in highly customized C++, while this library is written in POSIX C. This makes it easier to use in C programs, and wrappers for other languages such as Rust are easier to make.
- Unintrusive: `libui` assumes that your application is written around it. This is great for native Luna programs, as they can take advantage of libui's EventLoop and other nice features, but it's not so nice when you have to rewrite other programs to structure them around an EventLoop. This library does not do that: one call to `wind_connect` in one function, another to `wind_create_window`, and you're good to go!
## Building
To build, simply run `make`. This will create a static library called `libwind.a`.
To install, run `make install`. This will install the header (`wind.h`) and the library (`libwind.a`) into the system include and library directories, or into whatever DESTDIR is set to.
To build programs using libwind, include `wind.h` and link with libwind: `-lwind`.
## Compatibility
As the wind client protocol is still in the early stages of development, it is subject to change at any time. This library should be updated as soon as possible whenever that happens, so make sure to always have both the latest version of the [Luna](https://git.cloudapio.eu/apio/Luna) repository as well as this one.
## License
libwind is free and open-source software under the [BSD-2-Clause License](LICENSE).

268
src/wind.c Normal file
View File

@ -0,0 +1,268 @@
#include "wind.h"
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif
int wind_connect(wind_client_t* client, const char* path, int blocking)
{
memset(client, 0, sizeof *client);
client->fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (client->fd < 0) return -1;
struct sockaddr_un un;
un.sun_family = AF_UNIX;
strncpy(un.sun_path, path, sizeof(un.sun_path) - 1);
if (connect(client->fd, (struct sockaddr*)&un, sizeof(un)) < 0)
{
close(client->fd);
return -1;
}
if (!blocking) { fcntl(client->fd, F_SETFL, O_NONBLOCK); }
fcntl(client->fd, F_SETFD, FD_CLOEXEC);
return 0;
}
int wind_send_request(wind_client_t* client, uint8_t request_id, const void* request, size_t request_size)
{
if (write(client->fd, &request_id, sizeof request_id) < 0) return -1;
if (write(client->fd, request, request_size) < 0) return -1;
return 0;
}
int wind_read_response(wind_client_t* client, uint8_t response_id, void* response, size_t response_size)
{
int rc = read(client->fd, response, response_size);
if (rc < 0)
{
if (errno == EAGAIN || errno == EINTR)
{
client->operation_in_progress = 1;
client->saved_id = response_id;
return 0;
}
else
return -1;
}
return 1;
}
int wind_wait_for_sync_reply(wind_client_t* client, uint8_t response_id, void* response, size_t response_size)
{
int max_other_messages = 5;
while (max_other_messages)
{
uint8_t id;
ssize_t rc = read(client->fd, &id, sizeof id);
if (rc < 0 && (errno == EAGAIN || errno == EINTR)) continue;
if (id == 0) // Error result
{
while (1)
{
int code;
rc = read(client->fd, &code, sizeof code);
if (rc < 0 && (errno == EAGAIN || errno == EINTR)) continue;
errno = code;
return -1;
}
}
if (response_id != id)
{
client->handler(client, id, client->arg);
max_other_messages--;
continue;
}
while (1)
{
rc = read(client->fd, response, response_size);
if (rc < 0 && (errno == EAGAIN || errno == EINTR)) continue;
return 0;
}
}
errno = ENOMSG;
return -1;
}
int wind_client_fd(wind_client_t* client)
{
return client->fd;
}
int wind_disconnect(wind_client_t* client)
{
int rc = close(client->fd);
memset(client, 0, sizeof *client);
return rc;
}
void wind_set_handler(wind_client_t* client, int (*handler)(wind_client_t*, uint8_t, void*), void* arg)
{
client->handler = handler;
client->arg = arg;
}
int wind_check_for_messages(wind_client_t* client)
{
if (client->operation_in_progress)
{
client->operation_in_progress = 0;
client->handler(client, client->saved_id, client->arg);
return 0;
}
uint8_t id;
ssize_t rc = read(client->fd, &id, sizeof id);
if (rc < 0)
{
if (errno == EAGAIN) return 0; // No messages, and the caller does not want us to block.
if (errno == EINTR) return 0; // Let the caller check for anything having happened because a signal handler ran.
return -1;
}
client->handler(client, id, client->arg);
return 0;
}
static int adopt_shared_memory(const char* path, size_t size, void** ptr)
{
int fd = shm_open(path, O_RDWR, 0660);
if (fd < 0) return -1;
void* p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p == MAP_FAILED)
{
int olderr = errno;
close(fd);
errno = olderr;
return -1;
}
close(fd);
*ptr = p;
return 0;
}
int wind_create_window(wind_client_t* client, wind_window_t* window, struct wind_rect rect)
{
memset(window, 0, sizeof *window);
struct wind_create_window_request request;
memcpy(&request.rect, &rect, sizeof rect);
wind_send_request(client, wind_CreateWindow, &request, sizeof request);
struct wind_create_window_response response;
int result = wind_wait_for_sync_reply(client, wind_CreateWindowResponse, &response, sizeof response);
if (result < 0) return -1;
window->id = response.window;
char path[WIND_STRING_LENGTH + 1];
memset(path, 0, sizeof path);
strncpy(path, response.shm_path, WIND_STRING_LENGTH);
result = adopt_shared_memory(path, rect.height * rect.width * sizeof(int), &window->canvas);
if (result < 0)
{
wind_close_window(client, window);
return -1;
}
memcpy(&window->rect, &rect, sizeof rect);
struct wind_remove_shared_request shm_request = {
.window = response.window,
};
wind_send_request(client, wind_RemoveShared, &shm_request, sizeof shm_request);
return 0;
}
int wind_close_window(wind_client_t* client, wind_window_t* window)
{
struct wind_close_window_request request = {
.window = window->id,
};
int result = wind_send_request(client, wind_CloseWindow, &request, sizeof request);
if (window->canvas) munmap(window->canvas, window->rect.width * window->rect.height * sizeof(int));
return result;
}
int wind_invalidate(wind_client_t* client, wind_window_t* window)
{
struct wind_invalidate_request request = {
.window = window->id,
};
return wind_send_request(client, wind_Invalidate, &request, sizeof request);
}
int wind_set_window_title(wind_client_t* client, wind_window_t* window, const char* title)
{
struct wind_set_window_title_request request = {
.window = window->id,
};
strncpy(request.title, title, WIND_STRING_LENGTH - 1);
return wind_send_request(client, wind_SetWindowTitle, &request, sizeof request);
}
int wind_set_titlebar_height(wind_client_t* client, wind_window_t* window, int height)
{
struct wind_set_titlebar_height_request request = {
.window = window->id,
.height = height,
};
return wind_send_request(client, wind_SetTitlebarHeight, &request, sizeof request);
}
int wind_get_screen_dimensions(wind_client_t* client, struct wind_rect* rect)
{
struct wind_get_screen_rect_request request;
struct wind_get_screen_rect_response response;
wind_send_request(client, wind_GetScreenRect, &request, sizeof request);
int result = wind_wait_for_sync_reply(client, wind_GetScreenRectResponse, &response, sizeof response);
memcpy(rect, &response.rect, sizeof *rect);
return result;
}
int wind_set_special_window_attributes(wind_client_t* client, wind_window_t* window,
enum wind_window_attributes attributes)
{
struct wind_set_special_window_attributes_request request = {
.window = window->id,
.attributes = attributes,
};
return wind_send_request(client, wind_SetSpecialWindowAttributes, &request, sizeof request);
}

322
src/wind.h Normal file
View File

@ -0,0 +1,322 @@
#ifndef LIBWIND_H
#define LIBWIND_H
#include <stdint.h>
#include <stddef.h>
typedef struct wind_client_t
{
int fd;
int operation_in_progress;
uint8_t saved_id;
int(*handler)(struct wind_client_t*, uint8_t, void*);
void* arg;
} wind_client_t;
struct wind_point
{
int x;
int y;
};
struct wind_rect
{
struct wind_point pos;
int width;
int height;
};
typedef struct {
int id;
void* canvas;
struct wind_rect rect;
} wind_window_t;
enum wind_server_message
{
wind_ClientError,
wind_CreateWindow,
wind_RemoveShared,
wind_SetWindowTitle,
wind_Invalidate,
wind_CloseWindow,
wind_GetScreenRect,
wind_SetTitlebarHeight,
wind_SetSpecialWindowAttributes,
};
enum wind_client_message
{
wind_ServerError,
wind_CreateWindowResponse,
wind_WindowCloseRequest,
wind_MouseEvent,
wind_MouseLeave,
wind_GetScreenRectResponse,
wind_KeyEvent,
};
#define WIND_STRING_LENGTH 256
struct wind_create_window_request
{
struct wind_rect rect;
};
struct wind_remove_shared_request
{
int window;
};
struct wind_set_window_title_request
{
int window;
char title[WIND_STRING_LENGTH];
};
struct wind_invalidate_request
{
int window;
};
struct wind_close_window_request
{
int window;
};
struct wind_get_screen_rect_request
{
int _padding;
};
struct wind_set_titlebar_height_request
{
int window;
int height;
};
enum wind_window_attributes
{
wind_UNFOCUSEABLE = 1,
};
struct wind_set_special_window_attributes_request
{
int window;
enum wind_window_attributes attributes;
};
struct wind_create_window_response
{
int window;
char shm_path[WIND_STRING_LENGTH];
};
struct wind_window_close_request
{
int window;
};
enum wind_mouse_button
{
wind_mouse_Left = (1 << 0),
wind_mouse_Middle = (1 << 1),
wind_mouse_Right = (1 << 2),
};
struct wind_mouse_event_request
{
int window;
struct wind_point position;
enum wind_mouse_button buttons;
};
struct wind_mouse_leave_request
{
int window;
};
struct wind_get_screen_rect_response
{
struct wind_rect rect;
};
enum wind_keycode
{
// Function keys
wind_key_F1,
wind_key_F2,
wind_key_F3,
wind_key_F4,
wind_key_F5,
wind_key_F6,
wind_key_F7,
wind_key_F8,
wind_key_F9,
wind_key_F10,
wind_key_F11,
wind_key_F12,
// System keys
wind_key_Esc,
wind_key_PrtScr,
wind_key_Pause,
wind_key_Super,
wind_key_Menu,
// Modifier keys
wind_key_LeftShift,
wind_key_RightShift,
wind_key_LeftAlt,
wind_key_RightAlt, // or AltGr on some keyboards
wind_key_LeftControl,
wind_key_RightControl,
// Navigation keys
wind_key_Tab,
wind_key_Home,
wind_key_End,
wind_key_PageUp,
wind_key_PageDown,
wind_key_RightArrow,
wind_key_LeftArrow,
wind_key_UpArrow,
wind_key_DownArrow,
// Editing keys
wind_key_Backspace,
wind_key_Enter,
wind_key_Insert,
wind_key_Delete,
wind_key_KeypadEnter,
// Lock keys
wind_key_ScrollLock,
wind_key_CapsLock,
wind_key_NumLock,
// Keypad keys
wind_key_Keypad0,
wind_key_Keypad1,
wind_key_Keypad2,
wind_key_Keypad3,
wind_key_Keypad4,
wind_key_Keypad5,
wind_key_Keypad6,
wind_key_Keypad7,
wind_key_Keypad8,
wind_key_Keypad9,
wind_key_KeypadDot,
wind_key_KeypadPlus,
wind_key_KeypadMinus,
wind_key_KeypadMul,
wind_key_KeypadDiv,
// Character keys (depending on keyboard layout), examples in US QWERTY
wind_key_CH00, // `
wind_key_CH01, // 1
wind_key_CH02, // 2
wind_key_CH03, // 3
wind_key_CH04, // 4
wind_key_CH05, // 5
wind_key_CH06, // 6
wind_key_CH07, // 7
wind_key_CH08, // 8
wind_key_CH09, // 9
wind_key_CH10, // 0
wind_key_CH11, // -
wind_key_CH12, // =
wind_key_CH13, // Q
wind_key_CH14, // W
wind_key_CH15, // E
wind_key_CH16, // R
wind_key_CH17, // T
wind_key_CH18, // Y
wind_key_CH19, // U
wind_key_CH20, // I
wind_key_CH21, // O
wind_key_CH22, // P
wind_key_CH23, // [
wind_key_CH24, // ]
wind_key_CH25, // A
wind_key_CH26, // S
wind_key_CH27, // D
wind_key_CH28, // F
wind_key_CH29, // G
wind_key_CH30, // H
wind_key_CH31, // J
wind_key_CH32, // K
wind_key_CH33, // L
wind_key_CH34, // ;
wind_key_CH35, // '
wind_key_CH36, // #
wind_key_CH37, // Backslash
wind_key_CH38, // Z
wind_key_CH39, // X
wind_key_CH40, // C
wind_key_CH41, // V
wind_key_CH42, // B
wind_key_CH43, // N
wind_key_CH44, // M
wind_key_CH45, // ,
wind_key_CH46, // .
wind_key_CH47, // /
wind_key_CH48, // Space
// Multimedia keys
wind_key_MediaPrev,
wind_key_MediaNext,
wind_key_MediaMute,
wind_key_MediaCalc,
wind_key_MediaPlay,
wind_key_MediaStop,
wind_key_MediaVolDown,
wind_key_MediaVolUp,
// WWW keys
wind_key_WWWHome,
wind_key_WWWSearch,
wind_key_WWWFavorites,
wind_key_WWWRefresh,
wind_key_WWWStop,
wind_key_WWWForward,
wind_key_WWWBack,
wind_key_WWWMyComputer,
wind_key_WWWEmail,
wind_key_WWWSelect,
// Power keys
wind_key_ACPIPower,
wind_key_ACPISleep,
wind_key_ACPIWake,
// Unknown key
wind_key_Unknown,
};
struct wind_key_event_request
{
int window;
int pressed;
char letter;
char key;
enum wind_keycode code;
int modifiers;
};
#define WIND_SOCKET_PATH "/tmp/wind.sock"
int wind_connect(wind_client_t* client, const char* path, int blocking);
int wind_send_request(wind_client_t* client, uint8_t request_id, const void* request, size_t request_size);
int wind_read_response(wind_client_t* client, uint8_t response_id, void* response, size_t response_size);
int wind_wait_for_sync_reply(wind_client_t* client, uint8_t response_id, void* response, size_t response_size);
int wind_client_fd(wind_client_t* client);
int wind_disconnect(wind_client_t* client);
void wind_set_handler(wind_client_t* client, int(*handler)(wind_client_t*, uint8_t, void*), void* arg);
int wind_check_for_messages(wind_client_t* client);
int wind_create_window(wind_client_t* client, wind_window_t* window, struct wind_rect rect);
int wind_close_window(wind_client_t* client, wind_window_t* window);
int wind_invalidate(wind_client_t* client, wind_window_t* window);
int wind_set_window_title(wind_client_t* client, wind_window_t* window, const char* title);
int wind_set_titlebar_height(wind_client_t* client, wind_window_t* window, int height);
int wind_get_screen_dimensions(wind_client_t* client, struct wind_rect* rect);
int wind_set_special_window_attributes(wind_client_t* client, wind_window_t* window, enum wind_window_attributes attributes);
#endif