terminfo.c 20.5 KB
Newer Older
1 2 3
/* terminfo.c - simple terminfo module */
/*
 *  GRUB  --  GRand Unified Bootloader
4
 *  Copyright (C) 2003,2004,2005,2007  Free Software Foundation, Inc.
5
 *
6
 *  GRUB is free software: you can redistribute it and/or modify
7
 *  it under the terms of the GNU General Public License as published by
8
 *  the Free Software Foundation, either version 3 of the License, or
9 10
 *  (at your option) any later version.
 *
11
 *  GRUB is distributed in the hope that it will be useful,
12 13 14 15 16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
17
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
 */

/*
 * This file contains various functions dealing with different
 * terminal capabilities. For example, vt52 and vt100.
 */

#include <grub/types.h>
#include <grub/misc.h>
#include <grub/mm.h>
#include <grub/err.h>
#include <grub/dl.h>
#include <grub/term.h>
#include <grub/terminfo.h>
#include <grub/tparm.h>
33
#include <grub/extcmd.h>
34
#include <grub/i18n.h>
35
#include <grub/time.h>
36
#if defined(__powerpc__) && defined(GRUB_MACHINE_IEEE1275)
37 38
#include <grub/ieee1275/ieee1275.h>
#endif
39

40 41
GRUB_MOD_LICENSE ("GPLv3+");

42 43
#define ANSI_CSI 0x9b
#define ANSI_CSI_STR "\x9b"
44

45
static struct grub_term_output *terminfo_outputs;
46 47 48

/* Get current terminfo name.  */
char *
49
grub_terminfo_get_current (struct grub_term_output *term)
50
{
51 52 53
  struct grub_terminfo_output_state *data
    = (struct grub_terminfo_output_state *) term->data;
  return data->name;
54 55 56 57 58 59 60 61 62 63
}

/* Free *PTR and set *PTR to NULL, to prevent double-free.  */
static void
grub_terminfo_free (char **ptr)
{
  grub_free (*ptr);
  *ptr = 0;
}

64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
static void
grub_terminfo_all_free (struct grub_term_output *term)
{
  struct grub_terminfo_output_state *data
    = (struct grub_terminfo_output_state *) term->data;

  /* Free previously allocated memory.  */
  grub_terminfo_free (&data->name);
  grub_terminfo_free (&data->gotoxy);
  grub_terminfo_free (&data->cls);
  grub_terminfo_free (&data->reverse_video_on);
  grub_terminfo_free (&data->reverse_video_off);
  grub_terminfo_free (&data->cursor_on);
  grub_terminfo_free (&data->cursor_off);
}

80 81
/* Set current terminfo type.  */
grub_err_t
82 83
grub_terminfo_set_current (struct grub_term_output *term,
			   const char *str)
