source: mainline/uspace/app/nav/nav.c@ bb4d0b5

Last change on this file since bb4d0b5 was bb4d0b5, checked in by Jiri Svoboda <jiri@…>, 3 weeks ago

Allow user to decide whether to retry or abort when I/O error occurs.

  • Property mode set to 100644
File size: 17.6 KB
Line 
1/*
2 * Copyright (c) 2025 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 nav
30 * @{
31 */
32/** @file Navigator.
33 *
34 * HelenOS file manager.
35 */
36
37#include <fibril.h>
38#include <fmgt.h>
39#include <gfx/coord.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <str.h>
43#include <str_error.h>
44#include <task.h>
45#include <ui/fixed.h>
46#include <ui/filelist.h>
47#include <ui/resource.h>
48#include <ui/ui.h>
49#include <ui/window.h>
50#include "dlg/ioerrdlg.h"
51#include "menu.h"
52#include "newfile.h"
53#include "nav.h"
54#include "panel.h"
55
56#define EDITOR_CMD "/app/edit"
57
58static void wnd_close(ui_window_t *, void *);
59static void wnd_kbd(ui_window_t *, void *, kbd_event_t *);
60
61static ui_window_cb_t window_cb = {
62 .close = wnd_close,
63 .kbd = wnd_kbd
64};
65
66static void navigator_file_new_file(void *);
67static void navigator_file_open(void *);
68static void navigator_file_edit(void *);
69static void navigator_file_exit(void *);
70
71static nav_menu_cb_t navigator_menu_cb = {
72 .file_new_file = navigator_file_new_file,
73 .file_open = navigator_file_open,
74 .file_edit = navigator_file_edit,
75 .file_exit = navigator_file_exit
76};
77
78static void navigator_panel_activate_req(void *, panel_t *);
79static void navigator_panel_file_open(void *, panel_t *, const char *);
80
81static panel_cb_t navigator_panel_cb = {
82 .activate_req = navigator_panel_activate_req,
83 .file_open = navigator_panel_file_open
84};
85
86static void navigator_progress_babort(progress_dlg_t *, void *);
87static void navigator_progress_close(progress_dlg_t *, void *);
88
89progress_dlg_cb_t navigator_progress_cb = {
90 .babort = navigator_progress_babort,
91 .close = navigator_progress_close
92};
93
94static void navigator_io_err_abort(io_err_dlg_t *, void *);
95static void navigator_io_err_retry(io_err_dlg_t *, void *);
96static void navigator_io_err_close(io_err_dlg_t *, void *);
97
98static io_err_dlg_cb_t navigator_io_err_dlg_cb = {
99 .babort = navigator_io_err_abort,
100 .bretry = navigator_io_err_retry,
101 .close = navigator_io_err_close
102};
103
104/** Window close button was clicked.
105 *
106 * @param window Window
107 * @param arg Argument (navigator)
108 */
109static void wnd_close(ui_window_t *window, void *arg)
110{
111 navigator_t *navigator = (navigator_t *) arg;
112
113 ui_quit(navigator->ui);
114}
115
116/** Window keyboard event handler.
117 *
118 * @param window Window
119 * @param arg Argument (navigator)
120 * @param event Keyboard event
121 */
122static void wnd_kbd(ui_window_t *window, void *arg, kbd_event_t *event)
123{
124 navigator_t *navigator = (navigator_t *) arg;
125
126 if (event->type == KEY_PRESS &&
127 ((event->mods & KM_ALT) == 0) &&
128 ((event->mods & KM_SHIFT) == 0) &&
129 (event->mods & KM_CTRL) != 0) {
130 switch (event->key) {
131 case KC_M:
132 navigator_new_file_dlg(navigator);
133 break;
134 case KC_E:
135 navigator_file_edit((void *)navigator);
136 break;
137 case KC_Q:
138 ui_quit(navigator->ui);
139 break;
140 default:
141 break;
142 }
143 }
144
145 if (event->type == KEY_PRESS &&
146 ((event->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0)) {
147 switch (event->key) {
148 case KC_TAB:
149 navigator_switch_panel(navigator);
150 break;
151 default:
152 break;
153 }
154 }
155
156 ui_window_def_kbd(window, event);
157}
158
159/** Create navigator.
160 *
161 * @param display_spec Display specification
162 * @param rnavigator Place to store pointer to new navigator
163 * @return EOK on success or ane error code
164 */
165errno_t navigator_create(const char *display_spec,
166 navigator_t **rnavigator)
167{
168 navigator_t *navigator;
169 ui_wnd_params_t params;
170 gfx_rect_t rect;
171 gfx_rect_t arect;
172 gfx_coord_t pw;
173 unsigned i;
174 errno_t rc;
175
176 navigator = calloc(1, sizeof(navigator_t));
177 if (navigator == NULL)
178 return ENOMEM;
179
180 rc = ui_create(display_spec, &navigator->ui);
181 if (rc != EOK) {
182 printf("Error creating UI on display %s.\n", display_spec);
183 goto error;
184 }
185
186 ui_wnd_params_init(&params);
187 params.caption = "Navigator";
188 params.style &= ~ui_wds_decorated;
189 params.placement = ui_wnd_place_full_screen;
190
191 rc = ui_window_create(navigator->ui, &params, &navigator->window);
192 if (rc != EOK) {
193 printf("Error creating window.\n");
194 goto error;
195 }
196
197 ui_window_set_cb(navigator->window, &window_cb, (void *) navigator);
198 ui_window_get_app_rect(navigator->window, &arect);
199
200 rc = ui_fixed_create(&navigator->fixed);
201 if (rc != EOK) {
202 printf("Error creating fixed layout.\n");
203 goto error;
204 }
205
206 ui_window_add(navigator->window, ui_fixed_ctl(navigator->fixed));
207
208 rc = nav_menu_create(navigator->window, &navigator->menu);
209 if (rc != EOK)
210 goto error;
211
212 nav_menu_set_cb(navigator->menu, &navigator_menu_cb,
213 (void *)navigator);
214
215 rc = ui_fixed_add(navigator->fixed, nav_menu_ctl(navigator->menu));
216 if (rc != EOK) {
217 printf("Error adding control to layout.\n");
218 return rc;
219 }
220
221 /* Panel width */
222 pw = (arect.p1.x - arect.p0.x) / 2;
223
224 for (i = 0; i < 2; i++) {
225 rc = panel_create(navigator->window, i == 0,
226 &navigator->panel[i]);
227 if (rc != EOK)
228 goto error;
229
230 rect.p0.x = arect.p0.x + pw * i;
231 rect.p0.y = arect.p0.y + 1;
232 rect.p1.x = arect.p0.x + pw * (i + 1);
233 rect.p1.y = arect.p1.y - 1;
234 panel_set_rect(navigator->panel[i], &rect);
235
236 panel_set_cb(navigator->panel[i], &navigator_panel_cb,
237 navigator);
238
239 rc = ui_fixed_add(navigator->fixed,
240 panel_ctl(navigator->panel[i]));
241 if (rc != EOK) {
242 printf("Error adding control to layout.\n");
243 goto error;
244 }
245
246 rc = panel_read_dir(navigator->panel[i], ".");
247 if (rc != EOK) {
248 printf("Error reading directory.\n");
249 goto error;
250 }
251 }
252
253 rc = ui_window_paint(navigator->window);
254 if (rc != EOK) {
255 printf("Error painting window.\n");
256 goto error;
257 }
258
259 fibril_mutex_initialize(&navigator->io_err_act_lock);
260 fibril_condvar_initialize(&navigator->io_err_act_cv);
261 navigator->io_err_act_sel = false;
262
263 *rnavigator = navigator;
264 return EOK;
265error:
266 navigator_destroy(navigator);
267 return rc;
268}
269
270void navigator_destroy(navigator_t *navigator)
271{
272 unsigned i;
273
274 for (i = 0; i < 2; i++) {
275 if (navigator->panel[i] != NULL) {
276 ui_fixed_remove(navigator->fixed,
277 panel_ctl(navigator->panel[i]));
278 panel_destroy(navigator->panel[i]);
279 }
280 }
281
282 if (navigator->menu != NULL) {
283 ui_fixed_remove(navigator->fixed, nav_menu_ctl(navigator->menu));
284 nav_menu_destroy(navigator->menu);
285 }
286
287 if (navigator->window != NULL)
288 ui_window_destroy(navigator->window);
289 if (navigator->ui != NULL)
290 ui_destroy(navigator->ui);
291 free(navigator);
292}
293
294/** Run navigator on the specified display. */
295errno_t navigator_run(const char *display_spec)
296{
297 navigator_t *navigator;
298 errno_t rc;
299
300 rc = navigator_create(display_spec, &navigator);
301 if (rc != EOK)
302 return rc;
303
304 ui_run(navigator->ui);
305
306 navigator_destroy(navigator);
307 return EOK;
308}
309
310/** Get the currently active navigator panel.
311 *
312 * @param navigator Navigator
313 * @return Currently active panel
314 */
315panel_t *navigator_get_active_panel(navigator_t *navigator)
316{
317 int i;
318
319 for (i = 0; i < navigator_panels; i++) {
320 if (panel_is_active(navigator->panel[i]))
321 return navigator->panel[i];
322 }
323
324 /* This should not happen */
325 assert(false);
326 return NULL;
327}
328
329/** Switch to another navigator panel.
330 *
331 * Changes the currently active navigator panel to the next panel.
332 *
333 * @param navigator Navigator
334 */
335void navigator_switch_panel(navigator_t *navigator)
336{
337 errno_t rc;
338
339 if (panel_is_active(navigator->panel[0])) {
340 rc = panel_activate(navigator->panel[1]);
341 if (rc != EOK)
342 return;
343 panel_deactivate(navigator->panel[0]);
344 } else {
345 rc = panel_activate(navigator->panel[0]);
346 if (rc != EOK)
347 return;
348 panel_deactivate(navigator->panel[1]);
349 }
350}
351
352/** Refresh navigator panels.
353 *
354 * This needs to be called when the disk/directory contents might have
355 * changed.
356 *
357 * @param navigator Navigator
358 */
359void navigator_refresh_panels(navigator_t *navigator)
360{
361 errno_t rc;
362 unsigned i;
363
364 /* First refresh inactive panel. */
365
366 for (i = 0; i < 2; i++) {
367 if (!panel_is_active(navigator->panel[i])) {
368 rc = panel_refresh(navigator->panel[i]);
369 if (rc != EOK)
370 return;
371 }
372 }
373
374 /*
375 * Refresh active panel last so that working directory is left
376 * to that of the active panel.
377 */
378
379 for (i = 0; i < 2; i++) {
380 if (panel_is_active(navigator->panel[i])) {
381 rc = panel_refresh(navigator->panel[i]);
382 if (rc != EOK)
383 return;
384 }
385 }
386}
387
388/** File / New File menu entry selected */
389static void navigator_file_new_file(void *arg)
390{
391 navigator_t *navigator = (navigator_t *)arg;
392
393 navigator_new_file_dlg(navigator);
394}
395
396/** File / Open menu entry selected */
397static void navigator_file_open(void *arg)
398{
399 navigator_t *navigator = (navigator_t *)arg;
400 panel_t *panel;
401
402 panel = navigator_get_active_panel(navigator);
403 ui_file_list_open(panel->flist, ui_file_list_get_cursor(panel->flist));
404}
405
406/** Open file in text editor.
407 *
408 * @param navigator Navigator
409 * @param fname File name
410 *
411 * @return EOK on success or an error code
412 */
413static errno_t navigator_edit_file(navigator_t *navigator, const char *fname)
414{
415 task_id_t id;
416 task_wait_t wait;
417 task_exit_t texit;
418 int retval;
419 errno_t rc;
420
421 /* Free up and clean console for the child task. */
422 rc = ui_suspend(navigator->ui);
423 if (rc != EOK)
424 return rc;
425
426 rc = task_spawnl(&id, &wait, EDITOR_CMD, EDITOR_CMD, fname, NULL);
427 if (rc != EOK)
428 goto error;
429
430 rc = task_wait(&wait, &texit, &retval);
431 if ((rc != EOK) || (texit != TASK_EXIT_NORMAL))
432 goto error;
433
434 /* Resume UI operation */
435 rc = ui_resume(navigator->ui);
436 if (rc != EOK)
437 return rc;
438
439 navigator_refresh_panels(navigator);
440 (void) ui_paint(navigator->ui);
441 return EOK;
442error:
443 (void) ui_resume(navigator->ui);
444 (void) ui_paint(navigator->ui);
445 return rc;
446}
447
448/** Execute file entry.
449 *
450 * @param navigator Navigator
451 * @param fname File name
452 *
453 * @return EOK on success or an error code
454 */
455static errno_t navigator_exec_file(navigator_t *navigator, const char *fname)
456{
457 task_id_t id;
458 task_wait_t wait;
459 task_exit_t texit;
460 int retval;
461 errno_t rc;
462
463 /* Free up and clean console for the child task. */
464 rc = ui_suspend(navigator->ui);
465 if (rc != EOK)
466 return rc;
467
468 rc = task_spawnl(&id, &wait, fname, fname, NULL);
469 if (rc != EOK)
470 goto error;
471
472 rc = task_wait(&wait, &texit, &retval);
473 if ((rc != EOK) || (texit != TASK_EXIT_NORMAL))
474 goto error;
475
476 /* Resume UI operation */
477 rc = ui_resume(navigator->ui);
478 if (rc != EOK)
479 return rc;
480
481 navigator_refresh_panels(navigator);
482
483 (void) ui_paint(navigator->ui);
484 return EOK;
485error:
486 (void) ui_resume(navigator->ui);
487 (void) ui_paint(navigator->ui);
488 return rc;
489}
490
491/** Open panel file entry.
492 *
493 * Perform Open action on a file entry (based on extension).
494 *
495 * @param navigator Navigator
496 * @param fname File name
497 *
498 * @return EOK on success or an error code
499 */
500static errno_t navigator_open_file(navigator_t *navigator, const char *fname)
501{
502 const char *ext;
503
504 ext = str_rchr(fname, '.');
505 if (ext != NULL) {
506 if (str_casecmp(ext, ".txt") == 0)
507 return navigator_edit_file(navigator, fname);
508 }
509
510 return navigator_exec_file(navigator, fname);
511}
512
513/** File / Edit menu entry selected */
514static void navigator_file_edit(void *arg)
515{
516 navigator_t *navigator = (navigator_t *)arg;
517 ui_file_list_entry_t *entry;
518 ui_file_list_entry_attr_t attr;
519 panel_t *panel;
520
521 panel = navigator_get_active_panel(navigator);
522 entry = ui_file_list_get_cursor(panel->flist);
523 ui_file_list_entry_get_attr(entry, &attr);
524
525 (void)navigator_edit_file(navigator, attr.name);
526}
527
528/** File / Exit menu entry selected */
529static void navigator_file_exit(void *arg)
530{
531 navigator_t *navigator = (navigator_t *)arg;
532
533 ui_quit(navigator->ui);
534}
535
536/** Panel callback requesting panel activation.
537 *
538 * @param arg Argument (navigator_t *)
539 * @param panel Panel
540 */
541void navigator_panel_activate_req(void *arg, panel_t *panel)
542{
543 navigator_t *navigator = (navigator_t *)arg;
544
545 if (!panel_is_active(panel))
546 navigator_switch_panel(navigator);
547}
548
549/** Panel callback requesting file open.
550 *
551 * @param arg Argument (navigator_t *)
552 * @param panel Panel
553 * @param fname File name
554 */
555void navigator_panel_file_open(void *arg, panel_t *panel, const char *fname)
556{
557 navigator_t *navigator = (navigator_t *)arg;
558
559 (void)panel;
560 navigator_open_file(navigator, fname);
561}
562
563/** Wrapper fibril function for worker function.
564 *
565 * This is the main fibril function for the worker fibril. It executes
566 * the worker function, then clears worker FID to indicate the worker
567 * is finished.
568 *
569 * @param arg Argument (navigator_worker_job_t *)
570 * @return EOK
571 */
572static errno_t navigator_worker_func(void *arg)
573{
574 navigator_worker_job_t *job = (navigator_worker_job_t *)arg;
575
576 job->wfunc(job->arg);
577 job->navigator->worker_fid = 0;
578 free(job);
579 return EOK;
580}
581
582/** Start long-time work in a worker fibril.
583 *
584 * Actions which can take time (file operations) cannot block the main UI
585 * fibril. This function will start an action in the worker fibril, i.e.,
586 * in the background. At the same time the caller should create a modal
587 * progress dialog that will be shown until the work is completed.
588 *
589 * (Only a single worker can execute at any given time).
590 *
591 * @param nav Navigator
592 * @param wfunc Worker main function
593 * @param arg Argument to worker function
594 *
595 * @return EOK on success or an error code
596 */
597errno_t navigator_worker_start(navigator_t *nav, void (*wfunc)(void *),
598 void *arg)
599{
600 navigator_worker_job_t *job;
601
602 if (nav->worker_fid != 0)
603 return EBUSY;
604
605 job = calloc(1, sizeof(navigator_worker_job_t));
606 if (job == NULL)
607 return ENOMEM;
608
609 job->navigator = nav;
610 job->wfunc = wfunc;
611 job->arg = arg;
612
613 nav->worker_fid = fibril_create(navigator_worker_func, (void *)job);
614 if (nav->worker_fid == 0) {
615 free(job);
616 return ENOMEM;
617 }
618
619 fibril_add_ready(nav->worker_fid);
620 return EOK;
621}
622
623/** Abort button pressed in progress dialog.
624 *
625 * @param dlg Progress dialog
626 * @param arg Argument (navigator_t *)
627 */
628static void navigator_progress_babort(progress_dlg_t *dlg, void *arg)
629{
630 navigator_t *nav = (navigator_t *)arg;
631
632 (void)dlg;
633 nav->abort_op = true;
634}
635
636/** Progress dialog closed,
637 *
638 * @param dlg Progress dialog
639 * @param arg Argument (navigator_t *)
640 */
641static void navigator_progress_close(progress_dlg_t *dlg, void *arg)
642{
643 navigator_t *nav = (navigator_t *)arg;
644
645 (void)dlg;
646 nav->abort_op = true;
647}
648
649/** Called by fmgt to query for I/O error recovery action.
650 *
651 * @param arg Argument (navigator_t *)
652 * @param err I/O error report
653 * @return Recovery action to take.
654 */
655fmgt_error_action_t navigator_io_error_query(void *arg, fmgt_io_error_t *err)
656{
657 navigator_t *nav = (navigator_t *)arg;
658 io_err_dlg_t *dlg;
659 io_err_dlg_params_t params;
660 fmgt_error_action_t err_act;
661 char *text1;
662 errno_t rc;
663 int rv;
664
665 io_err_dlg_params_init(&params);
666 rv = asprintf(&text1, err->optype == fmgt_io_write ?
667 "Error writing file %s." : "Error reading file %s.",
668 err->fname);
669 if (rv < 0)
670 return fmgt_er_abort;
671
672 params.text1 = text1;
673 params.text2 = str_error(err->rc);
674
675 ui_lock(nav->ui);
676 rc = io_err_dlg_create(nav->ui, &params, &dlg);
677 if (rc != EOK) {
678 ui_unlock(nav->ui);
679 free(text1);
680 return fmgt_er_abort;
681 }
682
683 io_err_dlg_set_cb(dlg, &navigator_io_err_dlg_cb, (void *)nav);
684
685 ui_unlock(nav->ui);
686 free(text1);
687
688 fibril_mutex_lock(&nav->io_err_act_lock);
689
690 while (!nav->io_err_act_sel) {
691 fibril_condvar_wait(&nav->io_err_act_cv,
692 &nav->io_err_act_lock);
693 }
694
695 err_act = nav->io_err_act;
696 nav->io_err_act_sel = false;
697 fibril_mutex_unlock(&nav->io_err_act_lock);
698
699 return err_act;
700}
701
702/** I/O error dialog abort button was pressed.
703 *
704 * @param dlg I/O error dialog
705 * @param arg Argument (navigator_t *)
706 */
707static void navigator_io_err_abort(io_err_dlg_t *dlg, void *arg)
708{
709 navigator_t *nav = (navigator_t *)arg;
710
711 io_err_dlg_destroy(dlg);
712
713 fibril_mutex_lock(&nav->io_err_act_lock);
714 nav->io_err_act = fmgt_er_abort;
715 nav->io_err_act_sel = true;
716 fibril_condvar_signal(&nav->io_err_act_cv);
717 fibril_mutex_unlock(&nav->io_err_act_lock);
718}
719
720/** I/O error dialog retry button was pressed.
721 *
722 * @param dlg I/O error dialog
723 * @param arg Argument (navigator_t *)
724 */
725static void navigator_io_err_retry(io_err_dlg_t *dlg, void *arg)
726{
727 navigator_t *nav = (navigator_t *)arg;
728
729 io_err_dlg_destroy(dlg);
730
731 fibril_mutex_lock(&nav->io_err_act_lock);
732 nav->io_err_act = fmgt_er_retry;
733 nav->io_err_act_sel = true;
734 fibril_condvar_signal(&nav->io_err_act_cv);
735 fibril_mutex_unlock(&nav->io_err_act_lock);
736}
737
738/** I/O error dialog closure requested.
739 *
740 * @param dlg I/O error dialog
741 * @param arg Argument (navigator_t *)
742 */
743static void navigator_io_err_close(io_err_dlg_t *dlg, void *arg)
744{
745 navigator_t *nav = (navigator_t *)arg;
746
747 io_err_dlg_destroy(dlg);
748
749 fibril_mutex_lock(&nav->io_err_act_lock);
750 nav->io_err_act = fmgt_er_abort;
751 nav->io_err_act_sel = true;
752 fibril_condvar_signal(&nav->io_err_act_cv);
753 fibril_mutex_unlock(&nav->io_err_act_lock);
754}
755
756/** @}
757 */
Note: See TracBrowser for help on using the repository browser.