]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
cfdisk: add UI for linfdisk menus, ask for size
authorKarel Zak <kzak@redhat.com>
Wed, 29 Jan 2014 13:22:12 +0000 (14:22 +0100)
committerKarel Zak <kzak@redhat.com>
Tue, 11 Mar 2014 10:35:13 +0000 (11:35 +0100)
Signed-off-by: Karel Zak <kzak@redhat.com>
fdisks/cfdisk.c

index 9b5d91b70b96ce6332a8dcf7c2461c0162eaacf0..1abde1abb97aba8e065aab057d6161600ea232f4 100644 (file)
 #define TABLE_START_LINE       4
 #define MENU_START_LINE                (LINES - 5)
 #define INFO_LINE              (LINES - 2)
+#define HINT_LINE              (LINES - 1)
+
+#define CFDISK_ERR_ESC         5000
+
+#ifndef KEY_ESC
+# define KEY_ESC       '\033'
+#endif
+#ifndef KEY_DELETE
+# define KEY_DELETE    '\177'
+#endif
 
 /* colors */
 enum {
@@ -60,11 +70,17 @@ typedef int (menu_callback_t)(struct cfdisk *, int);
 static int menu_cb_main(struct cfdisk *cf, int key);
 static struct cfdisk_menudesc *menu_get_menuitem(struct cfdisk *cf, size_t idx);
 static struct cfdisk_menudesc *menu_get_menuitem_by_key(struct cfdisk *cf, int key, size_t *idx);
+static struct cfdisk_menu *menu_push(struct cfdisk *cf, size_t id, struct cfdisk_menudesc *desc);
+static struct cfdisk_menu *menu_pop(struct cfdisk *cf);
 
 static int ui_refresh(struct cfdisk *cf);
 static void ui_warnx(const char *fmt, ...);
 static void ui_warn(const char *fmt, ...);
 static void ui_info(const char *fmt, ...);
+static void ui_draw_menu(struct cfdisk *cf);
+static void ui_menu_goto(struct cfdisk *cf, int where);
+static int ui_get_size(struct cfdisk *cf, const char *prompt, uintmax_t *res,
+                      uintmax_t low, uintmax_t up);
 
 static int ui_enabled;
 
@@ -102,7 +118,11 @@ static struct cfdisk_menudesc menu_main[] = {
 };
 
 enum {
+       CFDISK_MENU_GENERATED   = -1,   /* used in libfdisk callback */
+
+       /* built-in menus */
        CFDISK_MENU_MAIN        = 0,
+
 };
 
 static struct cfdisk_menudesc *menus[] = {
@@ -266,6 +286,70 @@ static struct fdisk_partition *get_current_partition(struct cfdisk *cf)
        return fdisk_table_get_partition(cf->table, cf->lines_idx);
 }
 
+/* converts libfdisk FDISK_ASKTYPE_MENU to cfdisk menu and returns user's
+ * responseback to libfdisk
+ */
+static int ask_menu(struct fdisk_ask *ask, struct cfdisk *cf)
+{
+       struct cfdisk_menudesc *d, *cm;
+       int key;
+       size_t i = 0, nitems;
+       const char *name, *desc;
+
+       assert(ask);
+       assert(cf);
+
+       /* create cfdisk menu according to libfdisk ask-menu, note that the
+        * last cm[] item has to be empty -- so nitems + 1 */
+       nitems = fdisk_ask_menu_get_nitems(ask);
+       cm = calloc(nitems + 1, sizeof(struct cfdisk_menudesc));
+       if (!cm)
+               return -ENOMEM;
+
+       for (i = 0; i < nitems; i++) {
+               if (fdisk_ask_menu_get_item(ask, i, &key, &name, &desc))
+                       break;
+               cm[i].key = key;
+               cm[i].desc = desc;
+               cm[i].name = name;
+       }
+
+       /* make the new menu active */
+       menu_push(cf, CFDISK_MENU_GENERATED, cm);
+       ui_draw_menu(cf);
+       refresh();
+
+       /* wait for keys */
+       do {
+               switch (getch()) {
+               case KEY_LEFT:
+#ifdef KEY_BTAB
+               case KEY_BTAB:
+#endif
+                       ui_menu_goto(cf, cf->menu_idx - 1);
+                       break;
+               case KEY_RIGHT:
+               case '\t':
+                       ui_menu_goto(cf, cf->menu_idx + 1);
+                       break;
+               case KEY_ENTER:
+               case '\n':
+               case '\r':
+                       d = menu_get_menuitem(cf, cf->menu_idx);
+                       if (d)
+                               fdisk_ask_menu_set_result(ask, d->key);
+                       menu_pop(cf);
+                       free(cm);
+                       return 0;
+               }
+       } while (1);
+
+       menu_pop(cf);
+       free(cm);
+       return -1;
+}
+
+
 static int ask_callback(struct fdisk_context *cxt, struct fdisk_ask *ask,
                    void *data __attribute__((__unused__)))
 {
@@ -284,6 +368,9 @@ static int ask_callback(struct fdisk_context *cxt, struct fdisk_ask *ask,
        case FDISK_ASKTYPE_WARN:
                ui_warn(fdisk_ask_print_get_mesg(ask));
                break;
+       case FDISK_ASKTYPE_MENU:
+               ask_menu(ask, (struct cfdisk *) data);
+               break;
        default:
                ui_warnx(_("internal error: unsupported dialog type %d"),
                        fdisk_ask_get_type(ask));
@@ -320,7 +407,7 @@ static void ui_vprint_center(int line, int attrs, const char *fmt, va_list ap)
 
        xvasprintf(&buf, fmt, ap);
 
-       width = strlen(buf);                    /* TODO: count cells! */
+       width = mbs_safe_width(buf);
 
        attron(attrs);
        mvaddstr(line, (COLS - width) / 2, buf);
@@ -380,6 +467,23 @@ static void ui_clean_info(void)
        clrtoeol();
 }
 
+static void ui_hint(const char *fmt, ...)
+{
+       va_list ap;
+       va_start(ap, fmt);
+       if (ui_enabled)
+               ui_vprint_center(HINT_LINE, A_BOLD, fmt, ap);
+       else
+               vfprintf(stdout, fmt, ap);
+       va_end(ap);
+}
+
+static void ui_clean_hint(void)
+{
+       move(HINT_LINE, 0);
+       clrtoeol();
+}
+
 static void die_on_signal(int dummy __attribute__((__unused__)))
 {
        ui_end(NULL);
@@ -446,24 +550,26 @@ static void menu_update_ignore(struct cfdisk *cf)
                cf->menu_idx = 0;
 }
 
-static struct cfdisk_menu *menu_push(struct cfdisk *cf, size_t id)
+static struct cfdisk_menu *menu_push(
+                       struct cfdisk *cf,
+                       size_t id,
+                       struct cfdisk_menudesc *desc)
 {
        struct cfdisk_menu *m = xcalloc(1, sizeof(*m));
        struct cfdisk_menudesc *d;
 
        assert(cf);
-       assert(id < ARRAY_SIZE(menus));
 
        DBG(FRONTEND, dbgprint("menu: new menu"));
 
        m->prev = cf->menu;
        m->id = id;
-       m->desc = menus[id];
+       m->desc = desc ? desc : menus[id];
        m->callback = menu_callbacks[id];
 
        for (d = m->desc; d->name; d++) {
                const char *name = _(d->name);
-               size_t len = strlen(name);      /* TODO: we care about cells! */
+               size_t len = mbs_safe_width(name);
                if (len > m->width)
                        m->width = len;
                m->nitems++;
@@ -494,14 +600,16 @@ static struct cfdisk_menu *menu_pop(struct cfdisk *cf)
 static int menu_cb_main(struct cfdisk *cf, int key)
 {
        size_t n;
-       int ref = 0;
+       int ref = 0, rc;
        const char *info = NULL, *warn = NULL;
+       struct fdisk_partition *pa;
 
        assert(cf);
        assert(cf->cxt);
        assert(key);
 
        n = cf->lines_idx;      /* the current partition */
+       pa = get_current_partition(cf);
 
        switch (key) {
        case 'b': /* Bootable flag */
@@ -523,7 +631,34 @@ static int menu_cb_main(struct cfdisk *cf, int key)
                ref = 1;
                break;
        case 'n': /* New */
+       {
+               uint64_t start, size;
+               struct fdisk_partition *npa;    /* the new partition */
+
+               if (!pa || !fdisk_partition_is_freespace(pa))
+                       return -EINVAL;
+               npa = fdisk_new_partition();
+               if (!npa)
+                       return -ENOMEM;
+               /* free space range */
+               start = fdisk_partition_get_start(pa);
+               size = fdisk_partition_get_size(pa) * cf->cxt->sector_size;
+
+               if (ui_get_size(cf, _("Partition size: "), &size, 1, size)
+                               == -CFDISK_ERR_ESC)
+                       break;
+               size /= cf->cxt->sector_size; 
+               /* properties of the new partition */
+               fdisk_partition_set_start(npa, start);
+               fdisk_partition_set_size(npa, size);
+               fdisk_partition_partno_follow_default(npa, 1);
+               /* add to disk label -- libfdisk will ask for missing details */
+               rc = fdisk_add_partition(cf->cxt, npa);
+               fdisk_unref_partition(npa);
+               if (rc == 0)
+                       ref = 1;
                break;
+       }
        case 'q': /* Quit */
                return 1;
        case 't': /* Type */
@@ -544,7 +679,7 @@ static int menu_cb_main(struct cfdisk *cf, int key)
        return 0;
 }
 
-static int ui_init(struct cfdisk *cf)
+static int ui_init(struct cfdisk *cf __attribute__((__unused__)))
 {
        struct sigaction sa;
 
@@ -656,7 +791,7 @@ static void ui_draw_menuitem(struct cfdisk *cf,
                mvprintw(ln, cl, "[%s]", buf);
                standend();
                if (d->desc)
-                       ui_center(LINES - 1, d->desc);
+                       ui_hint(d->desc);
        } else
                mvprintw(ln, cl, "[%s]", buf);
 }
@@ -832,13 +967,177 @@ static int ui_refresh(struct cfdisk *cf)
        return 0;
 }
 
+static ssize_t ui_get_string(struct cfdisk *cf, const char *prompt,
+                            const char *hint, char *buf, size_t len)
+{
+       size_t cells = 0;
+       ssize_t i = 0, rc = -1;
+       wint_t c;
+       int ln = MENU_START_LINE, cl = 1;
+
+       assert(cf);
+       assert(buf);
+       assert(len);
+
+       move(ln, 0);
+       clrtoeol();
+
+       if (prompt) {
+               mvaddstr(ln, cl, prompt);
+               cl += mbs_safe_width(prompt);
+       }
+
+       /* default value */
+       if (*buf) {
+               i = strlen(buf);
+               cells = mbs_safe_width(buf);
+               mvaddstr(ln, cl, buf);
+       }
+
+       if (hint)
+               ui_hint(hint);
+       else
+               ui_clean_hint();
+
+       move(ln, cl + cells);
+       curs_set(1);
+       refresh();
+
+       while (1) {
+#if !defined(HAVE_SLCURSES_H) && !defined(HAVE_SLANG_SLCURSES_H) && \
+    defined(HAVE_LIBNCURSESW) && defined(HAVE_WIDECHAR)
+               if (get_wch(&c) == ERR) {
+#else
+               if ((c = getch()) == ERR) {
+#endif
+                       if (!isatty(STDIN_FILENO))
+                               exit(2);
+                       else
+                               goto done;
+               }
+               if (c == '\r' || c == '\n' || c == KEY_ENTER)
+                       break;
+
+               switch (c) {
+               case KEY_ESC:
+                       rc = -CFDISK_ERR_ESC;
+                       goto done;
+               case KEY_DELETE:
+               case '\b':
+               case KEY_BACKSPACE:
+                       if (i > 0) {
+                               cells--;
+                               i = mbs_truncate(buf, &cells);
+                               if (i < 0)
+                                       goto done;
+                               mvaddch(ln, cl + cells, ' ');
+                               move(ln, cl + cells);
+                       } else
+                               beep();
+                       break;
+               default:
+#if defined(HAVE_LIBNCURSESW) && defined(HAVE_WIDECHAR)
+                       if (i + 1 < (ssize_t) len && iswprint(c)) {
+                               wchar_t wc = (wchar_t) c;
+                               char s[MB_CUR_MAX + 1];
+                               int sz = wctomb(s, wc);
+
+                               if (sz > 0 && sz + i < (ssize_t) len) {
+                                       s[sz] = '\0';
+                                       mvaddnstr(ln, cl + cells, s, sz);
+                                       memcpy(buf + i, s, sz);
+                                       i += sz;
+                                       buf[i] = '\0';
+                                       cells += wcwidth(wc);
+                               } else
+                                       beep();
+                       }
+#else
+                       if (i + 1 < (ssize_t) len && isprint(c)) {
+                               mvaddch(ln, cl + cells, c);
+                               str[i++] = c;
+                               str[i] = '\0';
+                               cells++;
+                       }
+#endif
+                       else
+                               beep();
+               }
+               refresh();
+       }
+
+       rc = i;         /* success */
+done:
+       move(ln, 0);
+       clrtoeol();
+       curs_set(0);
+       refresh();
+
+       return rc;
+}
+
+/* @res is default value as well as result in bytes */
+static int ui_get_size(struct cfdisk *cf, const char *prompt, uintmax_t *res,
+                      uintmax_t low, uintmax_t up)
+{
+       char buf[128];
+       uintmax_t user = 0;
+       ssize_t rc;
+       char *dflt = size_to_human_string(0, *res);
+
+       DBG(FRONTEND, dbgprint("ui: get_size (default=%ju)", *res));
+
+       ui_clean_info();
+
+       do {
+               int pwr = 0;
+
+               snprintf(buf, sizeof(buf), "%s", dflt);
+               rc = ui_get_string(cf, prompt,
+                               _("The size may be followed by MiB, GiB or TiB suffix "
+                                 "(the \"iB\" is optional)."),
+                               buf, sizeof(buf));
+               if (rc == 0) {
+                       ui_warnx(_("Please, specify size."));
+                       continue;                       /* nothing specified */
+               } else if (rc == -CFDISK_ERR_ESC)
+                       break;                          /* cancel dialog */
+
+               rc = parse_size(buf, &user, &pwr);      /* response, parse */
+               if (rc == 0) {
+                       DBG(FRONTEND, dbgprint("ui: get_size user=%ju, power=%d", user, pwr));
+                       if (user < low) {
+                               ui_warnx(_("Minimal size is %ju"), low);
+                               rc = -ERANGE;
+                       }
+                       if (user > up && pwr && user < up + (1ULL << pwr * 10))
+                               /* ignore when the user specified size overflow
+                                * with in range specified by suffix (e.g. MiB) */
+                               user = up;
+
+                       if (user > up) {
+                               ui_warnx(_("Maximal size is %ju bytes."), up);
+                               rc = -ERANGE;
+                       }
+               }
+       } while (rc != 0);
+
+       if (rc == 0)
+               *res = user;
+       free(dflt);
+
+       DBG(FRONTEND, dbgprint("ui: get_size (result=%ju, rc=%zd)", *res, rc));
+       return rc;
+}
+
+
 static int ui_run(struct cfdisk *cf)
 {
        int rc;
 
        DBG(FRONTEND, dbgprint("ui: start COLS=%d, LINES=%d", COLS, LINES));
 
-       menu_push(cf, CFDISK_MENU_MAIN);
+       menu_push(cf, CFDISK_MENU_MAIN, NULL);
 
        rc = ui_refresh(cf);
        if (rc)