Correctly handle _NET_WM_WINDOW_TYPE == _NET_WM_WINDOW_TYPE_DOCK (for dzen2 -dock)
This commit is contained in:
parent
a19072b714
commit
1a0817eb39
|
@ -63,6 +63,9 @@ struct Workspace {
|
||||||
|
|
||||||
Client *fullscreen_client;
|
Client *fullscreen_client;
|
||||||
|
|
||||||
|
/* Contains all clients with _NET_WM_WINDOW_TYPE == _NET_WM_WINDOW_TYPE_DOCK */
|
||||||
|
SLIST_HEAD(dock_clients_head, Client) dock_clients;
|
||||||
|
|
||||||
/* Backpointer to the screen this workspace is on */
|
/* Backpointer to the screen this workspace is on */
|
||||||
i3Screen *screen;
|
i3Screen *screen;
|
||||||
|
|
||||||
|
@ -125,6 +128,10 @@ struct Client {
|
||||||
/* x, y, width, height */
|
/* x, y, width, height */
|
||||||
Rect rect;
|
Rect rect;
|
||||||
|
|
||||||
|
/* Height which was determined by reading the _NET_WM_STRUT_PARTIAL top/bottom of the screen
|
||||||
|
reservation */
|
||||||
|
int desired_height;
|
||||||
|
|
||||||
/* Name */
|
/* Name */
|
||||||
char *name;
|
char *name;
|
||||||
int name_len;
|
int name_len;
|
||||||
|
@ -132,6 +139,12 @@ struct Client {
|
||||||
/* fullscreen is pretty obvious */
|
/* fullscreen is pretty obvious */
|
||||||
bool fullscreen;
|
bool fullscreen;
|
||||||
|
|
||||||
|
enum { TITLEBAR_TOP = 0, TITLEBAR_LEFT, TITLEBAR_RIGHT, TITLEBAR_BOTTOM, TITLEBAR_OFF } titlebar_position;
|
||||||
|
|
||||||
|
/* If a client is set as a dock, it is placed at the very bottom of the screen and its
|
||||||
|
requested size is used */
|
||||||
|
bool dock;
|
||||||
|
|
||||||
/* After leaving fullscreen mode, a client needs to be reconfigured (configuration =
|
/* After leaving fullscreen mode, a client needs to be reconfigured (configuration =
|
||||||
setting X, Y, width and height). By setting the force_reconfigure flag, render_layout()
|
setting X, Y, width and height). By setting the force_reconfigure flag, render_layout()
|
||||||
will reconfigure the client. */
|
will reconfigure the client. */
|
||||||
|
@ -148,6 +161,7 @@ struct Client {
|
||||||
|
|
||||||
/* The following entry provides the necessary list pointers to use Client with LIST_* macros */
|
/* The following entry provides the necessary list pointers to use Client with LIST_* macros */
|
||||||
CIRCLEQ_ENTRY(Client) clients;
|
CIRCLEQ_ENTRY(Client) clients;
|
||||||
|
SLIST_ENTRY(Client) dock_clients;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -20,5 +20,6 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
|
||||||
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop);
|
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop);
|
||||||
int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *event);
|
int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *event);
|
||||||
int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message_event_t *event);
|
int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message_event_t *event);
|
||||||
|
int window_type_handler(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -23,6 +23,6 @@ extern TAILQ_HEAD(bindings_head, Binding) bindings;
|
||||||
extern xcb_event_handlers_t evenths;
|
extern xcb_event_handlers_t evenths;
|
||||||
extern char *pattern;
|
extern char *pattern;
|
||||||
extern int num_screens;
|
extern int num_screens;
|
||||||
extern xcb_atom_t atoms[6];
|
extern xcb_atom_t atoms[9];
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -16,11 +16,15 @@
|
||||||
#define _NET_WM_STATE_TOGGLE 2
|
#define _NET_WM_STATE_TOGGLE 2
|
||||||
|
|
||||||
enum { _NET_SUPPORTED = 0,
|
enum { _NET_SUPPORTED = 0,
|
||||||
_NET_SUPPORTING_WM_CHECK = 1,
|
_NET_SUPPORTING_WM_CHECK,
|
||||||
_NET_WM_NAME = 2,
|
_NET_WM_NAME,
|
||||||
_NET_WM_STATE_FULLSCREEN = 3,
|
_NET_WM_STATE_FULLSCREEN,
|
||||||
_NET_WM_STATE = 4,
|
_NET_WM_STATE,
|
||||||
UTF8_STRING = 5
|
_NET_WM_WINDOW_TYPE,
|
||||||
|
_NET_WM_WINDOW_TYPE_DOCK,
|
||||||
|
_NET_WM_DESKTOP,
|
||||||
|
_NET_WM_STRUT_PARTIAL,
|
||||||
|
UTF8_STRING
|
||||||
};
|
};
|
||||||
|
|
||||||
uint32_t get_colorpixel(xcb_connection_t *conn, xcb_window_t window, char *hex);
|
uint32_t get_colorpixel(xcb_connection_t *conn, xcb_window_t window, char *hex);
|
||||||
|
|
|
@ -377,3 +377,10 @@ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int window_type_handler(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
|
||||||
|
xcb_atom_t atom, xcb_get_property_reply_t *property) {
|
||||||
|
/* TODO: Implement this one. To do this, implement a little test program which sleep(1)s
|
||||||
|
before changing this property. */
|
||||||
|
printf("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n");
|
||||||
|
}
|
||||||
|
|
125
src/layout.c
125
src/layout.c
|
@ -128,29 +128,11 @@ void decorate_window(xcb_connection_t *conn, Client *client) {
|
||||||
free(label);
|
free(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void render_container(xcb_connection_t *connection, Container *container) {
|
/*
|
||||||
Client *client;
|
* Pushes the client’s x and y coordinates to X11
|
||||||
i3Font *font = load_font(connection, pattern);
|
*
|
||||||
|
|
||||||
if (container->mode == MODE_DEFAULT) {
|
|
||||||
int num_clients = 0;
|
|
||||||
CIRCLEQ_FOREACH(client, &(container->clients), clients)
|
|
||||||
num_clients++;
|
|
||||||
printf("got %d clients in this default container.\n", num_clients);
|
|
||||||
|
|
||||||
int current_client = 0;
|
|
||||||
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
|
|
||||||
/* TODO: at the moment, every column/row is screen / num_cols. This
|
|
||||||
* needs to be changed to "percentage of the screen" by
|
|
||||||
* default and adjustable by the user if necessary.
|
|
||||||
*/
|
*/
|
||||||
|
static void reposition_client(xcb_connection_t *connection, Client *client) {
|
||||||
/* Check if we changed client->x or client->y by updating it…
|
|
||||||
* Note the bitwise OR instead of logical OR to force evaluation of both statements */
|
|
||||||
if (client->force_reconfigure |
|
|
||||||
(client->rect.x != (client->rect.x = container->x)) |
|
|
||||||
(client->rect.y != (client->rect.y = container->y +
|
|
||||||
(container->height / num_clients) * current_client))) {
|
|
||||||
printf("frame needs to be pushed to %dx%d\n", client->rect.x, client->rect.y);
|
printf("frame needs to be pushed to %dx%d\n", client->rect.x, client->rect.y);
|
||||||
/* Note: We can use a pointer to client->x like an array of uint32_ts
|
/* Note: We can use a pointer to client->x like an array of uint32_ts
|
||||||
because it is followed by client->y by definition */
|
because it is followed by client->y by definition */
|
||||||
|
@ -158,10 +140,13 @@ static void render_container(xcb_connection_t *connection, Container *container)
|
||||||
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
|
XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: vertical default layout */
|
/*
|
||||||
if (client->force_reconfigure |
|
* Pushes the client’s width/height to X11 and resizes the child window
|
||||||
(client->rect.width != (client->rect.width = container->width)) |
|
*
|
||||||
(client->rect.height != (client->rect.height = container->height / num_clients))) {
|
*/
|
||||||
|
static void resize_client(xcb_connection_t *connection, Client *client) {
|
||||||
|
i3Font *font = load_font(connection, pattern);
|
||||||
|
|
||||||
printf("resizing client to %d x %d\n", client->rect.width, client->rect.height);
|
printf("resizing client to %d x %d\n", client->rect.width, client->rect.height);
|
||||||
xcb_configure_window(connection, client->frame,
|
xcb_configure_window(connection, client->frame,
|
||||||
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
|
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
|
||||||
|
@ -174,15 +159,61 @@ static void render_container(xcb_connection_t *connection, Container *container)
|
||||||
XCB_CONFIG_WINDOW_Y |
|
XCB_CONFIG_WINDOW_Y |
|
||||||
XCB_CONFIG_WINDOW_WIDTH |
|
XCB_CONFIG_WINDOW_WIDTH |
|
||||||
XCB_CONFIG_WINDOW_HEIGHT;
|
XCB_CONFIG_WINDOW_HEIGHT;
|
||||||
uint32_t values[4] = {2, /* x */
|
Rect rect;
|
||||||
font->height + 2 + 2, /* y */
|
if (client->titlebar_position == TITLEBAR_OFF) {
|
||||||
client->rect.width - (2 + 2), /* width */
|
rect.x = 0;
|
||||||
client->rect.height - ((font->height + 2 + 2) + 2)}; /* height */
|
rect.y = 0;
|
||||||
|
rect.width = client->rect.width;
|
||||||
|
rect.height = client->rect.height;
|
||||||
|
} else {
|
||||||
|
rect.x = 2;
|
||||||
|
rect.y = font->height + 2 + 2;
|
||||||
|
rect.width = client->rect.width - (2 + 2);
|
||||||
|
rect.height = client->rect.height - ((font->height + 2 + 2) + 2);
|
||||||
|
}
|
||||||
|
|
||||||
printf("child will be at %dx%d with size %dx%d\n",
|
printf("child will be at %dx%d with size %dx%d\n", rect.x, rect.y, rect.width, rect.height);
|
||||||
values[0], values[1], values[2], values[3]);
|
|
||||||
|
|
||||||
xcb_configure_window(connection, client->child, mask, values);
|
xcb_configure_window(connection, client->child, mask, &(rect.x));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void render_container(xcb_connection_t *connection, Container *container) {
|
||||||
|
Client *client;
|
||||||
|
|
||||||
|
if (container->mode == MODE_DEFAULT) {
|
||||||
|
int num_clients = 0;
|
||||||
|
CIRCLEQ_FOREACH(client, &(container->clients), clients)
|
||||||
|
if (!client->dock)
|
||||||
|
num_clients++;
|
||||||
|
printf("got %d clients in this default container.\n", num_clients);
|
||||||
|
|
||||||
|
int current_client = 0;
|
||||||
|
CIRCLEQ_FOREACH(client, &(container->clients), clients) {
|
||||||
|
/* TODO: currently, clients are assigned to the current container.
|
||||||
|
Therefore, we need to skip them here. Does anything harmful happen
|
||||||
|
if clients *do not* have a container. Is this the more desired
|
||||||
|
situation? Let’s find out… */
|
||||||
|
if (client->dock)
|
||||||
|
continue;
|
||||||
|
/* TODO: at the moment, every column/row is screen / num_cols. This
|
||||||
|
* needs to be changed to "percentage of the screen" by
|
||||||
|
* default and adjustable by the user if necessary.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Check if we changed client->x or client->y by updating it…
|
||||||
|
* Note the bitwise OR instead of logical OR to force evaluation of both statements */
|
||||||
|
if (client->force_reconfigure |
|
||||||
|
(client->rect.x != (client->rect.x = container->x)) |
|
||||||
|
(client->rect.y != (client->rect.y = container->y +
|
||||||
|
(container->height / num_clients) * current_client))) {
|
||||||
|
reposition_client(connection, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO: vertical default layout */
|
||||||
|
if (client->force_reconfigure |
|
||||||
|
(client->rect.width != (client->rect.width = container->width)) |
|
||||||
|
(client->rect.height != (client->rect.height = container->height / num_clients))) {
|
||||||
|
resize_client(connection, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client->force_reconfigure)
|
if (client->force_reconfigure)
|
||||||
|
@ -195,6 +226,24 @@ static void render_container(xcb_connection_t *connection, Container *container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void render_bars(xcb_connection_t *connection, Workspace *r_ws, int width, int height) {
|
||||||
|
Client *client;
|
||||||
|
SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients) {
|
||||||
|
if (client->force_reconfigure |
|
||||||
|
(client->rect.x != (client->rect.x = 0)) |
|
||||||
|
(client->rect.y != (client->rect.y = height)))
|
||||||
|
reposition_client(connection, client);
|
||||||
|
|
||||||
|
if (client->force_reconfigure |
|
||||||
|
(client->rect.width != (client->rect.width = width)) |
|
||||||
|
(client->rect.height != (client->rect.height = client->desired_height)))
|
||||||
|
resize_client(connection, client);
|
||||||
|
|
||||||
|
client->force_reconfigure = false;
|
||||||
|
height += client->desired_height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void render_layout(xcb_connection_t *connection) {
|
void render_layout(xcb_connection_t *connection) {
|
||||||
i3Screen *screen;
|
i3Screen *screen;
|
||||||
|
|
||||||
|
@ -206,11 +255,19 @@ void render_layout(xcb_connection_t *connection) {
|
||||||
if (r_ws->fullscreen_client != NULL)
|
if (r_ws->fullscreen_client != NULL)
|
||||||
/* This is easy: A client has entered fullscreen mode, so we don’t render at all */
|
/* This is easy: A client has entered fullscreen mode, so we don’t render at all */
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int width = r_ws->rect.width;
|
int width = r_ws->rect.width;
|
||||||
int height = r_ws->rect.height;
|
int height = r_ws->rect.height;
|
||||||
int x = r_ws->rect.x;
|
int x = r_ws->rect.x;
|
||||||
int y = r_ws->rect.y;
|
int y = r_ws->rect.y;
|
||||||
|
|
||||||
|
Client *client;
|
||||||
|
SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients) {
|
||||||
|
printf("got dock client: %p\n", client);
|
||||||
|
printf("it wants to be this height: %d\n", client->desired_height);
|
||||||
|
height -= client->desired_height;
|
||||||
|
}
|
||||||
|
|
||||||
printf("got %d rows and %d cols\n", r_ws->rows, r_ws->cols);
|
printf("got %d rows and %d cols\n", r_ws->rows, r_ws->cols);
|
||||||
printf("each of them therefore is %d px width and %d px height\n",
|
printf("each of them therefore is %d px width and %d px height\n",
|
||||||
width / r_ws->cols, height / r_ws->rows);
|
width / r_ws->cols, height / r_ws->rows);
|
||||||
|
@ -240,6 +297,8 @@ void render_layout(xcb_connection_t *connection) {
|
||||||
|
|
||||||
x += container->width;
|
x += container->width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render_bars(connection, r_ws, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_flush(connection);
|
xcb_flush(connection);
|
||||||
|
|
45
src/mainx.c
45
src/mainx.c
|
@ -50,7 +50,7 @@ TAILQ_HEAD(bindings_head, Binding) bindings = TAILQ_HEAD_INITIALIZER(bindings);
|
||||||
xcb_event_handlers_t evenths;
|
xcb_event_handlers_t evenths;
|
||||||
|
|
||||||
xcb_window_t root_win;
|
xcb_window_t root_win;
|
||||||
xcb_atom_t atoms[6];
|
xcb_atom_t atoms[9];
|
||||||
|
|
||||||
|
|
||||||
char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso8859-1";
|
char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso8859-1";
|
||||||
|
@ -122,6 +122,12 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
||||||
xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
|
xcb_visualid_t visual, xcb_window_t root, uint8_t depth,
|
||||||
int16_t x, int16_t y, uint16_t width, uint16_t height) {
|
int16_t x, int16_t y, uint16_t width, uint16_t height) {
|
||||||
|
|
||||||
|
xcb_get_property_cookie_t wm_type_cookie, strut_cookie;
|
||||||
|
|
||||||
|
/* Place requests for propertys ASAP */
|
||||||
|
wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
|
||||||
|
strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
|
||||||
|
|
||||||
Client *new = table_get(byChild, child);
|
Client *new = table_get(byChild, child);
|
||||||
if (new == NULL) {
|
if (new == NULL) {
|
||||||
/* TODO: When does this happen for existing clients? Is that a bug? */
|
/* TODO: When does this happen for existing clients? Is that a bug? */
|
||||||
|
@ -206,6 +212,32 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
|
||||||
/* Focus the new window */
|
/* Focus the new window */
|
||||||
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
|
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
|
||||||
|
|
||||||
|
/* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */
|
||||||
|
xcb_atom_t *atom;
|
||||||
|
xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
|
||||||
|
if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) {
|
||||||
|
for (int i = 0; i < xcb_get_property_value_length(preply); i++)
|
||||||
|
if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) {
|
||||||
|
printf("Window is a dock.\n");
|
||||||
|
new->dock = true;
|
||||||
|
new->titlebar_position = TITLEBAR_OFF;
|
||||||
|
new->force_reconfigure = true;
|
||||||
|
SLIST_INSERT_HEAD(&(new->container->workspace->dock_clients), new, dock_clients);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */
|
||||||
|
uint32_t *strut;
|
||||||
|
preply = xcb_get_property_reply(conn, strut_cookie, NULL);
|
||||||
|
if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
|
||||||
|
/* We only use a subset of the provided values, namely the reserved space at the top/bottom
|
||||||
|
of the screen. This is because the only possibility for bars is at to be at the top/bottom
|
||||||
|
with maximum horizontal size.
|
||||||
|
TODO: bars at the top */
|
||||||
|
new->desired_height = strut[3];
|
||||||
|
printf("the client wants to be %d pixels height\n", new->desired_height);
|
||||||
|
}
|
||||||
|
|
||||||
render_layout(conn);
|
render_layout(conn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,10 +360,17 @@ int main(int argc, char *argv[], char *env[]) {
|
||||||
GET_ATOM(_NET_WM_STATE_FULLSCREEN);
|
GET_ATOM(_NET_WM_STATE_FULLSCREEN);
|
||||||
GET_ATOM(_NET_SUPPORTING_WM_CHECK);
|
GET_ATOM(_NET_SUPPORTING_WM_CHECK);
|
||||||
GET_ATOM(_NET_WM_NAME);
|
GET_ATOM(_NET_WM_NAME);
|
||||||
GET_ATOM(UTF8_STRING);
|
|
||||||
GET_ATOM(_NET_WM_STATE);
|
GET_ATOM(_NET_WM_STATE);
|
||||||
|
GET_ATOM(_NET_WM_WINDOW_TYPE);
|
||||||
|
GET_ATOM(_NET_WM_DESKTOP);
|
||||||
|
GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK);
|
||||||
|
GET_ATOM(_NET_WM_STRUT_PARTIAL);
|
||||||
|
GET_ATOM(UTF8_STRING);
|
||||||
|
|
||||||
check_error(c, xcb_change_property_checked(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 5, atoms), "Could not set _NET_SUPPORTED");
|
xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, window_type_handler, NULL);
|
||||||
|
/* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */
|
||||||
|
|
||||||
|
check_error(c, xcb_change_property_checked(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED");
|
||||||
|
|
||||||
xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
|
xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root);
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ void init_table() {
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
workspaces[i].screen = NULL;
|
workspaces[i].screen = NULL;
|
||||||
|
SLIST_INIT(&(workspaces[i].dock_clients));
|
||||||
expand_table_cols(&(workspaces[i]));
|
expand_table_cols(&(workspaces[i]));
|
||||||
expand_table_rows(&(workspaces[i]));
|
expand_table_rows(&(workspaces[i]));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue