Init
This commit is contained in:
522
sys/sunwindowdev/ws.c
Normal file
522
sys/sunwindowdev/ws.c
Normal file
@@ -0,0 +1,522 @@
|
||||
#ifndef lint
|
||||
static char sccsid[] = "@(#)ws.c 1.1 94/10/31";
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Copyright (c) 1986 by Sun Microsystems, Inc.
|
||||
*/
|
||||
|
||||
/*
|
||||
* SunWindows Workstation/Wsindev open and close code. Includes tuning
|
||||
* parameters.
|
||||
*/
|
||||
|
||||
#include <sunwindowdev/wintree.h>
|
||||
#include <sys/errno.h>
|
||||
#include <sys/file.h> /* For f_data */
|
||||
#include <sys/kmem_alloc.h>
|
||||
#include <sundev/kbio.h> /* For keyboard ioctls */
|
||||
|
||||
#define WS_VQ_NODE_BYTES 8192
|
||||
u_int ws_vq_node_bytes = WS_VQ_NODE_BYTES;
|
||||
/* Number of bytes to use for input q nodes */
|
||||
int ws_vq_expand_times = 6;
|
||||
/* Number of times can expand q by ws_vq_node_bytes */
|
||||
#define WS_QUEUE_COLLAPSE_FACTOR 0 /* Try to collapse q to 1/nth */
|
||||
/* Zero mean collapse all the time */
|
||||
int ws_q_collapse_factor = WS_QUEUE_COLLAPSE_FACTOR;
|
||||
|
||||
extern void ws_break_handler();
|
||||
extern void ws_stop_handler();
|
||||
extern void ws_flush_handler();
|
||||
Ws_usr_async ws_break_default =
|
||||
{0, SHIFT_TOP, 1, TOP_FIRST + 'i', 1, ws_break_handler};
|
||||
Ws_usr_async ws_stop_default =
|
||||
{0, SHIFT_TOP, 1, SHIFT_TOP, 0, ws_stop_handler};
|
||||
Ws_usr_async ws_flush_default =
|
||||
{0, SHIFT_TOP, 1, TOP_FIRST + 'f', 1, ws_flush_handler};
|
||||
|
||||
Ws_focus_set ws_kbd_focus_pt_default = {LOC_WINENTER, 1, 0};
|
||||
Ws_focus_set ws_kbd_focus_sw_default = {LOC_WINENTER, 1, 0};
|
||||
struct timeval ws_event_timeout_default = {2, 0};
|
||||
/* 2 secs give micro synchronization, thus collapsing when tracking */
|
||||
|
||||
/*
|
||||
* Tuning parameters that are set if ws_tuning_done is 0 when start SunWindows.
|
||||
*/
|
||||
int ws_tuning_done; /* Controls whether the following should be */
|
||||
/* initialized. */
|
||||
int ws_fast_timeout; /* Fast polling rate in hz */
|
||||
int ws_slow_timeout; /* Slow polling rate in hz */
|
||||
int ws_fast_poll_duration; /* Stop fast polling after this # hz */
|
||||
int ws_loc_still; /* Locator is still after this number of hz */
|
||||
int ws_check_lock; /* Check locks every this # of hz if pid */
|
||||
int ws_no_pid_check_lock; /* Check locks every this # of hz if no pid */
|
||||
struct timeval ws_check_time; /* Check locks every this # usec */
|
||||
int winclistcharsmax; /* Limit the amount of clist space can use */
|
||||
int ws_q_collapse_trigger; /* # q items that trigger collapse */
|
||||
|
||||
ws_init_tuning()
|
||||
{
|
||||
#define WS_FAST_TIMEOUT (hz/40) /* Fast polling rate */
|
||||
#define WS_SLOW_TIMEOUT (hz/8) /* Slow polling rate */
|
||||
#define WS_FAST_POLL_DURATION (hz/5) /* Stop fast polling after this # hz */
|
||||
#define WS_MOUSE_STILL (hz/5) /* Locator is still after this # of hz*/
|
||||
#define WS_CHECK_LOCK (hz/2) /* Check locks every #of hz if pid */
|
||||
#define WS_NO_PID_CHECK_LOCK (hz*20) /* Check locks every #of hz if no pid */
|
||||
|
||||
if (!ws_tuning_done) {
|
||||
extern int hz;
|
||||
|
||||
/* Init things can't do statically */
|
||||
if (!ws_fast_timeout)
|
||||
ws_fast_timeout = WS_FAST_TIMEOUT;
|
||||
if (!ws_slow_timeout)
|
||||
ws_slow_timeout = WS_SLOW_TIMEOUT;
|
||||
if (!ws_fast_poll_duration)
|
||||
ws_fast_poll_duration = WS_FAST_POLL_DURATION;
|
||||
if (!ws_loc_still)
|
||||
ws_loc_still = WS_MOUSE_STILL;
|
||||
if (!ws_check_lock)
|
||||
ws_check_lock = WS_CHECK_LOCK;
|
||||
if (!ws_no_pid_check_lock)
|
||||
ws_no_pid_check_lock = WS_NO_PID_CHECK_LOCK;
|
||||
if (!ws_check_time.tv_usec)
|
||||
ws_check_time.tv_usec = 1000000 / hz * ws_check_lock;
|
||||
if (!winclistcharsmax)
|
||||
/* Use half of all clist space */
|
||||
winclistcharsmax = nclist * CBSIZE / 2;
|
||||
if ((!ws_q_collapse_trigger) && ws_q_collapse_factor)
|
||||
ws_q_collapse_trigger =
|
||||
(ws_vq_node_bytes / sizeof (Vuid_q_node)) /
|
||||
ws_q_collapse_factor;
|
||||
ws_tuning_done = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Find workstation using input_device_name */
|
||||
Workstation *
|
||||
ws_indev_match_name(input_device_name, indev_ptr)
|
||||
char *input_device_name;
|
||||
Wsindev **indev_ptr;
|
||||
{
|
||||
register Workstation *ws;
|
||||
register Wsindev *indev;
|
||||
|
||||
if (input_device_name[0] == '\0')
|
||||
return (WORKSTATION_NULL);
|
||||
for (ws = workstations; ws < &workstations[nworkstations]; ws++) {
|
||||
for (indev = ws->ws_indev; indev != WSINDEV_NULL;
|
||||
indev = indev->wsid_next) {
|
||||
if (indev->wsid_name[0] != '\0' &&
|
||||
(strncmp(input_device_name,
|
||||
indev->wsid_name, SCR_NAMESIZE) == 0)) {
|
||||
if (indev_ptr)
|
||||
*indev_ptr = indev;
|
||||
return (ws);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (WORKSTATION_NULL);
|
||||
}
|
||||
|
||||
/* Find workstation using dev */
|
||||
Workstation *
|
||||
ws_indev_match_dev(dev)
|
||||
dev_t dev;
|
||||
{
|
||||
register Workstation *ws;
|
||||
register Wsindev *indev;
|
||||
|
||||
for (ws = workstations; ws < &workstations[nworkstations]; ws++) {
|
||||
for (indev = ws->ws_indev; indev != WSINDEV_NULL;
|
||||
indev = indev->wsid_next) {
|
||||
if (indev->wsid_dev == dev)
|
||||
return (ws);
|
||||
}
|
||||
}
|
||||
return (WORKSTATION_NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* A workstation is opened by a window that trying to establish itself
|
||||
* as the root window of a first desktop at a workstation. The list of
|
||||
* desktops is searched for a conflict of names of framebuffers to avoid
|
||||
* blitzing an existing screen. The list of workstations is searched for
|
||||
* a conflict of names of input devices that the window has opened for
|
||||
* itself.
|
||||
*/
|
||||
Workstation *
|
||||
ws_open()
|
||||
{
|
||||
register Workstation *ws = WORKSTATION_NULL;
|
||||
register Workstation *ws_probe = WORKSTATION_NULL;
|
||||
extern int ws_poll_rate;
|
||||
|
||||
/* Initialize tuning parameters */
|
||||
ws_init_tuning();
|
||||
/* Allocate an unused workstation */
|
||||
for (ws_probe = workstations; ws_probe < &workstations[nworkstations];
|
||||
ws_probe++) {
|
||||
if (!(ws_probe->ws_flags & WSF_PRESENT)) {
|
||||
ws = ws_probe;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((ws == WORKSTATION_NULL) || (ws->ws_dtop != DESKTOP_NULL)) {
|
||||
win_errno = EBUSY;
|
||||
return (WORKSTATION_NULL);
|
||||
}
|
||||
/* Allocate the input q */
|
||||
ws->ws_qbytes = ws_vq_node_bytes;
|
||||
ws->ws_qdata = new_kmem_zalloc((u_int)ws->ws_qbytes, KMEM_SLEEP);
|
||||
/*
|
||||
* There is no need to check for kmem_zalloc here because
|
||||
* kmem_zalloc will wait until there is space before it returns
|
||||
if (ws->ws_qdata == NULL) {
|
||||
#ifdef WINDEVDEBUG
|
||||
printf("Couldn't allocate %D byte event buffer bytes\n",
|
||||
ws->ws_qbytes);
|
||||
#endif
|
||||
win_errno = ENOMEM;
|
||||
return (WORKSTATION_NULL);
|
||||
}
|
||||
*/
|
||||
vq_initialize(&ws->ws_q, ws->ws_qdata, ws->ws_qbytes);
|
||||
|
||||
/* Make present so that need ws_close to clean up after this point */
|
||||
ws->ws_flags |= WSF_PRESENT;
|
||||
/* Initialize input queue synchronization parameters */
|
||||
ws->ws_break = ws_break_default;
|
||||
ws->ws_stop = ws_stop_default;
|
||||
ws->ws_eventtimeout = ws_event_timeout_default;
|
||||
/* Initialize kbd focus changing events */
|
||||
ws->ws_kbd_focus_pt = ws_kbd_focus_pt_default;
|
||||
ws->ws_kbd_focus_sw = ws_kbd_focus_sw_default;
|
||||
/*
|
||||
* Start device driver looking for input and doing deadlock
|
||||
* resolution if haven't started yet.
|
||||
*/
|
||||
if (ws_poll_rate == 0) {
|
||||
int ws_interrupt();
|
||||
|
||||
ws_poll_rate = ws_fast_timeout;
|
||||
timeout(ws_interrupt, (caddr_t)0, ws_poll_rate);
|
||||
}
|
||||
return (ws);
|
||||
}
|
||||
|
||||
/*
|
||||
* A workstation is told by its last desktop to close, as is a desktop
|
||||
* told by its last window to close.
|
||||
*/
|
||||
ws_close(ws)
|
||||
register Workstation *ws;
|
||||
{
|
||||
#ifdef WINSVJ
|
||||
void ws_dealloc_rec_q(); /* Recording q dealloc routine */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* ws_close_indev can sleep (see ws_close_indev) which can
|
||||
* allow ws_interrupt to be called as a timeout. Make this
|
||||
* workstation invisible to ws_interrupt now.
|
||||
*/
|
||||
ws->ws_flags |= WSF_EXITING;
|
||||
/* Close input devices used by this workstation */
|
||||
while (ws->ws_indev != WSINDEV_NULL)
|
||||
/* ws_close_indev deletes ws_indev from list */
|
||||
if (ws_close_indev(ws, ws->ws_indev) == -1)
|
||||
break;
|
||||
|
||||
/* All desktops should be removed by now */
|
||||
#ifdef WINDEVDEBUG
|
||||
if (ws->ws_dtop != DESKTOP_NULL)
|
||||
printf("ws_close: desktop still opened\n");
|
||||
#endif
|
||||
|
||||
/* Deallocate input queue */
|
||||
if (ws->ws_qdata != NULL)
|
||||
kmem_free(ws->ws_qdata, (u_int)ws->ws_qbytes);
|
||||
|
||||
/* Deallocate input state descriptions */
|
||||
vuid_destroy_state(ws->ws_instate);
|
||||
vuid_destroy_state(ws->ws_rtstate);
|
||||
|
||||
/* Deallocate any event recording queue */
|
||||
#ifdef WINSVJ
|
||||
(void) ws_dealloc_rec_q(ws);
|
||||
#endif
|
||||
bzero((caddr_t)ws, sizeof (*ws));
|
||||
/* ws_interrupt turns self off after last workstation goes away */
|
||||
}
|
||||
|
||||
Wsindev *
|
||||
ws_open_indev(ws, name, fd)
|
||||
Workstation *ws;
|
||||
char *name;
|
||||
int fd;
|
||||
{
|
||||
register Wsindev *wsid;
|
||||
register int i;
|
||||
int mode;
|
||||
struct file *fp = 0;
|
||||
struct vnode *vp;
|
||||
Wsindev *wsid_tmp;
|
||||
dev_t dev;
|
||||
struct termios tios;
|
||||
|
||||
/* Dup file descriptor for device */
|
||||
if ((u.u_error = kern_dupfd(fd, &fp)))
|
||||
goto SilentError;
|
||||
vp = (struct vnode *)fp->f_data;
|
||||
if (vp->v_stream == NULL) {
|
||||
u.u_error = ENOSTR; /* not a streams device */
|
||||
goto SilentError;
|
||||
}
|
||||
dev = vp->v_rdev;
|
||||
/* See if can find this device open already */
|
||||
if (ws_indev_match_dev(dev) != WORKSTATION_NULL) {
|
||||
u.u_error = EBUSY;
|
||||
goto SilentError;
|
||||
}
|
||||
/* Allocate new input record */
|
||||
wsid = (Wsindev *)new_kmem_zalloc(sizeof (*wsid), KMEM_SLEEP);
|
||||
if (wsid == WSINDEV_NULL) {
|
||||
#ifdef WINDEVDEBUG
|
||||
printf("ws_open_indev: heap alloc failed\n");
|
||||
#endif
|
||||
u.u_error = ENOMEM;
|
||||
goto SilentError;
|
||||
}
|
||||
bzero((caddr_t)wsid, sizeof (*wsid));
|
||||
/* Initialize record */
|
||||
wsid->wsid_fp = fp;
|
||||
wsid->wsid_dev = dev;
|
||||
wsid->wsid_vp = vp;
|
||||
wsid->wsid_flags = 0;
|
||||
/* Copy name */
|
||||
for (i = 0; i < SCR_NAMESIZE; i++)
|
||||
wsid->wsid_name[i] = name[i];
|
||||
/* Determine current byte stream mode */
|
||||
strioctl(vp, VUIDGFORMAT, (caddr_t)&wsid->wsid_previous_mode, 0);
|
||||
if (u.u_error) {
|
||||
/* ioctl err VUIDGFORMAT */
|
||||
if ((u.u_error == ENOTTY) || (u.u_error == EINVAL)) {
|
||||
wsid->wsid_flags |= WSID_TREAT_AS_ASCII;
|
||||
wsid->wsid_previous_mode = VUID_NATIVE;
|
||||
} else
|
||||
goto Error;
|
||||
}
|
||||
/* Set up to send firm events */
|
||||
mode = VUID_FIRM_EVENT;
|
||||
strioctl(vp, VUIDSFORMAT, (caddr_t)&mode, 0);
|
||||
if (u.u_error) {
|
||||
/* ioctl err VUIDSFORMAT */
|
||||
if ((u.u_error == ENOTTY) || (u.u_error == EINVAL)) {
|
||||
wsid->wsid_flags |= WSID_TREAT_AS_ASCII;
|
||||
wsid->wsid_previous_mode = VUID_NATIVE;
|
||||
} else
|
||||
goto Error;
|
||||
}
|
||||
/* Get sgttyb flags */
|
||||
strioctl(vp, TCGETS, (caddr_t)&tios, 0);
|
||||
if (u.u_error) {
|
||||
/* ioctl err TCGETS */
|
||||
goto Error;
|
||||
}
|
||||
wsid->wsid_iflag = tios.c_iflag;
|
||||
wsid->wsid_oflag = tios.c_oflag;
|
||||
wsid->wsid_cflag = tios.c_cflag;
|
||||
wsid->wsid_lflag = tios.c_lflag;
|
||||
/* Set raw */
|
||||
tios.c_iflag = 0;
|
||||
tios.c_oflag = 0;
|
||||
tios.c_cflag &= ~(CSIZE|PARENB);
|
||||
tios.c_cflag |= CS8;
|
||||
tios.c_lflag = 0;
|
||||
strioctl(vp, TCSETSF, (caddr_t)&tios, 0);
|
||||
if (u.u_error) {
|
||||
/* ioctl err TCSETSF */
|
||||
goto Error;
|
||||
}
|
||||
/*
|
||||
* Get/set direct I/O flag. This is a hack to avoid /dev/console
|
||||
* interference when using /dev/kbd. Silent about any error
|
||||
* on KIOCGDIRECT because only /dev/kbd will respond.
|
||||
*/
|
||||
strioctl(vp, KIOCGDIRECT, (caddr_t)&wsid->wsid_direct_flag, 0);
|
||||
if (u.u_error == 0) {
|
||||
int directio = 1;
|
||||
|
||||
strioctl(vp, KIOCSDIRECT, (caddr_t)&directio, 0);
|
||||
if (u.u_error) {
|
||||
/* ioctl err KIOCSDIRECT */
|
||||
goto Error;
|
||||
}
|
||||
wsid->wsid_flags |= WSID_DIRECT_VALID;
|
||||
/*
|
||||
* It's a keyboard; set up not to OR in bucky bits.
|
||||
*/
|
||||
mode = 0;
|
||||
strioctl(vp, KIOCSCOMPAT, (caddr_t)&mode, 0);
|
||||
if (u.u_error) {
|
||||
/* ioctl err KIOCSCOMPAT */
|
||||
goto Error;
|
||||
}
|
||||
}
|
||||
/* Put new record in list */
|
||||
wsid_tmp = ws->ws_indev;
|
||||
ws->ws_indev = wsid;
|
||||
wsid->wsid_next = wsid_tmp;
|
||||
return (wsid);
|
||||
Error:
|
||||
#ifdef WINDEVDEBUG
|
||||
printf("err = %D\n", u.u_error);
|
||||
#endif
|
||||
SilentError:
|
||||
return (WSINDEV_NULL);
|
||||
}
|
||||
|
||||
int
|
||||
ws_close_indev(ws, wsid)
|
||||
Workstation *ws;
|
||||
Wsindev *wsid;
|
||||
{
|
||||
struct termios tios;
|
||||
register struct vnode *vp = (struct vnode *)wsid->wsid_fp->f_data;
|
||||
register Wsindev *indev, **indev_ptr;
|
||||
int on = 1;
|
||||
|
||||
/*
|
||||
* Remove wsid from list associated with workstation.
|
||||
* This will avoid any further read of this device because zsclose
|
||||
* (called from closef below) might sleep.
|
||||
* In other words, when zsclose sleeps, the ws_interrupt timeout
|
||||
* can occur. By closef the device's original blocking state
|
||||
* has been restored. Thus, when ws_interrupt does a read of this
|
||||
* device, it might block (sleep). Blocking from a timeout notification
|
||||
* will lead to a panic: sleep.
|
||||
*/
|
||||
indev_ptr = &ws->ws_indev;
|
||||
for (indev = ws->ws_indev; indev != WSINDEV_NULL;
|
||||
indev = indev->wsid_next) {
|
||||
if (indev == wsid) {
|
||||
*indev_ptr = indev->wsid_next;
|
||||
indev->wsid_next = WSINDEV_NULL;
|
||||
goto Found;
|
||||
}
|
||||
indev_ptr = &indev->wsid_next;
|
||||
}
|
||||
/* ws_close_indev: device not found */
|
||||
return (-1);
|
||||
Found:
|
||||
u.u_error = 0;
|
||||
/* Reset sgttyb flags */
|
||||
strioctl(vp, TCGETS, (caddr_t)&tios, 0);
|
||||
#ifdef WINDEVDEBUG
|
||||
if (u.u_error)
|
||||
printf("ioctl err %D TCGETS\n", u.u_error);
|
||||
#endif
|
||||
tios.c_iflag = wsid->wsid_iflag;
|
||||
tios.c_oflag = wsid->wsid_oflag;
|
||||
tios.c_cflag = wsid->wsid_cflag;
|
||||
tios.c_lflag = wsid->wsid_lflag;
|
||||
strioctl(vp, TCSETSF, (caddr_t)&tios, 0);
|
||||
#ifdef WINDEVDEBUG
|
||||
if (u.u_error)
|
||||
printf("ioctl err %D TCSETSF\n", u.u_error);
|
||||
#endif
|
||||
/* Reset vuid format */
|
||||
strioctl(vp, VUIDSFORMAT, (caddr_t)&wsid->wsid_previous_mode, 0);
|
||||
#ifdef WINDEVDEBUG
|
||||
if (u.u_error)
|
||||
printf("ioctl err %D VUIDSFORMAT\n", u.u_error);
|
||||
#endif
|
||||
/* Reset direct flag */
|
||||
if (wsid->wsid_flags & WSID_DIRECT_VALID) {
|
||||
strioctl(vp, KIOCSDIRECT, (caddr_t)&wsid->wsid_direct_flag, 0);
|
||||
#ifdef WINDEVDEBUG
|
||||
if (u.u_error)
|
||||
printf("ioctl err %d KIOCSDIRECT\n", u.u_error);
|
||||
#endif
|
||||
/*
|
||||
* It's a keyboard; set up to OR in bucky bits.
|
||||
*/
|
||||
strioctl(vp, KIOCSCOMPAT, (caddr_t)&on, 0);
|
||||
#ifdef WINDEVDEBUG
|
||||
if (u.u_error)
|
||||
printf("ioctl err %D KIOCSCOMPAT\n", u.u_error);
|
||||
#endif
|
||||
}
|
||||
/* Cleanup file pointer */
|
||||
if (wsid->wsid_fp)
|
||||
closef(wsid->wsid_fp);
|
||||
/* Release memory */
|
||||
kmem_free((caddr_t)wsid, sizeof (*wsid));
|
||||
return (0);
|
||||
}
|
||||
|
||||
void
|
||||
ws_shrink_queue(ws)
|
||||
register Workstation *ws;
|
||||
{
|
||||
Vuid_queue q;
|
||||
u_int qbytes;
|
||||
caddr_t qdata;
|
||||
|
||||
/* Allocate new input q */
|
||||
qbytes = ws_vq_node_bytes;
|
||||
qdata = new_kmem_zalloc(qbytes, KMEM_NOSLEEP);
|
||||
if (qdata == NULL)
|
||||
return;
|
||||
vq_initialize(&q, qdata, (u_int)qbytes);
|
||||
/* Deallocate old input queue */
|
||||
if (ws->ws_qdata != NULL)
|
||||
kmem_free(ws->ws_qdata, ws->ws_qbytes);
|
||||
/* Update ws */
|
||||
ws->ws_qbytes = qbytes;
|
||||
ws->ws_qdata = qdata;
|
||||
ws->ws_q = q;
|
||||
}
|
||||
|
||||
/* Deal with input queue overflow */
|
||||
void
|
||||
ws_handle_overflow(ws)
|
||||
register Workstation *ws;
|
||||
{
|
||||
Vuid_queue q;
|
||||
u_int qbytes;
|
||||
caddr_t qdata;
|
||||
Firm_event firm_event;
|
||||
|
||||
if (ws->ws_qbytes >= ws_vq_node_bytes * ws_vq_expand_times)
|
||||
goto Flush;
|
||||
/* Allocate new input q */
|
||||
qbytes = ws->ws_qbytes + ws_vq_node_bytes;
|
||||
qdata = new_kmem_zalloc(qbytes, KMEM_NOSLEEP);
|
||||
if (qdata == NULL)
|
||||
goto Flush;
|
||||
vq_initialize(&q, qdata, (u_int)qbytes);
|
||||
/* Copy contents of old q onto new one */
|
||||
while (vq_get(&ws->ws_q, &firm_event) != VUID_Q_EMPTY)
|
||||
if (vq_put(&q, &firm_event) == VUID_Q_OVERFLOW)
|
||||
#ifdef WINDEVDEBUG
|
||||
printf("Window input queue unexpectedly full!\n");
|
||||
#else
|
||||
;
|
||||
#endif
|
||||
/* Deallocate old input queue */
|
||||
if (ws->ws_qdata != NULL)
|
||||
kmem_free(ws->ws_qdata, ws->ws_qbytes);
|
||||
/* Update ws */
|
||||
ws->ws_qbytes = qbytes;
|
||||
ws->ws_qdata = qdata;
|
||||
ws->ws_q = q;
|
||||
return;
|
||||
Flush:
|
||||
/* Punt and flush queue */
|
||||
printf("Window input queue overflow!\n");
|
||||
ws_flush_input(ws);
|
||||
printf("Window input queue flushed!\n");
|
||||
}
|
||||
Reference in New Issue
Block a user