From d44f07a0c2cc410414bfd7b338ee071c17422a0a Mon Sep 17 00:00:00 2001 From: Marius Vlad Date: Mon, 4 Dec 2023 18:17:00 +0200 Subject: [PATCH 2/2] display: Add support for agl_shell version 8 Signed-off-by: Marius Vlad --- shell/wayland/display.cc | 156 ++++++++++++++++++++++-- shell/wayland/display.h | 58 +++++++++ third_party/agl/protocol/agl-shell.xml | 160 ++++++++++++++++++++++++- 3 files changed, 366 insertions(+), 8 deletions(-) diff --git a/shell/wayland/display.cc b/shell/wayland/display.cc index 3ee814a..aba050a 100644 --- a/shell/wayland/display.cc +++ b/shell/wayland/display.cc @@ -220,7 +220,7 @@ void Display::registry_handle_global(void* data, if (version >= 2) { d->m_agl.shell = static_cast( wl_registry_bind(registry, name, &agl_shell_interface, - std::min(static_cast(4), version))); + std::min(static_cast(8), version))); agl_shell_add_listener(d->m_agl.shell, &agl_shell_listener, data); } else { d->m_agl.shell = static_cast( @@ -980,6 +980,148 @@ void Display::agl_shell_bound_fail(void* data, struct agl_shell* shell) { d->m_agl.bound_ok = false; } +void Display::addAppToStack(std::string app_id) { + if (app_id == "homescreen") + return; + + bool found_app = false; + for (auto& i : apps_stack) { + if (i == app_id) { + found_app = true; + break; + } + } + + if (!found_app) { + apps_stack.push_back(app_id); + } else { + // fixme + } +} + +int Display::find_output_by_name(std::string output_name) { + int index = 0; + for (auto& i : m_all_outputs) { + if (i->name == output_name) { + return index; + } + index++; + } + + return -1; +} + +void Display::activateApp(std::string app_id) { + int default_output_index = 0; + + FML_LOG(INFO) << "got app_id " << app_id; + + // search for a pending application which might have a different output + auto iter = pending_app_list.begin(); + bool found_pending_app = false; + while (iter != pending_app_list.end()) { + auto app_to_search = iter->first; + FML_LOG(INFO) << "searching for " << app_to_search; + + if (app_to_search == app_id) { + found_pending_app = true; + break; + } + + iter++; + } + + if (found_pending_app) { + auto output_name = iter->second; + default_output_index = find_output_by_name(output_name); + + FML_LOG(INFO) << "Found app_id " << app_id << " at all"; + + if (default_output_index < 0) { + // try with remoting-remote-X which is the streaming + std::string new_remote_output = "remoting-" + output_name; + + default_output_index = find_output_by_name(new_remote_output); + if (default_output_index < 0) { + FML_LOG(INFO) << "Not activating app_id " << app_id << " at all"; + return; + } + } + + pending_app_list.erase(iter); + } + + FML_LOG(INFO) << "Activating app_id " << app_id << " on output " + << default_output_index; + agl_shell_activate_app(m_agl.shell, app_id.c_str(), + m_all_outputs[default_output_index]->output); + wl_display_flush(m_display); +} + +void Display::deactivateApp(std::string app_id) { + for (auto& i : apps_stack) { + if (i == app_id) { + // remove it from apps_stack + apps_stack.remove(i); + if (!apps_stack.empty()) + activateApp(apps_stack.back()); + break; + } + } +} + +void Display::processAppStatusEvent(const char* app_id, + const std::string event_type) { + if (!m_agl.shell) + return; + + if (event_type == "started") { + activateApp(std::string(app_id)); + } else if (event_type == "terminated") { + deactivateApp(std::string(app_id)); + } else if (event_type == "deactivated") { + // not handled + } +} + +void Display::agl_shell_app_on_output(void* data, + struct agl_shell* agl_shell, + const char* app_id, + const char* output_name) { + auto* d = static_cast(data); + + FML_LOG(INFO) << "Gove event app_on_out app_id " << app_id << " output name " + << output_name; + + // a couple of use-cases, if there is no app_id in the app_list then it + // means this is a request to map the application, from the start to a + // different output that the default one. We'd get an + // AGL_SHELL_APP_STATE_STARTED which will handle activation. + // + // if there's an app_id then it means we might have gotten an event to + // move the application to another output; so we'd need to process it + // by explicitly calling processAppStatusEvent() which would ultimately + // activate the application on other output. We'd have to pick-up the + // last activated window and activate the default output. + // + // finally if the outputs are identical probably that's an user-error - + // but the compositor won't activate it again, so we don't handle that. + std::pair new_pending_app = + std::pair(std::string(app_id), std::string(output_name)); + d->pending_app_list.push_back(new_pending_app); + + auto iter = d->apps_stack.begin(); + while (iter != d->apps_stack.end()) { + if (*iter == std::string(app_id)) { + FML_LOG(INFO) << "Gove event to move " << app_id << " to another output " + << output_name; + d->processAppStatusEvent(app_id, std::string("started")); + break; + } + iter++; + } +} + void Display::agl_shell_app_state(void* data, struct agl_shell* /* agl_shell */, const char* app_id, @@ -991,12 +1133,7 @@ void Display::agl_shell_app_state(void* data, FML_DLOG(INFO) << "Got AGL_SHELL_APP_STATE_STARTED for app_id " << app_id; if (d->m_agl.shell) { - // we always assume the first output advertised by the wl_output - // interface - unsigned int default_output_index = 0; - - agl_shell_activate_app(d->m_agl.shell, app_id, - d->m_all_outputs[default_output_index]->output); + d->processAppStatusEvent(app_id, std::string("started")); } break; @@ -1007,6 +1144,10 @@ void Display::agl_shell_app_state(void* data, case AGL_SHELL_APP_STATE_ACTIVATED: FML_DLOG(INFO) << "Got AGL_SHELL_APP_STATE_ACTIVATED for app_id " << app_id; + d->addAppToStack(std::string(app_id)); + break; + case AGL_SHELL_APP_STATE_DEACTIVATED: + d->processAppStatusEvent(app_id, std::string("deactivated")); break; default: break; @@ -1017,6 +1158,7 @@ const struct agl_shell_listener Display::agl_shell_listener = { .bound_ok = agl_shell_bound_ok, .bound_fail = agl_shell_bound_fail, .app_state = agl_shell_app_state, + .app_on_output = agl_shell_app_on_output, }; void Display::ivi_wm_surface_visibility(void* /* data */, diff --git a/shell/wayland/display.h b/shell/wayland/display.h index a0756f0..b919047 100644 --- a/shell/wayland/display.h +++ b/shell/wayland/display.h @@ -18,6 +18,7 @@ #pragma once #include +#include #include #include #include @@ -271,6 +272,44 @@ class Display { */ std::pair GetVideoModeSize(uint32_t index); + /** + * @brief deactivate/hide the application pointed by app_id + * @param[in] app_id the app_id + * @relation + * agl_shell + */ + void deactivateApp(std::string app_id); + /** + * @brief activate/show the application pointed by app_id + * @param[in] app_id the app_id + * @relation + * agl_shell + */ + void activateApp(std::string app_id); + /** + * @brief Add app_id to a list of list applications + * @param[in] app_id the app_id + * @relation + * agl_shell + */ + void addAppToStack(std::string app_id); + /** + * @brief Helper to retrieve the output using its output_name + * @param[in] output_name a std::string representing the output + * @retval an integer that can used to get the proper output + * @relation + * agl_sell + */ + int find_output_by_name(std::string output_name); + /** + * @brief helper to process the application status + * @param[in] app_id an array of char + * @param[in] event_type a std::string representing the type of event (started/stopped/terminated) + * @relation + * agl_shell + */ + void processAppStatusEvent(const char* app_id, const std::string event_type); + private: std::shared_ptr m_flutter_engine; @@ -300,6 +339,9 @@ class Display { uint32_t version = 0; } m_agl; + std::list apps_stack; + std::list> pending_app_list; + struct ivi_shell { struct ivi_application* application = nullptr; struct ivi_wm* ivi_wm = nullptr; @@ -982,6 +1024,22 @@ class Display { const char* app_id, uint32_t state); + /** + * @brief AGL app_app_on_output event + * @param[in,out] data Data of type Display + * @param[in] shell No use + * @param[in] app_id the application id for which this event was sent + * @param[in] state the state: CREATED/TERMINATED/ACTIVATED/DEACTIVATED + * @return void + * @relation + * wayland, agl-shell + * @note Do nothing + */ + static void agl_shell_app_on_output(void* data, + struct agl_shell* agl_shell, + const char* app_id, + const char* output_name); + static const struct agl_shell_listener agl_shell_listener; /** diff --git a/third_party/agl/protocol/agl-shell.xml b/third_party/agl/protocol/agl-shell.xml index bf5ab02..e010a80 100644 --- a/third_party/agl/protocol/agl-shell.xml +++ b/third_party/agl/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. @@ -200,5 +200,163 @@ + + + + Ask the compositor to hide the toplevel window for window + management purposes. Depending on the window role, this request + will either display the previously active window (or the background + in case there's no previously active surface) or temporarily (or + until a 'activate_app' is called upon) hide the surface. + + All the surfaces are identifiable by using the app_id, and no actions + are taken in case the app_id is not/was not present. + + See xdg_toplevel.set_app_id from the xdg-shell protocol for a + description of app_id. + + + + + + + Makes the application identified by app_id as floating. If the + application's window is already mapped, in a maximized, normal state, + it would transition to the float state. + + For applications that want to modify their own state, this request + must be done before the initial surface commit in order to take effect. + + If the application is already in floating state, this request wouldn't + do anything. + + There's no persistence of this request, once the application terminated + you'll to issue this request again for that particular app_id. + + The x, and y values would be initial position of the window where the + window surface will be placed. + + See xdg_toplevel.set_app_id from the xdg-shell protocol for a + description of app_id. + + + + + + + + + Returns the application identified by app_id as it was in the normal state. + This is useful to come back from other states to the maximized state, the + normal state applications are started. + + + + + + + Makes the application identified by app_id as fullscreen. If the + application's window is already mapped, in a maximized, normal state, + it would transition to the fullscreen state. + + For applications that want to modify their own state, this request + must be done before the initial surface commit in order to take effect. + + If the application is already in fullscreen state, this request wouldn't + do anything. + + There's no persistence of this request, once the application terminated + you'll to issue this request again for that particular app_id. + + See xdg_toplevel.set_app_id from the xdg-shell protocol for a + description of app_id. + + + + + + + This would allow the compositor to place an application on a particular + output, if that output is indeed available. This can happen before + application is started which would make the application start on that + particular output. If the application is already started it would + move the application to that output. + + There's no persistence of this request, once the application terminated + you'll need to issue this request again for that particular app_id. + + See xdg_toplevel.set_app_id from the xdg-shell protocol for a + description of app_id. + + + + + + + + Clients can use this event to be notified when an application + wants to be displayed on a certain output. This event is sent in + response to the set_app_output request. + + See xdg_toplevel.set_app_id from the xdg-shell protocol for a + description of app_id. + + + + + + + + + This interface allows another client bind to the agl_shell interface, + while there's another shell client already present. + + The client should first bind to this interface and then inform the + compositor with the 'doas_shell_client' request and it wants to bind to + the agl_shell interface. The client is still expected, if using a new + version of the agl_shell interface, to wait for the 'bound_ok' and + 'bound_fail' events before issueing any other requests/events. + + Note that this interface has its limitations, and the compositor would + still refuse the act for 'set_panel' or 'set_background' requests + of the agl_shell interface if there's already a client that used them. + + Any other requests or events should be delievered and handled as it would + a client bound to the agl_shell interface. + + + + + + + + + + Call the destructor once you're ready with agl_shell_ext interface. + This would reset the state and would make any requests made + on the agl_shell interface be terminated. The client would need + to bind again the agl_shell_ext and issue a 'doas_shell_client' + request. + + + + + + Prior to binding to agl_shell interface, this request would inform + the compositor that it wants to gain access the agl_shell interface. + The client is expected to wait for 'doas_shell_client_done' event and + check for a successful status before going further with binding to + the agl_shell interface. + + + + + + The client should check the status event to verify that the + compositor was able to handle the request. + + + -- 2.35.1