[HID] Redesigned support for Portals
- Added hotplug support on Windows - Added support for XamInputNonControllerGetRawEx & XamInputNonControllerSetRawEx
This commit is contained in:
committed by
Radosław Gliński
parent
3b8debcf5b
commit
d37c22aad0
@@ -20,7 +20,7 @@ add_subdirectory(gpu/null)
|
|||||||
add_subdirectory(gpu/vulkan)
|
add_subdirectory(gpu/vulkan)
|
||||||
add_subdirectory(hid)
|
add_subdirectory(hid)
|
||||||
add_subdirectory(hid/nop)
|
add_subdirectory(hid/nop)
|
||||||
add_subdirectory(hid/skylander)
|
add_subdirectory(hid/portal)
|
||||||
add_subdirectory(kernel)
|
add_subdirectory(kernel)
|
||||||
add_subdirectory(patcher)
|
add_subdirectory(patcher)
|
||||||
add_subdirectory(ui)
|
add_subdirectory(ui)
|
||||||
|
|||||||
@@ -297,6 +297,20 @@ void EmulatorWindow::EmulatorWindowListener::OnMouseUp(ui::MouseEvent& e) {
|
|||||||
emulator_window_.OnMouseUp(e);
|
emulator_window_.OnMouseUp(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EmulatorWindow::EmulatorWindowListener::OnUsbDeviceChanged(
|
||||||
|
bool is_arrival) {
|
||||||
|
auto* portal = emulator_window_.emulator()->input_system()->GetPortal();
|
||||||
|
if (!portal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_arrival) {
|
||||||
|
portal->OnDeviceArrival();
|
||||||
|
} else {
|
||||||
|
portal->OnDeviceRemoval();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void EmulatorWindow::DisplayConfigGameConfigLoadCallback::PostGameConfigLoad() {
|
void EmulatorWindow::DisplayConfigGameConfigLoadCallback::PostGameConfigLoad() {
|
||||||
emulator_window_.ApplyDisplayConfigForCvars();
|
emulator_window_.ApplyDisplayConfigForCvars();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,6 +150,8 @@ class EmulatorWindow {
|
|||||||
void OnMouseDown(ui::MouseEvent& e) override;
|
void OnMouseDown(ui::MouseEvent& e) override;
|
||||||
void OnMouseUp(ui::MouseEvent& e) override;
|
void OnMouseUp(ui::MouseEvent& e) override;
|
||||||
|
|
||||||
|
void OnUsbDeviceChanged(bool is_arrival) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EmulatorWindow& emulator_window_;
|
EmulatorWindow& emulator_window_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ if(_hid_demo AND _hid_srcs)
|
|||||||
list(REMOVE_ITEM _hid_srcs ${_hid_demo})
|
list(REMOVE_ITEM _hid_srcs ${_hid_demo})
|
||||||
set_target_properties(xenia-hid PROPERTIES SOURCES "${_hid_srcs}")
|
set_target_properties(xenia-hid PROPERTIES SOURCES "${_hid_srcs}")
|
||||||
endif()
|
endif()
|
||||||
target_link_libraries(xenia-hid PUBLIC xenia-base xenia-hid-skylander)
|
target_link_libraries(xenia-hid PUBLIC xenia-base xenia-hid-portal)
|
||||||
xe_target_defaults(xenia-hid)
|
xe_target_defaults(xenia-hid)
|
||||||
|
|||||||
@@ -16,9 +16,8 @@
|
|||||||
#include "xenia/hid/input_driver.h"
|
#include "xenia/hid/input_driver.h"
|
||||||
#include "xenia/kernel/util/shim_utils.h"
|
#include "xenia/kernel/util/shim_utils.h"
|
||||||
|
|
||||||
#include "xenia/hid/skylander/skylander_emulated.h"
|
|
||||||
#ifdef XE_PLATFORM_WIN32
|
#ifdef XE_PLATFORM_WIN32
|
||||||
#include "xenia/hid/skylander/skylander_hardware.h"
|
#include "xenia/hid/portal/hardware_portal.h"
|
||||||
#endif // XE_PLATFORM_WIN32
|
#endif // XE_PLATFORM_WIN32
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
@@ -34,10 +33,8 @@ DEFINE_double(
|
|||||||
"Defines deadzone level for right stick. Allowed range [0.0-1.0].", "HID");
|
"Defines deadzone level for right stick. Allowed range [0.0-1.0].", "HID");
|
||||||
|
|
||||||
InputSystem::InputSystem(xe::ui::Window* window) : window_(window) {
|
InputSystem::InputSystem(xe::ui::Window* window) : window_(window) {
|
||||||
skylander_portal_ = std::make_unique<SkylanderPortalEmulated>();
|
|
||||||
|
|
||||||
#ifdef XE_PLATFORM_WIN32
|
#ifdef XE_PLATFORM_WIN32
|
||||||
skylander_portal_ = std::make_unique<SkylanderPortalLibusb>();
|
portal_ = std::make_unique<HardwarePortal>();
|
||||||
#endif // XE_PLATFORM_WIN32
|
#endif // XE_PLATFORM_WIN32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
#include "xenia/base/mutex.h"
|
#include "xenia/base/mutex.h"
|
||||||
#include "xenia/hid/input.h"
|
#include "xenia/hid/input.h"
|
||||||
#include "xenia/hid/input_driver.h"
|
#include "xenia/hid/input_driver.h"
|
||||||
#include "xenia/hid/skylander/skylander_portal.h"
|
#include "xenia/hid/portal/portal.h"
|
||||||
#include "xenia/xbox.h"
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
@@ -56,7 +56,7 @@ class InputSystem {
|
|||||||
|
|
||||||
uint32_t GetLastUsedSlot() const { return last_used_slot; }
|
uint32_t GetLastUsedSlot() const { return last_used_slot; }
|
||||||
|
|
||||||
SkylanderPortal* GetSkylanderPortal() { return skylander_portal_.get(); }
|
Portal* GetPortal() { return portal_.get(); }
|
||||||
|
|
||||||
std::unique_lock<xe_unlikely_mutex> lock();
|
std::unique_lock<xe_unlikely_mutex> lock();
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ class InputSystem {
|
|||||||
|
|
||||||
std::vector<std::unique_ptr<InputDriver>> drivers_;
|
std::vector<std::unique_ptr<InputDriver>> drivers_;
|
||||||
|
|
||||||
std::unique_ptr<SkylanderPortal> skylander_portal_;
|
std::unique_ptr<Portal> portal_;
|
||||||
|
|
||||||
std::bitset<XUserMaxUserCount> connected_slots = {};
|
std::bitset<XUserMaxUserCount> connected_slots = {};
|
||||||
std::array<std::pair<joystick_value, joystick_value>, XUserMaxUserCount>
|
std::array<std::pair<joystick_value, joystick_value>, XUserMaxUserCount>
|
||||||
|
|||||||
13
src/xenia/hid/portal/CMakeLists.txt
Normal file
13
src/xenia/hid/portal/CMakeLists.txt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
add_library(xenia-hid-portal STATIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/portal.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/portal.cc
|
||||||
|
)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
target_sources(xenia-hid-portal PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/hardware_portal.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/hardware_portal.cc
|
||||||
|
)
|
||||||
|
target_link_libraries(xenia-hid-portal PUBLIC libusb)
|
||||||
|
endif()
|
||||||
|
xe_target_defaults(xenia-hid-portal)
|
||||||
120
src/xenia/hid/portal/hardware_portal.cc
Normal file
120
src/xenia/hid/portal/hardware_portal.cc
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2025 Xenia Canary. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "xenia/hid/portal/hardware_portal.h"
|
||||||
|
#include "xenia/base/logging.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace hid {
|
||||||
|
|
||||||
|
HardwarePortal::HardwarePortal() : Portal() {
|
||||||
|
libusb_init(&context_);
|
||||||
|
OpenDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
HardwarePortal::~HardwarePortal() {
|
||||||
|
if (handle_) {
|
||||||
|
CloseDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
libusb_exit(context_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwarePortal::OpenDevice() {
|
||||||
|
if (!context_ || connected_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
libusb_device** devs;
|
||||||
|
const ssize_t cnt = libusb_get_device_list(context_, &devs);
|
||||||
|
if (cnt < 0) {
|
||||||
|
// No device available... It might appear later.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ssize_t i = 0; i < cnt; ++i) {
|
||||||
|
libusb_device* dev = devs[i];
|
||||||
|
|
||||||
|
libusb_device_descriptor desc;
|
||||||
|
if (libusb_get_device_descriptor(dev, &desc) == 0) {
|
||||||
|
// Check if this device matches the target Vendor ID and Product ID.
|
||||||
|
// Small limitation. We're only supporting one device being connected at
|
||||||
|
// the time.
|
||||||
|
for (const auto& entry : kPortalVendorProductIdList) {
|
||||||
|
if (desc.idVendor == entry.first && desc.idProduct == entry.second) {
|
||||||
|
if (libusb_open(dev, &handle_) == 0) {
|
||||||
|
libusb_claim_interface(handle_, 0);
|
||||||
|
connected_ = true;
|
||||||
|
// We found device. No need to go thorugh remaining IDs.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We found device. No need to go thorugh remaining devices.
|
||||||
|
if (connected_) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
libusb_free_device_list(devs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwarePortal::CloseDevice() {
|
||||||
|
if (!connected_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
libusb_release_interface(handle_, 0);
|
||||||
|
libusb_close(handle_);
|
||||||
|
connected_ = false;
|
||||||
|
handle_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
X_STATUS HardwarePortal::ReadInternal(std::span<uint8_t> data,
|
||||||
|
int32_t& read_count) {
|
||||||
|
const int result = libusb_interrupt_transfer(
|
||||||
|
handle_, read_endpoint, data.data(), static_cast<int>(data.size()),
|
||||||
|
&read_count, timeout);
|
||||||
|
|
||||||
|
switch (result) {
|
||||||
|
case LIBUSB_ERROR_NO_DEVICE:
|
||||||
|
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||||
|
break;
|
||||||
|
case LIBUSB_ERROR_TIMEOUT:
|
||||||
|
return X_ERROR_SUCCESS;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result < 0) {
|
||||||
|
XELOGW("Portal[Read] returned error: {:08X}", result);
|
||||||
|
return X_ERROR_FUNCTION_FAILED;
|
||||||
|
}
|
||||||
|
return X_ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
X_STATUS HardwarePortal::WriteInternal(std::span<uint8_t> data) {
|
||||||
|
const int result = libusb_interrupt_transfer(
|
||||||
|
handle_, write_endpoint, data.data(), static_cast<int>(data.size()),
|
||||||
|
nullptr, timeout);
|
||||||
|
|
||||||
|
if (result < 0) {
|
||||||
|
XELOGW("Portal[Write] returned error: {:08X}", result);
|
||||||
|
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return X_ERROR_SUCCESS;
|
||||||
|
};
|
||||||
|
|
||||||
|
void HardwarePortal::OnDeviceArrival() { OpenDevice(); };
|
||||||
|
|
||||||
|
void HardwarePortal::OnDeviceRemoval() { CloseDevice(); };
|
||||||
|
|
||||||
|
} // namespace hid
|
||||||
|
} // namespace xe
|
||||||
52
src/xenia/hid/portal/hardware_portal.h
Normal file
52
src/xenia/hid/portal/hardware_portal.h
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2025 Xenia Canary. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_HID_PORTAL_HARDWARE_PORTAL_H_
|
||||||
|
#define XENIA_HID_PORTAL_HARDWARE_PORTAL_H_
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include "xenia/hid/portal/portal.h"
|
||||||
|
|
||||||
|
#include "third_party/libusb/libusb/libusb.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace hid {
|
||||||
|
|
||||||
|
constexpr std::array<std::pair<uint16_t, uint16_t>, 2>
|
||||||
|
kPortalVendorProductIdList = {
|
||||||
|
std::pair<uint16_t, uint16_t>{0x1430, 0x1F17},
|
||||||
|
std::pair<uint16_t, uint16_t>{0x24C6, 0xFA00}};
|
||||||
|
|
||||||
|
class HardwarePortal final : public Portal {
|
||||||
|
public:
|
||||||
|
HardwarePortal();
|
||||||
|
~HardwarePortal() override;
|
||||||
|
|
||||||
|
virtual void OnDeviceArrival() override;
|
||||||
|
virtual void OnDeviceRemoval() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual void OpenDevice() override;
|
||||||
|
virtual void CloseDevice() override;
|
||||||
|
virtual X_STATUS ReadInternal(std::span<uint8_t> data,
|
||||||
|
int32_t& read_count) override;
|
||||||
|
virtual X_STATUS WriteInternal(std::span<uint8_t> data) override;
|
||||||
|
|
||||||
|
const uint8_t read_endpoint = 0x81;
|
||||||
|
const uint8_t write_endpoint = 0x02;
|
||||||
|
const uint16_t timeout = 100;
|
||||||
|
|
||||||
|
libusb_context* context_ = nullptr;
|
||||||
|
libusb_device_handle* handle_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace hid
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_HID_PORTAL_HARDWARE_PORTAL_H_
|
||||||
74
src/xenia/hid/portal/portal.cc
Normal file
74
src/xenia/hid/portal/portal.cc
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2025 Xenia Canary. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "xenia/hid/portal/portal.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace hid {
|
||||||
|
|
||||||
|
Portal::Portal() {}
|
||||||
|
Portal::~Portal() {}
|
||||||
|
|
||||||
|
bool Portal::IsConnected() {
|
||||||
|
std::lock_guard<xe_mutex> guard(lock_);
|
||||||
|
return connected_;
|
||||||
|
}
|
||||||
|
|
||||||
|
X_STATUS Portal::Read(std::span<uint8_t> data, uint32_t& bytes_read,
|
||||||
|
uint16_t& state) {
|
||||||
|
std::lock_guard<xe_mutex> guard(lock_);
|
||||||
|
|
||||||
|
if (!connected_) {
|
||||||
|
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.size() > kPortalBufferSize) {
|
||||||
|
return X_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t read_count = 0;
|
||||||
|
X_STATUS status = ReadInternal(data, read_count);
|
||||||
|
|
||||||
|
if (XSUCCEEDED(status)) {
|
||||||
|
// According to XAM decompilation logic here is reversed. Difference in
|
||||||
|
// status should return 1, but these values are taken from internal parts of
|
||||||
|
// xinput raw implementation, so it might be completely opposite.
|
||||||
|
state = previous_status_ == status;
|
||||||
|
bytes_read = read_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
previous_status_ = status;
|
||||||
|
|
||||||
|
if (status == X_ERROR_DEVICE_NOT_CONNECTED) {
|
||||||
|
CloseDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
X_STATUS Portal::Write(std::span<uint8_t> data) {
|
||||||
|
std::lock_guard<xe_mutex> guard(lock_);
|
||||||
|
|
||||||
|
if (!connected_) {
|
||||||
|
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.size() > kPortalBufferSize) {
|
||||||
|
return X_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
const X_STATUS status = WriteInternal(data);
|
||||||
|
if (status == X_ERROR_DEVICE_NOT_CONNECTED) {
|
||||||
|
CloseDevice();
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace hid
|
||||||
|
} // namespace xe
|
||||||
55
src/xenia/hid/portal/portal.h
Normal file
55
src/xenia/hid/portal/portal.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2025 Xenia Canary. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_HID_PORTAL_PORTAL_H_
|
||||||
|
#define XENIA_HID_PORTAL_PORTAL_H_
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
#include "xenia/base/mutex.h"
|
||||||
|
#include "xenia/xbox.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace hid {
|
||||||
|
|
||||||
|
constexpr uint8_t kPortalBufferSize = 0x20;
|
||||||
|
|
||||||
|
class Portal {
|
||||||
|
public:
|
||||||
|
Portal();
|
||||||
|
virtual ~Portal();
|
||||||
|
|
||||||
|
bool IsConnected();
|
||||||
|
|
||||||
|
virtual X_STATUS Read(std::span<uint8_t> data, uint32_t& bytes_read,
|
||||||
|
uint16_t& state);
|
||||||
|
virtual X_STATUS Write(std::span<uint8_t> data);
|
||||||
|
|
||||||
|
virtual void OnDeviceArrival() = 0;
|
||||||
|
virtual void OnDeviceRemoval() = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool connected_ = false;
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual void OpenDevice() = 0;
|
||||||
|
virtual void CloseDevice() = 0;
|
||||||
|
|
||||||
|
virtual X_STATUS ReadInternal(std::span<uint8_t> data,
|
||||||
|
int32_t& read_count) = 0;
|
||||||
|
virtual X_STATUS WriteInternal(std::span<uint8_t> data) = 0;
|
||||||
|
|
||||||
|
xe_mutex lock_;
|
||||||
|
X_STATUS previous_status_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace hid
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
add_library(xenia-hid-skylander STATIC
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/skylander_portal.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/skylander_portal.cc
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/skylander_emulated.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/skylander_emulated.cc
|
|
||||||
)
|
|
||||||
if(WIN32)
|
|
||||||
target_sources(xenia-hid-skylander PRIVATE
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/skylander_hardware.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/skylander_hardware.cc
|
|
||||||
)
|
|
||||||
target_link_libraries(xenia-hid-skylander PUBLIC libusb)
|
|
||||||
endif()
|
|
||||||
xe_target_defaults(xenia-hid-skylander)
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
|
||||||
******************************************************************************
|
|
||||||
* Copyright 2025 Xenia Canary. All rights reserved. *
|
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "xenia/hid/skylander/skylander_emulated.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace hid {
|
|
||||||
|
|
||||||
SkylanderPortalEmulated::SkylanderPortalEmulated() : SkylanderPortal() {}
|
|
||||||
|
|
||||||
SkylanderPortalEmulated::~SkylanderPortalEmulated() {}
|
|
||||||
|
|
||||||
bool SkylanderPortalEmulated::init_device() { return false; }
|
|
||||||
|
|
||||||
void SkylanderPortalEmulated::destroy_device() {}
|
|
||||||
|
|
||||||
X_STATUS SkylanderPortalEmulated::read(std::vector<uint8_t>& data) {
|
|
||||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
X_STATUS SkylanderPortalEmulated::write(std::vector<uint8_t>& data) {
|
|
||||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace hid
|
|
||||||
} // namespace xe
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
|
||||||
******************************************************************************
|
|
||||||
* Copyright 2025 Xenia Canary. All rights reserved. *
|
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef XENIA_HID_SKYLANDER_SKYLANDER_PORTAL_EMULATED_H_
|
|
||||||
#define XENIA_HID_SKYLANDER_SKYLANDER_PORTAL_EMULATED_H_
|
|
||||||
|
|
||||||
#include "xenia/hid/skylander/skylander_portal.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace hid {
|
|
||||||
|
|
||||||
class SkylanderPortalEmulated final : public SkylanderPortal {
|
|
||||||
public:
|
|
||||||
SkylanderPortalEmulated();
|
|
||||||
~SkylanderPortalEmulated() override;
|
|
||||||
|
|
||||||
X_STATUS read(std::vector<uint8_t>& data) override;
|
|
||||||
X_STATUS write(std::vector<uint8_t>& data) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool init_device() override;
|
|
||||||
void destroy_device() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace hid
|
|
||||||
} // namespace xe
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
|
||||||
******************************************************************************
|
|
||||||
* Copyright 2025 Xenia Canary. All rights reserved. *
|
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "xenia/hid/skylander/skylander_hardware.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace hid {
|
|
||||||
|
|
||||||
SkylanderPortalLibusb::SkylanderPortalLibusb() : SkylanderPortal() {
|
|
||||||
libusb_init(&context_);
|
|
||||||
}
|
|
||||||
|
|
||||||
SkylanderPortalLibusb::~SkylanderPortalLibusb() {
|
|
||||||
if (handle_) {
|
|
||||||
destroy_device();
|
|
||||||
}
|
|
||||||
|
|
||||||
libusb_exit(context_);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SkylanderPortalLibusb::init_device() {
|
|
||||||
if (!context_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
libusb_device** devs;
|
|
||||||
ssize_t cnt = libusb_get_device_list(context_, &devs);
|
|
||||||
if (cnt < 0) {
|
|
||||||
// No device available... It might appear later.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool device_found = false;
|
|
||||||
for (ssize_t i = 0; i < cnt; ++i) {
|
|
||||||
libusb_device* dev = devs[i];
|
|
||||||
|
|
||||||
struct libusb_device_descriptor desc;
|
|
||||||
if (libusb_get_device_descriptor(dev, &desc) == 0) {
|
|
||||||
// Check if this device matches the target Vendor ID and Product ID
|
|
||||||
if (desc.idVendor == portal_vendor_product_id.first &&
|
|
||||||
desc.idProduct == portal_vendor_product_id.second) {
|
|
||||||
libusb_device_handle* handle;
|
|
||||||
if (libusb_open(dev, &handle) == 0) {
|
|
||||||
libusb_claim_interface(handle, 0);
|
|
||||||
handle_ = reinterpret_cast<uintptr_t*>(handle);
|
|
||||||
device_found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
libusb_free_device_list(devs, 0);
|
|
||||||
|
|
||||||
return device_found;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SkylanderPortalLibusb::destroy_device() {
|
|
||||||
libusb_release_interface(reinterpret_cast<libusb_device_handle*>(handle_), 0);
|
|
||||||
libusb_close(reinterpret_cast<libusb_device_handle*>(handle_));
|
|
||||||
handle_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
X_STATUS SkylanderPortalLibusb::read(std::vector<uint8_t>& data) {
|
|
||||||
if (!is_initialized()) {
|
|
||||||
if (!init_device()) {
|
|
||||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.size() > skylander_buffer_size) {
|
|
||||||
return X_ERROR_INVALID_PARAMETER;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int result = libusb_interrupt_transfer(
|
|
||||||
reinterpret_cast<libusb_device_handle*>(handle_), read_endpoint,
|
|
||||||
data.data(), static_cast<int>(data.size()), nullptr, timeout);
|
|
||||||
|
|
||||||
if (result == LIBUSB_ERROR_NO_DEVICE) {
|
|
||||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result < 0) {
|
|
||||||
destroy_device();
|
|
||||||
}
|
|
||||||
|
|
||||||
return X_ERROR_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
X_STATUS SkylanderPortalLibusb::write(std::vector<uint8_t>& data) {
|
|
||||||
if (!is_initialized()) {
|
|
||||||
if (!init_device()) {
|
|
||||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.size() > skylander_buffer_size) {
|
|
||||||
return X_ERROR_INVALID_PARAMETER;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int result = libusb_interrupt_transfer(
|
|
||||||
reinterpret_cast<libusb_device_handle*>(handle_), write_endpoint,
|
|
||||||
data.data(), static_cast<int>(data.size()), nullptr, timeout);
|
|
||||||
|
|
||||||
if (result == LIBUSB_ERROR_NO_DEVICE) {
|
|
||||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result < 0) {
|
|
||||||
destroy_device();
|
|
||||||
}
|
|
||||||
|
|
||||||
return X_ERROR_SUCCESS;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace hid
|
|
||||||
} // namespace xe
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
|
||||||
******************************************************************************
|
|
||||||
* Copyright 2025 Xenia Canary. All rights reserved. *
|
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef XENIA_HID_SKYLANDER_SKYLANDER_PORTAL_LIBUSB_H_
|
|
||||||
#define XENIA_HID_SKYLANDER_SKYLANDER_PORTAL_LIBUSB_H_
|
|
||||||
|
|
||||||
// Include order is important here. Including skylander_portal.h after libusb
|
|
||||||
// will cause conflicts.
|
|
||||||
#include "xenia/hid/skylander/skylander_portal.h"
|
|
||||||
|
|
||||||
#include "third_party/libusb/libusb/libusb.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace hid {
|
|
||||||
|
|
||||||
class SkylanderPortalLibusb final : public SkylanderPortal {
|
|
||||||
public:
|
|
||||||
SkylanderPortalLibusb();
|
|
||||||
~SkylanderPortalLibusb() override;
|
|
||||||
|
|
||||||
X_STATUS read(std::vector<uint8_t>& data) override;
|
|
||||||
X_STATUS write(std::vector<uint8_t>& data) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool init_device() override;
|
|
||||||
void destroy_device() override;
|
|
||||||
|
|
||||||
bool is_initialized() const { return handle_ != nullptr; }
|
|
||||||
|
|
||||||
const uint8_t read_endpoint = 0x81;
|
|
||||||
const uint8_t write_endpoint = 0x02;
|
|
||||||
const uint16_t timeout = 300;
|
|
||||||
|
|
||||||
libusb_context* context_ = nullptr;
|
|
||||||
uintptr_t* handle_ = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace hid
|
|
||||||
} // namespace xe
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
|
||||||
******************************************************************************
|
|
||||||
* Copyright 2025 Xenia Canary. All rights reserved. *
|
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "xenia/hid/skylander/skylander_portal.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace hid {
|
|
||||||
|
|
||||||
SkylanderPortal::SkylanderPortal() {}
|
|
||||||
SkylanderPortal::~SkylanderPortal() {}
|
|
||||||
|
|
||||||
} // namespace hid
|
|
||||||
} // namespace xe
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/**
|
|
||||||
******************************************************************************
|
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
|
||||||
******************************************************************************
|
|
||||||
* Copyright 2025 Xenia Canary. All rights reserved. *
|
|
||||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
||||||
******************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef XENIA_HID_SKYLANDER_SKYLANDER_PORTAL_H_
|
|
||||||
#define XENIA_HID_SKYLANDER_SKYLANDER_PORTAL_H_
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "xenia/xbox.h"
|
|
||||||
|
|
||||||
namespace xe {
|
|
||||||
namespace hid {
|
|
||||||
|
|
||||||
constexpr std::pair<uint16_t, uint16_t> portal_vendor_product_id = {0x1430,
|
|
||||||
0x1F17};
|
|
||||||
constexpr uint8_t skylander_buffer_size = 0x20;
|
|
||||||
|
|
||||||
class SkylanderPortal {
|
|
||||||
public:
|
|
||||||
SkylanderPortal();
|
|
||||||
virtual ~SkylanderPortal();
|
|
||||||
|
|
||||||
virtual X_STATUS read(std::vector<uint8_t>& data) = 0;
|
|
||||||
virtual X_STATUS write(std::vector<uint8_t>& data) = 0;
|
|
||||||
|
|
||||||
private:
|
|
||||||
virtual bool init_device() = 0;
|
|
||||||
virtual void destroy_device() = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace hid
|
|
||||||
} // namespace xe
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -246,43 +246,76 @@ X_HRESULT_result_t XamUserGetDeviceContext_entry(dword_t user_index,
|
|||||||
}
|
}
|
||||||
DECLARE_XAM_EXPORT1(XamUserGetDeviceContext, kInput, kStub);
|
DECLARE_XAM_EXPORT1(XamUserGetDeviceContext, kInput, kStub);
|
||||||
|
|
||||||
X_HRESULT_result_t XamInputNonControllerGetRaw_entry(
|
X_HRESULT_result_t XamInputNonControllerGetRawEx_entry(
|
||||||
lpdword_t state_ptr, lpdword_t buffer_length_ptr, lpdword_t buffer_ptr) {
|
dword_t device_id, lpdword_t buffer_ptr, lpdword_t buffer_length_ptr,
|
||||||
|
lpword_t state_ptr) {
|
||||||
|
if (device_id != 5 && device_id != 6) {
|
||||||
|
return X_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
if (!state_ptr || !buffer_length_ptr || !buffer_ptr) {
|
if (!state_ptr || !buffer_length_ptr || !buffer_ptr) {
|
||||||
return X_ERROR_INVALID_PARAMETER;
|
return X_ERROR_INVALID_PARAMETER;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint32_t data_size = *buffer_length_ptr;
|
if (*buffer_length_ptr == 0 || *buffer_length_ptr > hid::kPortalBufferSize) {
|
||||||
|
|
||||||
if (data_size == 0 || data_size > 0x20) {
|
|
||||||
return X_ERROR_INVALID_PARAMETER;
|
return X_ERROR_INVALID_PARAMETER;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto input_system = kernel_state()->emulator()->input_system();
|
auto portal = kernel_state()->emulator()->input_system()->GetPortal();
|
||||||
|
if (!portal) {
|
||||||
|
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> data(data_size, 0);
|
uint32_t bytes_read = *buffer_length_ptr;
|
||||||
const auto result = input_system->GetSkylanderPortal()->read(data);
|
uint16_t state = 0;
|
||||||
*state_ptr = 1;
|
|
||||||
memcpy(buffer_ptr, data.data(), data.size());
|
|
||||||
|
|
||||||
|
const auto result = portal->Read(
|
||||||
|
{kernel_memory()->TranslateVirtual(buffer_ptr.guest_address()),
|
||||||
|
*buffer_length_ptr},
|
||||||
|
bytes_read, state);
|
||||||
|
|
||||||
|
if (XSUCCEEDED(result)) {
|
||||||
|
*buffer_length_ptr = bytes_read;
|
||||||
|
*state_ptr = state;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
DECLARE_XAM_EXPORT1(XamInputNonControllerGetRaw, kInput, kStub);
|
DECLARE_XAM_EXPORT1(XamInputNonControllerGetRawEx, kInput, kSketchy);
|
||||||
|
|
||||||
|
X_HRESULT_result_t XamInputNonControllerSetRawEx_entry(dword_t device_id,
|
||||||
|
lpdword_t buffer_ptr,
|
||||||
|
dword_t buffer_length) {
|
||||||
|
if (device_id != 5 && device_id != 6) {
|
||||||
|
return X_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
if (!buffer_ptr || !buffer_length || buffer_length > hid::kPortalBufferSize) {
|
||||||
|
return X_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto portal = kernel_state()->emulator()->input_system()->GetPortal();
|
||||||
|
if (!portal) {
|
||||||
|
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return portal->Write(
|
||||||
|
{kernel_memory()->TranslateVirtual(buffer_ptr.guest_address()),
|
||||||
|
buffer_length});
|
||||||
|
}
|
||||||
|
DECLARE_XAM_EXPORT1(XamInputNonControllerSetRawEx, kInput, kSketchy);
|
||||||
|
|
||||||
|
X_HRESULT_result_t XamInputNonControllerGetRaw_entry(
|
||||||
|
lpword_t state_ptr, lpdword_t buffer_length_ptr, lpdword_t buffer_ptr) {
|
||||||
|
return XamInputNonControllerGetRawEx_entry(5, buffer_ptr, buffer_length_ptr,
|
||||||
|
state_ptr);
|
||||||
|
}
|
||||||
|
DECLARE_XAM_EXPORT1(XamInputNonControllerGetRaw, kInput, kSketchy);
|
||||||
|
|
||||||
X_HRESULT_result_t XamInputNonControllerSetRaw_entry(dword_t buffer_length,
|
X_HRESULT_result_t XamInputNonControllerSetRaw_entry(dword_t buffer_length,
|
||||||
lpdword_t buffer_ptr) {
|
lpdword_t buffer_ptr) {
|
||||||
if (!buffer_ptr || !buffer_length || buffer_length > 0x20) {
|
// Normally there are handled separatelly with different first param, but
|
||||||
return X_ERROR_INVALID_PARAMETER;
|
// whatever.
|
||||||
|
return XamInputNonControllerSetRawEx_entry(5, buffer_ptr, buffer_length);
|
||||||
}
|
}
|
||||||
|
DECLARE_XAM_EXPORT1(XamInputNonControllerSetRaw, kInput, kSketchy);
|
||||||
auto input_system = kernel_state()->emulator()->input_system();
|
|
||||||
|
|
||||||
std::vector<uint8_t> data(buffer_length, 0);
|
|
||||||
memcpy(data.data(), buffer_ptr, buffer_length);
|
|
||||||
|
|
||||||
return input_system->GetSkylanderPortal()->write(data);
|
|
||||||
}
|
|
||||||
DECLARE_XAM_EXPORT1(XamInputNonControllerSetRaw, kInput, kStub);
|
|
||||||
|
|
||||||
} // namespace xam
|
} // namespace xam
|
||||||
} // namespace kernel
|
} // namespace kernel
|
||||||
|
|||||||
@@ -490,6 +490,13 @@ void Window::OnMonitorUpdate(MonitorUpdateEvent& e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::OnUsbDeviceChanged(
|
||||||
|
bool is_arrival, WindowDestructionReceiver& destruction_receiver) {
|
||||||
|
SendEventToListeners(
|
||||||
|
[is_arrival](auto listener) { listener->OnUsbDeviceChanged(is_arrival); },
|
||||||
|
destruction_receiver);
|
||||||
|
}
|
||||||
|
|
||||||
bool Window::OnActualSizeUpdate(
|
bool Window::OnActualSizeUpdate(
|
||||||
uint32_t new_physical_width, uint32_t new_physical_height,
|
uint32_t new_physical_width, uint32_t new_physical_height,
|
||||||
WindowDestructionReceiver& destruction_receiver) {
|
WindowDestructionReceiver& destruction_receiver) {
|
||||||
|
|||||||
@@ -553,6 +553,8 @@ class Window {
|
|||||||
void OnDpiChanged(UISetupEvent& e,
|
void OnDpiChanged(UISetupEvent& e,
|
||||||
WindowDestructionReceiver& destruction_receiver);
|
WindowDestructionReceiver& destruction_receiver);
|
||||||
void OnMonitorUpdate(MonitorUpdateEvent& e);
|
void OnMonitorUpdate(MonitorUpdateEvent& e);
|
||||||
|
void OnUsbDeviceChanged(bool is_arrival,
|
||||||
|
WindowDestructionReceiver& destruction_receiver);
|
||||||
// For calling when the platform changes something in the non-maximized,
|
// For calling when the platform changes something in the non-maximized,
|
||||||
// non-fullscreen size of the window.
|
// non-fullscreen size of the window.
|
||||||
void OnDesiredLogicalSizeUpdate(uint32_t new_desired_logical_width,
|
void OnDesiredLogicalSizeUpdate(uint32_t new_desired_logical_width,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class WindowListener {
|
|||||||
virtual void OnLostFocus(UISetupEvent& e) {}
|
virtual void OnLostFocus(UISetupEvent& e) {}
|
||||||
|
|
||||||
virtual void OnFileDrop(FileDropEvent& e) {}
|
virtual void OnFileDrop(FileDropEvent& e) {}
|
||||||
|
virtual void OnUsbDeviceChanged(bool is_arrival) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class WindowInputListener {
|
class WindowInputListener {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include "xenia/ui/virtual_key.h"
|
#include "xenia/ui/virtual_key.h"
|
||||||
#include "xenia/ui/windowed_app_context_win.h"
|
#include "xenia/ui/windowed_app_context_win.h"
|
||||||
|
|
||||||
|
#include <Dbt.h>
|
||||||
#include <ShellScalingApi.h>
|
#include <ShellScalingApi.h>
|
||||||
#include <dwmapi.h>
|
#include <dwmapi.h>
|
||||||
|
|
||||||
@@ -48,6 +49,10 @@ Win32Window::~Win32Window() {
|
|||||||
DeleteTimerQueueTimer(nullptr, cursor_auto_hide_timer_, nullptr);
|
DeleteTimerQueueTimer(nullptr, cursor_auto_hide_timer_, nullptr);
|
||||||
cursor_auto_hide_timer_ = nullptr;
|
cursor_auto_hide_timer_ = nullptr;
|
||||||
}
|
}
|
||||||
|
if (usb_device_notify_) {
|
||||||
|
UnregisterDeviceNotification(usb_device_notify_);
|
||||||
|
usb_device_notify_ = nullptr;
|
||||||
|
}
|
||||||
if (hwnd_) {
|
if (hwnd_) {
|
||||||
// Set hwnd_ to null to ignore events from now on since this Win32Window is
|
// Set hwnd_ to null to ignore events from now on since this Win32Window is
|
||||||
// entering an indeterminate state.
|
// entering an indeterminate state.
|
||||||
@@ -203,6 +208,14 @@ bool Win32Window::OpenImpl() {
|
|||||||
// Enable file dragging from external sources
|
// Enable file dragging from external sources
|
||||||
DragAcceptFiles(hwnd_, true);
|
DragAcceptFiles(hwnd_, true);
|
||||||
|
|
||||||
|
// Register USB listener
|
||||||
|
DEV_BROADCAST_DEVICEINTERFACE filter = {};
|
||||||
|
filter.dbcc_size = sizeof(filter);
|
||||||
|
filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
|
||||||
|
usb_device_notify_ = RegisterDeviceNotificationW(
|
||||||
|
hwnd_, &filter,
|
||||||
|
DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
|
||||||
|
|
||||||
// Apply the initial state from the Window that the window shouldn't be
|
// Apply the initial state from the Window that the window shouldn't be
|
||||||
// visibly transitioned to.
|
// visibly transitioned to.
|
||||||
|
|
||||||
@@ -1084,7 +1097,16 @@ LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
|||||||
MonitorUpdateEvent update_event{this, true};
|
MonitorUpdateEvent update_event{this, true};
|
||||||
OnMonitorUpdate(update_event);
|
OnMonitorUpdate(update_event);
|
||||||
} break;
|
} break;
|
||||||
|
case WM_DEVICECHANGE: {
|
||||||
|
if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) {
|
||||||
|
bool is_arrival = (wParam == DBT_DEVICEARRIVAL);
|
||||||
|
WindowDestructionReceiver destruction_receiver(this);
|
||||||
|
OnUsbDeviceChanged(is_arrival, destruction_receiver);
|
||||||
|
if (destruction_receiver.IsWindowDestroyedOrClosed()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
case WM_DPICHANGED: {
|
case WM_DPICHANGED: {
|
||||||
// Note that for some reason, WM_DPICHANGED is not sent when the window is
|
// Note that for some reason, WM_DPICHANGED is not sent when the window is
|
||||||
// borderless fullscreen with per-monitor DPI awareness v1.
|
// borderless fullscreen with per-monitor DPI awareness v1.
|
||||||
|
|||||||
@@ -151,6 +151,8 @@ class Win32Window : public Window {
|
|||||||
// Whether the cursor has been hidden after the expiration of the timer, and
|
// Whether the cursor has been hidden after the expiration of the timer, and
|
||||||
// hasn't been revealed yet.
|
// hasn't been revealed yet.
|
||||||
bool cursor_currently_auto_hidden_ = false;
|
bool cursor_currently_auto_hidden_ = false;
|
||||||
|
|
||||||
|
HDEVNOTIFY usb_device_notify_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Win32MenuItem : public MenuItem {
|
class Win32MenuItem : public MenuItem {
|
||||||
|
|||||||
Reference in New Issue
Block a user