source: mainline/uspace/app/taskbar/tbsmenu.c@ d92b8e8f

Last change on this file since d92b8e8f was d92b8e8f, checked in by Jiri Svoboda <jiri@…>, 16 months ago

Start menu support for passing input device ID (multiseat)

  • Property mode set to 100644
File size: 14.9 KB
Line 
1/*
2 * Copyright (c) 2024 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup taskbar
30 * @{
31 */
32/** @file Taskbar start menu
33 */
34
35#include <gfx/coord.h>
36#include <stdbool.h>
37#include <stddef.h>
38#include <stdlib.h>
39#include <str.h>
40#include <task.h>
41#include <tbarcfg/tbarcfg.h>
42#include <ui/fixed.h>
43#include <ui/menu.h>
44#include <ui/menuentry.h>
45#include <ui/resource.h>
46#include <ui/ui.h>
47#include <ui/window.h>
48#include "tbsmenu.h"
49
50static void tbsmenu_smenu_close_req(ui_menu_t *, void *);
51
52/** Start menu callbacks */
53static ui_menu_cb_t tbsmenu_smenu_cb = {
54 .close_req = tbsmenu_smenu_close_req,
55};
56
57static void tbsmenu_button_down(ui_pbutton_t *, void *);
58
59/** Start button callbacks */
60static ui_pbutton_cb_t tbsmenu_button_cb = {
61 .down = tbsmenu_button_down
62};
63
64static void tbsmenu_smenu_entry_cb(ui_menu_entry_t *, void *);
65static errno_t tbsmenu_entry_start(tbsmenu_entry_t *);
66static void tbsmenu_cmd_fini(tbsmenu_cmd_t *);
67
68/** Create taskbar start menu.
69 *
70 * @param window Containing window
71 * @param fixed Fixed layout to which start button will be added
72 * @param dspec Display specification (for passing to applications)
73 * @param rtbsmenu Place to store pointer to new start menu
74 * @return @c EOK on success or an error code
75 */
76errno_t tbsmenu_create(ui_window_t *window, ui_fixed_t *fixed,
77 const char *dspec, tbsmenu_t **rtbsmenu)
78{
79 ui_resource_t *res = ui_window_get_res(window);
80 tbsmenu_t *tbsmenu = NULL;
81 errno_t rc;
82
83 tbsmenu = calloc(1, sizeof(tbsmenu_t));
84 if (tbsmenu == NULL) {
85 rc = ENOMEM;
86 goto error;
87 }
88
89 tbsmenu->display_spec = str_dup(dspec);
90 if (tbsmenu->display_spec == NULL) {
91 rc = ENOMEM;
92 goto error;
93 }
94
95 rc = ui_pbutton_create(res, "Start", &tbsmenu->sbutton);
96 if (rc != EOK)
97 goto error;
98
99 ui_pbutton_set_cb(tbsmenu->sbutton, &tbsmenu_button_cb,
100 (void *)tbsmenu);
101
102 ui_pbutton_set_default(tbsmenu->sbutton, true);
103
104 rc = ui_fixed_add(fixed, ui_pbutton_ctl(tbsmenu->sbutton));
105 if (rc != EOK)
106 goto error;
107
108 rc = ui_menu_create(window, &tbsmenu->smenu);
109 if (rc != EOK)
110 goto error;
111
112 ui_menu_set_cb(tbsmenu->smenu, &tbsmenu_smenu_cb, (void *)tbsmenu);
113
114 tbsmenu->window = window;
115 tbsmenu->fixed = fixed;
116 list_initialize(&tbsmenu->entries);
117
118 *rtbsmenu = tbsmenu;
119 return EOK;
120error:
121 if (tbsmenu != NULL && tbsmenu->display_spec != NULL)
122 free(tbsmenu->display_spec);
123 if (tbsmenu != NULL)
124 ui_pbutton_destroy(tbsmenu->sbutton);
125 if (tbsmenu != NULL)
126 free(tbsmenu);
127 return rc;
128}
129
130/** Load start menu from repository.
131 *
132 * @param tbsmenu Start menu
133 * @param Repository path
134 * @return EOK on success or an error code
135 */
136errno_t tbsmenu_load(tbsmenu_t *tbsmenu, const char *repopath)
137{
138 tbsmenu_entry_t *tentry;
139 tbarcfg_t *tbcfg = NULL;
140 smenu_entry_t *sme;
141 bool separator;
142 const char *caption;
143 const char *cmd;
144 bool terminal;
145 errno_t rc;
146
147 if (tbsmenu->repopath != NULL)
148 free(tbsmenu->repopath);
149
150 tbsmenu->repopath = str_dup(repopath);
151 if (tbsmenu->repopath == NULL)
152 return ENOMEM;
153
154 /* Remove existing entries */
155 tentry = tbsmenu_first(tbsmenu);
156 while (tentry != NULL) {
157 tbsmenu_remove(tbsmenu, tentry, false);
158 tentry = tbsmenu_first(tbsmenu);
159 }
160
161 rc = tbarcfg_open(repopath, &tbcfg);
162 if (rc != EOK)
163 goto error;
164
165 sme = tbarcfg_smenu_first(tbcfg);
166 while (sme != NULL) {
167 separator = smenu_entry_get_separator(sme);
168 if (separator == false) {
169 caption = smenu_entry_get_caption(sme);
170 cmd = smenu_entry_get_cmd(sme);
171 terminal = smenu_entry_get_terminal(sme);
172
173 rc = tbsmenu_add(tbsmenu, caption, cmd, terminal,
174 &tentry);
175 if (rc != EOK)
176 goto error;
177 } else {
178 rc = tbsmenu_add_sep(tbsmenu, &tentry);
179 if (rc != EOK)
180 goto error;
181 }
182
183 (void)tentry;
184
185 sme = tbarcfg_smenu_next(sme);
186 }
187
188 tbarcfg_close(tbcfg);
189 return EOK;
190error:
191 if (tbcfg != NULL)
192 tbarcfg_close(tbcfg);
193 return rc;
194}
195
196/** Reload start menu from repository (or schedule reload).
197 *
198 * @param tbsmenu Start menu
199 */
200void tbsmenu_reload(tbsmenu_t *tbsmenu)
201{
202 if (!tbsmenu_is_open(tbsmenu))
203 (void) tbsmenu_load(tbsmenu, tbsmenu->repopath);
204 else
205 tbsmenu->needs_reload = true;
206}
207
208/** Set start menu rectangle.
209 *
210 * @param tbsmenu Start menu
211 * @param rect Rectangle
212 */
213void tbsmenu_set_rect(tbsmenu_t *tbsmenu, gfx_rect_t *rect)
214{
215 tbsmenu->rect = *rect;
216 ui_pbutton_set_rect(tbsmenu->sbutton, rect);
217}
218
219/** Open taskbar start menu.
220 *
221 * @param tbsmenu Start menu
222 */
223void tbsmenu_open(tbsmenu_t *tbsmenu)
224{
225 (void) ui_menu_open(tbsmenu->smenu, &tbsmenu->rect,
226 tbsmenu->ev_idev_id);
227}
228
229/** Close taskbar start menu.
230 *
231 * @param tbsmenu Start menu
232 */
233void tbsmenu_close(tbsmenu_t *tbsmenu)
234{
235 ui_menu_close(tbsmenu->smenu);
236
237 if (tbsmenu->needs_reload)
238 (void) tbsmenu_load(tbsmenu, tbsmenu->repopath);
239}
240
241/** Determine if taskbar start menu is open.
242 *
243 * @param tbsmenu Start menu
244 * @return @c true iff start menu is open
245 */
246bool tbsmenu_is_open(tbsmenu_t *tbsmenu)
247{
248 return ui_menu_is_open(tbsmenu->smenu);
249}
250
251/** Destroy taskbar start menu.
252 *
253 * @param tbsmenu Start menu
254 */
255void tbsmenu_destroy(tbsmenu_t *tbsmenu)
256{
257 tbsmenu_entry_t *entry;
258
259 /* Destroy entries */
260 entry = tbsmenu_first(tbsmenu);
261 while (entry != NULL) {
262 tbsmenu_remove(tbsmenu, entry, false);
263 entry = tbsmenu_first(tbsmenu);
264 }
265
266 ui_fixed_remove(tbsmenu->fixed, ui_pbutton_ctl(tbsmenu->sbutton));
267 ui_pbutton_destroy(tbsmenu->sbutton);
268 ui_menu_destroy(tbsmenu->smenu);
269
270 free(tbsmenu);
271}
272
273/** Add entry to start menu.
274 *
275 * @param tbsmenu Start menu
276 * @param caption Caption
277 * @param cmd Command to run
278 * @param terminal Start in terminal
279 * @param entry Start menu entry
280 * @return @c EOK on success or an error code
281 */
282errno_t tbsmenu_add(tbsmenu_t *tbsmenu, const char *caption,
283 const char *cmd, bool terminal, tbsmenu_entry_t **rentry)
284{
285 errno_t rc;
286 tbsmenu_entry_t *entry;
287
288 entry = calloc(1, sizeof(tbsmenu_entry_t));
289 if (entry == NULL)
290 return ENOMEM;
291
292 entry->caption = str_dup(caption);
293 if (entry->caption == NULL) {
294 rc = ENOMEM;
295 goto error;
296 }
297
298 entry->cmd = str_dup(cmd);
299 if (entry->cmd == NULL) {
300 rc = ENOMEM;
301 goto error;
302 }
303
304 entry->terminal = terminal;
305
306 rc = ui_menu_entry_create(tbsmenu->smenu, caption, "", &entry->mentry);
307 if (rc != EOK)
308 goto error;
309
310 ui_menu_entry_set_cb(entry->mentry, tbsmenu_smenu_entry_cb,
311 (void *)entry);
312
313 entry->tbsmenu = tbsmenu;
314 list_append(&entry->lentries, &tbsmenu->entries);
315 *rentry = entry;
316 return EOK;
317error:
318 if (entry->caption != NULL)
319 free(entry->caption);
320 if (entry->cmd != NULL)
321 free(entry->cmd);
322 free(entry);
323 return rc;
324}
325
326/** Add separator entry to start menu.
327 *
328 * @param tbsmenu Start menu
329 * @param entry Start menu entry
330 * @return @c EOK on success or an error code
331 */
332errno_t tbsmenu_add_sep(tbsmenu_t *tbsmenu, tbsmenu_entry_t **rentry)
333{
334 errno_t rc;
335 tbsmenu_entry_t *entry;
336
337 entry = calloc(1, sizeof(tbsmenu_entry_t));
338 if (entry == NULL)
339 return ENOMEM;
340
341 rc = ui_menu_entry_sep_create(tbsmenu->smenu, &entry->mentry);
342 if (rc != EOK)
343 goto error;
344
345 ui_menu_entry_set_cb(entry->mentry, tbsmenu_smenu_entry_cb,
346 (void *)entry);
347
348 entry->tbsmenu = tbsmenu;
349 list_append(&entry->lentries, &tbsmenu->entries);
350 *rentry = entry;
351 return EOK;
352error:
353 free(entry);
354 return rc;
355}
356
357/** Remove entry from start menu.
358 *
359 * @param tbsmenu Start menu
360 * @param entry Start menu entry
361 * @param paint @c true to repaint start menu
362 */
363void tbsmenu_remove(tbsmenu_t *tbsmenu, tbsmenu_entry_t *entry,
364 bool paint)
365{
366 assert(entry->tbsmenu == tbsmenu);
367
368 list_remove(&entry->lentries);
369
370 ui_menu_entry_destroy(entry->mentry);
371 free(entry->caption);
372 free(entry->cmd);
373 free(entry);
374}
375
376/** Handle start menu close request.
377 *
378 * @param menu Menu
379 * @param arg Argument (tbsmenu_t *)
380 * @param wnd_id Window ID
381 */
382static void tbsmenu_smenu_close_req(ui_menu_t *menu, void *arg)
383{
384 tbsmenu_t *tbsmenu = (tbsmenu_t *)arg;
385
386 (void)tbsmenu;
387 ui_menu_close(menu);
388}
389
390/** Start menu entry was activated.
391 *
392 * @param smentry Start menu entry
393 * @param arg Argument (tbsmenu_entry_t *)
394 */
395static void tbsmenu_smenu_entry_cb(ui_menu_entry_t *smentry, void *arg)
396{
397 tbsmenu_entry_t *entry = (tbsmenu_entry_t *)arg;
398
399 (void)tbsmenu_entry_start(entry);
400}
401
402/** Get first start menu entry.
403 *
404 * @param tbsmenu Start menu
405 * @return First entry or @c NULL if the menu is empty
406 */
407tbsmenu_entry_t *tbsmenu_first(tbsmenu_t *tbsmenu)
408{
409 link_t *link;
410
411 link = list_first(&tbsmenu->entries);
412 if (link == NULL)
413 return NULL;
414
415 return list_get_instance(link, tbsmenu_entry_t, lentries);
416}
417
418/** Get last start menu entry.
419 *
420 * @param tbsmenu Start menu
421 * @return Last entry or @c NULL if the menu is empty
422 */
423tbsmenu_entry_t *tbsmenu_last(tbsmenu_t *tbsmenu)
424{
425 link_t *link;
426
427 link = list_last(&tbsmenu->entries);
428 if (link == NULL)
429 return NULL;
430
431 return list_get_instance(link, tbsmenu_entry_t, lentries);
432}
433
434/** Get next start menu entry.
435 *
436 * @param cur Current entry
437 * @return Next entry or @c NULL if @a cur is the last entry
438 */
439tbsmenu_entry_t *tbsmenu_next(tbsmenu_entry_t *cur)
440{
441 link_t *link;
442
443 link = list_next(&cur->lentries, &cur->tbsmenu->entries);
444 if (link == NULL)
445 return NULL;
446
447 return list_get_instance(link, tbsmenu_entry_t, lentries);
448}
449
450/** Get number of start menu entries.
451 *
452 * @param tbsmenu Start menu
453 * @return Number of entries
454 */
455size_t tbsmenu_count(tbsmenu_t *tbsmenu)
456{
457 return list_count(&tbsmenu->entries);
458}
459
460/** Start button was depressed.
461 *
462 * @param pbutton Push button
463 * @param arg Argument (tbsmenu_entry_t *)
464 */
465static void tbsmenu_button_down(ui_pbutton_t *pbutton, void *arg)
466{
467 tbsmenu_t *tbsmenu = (tbsmenu_t *)arg;
468
469 if (!tbsmenu_is_open(tbsmenu)) {
470 tbsmenu_open(tbsmenu);
471 } else {
472 /* menu is open */
473 tbsmenu_close(tbsmenu);
474 }
475}
476
477/** Split command string into individual parts.
478 *
479 * Command arguments are separated by spaces. There is no way
480 * to provide an argument containing spaces.
481 *
482 * @param str Command with arguments separated by spaces
483 * @param cmd Command structure to fill in
484 * @return EOK on success or an error code
485 */
486static errno_t tbsmenu_cmd_split(const char *str, tbsmenu_cmd_t *cmd)
487{
488 char *buf;
489 char *arg;
490 char *next;
491 size_t cnt;
492
493 buf = str_dup(str);
494 if (buf == NULL)
495 return ENOMEM;
496
497 /* Count the entries */
498 cnt = 0;
499 arg = str_tok(buf, " ", &next);
500 while (arg != NULL) {
501 ++cnt;
502 arg = str_tok(next, " ", &next);
503 }
504
505 /* Need to copy again as buf was mangled */
506 free(buf);
507 buf = str_dup(str);
508 if (buf == NULL)
509 return ENOMEM;
510
511 cmd->argv = calloc(cnt + 1, sizeof(char *));
512 if (cmd->argv == NULL) {
513 free(buf);
514 return ENOMEM;
515 }
516
517 /* Copy individual arguments */
518 cnt = 0;
519 arg = str_tok(buf, " ", &next);
520 while (arg != NULL) {
521 cmd->argv[cnt] = str_dup(arg);
522 if (cmd->argv[cnt] == NULL) {
523 tbsmenu_cmd_fini(cmd);
524 return ENOMEM;
525 }
526 ++cnt;
527
528 arg = str_tok(next, " ", &next);
529 }
530
531 cmd->argv[cnt] = NULL;
532
533 return EOK;
534}
535
536/** Free command structure.
537 *
538 * @param cmd Command
539 */
540static void tbsmenu_cmd_fini(tbsmenu_cmd_t *cmd)
541{
542 char **cp;
543
544 /* Free all pointers in NULL-terminated list */
545 cp = cmd->argv;
546 while (*cp != NULL) {
547 free(*cp);
548 ++cp;
549 }
550
551 /* Free the array of pointers */
552 free(cmd->argv);
553}
554
555/** Free command structure.
556 *
557 * @param cmd Command
558 * @param entry Start menu entry
559 * @param dspec Display specification
560 * @return EOK on success or an error code
561 */
562static errno_t tbsmenu_cmd_subst(tbsmenu_cmd_t *cmd, tbsmenu_entry_t *entry,
563 const char *dspec)
564{
565 char **cp;
566
567 (void)entry;
568
569 /* Walk NULL-terminated list of arguments */
570 cp = cmd->argv;
571 while (*cp != NULL) {
572 if (str_cmp(*cp, "%d") == 0) {
573 /* Display specification */
574 free(*cp);
575 *cp = str_dup(dspec);
576 if (*cp == NULL)
577 return ENOMEM;
578 }
579 ++cp;
580 }
581
582 return EOK;
583}
584
585/** Execute start menu entry.
586 *
587 * @param entry Start menu entry
588 *
589 * @return EOK on success or an error code
590 */
591static errno_t tbsmenu_entry_start(tbsmenu_entry_t *entry)
592{
593 task_id_t id;
594 task_wait_t wait;
595 task_exit_t texit;
596 tbsmenu_cmd_t cmd;
597 int retval;
598 bool suspended;
599 int i;
600 int cnt;
601 char **cp;
602 const char **targv = NULL;
603 char *dspec = NULL;
604 sysarg_t idev_id;
605 int rv;
606 errno_t rc;
607 ui_t *ui;
608
609 ui = ui_window_get_ui(entry->tbsmenu->window);
610 suspended = false;
611
612 idev_id = ui_menu_get_idev_id(entry->tbsmenu->smenu);
613
614 rv = asprintf(&dspec, "%s?idev=%zu",
615 entry->tbsmenu->display_spec, (size_t)idev_id);
616 if (rv < 0)
617 return ENOMEM;
618
619 /* Split command string into individual arguments */
620 rc = tbsmenu_cmd_split(entry->cmd, &cmd);
621 if (rc != EOK) {
622 free(dspec);
623 return rc;
624 }
625
626 /* Substitute metacharacters in command */
627 rc = tbsmenu_cmd_subst(&cmd, entry, dspec);
628 if (rc != EOK)
629 goto error;
630
631 /* Free up and clean console for the child task. */
632 rc = ui_suspend(ui);
633 if (rc != EOK)
634 goto error;
635
636 suspended = true;
637
638 /* Don't start in terminal if not running in a window */
639 if (entry->terminal && !ui_is_fullscreen(ui)) {
640 cnt = 0;
641 cp = cmd.argv;
642 while (*cp != NULL) {
643 ++cnt;
644 ++cp;
645 }
646
647 targv = calloc(cnt + 5, sizeof(char **));
648 if (targv == NULL)
649 goto error;
650
651 targv[0] = "/app/terminal";
652 targv[1] = "-d";
653 targv[2] = dspec;
654 targv[3] = "-c";
655
656 for (i = 0; i <= cnt; i++) {
657 targv[4 + i] = cmd.argv[i];
658 }
659
660 rc = task_spawnv(&id, &wait, targv[0], targv);
661 if (rc != EOK)
662 goto error;
663
664 free(targv);
665 targv = NULL;
666 } else {
667 rc = task_spawnv(&id, &wait, cmd.argv[0], (const char *const *)
668 cmd.argv);
669 if (rc != EOK)
670 goto error;
671 }
672
673 rc = task_wait(&wait, &texit, &retval);
674 if ((rc != EOK) || (texit != TASK_EXIT_NORMAL))
675 goto error;
676
677 /* Resume UI operation */
678 rc = ui_resume(ui);
679 if (rc != EOK)
680 goto error;
681
682 (void) ui_paint(ui);
683 return EOK;
684error:
685 free(dspec);
686 if (targv != NULL)
687 free(targv);
688 tbsmenu_cmd_fini(&cmd);
689 if (suspended) {
690 rc = ui_resume(ui);
691 if (rc != EOK) {
692 printf("Failed to resume UI.\n");
693 exit(1);
694 }
695 }
696 (void) ui_paint(ui);
697 return rc;
698}
699
700/** @}
701 */
Note: See TracBrowser for help on using the repository browser.