diff --git a/include/swaygrab/json.h b/include/swaygrab/json.h new file mode 100644 index 00000000..c1093ef1 --- /dev/null +++ b/include/swaygrab/json.h @@ -0,0 +1,10 @@ +#include +#include "wlc/wlc.h" + +void init_json_tree(int socketfd); +void free_json_tree(); +char *get_focused_output(); +char *create_payload(const char *output, struct wlc_geometry *g); +struct wlc_geometry *get_container_geometry(json_object *container); +json_object *get_focused_container(); +json_object *get_output_container(const char *output); diff --git a/sway/ipc-json.c b/sway/ipc-json.c index 5d10ff59..1ca7f9ce 100644 --- a/sway/ipc-json.c +++ b/sway/ipc-json.c @@ -169,7 +169,6 @@ static void ipc_json_describe_view(swayc_t *c, json_object *object) { json_object_object_add(object, "percent", (percent > 0) ? json_object_new_double(percent) : NULL); // TODO: make urgency actually work once Sway supports it json_object_object_add(object, "urgent", json_object_new_boolean(false)); - json_object_object_add(object, "layout", (strcmp(layout, "null") == 0) ? NULL : json_object_new_string(layout)); json_object_object_add(object, "last_split_layout", diff --git a/sway/ipc-server.c b/sway/ipc-server.c index 4c54e56a..ebb5ce58 100644 --- a/sway/ipc-server.c +++ b/sway/ipc-server.c @@ -43,6 +43,7 @@ static list_t *ipc_get_pixel_requests = NULL; struct get_pixels_request { struct ipc_client *client; wlc_handle output; + struct wlc_geometry geo; }; struct sockaddr_un *ipc_user_sockaddr(void); @@ -259,16 +260,12 @@ void ipc_get_pixels(wlc_handle output) { continue; } - const struct wlc_size *size = wlc_output_get_resolution(req->output); - struct wlc_geometry g = { - .size = *size, - .origin = { 0, 0 }, - }; + const struct wlc_size *size = &req->geo.size; struct wlc_geometry g_out; char response_header[9]; memset(response_header, 0, sizeof(response_header)); char *data = malloc(sizeof(response_header) + size->w * size->h * 4); - wlc_pixels_read(WLC_RGBA8888, &g, &g_out, data + sizeof(response_header)); + wlc_pixels_read(WLC_RGBA8888, &req->geo, &g_out, data + sizeof(response_header)); response_header[0] = 1; uint32_t *_size = (uint32_t *)(response_header + 1); @@ -425,7 +422,30 @@ void ipc_client_handle_command(struct ipc_client *client) { { char response_header[9]; memset(response_header, 0, sizeof(response_header)); - swayc_t *output = swayc_by_test(&root_container, output_by_name_test, buf); + + json_object *obj = json_tokener_parse(buf); + json_object *o, *x, *y, *w, *h; + + json_object_object_get_ex(obj, "output", &o); + json_object_object_get_ex(obj, "x", &x); + json_object_object_get_ex(obj, "y", &y); + json_object_object_get_ex(obj, "w", &w); + json_object_object_get_ex(obj, "h", &h); + + struct wlc_geometry g = { + .origin = { + .x = json_object_get_int(x), + .y = json_object_get_int(y) + }, + .size = { + .w = json_object_get_int(w), + .h = json_object_get_int(h) + } + }; + + swayc_t *output = swayc_by_test(&root_container, output_by_name_test, (void *)json_object_get_string(o)); + json_object_put(obj); + if (!output) { sway_log(L_ERROR, "IPC GET_PIXELS request with unknown output name"); ipc_send_reply(client, response_header, sizeof(response_header)); @@ -434,6 +454,7 @@ void ipc_client_handle_command(struct ipc_client *client) { struct get_pixels_request *req = malloc(sizeof(struct get_pixels_request)); req->client = client; req->output = output->handle; + req->geo = g; list_add(ipc_get_pixel_requests, req); wlc_output_schedule_render(output->handle); goto exit_cleanup; diff --git a/swaygrab/CMakeLists.txt b/swaygrab/CMakeLists.txt index b4aee357..a5e91e9c 100644 --- a/swaygrab/CMakeLists.txt +++ b/swaygrab/CMakeLists.txt @@ -6,6 +6,7 @@ include_directories( add_executable(swaygrab main.c + json.c ) target_link_libraries(swaygrab diff --git a/swaygrab/json.c b/swaygrab/json.c new file mode 100644 index 00000000..7cd73cbe --- /dev/null +++ b/swaygrab/json.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include "ipc-client.h" +#include "swaygrab/json.h" + +static json_object *tree; + +void init_json_tree(int socketfd) { + uint32_t len = 0; + char *res = ipc_single_command(socketfd, IPC_GET_TREE, NULL, &len); + tree = json_tokener_parse(res); +} + +void free_json_tree() { + json_object_put(tree); +} + +static bool is_focused(json_object *c) { + json_object *focused; + json_object_object_get_ex(c, "focused", &focused); + return json_object_get_boolean(focused); +} + +static json_object *get_focused_container_r(json_object *c) { + json_object *name; + json_object_object_get_ex(c, "name", &name); + if (is_focused(c)) { + return c; + } else { + json_object *nodes, *node, *child; + json_object_object_get_ex(c, "nodes", &nodes); + int i; + for (i = 0; i < json_object_array_length(nodes); i++) { + node = json_object_array_get_idx(nodes, i); + + if ((child = get_focused_container_r(node))) { + return child; + } + } + + json_object_object_get_ex(c, "floating_nodes", &nodes); + for (i = 0; i < json_object_array_length(nodes); i++) { + node = json_object_array_get_idx(nodes, i); + + if ((child = get_focused_container_r(node))) { + return child; + } + } + + } + + return NULL; +} + +json_object *get_focused_container() { + return get_focused_container_r(tree); +} + +char *get_focused_output() { + json_object *outputs, *output, *name; + json_object_object_get_ex(tree, "nodes", &outputs); + + for (int i = 0; i < json_object_array_length(outputs); i++) { + output = json_object_array_get_idx(outputs, i); + + if (get_focused_container_r(output)) { + json_object_object_get_ex(output, "name", &name); + return strdup(json_object_get_string(name)); + } + } + + return NULL; +} + +char *create_payload(const char *output, struct wlc_geometry *g) { + char *payload_str = malloc(256); + json_object *payload = json_object_new_object(); + + json_object_object_add(payload, "output", json_object_new_string(output)); + json_object_object_add(payload, "x", json_object_new_int(g->origin.x)); + json_object_object_add(payload, "y", json_object_new_int(g->origin.y)); + json_object_object_add(payload, "w", json_object_new_int(g->size.w)); + json_object_object_add(payload, "h", json_object_new_int(g->size.h)); + + snprintf(payload_str, 256, "%s", json_object_to_json_string(payload)); + return strdup(payload_str); +} + +struct wlc_geometry *get_container_geometry(json_object *container) { + struct wlc_geometry *geo = malloc(sizeof(struct wlc_geometry)); + json_object *rect, *x, *y, *w, *h; + + json_object_object_get_ex(container, "rect", &rect); + json_object_object_get_ex(rect, "x", &x); + json_object_object_get_ex(rect, "y", &y); + json_object_object_get_ex(rect, "width", &w); + json_object_object_get_ex(rect, "height", &h); + + geo->origin.x = json_object_get_int(x); + geo->origin.y = json_object_get_int(y); + geo->size.w = json_object_get_int(w); + geo->size.h = json_object_get_int(h); + + return geo; +} + +json_object *get_output_container(const char *output) { + json_object *outputs, *json_output, *name; + json_object_object_get_ex(tree, "nodes", &outputs); + + for (int i = 0; i < json_object_array_length(outputs); i++) { + json_output = json_object_array_get_idx(outputs, i); + json_object_object_get_ex(json_output, "name", &name); + + if (strcmp(json_object_get_string(name), output) == 0) { + return json_output; + } + } + + return NULL; +} diff --git a/swaygrab/main.c b/swaygrab/main.c index 96290957..a88a6bcc 100644 --- a/swaygrab/main.c +++ b/swaygrab/main.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -10,16 +11,17 @@ #include "log.h" #include "ipc-client.h" #include "util.h" +#include "swaygrab/json.h" void sway_terminate(int exit_code) { exit(exit_code); } -void grab_and_apply_magick(const char *file, const char *output, +void grab_and_apply_magick(const char *file, const char *payload, int socketfd, int raw) { - uint32_t len = strlen(output); + uint32_t len = strlen(payload); char *pixels = ipc_single_command(socketfd, - IPC_SWAY_GET_PIXELS, output, &len); + IPC_SWAY_GET_PIXELS, payload, &len); uint32_t *u32pixels = (uint32_t *)(pixels + 1); uint32_t width = u32pixels[0]; uint32_t height = u32pixels[1]; @@ -27,7 +29,13 @@ void grab_and_apply_magick(const char *file, const char *output, pixels += 9; if (width == 0 || height == 0) { - sway_abort("Unknown output %s.", output); + // indicates geometry was clamped by WLC because it was outside of the output's area + json_object *obj = json_tokener_parse(payload); + json_object *output; + json_object_object_get_ex(obj, "output", &output); + const char *name = json_object_get_string(output); + json_object_put(obj); + sway_abort("Unknown output %s.", name); } if (raw) { @@ -50,22 +58,28 @@ void grab_and_apply_magick(const char *file, const char *output, free(cmd); } -void grab_and_apply_movie_magic(const char *file, const char *output, +void grab_and_apply_movie_magic(const char *file, const char *payload, int socketfd, int raw, int framerate) { if (raw) { sway_log(L_ERROR, "Raw capture data is not yet supported. Proceeding with ffmpeg normally."); } - uint32_t len = strlen(output); + uint32_t len = strlen(payload); char *pixels = ipc_single_command(socketfd, - IPC_SWAY_GET_PIXELS, output, &len); + IPC_SWAY_GET_PIXELS, payload, &len); uint32_t *u32pixels = (uint32_t *)(pixels + 1); uint32_t width = u32pixels[0]; uint32_t height = u32pixels[1]; pixels += 9; if (width == 0 || height == 0) { - sway_abort("Unknown output %s.", output); + // indicates geometry was clamped by WLC because it was outside of the output's area + json_object *obj = json_tokener_parse(payload); + json_object *output; + json_object_object_get_ex(obj, "output", &output); + const char *name = json_object_get_string(output); + json_object_put(obj); + sway_abort("Unknown output %s.", name); } const char *fmt = "ffmpeg -f rawvideo -framerate %d " @@ -86,9 +100,9 @@ void grab_and_apply_movie_magic(const char *file, const char *output, int sleep = 0; while (sleep != -1) { clock_gettime(CLOCK_MONOTONIC, &start); - len = strlen(output); + len = strlen(payload); pixels = ipc_single_command(socketfd, - IPC_SWAY_GET_PIXELS, output, &len); + IPC_SWAY_GET_PIXELS, payload, &len); pixels += 9; len -= 9; @@ -112,30 +126,6 @@ void grab_and_apply_movie_magic(const char *file, const char *output, free(cmd); } -char *get_focused_output(int socketfd) { - uint32_t len = 0; - char *res = ipc_single_command(socketfd, IPC_GET_WORKSPACES, NULL, &len); - json_object *workspaces = json_tokener_parse(res); - - int length = json_object_array_length(workspaces); - json_object *workspace, *focused, *json_output; - char *output = NULL; - int i; - for (i = 0; i < length; ++i) { - workspace = json_object_array_get_idx(workspaces, i); - json_object_object_get_ex(workspace, "focused", &focused); - if (json_object_get_boolean(focused) == TRUE) { - json_object_object_get_ex(workspace, "output", &json_output); - output = strdup(json_object_get_string(json_output)); - break; - } - } - - json_object_put(workspaces); - free(res); - return output; -} - char *default_filename(const char *extension) { int ext_len = strlen(extension); int len = 28 + ext_len; // format: "2015-12-17-180040_swaygrab.ext" @@ -154,6 +144,7 @@ int main(int argc, char **argv) { char *socket_path = NULL; char *output = NULL; int framerate = 30; + bool grab_focused = false; init_log(L_INFO); @@ -165,6 +156,7 @@ int main(int argc, char **argv) { {"socket", required_argument, NULL, 's'}, {"raw", no_argument, NULL, 'r'}, {"rate", required_argument, NULL, 'R'}, + {"focused", no_argument, NULL, 'f'}, {0, 0, 0, 0} }; @@ -177,16 +169,20 @@ int main(int argc, char **argv) { " -v, --version Show the version number and quit.\n" " -s, --socket Use the specified socket.\n" " -R, --rate Specify framerate (default: 30)\n" - " -r, --raw Write raw rgba data to stdout.\n"; + " -r, --raw Write raw rgba data to stdout.\n" + " -f, --focused Grab the focused container.\n"; int c; while (1) { int option_index = 0; - c = getopt_long(argc, argv, "hco:vs:R:r", long_options, &option_index); + c = getopt_long(argc, argv, "hco:vs:R:rf", long_options, &option_index); if (c == -1) { break; } switch (c) { + case 'f': + grab_focused = true; + break; case 's': // Socket socket_path = strdup(optarg); break; @@ -235,10 +231,32 @@ int main(int argc, char **argv) { int socketfd = ipc_open_socket(socket_path); free(socket_path); - if (!output) { - output = get_focused_output(socketfd); + init_json_tree(socketfd); + + struct wlc_geometry *geo; + + if (grab_focused) { + output = get_focused_output(); + json_object *con = get_focused_container(); + json_object *name; + json_object_object_get_ex(con, "name", &name); + geo = get_container_geometry(con); + free(con); + } else { + if (!output) { + output = get_focused_output(); + } + geo = get_container_geometry(get_output_container(output)); + // the geometry of the output in the get_tree response is relative to a global (0, 0). + // we need it to be relative to itself, so set origin to (0, 0) always. + geo->origin.x = 0; + geo->origin.y = 0; } + const char *payload = create_payload(output, geo); + + free(geo); + if (!file) { if (!capture) { file = default_filename("png"); @@ -248,11 +266,12 @@ int main(int argc, char **argv) { } if (!capture) { - grab_and_apply_magick(file, output, socketfd, raw); + grab_and_apply_magick(file, payload, socketfd, raw); } else { - grab_and_apply_movie_magic(file, output, socketfd, raw, framerate); + grab_and_apply_movie_magic(file, payload, socketfd, raw, framerate); } + free_json_tree(); free(output); free(file); close(socketfd);