diff --git a/dlls/user32/input.c b/dlls/user32/input.c index 8b2ae805aa..0d5bddd0af 100644 --- a/dlls/user32/input.c +++ b/dlls/user32/input.c @@ -33,6 +33,7 @@ #include #define NONAMELESSUNION +#define NONAMELESSSTRUCT #include "ntstatus.h" #define WIN32_NO_STATUS @@ -129,6 +130,30 @@ BOOL CDECL __wine_send_input( HWND hwnd, const INPUT *input ) return !status; } +BOOL CDECL __wine_send_raw_input( const RAWINPUT *raw_input ) +{ + NTSTATUS status; + + SERVER_START_REQ( send_rawinput_message ) + { + req->input.type = raw_input->header.dwType; + switch (raw_input->header.dwType) + { + case RIM_TYPEMOUSE: + req->input.mouse.x = raw_input->data.mouse.lLastX; + req->input.mouse.y = raw_input->data.mouse.lLastY; + req->input.mouse.info = raw_input->data.mouse.ulExtraInformation; + req->input.mouse.button_flags = raw_input->data.mouse.u.s.usButtonFlags; + req->input.mouse.button_data = raw_input->data.mouse.u.s.usButtonData; + break; + } + status = wine_server_call( req ); + } + SERVER_END_REQ; + + return status; +} + /*********************************************************************** * update_mouse_coords diff --git a/dlls/user32/message.c b/dlls/user32/message.c index 43ce77c2dd..b6dd7d5932 100644 --- a/dlls/user32/message.c +++ b/dlls/user32/message.c @@ -2295,54 +2295,14 @@ static BOOL process_rawinput_message( MSG *msg, const struct hardware_msg_data * rawinput->header.dwType = msg_data->rawinput.type; if (msg_data->rawinput.type == RIM_TYPEMOUSE) { - static const unsigned int button_flags[] = - { - 0, /* MOUSEEVENTF_MOVE */ - RI_MOUSE_LEFT_BUTTON_DOWN, /* MOUSEEVENTF_LEFTDOWN */ - RI_MOUSE_LEFT_BUTTON_UP, /* MOUSEEVENTF_LEFTUP */ - RI_MOUSE_RIGHT_BUTTON_DOWN, /* MOUSEEVENTF_RIGHTDOWN */ - RI_MOUSE_RIGHT_BUTTON_UP, /* MOUSEEVENTF_RIGHTUP */ - RI_MOUSE_MIDDLE_BUTTON_DOWN, /* MOUSEEVENTF_MIDDLEDOWN */ - RI_MOUSE_MIDDLE_BUTTON_UP, /* MOUSEEVENTF_MIDDLEUP */ - }; - unsigned int i; - rawinput->header.dwSize = FIELD_OFFSET(RAWINPUT, data) + sizeof(RAWMOUSE); rawinput->header.hDevice = WINE_MOUSE_HANDLE; rawinput->header.wParam = 0; rawinput->data.mouse.usFlags = MOUSE_MOVE_RELATIVE; - rawinput->data.mouse.u.s.usButtonFlags = 0; - rawinput->data.mouse.u.s.usButtonData = 0; - for (i = 1; i < ARRAY_SIZE(button_flags); ++i) - { - if (msg_data->flags & (1 << i)) - rawinput->data.mouse.u.s.usButtonFlags |= button_flags[i]; - } - if (msg_data->flags & MOUSEEVENTF_WHEEL) - { - rawinput->data.mouse.u.s.usButtonFlags |= RI_MOUSE_WHEEL; - rawinput->data.mouse.u.s.usButtonData = msg_data->rawinput.mouse.data; - } - if (msg_data->flags & MOUSEEVENTF_HWHEEL) - { - rawinput->data.mouse.u.s.usButtonFlags |= RI_MOUSE_HORIZONTAL_WHEEL; - rawinput->data.mouse.u.s.usButtonData = msg_data->rawinput.mouse.data; - } - if (msg_data->flags & MOUSEEVENTF_XDOWN) - { - if (msg_data->rawinput.mouse.data == XBUTTON1) - rawinput->data.mouse.u.s.usButtonFlags |= RI_MOUSE_BUTTON_4_DOWN; - else if (msg_data->rawinput.mouse.data == XBUTTON2) - rawinput->data.mouse.u.s.usButtonFlags |= RI_MOUSE_BUTTON_5_DOWN; - } - if (msg_data->flags & MOUSEEVENTF_XUP) - { - if (msg_data->rawinput.mouse.data == XBUTTON1) - rawinput->data.mouse.u.s.usButtonFlags |= RI_MOUSE_BUTTON_4_UP; - else if (msg_data->rawinput.mouse.data == XBUTTON2) - rawinput->data.mouse.u.s.usButtonFlags |= RI_MOUSE_BUTTON_5_UP; - } + + rawinput->data.mouse.u.s.usButtonFlags = msg_data->rawinput.mouse.button_flags; + rawinput->data.mouse.u.s.usButtonData = msg_data->rawinput.mouse.button_data; rawinput->data.mouse.ulRawButtons = 0; rawinput->data.mouse.lLastX = msg_data->rawinput.mouse.x; diff --git a/dlls/user32/rawinput.c b/dlls/user32/rawinput.c index 2085fd3f9f..120de073c8 100644 --- a/dlls/user32/rawinput.c +++ b/dlls/user32/rawinput.c @@ -250,7 +250,7 @@ BOOL WINAPI DECLSPEC_HOTPATCH RegisterRawInputDevices(RAWINPUTDEVICE *devices, U TRACE("device %u: page %#x, usage %#x, flags %#x, target %p.\n", i, devices[i].usUsagePage, devices[i].usUsage, devices[i].dwFlags, devices[i].hwndTarget); - if (devices[i].dwFlags & ~RIDEV_REMOVE) + if (devices[i].dwFlags & ~(RIDEV_REMOVE|RIDEV_NOLEGACY)) FIXME("Unhandled flags %#x for device %u.\n", devices[i].dwFlags, i); d[i].usage_page = devices[i].usUsagePage; diff --git a/dlls/user32/user32.spec b/dlls/user32/user32.spec index d5b8597d8e..7103b86055 100644 --- a/dlls/user32/user32.spec +++ b/dlls/user32/user32.spec @@ -832,4 +832,5 @@ # or 'wine_' (for user-visible functions) to avoid namespace conflicts. # @ cdecl __wine_send_input(long ptr) +@ cdecl __wine_send_raw_input(ptr) @ cdecl __wine_set_pixel_format(long long) diff --git a/dlls/winex11.drv/mouse.c b/dlls/winex11.drv/mouse.c index f737a306a5..06cfbd9e0d 100644 --- a/dlls/winex11.drv/mouse.c +++ b/dlls/winex11.drv/mouse.c @@ -286,9 +286,9 @@ static void update_relative_valuators(XIAnyClassInfo **valuators, int n_valuator /*********************************************************************** - * enable_xinput2 + * X11DRV_SetupXInput2 */ -static void enable_xinput2(void) +void X11DRV_SetupXInput2(void) { #ifdef HAVE_X11_EXTENSIONS_XINPUT2_H struct x11drv_thread_data *data = x11drv_thread_data(); @@ -318,7 +318,6 @@ static void enable_xinput2(void) memset( mask_bits, 0, sizeof(mask_bits) ); XISetMask( mask_bits, XI_DeviceChanged ); XISetMask( mask_bits, XI_RawMotion ); - XISetMask( mask_bits, XI_ButtonPress ); pXISelectEvents( data->display, DefaultRootWindow( data->display ), &mask, 1 ); @@ -349,7 +348,7 @@ static void disable_xinput2(void) struct x11drv_thread_data *data = x11drv_thread_data(); XIEventMask mask; - if (data->xi2_state != xi_enabled) return; + if (data->xi2_state < xi_enabled) return; TRACE( "disabling\n" ); data->xi2_state = xi_disabled; @@ -368,6 +367,21 @@ static void disable_xinput2(void) #endif } +static void use_xinput2_path(void) +{ + struct x11drv_thread_data *thread_data = x11drv_thread_data(); + + if (thread_data->xi2_state == xi_enabled) + thread_data->xi2_state = xi_extra; +} + +static void disable_xinput2_path(void) +{ + struct x11drv_thread_data *thread_data = x11drv_thread_data(); + + if (thread_data->xi2_state == xi_extra) + thread_data->xi2_state = xi_enabled; +} /*********************************************************************** * grab_clipping_window @@ -393,9 +407,9 @@ static BOOL grab_clipping_window( const RECT *clip ) return TRUE; /* enable XInput2 unless we are already clipping */ - if (!data->clip_hwnd) enable_xinput2(); + if (!data->clip_hwnd) use_xinput2_path(); - if (data->xi2_state != xi_enabled) + if (data->xi2_state < xi_extra) { WARN( "XInput2 not supported, refusing to clip to %s\n", wine_dbgstr_rect(clip) ); DestroyWindow( msg_hwnd ); @@ -423,7 +437,7 @@ static BOOL grab_clipping_window( const RECT *clip ) if (!clipping_cursor) { - disable_xinput2(); + disable_xinput2_path(); DestroyWindow( msg_hwnd ); return FALSE; } @@ -489,7 +503,7 @@ LRESULT clip_cursor_notify( HWND hwnd, HWND new_clip_hwnd ) TRACE( "clip hwnd reset from %p\n", hwnd ); data->clip_hwnd = 0; data->clip_reset = GetTickCount(); - disable_xinput2(); + disable_xinput2_path(); DestroyWindow( hwnd ); } else if (hwnd == GetForegroundWindow()) /* request to clip */ @@ -1724,16 +1738,18 @@ static BOOL X11DRV_RawMotion( XGenericEventCookie *xev ) { XIRawEvent *event = xev->data; const double *values = event->valuators.values; + const double *raw_values = event->raw_values; RECT virtual_rect; INPUT input; + RAWINPUT raw_input; int i; - double dx = 0, dy = 0, val; + double dx = 0, dy = 0, raw_dx = 0, raw_dy = 0, val, raw_val; struct x11drv_thread_data *thread_data = x11drv_thread_data(); struct x11drv_valuator_data *x_rel, *y_rel; if (thread_data->x_rel_valuator.number < 0 || thread_data->y_rel_valuator.number < 0) return FALSE; if (!event->valuators.mask_len) return FALSE; - if (thread_data->xi2_state != xi_enabled) return FALSE; + if (thread_data->xi2_state < xi_enabled) return FALSE; /* If there is no slave currently detected, no previous motion nor device * change events were received. Look it up now on the device list in this @@ -1758,25 +1774,21 @@ static BOOL X11DRV_RawMotion( XGenericEventCookie *xev ) x_rel = &thread_data->x_rel_valuator; y_rel = &thread_data->y_rel_valuator; - input.u.mi.mouseData = 0; - input.u.mi.dwFlags = MOUSEEVENTF_MOVE; - input.u.mi.time = EVENT_x11_time_to_win32_time( event->time ); - input.u.mi.dwExtraInfo = 0; - input.u.mi.dx = 0; - input.u.mi.dy = 0; - virtual_rect = get_virtual_screen_rect(); for (i = 0; i <= max ( x_rel->number, y_rel->number ); i++) { if (!XIMaskIsSet( event->valuators.mask, i )) continue; val = *values++; + raw_val = *raw_values++; if (i == x_rel->number) { input.u.mi.dx = dx = val; if (x_rel->min < x_rel->max) input.u.mi.dx = val * (virtual_rect.right - virtual_rect.left) / (x_rel->max - x_rel->min); + + raw_dx = raw_val; } if (i == y_rel->number) { @@ -1784,6 +1796,8 @@ static BOOL X11DRV_RawMotion( XGenericEventCookie *xev ) if (y_rel->min < y_rel->max) input.u.mi.dy = val * (virtual_rect.bottom - virtual_rect.top) / (y_rel->max - y_rel->min); + + raw_dy = raw_val; } } @@ -1793,10 +1807,30 @@ static BOOL X11DRV_RawMotion( XGenericEventCookie *xev ) return FALSE; } - TRACE( "pos %d,%d (event %f,%f)\n", input.u.mi.dx, input.u.mi.dy, dx, dy ); + raw_input.data.mouse.lLastX = raw_dx; + raw_input.data.mouse.lLastY = raw_dy; + raw_input.data.mouse.u.usButtonFlags = 0; + raw_input.data.mouse.u.usButtonData = 0; + raw_input.data.mouse.ulExtraInformation = 0; + + TRACE("raw event %f,%f\n", raw_dx, raw_dy); + + raw_input.header.dwType = RIM_TYPEMOUSE; + __wine_send_raw_input( &raw_input ); + + if (thread_data->xi2_state == xi_extra) + { + input.u.mi.mouseData = 0; + input.u.mi.dwFlags = MOUSEEVENTF_MOVE; + input.u.mi.time = EVENT_x11_time_to_win32_time( event->time ); + input.u.mi.dwExtraInfo = 0; + + TRACE( "pos %d,%d (event %f,%f)\n", input.u.mi.dx, input.u.mi.dy, dx, dy ); + + input.type = INPUT_MOUSE; + __wine_send_input( 0, &input ); + } - input.type = INPUT_MOUSE; - __wine_send_input( 0, &input ); return TRUE; } diff --git a/dlls/winex11.drv/x11drv.h b/dlls/winex11.drv/x11drv.h index a0308b0675..7c9fe7b90b 100644 --- a/dlls/winex11.drv/x11drv.h +++ b/dlls/winex11.drv/x11drv.h @@ -194,6 +194,7 @@ extern BOOL X11DRV_UnrealizePalette( HPALETTE hpal ) DECLSPEC_HIDDEN; extern void X11DRV_Xcursor_Init(void) DECLSPEC_HIDDEN; extern void X11DRV_XInput2_Init(void) DECLSPEC_HIDDEN; +extern void X11DRV_SetupXInput2(void) DECLSPEC_HIDDEN; extern DWORD copy_image_bits( BITMAPINFO *info, BOOL is_r8g8b8, XImage *image, const struct gdi_image_bits *src_bits, struct gdi_image_bits *dst_bits, @@ -335,7 +336,7 @@ struct x11drv_thread_data HWND clip_hwnd; /* message window stored in desktop while clipping is active */ DWORD clip_reset; /* time when clipping was last reset */ HKL kbd_layout; /* active keyboard layout */ - enum { xi_unavailable = -1, xi_unknown, xi_disabled, xi_enabled } xi2_state; /* XInput2 state */ + enum { xi_unavailable = -1, xi_unknown, xi_disabled, xi_enabled, xi_extra } xi2_state; /* XInput2 state */ void *xi2_devices; /* list of XInput2 devices (valid when state is enabled) */ int xi2_device_count; struct x11drv_valuator_data x_rel_valuator; diff --git a/dlls/winex11.drv/x11drv_main.c b/dlls/winex11.drv/x11drv_main.c index e67a3c05a9..66f6d1dcfc 100644 --- a/dlls/winex11.drv/x11drv_main.c +++ b/dlls/winex11.drv/x11drv_main.c @@ -679,6 +679,7 @@ struct x11drv_thread_data *x11drv_init_thread_data(void) TlsSetValue( thread_data_tls_index, data ); if (use_xim) X11DRV_SetupXIM(); + X11DRV_SetupXInput2(); return data; } diff --git a/include/winuser.h b/include/winuser.h index 3cffaa19ac..5d8774b6e6 100644 --- a/include/winuser.h +++ b/include/winuser.h @@ -4354,6 +4354,7 @@ WORD WINAPI SYSTEM_KillSystemTimer( WORD ); #ifdef __WINESRC__ WINUSERAPI BOOL CDECL __wine_send_input( HWND hwnd, const INPUT *input ); +WINUSERAPI BOOL CDECL __wine_send_raw_input( const RAWINPUT *raw_input ); #endif #ifdef __cplusplus diff --git a/server/protocol.def b/server/protocol.def index 5adc83db8b..0f641ecf01 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -307,7 +307,8 @@ struct hardware_msg_data int type; /* RIM_TYPEMOUSE */ int x; /* x coordinate */ int y; /* y coordinate */ - unsigned int data; /* mouse data */ + unsigned short button_flags; /* mouse data */ + unsigned short button_data; } mouse; } rawinput; }; @@ -357,6 +358,25 @@ typedef union } hw; } hw_input_t; +typedef union +{ + int type; + struct + { + int type; /* RIM_TYPEMOUSE */ + int x; + int y; + unsigned short button_flags; + unsigned short button_data; + lparam_t info; + } mouse; + struct + { + int type; /* RIM_TYPEKEYBOARD */ + /* TODO: fill this in if/when necessary */ + } kbd; +} hw_rawinput_t; + typedef union { unsigned char bytes[1]; /* raw data for sent messages */ @@ -2293,6 +2313,11 @@ enum message_type #define SEND_HWMSG_INJECTED 0x01 +@REQ(send_rawinput_message) + hw_rawinput_t input; +@END + + /* Get a message from the current queue */ @REQ(get_message) unsigned int flags; /* PM_* flags */ diff --git a/server/queue.c b/server/queue.c index 24239916af..2891caa8e8 100644 --- a/server/queue.c +++ b/server/queue.c @@ -1624,11 +1624,78 @@ static int queue_mouse_message( struct desktop *desktop, user_handle_t win, cons WM_MOUSEHWHEEL /* 0x1000 = MOUSEEVENTF_HWHEEL */ }; + static const unsigned int raw_button_flags[] = { + 0, /* 0x0001 = MOUSEEVENTF_MOVE */ + RI_MOUSE_LEFT_BUTTON_DOWN, /* 0x0002 = MOUSEEVENTF_LEFTDOWN */ + RI_MOUSE_LEFT_BUTTON_UP, /* 0x0004 = MOUSEEVENTF_LEFTUP */ + RI_MOUSE_RIGHT_BUTTON_DOWN, /* 0x0008 = MOUSEEVENTF_RIGHTDOWN */ + RI_MOUSE_RIGHT_BUTTON_UP, /* 0x0010 = MOUSEEVENTF_RIGHTUP */ + RI_MOUSE_MIDDLE_BUTTON_DOWN, /* 0x0020 = MOUSEEVENTF_MIDDLEDOWN */ + RI_MOUSE_MIDDLE_BUTTON_UP, /* 0x0040 = MOUSEEVENTF_MIDDLEUP */ + }; + desktop->cursor.last_change = get_tick_count(); flags = input->mouse.flags; time = input->mouse.time; if (!time) time = desktop->cursor.last_change; + if ((device = current->process->rawinput_mouse)) + { + if (flags & ~MOUSEEVENTF_MOVE) + { + if (!(msg = alloc_hardware_message( input->mouse.info, source, time ))) return 0; + msg_data = msg->data; + + msg->win = device->target; + msg->msg = WM_INPUT; + msg->wparam = RIM_INPUT; + msg->lparam = 0; + + msg_data->flags = 0; + msg_data->rawinput.type = RIM_TYPEMOUSE; + msg_data->rawinput.mouse.x = 0; + msg_data->rawinput.mouse.y = 0; + msg_data->rawinput.mouse.button_flags = 0; + msg_data->rawinput.mouse.button_data = 0; + + for (i = 1; i < ARRAY_SIZE(raw_button_flags); ++i) + { + if (flags & (1 << i)) + msg_data->rawinput.mouse.button_flags |= raw_button_flags[i]; + } + + if (flags & MOUSEEVENTF_WHEEL) + { + msg_data->rawinput.mouse.button_flags |= RI_MOUSE_WHEEL; + msg_data->rawinput.mouse.button_data = input->mouse.data; + } + if (flags & MOUSEEVENTF_HWHEEL) + { + msg_data->rawinput.mouse.button_flags |= RI_MOUSE_HORIZONTAL_WHEEL; + msg_data->rawinput.mouse.button_data = input->mouse.data; + } + if (flags & MOUSEEVENTF_XDOWN) + { + if (input->mouse.data == XBUTTON1) + msg_data->rawinput.mouse.button_flags |= RI_MOUSE_BUTTON_4_DOWN; + else if (input->mouse.data == XBUTTON2) + msg_data->rawinput.mouse.button_flags |= RI_MOUSE_BUTTON_5_DOWN; + } + if (flags & MOUSEEVENTF_XUP) + { + if (input->mouse.data == XBUTTON1) + msg_data->rawinput.mouse.button_flags |= RI_MOUSE_BUTTON_4_UP; + else if (input->mouse.data == XBUTTON2) + msg_data->rawinput.mouse.button_flags |= RI_MOUSE_BUTTON_5_UP; + } + + queue_hardware_message( desktop, msg, 0 ); + } + + if (device->flags & RIDEV_NOLEGACY) + return FALSE; + } + if (flags & MOUSEEVENTF_MOVE) { if (flags & MOUSEEVENTF_ABSOLUTE) @@ -1651,25 +1718,6 @@ static int queue_mouse_message( struct desktop *desktop, user_handle_t win, cons y = desktop->cursor.y; } - if ((device = current->process->rawinput_mouse)) - { - if (!(msg = alloc_hardware_message( input->mouse.info, source, time ))) return 0; - msg_data = msg->data; - - msg->win = device->target; - msg->msg = WM_INPUT; - msg->wparam = RIM_INPUT; - msg->lparam = 0; - - msg_data->flags = flags; - msg_data->rawinput.type = RIM_TYPEMOUSE; - msg_data->rawinput.mouse.x = x - desktop->cursor.x; - msg_data->rawinput.mouse.y = y - desktop->cursor.y; - msg_data->rawinput.mouse.data = input->mouse.data; - - queue_hardware_message( desktop, msg, 0 ); - } - for (i = 0; i < ARRAY_SIZE( messages ); i++) { if (!messages[i]) continue; @@ -2370,6 +2418,47 @@ DECL_HANDLER(send_hardware_message) release_object( desktop ); } +/* send a hardware rawinput message to the queue thread */ +DECL_HANDLER(send_rawinput_message) +{ + const struct rawinput_device *device; + struct hardware_msg_data *msg_data; + struct message *msg; + struct desktop *desktop; + struct hw_msg_source source = { IMDT_MOUSE, IMO_HARDWARE }; + + desktop = get_thread_desktop( current, 0 ); + + switch (req->input.type) + { + case RIM_TYPEMOUSE: + if ((device = current->process->rawinput_mouse)) + { + if (!(msg = alloc_hardware_message( req->input.mouse.info, source, 0 ))) return; + msg_data = msg->data; + + msg->win = device->target; + msg->msg = WM_INPUT; + msg->wparam = RIM_INPUT; + msg->lparam = 0; + + msg_data->flags = 0; + msg_data->rawinput.type = RIM_TYPEMOUSE; + msg_data->rawinput.mouse.x = req->input.mouse.x; + msg_data->rawinput.mouse.y = req->input.mouse.y; + msg_data->rawinput.mouse.button_flags = req->input.mouse.button_flags; + msg_data->rawinput.mouse.button_data = req->input.mouse.button_data; + + queue_hardware_message( desktop, msg, 0 ); + } + break; + default: + set_error( STATUS_INVALID_PARAMETER ); + } + + release_object(desktop); +} + /* post a quit message to the current queue */ DECL_HANDLER(post_quit_message) { diff --git a/server/trace.c b/server/trace.c index 3562823659..97585f4507 100644 --- a/server/trace.c +++ b/server/trace.c @@ -390,6 +390,30 @@ static void dump_hw_input( const char *prefix, const hw_input_t *input ) } } +static void dump_hw_rawinput( const char *prefix, const hw_rawinput_t *rawinput ) +{ + switch (rawinput->type) + { + case RIM_TYPEMOUSE: + fprintf( stderr, "%s{type=MOUSE,x=%d,y=%d,button_flags=%04hx,button_data=%04hx", + prefix, rawinput->mouse.x, rawinput->mouse.y, rawinput->mouse.button_flags, + rawinput->mouse.button_data); + dump_uint64( ",info=", &rawinput->mouse.info ); + fputc( '}', stderr ); + break; + case RIM_TYPEKEYBOARD: + fprintf( stderr, "%s{type=KEYBOARD}\n", prefix); + break; + case RIM_TYPEHID: + fprintf( stderr, "%s{type=HID}\n", prefix); + break; + default: + fprintf( stderr, "%s{type=%04x}", prefix, rawinput->type); + break; + } +} + + static void dump_luid( const char *prefix, const luid_t *luid ) { fprintf( stderr, "%s%d.%u", prefix, luid->high_part, luid->low_part ); diff --git a/tools/make_requests b/tools/make_requests index 367f245653..b293ff13a6 100755 --- a/tools/make_requests +++ b/tools/make_requests @@ -53,6 +53,7 @@ my %formats = "ioctl_code_t" => [ 4, 4, "&dump_ioctl_code" ], "cpu_type_t" => [ 4, 4, "&dump_cpu_type" ], "hw_input_t" => [ 32, 8, "&dump_hw_input" ], + "hw_rawinput_t" => [ 24, 8, "&dump_hw_rawinput" ] ); my @requests = ();