diff options
author | Marius Vlad <marius.vlad@collabora.com> | 2024-01-25 18:55:15 +0200 |
---|---|---|
committer | Marius Vlad <marius.vlad@collabora.com> | 2024-02-22 13:51:33 +0000 |
commit | 09fa5536e759792c80341305a536cd59aa801c6d (patch) | |
tree | 407b3133e0915a8f70238c40be9bb8452532ca45 | |
parent | 8a7f3fbbf0fd94bb1c29c59663392506a213c4b1 (diff) |
layout/shell: Add basic support for split window
This introduces a new set_split request to allow changing the tile
orientation of the window. See the protocol XML for more implementation
details.
Of importance difference from the previous implementation is that
this patch makes use of the xdg-shell protocol, such that orientation
is being handled over the configure event to the client.
The protocol specifies a width to allow the client to control how much
of the output be assign the split window and also a sticky window
functionality.
Bug-AGL: SPEC-4839
Signed-off-by: Marius Vlad <marius.vlad@collabora.com>
Change-Id: Ia8b7d04a7514f55d647c3ea76b13bab51a3586aa
-rw-r--r-- | grpc-proxy/main-grpc.cpp | 2 | ||||
-rw-r--r-- | protocol/agl-shell.xml | 73 | ||||
-rw-r--r-- | src/compositor.c | 1 | ||||
-rw-r--r-- | src/desktop.c | 9 | ||||
-rw-r--r-- | src/ivi-compositor.h | 19 | ||||
-rw-r--r-- | src/layout.c | 42 | ||||
-rw-r--r-- | src/shell.c | 260 |
7 files changed, 402 insertions, 4 deletions
diff --git a/grpc-proxy/main-grpc.cpp b/grpc-proxy/main-grpc.cpp index b86f3d8..92d4862 100644 --- a/grpc-proxy/main-grpc.cpp +++ b/grpc-proxy/main-grpc.cpp @@ -352,7 +352,7 @@ register_shell_ext(void) if (it->interface_name == "agl_shell") { sh->shell = static_cast<struct agl_shell *>(wl_registry_bind(registry, it->id, - &agl_shell_interface, std::min(static_cast<uint32_t>(10), + &agl_shell_interface, std::min(static_cast<uint32_t>(11), it->version)) ); agl_shell_add_listener(sh->shell, &shell_listener, sh); diff --git a/protocol/agl-shell.xml b/protocol/agl-shell.xml index b11beb6..f8aee4c 100644 --- a/protocol/agl-shell.xml +++ b/protocol/agl-shell.xml @@ -22,7 +22,7 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. </copyright> - <interface name="agl_shell" version="10"> + <interface name="agl_shell" version="11"> <description summary="user interface for Automotive Grade Linux platform"> Starting with version 2 of the protocol, the client is required to wait for the 'bound_ok' or 'bound_fail' events in order to proceed further. @@ -67,6 +67,14 @@ <entry name="deactivated" value="3"/> </enum> + <enum name="tile_orientation" since="11"> + <entry name="none" value="0"/> + <entry name="left" value="1"/> + <entry name="right" value="2"/> + <entry name="top" value="3"/> + <entry name="bottom" value="4"/> + </enum> + <request name="ready"> <description summary="client is ready to be shown"> Tell the server that this client is ready to be shown. The server @@ -342,6 +350,69 @@ <arg name="width" type="int"/> <arg name="height" type="int"/> </request> + + <request name="set_app_split" since="11"> + <description summary="set the application split"> + This requests asks the compositor to change the application from the + original mode (whatever that might be) to a split, tiled orientation + mode defined in the tile orientation enum. + Clients need to implement resizing (to handle xdg-shell configure + events) for this to work correctly. + + This request only handles a single level of tiling for practical + reasons: to keep implementation simple and straight forward. The + compositor will ignore requests if there are already two windows + present. The client can verify this request succeed by checking the + xdg-shell configure event and with it, the states sent + by the compositor. + + If there's no app_id with the supplied name, the compositor will add + the app to a pending list in order to be applied when an application + gets started, or if the application sets its application after the + initial wl_surface.commit request. + + Applications can use this approach if they want to be started in a + tiled orientation position, before creating the xdg-shell toplevel role. + + A none orientation type would make the window go back to the original + maximized mode. If two windows are side by side, returning one of them + back the original mode would mean the other one will be made hidden + and the one doing the request for the none orientation will become + the currently active window. A further activation, using activate_app + request for the other window would make that one active. + + Closing the window in the tiled orientation state implies that either + the background surface will displayed, or in case there was another + applications being shown at that time, will make that application be + returned to the original, maximized state. + + The tiled orientation could be applied independently of each other, + such that a client can transition from one tiled orientation to + another. Note that any other window already present would literally + take the opposite orientation with the one currently being changed. So + tiled orientation modification automatically implies a tile orientation + for any other application already present/active at that time. + + In case there's already a client active at that time, it will be + attributed automatically the opposite tiled orientation, such that two + concurrent applications can be displayed at the same time. + + The orientation tiles can not be combined, and only state at a time + can be active. Only horizontal and vertical tiling is possible. A + horizontal and vertical tile orientation simultaneously is not + possible. + + Input focus is being delivered to the last started/activated window, + such that users can cycle between that one or the other, assumes there's + another window in the first place. + + See xdg_toplevel.set_app_id from the xdg-shell protocol for a + description of app_id. + </description> + <arg name="app_id" type="string"/> + <arg name="orientation" type="uint" enum="tile_orientation"/> + <arg name="output" type="object" interface="wl_output"/> + </request> </interface> <interface name="agl_shell_ext" version="1"> diff --git a/src/compositor.c b/src/compositor.c index 52f1b18..827ef7a 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -1730,6 +1730,7 @@ int wet_main(int argc, char *argv[], const struct weston_testsuite_data *test_da wl_list_init(&ivi.remote_pending_apps); wl_list_init(&ivi.desktop_clients); wl_list_init(&ivi.child_process_list); + wl_list_init(&ivi.pending_apps); /* Prevent any clients we spawn getting our stdin */ os_fd_set_cloexec(STDIN_FILENO); diff --git a/src/desktop.c b/src/desktop.c index 3fd09bc..7875eb9 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -312,6 +312,7 @@ desktop_surface_removed(struct weston_desktop_surface *dsurface, void *userdata) output->area = output->area_saved; } + /* reset the active surface as well */ if (output && output->active && output->active == surface) { output->active->view->is_mapped = false; @@ -342,6 +343,14 @@ desktop_surface_removed(struct weston_desktop_surface *dsurface, void *userdata) weston_view_destroy(surface->view); } + if (surface->role == IVI_SURFACE_ROLE_TILE) { + ivi_layout_reset_split_surfaces(surface->ivi); + // activate previous when resizing back to give input set + // output active for allowing to resizing again if needed + if (output->previous_active) + ivi_layout_activate_by_surf(output, output->previous_active); + } + /* invalidate agl-shell surfaces so we can re-use them when * binding again */ if (surface->role == IVI_SURFACE_ROLE_PANEL) { diff --git a/src/ivi-compositor.h b/src/ivi-compositor.h index 4c04ec7..1c1d678 100644 --- a/src/ivi-compositor.h +++ b/src/ivi-compositor.h @@ -117,6 +117,8 @@ struct ivi_compositor { struct wl_list split_pending_apps; struct wl_list remote_pending_apps; + struct wl_list pending_apps; /** pending_app::link */ + struct wl_listener destroy_listener; struct wl_listener transform_listener; const struct weston_xwayland_surface_api *xwayland_surface_api; @@ -194,6 +196,7 @@ enum ivi_surface_role { IVI_SURFACE_ROLE_SPLIT_V, IVI_SURFACE_ROLE_SPLIT_H, IVI_SURFACE_ROLE_REMOTE, + IVI_SURFACE_ROLE_TILE, }; struct ivi_bounding_box { @@ -229,6 +232,18 @@ struct pending_remote { struct wl_list link; /** ivi_compositor::remote_pending_apps */ }; +struct pending_app { + struct ivi_output *ioutput; + enum ivi_surface_role role; + char *app_id; + struct wl_list link; /** ivi_compositor::pending_apps */ +}; + +struct pending_app_tile { + struct pending_app base; + uint32_t orientation; +}; + struct ivi_desktop_surface { struct ivi_output *pending_output; struct ivi_output *last_output; @@ -278,6 +293,7 @@ struct ivi_surface { struct wl_list link; int focus_count; + uint32_t orientation; struct { enum ivi_surface_flags flags; @@ -522,4 +538,7 @@ parse_activation_area(const char *geometry, struct ivi_output *output); bool is_shell_surface_xwayland(struct ivi_surface *surf); +void +ivi_layout_reset_split_surfaces(struct ivi_compositor *ivi); + #endif diff --git a/src/layout.c b/src/layout.c index 4b8a691..b3a7329 100644 --- a/src/layout.c +++ b/src/layout.c @@ -48,6 +48,7 @@ static const char *ivi_roles_as_string[] = { [IVI_SURFACE_ROLE_SPLIT_V] = "SPLIT_V", [IVI_SURFACE_ROLE_FULLSCREEN] = "FULLSCREEN", [IVI_SURFACE_ROLE_REMOTE] = "REMOTE", + [IVI_SURFACE_ROLE_TILE] = "SPLIT", }; bool @@ -284,6 +285,13 @@ ivi_layout_activate_complete(struct ivi_output *output, woutput->x + output->area.x, woutput->y + output->area.y); + /* reset any previous orientation */ + if (surf->orientation != AGL_SHELL_TILE_ORIENTATION_NONE) { + weston_log("%s() resetting itself to none orientation\n", __func__); + surf->orientation = AGL_SHELL_TILE_ORIENTATION_NONE; + weston_desktop_surface_set_orientation(surf->dsurface, + surf->orientation); + } view->is_mapped = true; surf->mapped = true; view->surface->is_mapped = true; @@ -1025,6 +1033,22 @@ ivi_layout_surface_is_split_or_fullscreen(struct ivi_surface *surf) } void +ivi_layout_reset_split_surfaces(struct ivi_compositor *ivi) +{ + struct ivi_surface *ivisurf; + + wl_list_for_each(ivisurf, &ivi->surfaces, link) { + struct weston_desktop_surface *dsurf = ivisurf->dsurface; + + if (ivisurf->orientation != AGL_SHELL_TILE_ORIENTATION_NONE) { + weston_log("%s() resetting apps to none orientation\n", __func__); + ivisurf->orientation = AGL_SHELL_TILE_ORIENTATION_NONE; + weston_desktop_surface_set_orientation(dsurf, ivisurf->orientation); + } + } +} + +void ivi_layout_activate_by_surf(struct ivi_output *output, struct ivi_surface *surf) { struct ivi_compositor *ivi = output->ivi; @@ -1058,15 +1082,26 @@ ivi_layout_activate_by_surf(struct ivi_output *output, struct ivi_surface *surf) ivi_layout_fullscreen_re_add(surf); return; } +#if 0 + /* reset tile to desktop to allow to resize correctly */ + if (surf->role == IVI_SURFACE_ROLE_TILE && output->active == surf) { + weston_log("%s() resetting tile role!\n", __func__); + surf->role = IVI_SURFACE_ROLE_DESKTOP; + } +#endif /* do not 're'-activate surfaces that are split or active */ if (surf == output->active || - ivi_layout_surface_is_split_or_fullscreen(surf)) { + ivi_layout_surface_is_split_or_fullscreen(surf) || + surf->role != IVI_SURFACE_ROLE_DESKTOP) { weston_log("Application %s is already active on output %s\n", app_id, output->output->name); return; } + // destroy any split types to allow correct re-activation + ivi_layout_reset_split_surfaces(surf->ivi); + if (surf->role == IVI_SURFACE_ROLE_REMOTE) { struct ivi_output *remote_output = ivi_layout_find_with_app_id(app_id, ivi); @@ -1149,8 +1184,13 @@ ivi_layout_get_output_from_surface(struct ivi_surface *surf) case IVI_SURFACE_ROLE_REMOTE: ivi_output = surf->remote.output; break; + case IVI_SURFACE_ROLE_TILE: + ivi_output = surf->current_completed_output; + break; case IVI_SURFACE_ROLE_NONE: default: + if (surf->view->output) + return to_ivi_output(surf->view->output); break; } diff --git a/src/shell.c b/src/shell.c index 2b6bd63..7f445e6 100644 --- a/src/shell.c +++ b/src/shell.c @@ -49,6 +49,13 @@ static void create_black_curtain_view(struct ivi_output *output); +static void +_ivi_set_shell_surface_split(struct ivi_surface *surface, struct ivi_output *output, + uint32_t orientation, bool to_activate); + +static uint32_t +reverse_orientation(uint32_t orientation); + void agl_shell_desktop_advertise_application_id(struct ivi_compositor *ivi, struct ivi_surface *surface) @@ -587,6 +594,24 @@ ivi_check_pending_surface_desktop(struct ivi_surface *surface, *role = IVI_SURFACE_ROLE_DESKTOP; } +struct pending_app * +ivi_check_pending_app_type(struct ivi_surface *surface, enum ivi_surface_role role) +{ + struct pending_app *papp; + const char *app_id = NULL; + + app_id = weston_desktop_surface_get_app_id(surface->dsurface); + if (!app_id) + return NULL; + + wl_list_for_each(papp, &surface->ivi->pending_apps, link) { + if (strcmp(app_id, papp->app_id) == 0 && papp->role == role) + return papp; + } + + return NULL; +} + void ivi_check_pending_desktop_surface(struct ivi_surface *surface) @@ -621,6 +646,34 @@ ivi_check_pending_desktop_surface(struct ivi_surface *surface) return; } + /* new way of doing it */ + struct pending_app *papp = + ivi_check_pending_app_type(surface, IVI_SURFACE_ROLE_TILE); + if (papp) { + struct pending_app_tile *papp_tile = + container_of(papp, struct pending_app_tile, base); + + // handle the currently active surface + if (papp->ioutput->active) { + _ivi_set_shell_surface_split(papp->ioutput->active, NULL, + reverse_orientation(papp_tile->orientation), false); + } + + surface->role = IVI_SURFACE_ROLE_TILE; + surface->current_completed_output = papp->ioutput; + wl_list_insert(&surface->ivi->surfaces, &surface->link); + + _ivi_set_shell_surface_split(surface, papp->ioutput, + papp_tile->orientation, true); + + /* remove it from pending */ + wl_list_remove(&papp->link); + free(papp->app_id); + free(papp); + + return; + } + /* if we end up here means we have a regular desktop app and * try to activate it */ ivi_set_desktop_surface(surface); @@ -1696,6 +1749,210 @@ shell_set_app_scale(struct wl_client *client, struct wl_resource *res, } static void +_ivi_set_pending_desktop_surface_split(struct wl_resource *output, + const char *app_id, uint32_t orientation) +{ + weston_log("%s() added split surface for app_id '%s' with " + "orientation %d to pending\n", __func__, app_id, orientation); + + struct weston_head *head = weston_head_from_resource(output); + struct weston_output *woutput = weston_head_get_output(head); + struct ivi_output *ivi_output = to_ivi_output(woutput); + + struct pending_app_tile *app_tile = zalloc(sizeof(*app_tile)); + + app_tile->base.app_id = strdup(app_id); + app_tile->base.ioutput = ivi_output; + app_tile->base.role = IVI_SURFACE_ROLE_TILE; + + app_tile->orientation = orientation; + wl_list_insert(&ivi_output->ivi->pending_apps, &app_tile->base.link); +} + + +static uint32_t +reverse_orientation(uint32_t orientation) +{ + + switch (orientation) { + case AGL_SHELL_TILE_ORIENTATION_LEFT: + return AGL_SHELL_TILE_ORIENTATION_RIGHT; + break; + case AGL_SHELL_TILE_ORIENTATION_RIGHT: + return AGL_SHELL_TILE_ORIENTATION_LEFT; + break; + case AGL_SHELL_TILE_ORIENTATION_TOP: + return AGL_SHELL_TILE_ORIENTATION_BOTTOM; + break; + case AGL_SHELL_TILE_ORIENTATION_BOTTOM: + return AGL_SHELL_TILE_ORIENTATION_TOP; + break; + default: + return AGL_SHELL_TILE_ORIENTATION_NONE; + } +} + +static void +_ivi_set_shell_surface_split(struct ivi_surface *surface, struct ivi_output *ioutput, + uint32_t orientation, bool to_activate) +{ + struct ivi_compositor *ivi = surface->ivi; + struct weston_geometry geom = {}; + struct ivi_output *output = NULL; + + int width, height; + int x, y; + + geom = weston_desktop_surface_get_geometry(surface->dsurface); + output = ivi_layout_get_output_from_surface(surface); + + if (!output) + output = ioutput; + + width = output->area.width; + height = output->area.height; + + switch (orientation) { + case AGL_SHELL_TILE_ORIENTATION_LEFT: + case AGL_SHELL_TILE_ORIENTATION_RIGHT: + width /= 2; + break; + case AGL_SHELL_TILE_ORIENTATION_TOP: + case AGL_SHELL_TILE_ORIENTATION_BOTTOM: + height /= 2; + break; + case AGL_SHELL_TILE_ORIENTATION_NONE: + break; + default: + /* nothing */ + assert(!"Invalid orientation passed"); + + } + + x = output->area.x - geom.x; + y = output->area.y - geom.y; + + if (orientation == AGL_SHELL_TILE_ORIENTATION_RIGHT) + x += width; + else if (orientation == AGL_SHELL_TILE_ORIENTATION_BOTTOM) + y += height; + + if (to_activate) { + struct weston_view *ev = surface->view; + struct ivi_shell_seat *ivi_seat = NULL; + struct weston_seat *wseat = get_ivi_shell_weston_first_seat(ivi); + + if (wseat) + ivi_seat = get_ivi_shell_seat(wseat); + + if (!weston_view_is_mapped(ev)) + weston_view_update_transform(ev); + else + weston_layer_entry_remove(&ev->layer_link); + + + // mark view as mapped + ev->is_mapped = true; + ev->surface->is_mapped = true; + surface->mapped = true; + + // update older/new active surface + output->previous_active = output->active; + output->active = surface; + + // add to the layer and inflict damage + weston_view_set_output(ev, output->output); + weston_layer_entry_insert(&ivi->normal.view_list, &ev->layer_link); + weston_view_geometry_dirty(ev); + weston_surface_damage(ev->surface); + + // handle input / keyboard + if (ivi_seat) + ivi_shell_activate_surface(surface, ivi_seat, WESTON_ACTIVATE_FLAG_NONE); + } + + weston_view_set_position(surface->view, x, y); + weston_desktop_surface_set_size(surface->dsurface, width, height); + weston_desktop_surface_set_orientation(surface->dsurface, orientation); + surface->orientation = orientation; + + weston_compositor_schedule_repaint(ivi->compositor); + + weston_log("%s() Setting to x=%d, y=%d, width=%d, height=%d, orientation=%d\n", + __func__, x, y, width, height, orientation); + +} + +static int +shell_ivi_surf_count_split_surfaces(struct ivi_compositor *ivi) +{ + int count = 0; + struct ivi_surface *surf; + + wl_list_for_each(surf, &ivi->surfaces, link) { + if (surf->orientation > AGL_SHELL_TILE_ORIENTATION_NONE) + count++; + } + + return count; +} + + +static +void shell_set_app_split(struct wl_client *client, struct wl_resource *res, + const char *app_id, uint32_t orientation, + struct wl_resource *output_res) +{ + struct ivi_surface *surf; + struct ivi_compositor *ivi = wl_resource_get_user_data(res); + + struct weston_head *head = weston_head_from_resource(output_res); + struct weston_output *woutput = weston_head_get_output(head); + struct ivi_output *output = to_ivi_output(woutput); + + if (!app_id) + return; + + if (shell_ivi_surf_count_split_surfaces(ivi) > 2) { + weston_log("Found more than two split surfaces in tile orientation.\n"); + return; + } + + /* add it as pending until */ + surf = ivi_find_app(ivi, app_id); + if (!surf) { + _ivi_set_pending_desktop_surface_split(output_res, app_id, orientation); + return; + } + + /* otherwise, take actions now */ + weston_log("%s() added split surface for app_id '%s' with orientation %d\n", + __func__, app_id, orientation); + + if (output->previous_active) { + struct weston_view *ev = output->previous_active->view; + + if (!weston_view_is_mapped(ev)) + weston_view_update_transform(ev); + else + weston_layer_entry_remove(&ev->layer_link); + + ev->is_mapped = true; + ev->surface->is_mapped = true; + output->previous_active->mapped = true; + + weston_view_set_output(ev, woutput); + + weston_layer_entry_insert(&ivi->normal.view_list, &ev->layer_link); + + _ivi_set_shell_surface_split(output->previous_active, NULL, + reverse_orientation(orientation), false); + } + + _ivi_set_shell_surface_split(surf, NULL, orientation, false); +} + +static void shell_ext_destroy(struct wl_client *client, struct wl_resource *res) { struct ivi_compositor *ivi = wl_resource_get_user_data(res); @@ -1737,6 +1994,7 @@ static const struct agl_shell_interface agl_shell_implementation = { .set_app_output = shell_set_app_output, .set_app_position = shell_set_app_position, .set_app_scale = shell_set_app_scale, + .set_app_split = shell_set_app_split, }; static const struct agl_shell_ext_interface agl_shell_ext_implementation = { @@ -2042,7 +2300,7 @@ int ivi_shell_create_global(struct ivi_compositor *ivi) { ivi->agl_shell = wl_global_create(ivi->compositor->wl_display, - &agl_shell_interface, 10, + &agl_shell_interface, 11, ivi, bind_agl_shell); if (!ivi->agl_shell) { weston_log("Failed to create wayland global.\n"); |