84
{
85 86
  struct grub_terminfo_output_state *data
    = (struct grub_terminfo_output_state *) term->data;
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
  /* TODO
   * Lookup user specified terminfo type. If found, set term variables
   * as appropriate. Otherwise return an error.
   *
   * How should this be done?
   *  a. A static table included in this module.
   *     - I do not like this idea.
   *  b. A table stored in the configuration directory.
   *     - Users must convert their terminfo settings if we have not already.
   *  c. Look for terminfo files in the configuration directory.
   *     - /usr/share/terminfo is 6.3M on my system.
   *     - /usr/share/terminfo is not on most users boot partition.
   *     + Copying the terminfo files you want to use to the grub
   *       configuration directory is easier then (b).
   *  d. Your idea here.
   */

104
  grub_terminfo_all_free (term);
105

106 107
  if (grub_strcmp ("vt100", str) == 0)
    {
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
      data->name              = grub_strdup ("vt100");
      data->gotoxy            = grub_strdup ("\e[%i%p1%d;%p2%dH");
      data->cls               = grub_strdup ("\e[H\e[J");
      data->reverse_video_on  = grub_strdup ("\e[7m");
      data->reverse_video_off = grub_strdup ("\e[m");
      data->cursor_on         = grub_strdup ("\e[?25h");
      data->cursor_off        = grub_strdup ("\e[?25l");
      data->setcolor          = NULL;
      return grub_errno;
    }

  if (grub_strcmp ("vt100-color", str) == 0)
    {
      data->name              = grub_strdup ("vt100-color");
      data->gotoxy            = grub_strdup ("\e[%i%p1%d;%p2%dH");
      data->cls               = grub_strdup ("\e[H\e[J");
      data->reverse_video_on  = grub_strdup ("\e[7m");
      data->reverse_video_off = grub_strdup ("\e[m");
      data->cursor_on         = grub_strdup ("\e[?25h");
      data->cursor_off        = grub_strdup ("\e[?25l");
      data->setcolor          = grub_strdup ("\e[3%p1%dm\e[4%p2%dm");
      return grub_errno;
    }

132 133 134
  if (grub_strcmp ("arc", str) == 0)
    {
      data->name              = grub_strdup ("arc");
135 136 137 138
      data->gotoxy            = grub_strdup (ANSI_CSI_STR "%i%p1%d;%p2%dH");
      data->cls               = grub_strdup (ANSI_CSI_STR "2J");
      data->reverse_video_on  = grub_strdup (ANSI_CSI_STR "7m");
      data->reverse_video_off = grub_strdup (ANSI_CSI_STR "0m");
139 140
      data->cursor_on         = 0;
      data->cursor_off        = 0;
141 142
      data->setcolor          = grub_strdup (ANSI_CSI_STR "3%p1%dm"
					     ANSI_CSI_STR "4%p2%dm");
143 144 145
      return grub_errno;
    }

146 147
  if (grub_strcmp ("ieee1275", str) == 0
      || grub_strcmp ("ieee1275-nocursor", str) == 0)
148 149 150 151 152 153 154 155 156
    {
      data->name              = grub_strdup ("ieee1275");
      data->gotoxy            = grub_strdup ("\e[%i%p1%d;%p2%dH");
      /* Clear the screen.  Using serial console, screen(1) only recognizes the
       * ANSI escape sequence.  Using video console, Apple Open Firmware
       * (version 3.1.1) only recognizes the literal ^L.  So use both.  */
      data->cls               = grub_strdup ("\e[2J");
      data->reverse_video_on  = grub_strdup ("\e[7m");
      data->reverse_video_off = grub_strdup ("\e[m");
157 158 159 160 161 162 163 164 165 166
      if (grub_strcmp ("ieee1275", str) == 0)
	{
	  data->cursor_on         = grub_strdup ("\e[?25h");
	  data->cursor_off        = grub_strdup ("\e[?25l");
	}
      else
	{
	  data->cursor_on         = 0;
	  data->cursor_off        = 0;
	}
167 168 169 170 171 172 173 174 175 176 177 178 179 180
      data->setcolor          = grub_strdup ("\e[3%p1%dm\e[4%p2%dm");
      return grub_errno;
    }

  if (grub_strcmp ("dumb", str) == 0)
    {
      data->name              = grub_strdup ("dumb");
      data->gotoxy            = NULL;
      data->cls               = NULL;
      data->reverse_video_on  = NULL;
      data->reverse_video_off = NULL;
      data->cursor_on         = NULL;
      data->cursor_off        = NULL;
      data->setcolor          = NULL;
181 182
      return grub_errno;
    }
183

184 185
  return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unknown terminfo type `%s'"),
		     str);
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
grub_err_t
grub_terminfo_output_register (struct grub_term_output *term,
			       const char *type)
{
  grub_err_t err;
  struct grub_terminfo_output_state *data;

  err = grub_terminfo_set_current (term, type);

  if (err)
    return err;

  data = (struct grub_terminfo_output_state *) term->data;
  data->next = terminfo_outputs;
  terminfo_outputs = term;

  return GRUB_ERR_NONE;
}

grub_err_t
grub_terminfo_output_unregister (struct grub_term_output *term)
{
  struct grub_term_output **ptr;

  for (ptr = &terminfo_outputs; *ptr;
       ptr = &((struct grub_terminfo_output_state *) (*ptr)->data)->next)
    if (*ptr == term)
      {
	grub_terminfo_all_free (term);
	*ptr = ((struct grub_terminfo_output_state *) (*ptr)->data)->next;
	return GRUB_ERR_NONE;
      }
220
  return grub_error (GRUB_ERR_BUG, "terminal not found");
221 222
}

223 224
/* Wrapper for grub_putchar to write strings.  */
static void
225
putstr (struct grub_term_output *term, const char *str)
226
{
227 228
  struct grub_terminfo_output_state *data
    = (struct grub_terminfo_output_state *) term->data;
229
  while (*str)
230
    data->put (term, *str++);
231 232
}

233
struct grub_term_coordinate
234 235 236 237 238
grub_terminfo_getxy (struct grub_term_output *term)
{
  struct grub_terminfo_output_state *data
    = (struct grub_terminfo_output_state *) term->data;

239
  return data->pos;
240 241 242 243
}

void
grub_terminfo_gotoxy (struct grub_term_output *term,
244
		      struct grub_term_coordinate pos)
245 246 247 248
{
  struct grub_terminfo_output_state *data
    = (struct grub_terminfo_output_state *) term->data;

249
  if (pos.x > grub_term_width (term) || pos.y > grub_term_height (term))
250
    {
251
      grub_error (GRUB_ERR_BUG, "invalid point (%u,%u)", pos.x, pos.y);
252 253 254 255
      return;
    }

  if (data->gotoxy)
256
    putstr (term, grub_terminfo_tparm (data->gotoxy, pos.y, pos.x));
257 258
  else
    {
259
      if ((pos.y == data->pos.y) && (pos.x == data->pos.x - 1))
260
	data->put (term, '\b');
261
    }
262

263
  data->pos = pos;
264 265
}

266
/* Clear the screen.  */
267
void
268
grub_terminfo_cls (struct grub_term_output *term)
269
{
270 271 272 273
  struct grub_terminfo_output_state *data
    = (struct grub_terminfo_output_state *) term->data;

  putstr (term, grub_terminfo_tparm (data->cls));
274
  grub_terminfo_gotoxy (term, (struct grub_term_coordinate) { 0, 0 });
275 276 277
}

void
278 279
grub_terminfo_setcolorstate (struct grub_term_output *term,
			     const grub_term_color_state state)
280
{
281 282 283 284 285 286 287
  struct grub_terminfo_output_state *data
    = (struct grub_terminfo_output_state *) term->data;

  if (data->setcolor)
    {
      int fg;
      int bg;
288 289 290 291 292 293 294 295 296 297 298
      /* Map from VGA to terminal colors.  */
      const int colormap[8] 
	= { 0, /* Black. */
	    4, /* Blue. */
	    2, /* Green. */
	    6, /* Cyan. */
	    1, /* Red.  */
	    5, /* Magenta.  */
	    3, /* Yellow.  */
	    7, /* White.  */
      };
299 300 301 302 303

      switch (state)
	{
	case GRUB_TERM_COLOR_STANDARD:
	case GRUB_TERM_COLOR_NORMAL:
304 305
	  fg = grub_term_normal_color & 0x0f;
	  bg = grub_term_normal_color >> 4;
306 307
	  break;
	case GRUB_TERM_COLOR_HIGHLIGHT:
308 309
	  fg = grub_term_highlight_color & 0x0f;
	  bg = grub_term_highlight_color >> 4;
310 311 312 313 314
	  break;
	default:
	  return;
	}

315 316
      putstr (term, grub_terminfo_tparm (data->setcolor, colormap[fg & 7],
					 colormap[bg & 7]));
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
      return;
    }

  switch (state)
    {
    case GRUB_TERM_COLOR_STANDARD:
    case GRUB_TERM_COLOR_NORMAL:
      putstr (term, grub_terminfo_tparm (data->reverse_video_off));
      break;
    case GRUB_TERM_COLOR_HIGHLIGHT:
      putstr (term, grub_terminfo_tparm (data->reverse_video_on));
      break;
    default:
      break;
    }
332 333 334
}

void
335
grub_terminfo_setcursor (struct grub_term_output *term, const int on)
336
{
337 338 339 340 341 342 343
  struct grub_terminfo_output_state *data
    = (struct grub_terminfo_output_state *) term->data;

  if (on)
    putstr (term, grub_terminfo_tparm (data->cursor_on));
  else
    putstr (term, grub_terminfo_tparm (data->cursor_off));
344 345
}

346
/* The terminfo version of putchar.  */
347
void
348 349
grub_terminfo_putchar (struct grub_term_output *term,
		       const struct grub_unicode_glyph *c)
350
{
351 352 353 354 355 356 357 358 359 360 361
  struct grub_terminfo_output_state *data
    = (struct grub_terminfo_output_state *) term->data;

  /* Keep track of the cursor.  */
  switch (c->base)
    {
    case '\a':
      break;

    case '\b':
    case 127:
362 363
      if (data->pos.x > 0)
	data->pos.x--;
364 365 366
    break;

    case '\n':
367 368
      if (data->pos.y < grub_term_height (term) - 1)
	data->pos.y++;
369 370 371
      break;

    case '\r':
372
      data->pos.x = 0;
373 374 375
      break;

    default:
376
      if ((int) data->pos.x + c->estimated_width >= (int) grub_term_width (term) + 1)
377
	{
378 379 380
	  data->pos.x = 0;
	  if (data->pos.y < grub_term_height (term) - 1)
	    data->pos.y++;
381 382
	  data->put (term, '\r');
	  data->put (term, '\n');
383
	}
384
      data->pos.x += c->estimated_width;
385 386 387
      break;
    }

388
  data->put (term, c->base);
389 390
}

391
struct grub_term_coordinate
392 393 394 395 396
grub_terminfo_getwh (struct grub_term_output *term)
{
  struct grub_terminfo_output_state *data
    = (struct grub_terminfo_output_state *) term->data;

397
  return data->size;
398 399
}

400
static void
401 402
grub_terminfo_readkey (struct grub_term_input *term, int *keys, int *len,
		       int (*readkey) (struct grub_term_input *term))
403 404 405 406 407 408 409 410 411
{
  int c;

#define CONTINUE_READ						\
  {								\
    grub_uint64_t start;					\
    /* On 9600 we have to wait up to 12 milliseconds.  */	\
    start = grub_get_time_ms ();				\
    do								\
412
      c = readkey (term);					\
413
    while (c == -1 && grub_get_time_ms () - start < 100);	\
414 415 416 417 418 419 420
    if (c == -1)						\
      return;							\
								\
    keys[*len] = c;						\
    (*len)++;							\
  }

421
  c = readkey (term);
422 423 424 425 426 427 428
  if (c < 0)
    {
      *len = 0;
      return;
    }
  *len = 1;
  keys[0] = c;
429
  if (c != ANSI_CSI && c != '\e')
430 431 432 433
    {
      /* Backspace: Ctrl-h.  */
      if (c == 0x7f)
	c = '\b'; 
434 435
      if (c < 0x20 && c != '\t' && c!= '\b' && c != '\n' && c != '\r')
	c = GRUB_TERM_CTRL | (c - 1 + 'a');
436 437 438 439 440 441 442 443 444
      *len = 1;
      keys[0] = c;
      return;
    }

  {
    static struct
    {
      char key;
445
      unsigned ascii;
446 447 448
    }
    three_code_table[] =
      {
449 450 451 452 453 454 455 456 457 458
	{'4', GRUB_TERM_KEY_DC},
	{'A', GRUB_TERM_KEY_UP},
	{'B', GRUB_TERM_KEY_DOWN},
	{'C', GRUB_TERM_KEY_RIGHT},
	{'D', GRUB_TERM_KEY_LEFT},
	{'F', GRUB_TERM_KEY_END},
	{'H', GRUB_TERM_KEY_HOME},
	{'K', GRUB_TERM_KEY_END},
	{'P', GRUB_TERM_KEY_DC},
	{'?', GRUB_TERM_KEY_PPAGE},
459 460
	{'/', GRUB_TERM_KEY_NPAGE},
	{'@', GRUB_TERM_KEY_INSERT},
461 462
      };

463
    static unsigned four_code_table[] =
464
      {
465 466 467 468 469 470 471 472 473 474 475 476 477
	[1] = GRUB_TERM_KEY_HOME,
	[3] = GRUB_TERM_KEY_DC,
	[5] = GRUB_TERM_KEY_PPAGE,
	[6] = GRUB_TERM_KEY_NPAGE,
	[7] = GRUB_TERM_KEY_HOME,
	[8] = GRUB_TERM_KEY_END,
	[17] = GRUB_TERM_KEY_F6,
	[18] = GRUB_TERM_KEY_F7,
	[19] = GRUB_TERM_KEY_F8,
	[20] = GRUB_TERM_KEY_F9,
	[21] = GRUB_TERM_KEY_F10,
	[23] = GRUB_TERM_KEY_F11,
	[24] = GRUB_TERM_KEY_F12,
478
      };
479 480
    char fx_key[] = 
      { 'P', 'Q', 'w', 'x', 't', 'u',
481
        'q', 'r', 'p', 'M', 'A', 'B', 'H', 'F' };
482 483 484 485
    unsigned fx_code[] = 
	{ GRUB_TERM_KEY_F1, GRUB_TERM_KEY_F2, GRUB_TERM_KEY_F3,
	  GRUB_TERM_KEY_F4, GRUB_TERM_KEY_F5, GRUB_TERM_KEY_F6,
	  GRUB_TERM_KEY_F7, GRUB_TERM_KEY_F8, GRUB_TERM_KEY_F9,
486 487
	  GRUB_TERM_KEY_F10, GRUB_TERM_KEY_F11, GRUB_TERM_KEY_F12,
	  GRUB_TERM_KEY_HOME, GRUB_TERM_KEY_END };
488 489 490 491 492 493
    unsigned i;

    if (c == '\e')
      {
	CONTINUE_READ;

494 495 496 497
	if (c == 'O')
	  {
	    CONTINUE_READ;

498 499 500 501 502 503 504
	    for (i = 0; i < ARRAY_SIZE (fx_key); i++)
	      if (fx_key[i] == c)
		{
		  keys[0] = fx_code[i];
		  *len = 1;
		  return;
		}
505 506
	  }

507 508 509 510 511 512 513 514 515 516 517 518 519 520
	if (c != '[')
	  return;
      }

    CONTINUE_READ;
	
    for (i = 0; i < ARRAY_SIZE (three_code_table); i++)
      if (three_code_table[i].key == c)
	{
	  keys[0] = three_code_table[i].ascii;
	  *len = 1;
	  return;
	}

521 522
    switch (c)
      {
523 524 525 526 527 528 529 530 531
      case '[':
	CONTINUE_READ;
	if (c >= 'A' && c <= 'E')
	  {
	    keys[0] = GRUB_TERM_KEY_F1 + c - 'A';
	    *len = 1;
	    return;
	  }
	return;
532 533 534 535 536 537 538 539 540 541 542 543
      case 'O':
	CONTINUE_READ;
	for (i = 0; i < ARRAY_SIZE (fx_key); i++)
	  if (fx_key[i] == c)
	    {
	      keys[0] = fx_code[i];
	      *len = 1;
	      return;
	    }
	return;

      case '0':
544
	{
545
	  int num = 0;
546
	  CONTINUE_READ;
547
	  if (c != '0' && c != '1')
548
	    return;
549 550 551 552 553 554 555 556 557 558 559
	  num = (c - '0') * 10;
	  CONTINUE_READ;
	  if (c < '0' || c > '9')
	    return;
	  num += (c - '0');
	  if (num == 0 || num > 12)
	    return;
	  CONTINUE_READ;
	  if (c != 'q')
	    return;
	  keys[0] = fx_code[num - 1];
560 561
	  *len = 1;
	  return;
562 563
	}	  

564 565 566 567 568
      case '1' ... '9':
	{
	  unsigned val = c - '0';
	  CONTINUE_READ;
	  if (c >= '0' && c <= '9')
569
	    {
570
	      val = val * 10 + (c - '0');
571 572
	      CONTINUE_READ;
	    }
573 574 575 576 577 578 579 580 581 582 583
	  if (c != '~')
	    return;
	  if (val >= ARRAY_SIZE (four_code_table)
	      || four_code_table[val] == 0)
	    return;
	  keys[0] = four_code_table[val];
	  *len = 1;
	  return;
	}
	default:
	  return;
584
      }
585 586 587 588
  }
#undef CONTINUE_READ
}

589
/* The terminfo version of getkey.  */
590
int
591
grub_terminfo_getkey (struct grub_term_input *termi)
592 593 594 595
{
  struct grub_terminfo_input_state *data
    = (struct grub_terminfo_input_state *) (termi->data);
  if (data->npending)
596
    {
597
      int ret;
598
      data->npending--;
599 600 601 602
      ret = data->input_buf[0];
      grub_memmove (data->input_buf, data->input_buf + 1, data->npending
		    * sizeof (data->input_buf[0]));
      return ret;
603
    }
604

605 606
  grub_terminfo_readkey (termi, data->input_buf,
			 &data->npending, data->readkey);
607

608
#if defined(__powerpc__) && defined(GRUB_MACHINE_IEEE1275)
609 610 611 612 613 614 615 616 617 618 619
  if (data->npending == 1 && data->input_buf[0] == '\e'
      && grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_BROKEN_REPEAT)
      && grub_get_time_ms () - data->last_key_time < 1000
      && (data->last_key & GRUB_TERM_EXTENDED))
    {
      data->npending = 0;
      data->last_key_time = grub_get_time_ms ();
      return data->last_key;
    }
#endif

620
  if (data->npending)
621
    {
622
      int ret;
623
      data->npending--;
624
      ret = data->input_buf[0];
625
#if defined(__powerpc__) && defined(GRUB_MACHINE_IEEE1275)
626 627 628 629 630 631
      if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_BROKEN_REPEAT))
	{
	  data->last_key = ret;
	  data->last_key_time = grub_get_time_ms ();
	}
#endif
632 633 634
      grub_memmove (data->input_buf, data->input_buf + 1, data->npending
		    * sizeof (data->input_buf[0]));
      return ret;
635
    }
636

637
  return GRUB_TERM_NO_KEY;
638 639 640 641 642 643 644 645 646 647 648 649
}

grub_err_t
grub_terminfo_input_init (struct grub_term_input *termi)
{
  struct grub_terminfo_input_state *data
    = (struct grub_terminfo_input_state *) (termi->data);
  data->npending = 0;

  return GRUB_ERR_NONE;
}

650 651 652 653 654 655 656
grub_err_t
grub_terminfo_output_init (struct grub_term_output *term)
{
  grub_terminfo_cls (term);
  return GRUB_ERR_NONE;
}

657 658
/* GRUB Command.  */

659 660 661 662 663 664 665 666 667 668
static grub_err_t
print_terminfo (void)
{
  const char *encoding_names[(GRUB_TERM_CODE_TYPE_MASK 
			      >> GRUB_TERM_CODE_TYPE_SHIFT) + 1]
    = {
    /* VGA and glyph descriptor types are just for completeness,
       they are not used on terminfo terminals.
    */
    [GRUB_TERM_CODE_TYPE_ASCII >> GRUB_TERM_CODE_TYPE_SHIFT] = _("ASCII"),
669
    [GRUB_TERM_CODE_TYPE_CP437 >> GRUB_TERM_CODE_TYPE_SHIFT] = "CP-437",
670 671 672
    [GRUB_TERM_CODE_TYPE_UTF8_LOGICAL >> GRUB_TERM_CODE_TYPE_SHIFT]
    = _("UTF-8"),
    [GRUB_TERM_CODE_TYPE_UTF8_VISUAL >> GRUB_TERM_CODE_TYPE_SHIFT]
673 674 675 676
    /* TRANSLATORS: visually ordered UTF-8 is a non-compliant encoding
       based on UTF-8 with right-to-left languages written in reverse.
       Used on some terminals. Normal UTF-8 is refered as
       "logically-ordered UTF-8" by opposition.  */
677
    = _("visually-ordered UTF-8"),
678 679
    [GRUB_TERM_CODE_TYPE_VISUAL_GLYPHS >> GRUB_TERM_CODE_TYPE_SHIFT]
    = "Glyph descriptors",
680
    _("Unknown encoding"), _("Unknown encoding"), _("Unknown encoding")
681 682 683
  };
  struct grub_term_output *cur;

684
  grub_puts_ (N_("Current terminfo types:"));
685 686
  for (cur = terminfo_outputs; cur;
       cur = ((struct grub_terminfo_output_state *) cur->data)->next)
687
    grub_printf ("%s: %s\t%s\t%dx%d\n", cur->name,
688 689
		 grub_terminfo_get_current(cur),
		 encoding_names[(cur->flags & GRUB_TERM_CODE_TYPE_MASK)
690
				>> GRUB_TERM_CODE_TYPE_SHIFT],
691 692
		 ((struct grub_terminfo_output_state *) cur->data)->pos.x,
	         ((struct grub_terminfo_output_state *) cur->data)->pos.y);
693 694 695 696

  return GRUB_ERR_NONE;
}

697 698 699 700 701 702
static const struct grub_arg_option options[] =
{
  {"ascii", 'a', 0, N_("Terminal is ASCII-only [default]."),  0, ARG_TYPE_NONE},
  {"utf8",  'u', 0, N_("Terminal is logical-ordered UTF-8."), 0, ARG_TYPE_NONE},
  {"visual-utf8", 'v', 0, N_("Terminal is visually-ordered UTF-8."), 0,
   ARG_TYPE_NONE},
703
  {"geometry", 'g', 0, N_("Terminal has specified geometry."),
704 705
   /* TRANSLATORS: "x" has to be entered in, like an identifier, so please don't
      use better Unicode codepoints.  */
706 707 708 709 710 711 712 713 714 715 716 717
   N_("WIDTHxHEIGHT."), ARG_TYPE_STRING},
  {0, 0, 0, 0, 0, 0}
};

enum
  {
    OPTION_ASCII,
    OPTION_UTF8,
    OPTION_VISUAL_UTF8,
    OPTION_GEOMETRY
  };

718
static grub_err_t
719
grub_cmd_terminfo (grub_extcmd_context_t ctxt, int argc, char **args)
720
{
721
  struct grub_term_output *cur;
722
  int encoding = GRUB_TERM_CODE_TYPE_ASCII;
723 724
  struct grub_arg_list *state = ctxt->state;
  int w = 0, h = 0;
725

726
  if (argc == 0)
727 728
    return print_terminfo ();

729 730
  if (state[OPTION_ASCII].set)
    encoding = GRUB_TERM_CODE_TYPE_ASCII;
731

732 733 734 735 736
  if (state[OPTION_UTF8].set)
    encoding = GRUB_TERM_CODE_TYPE_UTF8_LOGICAL;

  if (state[OPTION_VISUAL_UTF8].set)
    encoding = GRUB_TERM_CODE_TYPE_UTF8_VISUAL;
737

738 739 740 741 742 743 744 745
  if (state[OPTION_GEOMETRY].set)
    {
      char *ptr = state[OPTION_GEOMETRY].arg;
      w = grub_strtoul (ptr, &ptr, 0);
      if (grub_errno)
	return grub_errno;
      if (*ptr != 'x')
	return grub_error (GRUB_ERR_BAD_ARGUMENT,
746
			   N_("incorrect terminal dimensions specification"));
747 748 749 750 751
      ptr++;
      h = grub_strtoul (ptr, &ptr, 0);
      if (grub_errno)
	return grub_errno;
    }
752 753 754

  for (cur = terminfo_outputs; cur;
       cur = ((struct grub_terminfo_output_state *) cur->data)->next)
755 756 757
    if (grub_strcmp (args[0], cur->name) == 0
	|| (grub_strcmp (args[0], "ofconsole") == 0
	    && grub_strcmp ("console", cur->name) == 0))
758 759
      {
	cur->flags = (cur->flags & ~GRUB_TERM_CODE_TYPE_MASK) | encoding;
760 761 762 763 764

	if (w && h)
	  {
	    struct grub_terminfo_output_state *data
	      = (struct grub_terminfo_output_state *) cur->data;
765 766
	    data->size.x = w;
	    data->size.y = h;
767 768 769
	  }

	if (argc == 1)
770 771
	  return GRUB_ERR_NONE;

772
	return grub_terminfo_set_current (cur, args[1]);
773
      }
774

775
  return grub_error (GRUB_ERR_BAD_ARGUMENT,
776
		     N_("terminal %s isn't found or it's not handled by terminfo"),
777
		     args[0]);
778 779
}

780
static grub_extcmd_t cmd;
781

782
GRUB_MOD_INIT(terminfo)
783
{
784 785 786 787
  cmd = grub_register_extcmd ("terminfo", grub_cmd_terminfo, 0,
			      N_("[[-a|-u|-v] [-g WxH] TERM [TYPE]]"),
			      N_("Set terminfo type of TERM  to TYPE.\n"),
			      options);
788 789
}

790
GRUB_MOD_FINI(terminfo)
791
{
792
  grub_unregister_extcmd (cmd);
793
}