From 9787b95ed714567c908940e82c4314207e826c67 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Sat, 3 Dec 2022 13:32:05 +0100 Subject: [PATCH] gint/usbtrace: add a USB tracer function --- CMakeLists.txt | 1 + include/gintctl/gint.h | 3 + include/gintctl/widgets/gscreen.h | 13 +- src/gint/usbtrace.c | 564 ++++++++++++++++++++++++++++++ src/gintctl.c | 3 + src/widgets/gscreen.c | 34 +- 6 files changed, 607 insertions(+), 11 deletions(-) create mode 100644 src/gint/usbtrace.c diff --git a/CMakeLists.txt b/CMakeLists.txt index a013672..54102a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ set(SOURCES src/gint/tlb.c src/gint/topti.c src/gint/usb.c + src/gint/usbtrace.c src/libs/bfile.c src/libs/justui.c src/libs/openlibm.c diff --git a/include/gintctl/gint.h b/include/gintctl/gint.h index 6102ad2..f62fb98 100644 --- a/include/gintctl/gint.h +++ b/include/gintctl/gint.h @@ -59,6 +59,9 @@ void gintctl_gint_kmalloc(void); /* gintctl_gint_usb(): USB communication */ void gintctl_gint_usb(void); +/* gintctl_gint_usbtrace(): A detailed USB troubleshooter */ +void gintctl_gint_usbtrace(void); + #ifdef FX9860G /* gintctl_gint_gray(): Gray engine tuning */ diff --git a/include/gintctl/widgets/gscreen.h b/include/gintctl/widgets/gscreen.h index 3a81375..7d75e48 100644 --- a/include/gintctl/widgets/gscreen.h +++ b/include/gintctl/widgets/gscreen.h @@ -30,15 +30,15 @@ typedef struct { /* Fixed widets */ jlabel *title; jfkeys *fkeys; - /* Current function bar level */ - int8_t fkey_level; } gscreen; struct gscreen_tab { - /* TODO: gscreen: Hide title bar and/or status bar */ + /* Set whether the title is visible or the fkeys is visible */ bool title_visible, fkeys_visible; - /* Most recent focused widget one */ + /* Fkey level associated with the tab */ + int fkey_level; + /* Most recent focused widget in the tab (regains focus when switching) */ jwidget *focus; }; @@ -63,7 +63,7 @@ void gscreen_destroy(gscreen *s); // Function bar settings //--- -/* gscreen_set_fkeys_level(): Select the function key bar */ +/* gscreen_set_fkeys_level(): Override the function key bar */ void gscreen_set_fkeys_level(gscreen *s, int level); //--- @@ -86,6 +86,9 @@ void gscreen_set_tab_title_visible(gscreen *s, int tab, bool visible); /* gscreen_set_tab_fkeys_visible(): Set whether fkeys are shown on a tab */ void gscreen_set_tab_fkeys_visible(gscreen *s, int tab, bool visible); +/* gscreen_set_tab_fkeys_level(): Set fkeys level shown on a tab */ +void gscreen_set_tab_fkeys_level(gscreen *s, int tab, int level); + //--- // Tab navigation //--- diff --git a/src/gint/usbtrace.c b/src/gint/usbtrace.c new file mode 100644 index 0000000..40e2389 --- /dev/null +++ b/src/gint/usbtrace.c @@ -0,0 +1,564 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef FXCG50 + +#define USB SH7305_USB + +//=== Command and trace models === + +enum { + COMMAND_OPEN = 0, + COMMAND_OPEN_WAIT, + COMMAND_CLOSE, + /* Async writes */ + COMMAND_WRITE_ASYNC_TEXT_HEADER, + COMMAND_WRITE_ASYNC_IMAGE_HEADER, + COMMAND_WRITE_ASYNC_SHORT_TEXT, + COMMAND_WRITE_ASYNC_VRAM, + /* Sync writes */ + COMMAND_WRITE_SYNC_TEXT_HEADER, + COMMAND_WRITE_SYNC_IMAGE_HEADER, + COMMAND_WRITE_SYNC_SHORT_TEXT, + COMMAND_WRITE_SYNC_VRAM, + /* Control */ + COMMAND_COMMIT_ASYNC, + COMMAND_COMMIT_SYNC, + COMMAND_WORLD_SWITCH, + + COMMAND__TOTAL, +}; + +struct trace { + char const *message; + uint16_t SYSCFG, SYSSTS, DVSTCTR; + uint16_t CFIFOSEL, CFIFOCTR; + uint16_t D0FIFOSEL, D0FIFOCTR; + uint16_t D1FIFOSEL, D1FIFOCTR; + uint16_t INTENB0, BRDYENB, NRDYENB, BEMPENB; + uint16_t INTSTS0, BRDYSTS, NRDYSTS, BEMPSTS; + uint16_t PIPESEL, PIPECFG; + uint16_t PIPECTR[9]; +}; + +#define MAX_COMMANDS 64 +#define MAX_TRACES 256 /* ~14 kB */ + +static int *commands = NULL; +static int commands_len = 0; + +static struct trace *traces = NULL; +static int traces_len = 0; + +//=== Model interface === + +static void commands_clear(void) +{ + commands_len = 0; +} + +static void commands_add(int c) +{ + if(commands_len >= MAX_COMMANDS) + return; + commands[commands_len++] = c; +} + +static int commands_move_by(int index, int diff) +{ + if(index < 0 || index >= commands_len) + return index; + if(index + diff < 0 || index + diff >= commands_len || diff == 0) + return index; + + int c = commands[index + diff]; + commands[index + diff] = commands[index]; + commands[index] = c; + return index + diff; +} + +static void traces_clear(void) +{ + traces_len = 0; +} + +static struct trace *traces_add(void) +{ + if(traces_len >= MAX_TRACES) + return NULL; + return &traces[traces_len++]; +} + +//=== Painters === + +static char const * const command_texts[] = { + "usb_open({&usb_ff_bulk, NULL})", + "usb_open_wait()", + "usb_close()", + + "usb_write_async() [text header]", + "usb_write_async() [image header]", + "usb_write_async() [short text]", + "usb_write_async() [VRAM]", + + "usb_write_sync() [text header]", + "usb_write_sync() [image header]", + "usb_write_sync() [short text]", + "usb_write_sync() [VRAM]", + + "usb_commit_async()", + "usb_commit_sync()", + "World switch", +}; + +static void paint_command(int x, int y, int w, int h, GUNUSED jlist *l, + int index, bool selected) +{ + if(index < commands_len) { + dprint(x+6, y+3, C_RGB(16, 16, 16), "[%02d]", index); + dtext(x+38, y+3, C_BLACK, command_texts[commands[index]]); + + if(selected) { + /* Up/Down arrows */ + int ah = 4; + int spacing = 3; + for(int i = 0; i < 2 * ah; i++) { + int aw = (i < ah) ? i : 2*ah-i-1; + int dy = (i < ah) ? i+spacing : h-ah-spacing+(i-ah); + int dx = w - 10; + dline(x+dx-aw, y+dy, x+dx+aw, y+dy, C_BLACK); + } + } + } + else { + dtext(x+8, y+3, C_BLACK, ""); + } + + if(selected) + drect(x, y, x+w-1, y+h-1, C_INVERT); +} + +static void info_command(GUNUSED jlist *l, GUNUSED int index, + jlist_item_info *info) +{ + info->delegate = NULL; + info->selectable = true; + info->triggerable = true; + info->natural_width = 116; + info->natural_height = 15; +} + +static void paint_commandoption(int x, int y, int w, int h, GUNUSED jlist *l, + int index, bool selected) +{ + dtext(x+8, y+3, C_BLACK, command_texts[index]); + + if(selected) + drect(x, y, x+w-1, y+h-1, C_INVERT); +} + +static void info_commandoption(GUNUSED jlist *l, GUNUSED int index, + jlist_item_info *info) +{ + info->delegate = NULL; + info->selectable = true; + info->triggerable = true; + info->natural_width = 116; + info->natural_height = 15; +} + +static void paint_trace(int x, int y, int w, int h, GUNUSED jlist *l, + int index, bool selected) +{ + dprint(x+8, y+3, C_BLACK, "<> %s", traces[index].message); + + if(selected) + drect(x, y, x+w-1, y+h-1, C_INVERT); +} + +static void info_trace(GUNUSED jlist *l, GUNUSED int index, + jlist_item_info *info) +{ + info->delegate = NULL; + info->selectable = true; + info->triggerable = true; + info->natural_width = 116; + info->natural_height = 15; +} + +//=== Command execution infrastructure ===// + +// TODO: Collect logs + +static void usbtrace_trace(char const *message) +{ + struct trace *t = traces_add(); + if(!t) + return; + + t->message = message; + t->SYSCFG = USB.SYSCFG.word; + t->SYSSTS = USB.SYSSTS.word; + t->DVSTCTR = USB.DVSTCTR.word; + t->CFIFOSEL = USB.CFIFOSEL.word; + t->CFIFOCTR = USB.CFIFOCTR.word; + t->D0FIFOSEL = USB.D0FIFOSEL.word; + t->D0FIFOCTR = USB.D0FIFOCTR.word; + t->D1FIFOSEL = USB.D1FIFOSEL.word; + t->D1FIFOCTR = USB.D1FIFOCTR.word; + t->INTENB0 = USB.INTENB0.word; + t->BRDYENB = USB.BRDYENB.word; + t->NRDYENB = USB.NRDYENB.word; + t->BEMPENB = USB.BEMPENB.word; + t->INTSTS0 = USB.INTSTS0.word; + t->BRDYSTS = USB.BRDYSTS.word; + t->NRDYSTS = USB.NRDYSTS.word; + t->BEMPSTS = USB.BEMPSTS.word; + t->PIPESEL = USB.PIPESEL.word; + t->PIPECFG = USB.PIPECFG.word; + + for(int i = 0; i < 9; i++) + t->PIPECTR[i] = USB.PIPECTR[i].word; +} + +static void execute_tracer(void) +{ + usb_set_trace(usbtrace_trace); + usb_interface_t const *intf[] = { &usb_ff_bulk, NULL }; + + char const *text_fmt = "Hello from USB tracer! Message %03d.\n"; + char text[64]; + int text_count = 0; + int text_size = 36; + + void *image = gint_vram; + int image_size = DWIDTH * DHEIGHT * 2; + + for(int i = 0; i < commands_len; i++) { + USB_TRACE(command_texts[commands[i]]); + + + switch(commands[i]) { + case COMMAND_OPEN: + usb_open(intf, GINT_CALL_NULL); + break; + case COMMAND_OPEN_WAIT: + usb_open_wait(); + break; + case COMMAND_CLOSE: + usb_close(); + break; + + case COMMAND_WRITE_ASYNC_TEXT_HEADER:{ + usb_fxlink_header_t h; + usb_fxlink_fill_header(&h, "fxlink", "text", text_size); + usb_write_async(usb_ff_bulk_output(), &h, sizeof h, 1, false, + GINT_CALL_NULL); + break;} + + case COMMAND_WRITE_ASYNC_IMAGE_HEADER:{ + usb_fxlink_header_t h; + usb_fxlink_image_t sh; + usb_fxlink_fill_header(&h, "fxlink", "image", + sizeof sh + image_size); + sh.width = htole32(DWIDTH); + sh.height = htole32(DHEIGHT); + sh.pixel_format = htole32(USB_FXLINK_IMAGE_RGB565); + usb_write_async(usb_ff_bulk_output(), &h, sizeof h, 4, false, + GINT_CALL_NULL); + usb_write_async(usb_ff_bulk_output(), &sh, sizeof sh, 4, false, + GINT_CALL_NULL); + break;} + + case COMMAND_WRITE_ASYNC_SHORT_TEXT: + sprintf(text, text_fmt, ++text_count); + usb_write_async(usb_ff_bulk_output(), text, text_size, 1, false, + GINT_CALL_NULL); + break; + + case COMMAND_WRITE_ASYNC_VRAM: + usb_write_async(usb_ff_bulk_output(), image, image_size, 4, false, + GINT_CALL_NULL); + break; + + case COMMAND_WRITE_SYNC_TEXT_HEADER:{ + usb_fxlink_header_t h; + usb_fxlink_fill_header(&h, "fxlink", "text", text_size); + usb_write_sync(usb_ff_bulk_output(), &h, sizeof h, 1, false); + break;} + + case COMMAND_WRITE_SYNC_IMAGE_HEADER:{ + usb_fxlink_header_t h; + usb_fxlink_image_t sh; + usb_fxlink_fill_header(&h, "fxlink", "image", + sizeof sh + image_size); + sh.width = htole32(DWIDTH); + sh.height = htole32(DHEIGHT); + sh.pixel_format = htole32(USB_FXLINK_IMAGE_RGB565); + usb_write_sync(usb_ff_bulk_output(), &h, sizeof h, 4, false); + usb_write_sync(usb_ff_bulk_output(), &sh, sizeof sh, 4, false); + break;} + + case COMMAND_WRITE_SYNC_SHORT_TEXT: + sprintf(text, text_fmt, ++text_count); + usb_write_sync(usb_ff_bulk_output(), text, text_size, 1, false); + break; + + case COMMAND_WRITE_SYNC_VRAM: + usb_write_sync(usb_ff_bulk_output(), image, image_size, 4, false); + break; + + case COMMAND_COMMIT_ASYNC: + usb_commit_async(usb_ff_bulk_output(), GINT_CALL_NULL); + break; + case COMMAND_COMMIT_SYNC: + usb_commit_sync(usb_ff_bulk_output()); + break; + case COMMAND_WORLD_SWITCH: + gint_world_switch(GINT_CALL_NULL); + break; + } + } + + usb_set_trace(NULL); +} + +static void val(int order, char const *name, uint32_t value, uint32_t old) +{ + int y = 12 * (order / 3) + 20; + int x = 6 + 128 * (order % 3); + + int fg = (value != old) ? C_RGB(31, 8, 8) : C_WHITE; + + dprint(x, y, C_WHITE, "%s", name); + dprint(x+_(40,88), y, fg, "%04X", value); +} + +#define val(order, name, t1, t2, value) \ + val(order, name, t1->value, t2 ? t2->value : t1->value) + +static void draw_registers(struct trace const *t, struct trace const *prev) +{ + val( 0, "SYSCFG", t, prev, SYSCFG); + val( 1, "SYSSTS", t, prev, SYSSTS); + val( 2, "DVSTCTR", t, prev, DVSTCTR); + + val( 3, "CFIFOSEL", t, prev, CFIFOSEL); + val( 4, "D0FIFOSEL", t, prev, D0FIFOSEL); + val( 5, "D1FIFOSEL", t, prev, D1FIFOSEL); + val( 6, "CFIFOCTR", t, prev, CFIFOCTR); + val( 7, "D0FIFOCTR", t, prev, D0FIFOCTR); + val( 8, "D1FIFOCTR", t, prev, D1FIFOCTR); + + val( 9, "INTENB0", t, prev, INTENB0); + val(10, "INTSTS0", t, prev, INTSTS0); + + val(12, "BRDYENB", t, prev, BRDYENB); + val(13, "NRDYENB", t, prev, NRDYENB); + val(14, "BEMPENB", t, prev, BEMPENB); + val(15, "BRDYSTS", t, prev, BRDYSTS); + val(16, "NRDYSTS", t, prev, NRDYSTS); + val(17, "BEMPSTS", t, prev, BEMPSTS); + + val(18, "PIPESEL", t, prev, PIPESEL); + val(19, "PIPECFG", t, prev, PIPECFG); + + val(21, "PIPE1CTR", t, prev, PIPECTR[0]); + val(22, "PIPE2CTR", t, prev, PIPECTR[1]); + val(23, "PIPE3CTR", t, prev, PIPECTR[2]); + val(24, "PIPE4CTR", t, prev, PIPECTR[3]); + val(25, "PIPE5CTR", t, prev, PIPECTR[4]); + val(26, "PIPE6CTR", t, prev, PIPECTR[5]); + val(27, "PIPE7CTR", t, prev, PIPECTR[6]); + val(28, "PIPE8CTR", t, prev, PIPECTR[7]); + val(29, "PIPE9CTR", t, prev, PIPECTR[8]); +} + +static int browse_traces(int cursor) +{ + if(cursor < 0 || cursor >= traces_len) + return cursor; + + while(1) { + struct trace *t = &traces[cursor]; + + dclear(0x5555); + if(cursor > 0) + dtext_opt(4, 4, C_WHITE, C_NONE, DTEXT_LEFT, DTEXT_TOP, "<"); + if(cursor < traces_len+1) + dtext_opt(DWIDTH-5, 4, C_WHITE, C_NONE, DTEXT_RIGHT, DTEXT_TOP, + ">"); + dprint(20, 4, C_WHITE, "[%3d/%3d] %s", cursor+1, traces_len, + t->message); + draw_registers(t, cursor > 0 ? &traces[cursor - 1] : NULL); + dupdate(); + + key_event_t ev = getkey(); + int k = ev.key; + if(k == KEY_LEFT && !ev.shift && cursor > 0) + cursor--; + if(k == KEY_RIGHT && !ev.shift && cursor < traces_len - 1) + cursor++; + if(k == KEY_LEFT && ev.shift) + cursor = 0; + if(k == KEY_RIGHT && ev.shift) + cursor = traces_len - 1; + if(k == KEY_EXIT) + return cursor; + } +} + +//=== Main function === + +void gintctl_gint_usbtrace(void) +{ + if(commands == NULL) + commands = malloc(MAX_COMMANDS * sizeof *commands); + if(traces == NULL) + traces = malloc(MAX_TRACES * sizeof *traces); + + if(commands == NULL || traces == NULL) { + dclear(C_WHITE); + dprint(1, 1, C_BLACK, "Alloc failure!"); + dupdate(); + getkey(); + return; + } + + commands_clear(); + traces_clear(); + + gscreen *scr = gscreen_create("Live USB state tracing", + "/PROG;/TRACES;;;;#RUN|/PROG;/TRACES;;;#CLEAR;"); + + // Command composition tab + + jscrolledlist *tab1 = jscrolledlist_create(NULL, info_command, + paint_command); + jlist *commands_list = tab1->list; + jlist_update_model(commands_list, 1); + + // Trace browsing tab + + jscrolledlist *tab2 = jscrolledlist_create(NULL, info_trace, + paint_trace); + jlist *traces_list = tab2->list; + jlist_update_model(traces_list, 0); + + // New command selection tab + + jscrolledlist *tab3 = jscrolledlist_create(NULL, info_commandoption, + paint_commandoption); + jlist *commandoptions_list = tab3->list; + jlist_update_model(commandoptions_list, COMMAND__TOTAL); + + // Scene setup + + gscreen_add_tab(scr, tab1, commands_list); + gscreen_set_tab_fkeys_level(scr, 0, 0); + gscreen_add_tab(scr, tab2, traces_list); + gscreen_set_tab_fkeys_level(scr, 1, 1); + gscreen_add_tab(scr, tab3, commandoptions_list); + gscreen_set_tab_fkeys_visible(scr, 2, false); + + gscreen_show_tab(scr, 0); + jscene_set_focused_widget(scr->scene, commands_list); + + commands_add(COMMAND_OPEN); + commands_add(COMMAND_OPEN_WAIT); + commands_add(COMMAND_WRITE_SYNC_TEXT_HEADER); + commands_add(COMMAND_WRITE_SYNC_SHORT_TEXT); + commands_add(COMMAND_COMMIT_SYNC); + commands_add(COMMAND_WORLD_SWITCH); + commands_add(COMMAND_OPEN_WAIT); + commands_add(COMMAND_WRITE_SYNC_TEXT_HEADER); + commands_add(COMMAND_WRITE_SYNC_SHORT_TEXT); + commands_add(COMMAND_COMMIT_SYNC); + commands_add(COMMAND_WRITE_SYNC_TEXT_HEADER); + commands_add(COMMAND_WRITE_SYNC_SHORT_TEXT); + commands_add(COMMAND_COMMIT_SYNC); + jlist_update_model(commands_list, commands_len+1); + + while(1) { + jevent e = jscene_run(scr->scene); + void *focus = jscene_focused_widget(scr->scene); + int key = 0; + if(e.type == JSCENE_KEY && e.key.type == KEYEV_DOWN) + key = e.key.key; + + if(e.type == JSCENE_PAINT) { + dclear(C_WHITE); + jscene_render(scr->scene); + dupdate(); + } + + if(e.type == JLIST_ITEM_TRIGGERED && e.source == commands_list) { + if(e.data >= commands_len) + gscreen_show_tab(scr, 2); + } + if(e.type == JLIST_ITEM_TRIGGERED && e.source == commandoptions_list) { + commands_add(e.data); + jlist_update_model(commands_list, commands_len + 1); + gscreen_show_tab(scr, 0); + } + if(e.type == JLIST_ITEM_TRIGGERED && e.source == traces_list) { + int cursor = jlist_selected_item(traces_list); + cursor = browse_traces(cursor); + jlist_select(traces_list, cursor); + } + + if(key == KEY_F5 && gscreen_in(scr, 1)) { + traces_clear(); + jlist_update_model(traces_list, traces_len); + } + + if(key == KEY_F6 && gscreen_in(scr, 0)) { + execute_tracer(); + jlist_update_model(traces_list, traces_len); + gscreen_show_tab(scr, 1); + } + + //--- + + if(key == KEY_F1 && !gscreen_in(scr, 2)) { + gscreen_show_tab(scr, 0); + } + if(key == KEY_F2 && !gscreen_in(scr, 2)) { + gscreen_show_tab(scr, 1); + } + + if(key == KEY_UP && e.key.alpha && focus == commands_list) { + int s = commands_move_by(jlist_selected_item(commands_list), -1); + // TODO: Factor out these list model updates + jlist_update_model(commands_list, commands_len + 1); + jlist_select(commands_list, s); + } + if(key == KEY_DOWN && e.key.alpha && focus == commands_list) { + int s = commands_move_by(jlist_selected_item(commands_list), +1); + jlist_update_model(commands_list, commands_len + 1); + jlist_select(commands_list, s); + } + + if(key == KEY_EXIT && (gscreen_in(scr, 0) || gscreen_in(scr, 1))) + break; + if((key == KEY_EXIT || key == KEY_F6) && gscreen_in(scr, 2)) + gscreen_show_tab(scr, 0); + } + + gscreen_destroy(scr); +} + +#endif diff --git a/src/gintctl.c b/src/gintctl.c index b235742..b946341 100644 --- a/src/gintctl.c +++ b/src/gintctl.c @@ -51,6 +51,9 @@ struct menu menu_gint = { { "DMA control", gintctl_gint_dma, MENU_SH4_ONLY }, { "Real-time clock", gintctl_gint_rtc, 0 }, { "USB communication", gintctl_gint_usb, MENU_SH4_ONLY }, + #ifdef FXCG50 + { "USB tracer", gintctl_gint_usbtrace, MENU_SH4_ONLY }, + #endif { "Basic rendering", gintctl_gint_render, 0 }, { "Image rendering", gintctl_gint_image, 0 }, { "Text rendering", gintctl_gint_topti, 0 }, diff --git a/src/widgets/gscreen.c b/src/widgets/gscreen.c index 9d60069..36a9769 100644 --- a/src/widgets/gscreen.c +++ b/src/widgets/gscreen.c @@ -24,7 +24,6 @@ gscreen *gscreen_create(char const *name, char const *labels) g->scene = s; g->tabs = NULL; g->tab_count = 0; - g->fkey_level = 0; jlabel *title = name ? jlabel_create(name, s) : NULL; jwidget *stack = jwidget_create(s); @@ -85,7 +84,7 @@ static jwidget *tab_stack(gscreen *s) void gscreen_set_fkeys_level(gscreen *s, int level) { - s->fkey_level = level; + jfkeys_set_level(s->fkeys, level); } //--- @@ -98,11 +97,15 @@ void gscreen_add_tab(gscreen *s, void *widget, void *focus) if(!t) return; s->tabs = t; - s->tabs[s->tab_count].title_visible = (s->title != NULL); - s->tabs[s->tab_count].fkeys_visible = (s->fkeys != NULL); + s->tabs[s->tab_count].title_visible = true; + s->tabs[s->tab_count].fkeys_visible = true; s->tabs[s->tab_count].focus = focus; + s->tabs[s->tab_count].fkey_level = 0; s->tab_count++; + if(s->tab_count == 1) + jscene_set_focused_widget(s->scene, focus); + jwidget_add_child(tab_stack(s), widget); jwidget_set_stretch(widget, 1, 1, false); } @@ -138,6 +141,15 @@ void gscreen_set_tab_fkeys_visible(gscreen *s, int tab, bool visible) jwidget_set_visible(s->fkeys, visible); } +void gscreen_set_tab_fkeys_level(gscreen *s, int tab, int level) +{ + if(!s->fkeys || tab < 0 || tab >= s->tab_count) return; + s->tabs[tab].fkey_level = level; + + if(gscreen_current_tab(s) == tab) + jfkeys_set_level(s->fkeys, level); +} + //--- // Tab navigation //--- @@ -161,8 +173,18 @@ bool gscreen_show_tab(gscreen *s, int tab) stack->update = 1; /* Hide or show title and function key bar as needed */ - jwidget_set_visible(s->title, s->tabs[tab].title_visible); - if(s->fkeys) jwidget_set_visible(s->fkeys, s->tabs[tab].fkeys_visible); + if(s->title) { + jwidget_set_visible(s->title, s->tabs[tab].title_visible); + } + if(s->fkeys) { + if(s->tabs[tab].fkeys_visible) { + jfkeys_set_level(s->fkeys, s->tabs[tab].fkey_level); + jwidget_set_visible(s->fkeys, true); + } + else { + jwidget_set_visible(s->fkeys, false); + } + } return true; }