diff options
author | Andreas Eversberg <jolly@eversberg.eu> | 2011-12-01 12:11:40 +0100 |
---|---|---|
committer | Andreas Eversberg <jolly@eversberg.eu> | 2016-09-25 08:11:47 +0200 |
commit | 4ddd47a80422a540306ff6992599bc77f52a2c84 (patch) | |
tree | 24e0099b7409c85bfd1963b2c8d822464d958dfd | |
parent | 958292f3a4024c54115035cb3379e301168fbfde (diff) |
layer23/mobile: Adding user interface to mobile application
Enable it in the VTY:
en
conf t
ms 1
user-interface
telnet-port 4248
write
It will show a status screen. Different items to display in the status
screen can be enabled/disabled via configuration.
Dialing and handling of one or multiple calls are possible.
This interface is not complete.
-rw-r--r-- | src/host/layer23/include/osmocom/bb/common/osmocom_data.h | 2 | ||||
-rw-r--r-- | src/host/layer23/include/osmocom/bb/mobile/Makefile.am | 2 | ||||
-rw-r--r-- | src/host/layer23/include/osmocom/bb/mobile/gui.h | 50 | ||||
-rw-r--r-- | src/host/layer23/include/osmocom/bb/mobile/settings.h | 4 | ||||
-rw-r--r-- | src/host/layer23/include/osmocom/bb/mobile/vty.h | 4 | ||||
-rw-r--r-- | src/host/layer23/src/mobile/Makefile.am | 4 | ||||
-rw-r--r-- | src/host/layer23/src/mobile/app_mobile.c | 17 | ||||
-rw-r--r-- | src/host/layer23/src/mobile/gsm480_ss.c | 27 | ||||
-rw-r--r-- | src/host/layer23/src/mobile/gui.c | 2481 | ||||
-rw-r--r-- | src/host/layer23/src/mobile/main.c | 2 | ||||
-rw-r--r-- | src/host/layer23/src/mobile/mnccms.c | 14 | ||||
-rw-r--r-- | src/host/layer23/src/mobile/settings.c | 7 | ||||
-rw-r--r-- | src/host/layer23/src/mobile/vty_interface.c | 175 |
13 files changed, 2775 insertions, 14 deletions
diff --git a/src/host/layer23/include/osmocom/bb/common/osmocom_data.h b/src/host/layer23/include/osmocom/bb/common/osmocom_data.h index d42cf09b..191fb96d 100644 --- a/src/host/layer23/include/osmocom/bb/common/osmocom_data.h +++ b/src/host/layer23/include/osmocom/bb/common/osmocom_data.h @@ -19,6 +19,7 @@ struct osmocom_ms; #include <osmocom/bb/mobile/gsm48_mm.h> #include <osmocom/bb/mobile/gsm48_cc.h> #include <osmocom/bb/mobile/mncc_sock.h> +#include <osmocom/bb/mobile/gui.h> #include <osmocom/bb/common/sim.h> #include <osmocom/bb/common/l1ctl.h> @@ -78,6 +79,7 @@ struct osmocom_ms { struct gsm48_cclayer cclayer; struct osmomncc_entity mncc_entity; struct llist_head trans_list; + struct gsm_ui gui; }; enum osmobb_sig_subsys { diff --git a/src/host/layer23/include/osmocom/bb/mobile/Makefile.am b/src/host/layer23/include/osmocom/bb/mobile/Makefile.am index 6adb6991..38a5b4e6 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/Makefile.am +++ b/src/host/layer23/include/osmocom/bb/mobile/Makefile.am @@ -1,3 +1,3 @@ noinst_HEADERS = gsm322.h gsm480_ss.h gsm411_sms.h gsm48_cc.h gsm48_mm.h \ gsm48_rr.h mncc.h settings.h subscriber.h support.h \ - transaction.h vty.h mncc_sock.h mnccms.h + transaction.h vty.h mncc_sock.h mnccms.h gui.h diff --git a/src/host/layer23/include/osmocom/bb/mobile/gui.h b/src/host/layer23/include/osmocom/bb/mobile/gui.h new file mode 100644 index 00000000..a67044d8 --- /dev/null +++ b/src/host/layer23/include/osmocom/bb/mobile/gui.h @@ -0,0 +1,50 @@ +#ifndef _gui_h +#define _gui_h + +#include <osmocom/bb/ui/ui.h> + +struct status_screen { + const char *feature; + const char *feature_vty; + const char *feature_help; + int default_en; + int lines; + int (*display_func)(struct osmocom_ms *ms, + char *text); +}; + +extern struct status_screen status_screen[]; + +#define GUI_NUM_STATUS 9 /* number of status infos */ +#define GUI_NUM_STATUS_LINES 32 /* total number of lines of all status infos */ + +struct gsm_ui { + struct ui_inst ui; /* user interface instance */ + int menu; /* current menu */ + const char *status_lines[GUI_NUM_STATUS_LINES]; + /* list of status lines */ + char status_text[GUI_NUM_STATUS_LINES * (UI_COLS + 1) + 128]; + /* memory for status lines (extra 128 bytes + * in case of overflow */ + struct osmo_timer_list timer; + /* refresh timer */ + char dialing[33]; /* dailing buffer */ + int selected_call; /* call that is selected */ + + /* select menus */ + void *select_menu; /* current menu */ + void *choose_menu; /* current choose item in menu */ + + /* supserv */ + int ss_lines; /* number of lines we display */ + int ss_pending, ss_active; + /* state of transaction */ +}; + +void gui_init_status_config(void); +int gui_start(struct osmocom_ms *ms); +int gui_stop(struct osmocom_ms *ms); +int gui_notify_call(struct osmocom_ms *ms); +int gui_notify_ss(struct osmocom_ms *ms, const char *fmt, ...); + +#endif /* _gui_h */ diff --git a/src/host/layer23/include/osmocom/bb/mobile/settings.h b/src/host/layer23/include/osmocom/bb/mobile/settings.h index 37f87873..b5f25e83 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/settings.h +++ b/src/host/layer23/include/osmocom/bb/mobile/settings.h @@ -104,6 +104,10 @@ struct gsm_settings { /* Ring tone */ uint8_t ringtone; /* 0 = off */ + + /* UI */ + uint16_t ui_port; /* telnet port: 0, if disabled */ + uint32_t status_enable; /* status display flags */ }; struct gsm_settings_abbrev { diff --git a/src/host/layer23/include/osmocom/bb/mobile/vty.h b/src/host/layer23/include/osmocom/bb/mobile/vty.h index 1f1341bc..d59853fd 100644 --- a/src/host/layer23/include/osmocom/bb/mobile/vty.h +++ b/src/host/layer23/include/osmocom/bb/mobile/vty.h @@ -10,10 +10,12 @@ enum ms_vty_node { MS_NODE = _LAST_OSMOVTY_NODE + 1, TESTSIM_NODE, SUPPORT_NODE, + UI_NODE, }; enum node_type ms_vty_go_parent(struct vty *vty); -int ms_vty_init(void); +int ms_vty_init(void *tall_ctx); +void ms_vty_exit(void); extern void vty_notify(struct osmocom_ms *ms, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); #endif diff --git a/src/host/layer23/src/mobile/Makefile.am b/src/host/layer23/src/mobile/Makefile.am index 04dd0257..90cebe42 100644 --- a/src/host/layer23/src/mobile/Makefile.am +++ b/src/host/layer23/src/mobile/Makefile.am @@ -1,11 +1,11 @@ AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBGPS_CFLAGS) -LDADD = ../common/liblayer23.a $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBGPS_LIBS) +LDADD = ../common/liblayer23.a ../ui/libui.a $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBGPS_LIBS) noinst_LIBRARIES = libmobile.a libmobile_a_SOURCES = gsm322.c gsm480_ss.c gsm411_sms.c gsm48_cc.c gsm48_mm.c \ gsm48_rr.c mnccms.c settings.c subscriber.c support.c \ - transaction.c vty_interface.c voice.c mncc_sock.c + transaction.c vty_interface.c voice.c mncc_sock.c gui.c bin_PROGRAMS = mobile diff --git a/src/host/layer23/src/mobile/app_mobile.c b/src/host/layer23/src/mobile/app_mobile.c index 59284e9d..1473e77c 100644 --- a/src/host/layer23/src/mobile/app_mobile.c +++ b/src/host/layer23/src/mobile/app_mobile.c @@ -39,6 +39,7 @@ #include <osmocom/bb/mobile/mnccms.h> #include <osmocom/bb/mobile/voice.h> #include <osmocom/bb/common/sap_interface.h> +#include <osmocom/bb/ui/telnet_interface.h> #include <osmocom/vty/telnet_interface.h> #include <osmocom/core/msgb.h> @@ -155,6 +156,8 @@ int mobile_exit(struct osmocom_ms *ms, int force) return -EBUSY; } + gui_stop(ms); + ui_telnet_exit(&ms->gui.ui); gsm322_exit(ms); gsm48_mm_exit(ms); gsm48_rr_exit(ms); @@ -228,6 +231,17 @@ int mobile_init(struct osmocom_ms *ms) } #endif + if (ms->settings.ui_port) { + rc = ui_telnet_init(&ms->gui.ui, l23_ctx, ms->settings.ui_port); + if (rc < 0) { + fprintf(stderr, "Failed during ui_telnet_init()\n"); + mobile_exit(ms, 1); + return rc; + } + printf("UI available on port %u.\n", ms->settings.ui_port); + gui_start(ms); + } + gsm_random_imei(&ms->settings); ms->shutdown = 0; @@ -373,6 +387,7 @@ int l23_app_exit(void) osmo_gps_close(); + ms_vty_exit(); telnet_exit(); return 0; @@ -396,7 +411,7 @@ int l23_app_init(int (*mncc_recv)(struct osmocom_ms *ms, int, void *), osmo_gps_init(); vty_init(&vty_info); - ms_vty_init(); + ms_vty_init(l23_ctx); dummy_conn.priv = NULL; vty_reading = 1; if (config_file != NULL) { diff --git a/src/host/layer23/src/mobile/gsm480_ss.c b/src/host/layer23/src/mobile/gsm480_ss.c index d626a0eb..7a931258 100644 --- a/src/host/layer23/src/mobile/gsm480_ss.c +++ b/src/host/layer23/src/mobile/gsm480_ss.c @@ -266,6 +266,7 @@ void _gsm480_ss_trans_free(struct gsm_trans *trans) } vty_notify(trans->ms, NULL); vty_notify(trans->ms, "Service connection terminated.\n"); + gui_notify_ss(trans->ms, NULL); /* end of SS processing */ } /* release MM connection, free transaction */ @@ -426,6 +427,8 @@ static int return_imei(struct osmocom_ms *ms) sprintf(text, "IMEI: %s SV: %s", set->imei, set->imeisv + strlen(set->imei)); gsm480_ss_result(ms, text, 0); + gui_notify_ss(ms, "%s\n", text); + gui_notify_ss(ms, NULL); /* end of SS processing */ return 0; } @@ -579,6 +582,7 @@ int ss_send(struct osmocom_ms *ms, const char *code, int new_trans) /* if there is an old transaction, check if we can send data */ if (trans) { if (trans->ss.state != GSM480_SS_ST_ACTIVE) { + //FIXME: process hangup in pending state LOGP(DSS, LOGL_INFO, "Pending trans not active.\n"); gsm480_ss_result(trans->ms, "Current service pending", 0); @@ -786,6 +790,7 @@ static int gsm480_rx_ussd(struct gsm_trans *trans, const uint8_t *data, if (text[0] && text[strlen(text) - 1] == '\n') text[strlen(text) - 1] = '\0'; gsm480_ss_result(trans->ms, text, 0); + gui_notify_ss(trans->ms, "%s\n", text); return 0; } @@ -809,10 +814,13 @@ static int gsm480_rx_cf(struct gsm_trans *trans, const uint8_t *data, return -EINVAL; } if (data[0] == 0x80) { - if ((tag_data[0] & 0x01)) + if ((tag_data[0] & 0x01)) { vty_notify(ms, "Status: activated\n"); - else + gui_notify_ss(ms, "activated\n"); + } else { vty_notify(ms, "Status: deactivated\n"); + gui_notify_ss(ms, "deactivated\n"); + } return 0; } @@ -848,6 +856,7 @@ static int gsm480_rx_cf(struct gsm_trans *trans, const uint8_t *data, break; default: vty_notify(ms, "Call Forwarding reply unsupported.\n"); + gui_notify_ss(ms, "reply unsupported\n"); return 0; } @@ -882,17 +891,26 @@ static int gsm480_rx_cf(struct gsm_trans *trans, const uint8_t *data, vty_notify(ms, "Bearer Service: %s\n", get_value_string(Bearerservice_vals, tag_data[0])); + gui_notify_ss(ms, "Bearer: %s\n", + get_value_string(Bearerservice_vals, + tag_data[0])); break; case 0x83: vty_notify(ms, "Teleservice: %s\n", get_value_string(Teleservice_vals, tag_data[0])); + gui_notify_ss(ms, "Tele: %s\n", + get_value_string(Teleservice_vals, + tag_data[0])); break; case 0x84: - if ((tag_data[0] & 0x01)) + if ((tag_data[0] & 0x01)) { vty_notify(ms, "Status: activated\n"); - else + gui_notify_ss(ms, "activated\n"); + } else { vty_notify(ms, "Status: deactivated\n"); + gui_notify_ss(ms, "deactivated\n"); + } break; case 0x85: if (((tag_data[0] & 0x70) >> 4) == 1) @@ -905,6 +923,7 @@ static int gsm480_rx_cf(struct gsm_trans *trans, const uint8_t *data, sizeof(number) - strlen(number), tag_data - 1, 1); vty_notify(ms, "Destination: %s\n", number); + gui_notify_ss(ms, "Dest: %s\n", number); break; } len2 -= tag_data - data2 + tag_len; diff --git a/src/host/layer23/src/mobile/gui.c b/src/host/layer23/src/mobile/gui.c new file mode 100644 index 00000000..067b168b --- /dev/null +++ b/src/host/layer23/src/mobile/gui.c @@ -0,0 +1,2481 @@ +/* + * (C) 2011 by Andreas Eversberg <jolly@eversberg.eu> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * 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 along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include <stdio.h> + +#include <osmocom/gsm/rsl.h> +#include <osmocom/bb/common/osmocom_data.h> +#include <osmocom/bb/common/networks.h> +#include <osmocom/bb/ui/telnet_interface.h> +#include <osmocom/bb/mobile/mnccms.h> +#include <osmocom/bb/mobile/gsm480_ss.h> + +/* + * status screen generation + */ + +static int status_netname(struct osmocom_ms *ms, char *text) +{ + struct gsm48_mmlayer *mm = &ms->mmlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm_subscriber *subscr = &ms->subscr; + int len, shift; + + /* No Service */ + if (ms->shutdown == 2 || !ms->started) { + strncpy(text, "SHUTDOWN", UI_COLS); + } else + /* No Service */ + if (mm->state == GSM48_MM_ST_MM_IDLE + && mm->substate == GSM48_MM_SST_NO_CELL_AVAIL) { + strncpy(text, "No Service", UI_COLS); + } else + /* Searching */ + if (mm->state == GSM48_MM_ST_MM_IDLE + && (mm->substate == GSM48_MM_SST_PLMN_SEARCH_NORMAL + || mm->substate == GSM48_MM_SST_PLMN_SEARCH)) { + strncpy(text, "Searching...", UI_COLS); + } else + if (mm->state == GSM48_MM_ST_MM_IDLE + && (mm->substate != GSM48_MM_SST_NORMAL_SERVICE + && mm->substate != GSM48_MM_SST_PLMN_SEARCH_NORMAL)) { + strncpy(text, "Emerg. only", UI_COLS); + } else + /* no network selected */ + if (!cs->selected) { + strncpy(text, "", UI_COLS); + } else + /* HPLM is the currently selected network, and we have a SPN */ + if (cs->selected && subscr->sim_valid && subscr->sim_spn[0] + && gsm_match_mnc(cs->sel_mcc, cs->sel_mnc, subscr->imsi)) { + strncpy(text, subscr->sim_spn, UI_COLS); + } else + /* network name set for currently selected network */ + if (cs->selected && (mm->name_short[0] || mm->name_long[0]) + && cs->sel_mcc == mm->name_mcc && cs->sel_mnc == mm->name_mnc) { + const char *name; + + /* only short name */ + if (mm->name_short[0] && !mm->name_long[0]) + name = mm->name_short; + /* only long name */ + else if (!mm->name_short[0] && mm->name_long[0]) + name = mm->name_long; + /* both names, long name fits */ + else if (strlen(mm->name_long) <= UI_COLS) + name = mm->name_long; + /* both names, use short name, even if it does not fit */ + else + name = mm->name_short; + + strncpy(text, name, UI_COLS); + } else + /* no network name for currently selected network */ + { + const char *mcc_name, *mnc_name; + int mcc_len, mnc_len; + + mcc_name = gsm_get_mcc(cs->sel_mcc); + mnc_name = gsm_get_mnc(cs->sel_mcc, cs->sel_mnc); + mcc_len = strlen(mcc_name); + mnc_len = strlen(mnc_name); + + /* MCC / MNC fits */ + if (mcc_len + 3 + mnc_len <= UI_COLS) + sprintf(text, "%s / %s", mcc_name, mnc_name); + /* MCC/MNC fits */ + else if (mcc_len + 1 + mnc_len <= UI_COLS) + sprintf(text, "%s/%s", mcc_name, mnc_name); + /* use MNC, even if it does not fit */ + else + strncpy(text, mnc_name, UI_COLS); + } + text[UI_COLS] = '\0'; + + /* center */ + len = strlen(text); + if (len + 1 < UI_COLS) { + shift = (UI_COLS - len) / 2; + memcpy(text + shift, text, len + 1); + memset(text, ' ', shift); + } + + return 1; +} + +static int status_lai(struct osmocom_ms *ms, char *text) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + + sprintf(text, "%s %s %04x", gsm_print_mcc(cs->sel_mcc), + gsm_print_mnc(cs->sel_mnc), cs->sel_lac); + text[UI_COLS] = '\0'; + return 1; +} + +static int status_imsi(struct osmocom_ms *ms, char *text) +{ + struct gsm_subscriber *subscr = &ms->subscr; + int len; + + if (subscr->imsi[0]) + strcpy(text, subscr->imsi); + else + strcpy(text, "---------------"); + if (subscr->tmsi < 0xfffffffe) + sprintf(strchr(text, '\0'), " %08x", subscr->tmsi); + else + strcat(text, " --------"); + len = strlen(text); + /* wrap */ + if (len > UI_COLS) { + memcpy(text + UI_COLS + 1, text + UI_COLS, len - UI_COLS + 1); + text[UI_COLS] = '\0'; + text[2 * UI_COLS + 1] = '\0'; + + return 2; + } + + return 1; +} + +static int status_imei(struct osmocom_ms *ms, char *text) +{ + struct gsm_settings *set = &ms->settings; + int len; + + sprintf(text, "%s/%s", set->imei, set->imeisv + strlen(set->imei)); + len = strlen(text); + /* wrap */ + if (len > UI_COLS) { + memcpy(text + UI_COLS + 1, text + UI_COLS, len - UI_COLS + 1); + text[UI_COLS] = '\0'; + text[2 * UI_COLS + 1] = '\0'; + + return 2; + } + + return 1; +} + +static int status_channel(struct osmocom_ms *ms, char *text) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm322_cellsel *cs = &ms->cellsel; + uint16_t arfcn = 0xffff; + int hopping = 0; + + /* arfcn */ + if (rr->dm_est) { + if (rr->cd_now.h) + hopping = 1; + else + arfcn = rr->cd_now.arfcn; + } else if (cs->selected) + arfcn = cs->sel_arfcn; + + if (hopping) + strcpy(text, "a:HOPP"); + else if (arfcn < 0xffff) { + sprintf(text, "a:%d", arfcn & 1023); + if (arfcn & ARFCN_PCS) + strcat(text, "P"); + else if (arfcn >= 512 && arfcn <= 885) + strcat(text, "D"); + else if (arfcn < 10) + strcat(text, " "); + else if (arfcn < 100) + strcat(text, " "); + else if (arfcn < 1000) + strcat(text, " "); + } else + strcpy(text, "a:----"); + + /* channel */ + if (!rr->dm_est) + strcat(text, " b:0"); + else { + uint8_t ch_type, ch_subch, ch_ts; + + rsl_dec_chan_nr(rr->cd_now.chan_nr, &ch_type, &ch_subch, + &ch_ts); + switch (ch_type) { + case RSL_CHAN_SDCCH8_ACCH: + case RSL_CHAN_SDCCH4_ACCH: + sprintf(strchr(text, '\0'), " s:%d/%d", ch_ts, + ch_subch); + break; + case RSL_CHAN_Lm_ACCHs: + sprintf(strchr(text, '\0'), " h:%d/%d", ch_ts, + ch_subch); + break; + case RSL_CHAN_Bm_ACCHs: + sprintf(strchr(text, '\0'), " f:%d", ch_ts); + break; + default: + sprintf(strchr(text, '\0'), " ?:%d", ch_ts); + break; + } + } + text[UI_COLS] = '\0'; + + return 1; +} + +static int status_rx(struct osmocom_ms *ms, char *text) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + + /* rxlev */ + if (rr->rxlev != 255) { + sprintf(text, "rx:%d dbm", rr->rxlev - 110); + } else + strcpy(text, "rx:--"); + text[UI_COLS] = '\0'; + + return 1; +} + +static int status_tx(struct osmocom_ms *ms, char *text) +{ + struct gsm48_rrlayer *rr = &ms->rrlayer; + struct gsm_settings *set = &ms->settings; + + /* ta, pwr */ + if (rr->dm_est) + sprintf(text, "ta:%d tx:%d", + rr->cd_now.ind_ta - set->alter_delay, + (set->alter_tx_power) ? set->alter_tx_power_value + : rr->cd_now.ind_tx_power); + else + strcpy(text, "ta:-- tx:--"); + text[UI_COLS] = '\0'; + + return 1; +} + +static int status_nb(struct osmocom_ms *ms, char *text) +{ + struct gsm322_cellsel *cs = &ms->cellsel; + struct gsm322_nb_summary *nb; + int i; + + for (i = 0; i < 6; i++) { + nb = &cs->nb_summary[i]; + if (nb->valid) { + sprintf(text, "%d:%d", i + 1, + nb->arfcn & 1023); + if (nb->arfcn & ARFCN_PCS) + strcat(text, "P"); + else if (nb->arfcn >= 512 && nb->arfcn <= 885) + strcat(text, "D"); + else if (nb->arfcn < 10) + strcat(text, " "); + else if (nb->arfcn < 100) + strcat(text, " "); + else if (nb->arfcn < 1000) + strcat(text, " "); + sprintf(strchr(text, '\0'), " %d", nb->rxlev_dbm); + } else + sprintf(text, "%d:-", i + 1); + text[UI_COLS] = '\0'; + text += (UI_COLS + 1); + } + + return i; +} + +static int status_fkeys(struct osmocom_ms *ms, char *text) +{ + return 0; +} + +/* + * status screen structure + */ + +#define SHOW_HIDE "(show|hide)" +#define SHOW_HIDE_STR "Show this feature on display\n" \ + "Do not show this feature on display" + +struct status_screen status_screen[GUI_NUM_STATUS] = { + /* network name */ + { + .feature = "network-name", + .feature_vty = "network-name " SHOW_HIDE, + .feature_help = "Show network name on display\n" + SHOW_HIDE_STR, + .default_en = 1, + .lines = 1, + .display_func = status_netname, + }, + /* LAI */ + { + .feature = "location-area-info", + .feature_vty = "location-area-info " SHOW_HIDE, + .feature_help = "Show LAI on display\n" SHOW_HIDE_STR, + .default_en = 0, + .lines = 1, + .display_func = status_lai, + }, + /* IMSI */ + { + .feature = "imsi", + .feature_vty = "imsi " SHOW_HIDE, + .feature_help = "Show IMSI/TMSI on display\n" SHOW_HIDE_STR, + .default_en = 0, + .lines = 2, + .display_func = status_imsi, + }, + /* IMEI */ + { + .feature = "imei", + .feature_vty = "imei " SHOW_HIDE, + .feature_help = "Show IMEI on display\n" SHOW_HIDE_STR, + .default_en = 0, + .lines = 2, + .display_func = status_imei, + }, + /* channel */ + { + .feature = "channel", + .feature_vty = "channel " SHOW_HIDE, + .feature_help = "Show current channel on display\n" + SHOW_HIDE_STR, + .default_en = 0, + .lines = 1, + .display_func = status_channel, + }, + /* tx */ + { + .feature = "tx", + .feature_vty = "tx " SHOW_HIDE, + .feature_help = "Show current timing advance and rx level " + "on display\n" SHOW_HIDE_STR, + .default_en = 0, + .lines = 1, + .display_func = status_tx, + }, + /* rx */ + { + .feature = "rx", + .feature_vty = "rx " SHOW_HIDE, + .feature_help = "Show current rx level on display\n" + SHOW_HIDE_STR, + .default_en = 0, + .lines = 1, + .display_func = status_rx, + }, + /* nb */ + { + .feature = "neighbours", + .feature_vty = "neighbours " SHOW_HIDE, + .feature_help = "Show neighbour cells on display\n" + SHOW_HIDE_STR, + .default_en = 0, + .lines = 6, + .display_func = status_nb, + }, + /* function keys */ + { + .feature = "function-keys", + .feature_vty = "function-keys " SHOW_HIDE, + .feature_help = "Show function keys (two keys right " + "below the display) on the display's bottom " + "line\n" SHOW_HIDE_STR, + .default_en = 1, + .lines = 0, + .display_func = status_fkeys, + }, +}; + +/* + * select menus + */ + +enum config_type { + SELECT_NODE, + SELECT_CHOOSE, + SELECT_NUMBER, + SELECT_INT, + SELECT_FIXINT, + SELECT_FIXSTRING, +}; + +struct gui_choose_set { + const char *name; + int value; +}; + +static struct gui_choose_set enable_disable_set[] = { + { "Enabled", 1 }, + { "Disabled", 0 }, + { NULL, 0 } +}; + +static struct gui_choose_set activated_deactivated_set[] = { + { "Activated", 1 }, + { "Deactivated", 0 }, + { NULL, 0 } +}; + +static struct gui_choose_set sim_type_set[] = { + { "None", GSM_SIM_TYPE_NONE }, + { "Reader", GSM_SIM_TYPE_READER }, + { "Test SIM", GSM_SIM_TYPE_TEST }, + { NULL, 0 } +}; + +static struct gui_choose_set network_mode_set[] = { + { "Automatic", PLMN_MODE_AUTO }, + { "Manual", PLMN_MODE_MANUAL }, + { NULL, 0 } +}; + +static struct gui_choose_set codec_set[] = { + { "Full-rate", 0 }, + { "Half-rate", 1 }, + { NULL, 0 } +}; + +/* structure defines one line in a config menu */ +struct gui_select_line { + const char *title; /* menu title */ + struct gui_select_line *parent; /* parent menu */ + + const char *name; /* entry name */ + enum config_type type; /* entry type */ + + /* sub node type */ + struct gui_select_line *node; /* sub menu */ + + /* select/number/int/fixint/fixstring type */ + struct gui_choose_set *set; /* settable values */ + void * (*query)(struct osmocom_ms *ms); + int (*cmd)(struct osmocom_ms *ms, void *vlaue); + int restart; /* if change requres reset */ + int digits; /* max number of digits */ + int min, max; /* range for integer */ + int fix; /* fixed value to use */ + char *fixstring; /* fixed string to use */ +}; + +static void *config_knocking_query(struct osmocom_ms *ms) +{ + static int value; + + value = ms->settings.cw; + + return &value; +} +static int config_knocking_cmd(struct osmocom_ms *ms, void *value) +{ + ms->settings.cw = *(int *)value; + + return 0; +} + +static void *config_autoanswer_query(struct osmocom_ms *ms) +{ + static int value; + + value = ms->settings.auto_answer; + + return &value; +} +static int config_autoanswer_cmd(struct osmocom_ms *ms, void *value) +{ + ms->settings.auto_answer = *(int *)value; + + return 0; +} + +static void *config_clip_query(struct osmocom_ms *ms) +{ + static int value; + + value = ms->settings.clip; + + return &value; +} +static int config_clip_cmd(struct osmocom_ms *ms, void *value) +{ + ms->settings.clip = *(int *)value; + if (value) + ms->settings.clir = 0; + + return 0; +} + +static void *config_clir_query(struct osmocom_ms *ms) +{ + static int value; + + value = ms->settings.clir; + + return &value; +} +static int config_clir_cmd(struct osmocom_ms *ms, void *value) +{ + ms->settings.clir = *(int *)value; + if (value) + ms->settings.clip = 0; + + return 0; +} + +static void *config_sca_query(struct osmocom_ms *ms) +{ + return ms->settings.sms_sca; +} +static int config_sca_cmd(struct osmocom_ms *ms, void *value) +{ + strncpy(ms->settings.sms_sca, value, sizeof(ms->settings.sms_sca) - 1); + + return 0; +} + +static void *config_sim_type_query(struct osmocom_ms *ms) +{ + static int value; + + value = ms->settings.sim_type; + + return &value; +} +static int config_sim_type_cmd(struct osmocom_ms *ms, void *value) +{ + ms->settings.sim_type = *(int *)value; + + return 0; +} + +static void *config_network_query(struct osmocom_ms *ms) +{ + static int value; + + value = ms->settings.plmn_mode; + + return &value; +} +static int config_network_cmd(struct osmocom_ms *ms, void *value) +{ + int mode = *(int *)value; + struct msgb *nmsg; + + if (!ms->started) + ms->settings.plmn_mode = mode; + else { + nmsg = gsm322_msgb_alloc((mode == PLMN_MODE_AUTO) ? + GSM322_EVENT_SEL_AUTO : GSM322_EVENT_SEL_MANUAL); + if (!nmsg) + return -ENOMEM; + gsm322_plmn_sendmsg(ms, nmsg); + } + + return 0; +} + +static void *config_imei_query(struct osmocom_ms *ms) +{ + return ms->settings.imei; +} +static int config_imei_cmd(struct osmocom_ms *ms, void *value) +{ + if (gsm_check_imei(value, "0")) + return -EINVAL; + + strncpy(ms->settings.imei, value, sizeof(ms->settings.imei) - 1); + /* only copy the number of digits in imei */ + strncpy(ms->settings.imeisv, value, strlen(ms->settings.imei)); + + return 0; +} + +static void *config_imeisv_query(struct osmocom_ms *ms) +{ + return ms->settings.imeisv + strlen(ms->settings.imei); +} +static int config_imeisv_cmd(struct osmocom_ms *ms, void *value) +{ + if (gsm_check_imei(ms->settings.imei, value)) + return -EINVAL; + + /* only copy the sv */ + strncpy(ms->settings.imeisv + strlen(ms->settings.imei), value, 1); + + return 0; +} + +static void *config_imei_random_query(struct osmocom_ms *ms) +{ + static int value; + + value = ms->settings.imei_random; + + return &value; +} +static int config_imei_random_cmd(struct osmocom_ms *ms, void *value) +{ + ms->settings.imei_random = *(int *)value; + + return 0; +} + +static void *config_emerg_imsi_query(struct osmocom_ms *ms) +{ + return ms->settings.emergency_imsi; +} +static int config_emerg_imsi_cmd(struct osmocom_ms *ms, void *value) +{ + if (strlen(value) && gsm_check_imsi(value)) + return -EINVAL; + + strcpy(ms->settings.emergency_imsi, value); + + return 0; +} + +static void *config_tx_power_query(struct osmocom_ms *ms) +{ + static int value; + + value = ms->settings.alter_tx_power_value; + + return &value; +} +static int config_tx_power_cmd(struct osmocom_ms *ms, void *value) +{ + ms->settings.alter_tx_power_value = *(int *)value; + ms->settings.alter_tx_power = 1; + + return 0; +} + +static int config_tx_power_auto_cmd(struct osmocom_ms *ms, void *value) +{ + ms->settings.alter_tx_power = 0; + + return 0; +} + +static void *config_sim_delay_query(struct osmocom_ms *ms) +{ + static int value; + + value = ms->settings.alter_delay; + + return &value; +} +static int config_sim_delay_cmd(struct osmocom_ms *ms, void *value) +{ + ms->settings.alter_delay = *(int *)value; + gsm48_rr_alter_delay(ms); + + return 0; +} + +static void *config_stick_query(struct osmocom_ms *ms) +{ + static int value; + + value = ms->settings.stick_arfcn & 1023; + + return &value; +} +static int config_stick_disable_cmd(struct osmocom_ms *ms, void *value) +{ + ms->settings.stick = 0; + + return 0; +} + +static int config_stick_cmd(struct osmocom_ms *ms, void *value) +{ + ms->settings.stick_arfcn = *(int *)value; + ms->settings.stick = 1; + + return 0; +} + +static int config_stick_pcs_cmd(struct osmocom_ms *ms, void *value) +{ + ms->settings.stick_arfcn = *(int *)value | ARFCN_PCS; + ms->settings.stick = 1; + + return 0; +} + +static void *config_support_half_query(struct osmocom_ms *ms) +{ + static int value = 1; + + if (!ms->settings.half_v1 && !ms->settings.half_v3) + value = 0; + else + value = ms->settings.half; + + return &value; +} +static int config_support_half_cmd(struct osmocom_ms *ms, void *value) +{ + if (!ms->settings.half_v1 && !ms->settings.half_v3) + return -ENOTSUP; + ms->settings.half = *(int *)value; + if (ms->settings.half == 0) + ms->settings.half_prefer = 0; + + return 0; +} + +static void *config_prefer_codec_query(struct osmocom_ms *ms) +{ + static int value = 0; + + if (!ms->settings.half_v1 && !ms->settings.half_v3) + return &value; + if (!ms->settings.half) + return &value; + value = ms->settings.half_prefer; + + return &value; +} +static int config_prefer_codec_cmd(struct osmocom_ms *ms, void *value) +{ + if (*(int *)value == 0) { + if (!ms->settings.full_v1 && !ms->settings.full_v2 + && !ms->settings.full_v3) + return -ENOTSUP; + ms->settings.half_prefer = 0; + } else { + if (!ms->settings.half_v1 && !ms->settings.half_v3) + return -ENOTSUP; + ms->settings.half = 1; + ms->settings.half_prefer = 1; + } + + return 0; +} + +static void *config_status_query(struct osmocom_ms *ms) +{ + static int value; + struct gui_select_line *menu = ms->gui.choose_menu; + int i = 0; + + for (i = 0; i < GUI_NUM_STATUS; i++) { + if (!strcmp(menu->name, status_screen[i].feature)) + break; + } + + value = (ms->settings.status_enable >> i) & 1; + + return &value; +} +static int config_status_cmd(struct osmocom_ms *ms, void *value) +{ + struct gui_select_line *menu = ms->gui.choose_menu; + int i = 0; + + for (i = 0; i < GUI_NUM_STATUS; i++) { + if (!strcmp(menu->name, status_screen[i].feature)) + break; + } + if (*(int *)value) + ms->settings.status_enable |= (1 << i); + else + ms->settings.status_enable &= ~(1 << i); + + return 0; +} + +static struct gui_select_line config_setup[]; + +/* call node */ +static struct gui_select_line config_call[] = { + { + .title = "Call SETUP", + .parent = config_setup, + }, + { + .name = "Knocking", + .type = SELECT_CHOOSE, + .set = enable_disable_set, + .query = config_knocking_query, + .cmd = config_knocking_cmd, + .restart = 0, + }, + { + .name = "Autoanswer", + .type = SELECT_CHOOSE, + .set = enable_disable_set, + .query = config_autoanswer_query, + .cmd = config_autoanswer_cmd, + .restart = 0, + }, + { + .name = "CLIP", + .type = SELECT_CHOOSE, + .set = activated_deactivated_set, + .query = config_clip_query, + .cmd = config_clip_cmd, + .restart = 0, + }, + { + .name = "CLIR", + .type = SELECT_CHOOSE, + .set = activated_deactivated_set, + .query = config_clir_query, + .cmd = config_clir_cmd, + .restart = 0, + }, + { + .name = NULL, + }, +}; + +/* SMS node */ +static struct gui_select_line config_sms[] = { + { + .title = "SMS Setup", + .parent = config_setup, + }, + { + .name = "SCA Number", + .type = SELECT_NUMBER, + .query = config_sca_query, + .cmd = config_sca_cmd, + .digits = 20, + }, + { + .name = NULL, + }, +}; + +static struct gui_select_line config_phone[]; + +/* IMEI node */ +static struct gui_select_line config_imei[] = { + { + .title = "IMEI Setup", + .parent = config_phone, + }, + { + .name = "IMEI Number", + .type = SELECT_NUMBER, + .query = config_imei_query, + .cmd = config_imei_cmd, + .digits = 15, + }, + { + .name = "Software Ver", + .type = SELECT_NUMBER, + .query = config_imeisv_query, + .cmd = config_imeisv_cmd, + .digits = 1, + }, + { + .name = "Fixed IMEI", + .type = SELECT_FIXINT, + .cmd = config_imei_random_cmd, + .fix = 0, + }, + { + .name = "Randomize", + .type = SELECT_INT, + .query = config_imei_random_query, + .cmd = config_imei_random_cmd, + .min = 0, + .max = 15, + }, + { + .name = NULL, + }, +}; + +/* codec node */ +static struct gui_select_line config_codec[] = { + { + .title = "Voice Codec", + .parent = config_phone, + }, + { + .name = "Half-rate", + .type = SELECT_CHOOSE, + .set = enable_disable_set, + .query = config_support_half_query, + .cmd = config_support_half_cmd, + .restart = 0, + }, + { + .name = "Prefer Codec", + .type = SELECT_CHOOSE, + .set = codec_set, + .query = config_prefer_codec_query, + .cmd = config_prefer_codec_cmd, + .restart = 0, + }, + { + .name = NULL, + }, +}; + +#if 0 +/* support node */ +static struct gui_select_line config_support[] = { + { + .title = "Support", + .parent = config_phone, + }, + { + .name = NULL, + }, +}; +#endif + +/* emergency IMSI */ +static struct gui_select_line config_emerg_imsi[] = { + { + .title = "Emerg. IMSI", + .parent = config_phone, + }, + { + .name = "IMSI", + .type = SELECT_NUMBER, + .query = config_emerg_imsi_query, + .cmd = config_emerg_imsi_cmd, + .digits = 15, + }, + { + .name = "No IMSI", + .type = SELECT_FIXSTRING, + .cmd = config_emerg_imsi_cmd, + .fixstring = "", + }, + { + .name = NULL, + }, +}; + +/* TX power node */ +static struct gui_select_line config_tx_power[] = { + { + .title = "TX Power", + .name = "Level", /* name of fixed selection */ + .parent = config_phone, + }, + { + .name = "Auto", + .type = SELECT_FIXINT, + .cmd = config_tx_power_auto_cmd, + .fix = 0, + }, + { + .name = "Full", + .type = SELECT_FIXINT, + .cmd = config_tx_power_cmd, + .fix = 0, + }, + { + .name = "Level", + .type = SELECT_INT, + .query = config_tx_power_query, + .cmd = config_tx_power_cmd, + .min = 0, + .max = 31, + }, + { + .name = NULL, + }, +}; + +/* simulated delay node */ +static struct gui_select_line config_sim_delay[] = { + { + .title = "Sim. Delay", + .name = "Delay", /* name of fixed selection */ + .parent = config_phone, + }, + { + .name = "Disabled", + .type = SELECT_FIXINT, + .cmd = config_sim_delay_cmd, + .fix = 0, + }, + { + .name = "Level", + .type = SELECT_INT, + .query = config_sim_delay_query, + .cmd = config_sim_delay_cmd, + .min = -128, + .max = 127, + }, + { + .name = NULL, + }, +}; + +/* stick node */ +static struct gui_select_line config_stick[] = { + { + .title = "Stick ARFCN", + .name = "Stick", /* name of fixed selection */ + .parent = config_phone, + }, + { + .name = "Disabled", + .type = SELECT_FIXINT, + .cmd = config_stick_disable_cmd, + .fix = 0, + }, + { + .name = "ARFCN", + .type = SELECT_INT, + .query = config_stick_query, + .cmd = config_stick_cmd, + .min = 0, + .max = 1023, + }, + { + .name = "ARFCN PCS", + .type = SELECT_INT, + .query = config_stick_query, + .cmd = config_stick_pcs_cmd, + .min = 512, + .max = 810, + }, + { + .name = NULL, + }, +}; + +/* phone node */ +static struct gui_select_line config_phone[] = { + { + .title = "Phone Config", + .parent = config_setup, + }, + { + .name = "IMEI", + .type = SELECT_NODE, + .node = config_imei, + }, + { + .name = "Codec", + .type = SELECT_NODE, + .node = config_codec, + }, +#if 0 + { + .name = "Support", + .type = SELECT_NODE, + .node = config_support, + }, +#endif + { + .name = "Emerg. IMSI", + .type = SELECT_NODE, + .node = config_emerg_imsi, + }, + { + .name = "TX Power", + .type = SELECT_NODE, + .node = config_tx_power, + }, + { + .name = "Sim. Delay", + .type = SELECT_NODE, + .node = config_sim_delay, + }, + { + .name = "Stick ARFCN", + .type = SELECT_NODE, + .node = config_stick, + }, + { + .name = NULL, + }, +}; + +/* network node */ +static struct gui_select_line config_network[] = { + { + .title = "Net Config", + .parent = config_setup, + }, + { + .name = "Network Sel", + .type = SELECT_CHOOSE, + .set = network_mode_set, + .query = config_network_query, + .cmd = config_network_cmd, + .restart = 0, + }, + { + .name = NULL, + }, +}; + +/* status node */ +static struct gui_select_line config_status[GUI_NUM_STATUS + 2]; + +void gui_init_status_config(void) +{ + int i; + + memset(config_status, 0, sizeof(config_status)); + + config_status[0].title = "Status Set"; + config_status[0].parent = config_setup; + + for (i = 0; i < GUI_NUM_STATUS; i++) { + config_status[i + 1].name = status_screen[i].feature; + config_status[i + 1].type = SELECT_CHOOSE; + config_status[i + 1].set = enable_disable_set; + config_status[i + 1].query = config_status_query; + config_status[i + 1].cmd = config_status_cmd; + config_status[i + 1].restart = 0; + } +}; + +static struct gui_select_line config_sim[]; + +/* test SIM node */ +static struct gui_select_line config_test_sim[] = { + { + .title = "Test SIM", + .parent = config_sim, + }, + { + .name = NULL, + }, +}; + +/* SIM node */ +static struct gui_select_line config_sim[] = { + { + .title = "SIM Setup", + .parent = config_setup, + }, + { + .name = "SIM Type", + .type = SELECT_CHOOSE, + .set = sim_type_set, + .query = config_sim_type_query, + .cmd = config_sim_type_cmd, + .restart = 1, + }, + { + .name = "Test SIM", + .type = SELECT_NODE, + .node = config_test_sim, + }, + { + .name = NULL, + }, +}; + +/* setup node */ +static struct gui_select_line config_setup[] = { + { + .title = "Setup", + .parent = NULL, + }, + { + .name = "Call", + .type = SELECT_NODE, + .node = config_call, + }, + { + .name = "SMS", + .type = SELECT_NODE, + .node = config_sms, + }, + { + .name = "Phone", + .type = SELECT_NODE, + .node = config_phone, + }, + { + .name = "Network", + .type = SELECT_NODE, + .node = config_network, + }, + { + .name = "Status", + .type = SELECT_NODE, + .node = config_status, + }, + { + .name = "SIM", + .type = SELECT_NODE, + .node = config_sim, + }, + { + .name = NULL, + }, +}; + +static struct gui_select_line menu_menu[]; + +/* SMS node */ +static struct gui_select_line menu_sms[] = { + { + .title = "SMS Menu", + .parent = menu_menu, + }, + { + .name = NULL, + }, +}; + +/* SIM node */ +static struct gui_select_line menu_sim[] = { + { + .title = "SIM Menu", + .parent = menu_menu, + }, + { + .name = NULL, + }, +}; + +/* network node */ +static struct gui_select_line menu_network[] = { + { + .title = "Net Menu", + .parent = menu_menu, + }, + { + .name = NULL, + }, +}; + +static struct gui_select_line menu_forwarding[]; + +/* activate node */ +static struct gui_select_line menu_fwd_activate[] = { + { + .title = "Fwd Activate", + .parent = menu_forwarding, + }, + { + .name = NULL, + }, +}; + +/* deactivate node */ +static struct gui_select_line menu_fwd_deactivate[] = { + { + .title = "Fwd Deactivate", + .parent = menu_forwarding, + }, + { + .name = NULL, + }, +}; + +/* query node */ +static struct gui_select_line menu_fwd_query[] = { + { + .title = "Fwd Query", + .parent = menu_forwarding, + }, + { + .name = NULL, + }, +}; + +/* forwarding node */ +static struct gui_select_line menu_forwarding[] = { + { + .title = "Fwd Menu", + .parent = menu_menu, + }, + { + .name = "Activate", + .type = SELECT_NODE, + .node = menu_fwd_activate, + }, + { + .name = "Deactivate", + .type = SELECT_NODE, + .node = menu_fwd_deactivate, + }, + { + .name = "Query", + .type = SELECT_NODE, + .node = menu_fwd_query, + }, + { + .name = NULL, + }, +}; + +/* menu node */ +static struct gui_select_line menu_menu[] = { + { + .title = "Menu", + .parent = NULL, + }, + { + .name = "SMS", + .type = SELECT_NODE, + .node = menu_sms, + }, + { + .name = "SIM", + .type = SELECT_NODE, + .node = menu_sim, + }, + { + .name = "Network", + .type = SELECT_NODE, + .node = menu_network, + }, + { + .name = "Forwarding", + .type = SELECT_NODE, + .node = menu_forwarding, + }, + { + .name = NULL, + }, +}; + +/* + * UI handling for mobile instance + */ + +static void update_status(void *arg); +static int gui_select(struct osmocom_ms *ms, struct gui_select_line *menu); +static int gui_choose(struct osmocom_ms *ms, struct gui_select_line *menu); +static int gui_number(struct osmocom_ms *ms, struct gui_select_line *menu); +static int gui_int(struct osmocom_ms *ms, struct gui_select_line *menu); +static int gui_fixint(struct osmocom_ms *ms, struct gui_select_line *menu, + struct gui_select_line *value); +static int gui_fixstring(struct osmocom_ms *ms, struct gui_select_line *menu, + struct gui_select_line *value); +static int gui_chosen(struct osmocom_ms *ms, const char *value); +static int gui_supserv(struct gsm_ui *gui, int clear); +static int gui_input(struct gsm_ui *gui, int menu); + +enum { + MENU_STATUS, + MENU_DIALING, + MENU_CALL, + MENU_SELECT, + MENU_SUPSERV, + MENU_SS_INPUT, +}; + +static int telnet_cb(struct ui_inst *ui) +{ + struct gsm_ui *gui = container_of(ui, struct gsm_ui, ui); + struct osmocom_ms *ms = container_of(gui, struct osmocom_ms, gui); + + if (!llist_empty(&gui->ui.active_connections)) { + /* start status screen again, if timer is not running */ + if (gui->menu == MENU_STATUS + && !osmo_timer_pending(&gui->timer)) + gui_start(ms); + else { + /* refresh, if someone connects */ + ui_inst_refresh(ui); + } + } else { + /* stop if no more connection */ + gui_stop(ms); + } + return 0; +} + +static int beep_cb(struct ui_inst *ui) +{ + ui_telnet_puts(ui, ""); + + return 0; +} + +static int key_dialing_cb(struct ui_inst *ui, enum ui_key kp); + +static int gui_dialing(struct gsm_ui *gui) +{ + struct ui_inst *ui = &gui->ui; + + /* go to dialing screen */ + gui->menu = MENU_DIALING; + ui_inst_init(ui, &ui_stringview, key_dialing_cb, beep_cb, + telnet_cb); + ui->title = "Number:"; + ui->ud.stringview.number = gui->dialing; + ui->ud.stringview.num_len = sizeof(gui->dialing); + ui->ud.stringview.pos = 1; + ui_inst_refresh(ui); + + return 0; +} + +static int key_dialing_cb(struct ui_inst *ui, enum ui_key kp) +{ + struct gsm_ui *gui = container_of(ui, struct gsm_ui, ui); + struct osmocom_ms *ms = container_of(gui, struct osmocom_ms, gui); + struct gsm_call *call; + int num_calls = 0; + + if (kp == UI_KEY_PICKUP) { + char *number = ui->ud.stringview.number; + int i; + + /* check for supplementary services first */ + if (number[0] == '*' || number[0] == '#') { + gui_supserv(gui, 1); + + return 1; /* handled */ + } + /* check if number contains not dialable digits */ + for (i = 0; i < strlen(number); i++) { + if ((i != 0 && number[i] == '+') + || !strchr("01234567890*#abc+", number[i])) { + /* point to error digit */ + ui->ud.stringview.pos = i; + ui_inst_refresh(ui); + return 1; /* handled */ + } + } + mncc_call(ms, ui->ud.stringview.number); + + /* go to call screen */ + gui->menu = MENU_STATUS; + gui_notify_call(ms); + + return 1; /* handled */ + } + if (kp == UI_KEY_HANGUP) { + llist_for_each_entry(call, &ms->mncc_entity.call_list, + entry) { + num_calls++; + } + if (!num_calls) { + /* go to status screen */ + gui_start(ms); + } else { + /* go to call screen */ + gui->menu = MENU_STATUS; + gui_notify_call(ms); + } + + return 1; /* handled */ + } + + return 0; +} + +static int key_status_cb(struct ui_inst *ui, enum ui_key kp) +{ + struct gsm_ui *gui = container_of(ui, struct gsm_ui, ui); + struct osmocom_ms *ms = container_of(gui, struct osmocom_ms, gui); + + if ((kp >= UI_KEY_0 && kp <= UI_KEY_9) || kp == UI_KEY_STAR + || kp == UI_KEY_HASH) { + gui->dialing[0] = kp; + gui->dialing[1] = '\0'; + /* go to dialing screen */ + gui_dialing(gui); + + return 1; /* handled */ + } + if (kp == UI_KEY_PICKUP) { + gui->dialing[0] = '\0'; + /* go to dialing screen */ + gui_dialing(gui); + } + if (kp == UI_KEY_F1) { + /* go to setup screen */ + gui_select(ms, menu_menu); + + return 1; /* handled */ + } + if (kp == UI_KEY_F2) { + /* go to setup screen */ + gui_select(ms, config_setup); + + return 1; /* handled */ + } + + return 0; +} + +/* generate status and display it */ +static void update_status(void *arg) +{ + struct osmocom_ms *ms = arg; + struct gsm_settings *set = &ms->settings; + struct gsm_ui *gui = &ms->gui; + int i, j = 0, k, n, lines = 0, has_network_name = 0, has_bottom_line = 0; + char *p = gui->status_text; + + /* no timer, if no telnet connection */ + if (!UI_TARGET && llist_empty(&gui->ui.active_connections)) + return; + + /* if timer fires */ + if (gui->menu != MENU_STATUS) + return; + + gui->ui.bottom_line = NULL; + + for (i = 0; i < GUI_NUM_STATUS; i++) { + if (i == 0) + has_network_name = 1; + lines += status_screen[i].lines; + if (!(set->status_enable & (1 << i))) + continue; + /* finish loop if number of lines exceed the definition */ + if (lines > GUI_NUM_STATUS_LINES) + continue; + /* special case where function keys' help is displayed */ + if (i == GUI_NUM_STATUS - 1) { + has_bottom_line = 1; + gui->ui.bottom_line = "menu setup"; + continue; + } + n = status_screen[i].display_func(ms, p); + while (n--) { + gui->status_lines[j] = p; + p += (UI_COLS + 1); + j++; + if (j == GUI_NUM_STATUS_LINES) + break; + } + } + + /* if network name is present */ + if (has_network_name) { + /* if not all lines are occupied */ + if (j + has_bottom_line < UI_ROWS && j > 1) { + /* insert space below network name */ + for (k = j; k > 1; k--) + gui->status_lines[k] = gui->status_lines[k - 1]; + gui->status_lines[1] = ""; + j++; + } + /* if not all lines are occupied */ + if (j + has_bottom_line < UI_ROWS && j > 1) { + /* insert space above network name */ + for (k = j; k > 0; k--) + gui->status_lines[k] = gui->status_lines[k - 1]; + gui->status_lines[0] = ""; + j++; + } + } + + gui->ui.ud.listview.lines = j; + gui->ui.ud.listview.text = gui->status_lines; + gui->ui.title = NULL; + ui_inst_refresh(&gui->ui); + + /* schedule next refresh */ + gui->timer.cb = update_status; + gui->timer.data = ms; + osmo_timer_schedule(&gui->timer, 1,0); +} + +int gui_start(struct osmocom_ms *ms) +{ + /* go to status screen */ + ms->gui.menu = MENU_STATUS; + ui_inst_init(&ms->gui.ui, &ui_listview, key_status_cb, beep_cb, + telnet_cb); + update_status(ms); + + return 0; +} + +int gui_stop(struct osmocom_ms *ms) +{ + struct gsm_ui *gui = &ms->gui; + + osmo_timer_del(&gui->timer); + + return 0; +} + +static int key_call_cb(struct ui_inst *ui, enum ui_key kp) +{ + struct gsm_ui *gui = container_of(ui, struct gsm_ui, ui); + struct osmocom_ms *ms = container_of(gui, struct osmocom_ms, gui); + struct gsm_call *call, *selected_call = NULL; + int num_calls = 0, num_hold = 0; + + if (kp == UI_KEY_UP) { + if (gui->selected_call > 0) { + gui->selected_call--; + gui_notify_call(ms); + } + + return 1; /* handled */ + } + if (kp == UI_KEY_DOWN) { + gui->selected_call++; + gui_notify_call(ms); + + return 1; /* handled */ + } + llist_for_each_entry(call, &ms->mncc_entity.call_list, entry) { + if (num_calls == gui->selected_call) + selected_call = call; + num_calls++; + if (call->call_state == CALL_ST_HOLD) + num_hold++; + } + if (!selected_call) + return 1; + if (kp == UI_KEY_HANGUP) { + mncc_hangup(ms, gui->selected_call + 1); + + return 1; /* handled */ + } + if (kp == UI_KEY_PICKUP) { + if (selected_call->call_state == CALL_ST_MT_RING + || selected_call->call_state == CALL_ST_MT_KNOCK) + mncc_answer(ms, gui->selected_call + 1); + else + if (selected_call->call_state == CALL_ST_HOLD) + mncc_retrieve(ms, gui->selected_call + 1); + + return 1; /* handled */ + } + if (kp == UI_KEY_F1) { + /* only if all calls on hold */ + if (num_calls == num_hold) { + gui->dialing[0] = '\0'; + /* go to dialing screen */ + gui_dialing(gui); + } + + return 1; /* handled */ + } + if (kp == UI_KEY_F2) { + if (selected_call->call_state == CALL_ST_ACTIVE) + mncc_hold(ms, gui->selected_call + 1); + else + if (selected_call->call_state == CALL_ST_HOLD) + mncc_retrieve(ms, gui->selected_call + 1); + + return 1; /* handled */ + } + if ((kp >= UI_KEY_0 && kp <= UI_KEY_9) || kp == UI_KEY_STAR + || kp == UI_KEY_HASH) { + if (selected_call->call_state == CALL_ST_ACTIVE) { + /* if dtmf is not supported */ + if (!ms->settings.cc_dtmf) + return 1; /* handled */ + char dtmf[2]; + + dtmf[0] = kp; + dtmf[1] = '\0'; + mncc_dtmf(ms, gui->selected_call + 1, dtmf); + return 1; /* handled */ + } + /* only if all calls on hold */ + if (num_calls == num_hold) { + gui->dialing[0] = kp; + gui->dialing[1] = '\0'; + /* go to dialing screen */ + gui_dialing(gui); + } + + return 1; /* handled */ + } + + return 0; +} + +/* call instances have changed */ +int gui_notify_call(struct osmocom_ms *ms) +{ + struct gsm_ui *gui = &ms->gui; + struct gsm_call *call; + const char *state; + char *p = gui->status_text, *n; + int len, shift; + int j = 0, calls = 0, calls_on_hold = 0; + int last_call_j_first = 0, selected_call_j_first = 0; + int last_call_j_last = 0, selected_call_j_last = 0; + struct gsm_call *last_call = NULL, *selected_call = NULL; + + if (gui->menu != MENU_STATUS + && gui->menu != MENU_CALL) + return 0; + + if (gui->menu == MENU_STATUS) { + ms->gui.menu = MENU_CALL; + ui_inst_init(&ms->gui.ui, &ui_listview, key_call_cb, beep_cb, + telnet_cb); + gui->selected_call = 999; + /* continue here */ + } + + llist_for_each_entry(call, &ms->mncc_entity.call_list, entry) { + switch (call->call_state) { + case CALL_ST_MO_INIT: + state = "Dialing..."; + break; + case CALL_ST_MO_PROC: + state = "Proceeding"; + break; + case CALL_ST_MO_ALERT: + state = "Ringing"; + break; + case CALL_ST_MT_RING: + state = "Incomming"; + break; + case CALL_ST_MT_KNOCK: + state = "Knocking"; + break; + case CALL_ST_ACTIVE: + state = "Connected"; + break; + case CALL_ST_HOLD: + state = "On Hold"; + calls_on_hold++; + break; + case CALL_ST_DISC_TX: + state = "Releasing"; + break; + case CALL_ST_DISC_RX: + state = "Hung Up"; + break; + default: + continue; + } + + /* store first line of selected call */ + if (calls == gui->selected_call) { + selected_call_j_first = j; + selected_call = call; + } + /* set first line of last call */ + last_call_j_first = j; + last_call = call; + + /* state */ + strncpy(p, state, UI_COLS); + p[UI_COLS] = '\0'; + /* center */ + len = strlen(p); + if (len + 1 < UI_COLS) { + shift = (UI_COLS - len) / 2; + memcpy(p + shift, p, len + 1); + memset(p, ' ', shift); + } + gui->status_lines[j] = p; + p += (UI_COLS + 1); + j++; + if (j == GUI_NUM_STATUS_LINES) + break; + + /* number */ + n = call->number; + while (1) { + strncpy(p, n, UI_COLS); + p[UI_COLS] = '\0'; + gui->status_lines[j] = p; + p += (UI_COLS + 1); + j++; + if (j == GUI_NUM_STATUS_LINES) + break; + if (strlen(n) <= UI_COLS) + break; + n += UI_COLS; + } + if (j == GUI_NUM_STATUS_LINES) + break; + + /* store last line of selected call */ + if (calls == gui->selected_call) + selected_call_j_last = j; + /* set last line of last call */ + last_call_j_last = j; + + /* empty line */ + p[0] = '\0'; + gui->status_lines[j] = p; + p += (UI_COLS + 1); + j++; + if (j == GUI_NUM_STATUS_LINES) + break; + + /* count calls */ + calls++; + } + + /* return to status menu */ + if (!calls) + return gui_start(ms); + + /* remove last empty line */ + if (j) + j--; + + /* in case there are less calls than the selected one */ + if (!calls) { + gui->selected_call = 0; + } else if (gui->selected_call >= calls) { + gui->selected_call = calls - 1; + selected_call_j_first = last_call_j_first; + selected_call_j_last = last_call_j_last; + selected_call = last_call; + } + + /* adjust vpos, so the selected call fits on display */ + if (selected_call_j_last - gui->ui.ud.listview.vpos > UI_ROWS - 2) + gui->ui.ud.listview.vpos = selected_call_j_last - (UI_ROWS - 1); + if (gui->ui.ud.listview.vpos > selected_call_j_first) + gui->ui.ud.listview.vpos = selected_call_j_first; + + + /* mark selected call */ + if (calls > 1) + gui->status_text[selected_call_j_first * (UI_COLS + 1)] = '*'; + + /* if only one call */ + if (calls == 1) { + /* insert space above call state */ + memcpy(gui->status_lines + 1, gui->status_lines, + j * sizeof(char *)); + gui->status_lines[0] = ""; + j++; + if (j > 2) { + /* insert space below call state */ + memcpy(gui->status_lines + 3, gui->status_lines + 2, + (j - 2) * sizeof(char *)); + gui->status_lines[2] = ""; + j++; + } + } + /* set bottom line */ + gui->ui.bottom_line = " "; + if (calls && selected_call) { + switch (selected_call->call_state) { + case CALL_ST_ACTIVE: + gui->ui.bottom_line = " hold"; + break; + case CALL_ST_HOLD: + /* offer new call, if all calls are on hold */ + if (calls_on_hold == calls) + gui->ui.bottom_line = "new resume"; + else + gui->ui.bottom_line = " resume"; + break; + } + } + + gui->ui.ud.listview.lines = j; + gui->ui.ud.listview.text = gui->status_lines; + ui_inst_refresh(&gui->ui); + + return 0; +} + +static int key_select_cb(struct ui_inst *ui, enum ui_key kp) +{ + struct gsm_ui *gui = container_of(ui, struct gsm_ui, ui); + struct osmocom_ms *ms = container_of(gui, struct osmocom_ms, gui); + struct gui_select_line *menu = gui->select_menu; + + if (kp == UI_KEY_F1) { + /* go paretn menu, or status screen, if none */ + if (menu[0].parent) + gui_select(ms, menu[0].parent); + else + gui_start(ms); + + return 1; /* hanlded */ + } + if (kp == UI_KEY_F2) { + /* if menu is empty */ + if (!menu[ui->ud.selectview.cursor + 1].name) + return 1; /* handled */ + /* go sub node */ + switch (menu[ui->ud.selectview.cursor + 1].type) { + case SELECT_NODE: + gui_select(ms, menu[ui->ud.selectview.cursor + 1].node); + break; + case SELECT_CHOOSE: + gui_choose(ms, &menu[ui->ud.selectview.cursor + 1]); + break; + case SELECT_NUMBER: + gui_number(ms, &menu[ui->ud.selectview.cursor + 1]); + break; + case SELECT_INT: + gui_int(ms, &menu[ui->ud.selectview.cursor + 1]); + break; + case SELECT_FIXINT: + gui_fixint(ms, menu, + &menu[ui->ud.selectview.cursor + 1]); + break; + case SELECT_FIXSTRING: + gui_fixstring(ms, menu, + &menu[ui->ud.selectview.cursor + 1]); + break; + } + + return 1; /* hanlded */ + } + + return 0; +} + +/* initialize a select menu */ +static int gui_select(struct osmocom_ms *ms, struct gui_select_line *menu) +{ + struct gsm_ui *gui = &ms->gui; + char *p = gui->status_text; + int i; + + gui->menu = MENU_SELECT; + ui_inst_init(&gui->ui, &ui_selectview, key_select_cb, beep_cb, + telnet_cb); + + /* set menu */ + gui->select_menu = menu; + gui->ui.title = menu->title; + menu++; + + /* create list */ + for (i = 0; menu[i].name; i++) { + if (i == GUI_NUM_STATUS_LINES) + break; + strncpy(p, menu[i].name, UI_COLS); + p[UI_COLS] = '\0'; + gui->status_lines[i] = p; + p += UI_COLS + 1; + } + + gui->ui.bottom_line = "back enter"; + + gui->ui.ud.selectview.lines = i; + gui->ui.ud.selectview.text = gui->status_lines; + ui_inst_refresh(&gui->ui); + + return 0; +} + +static int key_choose_cb(struct ui_inst *ui, enum ui_key kp) +{ + struct gsm_ui *gui = container_of(ui, struct gsm_ui, ui); + struct osmocom_ms *ms = container_of(gui, struct osmocom_ms, gui); + struct gui_select_line *menu = gui->choose_menu; + struct gui_choose_set *set = menu->set; + int rc; + + if (kp == UI_KEY_F1) { + /* go back to selection */ + gui_select(ms, gui->select_menu); + + return 1; /* hanlded */ + } + if (kp == UI_KEY_F2) { + /* set selection */ + set += ui->ud.selectview.cursor; + rc = menu->cmd(ms, &set->value); + if (rc) + gui_chosen(ms, NULL); + else + gui_chosen(ms, set->name); + + return 1; /* hanlded */ + } + + return 0; +} + +static int gui_choose(struct osmocom_ms *ms, struct gui_select_line *menu) +{ + struct gsm_ui *gui = &ms->gui; + char *p = gui->status_text; + int i; + struct gui_choose_set *set = menu->set; + int cursor = 0; + int value; + + ui_inst_init(&gui->ui, &ui_selectview, key_choose_cb, beep_cb, + telnet_cb); + + /* set menu */ + gui->choose_menu = menu; + gui->ui.title = menu->name; + + value = *(int *)menu->query(ms); + + /* create list of items to choose */ + for (i = 0; set[i].name; i++) { + if (i == GUI_NUM_STATUS_LINES) + break; + if (value == set[i].value) + cursor = i; + strncpy(p, set[i].name, UI_COLS); + p[UI_COLS] = '\0'; + gui->status_lines[i] = p; + p += UI_COLS + 1; + } + + gui->ui.bottom_line = "back set"; + + gui->ui.ud.selectview.lines = i; + gui->ui.ud.selectview.text = gui->status_lines; + gui->ui.ud.selectview.cursor = cursor; + ui_inst_refresh(&gui->ui); + + return 0; +} + +static int key_number_cb(struct ui_inst *ui, enum ui_key kp) +{ + struct gsm_ui *gui = container_of(ui, struct gsm_ui, ui); + struct osmocom_ms *ms = container_of(gui, struct osmocom_ms, gui); + struct gui_select_line *menu = gui->choose_menu; + int rc; + + if (kp == UI_KEY_HANGUP) { + /* go back to selection */ + gui_select(ms, gui->select_menu); + + return 1; /* hanlded */ + } + if (kp == UI_KEY_PICKUP) { + char *number = ui->ud.stringview.number; + int i; + + /* check if number is valid */ + for (i = 0; i < strlen(number); i++) { + if ((i != 0 && number[i] == '+') + || !strchr("01234567890*#abc+", number[i])) { + /* point to error digit */ + ui->ud.stringview.pos = i; + ui_inst_refresh(ui); + return 1; /* handled */ + } + } + /* set selection */ + rc = menu->cmd(ms, gui->dialing); + if (rc) + gui_chosen(ms, NULL); + else + gui_chosen(ms, gui->dialing); + + return 1; /* hanlded */ + } + + return 0; +} + +static int gui_number(struct osmocom_ms *ms, struct gui_select_line *menu) +{ + struct gsm_ui *gui = &ms->gui; + + ui_inst_init(&gui->ui, &ui_stringview, key_number_cb, beep_cb, + telnet_cb); + + /* set menu */ + gui->choose_menu = menu; + gui->ui.title = menu->name; + strncpy(gui->dialing, menu->query(ms), sizeof(gui->dialing) - 1); + gui->ui.ud.stringview.number = gui->dialing; + gui->ui.ud.stringview.num_len = menu->digits + 1; + gui->ui.ud.stringview.pos = strlen(gui->dialing); + ui_inst_refresh(&gui->ui); + + return 0; +} + +static int key_int_cb(struct ui_inst *ui, enum ui_key kp) +{ + struct gsm_ui *gui = container_of(ui, struct gsm_ui, ui); + struct osmocom_ms *ms = container_of(gui, struct osmocom_ms, gui); + struct gui_select_line *menu = gui->choose_menu; + int rc; + + if (kp == UI_KEY_F1) { + /* go back to selection */ + gui_select(ms, gui->select_menu); + + return 1; /* hanlded */ + } + if (kp == UI_KEY_F2) { + int value; + + if (ui->ud.intview.sign) + value = 0 - ui->ud.intview.value; + else + value = ui->ud.intview.value; + /* if entered value exceeds range, let ui fix that */ + if (value < menu->min || value > menu->max) + return 0; /* unhandled */ + /* set selection */ + rc = menu->cmd(ms, &value); + if (rc) + gui_chosen(ms, NULL); + else { + char val_str[16]; + + sprintf(val_str, "%d", value); + gui_chosen(ms, val_str); + } + + return 1; /* hanlded */ + } + + return 0; +} + +static int gui_int(struct osmocom_ms *ms, struct gui_select_line *menu) +{ + struct gsm_ui *gui = &ms->gui; + int value; + + ui_inst_init(&gui->ui, &ui_intview, key_int_cb, beep_cb, + telnet_cb); + + /* set menu */ + gui->choose_menu = menu; + gui->ui.title = menu->name; + value = *(int *)menu->query(ms); + if (value < 0) { + gui->ui.ud.intview.value = 0 - value; + gui->ui.ud.intview.sign = 1; + } else { + gui->ui.ud.intview.value = value; + gui->ui.ud.intview.sign = 0; + } + gui->ui.ud.intview.min = menu->min; + gui->ui.ud.intview.max = menu->max; + gui->ui.bottom_line = "back set"; + ui_inst_refresh(&gui->ui); + + return 0; +} + +static int gui_fixint(struct osmocom_ms *ms, struct gui_select_line *menu, + struct gui_select_line *value) +{ + struct gsm_ui *gui = &ms->gui; + int rc; + + /* set fixed value */ + gui->choose_menu = menu; + rc = value->cmd(ms, &value->fix); + if (rc) + gui_chosen(ms, NULL); + else { + gui_chosen(ms, value->name); + } + + return 0; +} + +static int gui_fixstring(struct osmocom_ms *ms, struct gui_select_line *menu, + struct gui_select_line *value) +{ + struct gsm_ui *gui = &ms->gui; + int rc; + + /* set fixed value */ + gui->choose_menu = menu; + rc = value->cmd(ms, value->fixstring); + if (rc) + gui_chosen(ms, NULL); + else { + gui_chosen(ms, value->name); + } + + return 0; +} + +static int key_chosen_cb(struct ui_inst *ui, enum ui_key kp) +{ + struct gsm_ui *gui = container_of(ui, struct gsm_ui, ui); + struct osmocom_ms *ms = container_of(gui, struct osmocom_ms, gui); + + /* go back to selection */ + gui_select(ms, gui->select_menu); + + return 1; /* handled */ +} + +static int gui_chosen(struct osmocom_ms *ms, const char *value) +{ + struct gsm_ui *gui = &ms->gui; + char *p = gui->status_text; + struct gui_select_line *menu = gui->choose_menu; + int j = 0; + + ui_inst_init(&gui->ui, &ui_listview, key_chosen_cb, beep_cb, + telnet_cb); + + /* set menu */ + gui->ui.title = menu->name; + + p[0] = '\0'; + gui->status_lines[j++] = p; + p += UI_COLS + 1; + if (value) { + if (menu->restart) + strcpy(p, "after reset:"); + else + strcpy(p, "is now:"); + p[UI_COLS] = '\0'; + gui->status_lines[j++] = p; + p += UI_COLS + 1; + p[0] = '\0'; + gui->status_lines[j++] = p; + p += UI_COLS + 1; + while (*value) { + if (j == GUI_NUM_STATUS_LINES) + break; + strncpy(p, value, UI_COLS); + p[UI_COLS] = '\0'; + value += strlen(p); + gui->status_lines[j++] = p; + p += UI_COLS + 1; + } + } else { + strcpy(p, "failed!"); + p[UI_COLS] = '\0'; + gui->status_lines[j++] = p; + p += UI_COLS + 1; + } + + gui->ui.bottom_line = "back "; + + gui->ui.ud.selectview.lines = j; + gui->ui.ud.selectview.text = gui->status_lines; + ui_inst_refresh(&gui->ui); + + return 0; +} + +static int key_supserv_cb(struct ui_inst *ui, enum ui_key kp) +{ + struct gsm_ui *gui = container_of(ui, struct gsm_ui, ui); + struct osmocom_ms *ms = container_of(gui, struct osmocom_ms, gui); + + if ((kp >= UI_KEY_0 && kp <= UI_KEY_9) || kp == UI_KEY_STAR + || kp == UI_KEY_HASH) { + /* no key if pending */ + if (!gui->ss_active) + return 0; + gui->dialing[0] = kp; + gui->dialing[1] = '\0'; + /* go to input screen */ + gui_input(gui, MENU_SS_INPUT); + + return 1; /* handled */ + } + if (kp == UI_KEY_PICKUP) { + if (!gui->ss_active) + return 0; + gui->dialing[0] = '\0'; + /* go to input screen */ + gui_input(gui, MENU_SS_INPUT); + + return 1; /* handled */ + } + if (kp == UI_KEY_F2) { + /* no key if pending */ + if (gui->ss_pending) + return 0; + /* go back to start */ + gui_start(ms); + /* terminate SS connection */ + if (gui->ss_active) + ss_send(ms, "hangup", 0); + + return 1; /* hanlded */ + } + + return 0; +} + +static int gui_supserv(struct gsm_ui *gui, int clear) { + struct osmocom_ms *ms = container_of(gui, struct osmocom_ms, gui); + struct ui_inst *ui = &gui->ui; + + /* if we don't want to keep our list buffer */ + if (clear) + gui->ss_lines = 0; + + /* go to supplementary services screen */ + gui->menu = MENU_SUPSERV; + ui_inst_init(ui, &ui_listview, key_supserv_cb, beep_cb, + telnet_cb); + ui->title = "SS Request"; + ui->bottom_line = " "; + gui->ui.ud.listview.lines = gui->ss_lines; + gui->ui.ud.listview.text = gui->status_lines; + ui_inst_refresh(ui); + + /* send request to supserv process */ + gui->ss_active = 0; + gui->ss_pending = 1; /* must be set prior call, gui_notify_ss might be + called there + */ + if (gui->dialing[0]) + ss_send(ms, gui->dialing, 0); + + return 0; +} + +int gui_notify_ss(struct osmocom_ms *ms, const char *fmt, ...) +{ + struct gsm_ui *gui = &ms->gui; + struct ui_inst *ui = &gui->ui; + char buffer[1000], *b = buffer, *start, *end, *p; + int j = gui->ss_lines; + va_list args; + + /* if connection is pending, clear listview buffer, otherwise append */ + if (gui->ss_pending) { + j = 0; + gui->ss_pending = 0; + } + p = gui->status_text + j * (UI_COLS + 1); + + /* not our process */ + if (gui->menu != MENU_SS_INPUT + && gui->menu != MENU_SUPSERV) + return 0; + + /* change back to supserv display */ + if (gui->menu != MENU_SUPSERV) { + gui->menu = MENU_SUPSERV; + ui_inst_init(ui, &ui_listview, key_supserv_cb, beep_cb, + telnet_cb); + ui->title = "SS Request"; + gui->ui.ud.listview.lines = gui->ss_lines; + gui->ui.ud.listview.text = gui->status_lines; + } + + /* end of process indication */ + if (!fmt) { + gui->ss_active = 0; + ui->bottom_line = " back"; + ui_inst_refresh(&gui->ui); + + return 0; + } else + gui->ss_active = 1; + + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer) - 1, fmt, args); + buffer[sizeof(buffer) - 1] = '\0'; + va_end(args); + + ui->title = NULL; + /* print buffer to listview */ + while (*b) { + if (j == GUI_NUM_STATUS_LINES) + break; + /* find start and end of a line. the line ends with \n or \0 */ + start = b; + end = strchr(b, '\n'); + if (end) + b = end + 1; + else { + end = b + strlen(b); + b = end; + } + /* if line is longer than display width */ + if (end - start > UI_COLS) { + /* loop until next word exceeds line */ + end = start; + while ((b = strchr(end, ' '))) { + if (!b) + break; + if (b - start > UI_COLS) + break; + end = b + 1; + } + /* if word is longer than line, we must break inside */ + if (start == end) { + end = start + UI_COLS; + b = end; + } else { + b = end; + end--; /* remove last space */ + } + } + /* copy line into buffer */ + if (end - start) + memcpy(p, start, end - start); + p[end - start] = '\0'; + gui->status_lines[j++] = p; + p += UI_COLS + 1; + } + gui->ss_lines = gui->ui.ud.listview.lines = j; + ui->bottom_line = " end"; + ui_inst_refresh(&gui->ui); + + return 0; +} + +static int key_input_cb(struct ui_inst *ui, enum ui_key kp); + +static int gui_input(struct gsm_ui *gui, int menu) +{ + struct ui_inst *ui = &gui->ui; + + /* go to input screen */ + gui->menu = menu; + ui_inst_init(ui, &ui_stringview, key_input_cb, beep_cb, + telnet_cb); + ui->title = "Select:"; + ui->ud.stringview.number = gui->dialing; + ui->ud.stringview.num_len = sizeof(gui->dialing); + ui->ud.stringview.pos = 1; + ui_inst_refresh(ui); + + return 0; +} + +static int key_input_cb(struct ui_inst *ui, enum ui_key kp) +{ + struct gsm_ui *gui = container_of(ui, struct gsm_ui, ui); + + if (kp == UI_KEY_PICKUP) { + /* go to previous screen and use input */ + gui_supserv(gui, 1); + + return 1; /* handled */ + } + if (kp == UI_KEY_HANGUP) { + /* go to previous screen */ + gui->dialing[0] = '\0'; + gui_supserv(gui, 0); + + return 1; /* handled */ + } + + return 0; +} + diff --git a/src/host/layer23/src/mobile/main.c b/src/host/layer23/src/mobile/main.c index 0eadda27..8bb5ec57 100644 --- a/src/host/layer23/src/mobile/main.c +++ b/src/host/layer23/src/mobile/main.c @@ -209,6 +209,8 @@ int main(int argc, char **argv) srand(time(NULL)); + gui_init_status_config(); + INIT_LLIST_HEAD(&ms_list); log_init(&log_info, NULL); stderr_target = log_target_create_stderr(); diff --git a/src/host/layer23/src/mobile/mnccms.c b/src/host/layer23/src/mobile/mnccms.c index c61ddc03..c4fee26e 100644 --- a/src/host/layer23/src/mobile/mnccms.c +++ b/src/host/layer23/src/mobile/mnccms.c @@ -408,9 +408,11 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg) && data->progress.descr == 8) { vty_notify(ms, "Please hang up!\n"); call->call_state = CALL_ST_DISC_RX; + gui_notify_call(ms); break; } free_call(call); + gui_notify_call(ms); cause = GSM48_CC_CAUSE_NORM_CALL_CLEAR; goto release; case MNCC_REL_IND: @@ -423,11 +425,13 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg) LOGP(DMNCC, LOGL_INFO, "Call has been released (cause %d)\n", data->cause.value); free_call(call); + gui_notify_call(ms); break; case MNCC_CALL_PROC_IND: vty_notify(ms, NULL); vty_notify(ms, "Call is proceeding\n"); call->call_state = CALL_ST_MO_PROC; + gui_notify_call(ms); LOGP(DMNCC, LOGL_INFO, "Call is proceeding\n"); if ((data->fields & MNCC_F_BEARER_CAP) && data->bearer_cap.speech_ver[0] >= 0) { @@ -438,12 +442,14 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg) vty_notify(ms, NULL); vty_notify(ms, "Call is alerting\n"); call->call_state = CALL_ST_MO_ALERT; + gui_notify_call(ms); LOGP(DMNCC, LOGL_INFO, "Call is alerting\n"); break; case MNCC_SETUP_CNF: vty_notify(ms, NULL); vty_notify(ms, "Call is answered\n"); call->call_state = CALL_ST_ACTIVE; + gui_notify_call(ms); LOGP(DMNCC, LOGL_INFO, "Call is answered\n"); break; case MNCC_SETUP_IND: @@ -550,6 +556,7 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg) call->call_state = CALL_ST_MT_KNOCK; } update_ringer(call); + gui_notify_call(ms); memset(&mncc, 0, sizeof(struct gsm_mncc)); mncc.callref = call->callref; mncc_tx_to_cc(ms, MNCC_ALERT_REQ, &mncc); @@ -568,24 +575,28 @@ int mncc_recv_mobile(struct osmocom_ms *ms, int msg_type, void *arg) vty_notify(ms, "Call is on hold\n"); LOGP(DMNCC, LOGL_INFO, "Call is on hold\n"); call->call_state = CALL_ST_HOLD; + gui_notify_call(ms); break; case MNCC_HOLD_REJ: vty_notify(ms, NULL); vty_notify(ms, "Call hold was rejected\n"); LOGP(DMNCC, LOGL_INFO, "Call hold was rejected\n"); call->call_state = CALL_ST_ACTIVE; + gui_notify_call(ms); break; case MNCC_RETRIEVE_CNF: vty_notify(ms, NULL); vty_notify(ms, "Call is retrieved\n"); LOGP(DMNCC, LOGL_INFO, "Call is retrieved\n"); call->call_state = CALL_ST_ACTIVE; + gui_notify_call(ms); break; case MNCC_RETRIEVE_REJ: vty_notify(ms, NULL); vty_notify(ms, "Call retrieve was rejected\n"); LOGP(DMNCC, LOGL_INFO, "Call retrieve was rejected\n"); call->call_state = CALL_ST_HOLD; + gui_notify_call(ms); break; case MNCC_FACILITY_IND: LOGP(DMNCC, LOGL_INFO, "Facility info not displayed, " @@ -671,6 +682,7 @@ int mncc_call(struct osmocom_ms *ms, char *number) } } call->call_state = CALL_ST_MO_INIT; + gui_notify_call(ms); return mncc_tx_to_cc(ms, MNCC_SETUP_REQ, &setup); } @@ -709,6 +721,7 @@ int mncc_hangup(struct osmocom_ms *ms, int index) } call->call_state = CALL_ST_DISC_TX; + gui_notify_call(ms); memset(&disc, 0, sizeof(struct gsm_mncc)); disc.callref = call->callref; @@ -765,6 +778,7 @@ int mncc_answer(struct osmocom_ms *ms, int index) } call->call_state = CALL_ST_ACTIVE; update_ringer(call); + gui_notify_call(ms); memset(&rsp, 0, sizeof(struct gsm_mncc)); rsp.callref = call->callref; diff --git a/src/host/layer23/src/mobile/settings.c b/src/host/layer23/src/mobile/settings.c index 2b518379..3b4c2921 100644 --- a/src/host/layer23/src/mobile/settings.c +++ b/src/host/layer23/src/mobile/settings.c @@ -35,6 +35,7 @@ int gsm_settings_init(struct osmocom_ms *ms) { struct gsm_settings *set = &ms->settings; struct gsm_support *sup = &ms->support; + int i; strcpy(set->layer2_socket_path, layer2_socket_path); strcpy(set->sap_socket_path, sap_socket_path); @@ -85,10 +86,14 @@ int gsm_settings_init(struct osmocom_ms *ms) if (sup->half_v1 || sup->half_v3) set->half = 1; - /* software features */ set->cc_dtmf = 1; + /* display status feature */ + set->status_enable = 0; + for (i = 0; i < GUI_NUM_STATUS; i++) + set->status_enable |= (status_screen[i].default_en << i); + INIT_LLIST_HEAD(&set->abbrev); return 0; diff --git a/src/host/layer23/src/mobile/vty_interface.c b/src/host/layer23/src/mobile/vty_interface.c index c95ed85d..c35ea222 100644 --- a/src/host/layer23/src/mobile/vty_interface.c +++ b/src/host/layer23/src/mobile/vty_interface.c @@ -66,6 +66,12 @@ struct cmd_node support_node = { 1 }; +struct cmd_node ui_node = { + UI_NODE, + "%s(user-interface)#", + 1 +}; + static void print_vty(void *priv, const char *fmt, ...) { char buffer[1000]; @@ -150,6 +156,10 @@ static struct osmocom_ms *get_ms(const char *name, struct vty *vty) return NULL; } +/* + * show things + */ + static void gsm_ms_dump(struct osmocom_ms *ms, struct vty *vty) { struct gsm_settings *set = &ms->settings; @@ -467,6 +477,10 @@ DEFUN(no_monitor_network, no_monitor_network_cmd, "no monitor network MS_NAME", return CMD_SUCCESS; } +/* + * SIM functions + */ + static int _sim_test_cmd(struct vty *vty, int argc, const char *argv[], int attached) { @@ -744,6 +758,10 @@ DEFUN(sim_lai, sim_lai_cmd, "sim lai MS_NAME MCC MNC LAC", return CMD_SUCCESS; } +/* + * network functions + */ + DEFUN(network_select, network_select_cmd, "network select MS_NAME MCC MNC [force]", "Select ...\nSelect Network\nName of MS (see \"show ms\")\n" @@ -932,6 +950,10 @@ DEFUN(call_dtmf, call_dtmf_cmd, "call MS_NAME dtmf DIGITS [NUMBER]", return CMD_SUCCESS; } +/* + * SMS functions + */ + DEFUN(sms, sms_cmd, "sms MS_NAME NUMBER .LINE", "Send an SMS\nName of MS (see \"show ms\")\nPhone number to send SMS " "(Use digits '0123456789*#abc', and '+' to dial international)\n" @@ -981,6 +1003,10 @@ DEFUN(sms, sms_cmd, "sms MS_NAME NUMBER .LINE", return CMD_SUCCESS; } +/* + * SS functions + */ + DEFUN(service, service_cmd, "service MS_NAME (*#06#|*#21#|*#67#|*#61#|*#62#" "|*#002#|*#004#|*xx*number#|*xx#|#xx#|##xx#|STRING|hangup)", "Send a Supplementary Service request\nName of MS (see \"show ms\")\n" @@ -1012,6 +1038,10 @@ DEFUN(service, service_cmd, "service MS_NAME (*#06#|*#21#|*#67#|*#61#|*#62#" #define TEST_STR "Test functions\n" +/* + * other functions + */ + DEFUN(test_reselection, test_reselection_cmd, "test re-selection NAME", TEST_STR "Manually trigger cell re-selection\n" "Name of MS (see \"show ms\")") @@ -1147,6 +1177,10 @@ DEFUN(network_search, network_search_cmd, "network search MS_NAME", return CMD_SUCCESS; } +/* + * global configuration + */ + DEFUN(cfg_gps_enable, cfg_gps_enable_cmd, "gps enable", "GPS receiver") { @@ -1257,7 +1291,10 @@ DEFUN(cfg_no_hide_default, cfg_no_hide_default_cmd, "no hide-default", return CMD_SUCCESS; } -/* per MS config */ +/* + * per MS config + */ + DEFUN(cfg_ms, cfg_ms_cmd, "ms MS_NAME", "Select a mobile station to configure\nName of MS (see \"show ms\")") { @@ -1384,6 +1421,7 @@ static void config_write_ms(struct vty *vty, struct osmocom_ms *ms) struct gsm_settings *set = &ms->settings; struct gsm_support *sup = &ms->support; struct gsm_settings_abbrev *abbrev; + int i; vty_out(vty, "ms %s%s", ms->name, VTY_NEWLINE); if (set->ringtone) @@ -1499,6 +1537,24 @@ static void config_write_ms(struct vty *vty, struct osmocom_ms *ms) abbrev->number, (abbrev->name[0]) ? " " : "", abbrev->name, VTY_NEWLINE); } + vty_out(vty, " user-interface%s", VTY_NEWLINE); + if (set->ui_port) + vty_out(vty, " telnet-port %d%s", set->ui_port, VTY_NEWLINE); + else + if (!hide_default) + vty_out(vty, " no telnet-port%s", VTY_NEWLINE); + for (i = 0; i < GUI_NUM_STATUS; i++) { + if (hide_default && ((set->status_enable >> i) & 1) + == status_screen[i].default_en) + continue; + if (((set->status_enable >> i) & 1)) + vty_out(vty, " %s show%s", + status_screen[i].feature, VTY_NEWLINE); + else + vty_out(vty, " %s hide%s", + status_screen[i].feature, VTY_NEWLINE); + } + vty_out(vty, " exit%s", VTY_NEWLINE); vty_out(vty, " support%s", VTY_NEWLINE); SUP_WRITE(sms_ptp, "sms"); SUP_WRITE(a5_1, "a5/1"); @@ -2303,7 +2359,86 @@ static int config_write_dummy(struct vty *vty) return CMD_SUCCESS; } -/* per support config */ +/* + * per user interface config + */ + +DEFUN(cfg_ms_ui, cfg_ms_ui_cmd, "user-interface", + "Configure user interface") +{ + vty->node = UI_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_ui_telnet_port, cfg_ms_ui_telnet_port_cmd, "telnet-port <1-65534>", + "Enable telnet interface of UI\nTCP Port number") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->ui_port = atoi(argv[0]); + + vty_restart_if_started(vty, ms); + + return CMD_SUCCESS; +} + +DEFUN(cfg_ms_ui_no_telnet_port, cfg_ms_ui_no_telnet_port_cmd, "no telnet-port", + NO_STR "Disable telnet interface of UI") +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + + set->ui_port = 0; + + vty_restart_if_started(vty, ms); + + return CMD_SUCCESS; +} + +static struct cmd_element *cfg_ms_ui_status_cmd = NULL; + +DEFUN_CMD_FUNC_TEXT(cfg_ms_ui_status) +{ + struct osmocom_ms *ms = vty->index; + struct gsm_settings *set = &ms->settings; + int i; + + for (i = 0; i < GUI_NUM_STATUS; i++) { + if (!strcmp(self->string, status_screen[i].feature_vty)) + break; + } + if (i == GUI_NUM_STATUS) { + vty_out(vty, "cmdstr %s does not exist!%s", self->string, + VTY_NEWLINE); + return CMD_WARNING; + } + + set->status_enable &= ~(1 << i); + + if (argv[0][0] == 's') { + int lines = 0, j; + + for (j = 0; j < GUI_NUM_STATUS; j++) { + if ((set->status_enable & (1 << i)) || i == j) + lines += status_screen[j].lines; + } + if (lines > UI_ROWS) + vty_out(vty, "There are not enough lines left to " + "display this status info. Please disable " + "other info(s).%s", VTY_NEWLINE); + else + set->status_enable |= (1 << i); + } + + return CMD_SUCCESS; +} + +/* + * per support config + */ + DEFUN(cfg_ms_support, cfg_ms_support_cmd, "support", "Define supported features") { @@ -2622,7 +2757,10 @@ DEFUN(cfg_ms_sup_no_skip_max_per_band, cfg_ms_sup_no_skip_max_per_band_cmd, return CMD_SUCCESS; } -/* per testsim config */ +/* + * per testsim config + */ + DEFUN(cfg_ms_testsim, cfg_ms_testsim_cmd, "test-sim", "Configure test SIM emulation") { @@ -2914,6 +3052,7 @@ gDEFUN(ournode_exit, break; case TESTSIM_NODE: case SUPPORT_NODE: + case UI_NODE: vty->node = MS_NODE; break; default: @@ -2936,6 +3075,7 @@ gDEFUN(ournode_end, case MS_NODE: case TESTSIM_NODE: case SUPPORT_NODE: + case UI_NODE: vty_config_unlock(vty); vty->node = ENABLE_NODE; vty->index = NULL; @@ -2958,8 +3098,10 @@ DEFUN(off, off_cmd, "off", #define SUP_NODE(item) \ install_element(SUPPORT_NODE, &cfg_ms_sup_item_cmd); -int ms_vty_init(void) +int ms_vty_init(void *tall_ctx) { + int i; + install_element_ve(&show_ms_cmd); install_element_ve(&show_subscr_cmd); install_element_ve(&show_support_cmd); @@ -3062,6 +3204,23 @@ int ms_vty_init(void) install_element(MS_NODE, &cfg_ms_no_nb_dedicated_cmd); install_element(MS_NODE, &cfg_ms_ringtone_cmd); install_element(MS_NODE, &cfg_ms_no_ringtone_cmd); + install_element(MS_NODE, &cfg_ms_ui_cmd); + install_node(&ui_node, config_write_dummy); + install_default(UI_NODE); + install_element(UI_NODE, &ournode_exit_cmd); + install_element(UI_NODE, &ournode_end_cmd); + install_element(UI_NODE, &cfg_ms_ui_telnet_port_cmd); + install_element(UI_NODE, &cfg_ms_ui_no_telnet_port_cmd); + cfg_ms_ui_status_cmd = + talloc_zero_array(tall_ctx, struct cmd_element, GUI_NUM_STATUS); + for (i = 0; i < GUI_NUM_STATUS; i++) { + cfg_ms_ui_status_cmd[i].string = status_screen[i].feature_vty; + cfg_ms_ui_status_cmd[i].func = cfg_ms_ui_status; + cfg_ms_ui_status_cmd[i].doc = status_screen[i].feature_help; + cfg_ms_ui_status_cmd[i].attr = 0; + cfg_ms_ui_status_cmd[i].daemon = 0; + install_element(UI_NODE, &cfg_ms_ui_status_cmd[i]); + } install_element(MS_NODE, &cfg_ms_support_cmd); install_node(&support_node, config_write_dummy); install_default(SUPPORT_NODE); @@ -3121,6 +3280,7 @@ int ms_vty_init(void) install_element(SUPPORT_NODE, &cfg_ms_sup_dsc_max_cmd); install_element(SUPPORT_NODE, &cfg_ms_sup_skip_max_per_band_cmd); install_element(SUPPORT_NODE, &cfg_ms_sup_no_skip_max_per_band_cmd); + install_element(MS_NODE, &cfg_ms_testsim_cmd); install_node(&testsim_node, config_write_dummy); install_default(TESTSIM_NODE); install_element(TESTSIM_NODE, &ournode_exit_cmd); @@ -3141,6 +3301,13 @@ int ms_vty_init(void) return 0; } +void ms_vty_exit(void) +{ + if (cfg_ms_ui_status_cmd) + talloc_free(cfg_ms_ui_status_cmd); + cfg_ms_ui_status_cmd = NULL; +} + void vty_notify(struct osmocom_ms *ms, const char *fmt, ...) { struct telnet_connection *connection; |