mirror of
https://github.com/CTCaer/hekate
synced 2024-12-23 04:01:13 +00:00
Add status bar (battery info)
This gets updated in menus and backup/restore options and it's still visible to other options
This commit is contained in:
parent
2f120d1cbb
commit
828b616468
7 changed files with 77 additions and 35 deletions
|
@ -43,6 +43,7 @@ void set_default_configuration()
|
||||||
h_cfg.customlogo = 0;
|
h_cfg.customlogo = 0;
|
||||||
h_cfg.verification = 2;
|
h_cfg.verification = 2;
|
||||||
h_cfg.se_keygen_done = 0;
|
h_cfg.se_keygen_done = 0;
|
||||||
|
h_cfg.sbar_time_keeping = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int create_config_entry()
|
int create_config_entry()
|
||||||
|
|
|
@ -27,6 +27,7 @@ typedef struct _hekate_config
|
||||||
u32 verification;
|
u32 verification;
|
||||||
// Global temporary config.
|
// Global temporary config.
|
||||||
int se_keygen_done;
|
int se_keygen_done;
|
||||||
|
u32 sbar_time_keeping;
|
||||||
}hekate_config;
|
}hekate_config;
|
||||||
|
|
||||||
void set_default_configuration();
|
void set_default_configuration();
|
||||||
|
|
|
@ -136,6 +136,11 @@ void gfx_clear_color(gfx_ctxt_t *ctxt, u32 color)
|
||||||
ctxt->fb[i] = color;
|
ctxt->fb[i] = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void gfx_clear_partial_grey(gfx_ctxt_t *ctxt, u8 color, u32 pos_x, u32 height)
|
||||||
|
{
|
||||||
|
memset(ctxt->fb + pos_x * ctxt->stride , color, height * 4 * ctxt->stride);
|
||||||
|
}
|
||||||
|
|
||||||
void gfx_con_init(gfx_con_t *con, gfx_ctxt_t *ctxt)
|
void gfx_con_init(gfx_con_t *con, gfx_ctxt_t *ctxt)
|
||||||
{
|
{
|
||||||
con->gfx_ctxt = ctxt;
|
con->gfx_ctxt = ctxt;
|
||||||
|
|
|
@ -44,6 +44,7 @@ typedef struct _gfx_con_t
|
||||||
|
|
||||||
void gfx_init_ctxt(gfx_ctxt_t *ctxt, u32 *fb, u32 width, u32 height, u32 stride);
|
void gfx_init_ctxt(gfx_ctxt_t *ctxt, u32 *fb, u32 width, u32 height, u32 stride);
|
||||||
void gfx_clear_grey(gfx_ctxt_t *ctxt, u8 color);
|
void gfx_clear_grey(gfx_ctxt_t *ctxt, u8 color);
|
||||||
|
void gfx_clear_partial_grey(gfx_ctxt_t *ctxt, u8 color, u32 pos_x, u32 height);
|
||||||
void gfx_clear_color(gfx_ctxt_t *ctxt, u32 color);
|
void gfx_clear_color(gfx_ctxt_t *ctxt, u32 color);
|
||||||
void gfx_con_init(gfx_con_t *con, gfx_ctxt_t *ctxt);
|
void gfx_con_init(gfx_con_t *con, gfx_ctxt_t *ctxt);
|
||||||
void gfx_con_setcol(gfx_con_t *con, u32 fgcol, int fillbg, u32 bgcol);
|
void gfx_con_setcol(gfx_con_t *con, u32 fgcol, int fillbg, u32 bgcol);
|
||||||
|
|
32
ipl/main.c
32
ipl/main.c
|
@ -354,7 +354,7 @@ void config_hw()
|
||||||
|
|
||||||
void print_fuseinfo()
|
void print_fuseinfo()
|
||||||
{
|
{
|
||||||
gfx_clear_grey(&gfx_ctxt, 0x1B);
|
gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256);
|
||||||
gfx_con_setpos(&gfx_con, 0, 0);
|
gfx_con_setpos(&gfx_con, 0, 0);
|
||||||
|
|
||||||
u32 burntFuses = 0;
|
u32 burntFuses = 0;
|
||||||
|
@ -407,7 +407,7 @@ void print_fuseinfo()
|
||||||
|
|
||||||
void print_kfuseinfo()
|
void print_kfuseinfo()
|
||||||
{
|
{
|
||||||
gfx_clear_grey(&gfx_ctxt, 0x1B);
|
gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256);
|
||||||
gfx_con_setpos(&gfx_con, 0, 0);
|
gfx_con_setpos(&gfx_con, 0, 0);
|
||||||
|
|
||||||
gfx_printf(&gfx_con, "%kKFuse contents:\n\n%k", 0xFF00DDFF, 0xFFCCCCCC);
|
gfx_printf(&gfx_con, "%kKFuse contents:\n\n%k", 0xFF00DDFF, 0xFFCCCCCC);
|
||||||
|
@ -442,7 +442,7 @@ void print_kfuseinfo()
|
||||||
|
|
||||||
void print_mmc_info()
|
void print_mmc_info()
|
||||||
{
|
{
|
||||||
gfx_clear_grey(&gfx_ctxt, 0x1B);
|
gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256);
|
||||||
gfx_con_setpos(&gfx_con, 0, 0);
|
gfx_con_setpos(&gfx_con, 0, 0);
|
||||||
|
|
||||||
static const u32 SECTORS_TO_MIB_COEFF = 11;
|
static const u32 SECTORS_TO_MIB_COEFF = 11;
|
||||||
|
@ -599,7 +599,7 @@ void print_sdcard_info()
|
||||||
{
|
{
|
||||||
static const u32 SECTORS_TO_MIB_COEFF = 11;
|
static const u32 SECTORS_TO_MIB_COEFF = 11;
|
||||||
|
|
||||||
gfx_clear_grey(&gfx_ctxt, 0x1B);
|
gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256);
|
||||||
gfx_con_setpos(&gfx_con, 0, 0);
|
gfx_con_setpos(&gfx_con, 0, 0);
|
||||||
|
|
||||||
if (sd_mount())
|
if (sd_mount())
|
||||||
|
@ -651,7 +651,7 @@ void print_sdcard_info()
|
||||||
|
|
||||||
void print_tsec_key()
|
void print_tsec_key()
|
||||||
{
|
{
|
||||||
gfx_clear_grey(&gfx_ctxt, 0x1B);
|
gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256);
|
||||||
gfx_con_setpos(&gfx_con, 0, 0);
|
gfx_con_setpos(&gfx_con, 0, 0);
|
||||||
|
|
||||||
sdmmc_storage_t storage;
|
sdmmc_storage_t storage;
|
||||||
|
@ -1137,7 +1137,8 @@ static void dump_emmc_selected(emmcPartType_t dumpType)
|
||||||
{
|
{
|
||||||
int res = 0;
|
int res = 0;
|
||||||
u32 timer = 0;
|
u32 timer = 0;
|
||||||
gfx_clear_grey(&gfx_ctxt, 0x1B);
|
gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256);
|
||||||
|
tui_sbar(&gfx_con, 1);
|
||||||
gfx_con_setpos(&gfx_con, 0, 0);
|
gfx_con_setpos(&gfx_con, 0, 0);
|
||||||
|
|
||||||
if (!sd_mount())
|
if (!sd_mount())
|
||||||
|
@ -1381,7 +1382,8 @@ static void restore_emmc_selected(emmcPartType_t restoreType)
|
||||||
{
|
{
|
||||||
int res = 0;
|
int res = 0;
|
||||||
u32 timer = 0;
|
u32 timer = 0;
|
||||||
gfx_clear_grey(&gfx_ctxt, 0x1B);
|
gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256);
|
||||||
|
tui_sbar(&gfx_con, 1);
|
||||||
gfx_con_setpos(&gfx_con, 0, 0);
|
gfx_con_setpos(&gfx_con, 0, 0);
|
||||||
|
|
||||||
gfx_printf(&gfx_con, "%kThis is a dangerous operation\nand may render your device inoperative!\n\n", 0xFFFFDD00);
|
gfx_printf(&gfx_con, "%kThis is a dangerous operation\nand may render your device inoperative!\n\n", 0xFFFFDD00);
|
||||||
|
@ -1513,7 +1515,7 @@ void dump_package1()
|
||||||
u8 *secmon = (u8 *)calloc(1, 0x40000);
|
u8 *secmon = (u8 *)calloc(1, 0x40000);
|
||||||
u8 *loader = (u8 *)calloc(1, 0x40000);
|
u8 *loader = (u8 *)calloc(1, 0x40000);
|
||||||
|
|
||||||
gfx_clear_grey(&gfx_ctxt, 0x1B);
|
gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256);
|
||||||
gfx_con_setpos(&gfx_con, 0, 0);
|
gfx_con_setpos(&gfx_con, 0, 0);
|
||||||
|
|
||||||
if (!sd_mount())
|
if (!sd_mount())
|
||||||
|
@ -1882,7 +1884,7 @@ void toggle_autorcm(){
|
||||||
sdmmc_storage_t storage;
|
sdmmc_storage_t storage;
|
||||||
sdmmc_t sdmmc;
|
sdmmc_t sdmmc;
|
||||||
|
|
||||||
gfx_clear_grey(&gfx_ctxt, 0x1B);
|
gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256);
|
||||||
gfx_con_setpos(&gfx_con, 0, 0);
|
gfx_con_setpos(&gfx_con, 0, 0);
|
||||||
|
|
||||||
if (!sdmmc_storage_init_mmc(&storage, &sdmmc, SDMMC_4, SDMMC_BUS_WIDTH_8, 4))
|
if (!sdmmc_storage_init_mmc(&storage, &sdmmc, SDMMC_4, SDMMC_BUS_WIDTH_8, 4))
|
||||||
|
@ -1984,7 +1986,7 @@ int fix_attributes(char *path, u32 *total, u32 is_root, u32 check_first_run)
|
||||||
|
|
||||||
void fix_sd_attr(u32 type)
|
void fix_sd_attr(u32 type)
|
||||||
{
|
{
|
||||||
gfx_clear_grey(&gfx_ctxt, 0x1B);
|
gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256);
|
||||||
gfx_con_setpos(&gfx_con, 0, 0);
|
gfx_con_setpos(&gfx_con, 0, 0);
|
||||||
|
|
||||||
char path[256];
|
char path[256];
|
||||||
|
@ -2143,7 +2145,7 @@ void print_battery_charger_info()
|
||||||
|
|
||||||
void print_battery_info()
|
void print_battery_info()
|
||||||
{
|
{
|
||||||
gfx_clear_grey(&gfx_ctxt, 0x1B);
|
gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256);
|
||||||
gfx_con_setpos(&gfx_con, 0, 0);
|
gfx_con_setpos(&gfx_con, 0, 0);
|
||||||
|
|
||||||
print_fuel_gauge_info();
|
print_fuel_gauge_info();
|
||||||
|
@ -2189,7 +2191,7 @@ void print_battery_info()
|
||||||
|
|
||||||
/* void fix_fuel_gauge_configuration()
|
/* void fix_fuel_gauge_configuration()
|
||||||
{
|
{
|
||||||
gfx_clear_grey(&gfx_ctxt, 0x1B);
|
gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256);
|
||||||
gfx_con_setpos(&gfx_con, 0, 0);
|
gfx_con_setpos(&gfx_con, 0, 0);
|
||||||
|
|
||||||
int battVoltage, avgCurrent;
|
int battVoltage, avgCurrent;
|
||||||
|
@ -2242,7 +2244,7 @@ void print_battery_info()
|
||||||
{
|
{
|
||||||
int avgCurrent;
|
int avgCurrent;
|
||||||
|
|
||||||
gfx_clear_grey(&gfx_ctxt, 0x1B);
|
gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256);
|
||||||
gfx_con_setpos(&gfx_con, 0, 0);
|
gfx_con_setpos(&gfx_con, 0, 0);
|
||||||
|
|
||||||
gfx_printf(&gfx_con, "%k\nThis will wipe your battery stats completely!\n"
|
gfx_printf(&gfx_con, "%k\nThis will wipe your battery stats completely!\n"
|
||||||
|
@ -2253,7 +2255,7 @@ void print_battery_info()
|
||||||
u32 btn = btn_wait();
|
u32 btn = btn_wait();
|
||||||
if (btn & BTN_POWER)
|
if (btn & BTN_POWER)
|
||||||
{
|
{
|
||||||
gfx_clear_grey(&gfx_ctxt, 0x1B);
|
gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256);
|
||||||
gfx_con_setpos(&gfx_con, 0, 0);
|
gfx_con_setpos(&gfx_con, 0, 0);
|
||||||
gfx_printf(&gfx_con, "%kKeep the USB cable connected!%k\n\n", 0xFFFFDD00, 0xFFCCCCCC);
|
gfx_printf(&gfx_con, "%kKeep the USB cable connected!%k\n\n", 0xFFFFDD00, 0xFFCCCCCC);
|
||||||
gfx_con_getpos(&gfx_con, &gfx_con.savedx, &gfx_con.savedy);
|
gfx_con_getpos(&gfx_con, &gfx_con.savedx, &gfx_con.savedy);
|
||||||
|
@ -2288,7 +2290,7 @@ void print_battery_info()
|
||||||
|
|
||||||
void fix_battery_desync()
|
void fix_battery_desync()
|
||||||
{
|
{
|
||||||
gfx_clear_grey(&gfx_ctxt, 0x1B);
|
gfx_clear_partial_grey(&gfx_ctxt, 0x1B, 0, 1256);
|
||||||
gfx_con_setpos(&gfx_con, 0, 0);
|
gfx_con_setpos(&gfx_con, 0, 0);
|
||||||
|
|
||||||
max77620_low_battery_monitor_config();
|
max77620_low_battery_monitor_config();
|
||||||
|
|
71
ipl/tui.c
71
ipl/tui.c
|
@ -18,15 +18,55 @@
|
||||||
#include "tui.h"
|
#include "tui.h"
|
||||||
#include "btn.h"
|
#include "btn.h"
|
||||||
#include "max17050.h"
|
#include "max17050.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
#ifdef MENU_LOGO_ENABLE
|
#ifdef MENU_LOGO_ENABLE
|
||||||
extern u8 *Kc_MENU_LOGO;
|
extern u8 *Kc_MENU_LOGO;
|
||||||
#define X_MENU_LOGO 119
|
#define X_MENU_LOGO 119
|
||||||
#define Y_MENU_LOGO 57
|
#define Y_MENU_LOGO 57
|
||||||
#define X_POS_MENU_LOGO 577
|
#define X_POS_MENU_LOGO 577
|
||||||
#define Y_POS_MENU_LOGO 1199
|
#define Y_POS_MENU_LOGO 1179
|
||||||
#endif //MENU_LOGO_ENABLE
|
#endif //MENU_LOGO_ENABLE
|
||||||
|
|
||||||
|
extern hekate_config h_cfg;
|
||||||
|
|
||||||
|
void tui_sbar(gfx_con_t *con, int force_update)
|
||||||
|
{
|
||||||
|
u32 timePassed = get_tmr_s() - h_cfg.sbar_time_keeping;
|
||||||
|
if (!force_update)
|
||||||
|
if (timePassed < 5)
|
||||||
|
return;
|
||||||
|
|
||||||
|
u8 prevFontSize = con->fntsz;
|
||||||
|
con->fntsz = 16;
|
||||||
|
h_cfg.sbar_time_keeping = get_tmr_s();
|
||||||
|
|
||||||
|
u32 battPercent = 0;
|
||||||
|
int battVoltCurr = 0;
|
||||||
|
|
||||||
|
gfx_con_getpos(con, &con->savedx, &con->savedy);
|
||||||
|
gfx_con_setpos(con, 0, 1260);
|
||||||
|
|
||||||
|
max17050_get_property(MAX17050_RepSOC, (int *)&battPercent);
|
||||||
|
max17050_get_property(MAX17050_VCELL, &battVoltCurr);
|
||||||
|
|
||||||
|
gfx_clear_partial_grey(con->gfx_ctxt, 0x30, 1256, 24);
|
||||||
|
gfx_printf(con, "%K%k Battery: %d.%d%% (%d mV) - Charge:", 0xFF303030, 0xFF888888,
|
||||||
|
(battPercent >> 8) & 0xFF, (battPercent & 0xFF) / 26, battVoltCurr);
|
||||||
|
|
||||||
|
max17050_get_property(MAX17050_AvgCurrent, &battVoltCurr);
|
||||||
|
|
||||||
|
if (battVoltCurr >= 0)
|
||||||
|
gfx_printf(con, " %k+%d mA %k%K\n",
|
||||||
|
0xFF008000, battVoltCurr / 1000, 0xFFCCCCCC, 0xFF1B1B1B);
|
||||||
|
else
|
||||||
|
gfx_printf(con, " %k-%d mA %k%K\n",
|
||||||
|
0xFF800000, (~battVoltCurr) / 1000, 0xFFCCCCCC, 0xFF1B1B1B);
|
||||||
|
con->fntsz = prevFontSize;
|
||||||
|
gfx_con_setpos(con, con->savedx, con->savedy);
|
||||||
|
}
|
||||||
|
|
||||||
void tui_pbar(gfx_con_t *con, int x, int y, u32 val, u32 fgcol, u32 bgcol)
|
void tui_pbar(gfx_con_t *con, int x, int y, u32 val, u32 fgcol, u32 bgcol)
|
||||||
{
|
{
|
||||||
u32 cx, cy;
|
u32 cx, cy;
|
||||||
|
@ -48,15 +88,18 @@ void tui_pbar(gfx_con_t *con, int x, int y, u32 val, u32 fgcol, u32 bgcol)
|
||||||
}
|
}
|
||||||
|
|
||||||
gfx_con_setpos(con, cx, cy);
|
gfx_con_setpos(con, cx, cy);
|
||||||
|
|
||||||
|
// Update status bar.
|
||||||
|
tui_sbar(con, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void *tui_do_menu(gfx_con_t *con, menu_t *menu)
|
void *tui_do_menu(gfx_con_t *con, menu_t *menu)
|
||||||
{
|
{
|
||||||
int idx = 0, prev_idx = 0, cnt = 0x7FFFFFFF;
|
int idx = 0, prev_idx = 0, cnt = 0x7FFFFFFF;
|
||||||
u32 battPercent = 0;
|
|
||||||
int battVoltCurr = 0;
|
|
||||||
|
|
||||||
gfx_clear_grey(con->gfx_ctxt, 0x1B);
|
gfx_clear_partial_grey(con->gfx_ctxt, 0x1B, 0, 1256);
|
||||||
|
tui_sbar(con, 1);
|
||||||
|
|
||||||
#ifdef MENU_LOGO_ENABLE
|
#ifdef MENU_LOGO_ENABLE
|
||||||
gfx_set_rect_rgb(con->gfx_ctxt, Kc_MENU_LOGO,
|
gfx_set_rect_rgb(con->gfx_ctxt, Kc_MENU_LOGO,
|
||||||
X_MENU_LOGO, Y_MENU_LOGO, X_POS_MENU_LOGO, Y_POS_MENU_LOGO);
|
X_MENU_LOGO, Y_MENU_LOGO, X_POS_MENU_LOGO, Y_POS_MENU_LOGO);
|
||||||
|
@ -113,21 +156,8 @@ void *tui_do_menu(gfx_con_t *con, menu_t *menu)
|
||||||
|
|
||||||
// Print help and battery status.
|
// Print help and battery status.
|
||||||
gfx_con_getpos(con, &con->savedx, &con->savedy);
|
gfx_con_getpos(con, &con->savedx, &con->savedy);
|
||||||
gfx_con_setpos(con, 0, 74 * 16);
|
gfx_con_setpos(con, 0, 1191);
|
||||||
gfx_printf(con, "%k VOL: Move up/down\n PWR: Select option\n\n", 0xFF555555);
|
gfx_printf(con, "%k VOL: Move up/down\n PWR: Select option%k", 0xFF555555, 0xFFCCCCCC);
|
||||||
|
|
||||||
max17050_get_property(MAX17050_RepSOC, (int *)&battPercent);
|
|
||||||
gfx_printf(con, " %d.%d%%", (battPercent >> 8) & 0xFF, (battPercent & 0xFF) / 26);
|
|
||||||
max17050_get_property(MAX17050_VCELL, &battVoltCurr);
|
|
||||||
gfx_printf(con, " (%d mV) ", battVoltCurr);
|
|
||||||
max17050_get_property(MAX17050_AvgCurrent, &battVoltCurr);
|
|
||||||
if (battVoltCurr >= 0)
|
|
||||||
gfx_printf(con, "\n %kCharging:%k %d mA %k\n",
|
|
||||||
0xFF008000, 0xFF555555, battVoltCurr / 1000, 0xFFCCCCCC);
|
|
||||||
else
|
|
||||||
gfx_printf(con, "\n %kDischarging:%k -%d mA %k\n",
|
|
||||||
0xFF800000, 0xFF555555, (~battVoltCurr) / 1000, 0xFFCCCCCC);
|
|
||||||
gfx_con_setpos(con, con->savedx, con->savedy);
|
|
||||||
|
|
||||||
// Wait for user command.
|
// Wait for user command.
|
||||||
u32 btn = btn_wait();
|
u32 btn = btn_wait();
|
||||||
|
@ -161,12 +191,13 @@ void *tui_do_menu(gfx_con_t *con, menu_t *menu)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
con->fntsz = 16;
|
con->fntsz = 16;
|
||||||
gfx_clear_grey(con->gfx_ctxt, 0x1B);
|
gfx_clear_partial_grey(con->gfx_ctxt, 0x1B, 0, 1256);
|
||||||
#ifdef MENU_LOGO_ENABLE
|
#ifdef MENU_LOGO_ENABLE
|
||||||
gfx_set_rect_rgb(con->gfx_ctxt, Kc_MENU_LOGO,
|
gfx_set_rect_rgb(con->gfx_ctxt, Kc_MENU_LOGO,
|
||||||
X_MENU_LOGO, Y_MENU_LOGO, X_POS_MENU_LOGO, Y_POS_MENU_LOGO);
|
X_MENU_LOGO, Y_MENU_LOGO, X_POS_MENU_LOGO, Y_POS_MENU_LOGO);
|
||||||
#endif //MENU_LOGO_ENABLE
|
#endif //MENU_LOGO_ENABLE
|
||||||
}
|
}
|
||||||
|
tui_sbar(con, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
@ -58,6 +58,7 @@ typedef struct _menu_t
|
||||||
#define MDEF_CAPTION(caption, color) { MENT_CAPTION, caption, color }
|
#define MDEF_CAPTION(caption, color) { MENT_CAPTION, caption, color }
|
||||||
#define MDEF_CHGLINE() {MENT_CHGLINE}
|
#define MDEF_CHGLINE() {MENT_CHGLINE}
|
||||||
|
|
||||||
|
void tui_sbar(gfx_con_t *con, int force_update);
|
||||||
void tui_pbar(gfx_con_t *con, int x, int y, u32 val, u32 fgcol, u32 bgcol);
|
void tui_pbar(gfx_con_t *con, int x, int y, u32 val, u32 fgcol, u32 bgcol);
|
||||||
void *tui_do_menu(gfx_con_t *con, menu_t *menu);
|
void *tui_do_menu(gfx_con_t *con, menu_t *menu);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue