diff --git a/cagebreak.c b/cagebreak.c index 300ac22..9caaa34 100644 --- a/cagebreak.c +++ b/cagebreak.c @@ -307,6 +307,8 @@ main(int argc, char *argv[]) { } server.nws = 1; + server.views_curr_id=0; + server.tiles_curr_id=0; server.message_config.fg_color[0] = 0.0; server.message_config.fg_color[1] = 0.0; server.message_config.fg_color[2] = 0.0; @@ -365,6 +367,12 @@ main(int argc, char *argv[]) { goto end; } + if(ipc_init(&server) != 0) { + wlr_log(WLR_ERROR, "Failed to initialize IPC"); + ret = 1; + goto end; + } + compositor = wlr_compositor_create(server.wl_display, renderer); if(!compositor) { wlr_log(WLR_ERROR, "Unable to create the wlroots compositor"); @@ -557,12 +565,6 @@ main(int argc, char *argv[]) { exit(0); } - if(ipc_init(&server) != 0) { - wlr_log(WLR_ERROR, "Failed to initialize IPC"); - ret = 1; - goto end; - } - { // config_file should only be visible as long as it is valid char *config_file = get_config_file(); if(config_file == NULL) { @@ -642,6 +644,7 @@ end: wlr_output_layout_destroy(server.output_layout); free(server.input); + free(server.message_config.font); pango_cairo_font_map_set_default(NULL); cairo_debug_reset_static_data(); FcFini(); diff --git a/ipc_server.c b/ipc_server.c index 7301867..168df16 100644 --- a/ipc_server.c +++ b/ipc_server.c @@ -13,6 +13,7 @@ #include "message.h" #include "parse.h" #include "server.h" +#include "util.h" #include #include @@ -22,6 +23,10 @@ #include #include +static const char ipc_magic[] = {'c', 'g', '-', 'i', 'p', 'c'}; + +#define IPC_HEADER_SIZE (sizeof(ipc_magic) + 4) + static void handle_display_destroy(struct wl_listener *listener, void *data) { struct cg_ipc_handle *ipc = wl_container_of(listener, ipc, display_destroy); @@ -109,6 +114,44 @@ ipc_init(struct cg_server *server) { return 0; } +int ipc_client_handle_writable(int client_fd, uint32_t mask, void *data) { + struct cg_ipc_client *client = data; + + if (mask & WL_EVENT_ERROR) { + ipc_client_disconnect(client); + return 0; + } + + if (mask & WL_EVENT_HANGUP) { + ipc_client_disconnect(client); + return 0; + } + + if (client->write_buffer_len <= 0) { + return 0; + } + + ssize_t written = write(client->fd, client->write_buffer, client->write_buffer_len); + + if (written == -1 && errno == EAGAIN) { + return 0; + } else if (written == -1) { + wlr_log(WLR_ERROR, "Unable to send data from queue to IPC client"); + ipc_client_disconnect(client); + return 0; + } + + memmove(client->write_buffer, client->write_buffer + written, client->write_buffer_len - written); + client->write_buffer_len -= written; + + if (client->write_buffer_len == 0 && client->writable_event_source) { + wl_event_source_remove(client->writable_event_source); + client->writable_event_source = NULL; + } + + return 0; +} + int ipc_handle_connection(int fd, uint32_t mask, void *data) { (void)fd; @@ -154,7 +197,7 @@ ipc_handle_connection(int fd, uint32_t mask, void *data) { client->event_source = wl_event_loop_add_fd(server->event_loop, client_fd, WL_EVENT_READABLE, ipc_client_handle_readable, client); - client->writable_event_source = NULL; + client->writable_event_source = wl_event_loop_add_fd(server->event_loop, client_fd, WL_EVENT_WRITABLE, ipc_client_handle_writable, client); client->write_buffer_size = 128; client->write_buffer_len = 0; @@ -275,3 +318,59 @@ ipc_client_handle_command(struct cg_ipc_client *client) { } client->read_buf_len -= offset; } + +void ipc_send_event_client(struct cg_ipc_client *client, const char *payload, uint32_t payload_length) { + char data[IPC_HEADER_SIZE]; + + memcpy(data, ipc_magic, sizeof(ipc_magic)); + memcpy(data + sizeof(ipc_magic), &payload_length, sizeof(payload_length)); + + while (client->write_buffer_len + IPC_HEADER_SIZE + payload_length >= + client->write_buffer_size) { + client->write_buffer_size *= 2; + } + + if (client->write_buffer_size > 4e6) { // 4 MB + wlr_log(WLR_ERROR, "Client write buffer too big (%zu), disconnecting client", + client->write_buffer_size); + ipc_client_disconnect(client); + return; + } + + char *new_buffer = realloc(client->write_buffer, client->write_buffer_size); + if (!new_buffer) { + wlr_log(WLR_ERROR, "Unable to reallocate ipc client write buffer"); + ipc_client_disconnect(client); + return; + } + client->write_buffer = new_buffer; + + memcpy(client->write_buffer + client->write_buffer_len, data, IPC_HEADER_SIZE); + client->write_buffer_len += IPC_HEADER_SIZE; + memcpy(client->write_buffer + client->write_buffer_len, payload, payload_length); + client->write_buffer_len += payload_length; +} + +void ipc_send_event(struct cg_server *server, const char *fmt, ...) { + if(wl_list_empty(&server->ipc.client_list)) { + return; + } + va_list args; + va_start(args, fmt); + char *msg = malloc_vsprintf_va_list(fmt, args); + if(msg == NULL) { + wlr_log(WLR_ERROR, "Unable to allocate memory for ipc event"); + va_end(args); + return; + } + va_end(args); + struct cg_ipc_client *it,*tmp; + uint32_t len=strlen(msg); + wl_list_for_each_safe(it,tmp,&server->ipc.client_list,link) { + if(it->writable_event_source==NULL) { + it->writable_event_source = wl_event_loop_add_fd(server->event_loop, it->fd, WL_EVENT_WRITABLE, ipc_client_handle_writable, it); + } + ipc_send_event_client(it, msg, len); + } + free(msg); +} diff --git a/ipc_server.h b/ipc_server.h index 60339df..61ba631 100644 --- a/ipc_server.h +++ b/ipc_server.h @@ -33,6 +33,8 @@ struct cg_ipc_handle { struct sockaddr_un *sockaddr; }; +void +ipc_send_event(struct cg_server *server, const char *fmt, ...); int ipc_init(struct cg_server *server); int diff --git a/keybinding.c b/keybinding.c index 8635b35..870c33a 100644 --- a/keybinding.c +++ b/keybinding.c @@ -212,6 +212,7 @@ swap_tile(struct cg_tile *tile, } seat_set_focus(server->seat, swap_tile->view); + ipc_send_event(tile->workspace->output->server,"swap_tile(tile_id:%d,swap_tile_id:%d,workspace:%d,output:%s)",tile->id,swap_tile->id,tile->workspace->num+1,tile->workspace->output->wlr_output->name); } void @@ -354,7 +355,7 @@ resize(struct cg_tile *tile, const struct cg_tile *parent, int coord_offset, } } - int old_x = tile->tile.x, old_y = tile->tile.y; + int old_x = tile->tile.x, old_y = tile->tile.y, old_height=tile->tile.height, old_width=tile->tile.width; *get_coord(tile) += coord_offset; *get_dim(tile) += dim_offset; struct wlr_box damage_box = { @@ -366,6 +367,7 @@ resize(struct cg_tile *tile, const struct cg_tile *parent, int coord_offset, if(tile->view != NULL) { view_maximize(tile->view, tile); } + ipc_send_event(tile->workspace->output->server,"resize_tile(tile_id:%d,old_dims:[%d;%d;%d;%d],new_dims:[%d;%d;%d;%d],workspace:%d,output:%s)",tile->id,old_x,old_y,old_height,old_width,tile->tile.x,tile->tile.y,tile->tile.height,tile->tile.width,tile->workspace->num+1,tile->workspace->output->wlr_output->name); } int * @@ -473,7 +475,7 @@ keybinding_workspace_fullscreen(struct cg_server *server) { workspace_free_tiles(output->workspaces[output->curr_workspace]); if(full_screen_workspace_tiles( server->output_layout, output->wlr_output, - output->workspaces[output->curr_workspace]) != 0) { + output->workspaces[output->curr_workspace],&server->tiles_curr_id) != 0) { wlr_log(WLR_ERROR, "Failed to allocate space for fullscreen workspace"); return; } @@ -486,6 +488,7 @@ keybinding_workspace_fullscreen(struct cg_server *server) { } seat_set_focus(server->seat, current_view); + ipc_send_event(output->server,"fullscreen(tile_id:%d,workspace:%d,output:%s)",output->workspaces[output->curr_workspace]->focused_tile->id,output->workspaces[output->curr_workspace]->num+1,output->wlr_output->name); } // Switch to a differerent virtual terminal @@ -550,6 +553,8 @@ keybinding_split_output(struct cg_output *output, bool vertical) { wlr_log(WLR_ERROR, "Failed to allocate new tile for splitting"); return; } + new_tile->id=output->server->tiles_curr_id; + ++output->server->tiles_curr_id; new_tile->tile.x = new_x; new_tile->tile.y = new_y; new_tile->tile.width = x + width - new_x; @@ -572,6 +577,7 @@ keybinding_split_output(struct cg_output *output, bool vertical) { if(original_view != NULL) { view_maximize(original_view, curr_workspace->focused_tile); } + ipc_send_event(output->server,"split(tile_id:%d,new_tile_id:%d,workspace:%d,output:%s,vertical:%d)",curr_workspace->focused_tile->id,new_tile->id,curr_workspace->num+1,curr_workspace->output->wlr_output->name,vertical); } static void @@ -579,7 +585,12 @@ keybinding_close_view(struct cg_view *view) { if(view == NULL) { return; } + struct cg_output *outp=view->workspace->output; + uint32_t view_id=view->id; + uint32_t tile_id=view->id; + uint32_t ws=view->workspace->num; view->impl->close(view); + ipc_send_event(outp->server,"close(view_id:%d,tile_id:%d,workspace:%d,output:%s)",view_id,tile_id,ws+1,outp->wlr_output->name); } static void @@ -611,6 +622,7 @@ set_output(struct cg_server *server, struct cg_output *output) { void keybinding_cycle_outputs(struct cg_server *server, bool reverse) { struct cg_output *output = NULL; + struct cg_output *old_output=server->curr_output; if(reverse) { output = wl_container_of(server->curr_output->link.prev, server->curr_output, link); @@ -626,6 +638,7 @@ keybinding_cycle_outputs(struct cg_server *server, bool reverse) { } } set_output(server, output); + ipc_send_event(output->server,"cycle_outputs(old_output:%s,new_output:%s,reverse:%d)",old_output->wlr_output->name,output->wlr_output->name,reverse); } /* Cycle through views, whereby the workspace does not change */ @@ -669,12 +682,14 @@ keybinding_cycle_views(struct cg_server *server, bool reverse) { curr_workspace->focused_tile->view = wl_container_of( next_view->link.prev, curr_workspace->focused_tile->view, link); seat_set_focus(server->seat, next_view); + ipc_send_event(curr_workspace->output->server,"cycle_views(old_view_id:%d,new_view_id:%d,tile_id:%d,workspace:%d,output:%s)",current_view->id,next_view->id,next_view->tile->id,curr_workspace->num+1,curr_workspace->output->wlr_output->name); } void keybinding_cycle_tiles(struct cg_server *server, bool reverse) { struct cg_output *output = server->curr_output; struct cg_workspace *workspace = output->workspaces[output->curr_workspace]; + struct cg_tile *old_tile=workspace->focused_tile; if(reverse) { workspace_focus_tile(workspace, workspace->focused_tile->prev); } else { @@ -683,6 +698,7 @@ keybinding_cycle_tiles(struct cg_server *server, bool reverse) { struct cg_view *next_view = workspace->focused_tile->view; seat_set_focus(server->seat, next_view); + ipc_send_event(output->server,"cycle_tiles(old_tile_id:%d,new_tile_id:%d,workspace:%d,output:%s)",old_tile->id,workspace->focused_tile->id,workspace->num+1,output->wlr_output->name); } int @@ -694,11 +710,13 @@ keybinding_switch_ws(struct cg_server *server, uint32_t ws) { return -1; } struct cg_output *output = server->curr_output; + uint32_t old_ws=server->curr_output->curr_workspace; output->curr_workspace = ws; seat_set_focus(server->seat, server->curr_output->workspaces[ws]->focused_tile->view); wlr_output_damage_add_whole(output->damage); message_printf(server->curr_output, "Workspace %d", ws + 1); + ipc_send_event(output->server,"switch_ws(old_workspace:%d,new_workspace:%d,output:%s)",old_ws+1,ws+1,output->wlr_output->name); return 0; } @@ -733,6 +751,7 @@ keybinding_move_view_to_cycle_output(struct cg_server *server, bool reverse) { if(wl_list_length(&server->outputs) <= 1) { return; } + struct cg_output *old_outp=server->curr_output; struct cg_view *view = server->curr_output->workspaces[server->curr_output->curr_workspace] ->focused_tile->view; @@ -761,11 +780,13 @@ keybinding_move_view_to_cycle_output(struct cg_server *server, bool reverse) { view_maximize(view, view->tile); seat_set_focus(server->seat, view); } + ipc_send_event(server,"move_view_cycle_output(view_id:%d,old_output:%s,new_output:%s)",view->id,old_outp->wlr_output->name,server->curr_output->wlr_output->name); } void keybinding_set_nws(struct cg_server *server, int nws) { struct cg_output *output; + int old_nws=server->nws; wl_list_for_each(output, &server->outputs, link) { for(unsigned int i = nws; i < server->nws; ++i) { struct cg_view *view, *tmp; @@ -794,6 +815,7 @@ keybinding_set_nws(struct cg_server *server, int nws) { output->workspaces = new_workspaces; for(int i = server->nws; i < nws; ++i) { output->workspaces[i] = full_screen_workspace(output); + output->workspaces[i]->num=i; if(!output->workspaces[i]) { wlr_log(WLR_ERROR, "Failed to allocate additional workspaces"); return; @@ -812,6 +834,7 @@ keybinding_set_nws(struct cg_server *server, int nws) { server->seat, server->curr_output->workspaces[server->curr_output->curr_workspace] ->focused_tile->view); + ipc_send_event(server,"set_nws(old_nws:%d,new_nws:%d)",old_nws,server->nws); } void @@ -828,15 +851,18 @@ keybinding_definemode(struct cg_server *server, char *mode) { server->modes[length] = NULL; server->modes[length - 1] = strdup(mode); + ipc_send_event(server,"definemode(mode:%s)",mode); } void keybinding_definekey(struct cg_server *server, struct keybinding *kb) { keybinding_list_push(server->keybindings, kb); + ipc_send_event(server,"definekey(modifiers:%d,key:%d,command:%d)",kb->modifiers,kb->key,kb->action); } void keybinding_set_background(struct cg_server *server, float *bg) { + ipc_send_event(server,"background(old_bg:[%f,%f,%f],new_bg:[%f,%f,%f])",server->bg_color[0],server->bg_color[1],server->bg_color[2],bg[0],bg[1],bg[2]); server->bg_color[0] = bg[0]; server->bg_color[1] = bg[1]; server->bg_color[2] = bg[2]; @@ -844,11 +870,13 @@ keybinding_set_background(struct cg_server *server, float *bg) { void keybinding_switch_output(struct cg_server *server, int output) { + struct cg_output *old_outp=server->curr_output; struct cg_output *it; int count = 1; wl_list_for_each(it, &server->outputs, link) { if(count == output) { set_output(server, it); + ipc_send_event(server,"switch_output(old_output:%s,new_output:%s)",old_outp->wlr_output->name,it->wlr_output->name); return; } ++count; @@ -859,6 +887,7 @@ keybinding_switch_output(struct cg_server *server, int output) { void keybinding_move_view_to_output(struct cg_server *server, int output_num) { + struct cg_output *old_outp=server->curr_output; struct cg_view *view = server->curr_output->workspaces[server->curr_output->curr_workspace] ->focused_tile->view; @@ -883,10 +912,18 @@ keybinding_move_view_to_output(struct cg_server *server, int output_num) { view_maximize(view, ws->focused_tile); seat_set_focus(server->seat, view); } + char *view_title=""; + int view_id=-1; + if(view != NULL) { + view_title=view->impl->get_title(view); + view_id=view->id; + } + ipc_send_event(server,"move_view_to_output(view_id:%d,old_output:%s,new_output:%s,view_title:%s)",view_id,old_outp->wlr_output->name,server->curr_output->wlr_output->name,view_title); } void keybinding_move_view_to_workspace(struct cg_server *server, uint32_t ws) { + uint32_t old_ws=server->curr_output->curr_workspace; struct cg_view *view = server->curr_output->workspaces[server->curr_output->curr_workspace] ->focused_tile->view; @@ -911,6 +948,7 @@ keybinding_move_view_to_workspace(struct cg_server *server, uint32_t ws) { view_maximize(view, ws->focused_tile); seat_set_focus(server->seat, view); } + ipc_send_event(server,"move_view_to_ws(view_id:%d,old_workspace:%d,new_workspace:%d,output:%s,view_title:%s)",view->id,old_ws,ws,view->workspace->output->wlr_output->name,view->impl->get_title(view)); } void @@ -959,12 +997,14 @@ keybinding_configure_output(struct cg_server *server, wl_list_for_each_safe(output, tmp_output, &server->outputs, link) { if(strcmp(config->output_name, output->wlr_output->name) == 0) { output_configure(server, output); + ipc_send_event(output->server,"configure_output(output:%s)",cfg->output_name); return; } } wl_list_for_each_safe(output, tmp_output, &server->disabled_outputs, link) { if(strcmp(config->output_name, output->wlr_output->name) == 0) { output_configure(server, output); + ipc_send_event(output->server,"configure_output(output:%s)",cfg->output_name); return; } } @@ -974,6 +1014,7 @@ void keybinding_configure_input(struct cg_server *server, struct cg_input_config *cfg) { cg_input_apply_config(cfg, server); + ipc_send_event(server,"configure_input(input:%s)",cfg->identifier); } void @@ -998,6 +1039,7 @@ keybinding_configure_message(struct cg_server *server, server->message_config.fg_color[2] = config->fg_color[2]; server->message_config.fg_color[3] = config->fg_color[3]; } + ipc_send_event(server,"configure_message()"); } void @@ -1047,6 +1089,7 @@ run_action(enum keybinding_action action, struct cg_server *server, server->seat->mode = data.u; break; case KEYBINDING_SWITCH_DEFAULT_MODE: + ipc_send_event(server,"switch_default_mode(old_mode:%d,mode:%d)",server->seat->default_mode,data.u); server->seat->mode = data.u; server->seat->default_mode = data.u; break; diff --git a/output.c b/output.c index 6370b39..d5ddba0 100644 --- a/output.c +++ b/output.c @@ -8,7 +8,7 @@ * See the LICENSE file accompanying this file. */ -#define _POSIX_C_SOURCE 200112L +#define _POSIX_C_SOURCE 200809L #include "config.h" #include @@ -480,6 +480,7 @@ output_clear(struct cg_output *output) { static void output_destroy(struct cg_output *output) { struct cg_server *server = output->server; + char *outp_name=strdup(output->wlr_output->name); wl_list_remove(&output->destroy.link); wl_list_remove(&output->mode.link); @@ -499,6 +500,12 @@ output_destroy(struct cg_output *output) { free(output); + if(outp_name!=NULL) { + ipc_send_event(server,"new_output(output:%s)",outp_name); + free(outp_name); + } else { + wlr_log(WLR_ERROR, "Failed to allocate memory for output name in output_destroy"); + } if(wl_list_empty(&server->outputs)) { wl_display_terminate(server->wl_display); } @@ -684,6 +691,7 @@ handle_new_output(struct wl_listener *listener, void *data) { output->workspaces = malloc(server->nws * sizeof(struct cg_workspace *)); for(unsigned int i = 0; i < server->nws; ++i) { output->workspaces[i] = full_screen_workspace(output); + output->workspaces[i]->num=i; if(!output->workspaces[i]) { wlr_log(WLR_ERROR, "Failed to allocate workspaces for output"); return; @@ -710,6 +718,7 @@ handle_new_output(struct wl_listener *listener, void *data) { wl_signal_add(&output->damage->events.frame, &output->damage_frame); output->damage_destroy.notify = handle_output_damage_destroy; wl_signal_add(&output->damage->events.destroy, &output->damage_destroy); + ipc_send_event(server,"new_output(output:%s,priority:%d)",output->wlr_output->name,output->priority); } #if CG_HAS_FANALYZE #pragma GCC diagnostic pop diff --git a/server.h b/server.h index 22dfe32..610210d 100644 --- a/server.h +++ b/server.h @@ -54,6 +54,8 @@ struct cg_server { char **modes; uint16_t nws; float *bg_color; + uint32_t views_curr_id; + uint32_t tiles_curr_id; #ifdef DEBUG bool debug_damage_tracking; #endif diff --git a/view.c b/view.c index 09fc463..afa418b 100644 --- a/view.c +++ b/view.c @@ -23,6 +23,7 @@ #include "server.h" #include "view.h" #include "workspace.h" +#include "ipc_server.h" #if CG_HAS_XWAYLAND #include "xwayland.h" #endif @@ -249,6 +250,12 @@ view_for_each_popup(struct cg_view *view, wlr_surface_iterator_func_t iterator, void view_unmap(struct cg_view *view) { + + uint32_t id=view->id; + uint32_t tile_id=view->tile->id; + uint32_t ws=view->workspace->num; + char *output_name=view->workspace->output->wlr_output->name; + char *title=view->impl->get_title(view); /* If the view is not mapped, do nothing */ if(view->wlr_surface == NULL) { return; @@ -302,6 +309,7 @@ view_unmap(struct cg_view *view) { wl_list_remove(&view->new_subsurface.link); view->wlr_surface = NULL; + ipc_send_event(view->workspace->server,"view_unmap(view_id:%d,tile_id:%d,workspace:%d,output:%s,view_title:%s)",id,tile_id,ws+1,output_name,title); } void @@ -340,6 +348,7 @@ view_map(struct cg_view *view, struct wlr_surface *surface, wl_list_insert(&ws->views, &view->link); } seat_set_focus(output->server->seat, view); + ipc_send_event(output->server,"view_map(view_id:%d,tile_id:%d,workspace:%d,output:%s,view_title:%s)",view->id,view->tile->id,view->workspace->num+1,view->workspace->output->wlr_output->name,view->impl->get_title(view)); } void @@ -363,6 +372,8 @@ view_init(struct cg_view *view, enum cg_view_type type, view->server = server; view->type = type; view->impl = impl; + view->id=server->views_curr_id; + ++server->views_curr_id; wl_list_init(&view->children); } diff --git a/view.h b/view.h index 62a1f32..772728d 100644 --- a/view.h +++ b/view.h @@ -32,6 +32,7 @@ struct cg_view { const struct cg_view_impl *impl; struct wl_listener new_subsurface; + uint32_t id; }; struct cg_view_impl { diff --git a/workspace.c b/workspace.c index 1b701e8..5b68b63 100644 --- a/workspace.c +++ b/workspace.c @@ -26,7 +26,7 @@ int full_screen_workspace_tiles(struct wlr_output_layout *layout, struct wlr_output *output, - struct cg_workspace *workspace) { + struct cg_workspace *workspace, uint32_t *tiles_curr_id) { workspace->focused_tile = calloc(1, sizeof(struct cg_tile)); if(!workspace->focused_tile) { return -1; @@ -40,6 +40,8 @@ full_screen_workspace_tiles(struct wlr_output_layout *layout, workspace->focused_tile->tile.width = output_box->width; workspace->focused_tile->tile.height = output_box->height; workspace->focused_tile->view = NULL; + workspace->focused_tile->id = *tiles_curr_id; + ++(*tiles_curr_id); return 0; } #if CG_HAS_FANALYZE @@ -53,8 +55,9 @@ full_screen_workspace(struct cg_output *output) { return NULL; } workspace->server = output->server; + workspace->num=-1; if(full_screen_workspace_tiles(output->server->output_layout, - output->wlr_output, workspace) != 0) { + output->wlr_output, workspace, &output->server->tiles_curr_id) != 0) { free(workspace); return NULL; } diff --git a/workspace.h b/workspace.h index a5da674..51b190e 100644 --- a/workspace.h +++ b/workspace.h @@ -12,6 +12,7 @@ struct cg_tile { struct cg_view *view; struct cg_tile *next; struct cg_tile *prev; + uint32_t id; }; struct cg_workspace { @@ -21,6 +22,7 @@ struct cg_workspace { struct cg_output *output; struct cg_tile *focused_tile; + uint32_t num; }; struct cg_workspace * @@ -28,7 +30,7 @@ full_screen_workspace(struct cg_output *output); int full_screen_workspace_tiles(struct wlr_output_layout *layout, struct wlr_output *output, - struct cg_workspace *workspace); + struct cg_workspace *workspace, uint32_t *tiles_curr_id); void workspace_free_tiles(struct cg_workspace *workspace); void