From: Marius Vlad Date: Tue, 3 Mar 2020 12:25:25 +0000 (+0200) Subject: policy: Init policy framework X-Git-Url: https://gerrit.automotivelinux.org/gerrit/gitweb?a=commitdiff_plain;ds=sidebyside;h=60e91c02c365355a94441eddc1851babeb440c73;p=src%2Fagl-compositor.git policy: Init policy framework This patch adds the policy framework, comprised from hooks which a policy engine can further customize, and which are checked upon surface creation, commit and activation. Users should create specialized versions of these callbacks when creating a policy engine. Further more, it adds a protocol to add states, events and policy rules. By default, the protocol has a few known states like 'start' or 'stop', but also 'show' and 'hide' as events. A policy rule allow to define the setup in which an event can happen based on the state of the rule, the action event itself, the application and (optional) an timeout. The policy rules are there to specify the state, the event, and application, but it is ultimately handled by a hook which will be called to handle the event. The compositor will arrange to pass all that information back to the handler, so the policy engine is in control to check in what circumstances the policy rule can be satisfied. These policy rules allow to handle transitional states which are common in AGL. For instance: If one would want to display the application 'navigation' in 3 seconds after and state has been changed to 'start', it should do add the following rule: ('navigation', STATE_START, SHOW, 3000ms, main_output) Then, when a 'start' state is propagated to the client shell and the client shell will relay it back to the compositor (using the 'apply' request), will arm an timer to execute the event handler after a 3000ms timeout. Signed-off-by: Marius Vlad Change-Id: Ie03c5f9b1ddb964949e4f9797cbbe2dd2b32a6b6 --- diff --git a/meson.build b/meson.build index 640be2e..4d89c73 100644 --- a/meson.build +++ b/meson.build @@ -47,11 +47,13 @@ dir_wp_base = dep_wp.get_pkgconfig_variable('pkgdatadir') agl_shell_xml = files('protocol/agl-shell.xml') agl_shell_desktop_xml = files('protocol/agl-shell-desktop.xml') +agl_shell_policy_xml = files('protocol/agl-shell-policy.xml') xdg_shell_xml = join_paths(dir_wp_base, 'stable', 'xdg-shell', 'xdg-shell.xml') protocols = [ { 'name': 'agl-shell', 'source': 'internal' }, { 'name': 'agl-shell-desktop', 'source': 'internal' }, + { 'name': 'agl-shell-policy', 'source': 'internal' }, { 'name': 'xdg-shell', 'source': 'wp-stable' }, ] @@ -120,13 +122,16 @@ srcs_agl_compositor = [ 'src/main.c', 'src/desktop.c', 'src/layout.c', + 'src/policy.c', 'src/shell.c', 'shared/option-parser.c', 'shared/os-compatibility.c', agl_shell_server_protocol_h, agl_shell_desktop_server_protocol_h, + agl_shell_policy_server_protocol_h, agl_shell_protocol_c, agl_shell_desktop_protocol_c, + agl_shell_policy_protocol_c, xdg_shell_protocol_c, ] diff --git a/protocol/agl-shell-policy.xml b/protocol/agl-shell-policy.xml new file mode 100644 index 0000000..649c9b5 --- /dev/null +++ b/protocol/agl-shell-policy.xml @@ -0,0 +1,128 @@ + + + + Copyright © 2020 Collabora, Ltd. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + This protocol provides the means to signal state changes to the compositor + and to add generic policy rules. The client is ultimately responsible for + writing the policy management, as this only exposes the API to do so. Any + policy added with 'add' request should be also correlated with the + policy_api::policy_rule_try_event callback that it will receive + all the arguments passed in the 'add' request. + + The callback receives all information provided in the 'add' request, and + will be executed once the state has been modified. + The initial state will be state to 'invalid', such that any state change + will trigger a check for all policy rules. + + This extension provides by default, a few generic states (see state + enum) and a two default events (see event enum). Users can add additional + states and events using 'add_state' and 'add_event' request. + All the default states and events are already known so there's no need + them. Adding a new policy rule will verify if the states and events are known, so + it is required to add, before-hand, a new state or event. + + Upon a state change, the client would inform the compositor using the + 'apply' request that the current state has been modified. + The compositor will then verify if any of, previously added rule policies, + matches that of the state, and starts to execute the installed hooks for it. + + + + + + + + + + + + + + + + + + + + + + + + + Add a new state to the state list. + + + + + + + + Add a new event the events list. + + + + + + + + Adds a policy rule for later processing, when the state change has been + requested upon 'apply' request. The timeout (expressed in ms) will only + be taken into consideration if bigger than zero. + + Users wanting to check the 'event' or 'state' should alter the + hook associated with it. + + + + + + + + + + + Signals a state change, which should trigger a check of current + policy rules that were added so far. Users that require additional + states should use 'add_state' before in case they want to use that new + state. The same is valid for events. + + + + + + + Event sent when the 'apply' request has finished. Client should verify + that 'state' received back matches that the one being applied in the + 'apply' request. + + Do note, that an invalid state could be sent back by the compositor in + case of error. + + + + + + + diff --git a/src/desktop.c b/src/desktop.c index eaace19..f3ea82b 100644 --- a/src/desktop.c +++ b/src/desktop.c @@ -24,6 +24,7 @@ */ #include "ivi-compositor.h" +#include "policy.h" #include #include @@ -80,6 +81,13 @@ desktop_surface_added(struct weston_desktop_surface *dsurface, void *userdata) surface->dsurface = dsurface; surface->role = IVI_SURFACE_ROLE_NONE; + if (ivi->policy->api.surface_create && + !ivi->policy->api.surface_create(surface, ivi)) { + free(surface); + wl_client_post_no_memory(client); + return; + } + weston_desktop_surface_set_user_data(dsurface, surface); if (ivi->shell_client.ready) { @@ -131,6 +139,12 @@ desktop_committed(struct weston_desktop_surface *dsurface, { struct ivi_surface *surface = weston_desktop_surface_get_user_data(dsurface); + struct ivi_policy *policy = surface->ivi->policy; + + if (policy->api.surface_commited && + !policy->api.surface_commited(surface, surface->ivi)) + return; + weston_compositor_schedule_repaint(surface->ivi->compositor); switch (surface->role) { diff --git a/src/ivi-compositor.h b/src/ivi-compositor.h index 446390d..4da12bc 100644 --- a/src/ivi-compositor.h +++ b/src/ivi-compositor.h @@ -82,6 +82,7 @@ struct ivi_compositor { struct wl_list surfaces; /* ivi_surface.link */ struct weston_desktop *desktop; + struct ivi_policy *policy; struct wl_list pending_surfaces; diff --git a/src/layout.c b/src/layout.c index 0c65b6d..5f5a99e 100644 --- a/src/layout.c +++ b/src/layout.c @@ -24,6 +24,7 @@ */ #include "ivi-compositor.h" +#include "policy.h" #include #include @@ -345,10 +346,17 @@ ivi_layout_activate(struct ivi_output *output, const char *app_id) struct weston_desktop_surface *dsurf; struct weston_view *view; struct weston_geometry geom; + struct ivi_policy *policy = output->ivi->policy; surf = ivi_find_app(ivi, app_id); if (!surf) return; + + if (policy->api.surface_activate && + !policy->api.surface_activate(surf, surf->ivi)) { + return; + } + #ifdef AGL_COMP_DEBUG weston_log("Found app_id %s\n", app_id); #endif diff --git a/src/policy.c b/src/policy.c new file mode 100644 index 0000000..939a97c --- /dev/null +++ b/src/policy.c @@ -0,0 +1,367 @@ +#include +#include +#include + +#include "shared/helpers.h" +#include "ivi-compositor.h" + +#include "policy.h" +#include "agl-shell-policy-server-protocol.h" + +static struct wl_global * +ivi_policy_proto_create(struct ivi_compositor *ivi, struct ivi_policy *policy); + +static void +ivi_policy_remove_state_event(struct state_event *st_ev) +{ + free(st_ev->name); + wl_list_remove(&st_ev->link); + free(st_ev); +} + +static void +ivi_policy_destroy_state_event(struct wl_list *list) +{ + struct state_event *st_ev, *tmp_st_ev; + wl_list_for_each_safe(st_ev, tmp_st_ev, list, link) + ivi_policy_remove_state_event(st_ev); +} + +static struct state_event * +ivi_policy_state_event_create(uint32_t val, const char *value) +{ + struct state_event *ev_st = zalloc(sizeof(*ev_st)); + size_t value_len = strlen(value); + + ev_st->value = val; + ev_st->name = zalloc(sizeof(char) * value_len + 1); + memcpy(ev_st->name, value, value_len); + + return ev_st; +} + +static void +ivi_policy_add_state(struct wl_client *client, + struct wl_resource *res, uint32_t state, const char *value) +{ + struct ivi_policy *policy = wl_resource_get_user_data(res); + struct state_event *ev_st = ivi_policy_state_event_create(state, value); + wl_list_insert(&policy->states, &ev_st->link); +} + +static void +ivi_policy_add_event(struct wl_client *client, + struct wl_resource *res, uint32_t ev, const char *value) +{ + struct ivi_policy *policy = wl_resource_get_user_data(res); + struct state_event *ev_st = ivi_policy_state_event_create(ev, value); + wl_list_insert(&policy->events, &ev_st->link); +} + +/* note, if protocol modifications, adapt here as well */ +static void +ivi_policy_add_default_states(struct ivi_policy *policy) +{ + const char *default_states[] = { "invalid", "start", "stop", "reverse" }; + for (uint32_t i = 0; i < ARRAY_LENGTH(default_states); i ++) { + struct state_event *ev_st = + ivi_policy_state_event_create(i, default_states[i]); + wl_list_insert(&policy->states, &ev_st->link); + } +} + +/* note, if protocol modifications, adapt here as well */ +static void +ivi_policy_add_default_events(struct ivi_policy *policy) +{ + const char *default_events[] = { "show", "hide" }; + for (uint32_t i = 0; i < ARRAY_LENGTH(default_events); i ++) { + struct state_event *ev_st = + ivi_policy_state_event_create(i, default_events[i]); + wl_list_insert(&policy->events, &ev_st->link); + } +} + +static void +ivi_policy_try_event(struct ivi_a_policy *a_policy) +{ + struct ivi_policy *policy = a_policy->policy; + + if (policy->api.policy_rule_try_event) + return policy->api.policy_rule_try_event(a_policy); +} + +static int +ivi_policy_try_event_timeout(void *user_data) +{ + struct ivi_a_policy *a_policy = user_data; + ivi_policy_try_event(a_policy); + return 0; +} + +static void +ivi_policy_setup_event_timeout(struct ivi_policy *ivi_policy, + struct ivi_a_policy *a_policy) +{ + struct ivi_compositor *ivi = ivi_policy->ivi; + struct wl_display *wl_display = ivi->compositor->wl_display; + struct wl_event_loop *loop = wl_display_get_event_loop(wl_display); + + a_policy->timer = wl_event_loop_add_timer(loop, + ivi_policy_try_event_timeout, + a_policy); + + wl_event_source_timer_update(a_policy->timer, a_policy->timeout); +} + +static void +ivi_policy_check_policies(struct wl_listener *listener, void *data) +{ + struct ivi_a_policy *a_policy; + struct ivi_policy *ivi_policy = + wl_container_of(listener, ivi_policy, listener_check_policies); + + ivi_policy->state_change_in_progress = true; + wl_list_for_each(a_policy, &ivi_policy->policies, link) { + if (ivi_policy->current_state == a_policy->state) { + /* check the timeout first to see if there's a timeout */ + if (a_policy->timeout > 0) + ivi_policy_setup_event_timeout(ivi_policy, + a_policy); + else + ivi_policy_try_event(a_policy); + } + } + + ivi_policy->previous_state = ivi_policy->current_state; + ivi_policy->state_change_in_progress = false; + agl_shell_policy_send_done(ivi_policy->resource, + ivi_policy->current_state); +} + +/* + * The generic way would be the following: + * + * - 'car' is in 'state' -> + * { do 'event' for app 'app_id' at 'timeout' time if same state as 'car_state' } + * + * a 0 timeout means, immediately, a timeout > 0, means to install timer an + * execute when timeout expires + * + * The following happens: + * 'car' changes its state -> verify what policy needs to be run + * 'car' in same state -> no action + * + */ +struct ivi_policy * +ivi_policy_create(struct ivi_compositor *ivi, + const struct ivi_policy_api *api, void *user_data) +{ + struct ivi_policy *policy = zalloc(sizeof(*policy)); + + policy->user_data = user_data; + policy->ivi = ivi; + policy->state_change_in_progress = false; + + policy->api.struct_size = + MIN(sizeof(struct ivi_policy_api), api->struct_size); + memcpy(&policy->api, api, policy->api.struct_size); + + policy->policy_shell = ivi_policy_proto_create(ivi, policy); + if (!policy->policy_shell) { + free(policy); + return NULL; + } + + wl_signal_init(&policy->signal_state_change); + + policy->listener_check_policies.notify = ivi_policy_check_policies; + wl_signal_add(&policy->signal_state_change, + &policy->listener_check_policies); + + policy->current_state = AGL_SHELL_POLICY_STATE_INVALID; + policy->previous_state = AGL_SHELL_POLICY_STATE_INVALID; + + wl_list_init(&policy->policies); + wl_list_init(&policy->events); + wl_list_init(&policy->states); + + /* add the default states and enums */ + ivi_policy_add_default_states(policy); + ivi_policy_add_default_events(policy); + + return policy; +} + +void +ivi_policy_destroy(struct ivi_policy *ivi_policy) +{ + struct ivi_a_policy *a_policy, *a_policy_tmp; + + if (!ivi_policy) + return; + + wl_list_for_each_safe(a_policy, a_policy_tmp, + &ivi_policy->policies, link) { + free(a_policy->app_id); + wl_list_remove(&a_policy->link); + free(a_policy); + } + + ivi_policy_destroy_state_event(&ivi_policy->states); + ivi_policy_destroy_state_event(&ivi_policy->events); + + if (ivi_policy->policy_shell) + wl_global_destroy(ivi_policy->policy_shell); + + free(ivi_policy); +} + +/* verifies if the state is one has been added */ +static bool +ivi_policy_state_is_known(uint32_t state, struct ivi_policy *policy) +{ + struct state_event *ev_st; + + wl_list_for_each(ev_st, &policy->states, link) { + if (ev_st->value == state) { + return true; + } + } + + return false; +} + +static void +ivi_policy_add(struct wl_client *client, struct wl_resource *res, + const char *app_id, uint32_t state, uint32_t event, + uint32_t timeout, struct wl_resource *output_res) +{ + size_t app_id_len; + struct weston_head *head = weston_head_from_resource(output_res); + struct weston_output *woutput = weston_head_get_output(head); + struct ivi_a_policy *c_policy = zalloc(sizeof(*c_policy)); + + struct ivi_output *output = to_ivi_output(woutput); + struct ivi_policy *policy = wl_resource_get_user_data(res); + + assert(!policy); + + if (policy->state_change_in_progress) { + weston_log("State change in progress\n"); + wl_resource_post_event(res, + AGL_SHELL_POLICY_ERROR_POLICY_STATE_CHANGE_IN_PROGRESS, + "State change in progress"); + return; + } + + /* we should be allow to do this in the first place, only if the + * hooks allows us to */ + if (policy->api.policy_rule_allow_to_add && + !policy->api.policy_rule_allow_to_add(policy)) { + wl_resource_post_event(res, + AGL_SHELL_POLICY_ERROR_POLICY_NOT_ALLOWED, + "Not allow to add policy"); + return; + } + + if (!ivi_policy_state_is_known(state, policy)) { + wl_resource_post_event(res, + AGL_SHELL_POLICY_ERROR_POLICY_STATE_UNKNOWN, + "State is not known, please add it"); + return; + } + + c_policy = zalloc(sizeof(*c_policy)); + + app_id_len = strlen(app_id); + c_policy->app_id = zalloc(sizeof(char) * app_id_len + 1); + memcpy(c_policy->app_id, app_id, app_id_len); + + c_policy->state = state; + c_policy->event = event; + c_policy->timeout = timeout; + c_policy->output = output; + c_policy->policy = policy; + + wl_list_insert(&policy->policies, &c_policy->link); +} + +/* we start with 'invalid' state, so a initial state to even 'stop' should + * trigger a check of policies + */ +static void +ivi_policy_state_change(struct wl_client *client, struct wl_resource *res, + uint32_t state) +{ + struct ivi_policy *policy = wl_resource_get_user_data(res); + bool found_state = false; + + if (!policy) { + weston_log("Failed to retrieve policy!\n"); + return; + } + + /* FIXME: should send here AGL_SHELL_POLICY_STATE_INVALID? */ + if (policy->current_state == state) { + /* send done with the same state value back */ + agl_shell_policy_send_done(policy->resource, + policy->current_state); + return; + } + + /* if we don't know the state, make sure it is first added */ + found_state = ivi_policy_state_is_known(state, policy); + if (!found_state) { + agl_shell_policy_send_done(policy->resource, + AGL_SHELL_POLICY_STATE_INVALID); + return; + } + + /* current_state is actually the new state */ + policy->current_state = state; + + /* signal that we need to check the current policies */ + wl_signal_emit(&policy->signal_state_change, policy); +} + +static const struct agl_shell_policy_interface ivi_policy_interface = { + ivi_policy_add_state, + ivi_policy_add_event, + ivi_policy_add, + ivi_policy_state_change, +}; + +static void +ivi_policy_bind(struct wl_client *client, void *data, + uint32_t version, uint32_t id) +{ + struct ivi_policy *ivi_policy = data; + struct wl_resource *resource; + + resource = wl_resource_create(client, &agl_shell_policy_interface, + version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + + wl_resource_set_implementation(resource, &ivi_policy_interface, + ivi_policy, NULL); + ivi_policy->resource = resource; +} + +static struct wl_global * +ivi_policy_proto_create(struct ivi_compositor *ivi, struct ivi_policy *policy) +{ + struct wl_global *policy_global = NULL; + + if (ivi->policy) + return NULL; + + policy_global = wl_global_create(ivi->compositor->wl_display, + &agl_shell_policy_interface, 1, + policy, ivi_policy_bind); + + return policy_global; +} diff --git a/src/policy.h b/src/policy.h new file mode 100644 index 0000000..bccc5ef --- /dev/null +++ b/src/policy.h @@ -0,0 +1,70 @@ +#ifndef POLICY_H +#define POLICY_H + +#include "ivi-compositor.h" +#include "agl-shell-policy-server-protocol.h" + +struct ivi_policy; + +struct state_event { + uint32_t value; + char *name; + struct wl_list link; /* ivi_policy::states or ivi_policy::events */ +}; + +struct ivi_a_policy { + struct ivi_policy *policy; + + char *app_id; + uint32_t state; + uint32_t event; + uint32_t timeout; + struct ivi_output *output; + struct wl_event_source *timer; + + struct wl_list link; /* ivi_policy::ivi_policies */ +}; + +struct ivi_policy_api { + size_t struct_size; + + bool (*surface_create)(struct ivi_surface *surf, void *user_data); + bool (*surface_commited)(struct ivi_surface *surf, void *user_data); + bool (*surface_activate)(struct ivi_surface *surf, void *user_data); + + bool (*policy_rule_allow_to_add)(void *user_data); + void (*policy_rule_try_event)(struct ivi_a_policy *a_policy); +}; + +struct ivi_policy { + struct ivi_compositor *ivi; + struct ivi_policy_api api; + void *user_data; + + /* used to inject policies back to the compositor */ + struct wl_global *policy_shell; + struct wl_resource *resource; + struct wl_list policies; /* ivi_policy_inject::link */ + + uint32_t current_state; + uint32_t previous_state; + bool state_change_in_progress; + + struct wl_list states; /* state_event::link */ + struct wl_list events; /* state_event::link */ + + struct wl_listener listener_check_policies; + struct wl_signal signal_state_change; +}; + + +struct ivi_policy * +ivi_policy_create(struct ivi_compositor *compositor, + const struct ivi_policy_api *api, void *user_data); +void +ivi_policy_destroy(struct ivi_policy *ivi_policy); + +int +ivi_policy_init(struct ivi_compositor *ivi); + +#endif