Compare commits

...

3 Commits

Author SHA1 Message Date
bicarus 5cabd026ae misc: various performance clean up (#844)
## Link to GitHub Issue or related Pull Request, if one exists
n/a

## Description of change

- Replace unnecessary precision timers with standard sleeps to reduce
wakeups and background CPU usage.
- Reuse buffers in raw input, touchscreen, and HID output paths to
eliminate steady-state allocations.
- Pre-index HID button groups and correctly process batched HID reports.

No functional changes.

## Testing
2026-07-28 21:05:32 -07:00
bicarus 7ccd938f9b overlay: mouse-as-touch needs to check if subscreen window is active (#842)
## Link to GitHub Issue or related Pull Request, if one exists
Fixes #840 

## Description of change
Currently, for subscreen games, mouse events are still delivered even
when the subscreen overlay window is not visible.

Change this so that the subscreen overlay must be active and under the
mouse cursor for the mouse-to-touch transformation to occur. Applies to
both native and wintouchemu.

## Testing
Needs to test everything again..

iidx:

- [ ]  full screen with overlay
- [ ]  single window with overlay
- [ ]  two window

Test: mouse, poke, api, real touch screen

sdvx:

- [ ] full screen with overlay
- [ ] windowed

popn

- [x] full screen with overlay
- [x] window with overlay
- [x] dedicated window

gitadora

- [x] single window with overlay
- [x] dedicated sub window

nostalgia

- [x] fullscreen
- [x] windowed

test: poke

wintouchemu
- [ ] Do all of the above again with wintouchemu
2026-07-28 01:14:47 -07:00
bicarus 88a210e82e iidx: camera hook improvements (#841)
## Link to GitHub Issue or related Pull Request, if one exists
#201

## Description of change

- Adds MJPEG camera support using Media Foundation decoding.
- Moves camera capture to asynchronous Source Reader callbacks.
- Supports native NV12 and YUY2 capture, preferring target-sized native
formats over MJPEG when possible.
- Improves capture and rendering performance through callback-paced
reads, bulk frame uploads, optimized row-based flips, and lazy
allocation of flip textures.
- Makes runtime media-type changes interrupt pending reads and safely
flush and reconfigure the Source Reader.
- Improves automatic mode selection based on aspect ratio, proximity to
1280x720, frame rate, and capture format.
- Hardens camera shutdown, flush handling, media-type changes, and
repeated capture failures.

## Testing
Tested with two cameras in tdj
2026-07-27 08:42:46 -07:00
26 changed files with 1117 additions and 332 deletions
+4 -4
View File
@@ -1,13 +1,14 @@
#include "keypads.h" #include "keypads.h"
#include <chrono>
#include <functional> #include <functional>
#include <thread>
#include <windows.h> #include <windows.h>
#include "avs/game.h" #include "avs/game.h"
#include "external/rapidjson/document.h" #include "external/rapidjson/document.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/precise_timer.h"
using namespace std::placeholders; using namespace std::placeholders;
using namespace rapidjson; using namespace rapidjson;
@@ -59,7 +60,6 @@ namespace api::modules {
// get params // get params
auto keypad = req.params[0].GetUint(); auto keypad = req.params[0].GetUint();
auto input = std::string(req.params[1].GetString()); auto input = std::string(req.params[1].GetString());
timeutils::PreciseSleepTimer timer;
// process all chars // process all chars
for (auto c : input) { for (auto c : input) {
@@ -93,11 +93,11 @@ namespace api::modules {
// set // set
eamuse_set_keypad_overrides(keypad, state); eamuse_set_keypad_overrides(keypad, state);
timer.sleep(sleep_time); std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
// unset // unset
eamuse_set_keypad_overrides(keypad, 0); eamuse_set_keypad_overrides(keypad, 0);
timer.sleep(sleep_time); std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
} }
} }
+3 -3
View File
@@ -1,11 +1,12 @@
#include "controller.h" #include "controller.h"
#include "serial.h" #include "serial.h"
#include <chrono>
#include <string> #include <string>
#include <thread>
#include <utility> #include <utility>
#include "util/logging.h" #include "util/logging.h"
#include "util/precise_timer.h"
#include "util/utils.h" #include "util/utils.h"
@@ -17,7 +18,6 @@ namespace api {
controller->init_state(this->state); controller->init_state(this->state);
this->thread = new std::thread([this] () { this->thread = new std::thread([this] () {
log_warning("api::serial", "listening on {} (baud: {})", this->port, this->baud); log_warning("api::serial", "listening on {} (baud: {})", this->port, this->baud);
timeutils::PreciseSleepTimer timer;
// read buffer // read buffer
uint8_t read_buffer[16*1024]; uint8_t read_buffer[16*1024];
@@ -162,7 +162,7 @@ namespace api {
// slow down on reconnect // slow down on reconnect
if (this->running) { if (this->running) {
timer.sleep(retry_time); std::this_thread::sleep_for(std::chrono::milliseconds(retry_time));
} }
} }
}); });
+10 -8
View File
@@ -534,7 +534,7 @@ namespace games::iidx {
if (mediaTypePointer && mediaTypePointer->IsString()) { if (mediaTypePointer && mediaTypePointer->IsString()) {
std::string mediaType = mediaTypePointer->GetString(); std::string mediaType = mediaTypePointer->GetString();
if (mediaType.length() > 0) { if (mediaType.length() > 0) {
camera->m_selectedMediaTypeDescription = mediaType; camera->SetSelectedMediaTypeDescription(mediaType);
camera->m_useAutoMediaType = false; camera->m_useAutoMediaType = false;
} else { } else {
camera->m_useAutoMediaType = true; camera->m_useAutoMediaType = true;
@@ -547,7 +547,7 @@ namespace games::iidx {
std::string drawModeString = drawModePointer->GetString(); std::string drawModeString = drawModePointer->GetString();
for (int j = 0; j < DRAW_MODE_SIZE; j++) { for (int j = 0; j < DRAW_MODE_SIZE; j++) {
if (DRAW_MODE_LABELS[j].compare(drawModeString) == 0) { if (DRAW_MODE_LABELS[j].compare(drawModeString) == 0) {
camera->m_drawMode = (LocalCameraDrawMode) j; camera->m_drawMode.store((LocalCameraDrawMode) j);
break; break;
} }
} }
@@ -556,12 +556,12 @@ namespace games::iidx {
// Flip // Flip
auto flipHorizontalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipHorizontal").Get(doc); auto flipHorizontalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipHorizontal").Get(doc);
if (flipHorizontalPointer && flipHorizontalPointer->IsBool()) { if (flipHorizontalPointer && flipHorizontalPointer->IsBool()) {
camera->m_flipHorizontal = flipHorizontalPointer->GetBool(); camera->m_flipHorizontal.store(flipHorizontalPointer->GetBool());
} }
auto flipVerticalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipVertical").Get(doc); auto flipVerticalPointer = rapidjson::Pointer(root + "/" + symLink + "/FlipVertical").Get(doc);
if (flipVerticalPointer && flipVerticalPointer->IsBool()) { if (flipVerticalPointer && flipVerticalPointer->IsBool()) {
camera->m_flipVertical = flipVerticalPointer->GetBool(); camera->m_flipVertical.store(flipVerticalPointer->GetBool());
} }
// Allow manual control // Allow manual control
@@ -625,15 +625,17 @@ namespace games::iidx {
if (camera->m_useAutoMediaType) { if (camera->m_useAutoMediaType) {
rapidjson::Pointer(root + "MediaType").Set(doc, ""); rapidjson::Pointer(root + "MediaType").Set(doc, "");
} else { } else {
rapidjson::Pointer(root + "MediaType").Set(doc, camera->m_selectedMediaTypeDescription); const auto mediaTypeDescription = camera->GetSelectedMediaTypeDescription();
rapidjson::Pointer(root + "MediaType").Set(doc, mediaTypeDescription);
} }
// Draw Mode // Draw Mode
rapidjson::Pointer(root + "DrawMode").Set(doc, DRAW_MODE_LABELS[camera->m_drawMode]); const auto drawMode = camera->m_drawMode.load();
rapidjson::Pointer(root + "DrawMode").Set(doc, DRAW_MODE_LABELS[drawMode]);
// Flip // Flip
rapidjson::Pointer(root + "FlipHorizontal").Set(doc, camera->m_flipHorizontal); rapidjson::Pointer(root + "FlipHorizontal").Set(doc, camera->m_flipHorizontal.load());
rapidjson::Pointer(root + "FlipVertical").Set(doc, camera->m_flipVertical); rapidjson::Pointer(root + "FlipVertical").Set(doc, camera->m_flipVertical.load());
// Manual control // Manual control
rapidjson::Pointer(root + "AllowManualControl").Set(doc, camera->m_allowManualControl); rapidjson::Pointer(root + "AllowManualControl").Set(doc, camera->m_allowManualControl);
File diff suppressed because it is too large Load Diff
+36 -11
View File
@@ -2,6 +2,7 @@
#if SPICE64 && !SPICE_XP #if SPICE64 && !SPICE_XP
#include <atomic>
#include <d3d9.h> #include <d3d9.h>
#include <dxva2api.h> #include <dxva2api.h>
#include <mutex> #include <mutex>
@@ -66,6 +67,8 @@ extern std::string CAMERA_CONTROL_LABELS[];
extern std::string DRAW_MODE_LABELS[]; extern std::string DRAW_MODE_LABELS[];
namespace games::iidx { namespace games::iidx {
class IIDXCameraSourceReaderCallback;
namespace Camera { namespace Camera {
struct PlayVideoCamera { struct PlayVideoCamera {
IDirect3DTexture9** d3d9_texture(const uintptr_t offset) { IDirect3DTexture9** d3d9_texture(const uintptr_t offset) {
@@ -87,7 +90,7 @@ namespace games::iidx {
class IIDXLocalCamera { class IIDXLocalCamera {
protected: protected:
virtual ~IIDXLocalCamera() {}; virtual ~IIDXLocalCamera();
LONG m_nRefCount; LONG m_nRefCount;
CRITICAL_SECTION m_critsec; CRITICAL_SECTION m_critsec;
@@ -101,9 +104,14 @@ namespace games::iidx {
// For reading frames from Camera // For reading frames from Camera
IMFMediaSource *m_pSource = nullptr; IMFMediaSource *m_pSource = nullptr;
IMFSourceReader *m_pSourceReader = nullptr; IMFSourceReader *m_pSourceReader = nullptr;
IMFSourceReaderEx *m_pSourceReaderEx = nullptr;
IIDXCameraSourceReaderCallback *m_pSourceReaderCallback = nullptr;
std::mutex m_mediaTypeMutex;
IMFMediaType *m_pendingMediaType = nullptr;
int m_selectedMediaTypeIndex = 0;
std::string m_selectedMediaTypeDescription = "";
// Camera Format information // Camera Format information
double m_frameRate = 0;
LONG m_cameraWidth; LONG m_cameraWidth;
LONG m_cameraHeight; LONG m_cameraHeight;
@@ -129,6 +137,12 @@ namespace games::iidx {
LPDIRECT3DTEXTURE9 m_conversionTexture = nullptr; LPDIRECT3DTEXTURE9 m_conversionTexture = nullptr;
IDirect3DSurface9 *m_pConversionSurf = nullptr; IDirect3DSurface9 *m_pConversionSurf = nullptr;
// upload surface for decoded camera frames returned in system memory
IDirect3DSurface9 *m_pDecodedSurf = nullptr;
GUID m_decodedSubtype = GUID_NULL;
GUID m_outputSubtype = GUID_NULL;
bool m_drawErrorLogged = false;
// Texture for custom transform (e.g. horizontal flip) // Texture for custom transform (e.g. horizontal flip)
LPDIRECT3DTEXTURE9 m_transformTexture = nullptr; LPDIRECT3DTEXTURE9 m_transformTexture = nullptr;
IDirect3DSurface9 *m_pTransformSurf = nullptr; IDirect3DSurface9 *m_pTransformSurf = nullptr;
@@ -153,21 +167,19 @@ namespace games::iidx {
BOOL m_initialized = false; BOOL m_initialized = false;
// True if all the setup steps succeeded // True if all the setup steps succeeded
BOOL m_active = false; std::atomic_bool m_active = false;
// Media type select // Media type select
std::vector<MediaTypeInfo> m_mediaTypeInfos = {}; std::vector<MediaTypeInfo> m_mediaTypeInfos = {};
int m_selectedMediaTypeIndex = 0;
bool m_useAutoMediaType = true; bool m_useAutoMediaType = true;
IMFMediaType *m_pAutoMediaType = nullptr; IMFMediaType *m_pAutoMediaType = nullptr;
std::string m_selectedMediaTypeDescription = "";
bool m_allowManualControl = false; bool m_allowManualControl = false;
LocalCameraDrawMode m_drawMode = DrawModeCrop4_3; std::atomic<LocalCameraDrawMode> m_drawMode = DrawModeCrop4_3;
// Render processing // Render processing
bool m_flipHorizontal = false; std::atomic_bool m_flipHorizontal = false;
bool m_flipVertical = false; std::atomic_bool m_flipVertical = false;
IIDXLocalCamera( IIDXLocalCamera(
std::string name, std::string name,
@@ -184,11 +196,14 @@ namespace games::iidx {
HRESULT GetCameraControlProp(int index, CameraControlProp *pProp); HRESULT GetCameraControlProp(int index, CameraControlProp *pProp);
HRESULT SetCameraControlProp(int index, long value, long flags); HRESULT SetCameraControlProp(int index, long value, long flags);
HRESULT ResetCameraControlProps(); HRESULT ResetCameraControlProps();
HRESULT FlushDrawCommands();
std::string GetName(); std::string GetName();
std::string GetFriendlyName(); std::string GetFriendlyName();
std::string GetSymLink(); std::string GetSymLink();
int GetSelectedMediaTypeIndex();
std::string GetSelectedMediaTypeDescription();
void SetSelectedMediaTypeDescription(const std::string &description);
HRESULT ChangeMediaType(IMFMediaType *pType); HRESULT ChangeMediaType(IMFMediaType *pType);
void RequestMediaType(IMFMediaType *pType);
HRESULT StartCapture(); HRESULT StartCapture();
void UpdateDrawRect(); void UpdateDrawRect();
@@ -197,12 +212,22 @@ namespace games::iidx {
void CreateThread(); void CreateThread();
MediaTypeInfo GetMediaTypeInfo(IMFMediaType *pType); MediaTypeInfo GetMediaTypeInfo(IMFMediaType *pType);
std::string GetVideoFormatName(GUID subtype); std::string GetVideoFormatName(GUID subtype);
HRESULT TryMediaType(IMFMediaType *pType, UINT32 *pBestWidth, double *pBestFrameRate); static bool CompareMediaTypes(const MediaTypeInfo &a, const MediaTypeInfo &b);
bool MatchesPreferredAspect(const MediaTypeInfo &info) const;
static bool IsBetterAutoType(const MediaTypeInfo &candidate, const MediaTypeInfo &current);
IMFMediaType *FindBestNativeAutoType(bool requirePreferredAspect) const;
IMFMediaType *FindBestAutoType(const GUID &subtype, bool requirePreferredAspect) const;
HRESULT ValidateMediaType(IMFMediaType *pType);
HRESULT InitTargetTexture(); HRESULT InitTargetTexture();
HRESULT EnsureTransformTextures();
HRESULT InitCameraControl(); HRESULT InitCameraControl();
void SetSelectedMediaType(int index, const std::string &description);
bool HasPendingMediaType();
HRESULT ApplyPendingMediaType();
HRESULT UploadDecodedSample(IMFMediaBuffer *pSrcBuffer);
HRESULT DrawSample(IMFMediaBuffer *pSrcBuffer); HRESULT DrawSample(IMFMediaBuffer *pSrcBuffer);
HRESULT ReadSample(); HRESULT ReadSample();
LPDIRECT3DTEXTURE9 Render(); HRESULT Render();
}; };
} }
+16
View File
@@ -15,6 +15,10 @@ namespace games::iidx {
_In_ UINT32 cInitialSize _In_ UINT32 cInitialSize
); );
typedef HRESULT (__stdcall * MFCreateMediaType_t)(
_Out_ IMFMediaType** ppMFType
);
typedef HRESULT (__stdcall * MFEnumDeviceSources_t)( typedef HRESULT (__stdcall * MFEnumDeviceSources_t)(
_In_ IMFAttributes* pAttributes, _In_ IMFAttributes* pAttributes,
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate, _Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
@@ -35,6 +39,7 @@ namespace games::iidx {
); );
static MFCreateAttributes_t MFCreateAttributes = nullptr; static MFCreateAttributes_t MFCreateAttributes = nullptr;
static MFCreateMediaType_t MFCreateMediaType = nullptr;
static MFEnumDeviceSources_t MFEnumDeviceSources = nullptr; static MFEnumDeviceSources_t MFEnumDeviceSources = nullptr;
static MFCreateSourceReaderFromMediaSource_t MFCreateSourceReaderFromMediaSource = nullptr; static MFCreateSourceReaderFromMediaSource_t MFCreateSourceReaderFromMediaSource = nullptr;
static MFGetService_t MFGetService = nullptr; static MFGetService_t MFGetService = nullptr;
@@ -66,6 +71,12 @@ namespace games::iidx {
log_fatal("mf_wrappers", "MFCreateAttributes failed to hook"); log_fatal("mf_wrappers", "MFCreateAttributes failed to hook");
} }
MFCreateMediaType = (MFCreateMediaType_t)
libutils::get_proc(mfplat_dll, "MFCreateMediaType");
if (!MFCreateMediaType) {
log_fatal("mf_wrappers", "MFCreateMediaType failed to hook");
}
MFEnumDeviceSources = (MFEnumDeviceSources_t) MFEnumDeviceSources = (MFEnumDeviceSources_t)
libutils::get_proc(mf_dll, "MFEnumDeviceSources"); libutils::get_proc(mf_dll, "MFEnumDeviceSources");
if (!MFEnumDeviceSources) { if (!MFEnumDeviceSources) {
@@ -92,6 +103,11 @@ namespace games::iidx {
return MFCreateAttributes(ppMFAttributes, cInitialSize); return MFCreateAttributes(ppMFAttributes, cInitialSize);
} }
HRESULT WrappedMFCreateMediaType (
_Out_ IMFMediaType** ppMFType) {
return MFCreateMediaType(ppMFType);
}
HRESULT WrappedMFEnumDeviceSources ( HRESULT WrappedMFEnumDeviceSources (
_In_ IMFAttributes* pAttributes, _In_ IMFAttributes* pAttributes,
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate, _Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
+3
View File
@@ -12,6 +12,9 @@ namespace games::iidx {
_Out_ IMFAttributes** ppMFAttributes, _Out_ IMFAttributes** ppMFAttributes,
_In_ UINT32 cInitialSize); _In_ UINT32 cInitialSize);
HRESULT WrappedMFCreateMediaType (
_Out_ IMFMediaType** ppMFType);
HRESULT WrappedMFEnumDeviceSources ( HRESULT WrappedMFEnumDeviceSources (
_In_ IMFAttributes* pAttributes, _In_ IMFAttributes* pAttributes,
_Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate, _Outptr_result_buffer_(*pcSourceActivate) IMFActivate*** pppSourceActivate,
+2 -4
View File
@@ -1,5 +1,6 @@
#include "poke.h" #include "poke.h"
#include <chrono>
#include <thread> #include <thread>
#include "windows.h" #include "windows.h"
@@ -11,7 +12,6 @@
#include "touch/native/inject.h" #include "touch/native/inject.h"
#include "touch/touch.h" #include "touch/touch.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/precise_timer.h"
namespace games::iidx::poke { namespace games::iidx::poke {
@@ -125,8 +125,6 @@ namespace games::iidx::poke {
// create new thread // create new thread
THREAD_RUNNING = true; THREAD_RUNNING = true;
THREAD = new std::thread([] { THREAD = new std::thread([] {
timeutils::PreciseSleepTimer timer;
// log // log
log_info("poke", "enabled"); log_info("poke", "enabled");
@@ -198,7 +196,7 @@ namespace games::iidx::poke {
} }
// slow down // slow down
timer.sleep(50); std::this_thread::sleep_for(std::chrono::milliseconds(50));
} }
if (!touch_points.empty()) { if (!touch_points.empty()) {
+2 -4
View File
@@ -1,6 +1,7 @@
#include "poke.h" #include "poke.h"
#include <atomic> #include <atomic>
#include <chrono>
#include <optional> #include <optional>
#include <thread> #include <thread>
@@ -11,7 +12,6 @@
#include "touch/native/inject.h" #include "touch/native/inject.h"
#include "touch/native/nativetouchhook.h" #include "touch/native/nativetouchhook.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/precise_timer.h"
namespace games::nost::poke { namespace games::nost::poke {
@@ -68,8 +68,6 @@ namespace games::nost::poke {
// create new thread // create new thread
THREAD_RUNNING = true; THREAD_RUNNING = true;
THREAD = new std::thread([] { THREAD = new std::thread([] {
timeutils::PreciseSleepTimer timer;
const int swipe_anim_total_frames = 6; const int swipe_anim_total_frames = 6;
const int swipe_anim_y = 300; const int swipe_anim_y = 300;
@@ -194,7 +192,7 @@ namespace games::nost::poke {
} }
// slow down // slow down
timer.sleep(30); std::this_thread::sleep_for(std::chrono::milliseconds(30));
} }
if (contact_active) { if (contact_active) {
@@ -2,10 +2,10 @@
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <thread>
#include "hooks/audio/util.h" #include "hooks/audio/util.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/precise_timer.h"
NullDiscardBackend::~NullDiscardBackend() { NullDiscardBackend::~NullDiscardBackend() {
this->running = false; this->running = false;
@@ -125,8 +125,6 @@ HRESULT NullDiscardBackend::on_release_buffer(uint32_t, DWORD) {
void NullDiscardBackend::pace_loop() { void NullDiscardBackend::pace_loop() {
using namespace std::chrono; using namespace std::chrono;
timeutils::PreciseSleepTimer timer;
// audio is discarded, so timing precision and drift do not matter; just wake the // audio is discarded, so timing precision and drift do not matter; just wake the
// game once per buffer period to keep its render thread from blocking on the event. // game once per buffer period to keep its render thread from blocking on the event.
const auto period = duration_cast<steady_clock::duration>( const auto period = duration_cast<steady_clock::duration>(
@@ -136,6 +134,6 @@ void NullDiscardBackend::pace_loop() {
if (this->relay_handle) { if (this->relay_handle) {
SetEvent(this->relay_handle); SetEvent(this->relay_handle);
} }
timer.sleep(period); std::this_thread::sleep_for(period);
} }
} }
+7 -9
View File
@@ -1,6 +1,7 @@
#include "eamuse.h" #include "eamuse.h"
#include <atomic> #include <atomic>
#include <chrono>
#include <fstream> #include <fstream>
#include <thread> #include <thread>
@@ -10,7 +11,6 @@
#include "rawinput/rawinput.h" #include "rawinput/rawinput.h"
#include "games/sdvx/sdvx.h" #include "games/sdvx/sdvx.h"
#include "util/logging.h" #include "util/logging.h"
#include "util/precise_timer.h"
#include "util/time.h" #include "util/time.h"
#include "util/utils.h" #include "util/utils.h"
#include "overlay/overlay.h" #include "overlay/overlay.h"
@@ -335,7 +335,6 @@ void eamuse_coin_start_thread() {
COIN_INPUT_THREAD = new std::thread([]() { COIN_INPUT_THREAD = new std::thread([]() {
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game()); auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
static bool COIN_INPUT_KEY_STATE = false; static bool COIN_INPUT_KEY_STATE = false;
timeutils::PreciseSleepTimer timer;
while (COIN_INPUT_THREAD_ACTIVE) { while (COIN_INPUT_THREAD_ACTIVE) {
// check input key // check input key
@@ -355,7 +354,7 @@ void eamuse_coin_start_thread() {
} }
// once every two frames // once every two frames
timer.sleep(1000 / 30); std::this_thread::sleep_for(std::chrono::milliseconds(1000 / 30));
} }
}); });
} }
@@ -409,7 +408,6 @@ void eamuse_pin_macro_start_thread() {
size_t pin_index[2] = {PIN_MACRO_VALUES[0].length(), PIN_MACRO_VALUES[1].length()}; size_t pin_index[2] = {PIN_MACRO_VALUES[0].length(), PIN_MACRO_VALUES[1].length()};
std::optional<uint8_t> active_unit = std::nullopt; std::optional<uint8_t> active_unit = std::nullopt;
timeutils::PreciseSleepTimer timer;
while (PIN_MACRO_THREAD_ACTIVE) { while (PIN_MACRO_THREAD_ACTIVE) {
// wait for key press or auto-trigger // wait for key press or auto-trigger
@@ -438,7 +436,7 @@ void eamuse_pin_macro_start_thread() {
} }
if (!active_unit.has_value()) { if (!active_unit.has_value()) {
timer.sleep(20); std::this_thread::sleep_for(std::chrono::milliseconds(20));
continue; continue;
} }
} }
@@ -454,22 +452,22 @@ void eamuse_pin_macro_start_thread() {
eamuse_set_keypad_overrides(unit, keypad_overrides[char_index]); eamuse_set_keypad_overrides(unit, keypad_overrides[char_index]);
} }
pin_index[unit]++; pin_index[unit]++;
timer.sleep(100); std::this_thread::sleep_for(std::chrono::milliseconds(100));
// clear // clear
eamuse_set_keypad_overrides(unit, 0); eamuse_set_keypad_overrides(unit, 0);
timer.sleep(50); std::this_thread::sleep_for(std::chrono::milliseconds(50));
// end of PIN // end of PIN
if (pin_index[unit] == PIN_MACRO_VALUES[unit].length()) { if (pin_index[unit] == PIN_MACRO_VALUES[unit].length()) {
active_unit = std::nullopt; active_unit = std::nullopt;
timer.sleep(120); std::this_thread::sleep_for(std::chrono::milliseconds(120));
} }
continue; continue;
} }
timer.sleep(200); std::this_thread::sleep_for(std::chrono::milliseconds(200));
} }
}); });
} }
+8
View File
@@ -495,6 +495,14 @@ namespace wintouchemu {
if (hWnd != nullptr && USE_MOUSE) { if (hWnd != nullptr && USE_MOUSE) {
bool button_pressed = get_async_primary_mouse(); bool button_pressed = get_async_primary_mouse();
// while the subscreen overlay cannot accept the mouse, treat the button as
// released; this ends any active touch and prevents new mouse touch contacts
if (overlay::OVERLAY &&
overlay::OVERLAY->has_subscreen_touch_transform() &&
!overlay::OVERLAY->accepts_subscreen_mouse_input()) {
button_pressed = false;
}
// if there was a touch event in the last 500 ms, don't insert new button presses // if there was a touch event in the last 500 ms, don't insert new button presses
if (button_pressed && (now - last_touch_event) < 500) { if (button_pressed && (now - last_touch_event) < 500) {
button_pressed = false; button_pressed = false;
+11
View File
@@ -908,6 +908,17 @@ bool overlay::SpiceOverlay::get_active() {
return this->active; return this->active;
} }
bool overlay::SpiceOverlay::is_subscreen_overlay_visible() {
return this->get_active() &&
this->window_sub != nullptr &&
this->window_sub->get_active();
}
bool overlay::SpiceOverlay::accepts_subscreen_mouse_input() {
return this->is_subscreen_overlay_visible() &&
this->window_sub->is_mouse_hovered();
}
bool overlay::SpiceOverlay::has_focus() { bool overlay::SpiceOverlay::has_focus() {
return this->get_active() && ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow); return this->get_active() && ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow);
} }
+9 -7
View File
@@ -109,6 +109,7 @@ namespace overlay {
void show_main_menu(); void show_main_menu();
void set_active(bool active); void set_active(bool active);
bool get_active(); bool get_active();
bool accepts_subscreen_mouse_input();
bool has_focus(); bool has_focus();
bool hotkeys_triggered(); bool hotkeys_triggered();
@@ -143,20 +144,20 @@ namespace overlay {
return this->hWnd; return this->hWnd;
} }
bool can_transform_touch_input() { bool has_subscreen_touch_transform() {
return (this->subscreen_mouse_handler != nullptr); return (this->subscreen_touch_transform != nullptr);
} }
bool transform_touch_point(LONG *x, LONG *y) { bool transform_touch_point(LONG *x, LONG *y) {
if (this->get_active() && this->subscreen_mouse_handler) { if (this->get_active() && this->subscreen_touch_transform) {
return this->subscreen_mouse_handler(x, y); return this->subscreen_touch_transform(x, y);
} else { } else {
return true; return true;
} }
} }
void set_subscreen_mouse_handler(const std::function<bool(LONG *, LONG *)> &f) { void set_subscreen_touch_transform(const std::function<bool(LONG *, LONG *)> &f) {
this->subscreen_mouse_handler = f; this->subscreen_touch_transform = f;
} }
// renderer // renderer
@@ -203,7 +204,7 @@ namespace overlay {
std::vector<std::unique_ptr<Window>> windows; std::vector<std::unique_ptr<Window>> windows;
std::function<bool(LONG *, LONG *)> subscreen_mouse_handler = nullptr; std::function<bool(LONG *, LONG *)> subscreen_touch_transform = nullptr;
bool active = false; bool active = false;
bool toggle_down = false; bool toggle_down = false;
@@ -216,6 +217,7 @@ namespace overlay {
// so render() never runs without a matching NewFrame. // so render() never runs without a matching NewFrame.
bool has_pending_frame = false; bool has_pending_frame = false;
bool is_subscreen_overlay_visible();
void init(); void init();
void add_font(const char* font, ImFontConfig* config, const ImWchar* glyphs); void add_font(const char* font, ImFontConfig* config, const ImWchar* glyphs);
}; };
+18 -2
View File
@@ -69,6 +69,7 @@ void overlay::Window::build() {
// check if active // check if active
if (!this->active) { if (!this->active) {
this->mouse_hovered = false;
return; return;
} }
@@ -106,10 +107,18 @@ void overlay::Window::build() {
// top spot. suppress that so only an explicit focus request (see below) // top spot. suppress that so only an explicit focus request (see below)
// decides what comes to the front. // decides what comes to the front.
const std::string window_id = this->title + "###" + to_string(this); const std::string window_id = this->title + "###" + to_string(this);
if (ImGui::Begin( const bool build_contents = ImGui::Begin(
window_id.c_str(), window_id.c_str(),
&this->active, &this->active,
this->flags | ImGuiWindowFlags_NoFocusOnAppearing)) { this->flags | ImGuiWindowFlags_NoFocusOnAppearing);
// keep the window hovered while its invisible input button is held during a drag
this->mouse_hovered =
ImGui::IsWindowHovered(
ImGuiHoveredFlags_RootAndChildWindows |
ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
if (build_contents) {
// window attributes - init_pos / init_size are only honored once // window attributes - init_pos / init_size are only honored once
// (ImGuiCond_Once), so compute them a single time instead of every frame // (ImGuiCond_Once), so compute them a single time instead of every frame
@@ -154,6 +163,9 @@ void overlay::Window::build() {
} else { } else {
// raw content does not have an ImGui window to hover
this->mouse_hovered = false;
// add raw content // add raw content
this->build_content(); this->build_content();
} }
@@ -201,3 +213,7 @@ bool overlay::Window::get_active() {
// now it depends on us // now it depends on us
return this->active; return this->active;
} }
bool overlay::Window::is_mouse_hovered() {
return this->mouse_hovered;
}
+2
View File
@@ -24,6 +24,7 @@ namespace overlay {
void toggle_active(); void toggle_active();
void set_active(bool active); void set_active(bool active);
bool get_active(); bool get_active();
bool is_mouse_hovered();
// raise this window to the top of the z-order on its next build. only // raise this window to the top of the z-order on its next build. only
// call in response to an explicit user action (keyboard toggle, UI // call in response to an explicit user action (keyboard toggle, UI
@@ -37,6 +38,7 @@ namespace overlay {
// set when the window transitions to visible so build() raises it to the // set when the window transitions to visible so build() raises it to the
// top of the z-order on the next frame // top of the z-order on the next frame
bool request_focus = false; bool request_focus = false;
bool mouse_hovered = false;
std::vector<Window*> children; std::vector<Window*> children;
// settings // settings
+26 -10
View File
@@ -82,9 +82,14 @@ namespace overlay::windows {
ImGui::Text("Rendering"); ImGui::Text("Rendering");
// Media Type Selector // Media Type Selector
int selectedMediaTypeIndex = selectedCamera->m_selectedMediaTypeIndex; int selectedMediaTypeIndex = selectedCamera->GetSelectedMediaTypeIndex();
auto numMediaTypes = selectedCamera->m_mediaTypeInfos.size(); auto numMediaTypes = selectedCamera->m_mediaTypeInfos.size();
if (numMediaTypes == 0) {
ImGui::TextColored(ImVec4(1.f, 1.f, 0.f, 1.f), "%s", "No supported media types");
return;
}
auto selectedMediaTypeIndexChanged = ImGui::BeginCombo( auto selectedMediaTypeIndexChanged = ImGui::BeginCombo(
"Media Type", selectedCamera->m_mediaTypeInfos.at(selectedMediaTypeIndex).description.c_str() "Media Type", selectedCamera->m_mediaTypeInfos.at(selectedMediaTypeIndex).description.c_str()
); );
@@ -103,9 +108,9 @@ namespace overlay::windows {
ImGui::EndCombo(); ImGui::EndCombo();
} }
if (selectedMediaTypeIndexChanged && selectedMediaTypeIndex != selectedCamera->m_selectedMediaTypeIndex) { if (selectedMediaTypeIndexChanged && selectedMediaTypeIndex != selectedCamera->GetSelectedMediaTypeIndex()) {
selectedCamera->m_useAutoMediaType = false; selectedCamera->m_useAutoMediaType = false;
selectedCamera->ChangeMediaType(selectedCamera->m_mediaTypeInfos.at(selectedMediaTypeIndex).p_mediaType); selectedCamera->RequestMediaType(selectedCamera->m_mediaTypeInfos.at(selectedMediaTypeIndex).p_mediaType);
} }
// Auto media type // Auto media type
@@ -114,13 +119,14 @@ namespace overlay::windows {
if (ImGui::Checkbox("Auto##MediaType", &isAutoMediaType)) { if (ImGui::Checkbox("Auto##MediaType", &isAutoMediaType)) {
selectedCamera->m_useAutoMediaType = isAutoMediaType; selectedCamera->m_useAutoMediaType = isAutoMediaType;
if (isAutoMediaType) { if (isAutoMediaType) {
selectedCamera->ChangeMediaType(selectedCamera->m_pAutoMediaType); selectedCamera->RequestMediaType(selectedCamera->m_pAutoMediaType);
} }
} }
// Draw mode // Draw mode
int selectedDrawModeIndex = selectedCamera->m_drawMode; const auto drawMode = selectedCamera->m_drawMode.load();
if (ImGui::BeginCombo("Draw Mode", DRAW_MODE_LABELS[selectedCamera->m_drawMode].c_str())) { int selectedDrawModeIndex = drawMode;
if (ImGui::BeginCombo("Draw Mode", DRAW_MODE_LABELS[drawMode].c_str())) {
for (size_t i = 0; i < DRAW_MODE_SIZE; i++) { for (size_t i = 0; i < DRAW_MODE_SIZE; i++) {
const bool is_selected = (selectedDrawModeIndex == (int) i); const bool is_selected = (selectedDrawModeIndex == (int) i);
if (ImGui::Selectable(DRAW_MODE_LABELS[i].c_str(), is_selected)) { if (ImGui::Selectable(DRAW_MODE_LABELS[i].c_str(), is_selected)) {
@@ -142,15 +148,25 @@ namespace overlay::windows {
"Letterbox to 4:3: Like Letterbox, but target 4:3" "Letterbox to 4:3: Like Letterbox, but target 4:3"
); );
if (selectedDrawModeIndex != selectedCamera->m_drawMode) { if (selectedDrawModeIndex != selectedCamera->m_drawMode.load()) {
selectedCamera->m_drawMode = (LocalCameraDrawMode)selectedDrawModeIndex; selectedCamera->m_drawMode.store((LocalCameraDrawMode)selectedDrawModeIndex);
selectedCamera->UpdateDrawRect(); selectedCamera->UpdateDrawRect();
} }
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::Checkbox("Horizontal Flip", &selectedCamera->m_flipHorizontal); bool flipHorizontal = selectedCamera->m_flipHorizontal.load();
if (ImGui::Checkbox("Horizontal Flip", &flipHorizontal)) {
selectedCamera->m_flipHorizontal.store(flipHorizontal);
}
ImGui::SameLine(); ImGui::SameLine();
ImGui::Checkbox("Vertical Flip", &selectedCamera->m_flipVertical); bool flipVertical = selectedCamera->m_flipVertical.load();
if (ImGui::Checkbox("Vertical Flip", &flipVertical)) {
selectedCamera->m_flipVertical.store(flipVertical);
}
ImGui::SameLine();
ImGui::HelpMarker(
"Flips are done by the CPU and can be very expensive, potentially causing frame drops! "
"Prefer to use camera's built-in flip if available.");
// Camera control parameters // Camera control parameters
ImGui::Separator(); ImGui::Separator();
+1 -1
View File
@@ -42,7 +42,7 @@ namespace overlay::windows {
this->texture_width = 0; this->texture_width = 0;
this->texture_height = 0; this->texture_height = 0;
overlay->set_subscreen_mouse_handler([this](LONG *x, LONG *y) -> bool { overlay->set_subscreen_touch_transform([this](LONG *x, LONG *y) -> bool {
// convert to normalized form (relative window coordinates 0.f-1.f) // convert to normalized form (relative window coordinates 0.f-1.f)
ImVec2 xy; ImVec2 xy;
+12 -5
View File
@@ -4,7 +4,6 @@
#include <thread> #include <thread>
#include <mutex> #include <mutex>
#include <vector> #include <vector>
#include <map>
#include <windows.h> #include <windows.h>
@@ -103,6 +102,13 @@ namespace rawinput {
std::vector<HIDTouchPoint> touch_points; std::vector<HIDTouchPoint> touch_points;
}; };
struct HIDButtonInputGroup {
USAGE usage_page;
USHORT link_collection;
std::vector<size_t> cap_indices;
std::vector<USAGE> usages;
};
struct DeviceHIDInfo { struct DeviceHIDInfo {
HANDLE handle; HANDLE handle;
_HIDP_CAPS caps; _HIDP_CAPS caps;
@@ -125,11 +131,12 @@ namespace rawinput {
std::vector<std::vector<bool>> button_states; std::vector<std::vector<bool>> button_states;
std::vector<std::vector<double>> button_up, button_down; std::vector<std::vector<double>> button_up, button_down;
std::vector<std::vector<uint8_t>> button_report_states;
std::vector<std::vector<bool>> button_output_states; std::vector<std::vector<bool>> button_output_states;
std::vector<HIDButtonInputGroup> button_input_groups;
// key: usage page, link collection std::vector<CHAR> output_report;
// value: number of buttons for that key (combine ranges and nonranges) std::vector<USAGE> button_output_usages;
std::map<std::pair<USAGE, ULONG>, ULONG> button_usage_pages; std::vector<USAGE> button_output_usages_off;
std::vector<float> value_states; std::vector<float> value_states;
std::vector<LONG> value_states_raw; std::vector<LONG> value_states_raw;
+86 -57
View File
@@ -1,6 +1,10 @@
#include "rawinput.h" #include "rawinput.h"
#include <algorithm>
#include <chrono>
#include <cstdarg> #include <cstdarg>
#include <map>
#include <thread>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -9,7 +13,6 @@
#include "util/logging.h" #include "util/logging.h"
#include "external/robin_hood.h" #include "external/robin_hood.h"
#include "util/precise_timer.h"
#include "util/time.h" #include "util/time.h"
#include "util/utils.h" #include "util/utils.h"
@@ -169,9 +172,8 @@ void rawinput::RawInputManager::input_hwnd_create() {
}); });
// wait for window creation being done // wait for window creation being done
timeutils::PreciseSleepTimer timer;
while (!this->input_hwnd) { while (!this->input_hwnd) {
timer.sleep(1); std::this_thread::sleep_for(std::chrono::milliseconds(1));
} }
} }
@@ -439,7 +441,9 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
std::vector<std::string> button_caps_names; std::vector<std::string> button_caps_names;
std::vector<std::vector<bool>> button_states; std::vector<std::vector<bool>> button_states;
std::vector<std::vector<double>> button_up, button_down; std::vector<std::vector<double>> button_up, button_down;
std::map<std::pair<USAGE, ULONG>, ULONG> button_usage_pages; std::vector<std::vector<uint8_t>> button_report_states;
std::vector<HIDButtonInputGroup> button_input_groups;
std::map<std::pair<USAGE, ULONG>, size_t> button_input_group_indices;
for (int button_cap_num = 0; button_cap_num < button_cap_length; button_cap_num++) { for (int button_cap_num = 0; button_cap_num < button_cap_length; button_cap_num++) {
auto &button_caps = button_cap_data[button_cap_num]; auto &button_caps = button_cap_data[button_cap_num];
@@ -458,7 +462,7 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1; int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
// ignore bad ranges reported by bad devices // ignore bad ranges reported by bad devices
if (button_count >= 0xffff) { if (button_count <= 0 || button_count >= 0xffff) {
log_warning("rawinput", "skipping bad button cap range for device {}, range [{}, {}]", log_warning("rawinput", "skipping bad button cap range for device {}, range [{}, {}]",
device_name, device_name,
button_caps.Range.UsageMin, button_caps.Range.UsageMin,
@@ -467,11 +471,30 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
} }
// fill vectors // fill vectors
const size_t cap_index = button_caps_list.size();
button_caps_list.emplace_back(button_caps); button_caps_list.emplace_back(button_caps);
button_states.emplace_back(std::vector<bool>(static_cast<unsigned int>(button_count), false)); button_states.emplace_back(std::vector<bool>(static_cast<unsigned int>(button_count), false));
button_up.emplace_back(std::vector<double>(static_cast<unsigned int>(button_count), 0.0)); button_up.emplace_back(std::vector<double>(static_cast<unsigned int>(button_count), 0.0));
button_down.emplace_back(std::vector<double>(static_cast<unsigned int>(button_count), 0.0)); button_down.emplace_back(std::vector<double>(static_cast<unsigned int>(button_count), 0.0));
button_usage_pages[std::make_pair(button_caps.UsagePage, button_caps.LinkCollection)] += button_count; button_report_states.emplace_back(static_cast<size_t>(button_count), 0);
const auto group_key = std::make_pair(
button_caps.UsagePage,
static_cast<ULONG>(button_caps.LinkCollection));
auto [group_it, inserted] = button_input_group_indices.try_emplace(
group_key,
button_input_groups.size());
if (inserted) {
button_input_groups.emplace_back(HIDButtonInputGroup {
.usage_page = button_caps.UsagePage,
.link_collection = button_caps.LinkCollection,
.cap_indices = {},
.usages = {},
});
}
auto &input_group = button_input_groups[group_it->second];
input_group.cap_indices.push_back(cap_index);
input_group.usages.resize(input_group.usages.size() + button_count);
// names // names
for (USAGE usg = button_caps.Range.UsageMin; usg <= button_caps.Range.UsageMax; usg++) { for (USAGE usg = button_caps.Range.UsageMin; usg <= button_caps.Range.UsageMax; usg++) {
@@ -512,7 +535,7 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1; int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
// ignore bad ranges reported by bad devices // ignore bad ranges reported by bad devices
if (button_count >= 0xffff) { if (button_count <= 0 || button_count >= 0xffff) {
log_warning("rawinput", "skipping bad button output cap range for device {}, range [{}, {}]", log_warning("rawinput", "skipping bad button output cap range for device {}, range [{}, {}]",
device_name, device_name,
button_caps.Range.UsageMin, button_caps.Range.UsageMin,
@@ -800,8 +823,18 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
new_device.hidInfo->button_states = std::move(button_states); new_device.hidInfo->button_states = std::move(button_states);
new_device.hidInfo->button_up = std::move(button_up); new_device.hidInfo->button_up = std::move(button_up);
new_device.hidInfo->button_down = std::move(button_down); new_device.hidInfo->button_down = std::move(button_down);
new_device.hidInfo->button_report_states = std::move(button_report_states);
new_device.hidInfo->button_output_states = std::move(button_output_states); new_device.hidInfo->button_output_states = std::move(button_output_states);
new_device.hidInfo->button_usage_pages = std::move(button_usage_pages); new_device.hidInfo->button_input_groups = std::move(button_input_groups);
new_device.hidInfo->output_report.resize(caps.OutputReportByteLength);
size_t max_button_output_count = 0;
for (const auto &button_output_state : new_device.hidInfo->button_output_states) {
if (button_output_state.size() > max_button_output_count) {
max_button_output_count = button_output_state.size();
}
}
new_device.hidInfo->button_output_usages.reserve(max_button_output_count);
new_device.hidInfo->button_output_usages_off.reserve(max_button_output_count);
new_device.hidInfo->value_states = std::move(value_states); new_device.hidInfo->value_states = std::move(value_states);
new_device.hidInfo->value_states_raw = std::move(value_states_raw); new_device.hidInfo->value_states_raw = std::move(value_states_raw);
new_device.hidInfo->value_output_states = std::move(value_output_states); new_device.hidInfo->value_output_states = std::move(value_output_states);
@@ -1573,11 +1606,17 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
if (!data_size) { if (!data_size) {
break; break;
} }
std::shared_ptr<RAWINPUT> data((RAWINPUT *) new char[data_size]{}); thread_local std::vector<RAWINPUT> data_buffer;
const size_t data_count =
(data_size + sizeof(RAWINPUT) - 1) / sizeof(RAWINPUT);
if (data_buffer.size() < data_count) {
data_buffer.resize(data_count);
}
auto data = data_buffer.data();
if (GetRawInputData( if (GetRawInputData(
(HRAWINPUT) lParam, (HRAWINPUT) lParam,
RID_INPUT, RID_INPUT,
data.get(), data,
&data_size, &data_size,
sizeof(RAWINPUTHEADER)) != data_size) { sizeof(RAWINPUTHEADER)) != data_size) {
break; break;
@@ -1791,17 +1830,13 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
+ (size_t) hid_report_index * data_hid.dwSizeHid; + (size_t) hid_report_index * data_hid.dwSizeHid;
// parse reports // parse reports
for (const auto &pair : device.hidInfo->button_usage_pages) { for (auto &input_group : device.hidInfo->button_input_groups) {
const auto usage_page = pair.first.first; auto &usages = input_group.usages;
const auto link_collection = pair.first.second; ULONG usages_length = static_cast<ULONG>(usages.size());
const auto button_count = pair.second;
ULONG usages_length = button_count;
std::vector<USAGE> usages(static_cast<size_t>(usages_length));
if (HidP_GetUsages( if (HidP_GetUsages(
HidP_Input, HidP_Input,
usage_page, input_group.usage_page,
link_collection, input_group.link_collection,
usages.data(), usages.data(),
&usages_length, &usages_length,
reinterpret_cast<PHIDP_PREPARSED_DATA>(device.hidInfo->preparsed_data.get()), reinterpret_cast<PHIDP_PREPARSED_DATA>(device.hidInfo->preparsed_data.get()),
@@ -1819,29 +1854,22 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
// log_info( // log_info(
// "rawinput", // "rawinput",
// "processing HID input for device {}, usage page {:x} and link collection {:x} with {} buttons, got {} reports", // "processing HID input for device {}, usage page {:x} and link collection {:x} with {} buttons, got {} reports",
// device.desc, // device.desc, input_group.usage_page,
// usage_page, link_collection, button_count, usages_length); // input_group.link_collection, usages.size(), usages_length);
// buttons // buttons
for (size_t cap_num = 0; cap_num < device.hidInfo->button_caps_list.size(); cap_num++) { for (const size_t cap_num : input_group.cap_indices) {
auto &button_caps = device.hidInfo->button_caps_list[cap_num]; auto &button_caps = device.hidInfo->button_caps_list[cap_num];
auto &button_states = device.hidInfo->button_states[cap_num]; auto &button_states = device.hidInfo->button_states[cap_num];
auto &button_down = device.hidInfo->button_down[cap_num]; auto &button_down = device.hidInfo->button_down[cap_num];
auto &button_up = device.hidInfo->button_up[cap_num]; auto &button_up = device.hidInfo->button_up[cap_num];
auto &new_states = device.hidInfo->button_report_states[cap_num];
// is this the right usage page and link collection?
if (button_caps.UsagePage != usage_page || button_caps.LinkCollection != link_collection) {
continue;
}
// get button count // get button count
int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1; int button_count = button_caps.Range.UsageMax - button_caps.Range.UsageMin + 1;
if (button_count <= 0) {
continue;
}
// update buttons // update buttons
std::vector<bool> new_states(button_count); std::fill(new_states.begin(), new_states.end(), 0);
for (ULONG usage_num = 0; usage_num < usages_length; usage_num++) { for (ULONG usage_num = 0; usage_num < usages_length; usage_num++) {
if (usages[usage_num] < button_caps.Range.UsageMin || if (usages[usage_num] < button_caps.Range.UsageMin ||
usages[usage_num] > button_caps.Range.UsageMax) { usages[usage_num] > button_caps.Range.UsageMax) {
@@ -1852,17 +1880,18 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
// guard against some buggy device sending an event for a usage below UsageMin // guard against some buggy device sending an event for a usage below UsageMin
if (usage < button_count) { if (usage < button_count) {
new_states[usage] = true; new_states[usage] = 1;
} }
} }
for (int button_num = 0; button_num < button_count; button_num++) { for (int button_num = 0; button_num < button_count; button_num++) {
if (!new_states[button_num] && button_states[button_num]) { const bool new_state = new_states[button_num] != 0;
if (!new_state && button_states[button_num]) {
device.updated = true; device.updated = true;
button_states[button_num] = new_states[button_num]; button_states[button_num] = new_state;
button_down[button_num] = input_time; button_down[button_num] = input_time;
} else if (new_states[button_num] && !button_states[button_num]) { } else if (new_state && !button_states[button_num]) {
device.updated = true; device.updated = true;
button_states[button_num] = new_states[button_num]; button_states[button_num] = new_state;
button_up[button_num] = input_time; button_up[button_num] = input_time;
} }
} }
@@ -2017,8 +2046,10 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
switch (hid->driver) { switch (hid->driver) {
case HIDDriver::Default: { case HIDDriver::Default: {
// allocate report auto &report_data = hid->output_report;
CHAR *report_data = new CHAR[hid->caps.OutputReportByteLength] {}; auto &usage_list = hid->button_output_usages;
auto &usage_off_list = hid->button_output_usages_off;
std::fill(report_data.begin(), report_data.end(), 0);
// set buttons // set buttons
for (size_t cap_no = 0; cap_no < hid->button_output_caps_list.size(); cap_no++) { for (size_t cap_no = 0; cap_no < hid->button_output_caps_list.size(); cap_no++) {
@@ -2026,10 +2057,8 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
auto &button_state_list = hid->button_output_states[cap_no]; auto &button_state_list = hid->button_output_states[cap_no];
// determine which buttons to turn on // determine which buttons to turn on
std::vector<USAGE> usage_list; usage_list.clear();
std::vector<USAGE> usage_off_list; usage_off_list.clear();
usage_list.reserve(button_state_list.size());
usage_off_list.reserve(button_state_list.size());
for (size_t state_no = 0; state_no < button_state_list.size(); state_no++) { for (size_t state_no = 0; state_no < button_state_list.size(); state_no++) {
if (button_state_list[state_no]) { if (button_state_list[state_no]) {
usage_list.push_back(button_cap.Range.UsageMin + (USAGE) state_no); usage_list.push_back(button_cap.Range.UsageMin + (USAGE) state_no);
@@ -2044,15 +2073,18 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
HidP_Output, HidP_Output,
button_cap.UsagePage, button_cap.UsagePage,
button_cap.LinkCollection, button_cap.LinkCollection,
&usage_list[0], usage_list.data(),
&usage_list_length, &usage_list_length,
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()), reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
report_data, report_data.data(),
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) { hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
// flush report // flush report
HidD_SetOutputReport(hid->handle, report_data, hid->caps.OutputReportByteLength); HidD_SetOutputReport(
memset(report_data, 0, hid->caps.OutputReportByteLength); hid->handle,
report_data.data(),
hid->caps.OutputReportByteLength);
std::fill(report_data.begin(), report_data.end(), 0);
} }
// clear the buttons // clear the buttons
@@ -2061,22 +2093,22 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
HidP_Output, HidP_Output,
button_cap.UsagePage, button_cap.UsagePage,
button_cap.LinkCollection, button_cap.LinkCollection,
&usage_off_list[0], usage_off_list.data(),
&usage_off_list_length, &usage_off_list_length,
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()), reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
report_data, report_data.data(),
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) { hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
// flush report // flush report
DWORD written_bytes = 0; DWORD written_bytes = 0;
WriteFile( WriteFile(
hid->handle, hid->handle,
reinterpret_cast<void *>(report_data), report_data.data(),
hid->caps.OutputReportByteLength, hid->caps.OutputReportByteLength,
&written_bytes, &written_bytes,
nullptr nullptr
); );
memset(report_data, 0, hid->caps.OutputReportByteLength); std::fill(report_data.begin(), report_data.end(), 0);
} }
} }
@@ -2105,19 +2137,19 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
value_cap.NotRange.Usage, value_cap.NotRange.Usage,
static_cast<ULONG>(usage_value), static_cast<ULONG>(usage_value),
reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()), reinterpret_cast<PHIDP_PREPARSED_DATA>(hid->preparsed_data.get()),
report_data, report_data.data(),
hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) { hid->caps.OutputReportByteLength) == HIDP_STATUS_INCOMPATIBLE_REPORT_ID) {
// flush report // flush report
DWORD written_bytes = 0; DWORD written_bytes = 0;
WriteFile( WriteFile(
hid->handle, hid->handle,
reinterpret_cast<void *>(report_data), report_data.data(),
hid->caps.OutputReportByteLength, hid->caps.OutputReportByteLength,
&written_bytes, &written_bytes,
nullptr nullptr
); );
memset(report_data, 0, hid->caps.OutputReportByteLength); std::fill(report_data.begin(), report_data.end(), 0);
} }
} }
@@ -2151,15 +2183,12 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
DWORD written_bytes = 0; DWORD written_bytes = 0;
WriteFile( WriteFile(
hid->handle, hid->handle,
reinterpret_cast<void *>(report_data), report_data.data(),
hid->caps.OutputReportByteLength, hid->caps.OutputReportByteLength,
&written_bytes, &written_bytes,
nullptr nullptr
); );
// delete report
delete[] report_data;
break; break;
} }
case HIDDriver::PacDrive: { case HIDDriver::PacDrive: {
+14 -5
View File
@@ -319,7 +319,8 @@ namespace rawinput::touch {
} }
// iterate all input data and get touch points // iterate all input data and get touch points
std::vector<HIDTouchPoint> touch_points; thread_local std::vector<HIDTouchPoint> touch_points;
touch_points.clear();
touch_points.reserve(touch.elements_x.size()); touch_points.reserve(touch.elements_x.size());
for (size_t i = 0; i < touch.elements_x.size(); i++) { for (size_t i = 0; i < touch.elements_x.size(); i++) {
@@ -424,9 +425,15 @@ namespace rawinput::touch {
} }
// process touch points // process touch points
std::vector<DWORD> touch_removes; thread_local std::vector<DWORD> touch_removes;
std::vector<TouchPoint> touch_writes; thread_local std::vector<TouchPoint> touch_writes;
std::vector<DWORD> touch_modifications; thread_local std::vector<DWORD> touch_modifications;
touch_removes.clear();
touch_writes.clear();
touch_modifications.clear();
touch_removes.reserve(touch.touch_points.size() + touch_points.size());
touch_writes.reserve(touch_points.size());
touch_modifications.reserve(touch_points.size());
// drop every touch point with the given id (marking it as released) // drop every touch point with the given id (marking it as released)
auto remove_touch_point = [&] (DWORD id) { auto remove_touch_point = [&] (DWORD id) {
@@ -581,7 +588,9 @@ namespace rawinput::touch {
auto deadline = get_system_milliseconds() - 50; auto deadline = get_system_milliseconds() - 50;
// check touch points // check touch points
std::vector<DWORD> touch_removes; thread_local std::vector<DWORD> touch_removes;
touch_removes.clear();
touch_removes.reserve(touch.touch_points.size());
auto touch_it = touch.touch_points.begin(); auto touch_it = touch.touch_points.begin();
while (touch_it != touch.touch_points.end()) { while (touch_it != touch.touch_points.end()) {
auto &hid_tp = *touch_it; auto &hid_tp = *touch_it;
+7 -11
View File
@@ -1,13 +1,13 @@
#include "reader.h" #include "reader.h"
#include <chrono>
#include <cstring>
#include <filesystem> #include <filesystem>
#include <thread> #include <thread>
#include <cstring>
#include <vector> #include <vector>
#include "util/logging.h" #include "util/logging.h"
#include "misc/eamuse.h" #include "misc/eamuse.h"
#include "util/precise_timer.h"
#include "util/utils.h" #include "util/utils.h"
#include "structuredmessage.h" #include "structuredmessage.h"
@@ -218,8 +218,6 @@ bool Reader::set_comm_state(DWORD BaudRate) {
} }
bool Reader::wait_for_handshake() { bool Reader::wait_for_handshake() {
timeutils::PreciseSleepTimer timer;
// baud rates // baud rates
DWORD baud_rates[] = { CBR_57600, CBR_38400, CBR_19200, CBR_9600 }; DWORD baud_rates[] = { CBR_57600, CBR_38400, CBR_19200, CBR_9600 };
@@ -270,7 +268,7 @@ bool Reader::wait_for_handshake() {
} }
// sleep // sleep
timer.sleep(50); std::this_thread::sleep_for(std::chrono::milliseconds(50));
} }
log_warning("reader", "{}: no handshake received for {} baud", this->port, baud_rates[i]); log_warning("reader", "{}: no handshake received for {} baud", this->port, baud_rates[i]);
@@ -407,7 +405,6 @@ void start_reader_thread(const std::string &port, int id) {
READER_THREAD_RUNNING = true; READER_THREAD_RUNNING = true;
READER_THREADS.push_back(new std::thread([port, id]() { READER_THREADS.push_back(new std::thread([port, id]() {
log_info("reader", "{}: starting reader thread", port); log_info("reader", "{}: starting reader thread", port);
timeutils::PreciseSleepTimer timer;
while (READER_THREAD_RUNNING) { while (READER_THREAD_RUNNING) {
@@ -445,22 +442,21 @@ void start_reader_thread(const std::string &port, int id) {
} }
if (did_read_card) { if (did_read_card) {
timer.sleep(2500); std::this_thread::sleep_for(std::chrono::milliseconds(2500));
} }
timer.sleep(20); std::this_thread::sleep_for(std::chrono::milliseconds(20));
} }
} }
// sleep between reader connection retries // sleep between reader connection retries
if (READER_THREAD_RUNNING) if (READER_THREAD_RUNNING)
timer.sleep(5000); std::this_thread::sleep_for(std::chrono::milliseconds(5000));
} }
})); }));
// wait for thread to start // wait for thread to start
timeutils::PreciseSleepTimer timer; std::this_thread::sleep_for(std::chrono::milliseconds(10));
timer.sleep(10);
} }
void stop_reader_thread() { void stop_reader_thread() {
+2 -2
View File
@@ -41,7 +41,7 @@ namespace nativetouch::inject {
} }
POINT transformed = *position; POINT transformed = *position;
return transform::screen_to_game(window, &transformed); return transform::mouse_to_game(window, &transformed);
} }
// release the active injected contact and its window capture // release the active injected contact and its window capture
@@ -108,7 +108,7 @@ namespace nativetouch::inject {
} }
POINT transformed = position; POINT transformed = position;
if (!transform::screen_to_game(window, &transformed)) { if (!transform::mouse_to_game(window, &transformed)) {
end_mouse_contact(window); end_mouse_contact(window);
return; return;
} }
+20 -1
View File
@@ -68,7 +68,7 @@ namespace nativetouch::transform {
static bool has_active_overlay_transform() { static bool has_active_overlay_transform() {
return overlay::OVERLAY != nullptr && return overlay::OVERLAY != nullptr &&
overlay::OVERLAY->get_active() && overlay::OVERLAY->get_active() &&
overlay::OVERLAY->can_transform_touch_input(); overlay::OVERLAY->has_subscreen_touch_transform();
} }
static bool transform_overlay_touch_position(POINT *position) { static bool transform_overlay_touch_position(POINT *position) {
@@ -120,6 +120,25 @@ namespace nativetouch::transform {
return transform_overlay_touch_position(position); return transform_overlay_touch_position(position);
} }
bool mouse_to_game(HWND window, POINT *position) {
// exception: iidx tdj dedicated subscreen window is allowed
if (is_tdj_dedicated_subscreen(window)) {
return screen_to_game(window, position);
}
// if this game has a subscreen overlay that can transform touch input
// but the window is hidden or not under the cursor, reject mouse-as-touch
// (e.g., iidx/sdvx are rejected here, but nostalgia is allowed)
if (overlay::OVERLAY != nullptr &&
overlay::OVERLAY->has_subscreen_touch_transform() &&
!overlay::OVERLAY->accepts_subscreen_mouse_input()) {
return false;
}
return screen_to_game(window, position);
}
// route hardware screen coordinates through dedicated or overlay mapping and report the result // route hardware screen coordinates through dedicated or overlay mapping and report the result
Result hardware_to_game(POINT *position) { Result hardware_to_game(POINT *position) {
const auto dedicated_subscreen = is_tdj_dedicated_subscreen(TDJ_SUBSCREEN_WINDOW); const auto dedicated_subscreen = is_tdj_dedicated_subscreen(TDJ_SUBSCREEN_WINDOW);
+1
View File
@@ -12,5 +12,6 @@ namespace nativetouch::transform {
bool is_tdj_dedicated_subscreen(HWND window); bool is_tdj_dedicated_subscreen(HWND window);
bool game_to_screen(HWND window, POINT *position); bool game_to_screen(HWND window, POINT *position);
bool screen_to_game(HWND window, POINT *position); bool screen_to_game(HWND window, POINT *position);
bool mouse_to_game(HWND window, POINT *position);
Result hardware_to_game(POINT *position); Result hardware_to_game(POINT *position);
} }
+2 -2
View File
@@ -944,7 +944,7 @@ void touch_get_points(std::vector<TouchPoint> &touch_points, bool overlay) {
if (!overlay && if (!overlay &&
overlay::OVERLAY && overlay::OVERLAY &&
overlay::OVERLAY->get_active() && overlay::OVERLAY->get_active() &&
!overlay::OVERLAY->can_transform_touch_input() && !overlay::OVERLAY->has_subscreen_touch_transform() &&
ImGui::GetIO().WantCaptureMouse) { ImGui::GetIO().WantCaptureMouse) {
return; return;
@@ -971,7 +971,7 @@ void touch_get_events(std::vector<TouchEvent> &touch_events, bool overlay) {
if (!overlay && if (!overlay &&
overlay::OVERLAY && overlay::OVERLAY &&
overlay::OVERLAY->get_active() && overlay::OVERLAY->get_active() &&
!overlay::OVERLAY->can_transform_touch_input() && !overlay::OVERLAY->has_subscreen_touch_transform() &&
ImGui::GetIO().WantCaptureMouse) { ImGui::GetIO().WantCaptureMouse) {
TOUCH_EVENTS.reset(); TOUCH_EVENTS.reset();