mirror of
https://git.planet-casio.com/Lephenixnoir/gint.git
synced 2024-12-28 04:23:36 +01:00
keyboard: add keydown() in the model
This change adds a keydown() function that is synchronized with events, ie. it returns the key state as seen by previously read events. It also completely eliminates low-level repeat events, which are not very meaningul as the keyboard scan frequency goes up (and would be meaningless if KEYSC interrupts were used), and adapts getkey() by giving it access to the current driver time through pollevent().
This commit is contained in:
parent
86cd9b98d4
commit
95a3345326
4 changed files with 254 additions and 98 deletions
1
TODO
1
TODO
|
@ -18,7 +18,6 @@ Complementary elements on existing code.
|
|||
* topti: support Unicode fonts
|
||||
* hardware: fill in the HWMEM_FITTLB flag
|
||||
* keyboard: think of extended functions
|
||||
* keyboard: implement keydown() in an event-compliant way
|
||||
* cpg: spread spectrum on fxcg50
|
||||
* bopti: blending modes for monochrome bitmaps (use topti assembler)
|
||||
* display: use more of topti's assembler in drect()
|
||||
|
|
|
@ -5,43 +5,84 @@
|
|||
#ifndef GINT_KEYBOARD
|
||||
#define GINT_KEYBOARD
|
||||
|
||||
/* Keyboard, key events and getkey()
|
||||
/* Keyboard, key events, keydown() and getkey()
|
||||
|
||||
gint's keyboard driver regularly scans the keyboard matrix and produces *key
|
||||
events*, the most primitive type of event exposed to the user. A key event
|
||||
basically says that a key has been pressed, held, or released at some point
|
||||
in time. They are suited for real-time applications like games.
|
||||
events*. A key event basically says that a key has been pressed or released
|
||||
at some point in time. Key events are a faithful description of everything
|
||||
that happens on the keyboard.
|
||||
|
||||
- pollevent() fetches the next queued keyboard event, and returns an event
|
||||
of type KEYEV_NONE if none is available.
|
||||
- waitevent() fetches the next queued keyboard event and waits if none is
|
||||
available. The timeout can be configured.
|
||||
These key events are stored in the *event queue*, from where they can be
|
||||
retrieved:
|
||||
|
||||
GUI programs like the system applications will prefer using a GetKey()-like
|
||||
functions that return a single key press at a time, heeds for releases, for
|
||||
SHIFT and ALPHA modifiers, handles backlight and return-to-menu.
|
||||
* pollevent() fetches the next keyboard event waiting in the queue. Events
|
||||
will always be returned in chronological order. If there is no keyboard
|
||||
event pending (ie. if nothing happened since last time events were read),
|
||||
this function returns a dummy event of type KEYEV_NONE.
|
||||
* waitevent() fetches the next keyboard event waiting in the queue. If the
|
||||
queue is empty, it waits until one becomes available or a timeout expires,
|
||||
whichever comes first. The timeout can be configured.
|
||||
|
||||
- getkey_opt() is gint's enhanced GetKey()-like function, with support for
|
||||
When events have been read, other functions can be read to check whether a
|
||||
certain key (or set of keys) is pressed.
|
||||
|
||||
* keydown() checks if the specified key is currently pressed. (*)
|
||||
* keydown_all() checks if all keys of a specified list are pressed. The list
|
||||
should end with value 0.
|
||||
* keydown_any() checks if any key of a specified list is pressed. The list
|
||||
should end with value 0.
|
||||
|
||||
(*) The keydown() functions do not report the absolute current state of the
|
||||
key, but rather the state of the key according to the events that have
|
||||
been retrieved so far. If the queue contains {KEY_A, KEYEV_DOWN} and
|
||||
{KEY_A, KEYEV_UP}, the following sequence of events will happen:
|
||||
|
||||
keydown(KEY_A) -> 0
|
||||
pollevent() -> {KEY_A, KEYEV_DOWN}
|
||||
keydown(KEY_A) -> 1
|
||||
pollevent() -> {KEY_A, KEYEV_UP}
|
||||
keydown(KEY_A) -> 0
|
||||
|
||||
Synchronizing keydown() and the events increases the accuracy of
|
||||
keyboard information for fast programs and its reliability for slow
|
||||
programs.
|
||||
|
||||
When all events have been read from the queue, keydown() returns the
|
||||
absolute current key state, which is what will happen 90% of the time.
|
||||
|
||||
Applications that are not interested in event contents but only in pressed
|
||||
keys can automatically read all events from the queue before they start
|
||||
using keydown():
|
||||
|
||||
* clearevents() reads all pending events from the input queue.
|
||||
|
||||
The previous functions are quite low-level. GUI programs that look like the
|
||||
system applications will prefer using a GetKey()-like functions that return
|
||||
a single key press at a time, heeds for releases, for SHIFT and ALPHA
|
||||
modifiers, handles repeats, backlight and return-to-menu.
|
||||
|
||||
* getkey_opt() is gint's enhanced GetKey()-like function, with support for
|
||||
custom repetition and many fine-tunable options.
|
||||
- getkey() is a specific call to getkey_opt(), that imitates GetKey(). */
|
||||
* getkey() is a specific call to getkey_opt(), that imitates GetKey().
|
||||
|
||||
These functions introduce a new type of key event called KEYEV_HOLD which
|
||||
represents a key repeat. */
|
||||
|
||||
#include <gint/defs/types.h>
|
||||
#include <gint/keycodes.h>
|
||||
|
||||
/* key_event_t - any keyboard event
|
||||
This structure represents an event that occurs on the keyboard. This is a
|
||||
low-level structure that is produced by the keyboard scanner. It reports key
|
||||
presses, key releases, and key repeats.
|
||||
/* key_event_t: Low-level or high-level keyboard event
|
||||
|
||||
These events are detected and reported each time the keyboard is scanned,
|
||||
which is 128 Hz by default, so you'll get 128 repeat events by second if a
|
||||
key is kept pressed. We could filter the events to emit one only every
|
||||
second, for example, but it's difficult to do it for all keys at the same
|
||||
time. Thus the control of repeat delays is left to getkey().
|
||||
This structure represents an event that occurs on the keyboard. It is first
|
||||
produced by the keyboard scanner with limited information, then possibly
|
||||
enriched by getkey(). Events are produced each time the keyboard is scanned,
|
||||
which is 128 Hz by default. Hence, a key press and release occuring in less
|
||||
than 8 ms might not be detected.
|
||||
|
||||
When [mod = 1], attributes [shift] and [alpha] indicate whether the key has
|
||||
been modified. Only key press events returned by getkey() have [mod = 1].
|
||||
Note that you can't have, e.g. [key=KEY_SHIFT] and [mod=1] at the same time.
|
||||
getkey() returns enriched events with [mod=1], in whic ase [shift] and
|
||||
[alpha] indicate whether the key has been modified. Only key press events
|
||||
returned by getkey() have [mod=1]. Note that you can't have, e.g.
|
||||
[key=KEY_SHIFT] and [mod=1] at the same time.
|
||||
|
||||
The [time] attribute indicates when the event occurred. It is a snapshot of
|
||||
a time counter that increases at each keyboard scan and *wraps around every
|
||||
|
@ -64,7 +105,7 @@ typedef struct
|
|||
|
||||
} GPACKED(4) key_event_t;
|
||||
|
||||
/* Keyboard event types, as in the type field of key_event_t */
|
||||
/* Keyboard event types, as in the [type] field of key_event_t */
|
||||
enum
|
||||
{
|
||||
KEYEV_NONE = 0, /* No event available (poll() only) */
|
||||
|
@ -87,23 +128,50 @@ enum
|
|||
#endif
|
||||
|
||||
//---
|
||||
// Keyboard functions
|
||||
// Event-level functions
|
||||
//---
|
||||
|
||||
/* pollevent() - poll the next keyboard event
|
||||
This function returns the next yet-unpolled event from the keyboard buffer.
|
||||
If no event is available, it returns a dummy event with type=KEYEV_NONE.
|
||||
This function always returns events with mod=0. */
|
||||
/* pollevent(): Poll the next keyboard event
|
||||
This function returns the next event from the event queue, chronologically.
|
||||
If no event is available, it returns a dummy event with type=KEYEV_NONE
|
||||
and time set to the current driver time. This function always returns events
|
||||
with mod=0. */
|
||||
key_event_t pollevent(void);
|
||||
|
||||
/* waitevent() - wait for the next keyboard event
|
||||
/* waitevent(): Wait for the next keyboard event
|
||||
This function works as pollevent() but waits if no event is available. When
|
||||
timeout=NULL, it waits indefinitely. Otherwise, it waits until *timeout
|
||||
becomes non-zero. It is particularly suitable to set *timeout to 1 using a
|
||||
timer with [timer_timeout] as callback. See <gint/timer.h>. */
|
||||
key_event_t waitevent(volatile int *timeout);
|
||||
|
||||
/* getkey() - wait for a pressed key
|
||||
/* clearevents(): Read all events waiting in the queue */
|
||||
void clearevents(void);
|
||||
|
||||
//---
|
||||
// Key state functions
|
||||
//---
|
||||
|
||||
/* keydown(): Current key state
|
||||
This function returns zero if the specified key is currently up (according
|
||||
to the last events that have been processed) and non-zero if it is down. */
|
||||
int keydown(int key);
|
||||
|
||||
/* keydown_all(): Check a set of keys for simultaneous input
|
||||
Returns non-zero if all provided keys are down. The list should end with a 0
|
||||
as terminator. */
|
||||
int keydown_all(int key1, ...);
|
||||
|
||||
/* keydown_any(): Check a set of keys for any input
|
||||
Returns nonzero if any one of the specified keys is currently pressed. The
|
||||
sequence should be terminated by a 0 integer. */
|
||||
int keydown_any(int key1, ...);
|
||||
|
||||
//---
|
||||
// High-level functions
|
||||
//---
|
||||
|
||||
/* getkey(): Wait for a key press
|
||||
|
||||
This function mimics the behavior of the fxlib GetKey(). It returns a
|
||||
key_event_t object where [mod=1], and where [shift] and [alpha] indicate
|
||||
|
@ -133,16 +201,18 @@ enum {
|
|||
GETKEY_REP_ARROWS = 0x10,
|
||||
GETKEY_REP_ALL = 0x20,
|
||||
|
||||
/* No modifiers */
|
||||
GETKEY_NONE = 0x00,
|
||||
/* Default settings of getkey() */
|
||||
GETKEY_DEFAULT = 0x1f,
|
||||
};
|
||||
|
||||
/* getkey_opt() - enhanced getkey()
|
||||
/* getkey_opt(): Enhanced getkey()
|
||||
|
||||
This function enhances getkey() with more general features. An
|
||||
or-combination of option flags (see above) must be supplied as first
|
||||
argument; 0 stands for no option. getkey_opt() returns the same kind of
|
||||
values as getkey().
|
||||
argument; GETKEY_NONE stands for no option. getkey_opt() returns the same
|
||||
kind of events as getkey().
|
||||
|
||||
getkey_opt() supports a generic timeout function in the form of a volatile
|
||||
pointer [timeout]. If it's NULL, getkey_opt() waits indefinitely. Otherwise,
|
||||
|
@ -155,7 +225,7 @@ enum {
|
|||
Returns a key event of type KEYEV_DOWN or KEYEV_HOLD with [mod=1]. */
|
||||
key_event_t getkey_opt(int options, volatile int *timeout);
|
||||
|
||||
/* getkey_repeat() - set repeat delays for getkey()
|
||||
/* getkey_repeat(): Set repeat delays for getkey()
|
||||
|
||||
This function updates the repeat delays of getkey() and getkey_opt(). The
|
||||
unit of the argument is in milliseconds, but the granularity of the delay is
|
||||
|
|
|
@ -30,15 +30,11 @@ key_event_t getkey_opt(int opt, volatile int *timeout)
|
|||
static int rep_key = 0;
|
||||
/* Number of repeats already emitted */
|
||||
static int rep_count = 0;
|
||||
/* Scan intervals elapsed since last repeat */
|
||||
/* Keyboard time when the key was pressed */
|
||||
static int rep_time = 0;
|
||||
|
||||
while(1) switch((ev = waitevent(timeout)).type)
|
||||
while(1) switch((ev = pollevent()).type)
|
||||
{
|
||||
/* Timeout has expired, return KEYEV_NONE */
|
||||
case KEYEV_NONE:
|
||||
return ev;
|
||||
|
||||
/* Key press: handle modifiers or return an event */
|
||||
case KEYEV_DOWN:
|
||||
key = ev.key;
|
||||
|
@ -75,45 +71,52 @@ key_event_t getkey_opt(int opt, volatile int *timeout)
|
|||
}
|
||||
|
||||
/* Return current event */
|
||||
rep_key = key;
|
||||
rep_key = key;
|
||||
rep_count = 0;
|
||||
rep_time = 0;
|
||||
rep_time = ev.time;
|
||||
|
||||
ev.mod = 1;
|
||||
ev.shift = shift;
|
||||
ev.alpha = alpha;
|
||||
return ev;
|
||||
|
||||
/* Return new events when a key is held (maybe) */
|
||||
case KEYEV_HOLD:
|
||||
if(ev.key != rep_key) break;
|
||||
/* If nothing happens, stop or wait for a repeat to occur */
|
||||
case KEYEV_NONE:
|
||||
/* Timeout has expired, return KEYEV_NONE */
|
||||
if(timeout && *timeout) return ev;
|
||||
|
||||
/* Check that this key can be repeated */
|
||||
/* Check that the last pressed key can be repeated */
|
||||
int arrow = (rep_key == KEY_LEFT || rep_key == KEY_RIGHT ||
|
||||
rep_key == KEY_UP || rep_key == KEY_DOWN);
|
||||
rep_key == KEY_UP || rep_key == KEY_DOWN);
|
||||
|
||||
if(!(opt & GETKEY_REP_ALL) && !(opt & GETKEY_REP_ARROWS &&
|
||||
arrow)) break;
|
||||
if(!rep_key || !(
|
||||
(opt & GETKEY_REP_ALL) ||
|
||||
(opt & GETKEY_REP_ARROWS && arrow)
|
||||
)) break;
|
||||
|
||||
/* If the key is key pressed long enough, create a new event */
|
||||
int target = (rep_count) ? rep_next : rep_first;
|
||||
if(++rep_time < target) break;
|
||||
int duration = (int16_t)(ev.time - rep_time);
|
||||
|
||||
rep_time -= target;
|
||||
int target = (rep_count) ? rep_next : rep_first;
|
||||
if(duration < target) break;
|
||||
|
||||
rep_time += target;
|
||||
rep_count++;
|
||||
|
||||
ev.mod = 1;
|
||||
ev.shift = shift;
|
||||
ev.alpha = alpha;
|
||||
ev.type = KEYEV_HOLD;
|
||||
ev.key = rep_key;
|
||||
return ev;
|
||||
|
||||
/* Reset repeating information if the repeated key is released */
|
||||
case KEYEV_UP:
|
||||
if(ev.key != rep_key) break;
|
||||
|
||||
rep_key = 0;
|
||||
rep_key = 0;
|
||||
rep_count = 0;
|
||||
rep_time = 0;
|
||||
rep_time = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,13 @@
|
|||
#include <gint/clock.h>
|
||||
#include <gint/keyboard.h>
|
||||
|
||||
#include <gint/drivers/iokbd.h>
|
||||
#include <gint/defs/attributes.h>
|
||||
#include <gint/defs/types.h>
|
||||
#include <gint/drivers/iokbd.h>
|
||||
#include <gint/hardware.h>
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
//---
|
||||
// Keyboard buffer
|
||||
//---
|
||||
|
@ -20,34 +23,39 @@
|
|||
internal state with the hardware state and generates events accordingly.
|
||||
Events can be seen as a delta-encoding of the keyboard state over time.
|
||||
|
||||
The user which sums up these events to maintain a full keyboard state must
|
||||
get a correct result. As a consequence, if an event cannot be generated
|
||||
(whatever the reason), the driver's internal copy of the keyboard state must
|
||||
not be updated (probably the event will be re-emitted at the next scan). */
|
||||
To ensure that adding pending events to the last-read state always gives the
|
||||
internal driger state, this array is not updated if the generation of an
|
||||
event fails. (Most likely the even will be regenerated at the next scan.) */
|
||||
GDATA static volatile uint8_t state[12] = { 0 };
|
||||
|
||||
/* The driver's current event state. This state corresponds to the sum of all
|
||||
events sent to the user so far.When the event queue is empty, this is equal
|
||||
to [state]. For each generated event, this array is updated to reflect the
|
||||
user's view of the keyboard. */
|
||||
GDATA static uint8_t current[12] = { 0 };
|
||||
|
||||
/* A driver event, which is a change in a full row instead of a single key. */
|
||||
typedef struct
|
||||
{
|
||||
uint time :12; /* Locally unique time identifier */
|
||||
uint row :4; /* Row number */
|
||||
uint old :8; /* Key status for the old row */
|
||||
uint new :8; /* Key status for the new row */
|
||||
uint16_t time; /* Locally unique time identifier */
|
||||
uint8_t row; /* Row number */
|
||||
uint8_t changed; /* Keys that changed state */
|
||||
uint8_t state; /* Key state for the new row */
|
||||
|
||||
} driver_event_t;
|
||||
|
||||
/* The keyboard event buffer. This is a circular list defined by [buffer_start]
|
||||
and [buffer_end]. To avoid an ambiguity when start = end, the buffer is not
|
||||
allowed to be full (at least one free cell must be remaining). */
|
||||
and [buffer_end]. To avoid an ambiguity when start == end, there must always
|
||||
be at least one free entry. */
|
||||
GBSS static driver_event_t buffer[KEYBOARD_QUEUE_SIZE];
|
||||
/* Buffer bounds */
|
||||
GDATA static int buffer_start = 0;
|
||||
GDATA static int buffer_end = 0;
|
||||
|
||||
/* Current time, in keyboard-scanning ticks */
|
||||
GDATA static int time = 0;
|
||||
GDATA static uint time = 0;
|
||||
|
||||
/* buffer_push() - add an event in the keyboard buffer
|
||||
/* buffer_push(): Add an event in the keyboard buffer
|
||||
Returns non-zero if the event cannot be pushed. */
|
||||
static int buffer_push(driver_event_t ev)
|
||||
{
|
||||
|
@ -59,7 +67,7 @@ static int buffer_push(driver_event_t ev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* buffer_poll() - generate key events from the buffer
|
||||
/* buffer_poll(): Generate key events from the buffer
|
||||
Sets [ev] and returns zero on success, otherwise non-zero. */
|
||||
static int buffer_poll(driver_event_t *ev)
|
||||
{
|
||||
|
@ -70,7 +78,7 @@ static int buffer_poll(driver_event_t *ev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* keysc_frame() - generate a round of events for the current frame */
|
||||
/* keysc_frame(): Generate driver events from KEYSC state */
|
||||
static void keysc_frame(void)
|
||||
{
|
||||
GALIGNED(2) uint8_t scan[12] = { 0 };
|
||||
|
@ -90,13 +98,13 @@ static void keysc_frame(void)
|
|||
/* Compare new data with the internal state. */
|
||||
int old = state[row];
|
||||
int new = scan[row];
|
||||
if(old == new && !new) continue;
|
||||
if(old == new) continue;
|
||||
|
||||
driver_event_t ev = {
|
||||
.time = time,
|
||||
.row = row,
|
||||
.old = old,
|
||||
.new = new,
|
||||
.time = time,
|
||||
.row = row,
|
||||
.changed = old ^ new,
|
||||
.state = new,
|
||||
};
|
||||
|
||||
/* Update internal status if the event could be pushed */
|
||||
|
@ -113,41 +121,59 @@ key_event_t pollevent(void)
|
|||
/* Number of pending events in the previous buffer */
|
||||
static int events_pending = 0;
|
||||
|
||||
/* Use pending events first, then poll the driver buffer */
|
||||
/* Use pending events first, if they exist */
|
||||
|
||||
if(events_pending > 0) return events[--events_pending];
|
||||
if(events_pending > 0)
|
||||
{
|
||||
key_event_t ev = events[--events_pending];
|
||||
|
||||
/* Update the current state buffer according to [ev] */
|
||||
int row = (ev.key >> 4) ^ 1;
|
||||
int col = 0x80 >> (ev.key & 0x7);
|
||||
|
||||
if(ev.type == KEYEV_DOWN) current[row] |= col;
|
||||
if(ev.type == KEYEV_UP) current[row] &= ~col;
|
||||
|
||||
return ev;
|
||||
}
|
||||
|
||||
/* If not key event is pending, generate a chunk by polling the driver
|
||||
event queue */
|
||||
|
||||
driver_event_t ev;
|
||||
if(buffer_poll(&ev)) return (key_event_t){ .type = KEYEV_NONE };
|
||||
if(buffer_poll(&ev))
|
||||
{
|
||||
key_event_t ev = { .type = KEYEV_NONE, .time = time };
|
||||
return ev;
|
||||
}
|
||||
|
||||
/* Generate new key events and return the first of them*/
|
||||
int old = ev.old << 1;
|
||||
int new = ev.new;
|
||||
int changed = ev.changed;
|
||||
int state = ev.state;
|
||||
|
||||
for(int code = ((ev.row ^ 1) << 4) | 0x7; code & 0x7; code--)
|
||||
{
|
||||
int kind = (old & 2) | (new & 1);
|
||||
old >>= 1;
|
||||
new >>= 1;
|
||||
if(changed & 1)
|
||||
{
|
||||
key_event_t keyev = {
|
||||
.time = ev.time,
|
||||
.type = 2 - (state & 1),
|
||||
.key = code,
|
||||
};
|
||||
events[events_pending++] = keyev;
|
||||
}
|
||||
|
||||
if(!kind) continue;
|
||||
|
||||
key_event_t keyev = {
|
||||
.time = ev.time,
|
||||
.type = kind,
|
||||
.key = code,
|
||||
};
|
||||
events[events_pending++] = keyev;
|
||||
changed >>= 1;
|
||||
state >>= 1;
|
||||
}
|
||||
|
||||
return events[--events_pending];
|
||||
/* Call recursively to avoid duplicating the event emission code */
|
||||
return pollevent();
|
||||
}
|
||||
|
||||
/* waitevent() - wait for the next keyboard event */
|
||||
key_event_t waitevent(volatile int *timeout)
|
||||
{
|
||||
key_event_t none = { .type = KEYEV_NONE };
|
||||
|
||||
while(1)
|
||||
{
|
||||
key_event_t ev = pollevent();
|
||||
|
@ -157,7 +183,65 @@ key_event_t waitevent(volatile int *timeout)
|
|||
sleep();
|
||||
}
|
||||
|
||||
return none;
|
||||
key_event_t ev = { .type = KEYEV_NONE, .time = time };
|
||||
return ev;
|
||||
}
|
||||
|
||||
/* clearevents(): Read all events waiting in the queue */
|
||||
void clearevents(void)
|
||||
{
|
||||
while(pollevent().type != KEYEV_NONE);
|
||||
}
|
||||
|
||||
//---
|
||||
// Immediate key access
|
||||
//---
|
||||
|
||||
/* keydown(): Current key state */
|
||||
int keydown(int key)
|
||||
{
|
||||
int row = (key >> 4) ^ 1;
|
||||
int col = 0x80 >> (key & 0x7);
|
||||
|
||||
return (current[row] & col) != 0;
|
||||
}
|
||||
|
||||
/* keydown_all(): Check a set of keys for simultaneous input
|
||||
Returns non-zero if all provided keys are down. The list should end with an
|
||||
integer 0 as terminator. */
|
||||
int keydown_all(int key, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, key);
|
||||
|
||||
int st = 1;
|
||||
while(key && st)
|
||||
{
|
||||
st = keydown(key);
|
||||
key = va_arg(args, int);
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
return st;
|
||||
}
|
||||
|
||||
/* keydown_any(): Check a set of keys for any input
|
||||
Returns nonzero if any one of the specified keys is currently pressed. THe
|
||||
sequence should be terminated by a 0 integer. */
|
||||
int keydown_any(int key, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, key);
|
||||
|
||||
int st = 0;
|
||||
while(key && !st)
|
||||
{
|
||||
st = keydown(key);
|
||||
key = va_arg(args, int);
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
return st;
|
||||
}
|
||||
|
||||
//---
|
||||
|
|
Loading…
Reference in a new issue