Files
RetroArch/input/input_pointer_frame.h
T
Davey Hughes 56952c9a25 Android: follow-up fixes for screens on other displays (gl and vulkan) (#20044)
* rgui: upload the menu again to the video driver a reinit makes

Closing content with RGUI up left the screen black until a key press.
The staged content load presents frames between its stages, so RGUI
can upload its framebuffer to the old video driver and clear FB_DIRTY
just before the driver stage replaces that driver. The new one has no
menu texture, nothing in the menu changes, and RGUI never uploads
again.

command_event_reinit sets FB_DIRTY after its reinit, but the staged
load and video-only resets reach the menu only through context_reset,
so RGUI marks its framebuffer dirty there. The menu_icon_ownership
harness gets an rgui lane that reinits the video driver and checks
the next upload reaches the new one.

* video: draw a frame queued before a view count shrink with its views

Under threaded video the wrapper ran SET_VIEW_COUNT ahead of the frame
it had already claimed, so a closing core's last queued frame drew
after its view chains were gone: whole, both screens stacked, until the
menu's first frame replaced it.

The wrapper still answers at once, but a smaller count reaches the
driver only once the frames queued before it have drawn. A grow applies
at once as before, with the main thread waiting for the preset parse.
Vulkan and glcore therefore build view chains only when the count
grows, so a deferred shrink never parses a preset. They also keep the
headset's whole-frame chain across a count change: it does not depend
on the count, and with chains built only on a grow nothing would
rebuild it after a shrink to 0.

* android: don't make a second screen's EGL surface once Java took it back

Java can take the second screen's surface back between the poll that
hands it over and the frame that makes its EGL surface. Skip making one
then; the screens go back to the main window as they do when a make
fails. Java can still take the surface back during the make itself; the
node's own ANativeWindow reference keeps the window alive through that.

* android: ask a display move by display id and number it

A move named its display by its place in the list, and Java looked
that up later on the UI thread against whatever list was newest, so a
display coming or going in between could send RetroArch elsewhere.
Java now gets the display id and fails the move when that display is
gone. Native code now counts the ids when it compares display lists,
so a display unplugged and plugged back is a new list and its failures
clear.

A failure was a bare flag, so one move's failure could end the next
move and mark its display unable. Each move now carries a number and a
failure counts only for the move it names. The move code also loses
two stores that were always overwritten.

* android: end the refocus hold after a move only when it is done

A move onto a display that is no longer valid now fails at once
instead of waiting out its timeout. The hold that keeps RetroArch's
window unfocusable after a move ends only on the focus loss Android
answers it with, not on a gain that was already queued; an earlier
hold ends only once the new move has started; and onStop() ends a
running hold rather than leaving it to its timer.

* video_windows: tell an external monitor by its display type

Android lists the AYN Thor's two panels and a DP monitor with the same
unknown connection type, so Auto took the monitor for a second panel:
on a one-screen phone Game Screen 2 went to it. Java now reads the
hidden Display.getType() by reflection where it can and passes
internal or external with each display. A display typed internal is
never a monitor; where the type is unknown, a non-default display with
an unknown connection and a product name now counts as one.

* video: give a Presentation on another display its own UI wait

The menu waits up to a second for the Presentation it is laid out for
to show, but the wait was only counted from the first one. Once a
Presentation had shown, one made on another display had no wait left
and the menu fell back to the main window at once.

* input: keep the menu's mouse where it was while the menu is in a window

With Mouse Support on, the menu moving into the Presentation read as a
mouse move to (0,0) for a frame, which marks the pointer active there.
The view keeps the main window's last mouse position instead.

* input: match a window's touches only from a windowed publication

A publication without windows after one with them left the old window
ids in place, so a window's touch lookup could still match them. Only
touches published in a window match now. No Android path publishes
that way today.

* vulkan: copy a waiting window's views on a new frame

A screen window that was up but had no free image on a new frame did
not get its views copied, so a later repeat that drew it, as a pause
with the menu there does, left black areas where those views go. They
are copied on every new frame now.

* video: load the idle logo from the assets directory without widgets

The idle logo's path came from the widgets, which only start with
On-Screen Notifications on, so with them off the idle window stayed
black. Without the widgets' path it is built from the assets
directory the way the widgets build it.

* android: hold the focus when Android moves RetroArch between displays

When the display RetroArch is on, or moving to, goes away, Android
moves the Activity to the default display and can send that display's
focus gain before the removed display's loss. A task moved to another
display and straight back gets no configuration change, and can read
the old display's loss last too. With the loss last, RetroArch pauses
while Android keeps it focused, and no later focus event wakes it.

Count these as moves, as RetroArch's own moves are: a removal, and any
change of RetroArch's display it did not ask for. From Android 10, with
more than one display, so is losing and regaining the top-resumed place
while focused, or regaining it unfocused within a second of losing it
after a loss no hold asked for. The refocus hold that covers a move's
late loss then covers these too: the window is made unfocusable until
Android has taken the focus, then gets the gain again. A gain already on
its way skips the hold, and a move marked while RetroArch is focused
ends after a second with no focus event.

* samples: close test gaps in video_windows and android_focus

The is_default check set the flag and read it back, so it could not
fail; Auto's fallback to the default display now shows the flag at
work. Cover a display listed without an ordinal, and display keys at
sizes 0 to 3, two unnamed displays and a 63-character name. Drop a
stale note from android_focus_test's header.

* video: fix wording left over from the display rows

The Screen Display sublabel still named the Separate mode, a setting
the display rows removed; it now says "when it has a window of its
own". The warnings for a screen window that can't be drawn, or whose
surface is gone, said its screens go back to the main window, but a
window that shows only the menu reaches them too. They now read like
the one for a window that can't be made: what it shows stays in the
main window.

Comments that no longer matched the code are reworded. pointer_frame's
model of the driver before the publication is named was_* rather than
today_*, and a wrong commit hash and a plan tag go from the tests.
video_driver_free_internal() uses VIDEO_ANDROID_RELEASE_KEPT_WINDOWS()
rather than open-coding it, with no change in behaviour.
2026-11-09 18:59:16 +01:00

757 lines
27 KiB
C

/* RetroArch - A frontend for libretro.
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __INPUT_POINTER_FRAME_H
#define __INPUT_POINTER_FRAME_H
/* What the frontend keeps of the mice and touches an input driver
* publishes at its poll, and how it answers for the mouse, the pointer
* and the lightgun's aim from them. input_driver.c is its one user; it
* is a header so that a test can drive the same code
* (samples/input/pointer_frame). It needs input_driver.h,
* gfx/video_driver.h's viewport calls and <string.h> before it. */
/* Android's 16 slots on the main window and 16 fingers on the second
* display */
#define INPUT_TOUCHES_MAX 32
/* The pointing devices an input driver published at this poll, and
* their places in the viewport, worked out the first time one is asked
* for after the poll and kept until the next. */
static struct
{
input_pointer_frame_t frame[MAX_USERS];
uint32_t confined_pos[MAX_USERS]; /* in the viewport, held to its edges */
uint32_t viewport_pos[MAX_USERS]; /* in the viewport, -0x8000 outside it */
uint32_t screen_pos[MAX_USERS];
uint32_t read_as[MAX_USERS]; /* the device kind last read as */
uint32_t touch_pos[INPUT_TOUCHES_MAX]; /* in the window; 0: none */
uint32_t touch_confined_pos[INPUT_TOUCHES_MAX];
uint32_t touch_screen_pos[INPUT_TOUCHES_MAX];
#ifdef HAVE_VIDEO_WINDOWS
input_video_window_t window[MAX_USERS]; /* where in_window says */
input_video_window_t touch_window[INPUT_TOUCHES_MAX];
#endif
struct video_viewport vp;
#ifdef HAVE_VIDEO_WINDOWS
uint16_t in_window; /* a bit a device: in a screen's own */
uint32_t touch_in_window;
#endif
uint16_t have_confined; /* a bit a device: done this poll */
uint16_t have_viewport;
uint32_t touch_viewport_pos[INPUT_TOUCHES_MAX]; /* -0x8000 outside */
uint32_t have_touch; /* a bit a touch: done this poll */
uint32_t have_touch_viewport;
uint32_t touch_present; /* a bit a touch: a contact is there */
uint32_t touch_down; /* ... and is pressed */
uint8_t count;
uint8_t touch_count;
uint16_t flags; /* enum input_pointers_flags */
bool vp_asked;
bool vp_ok;
} input_pointers;
#ifdef HAVE_VIDEO_WINDOWS
/* @count windows kept, and a bit for each in a screen's own; NULL is
* the main window's. */
static uint32_t input_pointer_windows_set(input_video_window_t *kept,
const input_video_window_t *windows, unsigned count)
{
unsigned i;
uint32_t in_window = 0;
if (!windows)
return 0;
memcpy(kept, windows, count * sizeof(*windows));
for (i = 0; i < count; i++)
if (windows[i].window)
in_window |= (uint32_t)1 << i;
return in_window;
}
#endif
static void input_pointer_frames_set(const input_pointer_frame_t *frames,
const input_video_window_t *windows, unsigned count, unsigned flags)
{
if (count > MAX_USERS)
count = MAX_USERS;
/* written in place by a driver the frames were lent to */
if (frames != input_pointers.frame)
memcpy(input_pointers.frame, frames, count * sizeof(*frames));
#ifdef HAVE_VIDEO_WINDOWS
input_pointers.in_window = (uint16_t)input_pointer_windows_set(
input_pointers.window, windows, count);
#else
(void)windows;
#endif
input_pointers.count = (uint8_t)count;
input_pointers.flags = (uint16_t)flags;
input_pointers.have_confined = 0;
input_pointers.have_viewport = 0;
input_pointers.vp_asked = false;
}
/* Where a touch is, whether there is one, and whether it is down are
* three things. A place can hold a contact that has lifted - the last
* place of a finger, a pen in the air - and a contact can be at 0,0,
* the window's corner: neither is told by the position. */
static void input_pointer_touches_set(const uint32_t *pos,
const input_video_window_t *windows, unsigned count,
unsigned present, unsigned down)
{
uint32_t all;
if (count > INPUT_TOUCHES_MAX)
count = INPUT_TOUCHES_MAX;
all = (count < 32) ? (((uint32_t)1 << count) - 1) : 0xffffffffu;
/* written in place by a driver the places were lent to */
if (pos != input_pointers.touch_pos)
memcpy(input_pointers.touch_pos, pos, count * sizeof(*pos));
#ifdef HAVE_VIDEO_WINDOWS
input_pointers.touch_in_window = input_pointer_windows_set(
input_pointers.touch_window, windows, count);
#else
(void)windows;
#endif
input_pointers.touch_count = (uint8_t)count;
input_pointers.touch_present = (uint32_t)present & all;
input_pointers.touch_down = (uint32_t)down & (uint32_t)present & all;
input_pointers.have_touch = 0;
input_pointers.have_touch_viewport = 0;
}
static bool input_pointer_viewport(void)
{
if (!input_pointers.vp_asked)
{
memset(&input_pointers.vp, 0, sizeof(input_pointers.vp));
input_pointers.vp_ok = video_driver_get_viewport_info(&input_pointers.vp);
input_pointers.vp_asked = true;
}
return input_pointers.vp_ok;
}
/* The published device a port reads as @device. */
static unsigned input_pointer_of_port(unsigned port, unsigned device)
{
if (input_pointers.flags & INPUT_POINTERS_BY_MOUSE_INDEX)
{
if ( (input_pointers.flags & INPUT_POINTERS_AIM_EVERY_PORT)
&& device != RETRO_DEVICE_MOUSE
&& device != RARCH_DEVICE_MOUSE_SCREEN)
return 0;
return input_mouse_port_index(port);
}
return (input_pointers.count == 1) ? 0 : port;
}
#ifdef HAVE_VIDEO_WINDOWS
/* @pos in a screen's own window, through the layout it last published:
* off-screen once the window is gone, and nowhere on the main window's
* screen. */
static void input_pointer_window_place(const input_video_window_t *w,
uint32_t pos, bool confined, uint32_t *place, uint32_t *screen_pos)
{
*screen_pos = VIDEO_POS_PACK(-0x8000, -0x8000);
if (!video_driver_translate_window_point(w->window, w->generation,
VIDEO_POS_X(pos), VIDEO_POS_Y(pos), !confined, place))
*place = VIDEO_POS_PACK(-0x8000, -0x8000);
}
#endif
/* Device @i's place in the viewport, or its own window's, and on the
* screen; false when there is no viewport to place it in. */
static bool input_pointer_place(unsigned i, bool confined,
uint32_t *pos, uint32_t *screen_pos)
{
uint16_t bit = (uint16_t)(1 << i);
uint16_t *have = confined
? &input_pointers.have_confined : &input_pointers.have_viewport;
#ifdef HAVE_VIDEO_WINDOWS
bool in_window = (input_pointers.in_window & bit) != 0;
#else
bool in_window = false;
#endif
if (!in_window && !input_pointer_viewport())
return false;
if (!(*have & bit))
{
uint32_t place = 0;
#ifdef HAVE_VIDEO_WINDOWS
if (in_window)
input_pointer_window_place(&input_pointers.window[i],
input_pointers.frame[i].pos, confined,
&place, &input_pointers.screen_pos[i]);
else
#endif
if (!video_driver_translate_coord_viewport(&input_pointers.vp,
VIDEO_POS_X(input_pointers.frame[i].pos),
VIDEO_POS_Y(input_pointers.frame[i].pos),
&place, &input_pointers.screen_pos[i], !confined))
{
input_pointers.vp_ok = false;
return false;
}
if (confined)
input_pointers.confined_pos[i] = place;
else
input_pointers.viewport_pos[i] = place;
*have |= bit;
}
*pos = confined
? input_pointers.confined_pos[i] : input_pointers.viewport_pos[i];
*screen_pos = input_pointers.screen_pos[i];
return true;
}
/* Touch @t's place in the viewport, or its own window's - held to its
* edges, or -0x8000 outside it - and on the screen; false when there is
* no viewport. */
static bool input_touch_place(unsigned t, bool confined,
uint32_t *pos, uint32_t *screen_pos)
{
uint32_t bit = (uint32_t)1 << t;
uint32_t *have = confined
? &input_pointers.have_touch : &input_pointers.have_touch_viewport;
uint32_t *kept = confined
? &input_pointers.touch_confined_pos[t]
: &input_pointers.touch_viewport_pos[t];
#ifdef HAVE_VIDEO_WINDOWS
bool in_window = (input_pointers.touch_in_window & bit) != 0;
#else
bool in_window = false;
#endif
if (!in_window && !input_pointer_viewport())
return false;
if (!(*have & bit))
{
#ifdef HAVE_VIDEO_WINDOWS
if (in_window)
input_pointer_window_place(&input_pointers.touch_window[t],
input_pointers.touch_pos[t], confined,
kept, &input_pointers.touch_screen_pos[t]);
else
#endif
if (!video_driver_translate_coord_viewport(&input_pointers.vp,
VIDEO_POS_X(input_pointers.touch_pos[t]),
VIDEO_POS_Y(input_pointers.touch_pos[t]),
kept, &input_pointers.touch_screen_pos[t], !confined))
{
input_pointers.vp_ok = false;
return false;
}
*have |= bit;
}
*pos = *kept;
*screen_pos = input_pointers.touch_screen_pos[t];
return true;
}
/* Whether touch @t is the second list's (@second) or the main one's:
* with WINDOWS_SECOND by its window, else every touch is the main
* list's. */
static bool input_touch_in_list(unsigned t, bool second)
{
#ifdef HAVE_VIDEO_WINDOWS
if (input_pointers.flags & INPUT_POINTERS_WINDOWS_SECOND)
return ((input_pointers.touch_in_window >> t) & 1) == (second ? 1u : 0u);
#else
(void)t;
#endif
return !second;
}
/* The slot of index @idx of a list, present or not; -1 for none. */
static int input_touch_slot(unsigned idx, bool second)
{
unsigned t;
for (t = 0; t < input_pointers.touch_count; t++)
{
if (!input_touch_in_list(t, second))
continue;
if (!idx)
return (int)t;
idx--;
}
return -1;
}
/* Whether touch @t is inside what it is read against: the viewport or
* its window's screens, or (@screen) the main window. */
static bool input_touch_inside(unsigned t, bool screen)
{
uint32_t pos;
uint32_t screen_pos;
if (!input_touch_place(t, false, &pos, &screen_pos))
return false;
if (screen)
pos = screen_pos;
return VIDEO_POS_X(pos) != -0x8000 && VIDEO_POS_Y(pos) != -0x8000;
}
/* Device @i's place in the main window, as published; none for one in
* a screen's own window. */
static uint32_t input_pointer_screen_place(unsigned i)
{
#ifdef HAVE_VIDEO_WINDOWS
if (input_pointers.in_window & (1 << i))
return VIDEO_POS_PACK(-0x8000, -0x8000);
#endif
return input_pointers.frame[i].pos;
}
/* Whether mouse @f, standing for touch @idx, is down. */
static bool input_pointer_mouse_down(const input_pointer_frame_t *f,
unsigned idx)
{
if (!(input_pointers.flags & INPUT_POINTERS_MOUSE_3_TOUCHES))
return (f->buttons & INPUT_POINTER_LEFT) != 0;
if (idx == 0)
return (f->buttons & (INPUT_POINTER_LEFT
| INPUT_POINTER_RIGHT | INPUT_POINTER_MIDDLE)) != 0;
if (idx == 1)
return (f->buttons & (INPUT_POINTER_RIGHT
| INPUT_POINTER_MIDDLE)) != 0;
return (f->buttons & INPUT_POINTER_MIDDLE) != 0;
}
/* Pointer @idx of mouse @i: the touch in slot @slot (-1: none) if
* there is one, else the mouse standing for it - if the port has a
* mouse, the list is the main one and the driver lets it stand in.
* False if there is neither, or no viewport to place it in. */
static bool input_pointer_touch(unsigned i, unsigned idx, bool second,
int slot, uint32_t *pos, uint32_t *screen_pos, bool *down)
{
const input_pointer_frame_t *f = &input_pointers.frame[
i < input_pointers.count ? i : 0];
bool real = slot >= 0
&& (input_pointers.touch_present & ((uint32_t)1 << slot));
bool three = (input_pointers.flags & INPUT_POINTERS_MOUSE_3_TOUCHES) != 0;
bool mouse = !second
&& !(input_pointers.flags & INPUT_POINTERS_TOUCH_SLOTS)
&& i < input_pointers.count && idx < (three ? 3u : 1u);
bool confined = !(input_pointers.flags & INPUT_POINTERS_POINTER_OFFSCREEN);
if (!real && !mouse)
return false;
if (real
? !input_touch_place((unsigned)slot, confined, pos, screen_pos)
: !input_pointer_place(i, confined, pos, screen_pos))
return false;
*down = real && (input_pointers.touch_down & ((uint32_t)1 << slot));
if ( !*down && mouse
&& !(real && (input_pointers.flags & INPUT_POINTERS_TOUCH_ALONE)))
*down = input_pointer_mouse_down(f, idx);
return true;
}
/* How many pointers of a list are down: its touches that are, or the
* mouse standing for one. */
static unsigned input_pointer_count(unsigned i, bool second)
{
unsigned n = 0;
unsigned t;
int first;
for (t = 0; t < input_pointers.touch_count; t++)
if ( input_touch_in_list(t, second)
&& (input_pointers.touch_down & ((uint32_t)1 << t)))
n++;
if (n || second || (input_pointers.flags & INPUT_POINTERS_TOUCH_SLOTS))
return n;
first = input_touch_slot(0, false);
if ( (input_pointers.flags & INPUT_POINTERS_TOUCH_ALONE)
&& first >= 0
&& (input_pointers.touch_present & ((uint32_t)1 << first)))
return 0;
if (i < input_pointers.count)
return input_pointer_mouse_down(&input_pointers.frame[i], 0) ? 1 : 0;
return 0;
}
/* Where the lightgun of device @i, read at @idx, aims: at the main
* list's first touch, for a driver that has it so and while there is
* one - with TOUCH_SLOTS at touch @idx, and only there - else at the
* mouse. */
static bool input_gun_place(unsigned i, unsigned idx, uint32_t *pos)
{
uint32_t screen_pos;
if (input_pointers.flags & INPUT_POINTERS_GUN_AT_TOUCH)
{
bool slots = (input_pointers.flags & INPUT_POINTERS_TOUCH_SLOTS) != 0;
int slot = input_touch_slot(slots ? idx : 0, false);
if ( slot >= 0
&& (input_pointers.touch_present & ((uint32_t)1 << slot)))
return input_touch_place((unsigned)slot, false, pos, &screen_pos);
if (slots)
return false;
}
if (i >= input_pointers.count)
return false;
return input_pointer_place(i, false, pos, &screen_pos);
}
/* A mouse's, a pointer's or a lightgun aim's position, packed. False if
* the frontend does not hold it and the driver is to be asked. */
static bool input_pointer_frame_pos(unsigned port, unsigned device,
unsigned idx, uint32_t *pos)
{
unsigned i;
uint32_t screen_pos;
unsigned flags = input_pointers.flags;
if (!input_pointers.count)
return false;
if ( (flags & INPUT_POINTERS_MOUSE_BY_DRIVER)
&& ( device == RETRO_DEVICE_MOUSE
|| device == RARCH_DEVICE_MOUSE_SCREEN
|| ( device == RETRO_DEVICE_LIGHTGUN
&& !(flags & INPUT_POINTERS_GUN_AT_TOUCH))))
return false;
*pos = 0;
i = input_pointer_of_port(port, device);
/* count never exceeds MAX_USERS (input_pointer_frames_set clamps
* it); the second test says so to the compiler. */
if (i < input_pointers.count && i < MAX_USERS)
input_pointers.read_as[i] = device;
else if ( device != RETRO_DEVICE_POINTER
&& device != RARCH_DEVICE_POINTER_SCREEN)
return true; /* no mouse here; a pointer may still be a touch */
switch (device)
{
case RETRO_DEVICE_MOUSE:
*pos = input_pointers.frame[i].rel;
return true;
case RARCH_DEVICE_MOUSE_SCREEN:
*pos = input_pointer_screen_place(i);
return true;
case RETRO_DEVICE_POINTER:
case RARCH_DEVICE_POINTER_SCREEN:
{
uint32_t vp_pos = 0;
bool down;
screen_pos = 0;
input_pointer_touch(i, idx, false, input_touch_slot(idx, false),
&vp_pos, &screen_pos, &down);
*pos = (device == RETRO_DEVICE_POINTER) ? vp_pos : screen_pos;
}
return true;
case RETRO_DEVICE_LIGHTGUN:
input_gun_place(i, idx, pos);
return true;
default:
break;
}
return false;
}
#ifdef HAVE_OVERLAY
/* Finger @idx of the pointer @port reads as @device (RETRO_DEVICE_POINTER
* or RARCH_DEVICE_POINTER_SCREEN), down or not and where, packed, at one
* look: what reading PRESSED, X and Y in turn gives. 1 down, 0 not; -1
* if the frontend does not hold it and the driver is to be asked. */
static int input_pointer_frame_finger(unsigned port, unsigned device,
unsigned idx, uint32_t *pos)
{
unsigned i;
int slot;
bool real;
uint32_t vp_pos = 0;
uint32_t screen_pos = 0;
bool screen = (device == RARCH_DEVICE_POINTER_SCREEN);
bool down;
if (!input_pointers.count)
return -1;
*pos = 0;
i = input_pointer_of_port(port, device);
if (i < input_pointers.count && i < MAX_USERS)
input_pointers.read_as[i] = device;
slot = input_touch_slot(idx, false);
real = slot >= 0
&& (input_pointers.touch_present & ((uint32_t)1 << slot));
if (!input_pointer_touch(i, idx, false, slot, &vp_pos, &screen_pos,
&down))
return 0;
*pos = screen ? screen_pos : vp_pos;
return (down
&& ( !real
|| !(input_pointers.flags & INPUT_POINTERS_PRESSED_INSIDE)
|| input_touch_inside((unsigned)slot, screen))) ? 1 : 0;
}
#endif
/* One value of a mouse, a pointer or a lightgun's aim. False if the
* frontend does not hold it and the driver is to be asked. */
static bool input_pointer_frame_read(unsigned port, unsigned device,
unsigned idx, unsigned id, int16_t *out)
{
const input_pointer_frame_t *f;
unsigned i;
uint32_t pos;
unsigned flags = input_pointers.flags;
if (!input_pointers.count)
return false;
switch (device)
{
case RETRO_DEVICE_MOUSE:
case RARCH_DEVICE_MOUSE_SCREEN:
if (flags & INPUT_POINTERS_MOUSE_BY_DRIVER)
return false;
break;
case RARCH_DEVICE_POINTER_SECOND:
if (!(flags & INPUT_POINTERS_WINDOWS_SECOND))
return false;
/* fall through */
case RETRO_DEVICE_POINTER:
case RARCH_DEVICE_POINTER_SCREEN:
if ( (flags & INPUT_POINTERS_POINTER_IDS_BY_DRIVER)
&& id != RETRO_DEVICE_ID_POINTER_X
&& id != RETRO_DEVICE_ID_POINTER_Y
&& id != RETRO_DEVICE_ID_POINTER_PRESSED
&& id != RETRO_DEVICE_ID_POINTER_IS_OFFSCREEN
&& id != RETRO_DEVICE_ID_POINTER_COUNT)
return false;
break;
case RETRO_DEVICE_LIGHTGUN:
/* its aim; its buttons are binds, and the driver's */
if ( id == RETRO_DEVICE_ID_LIGHTGUN_X
|| id == RETRO_DEVICE_ID_LIGHTGUN_Y)
{
if (flags & INPUT_POINTERS_MOUSE_BY_DRIVER)
return false;
break;
}
if ( id == RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X
|| id == RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y)
{
if ( (flags & INPUT_POINTERS_MOUSE_BY_DRIVER)
&& !(flags & INPUT_POINTERS_GUN_AT_TOUCH))
return false;
break;
}
if (id == RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN)
break;
return false;
default:
return false;
}
*out = 0;
i = input_pointer_of_port(port, device);
/* count never exceeds MAX_USERS (input_pointer_frames_set clamps
* it); the second test says so to the compiler. */
if (i < input_pointers.count && i < MAX_USERS)
input_pointers.read_as[i] = device;
else if ( device != RETRO_DEVICE_POINTER
&& device != RARCH_DEVICE_POINTER_SCREEN
&& device != RARCH_DEVICE_POINTER_SECOND)
return true; /* no mouse here; a pointer may still be a touch */
f = &input_pointers.frame[i < input_pointers.count ? i : 0];
switch (device)
{
case RETRO_DEVICE_MOUSE:
case RARCH_DEVICE_MOUSE_SCREEN:
pos = (device == RETRO_DEVICE_MOUSE)
? f->rel : input_pointer_screen_place(i);
switch (id)
{
case RETRO_DEVICE_ID_MOUSE_X:
*out = VIDEO_POS_X(pos);
break;
case RETRO_DEVICE_ID_MOUSE_Y:
*out = VIDEO_POS_Y(pos);
break;
case RETRO_DEVICE_ID_MOUSE_LEFT:
*out = (f->buttons & INPUT_POINTER_LEFT) != 0;
break;
case RETRO_DEVICE_ID_MOUSE_RIGHT:
*out = (f->buttons & INPUT_POINTER_RIGHT) != 0;
break;
case RETRO_DEVICE_ID_MOUSE_MIDDLE:
*out = (f->buttons & INPUT_POINTER_MIDDLE) != 0;
break;
case RETRO_DEVICE_ID_MOUSE_BUTTON_4:
*out = (f->buttons & INPUT_POINTER_BUTTON_4) != 0;
break;
case RETRO_DEVICE_ID_MOUSE_BUTTON_5:
*out = (f->buttons & INPUT_POINTER_BUTTON_5) != 0;
break;
case RETRO_DEVICE_ID_MOUSE_WHEELUP:
*out = (f->buttons & INPUT_POINTER_WHEEL_UP) != 0;
break;
case RETRO_DEVICE_ID_MOUSE_WHEELDOWN:
*out = (f->buttons & INPUT_POINTER_WHEEL_DOWN) != 0;
break;
case RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELUP:
*out = (f->buttons & INPUT_POINTER_HWHEEL_UP) != 0;
break;
case RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN:
*out = (f->buttons & INPUT_POINTER_HWHEEL_DOWN) != 0;
break;
default:
break;
}
break;
case RETRO_DEVICE_POINTER:
case RARCH_DEVICE_POINTER_SCREEN:
case RARCH_DEVICE_POINTER_SECOND:
{
uint32_t screen_pos;
bool down;
bool second = (device == RARCH_DEVICE_POINTER_SECOND);
bool screen = (device == RARCH_DEVICE_POINTER_SCREEN);
int slot;
bool real;
/* how many are down needs no place for them */
if (id == RETRO_DEVICE_ID_POINTER_COUNT)
{
*out = (int16_t)input_pointer_count(i, second);
break;
}
slot = input_touch_slot(idx, second);
real = slot >= 0
&& (input_pointers.touch_present & ((uint32_t)1 << slot));
if (!input_pointer_touch(i, idx, second, slot,
&pos, &screen_pos, &down))
break;
if (screen)
pos = screen_pos;
switch (id)
{
case RETRO_DEVICE_ID_POINTER_X:
*out = VIDEO_POS_X(pos);
break;
case RETRO_DEVICE_ID_POINTER_Y:
*out = VIDEO_POS_Y(pos);
break;
case RETRO_DEVICE_ID_POINTER_PRESSED:
*out = down
&& ( !real
|| !(flags & INPUT_POINTERS_PRESSED_INSIDE)
|| input_touch_inside((unsigned)slot, screen));
break;
case RETRO_DEVICE_ID_POINTER_IS_OFFSCREEN:
/* the touch's own place in the viewport */
if (real && (flags & INPUT_POINTERS_TOUCH_SLOTS))
input_touch_place((unsigned)slot, false,
&pos, &screen_pos);
*out = input_driver_pointer_is_offscreen(
VIDEO_POS_X(pos), VIDEO_POS_Y(pos));
break;
default:
break;
}
}
break;
case RETRO_DEVICE_LIGHTGUN:
if ( id == RETRO_DEVICE_ID_LIGHTGUN_X
|| id == RETRO_DEVICE_ID_LIGHTGUN_Y)
{
/* deprecated: the mouse's motion */
*out = (id == RETRO_DEVICE_ID_LIGHTGUN_X)
? VIDEO_POS_X(f->rel) : VIDEO_POS_Y(f->rel);
}
else if ( id == RETRO_DEVICE_ID_LIGHTGUN_IS_OFFSCREEN
&& (flags & INPUT_POINTERS_TOUCH_SLOTS))
{
/* the touch at its index, whatever aims the gun */
uint32_t screen_pos;
int slot = input_touch_slot(idx, false);
if ( slot >= 0
&& (input_pointers.touch_present & ((uint32_t)1 << slot))
&& input_touch_place((unsigned)slot, false,
&pos, &screen_pos))
*out = input_driver_pointer_is_offscreen(
VIDEO_POS_X(pos), VIDEO_POS_Y(pos));
}
else
{
if (!input_gun_place(i, idx, &pos))
break;
if (id == RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X)
*out = VIDEO_POS_X(pos);
else if (id == RETRO_DEVICE_ID_LIGHTGUN_SCREEN_Y)
*out = VIDEO_POS_Y(pos);
else
*out = input_driver_pointer_is_offscreen(
VIDEO_POS_X(pos), VIDEO_POS_Y(pos));
}
break;
default:
break;
}
return true;
}
#ifdef HAVE_VIDEO_WINDOWS
/* @v of @size pixels as a screen place: pixel 0 at -0x7fff, the last
* at 0x7fff, outside -0x8000, as video_driver_translate_coord_viewport()
* scales over the screen. */
static int input_window_scale(int v, int size)
{
if (v > 0 && v < size)
return ((v * 0xffff) / (size - 1)) - 0x8000;
if (v == 0)
return -0x7fff;
return -0x8000;
}
/* Touch @idx of those present in window @window, over that window's
* client area as it last drew; false if there is none or the window
* has not shown. */
static bool input_pointer_window_touch(unsigned window, unsigned idx,
uint32_t *screen_pos, bool *down)
{
unsigned t;
unsigned dims = 0;
int w, h;
if (!window || !video_driver_window_shown(window, &dims))
return false;
w = (int)VIDEO_SCALE_W(dims);
h = (int)VIDEO_SCALE_H(dims);
if (w <= 0 || h <= 0)
return false;
for (t = 0; t < input_pointers.touch_count; t++)
{
if ( !(input_pointers.touch_present & ((uint32_t)1 << t))
|| !(input_pointers.touch_in_window & ((uint32_t)1 << t))
|| input_pointers.touch_window[t].window != window)
continue;
if (idx)
{
idx--;
continue;
}
*screen_pos = VIDEO_POS_PACK(
input_window_scale(VIDEO_POS_X(input_pointers.touch_pos[t]), w),
input_window_scale(VIDEO_POS_Y(input_pointers.touch_pos[t]), h));
*down = (input_pointers.touch_down & ((uint32_t)1 << t)) != 0;
return true;
}
return false;
}
#endif
/* Before a driver's poll: one that publishes nothing is asked itself. */
static void input_pointer_frames_clear(void)
{
input_pointers.count = 0;
input_pointers.touch_count = 0;
/* and the viewport is asked for afresh, by whoever wants it first */
input_pointers.vp_asked = false;
}
#endif