From 0acbca5d1a9ca5aad361dfc2807f3822223c739d Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Fri, 5 Aug 2022 16:21:34 +0300 Subject: agl-shell: Add split functionality into agl-shell protocol Signed-off-by: Marius Vlad Change-Id: Id19a1865baef6e7c9804a281d583a4ac19421851 --- protocol/agl-shell.xml | 74 +++++++++++++- src/compositor.c | 1 + src/ivi-compositor.h | 17 ++++ src/layout.c | 17 +++- src/shell.c | 255 ++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 360 insertions(+), 4 deletions(-) diff --git a/protocol/agl-shell.xml b/protocol/agl-shell.xml index 84a423c..1f62354 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. - + 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. @@ -65,6 +65,15 @@ + + + + + + + + + Tell the server that this client is ready to be shown. The server @@ -156,5 +165,68 @@ + + + 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 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. + + + + + + diff --git a/src/compositor.c b/src/compositor.c index 7540fe3..ebe96a9 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -1646,6 +1646,7 @@ int wet_main(int argc, char *argv[], const struct weston_testsuite_data *test_da wl_list_init(&ivi.split_pending_apps); wl_list_init(&ivi.remote_pending_apps); wl_list_init(&ivi.desktop_clients); + wl_list_init(&ivi.pending_apps); /* Prevent any clients we spawn getting our stdin */ os_fd_set_cloexec(STDIN_FILENO); diff --git a/src/ivi-compositor.h b/src/ivi-compositor.h index dff11b9..11e7290 100644 --- a/src/ivi-compositor.h +++ b/src/ivi-compositor.h @@ -104,6 +104,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 weston_layer hidden; @@ -175,6 +177,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 { @@ -182,6 +185,18 @@ struct ivi_bounding_box { int width; int height; }; +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 pending_popup { struct ivi_output *ioutput; char *app_id; @@ -280,6 +295,8 @@ struct ivi_surface { HIDDEN, } state; + uint32_t orientation; + enum ivi_surface_role role; union { struct ivi_desktop_surface desktop; diff --git a/src/layout.c b/src/layout.c index 5d24a1f..df632d8 100644 --- a/src/layout.c +++ b/src/layout.c @@ -47,6 +47,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] = "TILE", }; const char * @@ -206,6 +207,10 @@ ivi_layout_activate_complete(struct ivi_output *output, woutput->x + output->area.x, woutput->y + output->area.y); + 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; @@ -804,10 +809,16 @@ ivi_layout_activate_by_surf(struct ivi_output *output, struct ivi_surface *surf) return; } + /* reset tile to desktop to allow to resize correctly */ + if (surf->role == IVI_SURFACE_ROLE_TILE && output->active == surf) + surf->role = IVI_SURFACE_ROLE_DESKTOP; + /* do not 're'-activate surfaces that are split or active */ - if (surf == output->active || - ivi_layout_surface_is_split_or_fullscreen(surf)) + if ((surf == output->active && surf->role != IVI_SURFACE_ROLE_DESKTOP) || + ivi_layout_surface_is_split_or_fullscreen(surf)) { + weston_log("Found split || fullscreen surface. Refusing to activate!\n"); return; + } if (surf->role == IVI_SURFACE_ROLE_REMOTE) { struct ivi_output *remote_output = @@ -883,6 +894,8 @@ ivi_layout_get_output_from_surface(struct ivi_surface *surf) 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 28c1117..8f31c13 100644 --- a/src/shell.c +++ b/src/shell.c @@ -38,6 +38,7 @@ #include #include "shared/os-compatibility.h" +#include "shared/helpers.h" #include "agl-shell-server-protocol.h" #include "agl-shell-desktop-server-protocol.h" @@ -49,6 +50,13 @@ static void create_black_surface_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) @@ -662,6 +670,25 @@ 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; + + // get app id + 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) @@ -696,6 +723,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; + 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); @@ -1287,12 +1342,210 @@ shell_destroy(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; + + ev->is_mapped = true; + ev->surface->is_mapped = true; + output->previous_active->mapped = true; + + weston_view_update_transform(ev); + 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 const struct agl_shell_interface agl_shell_implementation = { .destroy = shell_destroy, .ready = shell_ready, .set_background = shell_set_background, .set_panel = shell_set_panel, .activate_app = shell_activate_app, + .set_app_split = shell_set_app_split, }; static void @@ -1520,7 +1773,7 @@ int ivi_shell_create_global(struct ivi_compositor *ivi) { ivi->agl_shell = wl_global_create(ivi->compositor->wl_display, - &agl_shell_interface, 2, + &agl_shell_interface, 3, ivi, bind_agl_shell); if (!ivi->agl_shell) { weston_log("Failed to create wayland global.\n"); -- cgit 1.2.3-korg