Expose visible window regions through swmctl
db047604e9fe6c12bbab99554c6dcf537d72f2d1
1 parent
cce36366
README
+2 -2
| 125 | 125 | output. When `-s` is used, that output is connected to the startup command's |
|
| 126 | 126 | standard input. Workspace metadata uses `workspace N title VALUE` and |
|
| 127 | 127 | `workspace N color #RRGGBBAA` records; empty values mean unset. Use |
|
| 128 | 128 | `swmctl events --filter=active-window` to receive the active window title for |
|
| 129 | 129 | a custom bar module. |
|
| 130 | - | Visible window rectangles are published atomically to |
|
| 131 | - | `$XDG_RUNTIME_DIR/swm-windows` in the format accepted by `slurp -r`. |
|
| 130 | + | `swmctl window regions` prints visible window rectangles in the format accepted |
|
| 131 | + | by `slurp -r`. |
|
| 132 | 132 | ||
| 133 | 133 | ||
| 134 | 134 | # TESTING AND DEVELOPMENT |
|
| 135 | 135 | ||
| 136 | 136 | Run the complete test suite with: |
protocols/swm-toplevel-v1.xml
+38 -3
| 13 | 13 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
|
| 14 | 14 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|
| 15 | 15 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
| 16 | 16 | </copyright> |
|
| 17 | 17 | ||
| 18 | - | <interface name="swm_toplevel_manager_v1" version="1"> |
|
| 19 | - | <description summary="activate swm toplevels"> |
|
| 18 | + | <interface name="swm_toplevel_manager_v1" version="2"> |
|
| 19 | + | <description summary="inspect and activate swm toplevels"> |
|
| 20 | 20 | This interface activates mapped toplevels by the stable identifier from |
|
| 21 | - | ext-foreign-toplevel-list-v1. |
|
| 21 | + | ext-foreign-toplevel-list-v1 and provides visible window regions. |
|
| 22 | 22 | </description> |
|
| 23 | 23 | ||
| 24 | 24 | <enum name="error"> |
|
| 25 | 25 | <entry name="invalid_identifier" value="1" summary="the toplevel identifier does not exist"/> |
|
| 26 | 26 | </enum> |
| 33 | 33 | </request> |
|
| 34 | 34 | ||
| 35 | 35 | <request name="destroy" type="destructor"> |
|
| 36 | 36 | <description summary="destroy the manager object"/> |
|
| 37 | 37 | </request> |
|
| 38 | + | ||
| 39 | + | <request name="get_regions" since="2"> |
|
| 40 | + | <description summary="get visible window regions"> |
|
| 41 | + | The new object receives one snapshot of visible window regions. |
|
| 42 | + | </description> |
|
| 43 | + | <arg name="regions" type="new_id" interface="swm_region_list_v1"/> |
|
| 44 | + | </request> |
|
| 45 | + | </interface> |
|
| 46 | + | ||
| 47 | + | <interface name="swm_region_list_v1" version="1"> |
|
| 48 | + | <description summary="one visible window region snapshot"> |
|
| 49 | + | This object sends visible window rectangles and then a done event. |
|
| 50 | + | </description> |
|
| 51 | + | ||
| 52 | + | <event name="region"> |
|
| 53 | + | <description summary="one visible window rectangle"> |
|
| 54 | + | Coordinates and dimensions use compositor layout units. The title is |
|
| 55 | + | a display label with control characters replaced by spaces. |
|
| 56 | + | </description> |
|
| 57 | + | <arg name="x" type="int"/> |
|
| 58 | + | <arg name="y" type="int"/> |
|
| 59 | + | <arg name="width" type="int"/> |
|
| 60 | + | <arg name="height" type="int"/> |
|
| 61 | + | <arg name="title" type="string"/> |
|
| 62 | + | </event> |
|
| 63 | + | ||
| 64 | + | <event name="done"> |
|
| 65 | + | <description summary="end of the region snapshot"> |
|
| 66 | + | No more regions follow this event. |
|
| 67 | + | </description> |
|
| 68 | + | </event> |
|
| 69 | + | ||
| 70 | + | <request name="destroy" type="destructor"> |
|
| 71 | + | <description summary="destroy the region snapshot"/> |
|
| 72 | + | </request> |
|
| 38 | 73 | </interface> |
|
| 39 | 74 | </protocol> |
swm.c
+43 -33
| 608 | 608 | client_t *c, struct wlr_surface *surface, double sx, double sy, uint32_t time, bool refocus |
|
| 609 | 609 | ); |
|
| 610 | 610 | static void print_status(void); |
|
| 611 | 611 | static void publish_status(void); |
|
| 612 | 612 | static void status_idle_notify(void *data); |
|
| 613 | - | static void publish_windows(const char *runtime); |
|
| 614 | 613 | static void execute_command(char *const argv[], const char *context); |
|
| 615 | 614 | static void prepare_child(void); |
|
| 616 | 615 | static void power_manager_set_mode(struct wl_listener *listener, void *data); |
|
| 617 | 616 | static void quit(const arg_t *arg); |
|
| 618 | 617 | static void raise_client(const arg_t *arg); |
| 5011 | 5010 | /* Sanitize a field for status output, leaving an unknown value empty. */ |
|
| 5012 | 5011 | static void status_field(char *dst, size_t size, const char *src) { |
|
| 5013 | 5012 | swm_sanitize_field(dst, size, src, nullptr); |
|
| 5014 | 5013 | } |
|
| 5015 | 5014 | ||
| 5016 | - | /* Publish visible window rectangles in slurp's predefined-region format. */ |
|
| 5017 | - | void publish_windows(const char *runtime) { |
|
| 5018 | - | client_t *c; |
|
| 5019 | - | FILE *file; |
|
| 5020 | - | char path[MAX_STATE_PATH], tmppath[MAX_STATE_PATH], title[MAX_WINDOW_STATE_FIELD]; |
|
| 5021 | - | ||
| 5022 | - | if (!runtime || snprintf(path, sizeof(path), "%s/swm-windows", runtime) >= (int)sizeof(path) || |
|
| 5023 | - | !(file = file_replace_begin(path, tmppath, sizeof(tmppath)))) |
|
| 5024 | - | return; |
|
| 5025 | - | ||
| 5026 | - | wl_list_for_each(c, &clients, link) { |
|
| 5027 | - | if (!c->mon || !client_surface(c)->mapped || !c->scene->node.enabled) |
|
| 5028 | - | continue; |
|
| 5029 | - | status_field(title, sizeof(title), client_get_title(c)); |
|
| 5030 | - | fprintf( |
|
| 5031 | - | file, "%d,%d %dx%d %s\n", c->geom.x, c->geom.y, c->geom.width, c->geom.height, title |
|
| 5032 | - | ); |
|
| 5033 | - | } |
|
| 5034 | - | file_replace_commit(file, tmppath, path); |
|
| 5035 | - | } |
|
| 5036 | - | ||
| 5037 | 5015 | /* Publish the status once the current batch of changes has settled. */ |
|
| 5038 | 5016 | void status_idle_notify(void *data) { |
|
| 5039 | 5017 | status_idle = nullptr; |
|
| 5040 | 5018 | publish_status(); |
|
| 5041 | 5019 | } |
| 5054 | 5032 | publish_status(); |
|
| 5055 | 5033 | } |
|
| 5056 | 5034 | ||
| 5057 | 5035 | /* Publish current display, workspace, and focus state on standard output. */ |
|
| 5058 | 5036 | void publish_status(void) { |
|
| 5059 | - | monitor_t *m = nullptr; |
|
| 5060 | - | client_t *c; |
|
| 5061 | - | char title[MAX_STATUS_FIELD], appid[MAX_STATUS_FIELD]; |
|
| 5062 | - | const char *runtime; |
|
| 5063 | - | uint32_t occ, urg; |
|
| 5064 | - | int i; |
|
| 5037 | + | monitor_t *m = nullptr; |
|
| 5038 | + | client_t *c; |
|
| 5039 | + | char title[MAX_STATUS_FIELD], appid[MAX_STATUS_FIELD]; |
|
| 5040 | + | uint32_t occ, urg; |
|
| 5041 | + | int i; |
|
| 5065 | 5042 | ||
| 5066 | 5043 | occ = urg = 0; |
|
| 5067 | 5044 | wl_list_for_each(c, &clients, link) { |
|
| 5068 | 5045 | ftl_sync(c); |
|
| 5069 | 5046 |
| 5105 | 5082 | if (workspaces[i].color) |
|
| 5106 | 5083 | printf("workspace %d color #%08" PRIx32 "\n", i + 1, workspaces[i].color); |
|
| 5107 | 5084 | else |
|
| 5108 | 5085 | printf("workspace %d color \n", i + 1); |
|
| 5109 | 5086 | } |
|
| 5110 | - | runtime = getenv("XDG_RUNTIME_DIR"); |
|
| 5111 | - | publish_windows(runtime); |
|
| 5112 | 5087 | fflush(stdout); |
|
| 5113 | 5088 | workspace_broadcast(); |
|
| 5114 | 5089 | } |
|
| 5115 | 5090 | ||
| 5116 | 5091 | /* Replace a command child with its program or an error notification. */ |
| 5863 | 5838 | "toplevel identifier '%s' does not exist", |
|
| 5864 | 5839 | identifier |
|
| 5865 | 5840 | ); |
|
| 5866 | 5841 | } |
|
| 5867 | 5842 | ||
| 5843 | + | /* Release one completed region snapshot. */ |
|
| 5844 | + | static void region_list_destroy(struct wl_client *client, struct wl_resource *resource) { |
|
| 5845 | + | wl_resource_destroy(resource); |
|
| 5846 | + | } |
|
| 5847 | + | ||
| 5848 | + | static const struct swm_region_list_v1_interface region_list_impl = { |
|
| 5849 | + | .destroy = region_list_destroy, |
|
| 5850 | + | }; |
|
| 5851 | + | ||
| 5852 | + | /* Send the current visible window rectangles to one client. */ |
|
| 5853 | + | static void toplevel_control_get_regions( |
|
| 5854 | + | struct wl_client *client, struct wl_resource *resource, uint32_t id |
|
| 5855 | + | ) { |
|
| 5856 | + | struct wl_resource *regions; |
|
| 5857 | + | client_t *c; |
|
| 5858 | + | char title[MAX_WINDOW_STATE_FIELD]; |
|
| 5859 | + | ||
| 5860 | + | regions = wl_resource_create(client, &swm_region_list_v1_interface, 1, id); |
|
| 5861 | + | if (!regions) { |
|
| 5862 | + | wl_client_post_no_memory(client); |
|
| 5863 | + | return; |
|
| 5864 | + | } |
|
| 5865 | + | wl_resource_set_implementation(regions, ®ion_list_impl, nullptr, nullptr); |
|
| 5866 | + | wl_list_for_each(c, &clients, link) { |
|
| 5867 | + | if (!c->mon || !client_surface(c)->mapped || !c->scene->node.enabled) |
|
| 5868 | + | continue; |
|
| 5869 | + | status_field(title, sizeof(title), client_get_title(c)); |
|
| 5870 | + | swm_region_list_v1_send_region( |
|
| 5871 | + | regions, c->geom.x, c->geom.y, c->geom.width, c->geom.height, title |
|
| 5872 | + | ); |
|
| 5873 | + | } |
|
| 5874 | + | swm_region_list_v1_send_done(regions); |
|
| 5875 | + | } |
|
| 5876 | + | ||
| 5868 | 5877 | /* Release a toplevel control manager. */ |
|
| 5869 | 5878 | static void toplevel_control_destroy(struct wl_client *client, struct wl_resource *resource) { |
|
| 5870 | 5879 | wl_resource_destroy(resource); |
|
| 5871 | 5880 | } |
|
| 5872 | 5881 | ||
| 5873 | 5882 | static const struct swm_toplevel_manager_v1_interface toplevel_control_impl = { |
|
| 5874 | - | .activate = toplevel_control_activate, |
|
| 5875 | - | .destroy = toplevel_control_destroy, |
|
| 5883 | + | .activate = toplevel_control_activate, |
|
| 5884 | + | .destroy = toplevel_control_destroy, |
|
| 5885 | + | .get_regions = toplevel_control_get_regions, |
|
| 5876 | 5886 | }; |
|
| 5877 | 5887 | ||
| 5878 | 5888 | /* Publish swm's toplevel control interface to a client. */ |
|
| 5879 | 5889 | static void toplevel_control_bind( |
|
| 5880 | 5890 | struct wl_client *client, void *data, uint32_t version, uint32_t id |
| 6383 | 6393 | wl_list_init(&window_states); |
|
| 6384 | 6394 | wl_list_init(&input_popups); |
|
| 6385 | 6395 | load_window_states(); |
|
| 6386 | 6396 | wl_global_create(dpy, &ext_workspace_manager_v1_interface, 1, nullptr, workspace_manager_bind); |
|
| 6387 | 6397 | wl_global_create(dpy, &swm_workspace_manager_v1_interface, 1, nullptr, workspace_metadata_bind); |
|
| 6388 | - | wl_global_create(dpy, &swm_toplevel_manager_v1_interface, 1, nullptr, toplevel_control_bind); |
|
| 6398 | + | wl_global_create(dpy, &swm_toplevel_manager_v1_interface, 2, nullptr, toplevel_control_bind); |
|
| 6389 | 6399 | ||
| 6390 | 6400 | /* Publish window lists for taskbars and switchers. */ |
|
| 6391 | 6401 | ftl_mgr = wlr_foreign_toplevel_manager_v1_create(dpy); |
|
| 6392 | 6402 | ext_ftl_list = wlr_ext_foreign_toplevel_list_v1_create(dpy, 1); |
|
| 6393 | 6403 | wlr_ext_image_copy_capture_manager_v1_create(dpy, 1); |
swmctl.1.adoc
+6 -0
| 17 | 17 | ||
| 18 | 18 | *swmctl workspace clear* _N_ (*title*|*color*) |
|
| 19 | 19 | ||
| 20 | 20 | *swmctl workspace subscribe* [*--format=waybar*] |
|
| 21 | 21 | ||
| 22 | + | *swmctl window regions* |
|
| 23 | + | ||
| 22 | 24 | *swmctl events --filter=active-window* |
|
| 23 | 25 | ||
| 24 | 26 | == DESCRIPTION |
|
| 25 | 27 | ||
| 26 | 28 | *swmctl* connects to the running *swm* Wayland compositor and reads or changes |
| 50 | 52 | *events --filter=active-window* writes the current active window title, then |
|
| 51 | 53 | writes a new line when the active window or its title changes. It writes an |
|
| 52 | 54 | empty line when no window is active. Control characters in titles become spaces. |
|
| 53 | 55 | The command stays connected until it is stopped or the compositor exits. |
|
| 54 | 56 | ||
| 57 | + | *window regions* prints one snapshot of visible window rectangles. Each line |
|
| 58 | + | contains a position, a size, and a window title in the format used by |
|
| 59 | + | `slurp -r`. |
|
| 60 | + | ||
| 55 | 61 | == WAYBAR |
|
| 56 | 62 | ||
| 57 | 63 | The `ext/workspaces` module continues to display the numeric workspace names |
|
| 58 | 64 | `1` through `10`. A custom module can display the additional title and color |
|
| 59 | 65 | independently: |
swmctl.c
+66 -7
| 29 | 29 | /* Wayland globals required by one command family. */ |
|
| 30 | 30 | enum command_kind { |
|
| 31 | 31 | COMMAND_WORKSPACE, |
|
| 32 | 32 | COMMAND_WINDOW_LIST, |
|
| 33 | 33 | COMMAND_WINDOW_ACTIVATE, |
|
| 34 | + | COMMAND_WINDOW_REGIONS, |
|
| 34 | 35 | COMMAND_EVENTS, |
|
| 35 | 36 | }; |
|
| 36 | 37 | ||
| 37 | 38 | /* Cached state for one ext-foreign-toplevel handle. */ |
|
| 38 | 39 | typedef struct { |
| 54 | 55 | ||
| 55 | 56 | static struct wl_display *display; |
|
| 56 | 57 | static struct swm_workspace_manager_v1 *manager; |
|
| 57 | 58 | static struct ext_foreign_toplevel_list_v1 *toplevel_list; |
|
| 58 | 59 | static struct swm_toplevel_manager_v1 *toplevel_control; |
|
| 60 | + | static bool regions_done; |
|
| 59 | 61 | static struct wl_list windows; |
|
| 60 | 62 | static workspace_t workspaces[MAX_WORKSPACES]; |
|
| 61 | 63 | static bool subscribing; |
|
| 62 | 64 | static bool waybar; |
|
| 63 | 65 | static bool toplevel_list_finished; |
| 78 | 80 | " swmctl workspace set N (title TITLE | color COLOR)\n" |
|
| 79 | 81 | " swmctl workspace clear N (title | color)\n" |
|
| 80 | 82 | " swmctl workspace subscribe [--format=waybar]\n" |
|
| 81 | 83 | " swmctl window list\n" |
|
| 82 | 84 | " swmctl window activate IDENTIFIER\n" |
|
| 85 | + | " swmctl window regions\n" |
|
| 83 | 86 | " swmctl events --filter=active-window\n" |
|
| 84 | 87 | ); |
|
| 85 | 88 | exit(EXIT_FAILURE); |
|
| 86 | 89 | } |
|
| 87 | 90 |
| 485 | 488 | ) { |
|
| 486 | 489 | toplevel_list = |
|
| 487 | 490 | wl_registry_bind(registry, name, &ext_foreign_toplevel_list_v1_interface, 1); |
|
| 488 | 491 | ext_foreign_toplevel_list_v1_add_listener(toplevel_list, &window_list_listener, nullptr); |
|
| 489 | 492 | } else if ( |
|
| 490 | - | requested_kind == COMMAND_WINDOW_ACTIVATE && |
|
| 493 | + | (requested_kind == COMMAND_WINDOW_ACTIVATE || requested_kind == COMMAND_WINDOW_REGIONS) && |
|
| 491 | 494 | !strcmp(interface, swm_toplevel_manager_v1_interface.name) |
|
| 492 | 495 | ) { |
|
| 493 | - | toplevel_control = wl_registry_bind(registry, name, &swm_toplevel_manager_v1_interface, 1); |
|
| 496 | + | if (requested_kind == COMMAND_WINDOW_REGIONS && version < 2) |
|
| 497 | + | return; |
|
| 498 | + | toplevel_control = wl_registry_bind( |
|
| 499 | + | registry, |
|
| 500 | + | name, |
|
| 501 | + | &swm_toplevel_manager_v1_interface, |
|
| 502 | + | requested_kind == COMMAND_WINDOW_REGIONS ? 2 : 1 |
|
| 503 | + | ); |
|
| 494 | 504 | } else if ( |
|
| 495 | 505 | requested_kind == COMMAND_EVENTS && |
|
| 496 | 506 | !strcmp(interface, zwlr_foreign_toplevel_manager_v1_interface.name) |
|
| 497 | 507 | ) { |
|
| 498 | 508 | event_manager = |
| 597 | 607 | putchar('\t'); |
|
| 598 | 608 | puts(window->identifier); |
|
| 599 | 609 | } |
|
| 600 | 610 | } |
|
| 601 | 611 | ||
| 612 | + | /* Print one region in slurp's format. */ |
|
| 613 | + | static void region_snapshot_region( |
|
| 614 | + | void *data, |
|
| 615 | + | struct swm_region_list_v1 *regions, |
|
| 616 | + | int32_t x, |
|
| 617 | + | int32_t y, |
|
| 618 | + | int32_t width, |
|
| 619 | + | int32_t height, |
|
| 620 | + | const char *title |
|
| 621 | + | ) { |
|
| 622 | + | printf("%d,%d %dx%d %s\n", x, y, width, height, title); |
|
| 623 | + | } |
|
| 624 | + | ||
| 625 | + | /* Mark the region snapshot complete. */ |
|
| 626 | + | static void region_snapshot_done(void *data, struct swm_region_list_v1 *regions) { |
|
| 627 | + | regions_done = true; |
|
| 628 | + | } |
|
| 629 | + | ||
| 630 | + | static const struct swm_region_list_v1_listener region_list_listener = { |
|
| 631 | + | .region = region_snapshot_region, |
|
| 632 | + | .done = region_snapshot_done, |
|
| 633 | + | }; |
|
| 634 | + | ||
| 635 | + | /* Print a complete visible-window snapshot in slurp's region format. */ |
|
| 636 | + | static bool print_regions(void) { |
|
| 637 | + | struct swm_region_list_v1 *regions; |
|
| 638 | + | bool ok; |
|
| 639 | + | ||
| 640 | + | regions = swm_toplevel_manager_v1_get_regions(toplevel_control); |
|
| 641 | + | if (!regions) |
|
| 642 | + | return false; |
|
| 643 | + | swm_region_list_v1_add_listener(regions, ®ion_list_listener, nullptr); |
|
| 644 | + | ok = wl_display_roundtrip(display) >= 0 && regions_done && fflush(stdout) == 0; |
|
| 645 | + | swm_region_list_v1_destroy(regions); |
|
| 646 | + | return ok; |
|
| 647 | + | } |
|
| 648 | + | ||
| 602 | 649 | /* Release cached foreign-toplevel state. */ |
|
| 603 | 650 | static void destroy_windows(void) { |
|
| 604 | 651 | window_t *window, *tmp; |
|
| 605 | 652 | ||
| 606 | 653 | wl_list_for_each_safe(window, tmp, &windows, link) { |
| 640 | 687 | registry = wl_display_get_registry(display); |
|
| 641 | 688 | wl_registry_add_listener(registry, ®istry_listener, nullptr); |
|
| 642 | 689 | ||
| 643 | 690 | if (wl_display_roundtrip(display) < 0 || (kind == COMMAND_WORKSPACE && !manager) || |
|
| 644 | 691 | (kind == COMMAND_WINDOW_LIST && !toplevel_list) || |
|
| 645 | - | (kind == COMMAND_WINDOW_ACTIVATE && !toplevel_control) || |
|
| 692 | + | ((kind == COMMAND_WINDOW_ACTIVATE || kind == COMMAND_WINDOW_REGIONS) && |
|
| 693 | + | !toplevel_control) || |
|
| 646 | 694 | (kind == COMMAND_EVENTS && !event_manager)) { |
|
| 647 | 695 | fprintf( |
|
| 648 | 696 | stderr, |
|
| 649 | 697 | kind == COMMAND_WORKSPACE ? "swmctl: compositor does not support swm-workspace-v1\n" |
|
| 650 | 698 | : kind == COMMAND_WINDOW_LIST |
|
| 651 | 699 | ? "swmctl: compositor does not support ext-foreign-toplevel-list-v1\n" |
|
| 652 | 700 | : kind == COMMAND_EVENTS |
|
| 653 | 701 | ? "swmctl: compositor does not support wlr-foreign-toplevel-management-v1\n" |
|
| 702 | + | : kind == COMMAND_WINDOW_REGIONS |
|
| 703 | + | ? "swmctl: compositor does not support window regions\n" |
|
| 654 | 704 | : "swmctl: compositor does not support swm-toplevel-v1\n" |
|
| 655 | 705 | ); |
|
| 656 | 706 | exit(EXIT_FAILURE); |
|
| 657 | 707 | } |
|
| 658 | 708 | if (kind != COMMAND_WINDOW_ACTIVATE && wl_display_roundtrip(display) < 0) { |
| 693 | 743 | usage(); |
|
| 694 | 744 | } else if (!strcmp(command, "activate")) { |
|
| 695 | 745 | if (argc != 4) |
|
| 696 | 746 | usage(); |
|
| 697 | 747 | identifier = argv[3]; |
|
| 748 | + | } else if (!strcmp(command, "regions")) { |
|
| 749 | + | if (argc != 3) |
|
| 750 | + | usage(); |
|
| 698 | 751 | } else { |
|
| 699 | 752 | usage(); |
|
| 700 | 753 | } |
|
| 701 | 754 | } else if (strcmp(argv[1], "workspace")) { |
|
| 702 | 755 | usage(); |
| 743 | 796 | usage(); |
|
| 744 | 797 | } else { |
|
| 745 | 798 | usage(); |
|
| 746 | 799 | } |
|
| 747 | 800 | connect_manager( |
|
| 748 | - | !window_command ? COMMAND_WORKSPACE |
|
| 749 | - | : !strcmp(command, "list") ? COMMAND_WINDOW_LIST |
|
| 750 | - | : COMMAND_WINDOW_ACTIVATE |
|
| 801 | + | !window_command ? COMMAND_WORKSPACE |
|
| 802 | + | : !strcmp(command, "list") ? COMMAND_WINDOW_LIST |
|
| 803 | + | : !strcmp(command, "regions") ? COMMAND_WINDOW_REGIONS |
|
| 804 | + | : COMMAND_WINDOW_ACTIVATE |
|
| 751 | 805 | ); |
|
| 752 | 806 | ||
| 753 | 807 | if (window_command) { |
|
| 754 | 808 | if (!strcmp(command, "list")) |
|
| 755 | 809 | print_windows(); |
|
| 756 | - | else |
|
| 810 | + | else if (!strcmp(command, "regions")) { |
|
| 811 | + | if (!print_regions()) { |
|
| 812 | + | fprintf(stderr, "swmctl: failed to read window regions\n"); |
|
| 813 | + | return EXIT_FAILURE; |
|
| 814 | + | } |
|
| 815 | + | } else |
|
| 757 | 816 | swm_toplevel_manager_v1_activate(toplevel_control, identifier); |
|
| 758 | 817 | } else if (subscribing) { |
|
| 759 | 818 | while (wl_display_dispatch(display) >= 0) |
|
| 760 | 819 | ; |
|
| 761 | 820 | return EXIT_FAILURE; |
test/clients.py
+4 -3
| 707 | 707 | connection = Connection(WlCompositor, WlShm, XdgWmBase) |
|
| 708 | 708 | window = Window(connection, "output-float", 0xFF336699, fixed_size=True) |
|
| 709 | 709 | connection.roundtrips(4) |
|
| 710 | 710 | ||
| 711 | 711 | def geometry() -> tuple[int, ...]: |
|
| 712 | - | """Read this window's published outer rectangle.""" |
|
| 712 | + | """Read this window's outer rectangle from the control snapshot.""" |
|
| 713 | 713 | ||
| 714 | 714 | window.toplevel.set_title("output-float") |
|
| 715 | 715 | connection.roundtrips(3) |
|
| 716 | - | contents = (Path(os.environ["XDG_RUNTIME_DIR"]) / "swm-windows").read_text() |
|
| 716 | + | control = Path(os.environ["SWM_TEST_BUILD"]) / "bin/swmctl" |
|
| 717 | + | contents = subprocess.check_output([control, "window", "regions"], text=True) |
|
| 717 | 718 | match = re.search(r"^(-?\d+),(-?\d+) (\d+)x(\d+) output-float$", contents, re.MULTILINE) |
|
| 718 | 719 | if not match: |
|
| 719 | - | raise Failure("floating window has no published geometry") |
|
| 720 | + | raise Failure("floating window has no region") |
|
| 720 | 721 | return tuple(map(int, match.groups())) |
|
| 721 | 722 | ||
| 722 | 723 | before = geometry() |
|
| 723 | 724 | subprocess.run(["wlr-randr", "--output", "HEADLESS-1", "--pos", "100,80"], check=True) |
|
| 724 | 725 | moved = geometry() |
test/run.py
+6 -5
| 313 | 313 | path = Path(os.environ["SWM_TEST_DIR"]) / f"{name}.geometry" |
|
| 314 | 314 | return list(map(int, path.read_text().split())) |
|
| 315 | 315 | ||
| 316 | 316 | ||
| 317 | 317 | def published_geometry(title: str) -> list[int]: |
|
| 318 | - | """Return a client's compositor-published outer geometry.""" |
|
| 318 | + | """Return a client's outer geometry from the control snapshot.""" |
|
| 319 | 319 | ||
| 320 | - | path = Path(os.environ["XDG_RUNTIME_DIR"]) / "swm-windows" |
|
| 321 | 320 | match = re.search( |
|
| 322 | - | rf"^(\d+),(\d+) (\d+)x(\d+) {re.escape(title)}$", path.read_text(), re.MULTILINE |
|
| 321 | + | rf"^(\d+),(\d+) (\d+)x(\d+) {re.escape(title)}$", |
|
| 322 | + | run(BIN / "swmctl", "window", "regions"), |
|
| 323 | + | re.MULTILINE, |
|
| 323 | 324 | ) |
|
| 324 | 325 | if not match: |
|
| 325 | 326 | raise Failure(f"missing published geometry for {title}") |
|
| 326 | 327 | return list(map(int, match.groups())) |
|
| 327 | 328 |
| 370 | 371 | start_title_events() |
|
| 371 | 372 | one = start_client("one", "ff5588dd") |
|
| 372 | 373 | two = start_client("two", "ff55aa88") |
|
| 373 | 374 | ||
| 374 | 375 | def publication() -> None: |
|
| 375 | - | windows = (Path(os.environ["XDG_RUNTIME_DIR"]) / "swm-windows").read_text() |
|
| 376 | + | windows = run(BIN / "swmctl", "window", "regions") |
|
| 376 | 377 | if not re.search(r"^\d+,\d+ \d+x\d+ one$", windows, re.MULTILINE): |
|
| 377 | 378 | raise Failure("missing published rectangle for one") |
|
| 378 | 379 | if not re.search(r"^\d+,\d+ \d+x\d+ two$", windows, re.MULTILINE): |
|
| 379 | 380 | raise Failure("missing published rectangle for two") |
|
| 380 | 381 | before = current_title() |
|
| 381 | 382 | virtual_keyboard(36) |
|
| 382 | 383 | wait_title("two" if before == "one" else "one") |
|
| 383 | 384 | ||
| 384 | - | test("window publication and focus", publication) |
|
| 385 | + | test("window regions and focus", publication) |
|
| 385 | 386 | ||
| 386 | 387 | def active_window_events() -> None: |
|
| 387 | 388 | title = current_title() |
|
| 388 | 389 | log = client_log("active-window-events") |
|
| 389 | 390 | subscriber = spawn( |
test/unit.c
+0 -1
| 849 | 849 | unsetenv("SWM_TEST_MISSING"); |
|
| 850 | 850 | expand_argv(command, expanded, storage); |
|
| 851 | 851 | assert(!strcmp(expanded[0], "before-expanded value")); |
|
| 852 | 852 | assert(!strcmp(expanded[1], "")); |
|
| 853 | 853 | assert(expanded[2] == nullptr); |
|
| 854 | - | publish_windows(nullptr); |
|
| 855 | 854 | } |
|
| 856 | 855 | ||
| 857 | 856 | /* Verify configured autostart commands and child tracking with harmless stubs. */ |
|
| 858 | 857 | static void test_autostart_execution(void) { |
|
| 859 | 858 | const char *stub_path = getenv("SWM_TEST_STUB_PATH"); |