agl-shell: Add split functionality into agl-shell protocol sandbox/mvlad/split-v3-update
authorMarius Vlad <marius.vlad@collabora.com>
Fri, 5 Aug 2022 13:21:34 +0000 (16:21 +0300)
committerMarius Vlad <marius.vlad@collabora.com>
Tue, 16 Aug 2022 20:08:10 +0000 (23:08 +0300)
Signed-off-by: Marius Vlad <marius.vlad@collabora.com>
Change-Id: Id19a1865baef6e7c9804a281d583a4ac19421851

protocol/agl-shell.xml
src/compositor.c
src/ivi-compositor.h
src/layout.c
src/shell.c

index 84a423c..1f62354 100644 (file)
@@ -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="2">
+  <interface name="agl_shell" version="3">
     <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.
       <entry name="right" value="3"/>
     </enum>
 
+    <enum name="tile_orientation" since="3">
+      <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
       </description>
     </event>
 
+    <request name="set_app_split" since="3">
+      <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 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>
 </protocol>
index 7540fe3..ebe96a9 100644 (file)
@@ -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);
index dff11b9..11e7290 100644 (file)
@@ -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;
index 5d24a1f..df632d8 100644 (file)
@@ -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;
        }
 
index 28c1117..8f31c13 100644 (file)
@@ -38,6 +38,7 @@
 #include <libweston/config-parser.h>
 
 #include "shared/os-compatibility.h"
+#include "shared/helpers.h"
 
 #include "agl-shell-server-protocol.h"
 #include "agl-shell-desktop-server-protocol.h"
 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");