1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
|
From d44f07a0c2cc410414bfd7b338ee071c17422a0a Mon Sep 17 00:00:00 2001
From: Marius Vlad <marius.vlad@collabora.com>
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 <marius.vlad@collabora.com>
---
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<struct agl_shell*>(
wl_registry_bind(registry, name, &agl_shell_interface,
- std::min(static_cast<uint32_t>(4), version)));
+ std::min(static_cast<uint32_t>(8), version)));
agl_shell_add_listener(d->m_agl.shell, &agl_shell_listener, data);
} else {
d->m_agl.shell = static_cast<struct agl_shell*>(
@@ -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<Display*>(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 <chrono>
+#include <list>
#include <memory>
#include <mutex>
#include <string>
@@ -271,6 +272,44 @@ class Display {
*/
std::pair<int32_t, int32_t> 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<Engine> m_flutter_engine;
@@ -300,6 +339,9 @@ class Display {
uint32_t version = 0;
} m_agl;
+ std::list<std::string> apps_stack;
+ std::list<std::pair<const std::string, const std::string>> 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.
</copyright>
- <interface name="agl_shell" version="4">
+ <interface name="agl_shell" version="8">
<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.
@@ -200,5 +200,163 @@
<arg name="width" type="int" summary="width of rectangle"/>
<arg name="height" type="int" summary="height of rectangle"/>
</request>
+
+ <request name="deactivate_app" since="5">
+ <description summary="de-activate/hide window identified by app_id">
+ 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.
+ </description>
+ <arg name="app_id" type="string"/>
+ </request>
+
+ <request name="set_app_float" since="6">
+ <description summary="set the window identified by app_id as float">
+ 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.
+ </description>
+ <arg name="app_id" type="string"/>
+ <arg name="x" type="int" summary="x position"/>
+ <arg name="y" type="int" summary="y position"/>
+ </request>
+
+ <request name="set_app_normal" since="6">
+ <description summary="set the window identified by app_id as normally started">
+ 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.
+ </description>
+ <arg name="app_id" type="string"/>
+ </request>
+
+ <request name="set_app_fullscreen" since="7">
+ <description summary="">
+ 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.
+ </description>
+ <arg name="app_id" type="string"/>
+ </request>
+
+ <request name="set_app_output" since="8">
+ <description summary="Assign an application to a particular output">
+ 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.
+ </description>
+ <arg name="app_id" type="string"/>
+ <arg name="output" type="object" interface="wl_output"/>
+ </request>
+
+ <event name="app_on_output" since="8">
+ <description summary="Event sent as a reponse to set_app_output">
+ 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.
+ </description>
+ <arg name="app_id" type="string"/>
+ <arg name="output_name" type="string"/>
+ </event>
+ </interface>
+
+ <interface name="agl_shell_ext" version="1">
+ <description summary="extended user interface for Automotive Grade Linux platform">
+ 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.
+ </description>
+
+ <enum name="doas_shell_client_status">
+ <entry name="success" value="0"/>
+ <entry name="failed" value="1"/>
+ </enum>
+
+ <request name="destroy" type="destructor">
+ <description summary="destroys the factory object">
+ 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.
+ </description>
+ </request>
+
+ <request name="doas_shell_client">
+ <description summary="Informs the compositor it wants to bind to the
+ agl_shell interface">
+ 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.
+ </description>
+ </request>
+
+ <event name="doas_done">
+ <description summary="event sent as a reply to doas_shell_client">
+ The client should check the status event to verify that the
+ compositor was able to handle the request.
+ </description>
+ <arg name="status" type="uint" enum="doas_shell_client_status"/>
+ </event>
</interface>
</protocol>
--
2.35.1
|