346 lines
8.7 KiB
C
346 lines
8.7 KiB
C
#include "../ax.h"
|
|
#include "../backend.h"
|
|
#include "../concurrent/msg.h"
|
|
#include "../concurrent/msgq.h"
|
|
#include "../ctxt.h"
|
|
#include "../util.h"
|
|
#include "theme.h"
|
|
#include "window.h"
|
|
#include <unistd.h>
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* API functions :: init / cleanup
|
|
* -------------------------------------------------------------------------- */
|
|
|
|
struct ax_ctxt* ax_new(void)
|
|
{
|
|
// "bootstrap" a new region
|
|
struct rgn rgn0;
|
|
rgn_init(&rgn0, SMALL);
|
|
struct rgn* rgn = rmemdup_typed(&rgn0, struct rgn, &rgn0, 1);
|
|
// use it to make a new context
|
|
struct ax_ctxt* ax = ralloc_typed(rgn, struct ax_ctxt, 1);
|
|
ax__ctxt_init(ax, rgn);
|
|
return ax;
|
|
}
|
|
|
|
void ax_free(struct ax_ctxt* ax)
|
|
{
|
|
if (ax != NULL) {
|
|
ax__ctxt_cleanup(ax);
|
|
rgn_cleanup(ax->init_rgn);
|
|
}
|
|
}
|
|
|
|
const char* ax_get_error(struct ax_ctxt* ax)
|
|
{
|
|
return ax->err;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Internal 'ax_ctxt' operations
|
|
* -------------------------------------------------------------------------- */
|
|
|
|
static void spawn_backend(
|
|
struct rgn* init_rgn,
|
|
pthread_t* out_thid,
|
|
struct ax_backend** out_backend);
|
|
|
|
void ax__ctxt_init(struct ax_ctxt* ax, struct rgn* init_rgn)
|
|
{
|
|
/* init simple stuff */
|
|
|
|
ax->init_rgn = init_rgn;
|
|
|
|
ax->log_out = NULL;
|
|
ax->log_auto_close = false;
|
|
|
|
ax->err_rgn = rgn_new(init_rgn, SMALL);
|
|
ax->err = "";
|
|
|
|
ax->thmb_rgn = rgn_new(init_rgn, THEME_BUILDER_DESIRED_REGION_SIZE);
|
|
ax->thmb = NULL;
|
|
ax->sel_thm = NULL;
|
|
|
|
ax->winb_rgn = rgn_new(init_rgn, WINDOW_BUILDER_DESIRED_REGION_SIZE);
|
|
ax->winb = NULL;
|
|
ax->sel_win = NULL;
|
|
|
|
ax->bac = NULL;
|
|
spawn_backend(init_rgn, &ax->bac_thid, &ax->bac);
|
|
ASSERT(ax->bac != NULL, "backend wasn't initialized");
|
|
}
|
|
|
|
static void shutdown(struct msgq* mq, pthread_t thid);
|
|
|
|
void ax__ctxt_cleanup(struct ax_ctxt* ax)
|
|
{
|
|
shutdown(ax__backend_msgq(ax->bac), ax->bac_thid);
|
|
|
|
if (ax->log_auto_close) {
|
|
fclose(ax->log_out);
|
|
}
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* API functions :: logging
|
|
* -------------------------------------------------------------------------- */
|
|
|
|
void ax_set_logger(struct ax_ctxt* ax, int fd, bool auto_close)
|
|
{
|
|
if (ax->log_auto_close) {
|
|
fclose(ax->log_out);
|
|
}
|
|
ax->log_out = fdopen(fd, "w");
|
|
ax->log_auto_close = auto_close;
|
|
}
|
|
|
|
void ax_log(struct ax_ctxt* ax, const char* string)
|
|
{
|
|
if (ax->log_out != NULL) {
|
|
fprintf(ax->log_out, "[LOG] %s\n", string);
|
|
}
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* API functions :: themes
|
|
* -------------------------------------------------------------------------- */
|
|
|
|
void ax_begin_theme(struct ax_ctxt* ax)
|
|
{
|
|
rgn_clear(ax->thmb_rgn);
|
|
ax->thmb = ax__theme_builder_new(ax->thmb_rgn);
|
|
}
|
|
|
|
struct ax_theme* ax_end_theme(struct ax_ctxt* ax)
|
|
{
|
|
if (ax->thmb == NULL) {
|
|
ax_log(ax, "`ax_end_theme' called while not building a theme");
|
|
return NULL;
|
|
}
|
|
struct ax_theme* thm = ax__theme_builder_finish(
|
|
ax->thmb,
|
|
ax->init_rgn,
|
|
ax__backend_msgq(ax->bac));
|
|
ax->thmb = NULL;
|
|
rgn_clear(ax->thmb_rgn);
|
|
return thm;
|
|
}
|
|
|
|
void ax_set_theme_color(
|
|
struct ax_ctxt* ax, const char* cat_name, int64_t rgb)
|
|
{
|
|
if (ax->thmb == NULL) {
|
|
ax_log(ax, "`ax_set_theme_color' called while not building a theme");
|
|
return;
|
|
}
|
|
enum ax_color_cat cat;
|
|
if (ax__string_to_color_cat(cat_name, &cat) != 0) {
|
|
ax->thmb->err =
|
|
rsprintf(ax->thmb_rgn, "invalid color category `%s'", cat_name);
|
|
} else {
|
|
ax__theme_set_color(ax->thmb, cat, rgb);
|
|
}
|
|
}
|
|
|
|
void ax_set_theme_font(
|
|
struct ax_ctxt* ax, const char* cat_name, const char* fnt_path, size_t fnt_size)
|
|
{
|
|
if (ax->thmb == NULL) {
|
|
ax_log(ax, "`ax_set_theme_font' called while not building a theme");
|
|
return;
|
|
}
|
|
enum ax_font_cat cat;
|
|
if (ax__string_to_font_cat(cat_name, &cat) != 0) {
|
|
ax->thmb->err =
|
|
rsprintf(ax->thmb_rgn, "invalid font category `%s'", cat_name);
|
|
} else {
|
|
ax__theme_set_font(ax->thmb, cat, fnt_path, fnt_size);
|
|
}
|
|
}
|
|
|
|
void ax_set_theme_iconset(
|
|
struct ax_ctxt* ax, const char* iconset_dir)
|
|
{
|
|
if (ax->thmb == NULL) {
|
|
ax_log(ax, "`ax_set_theme_iconset' called while not building a theme");
|
|
return;
|
|
}
|
|
UNIMPLEMENTED();
|
|
}
|
|
|
|
void ax_theme_wait_until_loaded(struct ax_ctxt* ax, struct ax_theme* thm)
|
|
{
|
|
struct rgn rgn[1];
|
|
rgn_init(rgn, SMALL);
|
|
struct msgq* mq = msgq_new(rgn);
|
|
ax__theme_on_load(thm, mq);
|
|
msgq_begin_recv_and_wait(mq);
|
|
MSGQ_RECV_ALL(mq) {
|
|
default:
|
|
break;
|
|
}
|
|
msgq_end_recv(mq);
|
|
rgn_cleanup(rgn);
|
|
}
|
|
|
|
int ax_select_theme(struct ax_ctxt* ax, struct ax_theme* thm)
|
|
{
|
|
ASSERT_NON_NULL(thm, "theme");
|
|
if (thm->err != NULL) {
|
|
ax->err = thm->err;
|
|
return AX_ERR_THEME_INVALID;
|
|
}
|
|
if (!ax__theme_is_loaded(thm)) {
|
|
ax->err = "theme hasn't finished loading";
|
|
return AX_ERR_THEME_LOADING;
|
|
}
|
|
ax->sel_thm = thm;
|
|
ax_log(ax, "theme selected");
|
|
return 0;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* API functions :: window
|
|
* -------------------------------------------------------------------------- */
|
|
|
|
void ax_begin_window(struct ax_ctxt* ax)
|
|
{
|
|
rgn_clear(ax->winb_rgn);
|
|
ax->winb = ax__window_builder_new(ax->winb_rgn);
|
|
}
|
|
|
|
void ax_set_window_title(
|
|
struct ax_ctxt* ax, const char* text)
|
|
{
|
|
if (ax->winb == NULL) {
|
|
ax_log(ax, "`ax_set_window_title' called while not building a window");
|
|
return;
|
|
}
|
|
ax__window_set_title(ax->winb, text);
|
|
}
|
|
|
|
void ax_set_window_size(
|
|
struct ax_ctxt* ax, uint64_t w, uint64_t h, bool resizable)
|
|
{
|
|
if (ax->winb == NULL) {
|
|
ax_log(ax, "`ax_set_window_size' called while not building a window");
|
|
return;
|
|
}
|
|
ax__window_set_size(ax->winb, w, h);
|
|
ax__window_set_flag(ax->winb, AX_WIN_RESIZABLE, resizable);
|
|
}
|
|
|
|
struct ax_window* ax_end_window(struct ax_ctxt* ax)
|
|
{
|
|
if (ax->winb == NULL) {
|
|
ax_log(ax, "`ax_end_window' called while not building a window");
|
|
return NULL;
|
|
}
|
|
struct ax_window* win =
|
|
ax__window_builder_finish(
|
|
ax->winb,
|
|
ax->init_rgn,
|
|
ax__backend_msgq(ax->bac));
|
|
rgn_clear(ax->winb_rgn);
|
|
ax->winb = NULL;
|
|
return win;
|
|
}
|
|
|
|
int ax_select_window(struct ax_ctxt* ax, struct ax_window* win)
|
|
{
|
|
ASSERT_NON_NULL(win, "window");
|
|
ax->sel_win = win;
|
|
ax_log(ax, "window selected");
|
|
return 0;
|
|
}
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Backend worker thread
|
|
* -------------------------------------------------------------------------- */
|
|
|
|
struct backend_init_ctxt {
|
|
// "input"
|
|
struct rgn* rgn;
|
|
pthread_mutex_t mx;
|
|
pthread_cond_t cv;
|
|
|
|
// "output"
|
|
struct ax_backend* backend;
|
|
const char* err;
|
|
};
|
|
|
|
static void* backend_worker(void* arg);
|
|
|
|
static void spawn_backend(
|
|
struct rgn* init_rgn,
|
|
pthread_t* out_thid,
|
|
struct ax_backend** out_backend)
|
|
{
|
|
pthread_t thid;
|
|
struct backend_init_ctxt init;
|
|
init.rgn = init_rgn;
|
|
init.err = NULL;
|
|
pthread_mutex_init(&init.mx, NULL);
|
|
pthread_cond_init(&init.cv, NULL);
|
|
pthread_mutex_lock(&init.mx);
|
|
pthread_create(&thid, NULL, backend_worker, &init);
|
|
pthread_cond_wait(&init.cv, &init.mx);
|
|
pthread_mutex_unlock(&init.mx);
|
|
pthread_cond_destroy(&init.cv);
|
|
pthread_mutex_destroy(&init.mx);
|
|
|
|
if (init.err != NULL) {
|
|
ASSERT(0, "failed to initialize `%s' backend: %s",
|
|
ax__backend_impl_name, init.err);
|
|
}
|
|
if (out_thid != NULL) {
|
|
*out_thid = thid;
|
|
}
|
|
if (out_backend != NULL) {
|
|
*out_backend = init.backend;
|
|
}
|
|
}
|
|
|
|
static void* backend_worker(void* arg)
|
|
{
|
|
struct backend_init_ctxt* init = arg;
|
|
|
|
struct rgn bac_rgn[1];
|
|
rgn_init(bac_rgn, ax__backend_desired_region_size);
|
|
|
|
struct ax_backend* bac;
|
|
const char* err = NULL;
|
|
int rv = ax__backend_new(bac_rgn, &bac, &err);
|
|
|
|
pthread_mutex_lock(&init->mx);
|
|
init->backend = bac;
|
|
if (rv != 0) {
|
|
init->err = rstrdup(init->rgn, err);
|
|
}
|
|
pthread_cond_signal(&init->cv);
|
|
pthread_mutex_unlock(&init->mx);
|
|
|
|
if (rv != 0) {
|
|
goto cleanup;
|
|
}
|
|
|
|
// `bac` is succesfully initialized here!
|
|
|
|
while (!ax__backend_is_shutdown(bac)) {
|
|
ax__backend_wait_for_event(bac);
|
|
ax__backend_step(bac);
|
|
}
|
|
|
|
cleanup:
|
|
rgn_cleanup(bac_rgn);
|
|
return NULL;
|
|
}
|
|
|
|
static void shutdown(struct msgq* mq, pthread_t thid)
|
|
{
|
|
(void) msgq_begin_send_typed(mq, ax_msg_shutdown);
|
|
msgq_end_send(mq);
|
|
pthread_join(thid, NULL);
|
|
}
|