linux/tools/thermal/tmon/tui.c
<<
>>
Prefs
   1/*
   2 * tui.c ncurses text user interface for TMON program
   3 *
   4 * Copyright (C) 2013 Intel Corporation. All rights reserved.
   5 *
   6 * This program is free software; you can redistribute it and/or
   7 * modify it under the terms of the GNU General Public License version
   8 * 2 or later as published by the Free Software Foundation.
   9 *
  10 * This program is distributed in the hope that it will be useful,
  11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13 * GNU General Public License for more details.
  14 *
  15 * Author: Jacob Pan <jacob.jun.pan@linux.intel.com>
  16 *
  17 */
  18
  19#include <unistd.h>
  20#include <stdio.h>
  21#include <stdlib.h>
  22#include <string.h>
  23#include <stdint.h>
  24#include <ncurses.h>
  25#include <time.h>
  26#include <syslog.h>
  27#include <panel.h>
  28#include <pthread.h>
  29#include <signal.h>
  30
  31#include "tmon.h"
  32
  33static PANEL *data_panel;
  34static PANEL *dialogue_panel;
  35static PANEL *top;
  36
  37static WINDOW *title_bar_window;
  38static WINDOW *tz_sensor_window;
  39static WINDOW *cooling_device_window;
  40static WINDOW *control_window;
  41static WINDOW *status_bar_window;
  42static WINDOW *thermal_data_window;
  43static WINDOW *dialogue_window;
  44
  45char status_bar_slots[10][40];
  46static void draw_hbar(WINDOW *win, int y, int start, int len,
  47                unsigned long pattern, bool end);
  48
  49static int maxx, maxy;
  50static int maxwidth = 200;
  51
  52#define TITLE_BAR_HIGHT 1
  53#define SENSOR_WIN_HIGHT 4 /* one row for tz name, one for trip points */
  54
  55
  56/* daemon mode flag (set by startup parameter -d) */
  57static int  tui_disabled;
  58
  59static void close_panel(PANEL *p)
  60{
  61        if (p) {
  62                del_panel(p);
  63                p = NULL;
  64        }
  65}
  66
  67static void close_window(WINDOW *win)
  68{
  69        if (win) {
  70                delwin(win);
  71                win = NULL;
  72        }
  73}
  74
  75void close_windows(void)
  76{
  77        if (tui_disabled)
  78                return;
  79        /* must delete panels before their attached windows */
  80        if (dialogue_window)
  81                close_panel(dialogue_panel);
  82        if (cooling_device_window)
  83                close_panel(data_panel);
  84
  85        close_window(title_bar_window);
  86        close_window(tz_sensor_window);
  87        close_window(status_bar_window);
  88        close_window(cooling_device_window);
  89        close_window(control_window);
  90        close_window(thermal_data_window);
  91        close_window(dialogue_window);
  92
  93}
  94
  95void write_status_bar(int x, char *line)
  96{
  97        mvwprintw(status_bar_window, 0, x, "%s", line);
  98        wrefresh(status_bar_window);
  99}
 100
 101void setup_windows(void)
 102{
 103        int y_begin = 1;
 104
 105        if (tui_disabled)
 106                return;
 107
 108        getmaxyx(stdscr, maxy, maxx);
 109        resizeterm(maxy, maxx);
 110
 111        title_bar_window = subwin(stdscr, TITLE_BAR_HIGHT, maxx, 0, 0);
 112        y_begin += TITLE_BAR_HIGHT;
 113
 114        tz_sensor_window = subwin(stdscr, SENSOR_WIN_HIGHT, maxx, y_begin, 0);
 115        y_begin += SENSOR_WIN_HIGHT;
 116
 117        cooling_device_window = subwin(stdscr, ptdata.nr_cooling_dev + 3, maxx,
 118                                y_begin, 0);
 119        y_begin += ptdata.nr_cooling_dev + 3; /* 2 lines for border */
 120        /* two lines to show borders, one line per tz show trip point position
 121         * and value.
 122         * dialogue window is a pop-up, when needed it lays on top of cdev win
 123         */
 124
 125        dialogue_window = subwin(stdscr, ptdata.nr_cooling_dev+5, maxx-50,
 126                                DIAG_Y, DIAG_X);
 127
 128        thermal_data_window = subwin(stdscr, ptdata.nr_tz_sensor *
 129                                NR_LINES_TZDATA + 3, maxx, y_begin, 0);
 130        y_begin += ptdata.nr_tz_sensor * NR_LINES_TZDATA + 3;
 131        control_window = subwin(stdscr, 4, maxx, y_begin, 0);
 132
 133        scrollok(cooling_device_window, TRUE);
 134        maxwidth = maxx - 18;
 135        status_bar_window = subwin(stdscr, 1, maxx, maxy-1, 0);
 136
 137        strcpy(status_bar_slots[0], " Ctrl-c - Quit ");
 138        strcpy(status_bar_slots[1], " TAB - Tuning ");
 139        wmove(status_bar_window, 1, 30);
 140
 141        /* prepare panels for dialogue, if panel already created then we must
 142         * be doing resizing, so just replace windows with new ones, old ones
 143         * should have been deleted by close_window
 144         */
 145        data_panel = new_panel(cooling_device_window);
 146        if (!data_panel)
 147                syslog(LOG_DEBUG, "No data panel\n");
 148        else {
 149                if (dialogue_window) {
 150                        dialogue_panel = new_panel(dialogue_window);
 151                        if (!dialogue_panel)
 152                                syslog(LOG_DEBUG, "No dialogue panel\n");
 153                        else {
 154                                /* Set up the user pointer to the next panel*/
 155                                set_panel_userptr(data_panel, dialogue_panel);
 156                                set_panel_userptr(dialogue_panel, data_panel);
 157                                top = data_panel;
 158                        }
 159                } else
 160                        syslog(LOG_INFO, "no dialogue win, term too small\n");
 161        }
 162        doupdate();
 163        werase(stdscr);
 164        refresh();
 165}
 166
 167void resize_handler(int sig)
 168{
 169        /* start over when term gets resized, but first we clean up */
 170        close_windows();
 171        endwin();
 172        refresh();
 173        clear();
 174        getmaxyx(stdscr, maxy, maxx);  /* get the new screen size */
 175        setup_windows();
 176        /* rate limit */
 177        sleep(1);
 178        syslog(LOG_DEBUG, "SIG %d, term resized to %d x %d\n",
 179                sig, maxy, maxx);
 180        signal(SIGWINCH, resize_handler);
 181}
 182
 183const char cdev_title[] = " COOLING DEVICES ";
 184void show_cooling_device(void)
 185{
 186        int i, j, x, y = 0;
 187
 188        if (tui_disabled || !cooling_device_window)
 189                return;
 190
 191        werase(cooling_device_window);
 192        wattron(cooling_device_window, A_BOLD);
 193        mvwprintw(cooling_device_window,  1, 1,
 194                "ID  Cooling Dev   Cur    Max   Thermal Zone Binding");
 195        wattroff(cooling_device_window, A_BOLD);
 196        for (j = 0; j < ptdata.nr_cooling_dev; j++) {
 197                /* draw cooling device list on the left in the order of
 198                 * cooling device instances. skip unused idr.
 199                 */
 200                mvwprintw(cooling_device_window, j + 2, 1,
 201                        "%02d %12.12s%6d %6d",
 202                        ptdata.cdi[j].instance,
 203                        ptdata.cdi[j].type,
 204                        ptdata.cdi[j].cur_state,
 205                        ptdata.cdi[j].max_state);
 206        }
 207
 208        /* show cdev binding, y is the global cooling device instance */
 209        for (i = 0; i < ptdata.nr_tz_sensor; i++) {
 210                int tz_inst = ptdata.tzi[i].instance;
 211                for (j = 0; j < ptdata.nr_cooling_dev; j++) {
 212                        int cdev_inst;
 213                        y = j;
 214                        x = tz_inst * TZONE_RECORD_SIZE + TZ_LEFT_ALIGN;
 215
 216                        draw_hbar(cooling_device_window, y+2, x,
 217                                TZONE_RECORD_SIZE-1, ACS_VLINE, false);
 218
 219                        /* draw a column of spaces to separate thermal zones */
 220                        mvwprintw(cooling_device_window, y+2, x-1, " ");
 221                        if (ptdata.tzi[i].cdev_binding) {
 222                                cdev_inst = ptdata.cdi[j].instance;
 223                                unsigned long trip_binding =
 224                                        ptdata.tzi[i].trip_binding[cdev_inst];
 225                                int k = 0; /* per zone trip point id that
 226                                            * binded to this cdev, one to
 227                                            * many possible based on the
 228                                            * binding bitmask.
 229                                            */
 230                                syslog(LOG_DEBUG,
 231                                        "bind tz%d cdev%d tp%lx %d cdev%lx\n",
 232                                        i, j, trip_binding, y,
 233                                        ptdata.tzi[i].cdev_binding);
 234                                /* draw each trip binding for the cdev */
 235                                while (trip_binding >>= 1) {
 236                                        k++;
 237                                        if (!(trip_binding & 1))
 238                                                continue;
 239                                        /* draw '*' to show binding */
 240                                        mvwprintw(cooling_device_window,
 241                                                y + 2,
 242                                                x + ptdata.tzi[i].nr_trip_pts -
 243                                                k - 1, "*");
 244                                }
 245                        }
 246                }
 247        }
 248        /* draw border after data so that border will not be messed up
 249         * even there is not enough space for all the data to be shown
 250         */
 251        wborder(cooling_device_window, 0, 0, 0, 0, 0, 0, 0, 0);
 252        wattron(cooling_device_window, A_BOLD);
 253        mvwprintw(cooling_device_window, 0, maxx/2 - sizeof(cdev_title),
 254                cdev_title);
 255        wattroff(cooling_device_window, A_BOLD);
 256
 257        wrefresh(cooling_device_window);
 258}
 259
 260const char DIAG_TITLE[] = "[ TUNABLES ]";
 261#define DIAG_DEV_ROWS  5
 262void show_dialogue(void)
 263{
 264        int j, x = 0, y = 0;
 265        WINDOW *w = dialogue_window;
 266
 267        if (tui_disabled || !w)
 268                return;
 269
 270        werase(w);
 271        box(w, 0, 0);
 272        mvwprintw(w, 0, maxx/4, DIAG_TITLE);
 273        /* list all the available tunables */
 274        for (j = 0; j <= ptdata.nr_cooling_dev; j++) {
 275                y = j % DIAG_DEV_ROWS;
 276                if (y == 0 && j != 0)
 277                        x += 20;
 278                if (j == ptdata.nr_cooling_dev)
 279                        /* save last choice for target temp */
 280                        mvwprintw(w, y+1, x+1, "%C-%.12s", 'A'+j, "Set Temp");
 281                else
 282                        mvwprintw(w, y+1, x+1, "%C-%.10s-%2d", 'A'+j,
 283                                ptdata.cdi[j].type, ptdata.cdi[j].instance);
 284        }
 285        wattron(w, A_BOLD);
 286        mvwprintw(w, DIAG_DEV_ROWS+1, 1, "Enter Choice [A-Z]?");
 287        wattroff(w, A_BOLD);
 288        /* y size of dialogue win is nr cdev + 5, so print legend
 289         * at the bottom line
 290         */
 291        mvwprintw(w, ptdata.nr_cooling_dev+3, 1,
 292                "Legend: A=Active, P=Passive, C=Critical");
 293
 294        wrefresh(dialogue_window);
 295}
 296
 297void write_dialogue_win(char *buf, int y, int x)
 298{
 299        WINDOW *w = dialogue_window;
 300
 301        mvwprintw(w, y, x, "%s", buf);
 302}
 303
 304const char control_title[] = " CONTROLS ";
 305void show_control_w(void)
 306{
 307        unsigned long state;
 308
 309        get_ctrl_state(&state);
 310
 311        if (tui_disabled || !control_window)
 312                return;
 313
 314        werase(control_window);
 315        mvwprintw(control_window, 1, 1,
 316                "PID gain: kp=%2.2f ki=%2.2f kd=%2.2f Output %2.2f",
 317                p_param.kp, p_param.ki, p_param.kd, p_param.y_k);
 318
 319        mvwprintw(control_window, 2, 1,
 320                "Target Temp: %2.1fC, Zone: %d, Control Device: %.12s",
 321                p_param.t_target, target_thermal_zone, ctrl_cdev);
 322
 323        /* draw border last such that everything is within boundary */
 324        wborder(control_window, 0, 0, 0, 0, 0, 0, 0, 0);
 325        wattron(control_window, A_BOLD);
 326        mvwprintw(control_window, 0, maxx/2 - sizeof(control_title),
 327                control_title);
 328        wattroff(control_window, A_BOLD);
 329
 330        wrefresh(control_window);
 331}
 332
 333void initialize_curses(void)
 334{
 335        if (tui_disabled)
 336                return;
 337
 338        initscr();
 339        start_color();
 340        keypad(stdscr, TRUE);   /* enable keyboard mapping */
 341        nonl();                 /* tell curses not to do NL->CR/NL on output */
 342        cbreak();               /* take input chars one at a time */
 343        noecho();               /* dont echo input */
 344        curs_set(0);            /* turn off cursor */
 345        use_default_colors();
 346
 347        init_pair(PT_COLOR_DEFAULT, COLOR_WHITE, COLOR_BLACK);
 348        init_pair(PT_COLOR_HEADER_BAR, COLOR_BLACK, COLOR_WHITE);
 349        init_pair(PT_COLOR_ERROR, COLOR_BLACK, COLOR_RED);
 350        init_pair(PT_COLOR_RED, COLOR_WHITE, COLOR_RED);
 351        init_pair(PT_COLOR_YELLOW, COLOR_WHITE, COLOR_YELLOW);
 352        init_pair(PT_COLOR_GREEN, COLOR_WHITE, COLOR_GREEN);
 353        init_pair(PT_COLOR_BLUE, COLOR_WHITE, COLOR_BLUE);
 354        init_pair(PT_COLOR_BRIGHT, COLOR_WHITE, COLOR_BLACK);
 355
 356}
 357
 358void show_title_bar(void)
 359{
 360        int i;
 361        int x = 0;
 362
 363        if (tui_disabled || !title_bar_window)
 364                return;
 365
 366        wattrset(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR));
 367        wbkgd(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR));
 368        werase(title_bar_window);
 369
 370        mvwprintw(title_bar_window, 0, 0,
 371                "     TMON v%s", VERSION);
 372
 373        wrefresh(title_bar_window);
 374
 375        werase(status_bar_window);
 376
 377        for (i = 0; i < 10; i++) {
 378                if (strlen(status_bar_slots[i]) == 0)
 379                        continue;
 380                wattron(status_bar_window, A_REVERSE);
 381                mvwprintw(status_bar_window, 0, x, "%s", status_bar_slots[i]);
 382                wattroff(status_bar_window, A_REVERSE);
 383                x += strlen(status_bar_slots[i]) + 1;
 384        }
 385        wrefresh(status_bar_window);
 386}
 387
 388static void handle_input_val(int ch)
 389{
 390        char buf[32];
 391        int val;
 392        char path[256];
 393        WINDOW *w = dialogue_window;
 394
 395        echo();
 396        keypad(w, TRUE);
 397        wgetnstr(w, buf, 31);
 398        val = atoi(buf);
 399
 400        if (ch == ptdata.nr_cooling_dev) {
 401                snprintf(buf, 31, "Invalid Temp %d! %d-%d", val,
 402                        MIN_CTRL_TEMP, MAX_CTRL_TEMP);
 403                if (val < MIN_CTRL_TEMP || val > MAX_CTRL_TEMP)
 404                        write_status_bar(40, buf);
 405                else {
 406                        p_param.t_target = val;
 407                        snprintf(buf, 31, "Set New Target Temp %d", val);
 408                        write_status_bar(40, buf);
 409                }
 410        } else {
 411                snprintf(path, 256, "%s/%s%d", THERMAL_SYSFS,
 412                        CDEV, ptdata.cdi[ch].instance);
 413                sysfs_set_ulong(path, "cur_state", val);
 414        }
 415        noecho();
 416        dialogue_on = 0;
 417        show_data_w();
 418        show_control_w();
 419
 420        top = (PANEL *)panel_userptr(top);
 421        top_panel(top);
 422}
 423
 424static void handle_input_choice(int ch)
 425{
 426        char buf[48];
 427        int base = 0;
 428        int cdev_id = 0;
 429
 430        if ((ch >= 'A' && ch <= 'A' + ptdata.nr_cooling_dev) ||
 431                (ch >= 'a' && ch <= 'a' + ptdata.nr_cooling_dev)) {
 432                base = (ch < 'a') ? 'A' : 'a';
 433                cdev_id = ch - base;
 434                if (ptdata.nr_cooling_dev == cdev_id)
 435                        snprintf(buf, sizeof(buf), "New Target Temp:");
 436                else
 437                        snprintf(buf, sizeof(buf), "New Value for %.10s-%2d: ",
 438                                ptdata.cdi[cdev_id].type,
 439                                ptdata.cdi[cdev_id].instance);
 440                write_dialogue_win(buf, DIAG_DEV_ROWS+2, 2);
 441                handle_input_val(cdev_id);
 442        } else {
 443                snprintf(buf, sizeof(buf), "Invalid selection %d", ch);
 444                write_dialogue_win(buf, 8, 2);
 445        }
 446}
 447
 448void *handle_tui_events(void *arg)
 449{
 450        int ch;
 451
 452        keypad(cooling_device_window, TRUE);
 453        while ((ch = wgetch(cooling_device_window)) != EOF) {
 454                if (tmon_exit)
 455                        break;
 456                /* when term size is too small, no dialogue panels are set.
 457                 * we need to filter out such cases.
 458                 */
 459                if (!data_panel || !dialogue_panel ||
 460                        !cooling_device_window ||
 461                        !dialogue_window) {
 462
 463                        continue;
 464                }
 465                pthread_mutex_lock(&input_lock);
 466                if (dialogue_on) {
 467                        handle_input_choice(ch);
 468                        /* top panel filter */
 469                        if (ch == 'q' || ch == 'Q')
 470                                ch = 0;
 471                }
 472                switch (ch) {
 473                case KEY_LEFT:
 474                        box(cooling_device_window, 10, 0);
 475                        break;
 476                case 9: /* TAB */
 477                        top = (PANEL *)panel_userptr(top);
 478                        top_panel(top);
 479                        if (top == dialogue_panel) {
 480                                dialogue_on = 1;
 481                                show_dialogue();
 482                        } else {
 483                                dialogue_on = 0;
 484                                /* force refresh */
 485                                show_data_w();
 486                                show_control_w();
 487                        }
 488                        break;
 489                case 'q':
 490                case 'Q':
 491                        tmon_exit = 1;
 492                        break;
 493                }
 494                update_panels();
 495                doupdate();
 496                pthread_mutex_unlock(&input_lock);
 497        }
 498
 499        if (arg)
 500                *(int *)arg = 0; /* make gcc happy */
 501
 502        return NULL;
 503}
 504
 505/* draw a horizontal bar in given pattern */
 506static void draw_hbar(WINDOW *win, int y, int start, int len, unsigned long ptn,
 507                bool end)
 508{
 509        mvwaddch(win, y, start, ptn);
 510        whline(win, ptn, len);
 511        if (end)
 512                mvwaddch(win, y, MAX_DISP_TEMP+TDATA_LEFT, ']');
 513}
 514
 515static char trip_type_to_char(int type)
 516{
 517        switch (type) {
 518        case THERMAL_TRIP_CRITICAL: return 'C';
 519        case THERMAL_TRIP_HOT: return 'H';
 520        case THERMAL_TRIP_PASSIVE: return 'P';
 521        case THERMAL_TRIP_ACTIVE: return 'A';
 522        default:
 523                return '?';
 524        }
 525}
 526
 527/* fill a string with trip point type and value in one line
 528 * e.g.      P(56)    C(106)
 529 * maintain the distance one degree per char
 530 */
 531static void draw_tp_line(int tz, int y)
 532{
 533        int j;
 534        int x;
 535
 536        for (j = 0; j < ptdata.tzi[tz].nr_trip_pts; j++) {
 537                x = ptdata.tzi[tz].tp[j].temp / 1000;
 538                mvwprintw(thermal_data_window, y + 0, x + TDATA_LEFT,
 539                        "%c%d", trip_type_to_char(ptdata.tzi[tz].tp[j].type),
 540                        x);
 541                syslog(LOG_INFO, "%s:tz %d tp %d temp = %lu\n", __func__,
 542                        tz, j, ptdata.tzi[tz].tp[j].temp);
 543        }
 544}
 545
 546const char data_win_title[] = " THERMAL DATA ";
 547void show_data_w(void)
 548{
 549        int i;
 550
 551
 552        if (tui_disabled || !thermal_data_window)
 553                return;
 554
 555        werase(thermal_data_window);
 556        wattron(thermal_data_window, A_BOLD);
 557        mvwprintw(thermal_data_window, 0, maxx/2 - sizeof(data_win_title),
 558                data_win_title);
 559        wattroff(thermal_data_window, A_BOLD);
 560        /* draw a line as ruler */
 561        for (i = 10; i < MAX_DISP_TEMP; i += 10)
 562                mvwprintw(thermal_data_window, 1, i+TDATA_LEFT, "%2d", i);
 563
 564        for (i = 0; i < ptdata.nr_tz_sensor; i++) {
 565                int temp = trec[cur_thermal_record].temp[i] / 1000;
 566                int y = 0;
 567
 568                y = i * NR_LINES_TZDATA + 2;
 569                /* y at tz temp data line */
 570                mvwprintw(thermal_data_window, y, 1, "%6.6s%2d:[%3d][",
 571                        ptdata.tzi[i].type,
 572                        ptdata.tzi[i].instance, temp);
 573                draw_hbar(thermal_data_window, y, TDATA_LEFT, temp, ACS_RARROW,
 574                        true);
 575                draw_tp_line(i, y);
 576        }
 577        wborder(thermal_data_window, 0, 0, 0, 0, 0, 0, 0, 0);
 578        wrefresh(thermal_data_window);
 579}
 580
 581const char tz_title[] = "THERMAL ZONES(SENSORS)";
 582
 583void show_sensors_w(void)
 584{
 585        int i, j;
 586        char buffer[512];
 587
 588        if (tui_disabled || !tz_sensor_window)
 589                return;
 590
 591        werase(tz_sensor_window);
 592
 593        memset(buffer, 0, sizeof(buffer));
 594        wattron(tz_sensor_window, A_BOLD);
 595        mvwprintw(tz_sensor_window, 1, 1, "Thermal Zones:");
 596        wattroff(tz_sensor_window, A_BOLD);
 597
 598        mvwprintw(tz_sensor_window, 1, TZ_LEFT_ALIGN, "%s", buffer);
 599        /* fill trip points for each tzone */
 600        wattron(tz_sensor_window, A_BOLD);
 601        mvwprintw(tz_sensor_window, 2, 1, "Trip Points:");
 602        wattroff(tz_sensor_window, A_BOLD);
 603
 604        /* draw trip point from low to high for each tz */
 605        for (i = 0; i < ptdata.nr_tz_sensor; i++) {
 606                int inst = ptdata.tzi[i].instance;
 607
 608                mvwprintw(tz_sensor_window, 1,
 609                        TZ_LEFT_ALIGN+TZONE_RECORD_SIZE * inst, "%.9s%02d",
 610                        ptdata.tzi[i].type, ptdata.tzi[i].instance);
 611                for (j = ptdata.tzi[i].nr_trip_pts - 1; j >= 0; j--) {
 612                        /* loop through all trip points */
 613                        char type;
 614                        int tp_pos;
 615                        /* reverse the order here since trips are sorted
 616                         * in ascending order in terms of temperature.
 617                         */
 618                        tp_pos = ptdata.tzi[i].nr_trip_pts - j - 1;
 619
 620                        type = trip_type_to_char(ptdata.tzi[i].tp[j].type);
 621                        mvwaddch(tz_sensor_window, 2,
 622                                inst * TZONE_RECORD_SIZE + TZ_LEFT_ALIGN +
 623                                tp_pos, type);
 624                        syslog(LOG_DEBUG, "draw tz %d tp %d ch:%c\n",
 625                                inst, j, type);
 626                }
 627        }
 628        wborder(tz_sensor_window, 0, 0, 0, 0, 0, 0, 0, 0);
 629        wattron(tz_sensor_window, A_BOLD);
 630        mvwprintw(tz_sensor_window, 0, maxx/2 - sizeof(tz_title), tz_title);
 631        wattroff(tz_sensor_window, A_BOLD);
 632        wrefresh(tz_sensor_window);
 633}
 634
 635void disable_tui(void)
 636{
 637        tui_disabled = 1;
 638}
 639