summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/host/layer23/include/osmocom/bb/common/osmocom_data.h2
-rw-r--r--src/host/layer23/include/osmocom/bb/mobile/Makefile.am2
-rw-r--r--src/host/layer23/include/osmocom/bb/mobile/gui.h50
-rw-r--r--src/host/layer23/include/osmocom/bb/mobile/settings.h4
-rw-r--r--src/host/layer23/include/osmocom/bb/mobile/vty.h4
-rw-r--r--src/host/layer23/src/mobile/Makefile.am4
-rw-r--r--src/host/layer23/src/mobile/app_mobile.c17
-rw-r--r--src/host/layer23/src/mobile/gsm480_ss.c27
-rw-r--r--src/host/layer23/src/mobile/gui.c2481
-rw-r--r--src/host/layer23/src/mobile/main.c2
-rw-r--r--src/host/layer23/src/mobile/mnccms.c14
-rw-r--r--src/host/layer23/src/mobile/settings.c7
-rw-r--r--src/host/layer23/src/mobile/vty_interface.c175
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;