mirror of
https://github.com/rock88/moonlight-nx
synced 2025-02-16 12:38:29 +00:00
WIP:
New control controllers; HID Keyboard/Mouse support; Few improvements...
This commit is contained in:
parent
7a2237e38b
commit
934bda43dc
24 changed files with 1138 additions and 186 deletions
9
Makefile
9
Makefile
|
@ -44,7 +44,7 @@ APP_AUTHOR := port by rock88
|
|||
APP_VERSION := 1.1.0
|
||||
BUILD := build
|
||||
SOURCES := src src/libgamestream src/switch src/nanogui_resources src/streaming src/streaming/ffmpeg \
|
||||
src/crypto src/streaming/video src/crypto src/streaming/audio src/ui/windows src/ui/buttons src/ui \
|
||||
src/crypto src/streaming/video src/crypto src/streaming/audio src/ui/windows src/ui/buttons src/ui src/helpers src/controls\
|
||||
third_party/moonlight-common-c/enet third_party/moonlight-common-c/reedsolomon third_party/moonlight-common-c/src \
|
||||
third_party/nanogui/ext/nanovg/src third_party/nanogui/src
|
||||
DATA := data
|
||||
|
@ -65,7 +65,7 @@ ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
|
|||
M_INCLUDES := \
|
||||
-I$(TOPDIR)/src -I$(TOPDIR)/src/switch -I$(TOPDIR)/src/streaming -I$(TOPDIR)/src/crypto -I$(TOPDIR)/src/crypto/keys \
|
||||
-I$(TOPDIR)/src/streaming/ffmpeg -I$(TOPDIR)/src/streaming/video -I$(TOPDIR)/src/streaming/audio \
|
||||
-I$(TOPDIR)/src/nanogui_resources \
|
||||
-I$(TOPDIR)/src/nanogui_resources -I$(TOPDIR)/src/helpers -I$(TOPDIR)/src/controls \
|
||||
-I$(TOPDIR)/src/ui -I$(TOPDIR)/src/ui/buttons -I$(TOPDIR)/src/ui/windows \
|
||||
-I$(TOPDIR)/src/libgamestream \
|
||||
-I$(TOPDIR)/third_party/moonlight-common-c/reedsolomon \
|
||||
|
@ -126,7 +126,10 @@ MOONLIGHT_LIBRETRO_CXX_SOURCES = \
|
|||
GamepadMapper.cpp \
|
||||
InputSettingsWindow.cpp \
|
||||
Alert.cpp \
|
||||
WakeOnLanManager.cpp
|
||||
WakeOnLanManager.cpp \
|
||||
MouseController.cpp \
|
||||
KeyboardController.cpp \
|
||||
StreamControlsController.cpp
|
||||
|
||||
MOONLIGHT_COMMON_C_SOURCES = \
|
||||
callbacks.c \
|
||||
|
|
|
@ -25,6 +25,9 @@
|
|||
3602C3BA245DB3C800368900 /* AppListWindow.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3602C3B8245DB3C800368900 /* AppListWindow.cpp */; };
|
||||
3602C3BD245DBA9100368900 /* AppButton.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3602C3BB245DBA9100368900 /* AppButton.cpp */; };
|
||||
3603E93C246316400051287D /* InputController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3603E93A246316400051287D /* InputController.cpp */; };
|
||||
3620418D25D7F04400D21EE3 /* MouseController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3620418B25D7F04400D21EE3 /* MouseController.cpp */; };
|
||||
3620419825D85B6000D21EE3 /* KeyboardController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3620419625D85B5F00D21EE3 /* KeyboardController.cpp */; };
|
||||
362041A225D94D7700D21EE3 /* StreamControlsController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 362041A025D94D7700D21EE3 /* StreamControlsController.cpp */; };
|
||||
363898342471B7C500F99920 /* MbedTLSCryptoManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 363898312471B7C500F99920 /* MbedTLSCryptoManager.cpp */; };
|
||||
364A6A8F24786E2200460028 /* BoxArtManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 364A6A8D24786E2200460028 /* BoxArtManager.cpp */; };
|
||||
3652EFCD245B3B00001FABF3 /* widget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3652EF0F245B3B00001FABF3 /* widget.cpp */; };
|
||||
|
@ -144,6 +147,13 @@
|
|||
3602C3C0245DC7E300368900 /* Makefile */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = "<group>"; };
|
||||
3603E93A246316400051287D /* InputController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = InputController.cpp; sourceTree = "<group>"; };
|
||||
3603E93B246316400051287D /* InputController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = InputController.hpp; sourceTree = "<group>"; };
|
||||
3620418B25D7F04400D21EE3 /* MouseController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MouseController.cpp; sourceTree = "<group>"; };
|
||||
3620418C25D7F04400D21EE3 /* MouseController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MouseController.hpp; sourceTree = "<group>"; };
|
||||
3620419125D7FDDB00D21EE3 /* Singleton.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Singleton.hpp; sourceTree = "<group>"; };
|
||||
3620419625D85B5F00D21EE3 /* KeyboardController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = KeyboardController.cpp; sourceTree = "<group>"; };
|
||||
3620419725D85B5F00D21EE3 /* KeyboardController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = KeyboardController.hpp; sourceTree = "<group>"; };
|
||||
362041A025D94D7700D21EE3 /* StreamControlsController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = StreamControlsController.cpp; sourceTree = "<group>"; };
|
||||
362041A125D94D7700D21EE3 /* StreamControlsController.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = StreamControlsController.hpp; sourceTree = "<group>"; };
|
||||
3638982F2471B7C500F99920 /* MbedTLSCryptoManager.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MbedTLSCryptoManager.hpp; sourceTree = "<group>"; };
|
||||
363898302471B7C500F99920 /* OpenSSLCryptoManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OpenSSLCryptoManager.cpp; sourceTree = "<group>"; };
|
||||
363898312471B7C500F99920 /* MbedTLSCryptoManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MbedTLSCryptoManager.cpp; sourceTree = "<group>"; };
|
||||
|
@ -297,6 +307,7 @@
|
|||
3661D2FE2469E0C00060EE24 /* GLVideoRenderer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = GLVideoRenderer.hpp; sourceTree = "<group>"; };
|
||||
3678EF712476D9DA0097345D /* DebugFileRecorderAudioRenderer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DebugFileRecorderAudioRenderer.cpp; sourceTree = "<group>"; };
|
||||
3678EF722476D9DA0097345D /* DebugFileRecorderAudioRenderer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DebugFileRecorderAudioRenderer.hpp; sourceTree = "<group>"; };
|
||||
367CB1E025D312CF00114747 /* AVFrameHolder.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AVFrameHolder.hpp; sourceTree = "<group>"; };
|
||||
367CD958245DE25F00A95738 /* StreamWindow.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = StreamWindow.cpp; sourceTree = "<group>"; };
|
||||
367CD959245DE25F00A95738 /* StreamWindow.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = StreamWindow.hpp; sourceTree = "<group>"; };
|
||||
367D2D7224829A0800A946F4 /* LogsWindow.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = LogsWindow.cpp; sourceTree = "<group>"; };
|
||||
|
@ -404,6 +415,27 @@
|
|||
path = windows;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3620418A25D7F00500D21EE3 /* controls */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
362041A025D94D7700D21EE3 /* StreamControlsController.cpp */,
|
||||
362041A125D94D7700D21EE3 /* StreamControlsController.hpp */,
|
||||
3620418B25D7F04400D21EE3 /* MouseController.cpp */,
|
||||
3620418C25D7F04400D21EE3 /* MouseController.hpp */,
|
||||
3620419625D85B5F00D21EE3 /* KeyboardController.cpp */,
|
||||
3620419725D85B5F00D21EE3 /* KeyboardController.hpp */,
|
||||
);
|
||||
path = controls;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3620419025D7FDCC00D21EE3 /* helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3620419125D7FDDB00D21EE3 /* Singleton.hpp */,
|
||||
);
|
||||
path = helpers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3652ECC3245B3AFF001FABF3 /* nanogui */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -648,11 +680,13 @@
|
|||
36B406932459F41E005BD903 /* src */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3620419025D7FDCC00D21EE3 /* helpers */,
|
||||
36BFCCE92479723E00245D40 /* libgamestream */,
|
||||
36E63789246FFFDC0032F5FB /* crypto */,
|
||||
36DFE0CA2459FA3F00FC51CE /* nanogui_resources */,
|
||||
36DFDCF12459F79000FC51CE /* ui */,
|
||||
36D3F8462469C8DD00CDEF9B /* streaming */,
|
||||
3620418A25D7F00500D21EE3 /* controls */,
|
||||
36F1646E2474736E00D70AD9 /* switch */,
|
||||
36CF721C25D038C700878A8E /* switch_support */,
|
||||
364A6A8D24786E2200460028 /* BoxArtManager.cpp */,
|
||||
|
@ -704,6 +738,7 @@
|
|||
36BFCCF42479724900245D40 /* GameStreamClient.hpp */,
|
||||
36EB491124993A4C0059EDB7 /* WakeOnLanManager.cpp */,
|
||||
36EB491224993A4C0059EDB7 /* WakeOnLanManager.hpp */,
|
||||
367CB1E025D312CF00114747 /* AVFrameHolder.hpp */,
|
||||
);
|
||||
path = streaming;
|
||||
sourceTree = "<group>";
|
||||
|
@ -937,6 +972,7 @@
|
|||
3652F079245C292B001FABF3 /* RtpReorderQueue.c in Sources */,
|
||||
36BFCCF12479723E00245D40 /* xml.cpp in Sources */,
|
||||
3652F065245C292B001FABF3 /* list.c in Sources */,
|
||||
3620418D25D7F04400D21EE3 /* MouseController.cpp in Sources */,
|
||||
36DFE0CD2459FA3F00FC51CE /* nanogui_resources.cpp in Sources */,
|
||||
36DDACF824929919001133D1 /* InputSettingsWindow.cpp in Sources */,
|
||||
3652F06A245C292B001FABF3 /* protocol.c in Sources */,
|
||||
|
@ -956,6 +992,7 @@
|
|||
3652F070245C292B001FABF3 /* SimpleStun.c in Sources */,
|
||||
3652EFD8245B3B00001FABF3 /* graph.cpp in Sources */,
|
||||
3652EFDF245B3B00001FABF3 /* shader.cpp in Sources */,
|
||||
3620419825D85B6000D21EE3 /* KeyboardController.cpp in Sources */,
|
||||
3652F06C245C292B001FABF3 /* callbacks.c in Sources */,
|
||||
3652EFD9245B3B00001FABF3 /* popup.cpp in Sources */,
|
||||
3652EFEA245B3B00001FABF3 /* renderpass_gl.cpp in Sources */,
|
||||
|
@ -993,6 +1030,7 @@
|
|||
3652EFF3245B3B00001FABF3 /* slider.cpp in Sources */,
|
||||
3652EFCE245B3B00001FABF3 /* common.cpp in Sources */,
|
||||
3652F077245C292B001FABF3 /* VideoDepacketizer.c in Sources */,
|
||||
362041A225D94D7700D21EE3 /* StreamControlsController.cpp in Sources */,
|
||||
3652EFF1245B3B00001FABF3 /* messagedialog.cpp in Sources */,
|
||||
36F16475247473A300D70AD9 /* mbedtls_to_openssl_wrapper.cpp in Sources */,
|
||||
3652F06F245C292B001FABF3 /* ControlStream.c in Sources */,
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableAddressSanitizer = "YES"
|
||||
enableASanStackUseAfterReturn = "YES"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
|
@ -59,11 +58,6 @@
|
|||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
<AdditionalOptions>
|
||||
<AdditionalOption
|
||||
key = "NSZombieEnabled"
|
||||
value = "YES"
|
||||
isEnabled = "YES">
|
||||
</AdditionalOption>
|
||||
<AdditionalOption
|
||||
key = "MallocScribble"
|
||||
value = ""
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
#include <algorithm>
|
||||
#include <mutex>
|
||||
|
||||
#define BLANK_IMAGE_SIZE 6129
|
||||
|
||||
std::mutex m_mutex;
|
||||
|
||||
bool BoxArtManager::has_boxart(int app_id) {
|
||||
|
@ -17,26 +15,15 @@ bool BoxArtManager::has_boxart(int app_id) {
|
|||
Data data = Data::read_from_file(path);
|
||||
m_has_boxart[app_id] = !data.is_empty();
|
||||
|
||||
if (data.size() == BLANK_IMAGE_SIZE) {
|
||||
m_blanks.push_back(app_id);
|
||||
}
|
||||
return m_has_boxart[app_id];
|
||||
}
|
||||
|
||||
bool BoxArtManager::is_blank(int app_id) {
|
||||
return std::find(m_blanks.begin(), m_blanks.end(), app_id) != m_blanks.end();
|
||||
}
|
||||
|
||||
void BoxArtManager::set_data(Data data, int app_id) {
|
||||
std::lock_guard<std::mutex> guard(m_mutex);
|
||||
|
||||
std::string path = Settings::settings()->boxart_dir() + "/" + std::to_string(app_id) + ".png";
|
||||
data.write_to_file(path);
|
||||
m_has_boxart[app_id] = true;
|
||||
|
||||
if (data.size() == BLANK_IMAGE_SIZE) {
|
||||
m_blanks.push_back(app_id);
|
||||
}
|
||||
}
|
||||
|
||||
void BoxArtManager::make_texture_from_boxart(NVGcontext *ctx, int app_id) {
|
||||
|
@ -46,7 +33,12 @@ void BoxArtManager::make_texture_from_boxart(NVGcontext *ctx, int app_id) {
|
|||
Data data = Data::read_from_file(path);
|
||||
|
||||
int handle = nvgCreateImageMem(ctx, 0, data.bytes(), (int)data.size());
|
||||
m_texture_handle[app_id] = handle;
|
||||
|
||||
if (handle > 0) {
|
||||
m_texture_handle[app_id] = handle;
|
||||
} else {
|
||||
m_has_boxart[app_id] = false;
|
||||
}
|
||||
}
|
||||
|
||||
int BoxArtManager::texture_id(int app_id) {
|
||||
|
|
|
@ -15,7 +15,6 @@ public:
|
|||
}
|
||||
|
||||
bool has_boxart(int app_id);
|
||||
bool is_blank(int app_id);
|
||||
|
||||
void set_data(Data data, int app_id);
|
||||
void make_texture_from_boxart(NVGcontext *ctx, int app_id);
|
||||
|
@ -26,5 +25,4 @@ private:
|
|||
|
||||
std::map<int, bool> m_has_boxart;
|
||||
std::map<int, int> m_texture_handle;
|
||||
std::vector<int> m_blanks;
|
||||
};
|
||||
|
|
|
@ -63,36 +63,36 @@ void InputController::handle_scroll(double x, double y) {
|
|||
void InputController::handle_keyboard_event(int key, int scancode, int action, int modifiers) {
|
||||
nanogui::keyboard_callback_event(key, scancode, action, modifiers);
|
||||
|
||||
#ifndef __SWITCH__
|
||||
// Translate keyboard keys to gamepad
|
||||
GLFWgamepadstate glfw_gamepad_state;
|
||||
memset(&glfw_gamepad_state, 0, sizeof(GLFWgamepadstate));
|
||||
static int glfw_keyboard_state[GLFW_KEY_LAST];
|
||||
glfw_keyboard_state[key] = action != GLFW_RELEASE;
|
||||
|
||||
#define GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY, GLFW_GAMEPAD_BUTTON) \
|
||||
glfw_gamepad_state.buttons[GLFW_GAMEPAD_BUTTON] = glfw_keyboard_state[GLFW_KEY]
|
||||
|
||||
GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_UP, GLFW_GAMEPAD_BUTTON_DPAD_UP);
|
||||
GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_DOWN, GLFW_GAMEPAD_BUTTON_DPAD_DOWN);
|
||||
GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_LEFT, GLFW_GAMEPAD_BUTTON_DPAD_LEFT);
|
||||
GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_RIGHT, GLFW_GAMEPAD_BUTTON_DPAD_RIGHT);
|
||||
GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_Q, GLFW_GAMEPAD_BUTTON_LEFT_BUMPER);
|
||||
GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_E, GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER);
|
||||
GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_A, GLFW_GAMEPAD_BUTTON_A);
|
||||
GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_B, GLFW_GAMEPAD_BUTTON_B);
|
||||
GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_X, GLFW_GAMEPAD_BUTTON_X);
|
||||
GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_Y, GLFW_GAMEPAD_BUTTON_Y);
|
||||
GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_ESCAPE, GLFW_GAMEPAD_BUTTON_START);
|
||||
GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_MINUS, GLFW_GAMEPAD_BUTTON_BACK);
|
||||
GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_R, GLFW_GAMEPAD_BUTTON_LEFT_THUMB);
|
||||
GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_T, GLFW_GAMEPAD_BUTTON_RIGHT_THUMB);
|
||||
|
||||
glfw_gamepad_state.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER] = glfw_keyboard_state[GLFW_KEY_Z] ? 1 : -1;
|
||||
glfw_gamepad_state.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER] = glfw_keyboard_state[GLFW_KEY_C] ? 1 : -1;
|
||||
|
||||
handle_gamepad_event(glfw_gamepad_state);
|
||||
#endif
|
||||
// #ifndef __SWITCH__
|
||||
// // Translate keyboard keys to gamepad
|
||||
// GLFWgamepadstate glfw_gamepad_state;
|
||||
// memset(&glfw_gamepad_state, 0, sizeof(GLFWgamepadstate));
|
||||
// static int glfw_keyboard_state[GLFW_KEY_LAST];
|
||||
// glfw_keyboard_state[key] = action != GLFW_RELEASE;
|
||||
//
|
||||
// #define GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY, GLFW_GAMEPAD_BUTTON) \
|
||||
// glfw_gamepad_state.buttons[GLFW_GAMEPAD_BUTTON] = glfw_keyboard_state[GLFW_KEY]
|
||||
//
|
||||
// GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_UP, GLFW_GAMEPAD_BUTTON_DPAD_UP);
|
||||
// GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_DOWN, GLFW_GAMEPAD_BUTTON_DPAD_DOWN);
|
||||
// GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_LEFT, GLFW_GAMEPAD_BUTTON_DPAD_LEFT);
|
||||
// GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_RIGHT, GLFW_GAMEPAD_BUTTON_DPAD_RIGHT);
|
||||
// GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_Q, GLFW_GAMEPAD_BUTTON_LEFT_BUMPER);
|
||||
// GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_E, GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER);
|
||||
// GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_A, GLFW_GAMEPAD_BUTTON_A);
|
||||
// GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_B, GLFW_GAMEPAD_BUTTON_B);
|
||||
// GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_X, GLFW_GAMEPAD_BUTTON_X);
|
||||
// GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_Y, GLFW_GAMEPAD_BUTTON_Y);
|
||||
// GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_ESCAPE, GLFW_GAMEPAD_BUTTON_START);
|
||||
// GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_MINUS, GLFW_GAMEPAD_BUTTON_BACK);
|
||||
// GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_R, GLFW_GAMEPAD_BUTTON_LEFT_THUMB);
|
||||
// GLFW_KEYBOARD_TO_GAMEPAD(GLFW_KEY_T, GLFW_GAMEPAD_BUTTON_RIGHT_THUMB);
|
||||
//
|
||||
// glfw_gamepad_state.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER] = glfw_keyboard_state[GLFW_KEY_Z] ? 1 : -1;
|
||||
// glfw_gamepad_state.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER] = glfw_keyboard_state[GLFW_KEY_C] ? 1 : -1;
|
||||
//
|
||||
// handle_gamepad_event(glfw_gamepad_state);
|
||||
// #endif
|
||||
}
|
||||
|
||||
void InputController::handle_gamepad_event(GLFWgamepadstate& gamepad) {
|
||||
|
@ -301,22 +301,43 @@ void InputController::send_keyboard_to_stream() {
|
|||
}
|
||||
}
|
||||
|
||||
struct GamepadState {
|
||||
short buttonFlags;
|
||||
unsigned char leftTrigger;
|
||||
unsigned char rightTrigger;
|
||||
short leftStickX;
|
||||
short leftStickY;
|
||||
short rightStickX;
|
||||
short rightStickY;
|
||||
|
||||
bool is_equal(GamepadState other) {
|
||||
return buttonFlags == other.buttonFlags &&
|
||||
leftTrigger == other.leftTrigger &&
|
||||
rightTrigger == other.rightTrigger &&
|
||||
leftStickX == other.leftStickX &&
|
||||
leftStickY == other.leftStickY &&
|
||||
rightStickX == other.rightStickX &&
|
||||
rightStickY == other.rightStickY;
|
||||
}
|
||||
};
|
||||
|
||||
void InputController::send_gamepad_to_stream() {
|
||||
auto mapped_gamepad = GamepadMapper::mapper()->map(glfw_gamepad_state);
|
||||
short buttonFlags = 0;
|
||||
unsigned char leftTrigger = 0xFFFF * (mapped_gamepad.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER] + 1) / 2;
|
||||
unsigned char rightTrigger = 0xFFFF * (mapped_gamepad.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER] + 1) / 2;
|
||||
short leftStickX = mapped_gamepad.axes[GLFW_GAMEPAD_AXIS_LEFT_X] * 0x7FFF;
|
||||
short leftStickY = 0xFFFF - mapped_gamepad.axes[GLFW_GAMEPAD_AXIS_LEFT_Y] * 0x7FFF;
|
||||
short rightStickX = mapped_gamepad.axes[GLFW_GAMEPAD_AXIS_RIGHT_X] * 0x7FFF;
|
||||
short rightStickY = 0xFFFF - mapped_gamepad.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y] * 0x7FFF;
|
||||
GamepadState new_state;
|
||||
new_state.buttonFlags = 0;
|
||||
new_state.leftTrigger = 0xFFFF * (mapped_gamepad.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER] + 1) / 2;
|
||||
new_state.rightTrigger = 0xFFFF * (mapped_gamepad.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER] + 1) / 2;
|
||||
new_state.leftStickX = mapped_gamepad.axes[GLFW_GAMEPAD_AXIS_LEFT_X] * 0x7FFF;
|
||||
new_state.leftStickY = 0xFFFF - mapped_gamepad.axes[GLFW_GAMEPAD_AXIS_LEFT_Y] * 0x7FFF;
|
||||
new_state.rightStickX = mapped_gamepad.axes[GLFW_GAMEPAD_AXIS_RIGHT_X] * 0x7FFF;
|
||||
new_state.rightStickY = 0xFFFF - mapped_gamepad.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y] * 0x7FFF;
|
||||
|
||||
if (GamepadMapper::mapper()->gamepad_combo_is_enabled(glfw_gamepad_state, GamepadComboGuide)) {
|
||||
buttonFlags |= SPECIAL_FLAG;
|
||||
new_state.buttonFlags |= SPECIAL_FLAG;
|
||||
}
|
||||
|
||||
#define SET_GAME_PAD_STATE(LIMELIGHT_KEY, GLFW_GAMEPAD_BUTTON) \
|
||||
mapped_gamepad.buttons[GLFW_GAMEPAD_BUTTON] ? (buttonFlags |= LIMELIGHT_KEY) : (buttonFlags &= ~LIMELIGHT_KEY);
|
||||
mapped_gamepad.buttons[GLFW_GAMEPAD_BUTTON] ? (new_state.buttonFlags |= LIMELIGHT_KEY) : (new_state.buttonFlags &= ~LIMELIGHT_KEY);
|
||||
|
||||
SET_GAME_PAD_STATE(UP_FLAG, GLFW_GAMEPAD_BUTTON_DPAD_UP);
|
||||
SET_GAME_PAD_STATE(DOWN_FLAG, GLFW_GAMEPAD_BUTTON_DPAD_DOWN);
|
||||
|
@ -337,13 +358,19 @@ void InputController::send_gamepad_to_stream() {
|
|||
SET_GAME_PAD_STATE(X_FLAG, GLFW_GAMEPAD_BUTTON_X);
|
||||
SET_GAME_PAD_STATE(Y_FLAG, GLFW_GAMEPAD_BUTTON_Y);
|
||||
|
||||
LiSendControllerEvent(
|
||||
buttonFlags,
|
||||
leftTrigger,
|
||||
rightTrigger,
|
||||
leftStickX,
|
||||
leftStickY,
|
||||
rightStickX,
|
||||
rightStickY
|
||||
);
|
||||
static GamepadState last_gamepad_state;
|
||||
|
||||
if (!last_gamepad_state.is_equal(new_state)) {
|
||||
last_gamepad_state = new_state;
|
||||
|
||||
LiSendControllerEvent(
|
||||
new_state.buttonFlags,
|
||||
new_state.leftTrigger,
|
||||
new_state.rightTrigger,
|
||||
new_state.leftStickX,
|
||||
new_state.leftStickY,
|
||||
new_state.rightStickX,
|
||||
new_state.rightStickY
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
222
src/controls/KeyboardController.cpp
Normal file
222
src/controls/KeyboardController.cpp
Normal file
|
@ -0,0 +1,222 @@
|
|||
#include "KeyboardController.hpp"
|
||||
#include "Limelight.h"
|
||||
#include "Logger.hpp"
|
||||
#include <nanogui/opengl.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <switch.h>
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
||||
#define VK_0 0x30
|
||||
#define VK_A 0x41
|
||||
#define VK_F1 0x70
|
||||
|
||||
KeyCodes::KeyCodes(short glfw_keycode, short glfw_char_code, short glfw_shift_char_code, short moonlight_keycode):
|
||||
glfw_keycode(glfw_keycode),
|
||||
glfw_char_code(glfw_char_code),
|
||||
glfw_shift_char_code(glfw_shift_char_code),
|
||||
moonlight_keycode(moonlight_keycode) {}
|
||||
|
||||
KeyboardState::KeyboardState() {
|
||||
// F1 - F12
|
||||
for (int i = GLFW_KEY_F1; i <= GLFW_KEY_F12; i++) {
|
||||
keys.push_back({.codes = KeyCodes(i, -1, -1, VK_F1 + i - GLFW_KEY_F1) });
|
||||
}
|
||||
|
||||
// A - Z
|
||||
for (int i = GLFW_KEY_A; i <= GLFW_KEY_Z; i++) {
|
||||
keys.push_back({.codes = KeyCodes(i, i + 32, i, VK_A + i - GLFW_KEY_A) });
|
||||
}
|
||||
|
||||
// 0 - 9
|
||||
for (int i = GLFW_KEY_0; i <= GLFW_KEY_9; i++) {
|
||||
keys.push_back({.codes = KeyCodes(i, i, -1, VK_0 + i - GLFW_KEY_0) });
|
||||
}
|
||||
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_SPACE, GLFW_KEY_SPACE, -1, 0x20) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_APOSTROPHE, GLFW_KEY_APOSTROPHE, -1, 0xDE) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_COMMA, GLFW_KEY_COMMA, -1, 0xBC) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_MINUS, GLFW_KEY_MINUS, -1, 0xBD) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_PERIOD, GLFW_KEY_PERIOD, -1, 0xBE) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_SLASH, GLFW_KEY_SLASH, -1, 0xBF) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_SEMICOLON, GLFW_KEY_SEMICOLON, -1, 0xBA) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_EQUAL, GLFW_KEY_EQUAL, -1, 0xBB) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_LEFT_BRACKET, GLFW_KEY_LEFT_BRACKET, -1, 0xDB) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_RIGHT_BRACKET, GLFW_KEY_RIGHT_BRACKET, -1, 0xDD) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_BACKSLASH, GLFW_KEY_BACKSLASH, -1, 0xDC) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_GRAVE_ACCENT, GLFW_KEY_GRAVE_ACCENT, -1, 0xC0) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_WORLD_1, GLFW_KEY_GRAVE_ACCENT, -1, 0xC0) }); // Ok?
|
||||
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_ESCAPE, -1, -1, 0x1B) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_ENTER, -1, -1, 0x0D) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_TAB, -1, -1, 0x09) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_BACKSPACE, -1, -1, 0x08) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_CAPS_LOCK, -1, -1, 0x14) });
|
||||
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_LEFT_SHIFT, -1, -1, 0xA0) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_LEFT_CONTROL, -1, -1, 0xA2) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_LEFT_ALT, -1, -1, 0xA4) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_LEFT_SUPER, -1, -1, 0x5B) });
|
||||
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_RIGHT_SHIFT, -1, -1, 0xA1) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_RIGHT_CONTROL, -1, -1, 0xA3) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_RIGHT_ALT, -1, -1, 0xA5) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_RIGHT_SUPER, -1, -1, 0x5C) });
|
||||
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_LEFT, -1, -1, 0x25) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_RIGHT, -1, -1, 0x27) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_UP, -1, -1, 0x26) });
|
||||
keys.push_back({.codes = KeyCodes(GLFW_KEY_DOWN, -1, -1, 0x28) });
|
||||
}
|
||||
|
||||
static int find_glfw_key_index(int key, KeyboardState& state) {
|
||||
for (int i = 0; i < state.keys.size(); i++) {
|
||||
if (state.keys[i].codes.glfw_keycode == key) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int find_switch_key_index(int key, KeyboardState& state) {
|
||||
if (KBD_A <= key && key <= KBD_Z) {
|
||||
return find_glfw_key_index(key - KBD_A + GLFW_KEY_A, state);
|
||||
} else if (KBD_1 <= key && key <= KBD_9) {
|
||||
return find_glfw_key_index(key - KBD_1 + GLFW_KEY_1, state);
|
||||
} else if (KBD_F1 <= key && key <= KBD_F12) {
|
||||
return find_glfw_key_index(key - KBD_F1 + GLFW_KEY_F1, state);
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case KBD_0: return find_glfw_key_index(GLFW_KEY_0, state);
|
||||
case KBD_SPACE: return find_glfw_key_index(GLFW_KEY_SPACE, state);
|
||||
case KBD_APOSTROPHE: return find_glfw_key_index(GLFW_KEY_APOSTROPHE, state);
|
||||
case KBD_COMMA: return find_glfw_key_index(GLFW_KEY_COMMA, state);
|
||||
case KBD_MINUS: return find_glfw_key_index(GLFW_KEY_MINUS, state);
|
||||
case KBD_DOT: return find_glfw_key_index(GLFW_KEY_PERIOD, state);
|
||||
case KBD_SLASH: return find_glfw_key_index(GLFW_KEY_SLASH, state);
|
||||
case KBD_SEMICOLON: return find_glfw_key_index(GLFW_KEY_SEMICOLON, state);
|
||||
case KBD_EQUAL: return find_glfw_key_index(GLFW_KEY_EQUAL, state);
|
||||
case KBD_LEFTBRACE: return find_glfw_key_index(GLFW_KEY_LEFT_BRACKET, state);
|
||||
case KBD_RIGHTBRACE: return find_glfw_key_index(GLFW_KEY_RIGHT_BRACKET, state);
|
||||
case KBD_BACKSLASH: return find_glfw_key_index(GLFW_KEY_BACKSLASH, state);
|
||||
case KBD_GRAVE: return find_glfw_key_index(GLFW_KEY_GRAVE_ACCENT, state);
|
||||
case KBD_ESC: return find_glfw_key_index(GLFW_KEY_ESCAPE, state);
|
||||
case KBD_ENTER: return find_glfw_key_index(GLFW_KEY_ENTER, state);
|
||||
case KBD_TAB: return find_glfw_key_index(GLFW_KEY_TAB, state);
|
||||
case KBD_BACKSPACE: return find_glfw_key_index(GLFW_KEY_BACKSPACE, state);
|
||||
case KBD_CAPSLOCK: return find_glfw_key_index(GLFW_KEY_CAPS_LOCK, state);
|
||||
case KBD_LEFTSHIFT: return find_glfw_key_index(GLFW_KEY_LEFT_SHIFT, state);
|
||||
case KBD_LEFTCTRL: return find_glfw_key_index(GLFW_KEY_LEFT_CONTROL, state);
|
||||
case KBD_LEFTALT: return find_glfw_key_index(GLFW_KEY_LEFT_ALT, state);
|
||||
case KBD_LEFTMETA: return find_glfw_key_index(GLFW_KEY_LEFT_SUPER, state);
|
||||
case KBD_RIGHTSHIFT: return find_glfw_key_index(GLFW_KEY_RIGHT_SHIFT, state);
|
||||
case KBD_RIGHTCTRL: return find_glfw_key_index(GLFW_KEY_RIGHT_CONTROL, state);
|
||||
case KBD_RIGHTALT: return find_glfw_key_index(GLFW_KEY_RIGHT_ALT, state);
|
||||
case KBD_RIGHTMETA: return find_glfw_key_index(GLFW_KEY_RIGHT_SUPER, state);
|
||||
case KBD_LEFT: return find_glfw_key_index(GLFW_KEY_LEFT, state);
|
||||
case KBD_RIGHT: return find_glfw_key_index(GLFW_KEY_RIGHT, state);
|
||||
case KBD_UP: return find_glfw_key_index(GLFW_KEY_UP, state);
|
||||
case KBD_DOWN: return find_glfw_key_index(GLFW_KEY_DOWN, state);
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardController::init(GLFWwindow* window) {
|
||||
hidInitializeKeyboard();
|
||||
|
||||
glfwSetKeyCallback(window, [](GLFWwindow *w, int key, int scancode, int action, int mods) {
|
||||
#ifndef __SWITCH__
|
||||
KeyboardController::instance().handle_key(key, scancode, action, mods);
|
||||
#endif
|
||||
});
|
||||
|
||||
glfwSetCharCallback(window, [](GLFWwindow *w, auto input) {
|
||||
#ifndef __SWITCH__
|
||||
KeyboardController::instance().handle_char(input);
|
||||
#endif
|
||||
});
|
||||
|
||||
m_hid_keyboard_state.assign(256, false);
|
||||
}
|
||||
|
||||
void KeyboardController::handle_keyboard() {
|
||||
HidKeyboardState state;
|
||||
|
||||
if (hidGetKeyboardStates(&state, 1)) {
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
auto is_pressed = (state.keys[i / 64] & (1ul << (i % 64))) != 0;
|
||||
|
||||
if (m_hid_keyboard_state[i] != is_pressed) {
|
||||
int index = find_switch_key_index(i, m_keyboard_state);
|
||||
|
||||
if (index != -1) {
|
||||
if (is_pressed) {
|
||||
if ((state.modifiers & HidKeyboardModifier_Shift) && m_keyboard_state.keys[index].codes.glfw_shift_char_code != -1) {
|
||||
handle_char(m_keyboard_state.keys[index].codes.glfw_shift_char_code);
|
||||
} else if (m_keyboard_state.keys[index].codes.glfw_char_code != -1) {
|
||||
handle_char(m_keyboard_state.keys[index].codes.glfw_char_code);
|
||||
}
|
||||
}
|
||||
|
||||
int mods = 0;
|
||||
|
||||
if (state.modifiers & HidKeyboardModifier_Shift) {
|
||||
mods |= GLFW_MOD_SHIFT;
|
||||
}
|
||||
|
||||
if (state.modifiers & HidKeyboardModifier_Control) {
|
||||
mods |= GLFW_MOD_CONTROL;
|
||||
}
|
||||
|
||||
if ((state.modifiers & HidKeyboardModifier_LeftAlt) || (state.modifiers & HidKeyboardModifier_RightAlt)) {
|
||||
mods |= GLFW_MOD_ALT;
|
||||
}
|
||||
|
||||
handle_key(m_keyboard_state.keys[index].codes.glfw_keycode, 0, is_pressed ? GLFW_PRESS : GLFW_RELEASE, mods);
|
||||
} else {
|
||||
Logger::info("Keyboard", "Unhandled HID keyboard key: 0x%X", i);
|
||||
}
|
||||
|
||||
m_hid_keyboard_state[i] = is_pressed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardController::handle_key(int key, int scancode, int action, int mods) {
|
||||
nanogui::keyboard_callback_event(key, scancode, action, mods);
|
||||
|
||||
int index = find_glfw_key_index(key, m_keyboard_state);
|
||||
|
||||
if (index >= 0) {
|
||||
m_keyboard_state.keys[index].is_pressed = action != 0;
|
||||
} else {
|
||||
Logger::info("Keyboard", "Unhandled GLFW key: %i", key);
|
||||
}
|
||||
|
||||
m_keyboard_state.glfw_modifiers = mods;
|
||||
|
||||
short moonlight_modifiers = 0;
|
||||
|
||||
if (mods & GLFW_MOD_SHIFT) {
|
||||
moonlight_modifiers |= MODIFIER_SHIFT;
|
||||
}
|
||||
|
||||
if (mods & GLFW_MOD_CONTROL) {
|
||||
moonlight_modifiers |= MODIFIER_CTRL;
|
||||
}
|
||||
|
||||
if (mods & GLFW_MOD_ALT) {
|
||||
moonlight_modifiers |= MODIFIER_ALT;
|
||||
}
|
||||
|
||||
if (mods & GLFW_MOD_SUPER) {
|
||||
moonlight_modifiers |= MODIFIER_META;
|
||||
}
|
||||
|
||||
m_keyboard_state.moonlight_modifiers = moonlight_modifiers;
|
||||
}
|
||||
|
||||
void KeyboardController::handle_char(unsigned int input) {
|
||||
nanogui::keyboard_character_event(input);
|
||||
}
|
45
src/controls/KeyboardController.hpp
Normal file
45
src/controls/KeyboardController.hpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#include "Singleton.hpp"
|
||||
#include <vector>
|
||||
#pragma once
|
||||
|
||||
struct GLFWwindow;
|
||||
|
||||
struct KeyCodes {
|
||||
KeyCodes(short glfw_keycode = 0, short glfw_char_code = 0, short glfw_shift_char_code = 0, short moonlight_keycode = 0);
|
||||
|
||||
short glfw_keycode;
|
||||
short glfw_char_code;
|
||||
short glfw_shift_char_code;
|
||||
|
||||
short moonlight_keycode;
|
||||
};
|
||||
|
||||
struct KeyState {
|
||||
KeyCodes codes;
|
||||
bool is_pressed = false;
|
||||
};
|
||||
|
||||
struct KeyboardState {
|
||||
KeyboardState();
|
||||
|
||||
std::vector<KeyState> keys;
|
||||
int glfw_modifiers = 0;
|
||||
short moonlight_modifiers = 0;
|
||||
};
|
||||
|
||||
class KeyboardController: public Singleton<KeyboardController> {
|
||||
public:
|
||||
void init(GLFWwindow* window);
|
||||
void handle_keyboard();
|
||||
|
||||
const KeyboardState& keyboard_state() {
|
||||
return m_keyboard_state;
|
||||
}
|
||||
|
||||
private:
|
||||
KeyboardState m_keyboard_state;
|
||||
std::vector<bool> m_hid_keyboard_state;
|
||||
|
||||
void handle_key(int key, int scancode, int action, int mods);
|
||||
void handle_char(unsigned int input);
|
||||
};
|
149
src/controls/MouseController.cpp
Normal file
149
src/controls/MouseController.cpp
Normal file
|
@ -0,0 +1,149 @@
|
|||
#include "MouseController.hpp"
|
||||
#include "Application.hpp"
|
||||
#include <nanogui/opengl.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
void MouseController::init(GLFWwindow* window) {
|
||||
hidInitializeMouse();
|
||||
hidInitializeTouchScreen();
|
||||
|
||||
glfwSetCursorPosCallback(window, [](auto _, double x, double y) {
|
||||
MouseController::instance().handle_mouse_move(x, y);
|
||||
});
|
||||
|
||||
glfwSetMouseButtonCallback(window, [](auto _, int button, int action, int modifiers) {
|
||||
MouseController::instance().handle_mouse_buttons(button, action, modifiers);
|
||||
});
|
||||
|
||||
glfwSetScrollCallback(window, [](auto _, double x, double y) {
|
||||
MouseController::instance().handle_mouse_scroll(x, y);
|
||||
});
|
||||
}
|
||||
|
||||
void MouseController::handle_mouse() {
|
||||
HidMouseState hid_mouse_state;
|
||||
|
||||
if (hidGetMouseStates(&hid_mouse_state, 1) && (hid_mouse_state.attributes & HidMouseAttribute_IsConnected)) {
|
||||
m_has_hid_mouse = true;
|
||||
|
||||
if (m_hid_mouse_state.x != hid_mouse_state.x || m_hid_mouse_state.y != hid_mouse_state.y || m_hid_mouse_state.buttons != hid_mouse_state.buttons || m_hid_mouse_state.wheel_delta_x != hid_mouse_state.wheel_delta_x) {
|
||||
m_draw_cursor_for_hid_mouse = true;
|
||||
m_hid_mouse_state = hid_mouse_state;
|
||||
|
||||
MouseState state;
|
||||
state.x = hid_mouse_state.x;
|
||||
state.y = hid_mouse_state.y;
|
||||
state.l_pressed = hid_mouse_state.buttons & HidMouseButton_Left;
|
||||
state.r_pressed = hid_mouse_state.buttons & HidMouseButton_Right;
|
||||
state.scroll_y = (double)hid_mouse_state.wheel_delta_x / 100; // Why wheel_delta_x?
|
||||
|
||||
if (m_state.x != state.x || m_state.y != state.y) {
|
||||
nanogui::cursor_pos_callback_event(state.x, state.y);
|
||||
}
|
||||
|
||||
if (m_state.l_pressed != state.l_pressed) {
|
||||
nanogui::mouse_button_callback_event(NANOGUI_MOUSE_BUTTON_1, state.l_pressed ? NANOGUI_PRESS : NANOGUI_RELEASE, 0);
|
||||
}
|
||||
|
||||
if (m_state.r_pressed != state.r_pressed) {
|
||||
nanogui::mouse_button_callback_event(NANOGUI_MOUSE_BUTTON_2, state.r_pressed ? NANOGUI_PRESS : NANOGUI_RELEASE, 0);
|
||||
}
|
||||
|
||||
if (m_state.scroll_y != state.scroll_y) {
|
||||
nanogui::scroll_callback_event(0, state.scroll_y);
|
||||
}
|
||||
|
||||
set_new_mouse_state(state);
|
||||
}
|
||||
} else {
|
||||
m_has_hid_mouse = false;
|
||||
}
|
||||
|
||||
// Simulate mouse wheel with two finger movements...
|
||||
HidTouchScreenState hid_touch_screen_state;
|
||||
if (hidGetTouchScreenStates(&hid_touch_screen_state, 1) && hid_touch_screen_state.count == 2) {
|
||||
double y = (hid_touch_screen_state.touches[0].y + hid_touch_screen_state.touches[1].y) / 2;
|
||||
|
||||
if (m_last_touch_y != 0 && m_last_touch_y != y) {
|
||||
auto state = m_state;
|
||||
state.scroll_y = (m_last_touch_y - y) / 10;
|
||||
|
||||
if (set_new_mouse_state(state)) {
|
||||
// Invert scroll_y for make scroll line on an iOS/Android :C
|
||||
nanogui::scroll_callback_event(0, -state.scroll_y);
|
||||
}
|
||||
}
|
||||
|
||||
m_last_touch_y = y;
|
||||
} else {
|
||||
m_last_touch_y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void MouseController::draw_cursor(Application *app) {
|
||||
if (m_has_hid_mouse && m_draw_cursor_for_hid_mouse) {
|
||||
auto ctx = app->nvg_context();
|
||||
nvgSave(ctx);
|
||||
nvgReset(ctx);
|
||||
|
||||
nvgFillColor(ctx, nanogui::Color(255, 255, 255, 245));
|
||||
nvgFontSize(ctx, 20);
|
||||
nvgFontFace(ctx, "icons");
|
||||
nvgTextAlign(ctx, NVG_ALIGN_TOP | NVG_ALIGN_MIDDLE);
|
||||
nvgText(ctx, m_state.x, m_state.y, nanogui::utf8(FA_MOUSE_POINTER).data(), NULL);
|
||||
|
||||
nvgRestore(ctx);
|
||||
|
||||
app->nvg_flush();
|
||||
}
|
||||
}
|
||||
|
||||
void MouseController::handle_mouse_move(int x, int y) {
|
||||
m_draw_cursor_for_hid_mouse = false;
|
||||
|
||||
auto state = m_state;
|
||||
state.x = x;
|
||||
state.y = y;
|
||||
|
||||
if (set_new_mouse_state(state)) {
|
||||
nanogui::cursor_pos_callback_event(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void MouseController::handle_mouse_buttons(int button, int action, int modifiers) {
|
||||
m_draw_cursor_for_hid_mouse = false;
|
||||
|
||||
auto state = m_state;
|
||||
|
||||
if (button == NANOGUI_MOUSE_BUTTON_1) {
|
||||
state.l_pressed = action == NANOGUI_PRESS;
|
||||
} else if (button == NANOGUI_MOUSE_BUTTON_2) {
|
||||
state.r_pressed = action == NANOGUI_PRESS;
|
||||
}
|
||||
|
||||
if (set_new_mouse_state(state)) {
|
||||
nanogui::mouse_button_callback_event(button, action, modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
void MouseController::handle_mouse_scroll(int x, int y) {
|
||||
m_draw_cursor_for_hid_mouse = false;
|
||||
|
||||
auto state = m_state;
|
||||
state.scroll_y = y;
|
||||
|
||||
if (set_new_mouse_state(state)) {
|
||||
nanogui::scroll_callback_event(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
bool MouseController::set_new_mouse_state(MouseState state) {
|
||||
if (m_state.x != state.x || m_state.y != state.y || m_state.l_pressed != state.l_pressed || m_state.r_pressed != state.r_pressed || m_state.scroll_y != state.scroll_y) {
|
||||
m_state = state;
|
||||
m_is_dirty = true;
|
||||
//printf("New state: %ix%i, L: %i, R: %i\n", state.x, state.y, state.l_pressed, state.r_pressed);
|
||||
} else {
|
||||
m_is_dirty = false;
|
||||
}
|
||||
return m_is_dirty;
|
||||
}
|
42
src/controls/MouseController.hpp
Normal file
42
src/controls/MouseController.hpp
Normal file
|
@ -0,0 +1,42 @@
|
|||
#include "Singleton.hpp"
|
||||
#include <switch.h>
|
||||
#pragma once
|
||||
|
||||
class Application;
|
||||
struct GLFWwindow;
|
||||
|
||||
struct MouseState {
|
||||
int x;
|
||||
int y;
|
||||
double scroll_y;
|
||||
bool l_pressed;
|
||||
bool r_pressed;
|
||||
};
|
||||
|
||||
class MouseController: public Singleton<MouseController> {
|
||||
public:
|
||||
void init(GLFWwindow* window);
|
||||
void handle_mouse();
|
||||
void draw_cursor(Application *app);
|
||||
|
||||
const MouseState& mouse_state() {
|
||||
return m_state;
|
||||
}
|
||||
|
||||
const bool is_dirty() {
|
||||
return m_is_dirty;
|
||||
}
|
||||
|
||||
private:
|
||||
MouseState m_state = {0};
|
||||
HidMouseState m_hid_mouse_state = {0};
|
||||
bool m_is_dirty = false;
|
||||
bool m_has_hid_mouse = false;
|
||||
bool m_draw_cursor_for_hid_mouse = false;
|
||||
double m_last_touch_y = 0;
|
||||
|
||||
void handle_mouse_move(int x, int y);
|
||||
void handle_mouse_buttons(int button, int action, int modifiers);
|
||||
void handle_mouse_scroll(int x, int y);
|
||||
bool set_new_mouse_state(MouseState state);
|
||||
};
|
42
src/controls/StreamControlsController.cpp
Normal file
42
src/controls/StreamControlsController.cpp
Normal file
|
@ -0,0 +1,42 @@
|
|||
#include "StreamControlsController.hpp"
|
||||
#include "Limelight.h"
|
||||
#include "Logger.hpp"
|
||||
#include <math.h>
|
||||
|
||||
void StreamControlsController::send_to_stream(int width, int height) {
|
||||
// Mouse
|
||||
auto mouse_state = MouseController::instance().mouse_state();
|
||||
|
||||
if (mouse_state.x != m_mouse_state.x || mouse_state.y != m_mouse_state.y) {
|
||||
LiSendMousePositionEvent(m_mouse_state.x, m_mouse_state.y, width, height);
|
||||
}
|
||||
|
||||
if (mouse_state.l_pressed != m_mouse_state.l_pressed) {
|
||||
LiSendMouseButtonEvent(mouse_state.l_pressed ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, BUTTON_LEFT);
|
||||
}
|
||||
|
||||
if (mouse_state.r_pressed != m_mouse_state.r_pressed) {
|
||||
LiSendMouseButtonEvent(mouse_state.r_pressed ? BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
|
||||
}
|
||||
|
||||
if (mouse_state.scroll_y != m_mouse_state.scroll_y && m_mouse_state.scroll_y != 0) {
|
||||
LiSendHighResScrollEvent(m_mouse_state.scroll_y > 0 ? fmax(m_mouse_state.scroll_y, 1) : fmin(m_mouse_state.scroll_y, -1));
|
||||
}
|
||||
|
||||
m_mouse_state = mouse_state;
|
||||
|
||||
// Keyboard
|
||||
auto keyboard_state = KeyboardController::instance().keyboard_state();
|
||||
|
||||
for (int i = 0; i < keyboard_state.keys.size(); i++) {
|
||||
if (keyboard_state.keys[i].is_pressed != m_keyboard_state.keys[i].is_pressed) {
|
||||
LiSendKeyboardEvent(
|
||||
keyboard_state.keys[i].codes.moonlight_keycode,
|
||||
keyboard_state.keys[i].is_pressed ? KEY_ACTION_DOWN : KEY_ACTION_UP,
|
||||
keyboard_state.moonlight_modifiers
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
m_keyboard_state = keyboard_state;
|
||||
}
|
13
src/controls/StreamControlsController.hpp
Normal file
13
src/controls/StreamControlsController.hpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#include "Singleton.hpp"
|
||||
#include "MouseController.hpp"
|
||||
#include "KeyboardController.hpp"
|
||||
#pragma once
|
||||
|
||||
class StreamControlsController: public Singleton<StreamControlsController> {
|
||||
public:
|
||||
void send_to_stream(int width, int height);
|
||||
|
||||
private:
|
||||
MouseState m_mouse_state = {0};
|
||||
KeyboardState m_keyboard_state;
|
||||
};
|
16
src/helpers/Singleton.hpp
Normal file
16
src/helpers/Singleton.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
template<typename T>
|
||||
class Singleton {
|
||||
public:
|
||||
static T& instance() {
|
||||
static T instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
Singleton(const Singleton&) = delete;
|
||||
Singleton& operator= (const Singleton) = delete;
|
||||
|
||||
protected:
|
||||
Singleton() {}
|
||||
};
|
55
src/main.cpp
55
src/main.cpp
|
@ -6,6 +6,8 @@
|
|||
#include "InputController.hpp"
|
||||
#include "GameStreamClient.hpp"
|
||||
#include "Logger.hpp"
|
||||
#include "MouseController.hpp"
|
||||
#include "KeyboardController.hpp"
|
||||
#include <glad/glad.h>
|
||||
#include <switch.h>
|
||||
|
||||
|
@ -61,23 +63,8 @@ int main(int argc, const char * argv[]) {
|
|||
nanogui::window_resize_callback_event(width, height, m_fb_width, m_fb_height);
|
||||
});
|
||||
|
||||
glfwSetCursorPosCallback(window, [](GLFWwindow *w, double x, double y) {
|
||||
InputController::controller()->handle_cursor_event(m_width, m_height, x, y);
|
||||
});
|
||||
|
||||
glfwSetMouseButtonCallback(window, [](GLFWwindow *w, int button, int action, int modifiers) {
|
||||
InputController::controller()->handle_mouse_event(button, action, modifiers);
|
||||
});
|
||||
|
||||
glfwSetScrollCallback(window, [](GLFWwindow *w, double x, double y) {
|
||||
InputController::controller()->handle_scroll(x, y);
|
||||
});
|
||||
|
||||
glfwSetKeyCallback(window, [](GLFWwindow *w, int key, int scancode, int action, int mods) {
|
||||
InputController::controller()->handle_keyboard_event(key, scancode, action, mods);
|
||||
});
|
||||
|
||||
hidInitializeMouse();
|
||||
MouseController::instance().init(window);
|
||||
KeyboardController::instance().init(window);
|
||||
|
||||
nanogui::init();
|
||||
nanogui::ref<Application> app = new Application(Size(m_width, m_height), Size(m_fb_width, m_fb_height));
|
||||
|
@ -88,41 +75,21 @@ int main(int argc, const char * argv[]) {
|
|||
glfwPollEvents();
|
||||
|
||||
#ifdef __SWITCH__
|
||||
glfwGetGamepadState(GLFW_JOYSTICK_1, &glfw_gamepad_state);
|
||||
InputController::controller()->handle_gamepad_event(glfw_gamepad_state);
|
||||
|
||||
static double scroll_x = -1, scroll_y = -1;
|
||||
|
||||
hidScanInput();
|
||||
u32 touch_count = hidTouchCount();
|
||||
|
||||
if (touch_count == 2) {
|
||||
touchPosition touch1, touch2;
|
||||
hidTouchRead(&touch1, 0);
|
||||
hidTouchRead(&touch2, 1);
|
||||
|
||||
double x = (double)(touch1.px + touch2.px) / 2;
|
||||
double y = (double)(touch1.py + touch2.py) / 2;
|
||||
|
||||
if (scroll_x != -1 && scroll_y != -1 && (scroll_x != x || scroll_y != y)) {
|
||||
InputController::controller()->handle_scroll((scroll_x - x) / 10, (scroll_y - y) / 10);
|
||||
}
|
||||
|
||||
scroll_x = x;
|
||||
scroll_y = y;
|
||||
} else {
|
||||
scroll_x = -1;
|
||||
scroll_y = -1;
|
||||
}
|
||||
|
||||
glfwGetGamepadState(GLFW_JOYSTICK_1, &glfw_gamepad_state);
|
||||
InputController::controller()->handle_gamepad_event(glfw_gamepad_state);
|
||||
#endif
|
||||
|
||||
MouseController::instance().handle_mouse();
|
||||
KeyboardController::instance().handle_keyboard();
|
||||
|
||||
int width, height;
|
||||
glfwGetFramebufferSize(window, &width, &height);
|
||||
glViewport(0, 0, width, height);
|
||||
|
||||
nanogui::draw();
|
||||
|
||||
//MouseController::instance().draw_cursor(app);
|
||||
|
||||
glfwSwapBuffers(window);
|
||||
}
|
||||
|
||||
|
|
82
src/streaming/AVFrameHolder.hpp
Normal file
82
src/streaming/AVFrameHolder.hpp
Normal file
|
@ -0,0 +1,82 @@
|
|||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
}
|
||||
|
||||
#pragma once
|
||||
|
||||
class AVFrameHolder {
|
||||
public:
|
||||
static AVFrameHolder* holder() {
|
||||
static AVFrameHolder holder;
|
||||
return &holder;
|
||||
}
|
||||
|
||||
void push(AVFrame *frame) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
if (m_frame) {
|
||||
av_frame_free(&m_frame);
|
||||
m_frame = nullptr;
|
||||
}
|
||||
|
||||
if (frame == nullptr) {
|
||||
printf("frame is nullptr...\n");
|
||||
return;
|
||||
}
|
||||
|
||||
m_frame = av_frame_alloc();
|
||||
|
||||
if (m_frame == nullptr) {
|
||||
printf("m_frame is nullptr...\n");
|
||||
return;
|
||||
}
|
||||
|
||||
m_frame->format = frame->format;
|
||||
m_frame->width = frame->width;
|
||||
m_frame->height = frame->height;
|
||||
m_frame->channels = frame->channels;
|
||||
m_frame->channel_layout = frame->channel_layout;
|
||||
m_frame->nb_samples = frame->nb_samples;
|
||||
|
||||
int result = 0;
|
||||
result = av_frame_get_buffer(m_frame, 32);
|
||||
if (result != 0) {
|
||||
Logger::error("AVFrameHolder", "av_frame_get_buffer");
|
||||
}
|
||||
|
||||
result = av_frame_copy(m_frame, frame);
|
||||
if (result != 0) {
|
||||
Logger::error("AVFrameHolder", "av_frame_copy");
|
||||
}
|
||||
|
||||
result = av_frame_copy_props(m_frame, frame);
|
||||
if (result != 0) {
|
||||
Logger::error("AVFrameHolder", "av_frame_copy_props");
|
||||
}
|
||||
}
|
||||
|
||||
void get(const std::function<void(AVFrame*)> fn) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
if (m_frame) {
|
||||
fn(m_frame);
|
||||
}
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
// TODO: plz...
|
||||
}
|
||||
|
||||
private:
|
||||
AVFrameHolder() {};
|
||||
|
||||
std::mutex m_mutex;
|
||||
AVFrame *m_frame = nullptr;
|
||||
bool m_has_frame = false;
|
||||
};
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
#include "Settings.hpp"
|
||||
#include "InputController.hpp"
|
||||
#include "Logger.hpp"
|
||||
#include "AVFrameHolder.hpp"
|
||||
#include <nanogui/nanogui.h>
|
||||
|
||||
static MoonlightSession* m_active_session = nullptr;
|
||||
|
@ -240,13 +241,17 @@ void MoonlightSession::stop(int terminate_app) {
|
|||
}
|
||||
|
||||
LiStopConnection();
|
||||
AVFrameHolder::holder()->cleanup();
|
||||
}
|
||||
|
||||
void MoonlightSession::draw() {
|
||||
if (m_video_decoder && m_video_renderer) {
|
||||
if (auto frame = m_video_decoder->frame()) {
|
||||
AVFrameHolder::holder()->get([this](auto frame) {
|
||||
m_video_renderer->draw(m_config.width, m_config.height, frame);
|
||||
}
|
||||
});
|
||||
// if (auto frame = m_video_decoder->frame()) {
|
||||
// m_video_renderer->draw(m_config.width, m_config.height, frame);
|
||||
// }
|
||||
|
||||
m_session_stats.video_decode_stats = *m_video_decoder->video_decode_stats();
|
||||
m_session_stats.video_render_stats = *m_video_renderer->video_render_stats();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "FFmpegVideoDecoder.hpp"
|
||||
#include "Settings.hpp"
|
||||
#include "Logger.hpp"
|
||||
#include "AVFrameHolder.hpp"
|
||||
|
||||
// Disables the deblocking filter at the cost of image quality
|
||||
#define DISABLE_LOOP_FILTER 0x1
|
||||
|
@ -9,22 +10,83 @@
|
|||
|
||||
#define DECODER_BUFFER_SIZE 92 * 1024 * 2
|
||||
|
||||
#include <switch.h>
|
||||
#include <unistd.h>
|
||||
|
||||
class ThreadLockWatcher {
|
||||
public:
|
||||
void start() {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
is_alive = true;
|
||||
prev = current = 0;
|
||||
pthread_create(&thread, NULL, entry, this);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
is_alive = false;
|
||||
}
|
||||
|
||||
void enter(int line) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
last_line = line;
|
||||
prev = current;
|
||||
current++;
|
||||
}
|
||||
|
||||
private:
|
||||
bool is_alive = false;
|
||||
u64 prev = 0;
|
||||
u64 current = 0;
|
||||
int last_line = 0;
|
||||
|
||||
static void* entry(void* context) {
|
||||
while (true) {
|
||||
if (auto watcher = static_cast<ThreadLockWatcher *>(context)) {
|
||||
if (watcher->is_alive) {
|
||||
if (watcher->current != 0) {
|
||||
std::lock_guard<std::mutex> lock(watcher->m_mutex);
|
||||
|
||||
if (watcher->prev > watcher->current) {
|
||||
Logger::error("ThreadLockWatcher", "Thread Lock Detected on a line: %i", watcher->last_line);
|
||||
} else {
|
||||
watcher->prev++;
|
||||
}
|
||||
}
|
||||
usleep(500'000);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
std::mutex m_mutex;
|
||||
pthread_t thread;
|
||||
};
|
||||
|
||||
static ThreadLockWatcher watcher;
|
||||
#define DBG() watcher.enter(__LINE__);
|
||||
|
||||
FFmpegVideoDecoder::FFmpegVideoDecoder() {
|
||||
pthread_mutex_init(&m_mutex, NULL);
|
||||
//pthread_mutex_init(&m_mutex, NULL);
|
||||
watcher.start();
|
||||
}
|
||||
|
||||
FFmpegVideoDecoder::~FFmpegVideoDecoder() {
|
||||
pthread_mutex_destroy(&m_mutex);
|
||||
cleanup();
|
||||
|
||||
if (m_hardware_video_decoder) {
|
||||
delete m_hardware_video_decoder;
|
||||
}
|
||||
watcher.stop();
|
||||
//pthread_mutex_destroy(&m_mutex);
|
||||
}
|
||||
|
||||
int FFmpegVideoDecoder::setup(int video_format, int width, int height, int redraw_rate, void *context, int dr_flags) {
|
||||
m_stream_fps = redraw_rate;
|
||||
|
||||
Logger::info("FFmpeg", "Setup with format: %s, width: %i, height: %i, fps: %i", video_format == VIDEO_FORMAT_H264 ? "H264" : "HEVC", width, height, redraw_rate);
|
||||
|
||||
av_log_set_level(AV_LOG_QUIET);
|
||||
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58,10,100)
|
||||
avcodec_register_all();
|
||||
|
@ -103,14 +165,14 @@ int FFmpegVideoDecoder::setup(int video_format, int width, int height, int redra
|
|||
return -1;
|
||||
}
|
||||
|
||||
if (m_hardware_video_decoder) {
|
||||
m_hardware_video_decoder->prepare_decoder_context(m_decoder_context, nullptr);
|
||||
}
|
||||
Logger::info("FFmpeg", "Setup done!");
|
||||
|
||||
return DR_OK;
|
||||
}
|
||||
|
||||
void FFmpegVideoDecoder::cleanup() {
|
||||
Logger::info("FFmpeg", "Cleanup...");
|
||||
|
||||
if (m_decoder_context) {
|
||||
avcodec_close(m_decoder_context);
|
||||
av_free(m_decoder_context);
|
||||
|
@ -131,33 +193,46 @@ void FFmpegVideoDecoder::cleanup() {
|
|||
free(m_ffmpeg_buffer);
|
||||
m_ffmpeg_buffer = nullptr;
|
||||
}
|
||||
|
||||
Logger::info("FFmpeg", "Cleanup done!");
|
||||
}
|
||||
|
||||
int FFmpegVideoDecoder::submit_decode_unit(PDECODE_UNIT decode_unit) {
|
||||
DBG();
|
||||
if (decode_unit->fullLength < DECODER_BUFFER_SIZE) {
|
||||
PLENTRY entry = decode_unit->bufferList;
|
||||
|
||||
DBG();
|
||||
if (!m_last_frame) {
|
||||
m_video_decode_stats.measurement_start_timestamp = LiGetMillis();
|
||||
m_last_frame = decode_unit->frameNumber;
|
||||
DBG();
|
||||
}
|
||||
else {
|
||||
// Any frame number greater than m_LastFrameNumber + 1 represents a dropped frame
|
||||
m_video_decode_stats.network_dropped_frames += decode_unit->frameNumber - (m_last_frame + 1);
|
||||
m_video_decode_stats.total_frames += decode_unit->frameNumber - (m_last_frame + 1);
|
||||
m_last_frame = decode_unit->frameNumber;
|
||||
DBG();
|
||||
}
|
||||
|
||||
m_video_decode_stats.received_frames++;
|
||||
m_video_decode_stats.total_frames++;
|
||||
DBG();
|
||||
|
||||
int length = 0;
|
||||
while (entry != NULL) {
|
||||
if (length > DECODER_BUFFER_SIZE) {
|
||||
Logger::error("FFmpeg", "Big buffer to decode... !");
|
||||
}
|
||||
|
||||
DBG();
|
||||
memcpy(m_ffmpeg_buffer + length, entry->data, entry->length);
|
||||
length += entry->length;
|
||||
entry = entry->next;
|
||||
DBG();
|
||||
}
|
||||
|
||||
DBG();
|
||||
m_video_decode_stats.total_reassembly_time += LiGetMillis() - decode_unit->receiveTimeMs;
|
||||
|
||||
m_frames_in++;
|
||||
|
@ -168,23 +243,27 @@ int FFmpegVideoDecoder::submit_decode_unit(PDECODE_UNIT decode_unit) {
|
|||
Logger::error("FFmpeg", "Big buffer to decode...");
|
||||
}
|
||||
|
||||
DBG();
|
||||
if (decode(m_ffmpeg_buffer, length) == 0) {
|
||||
m_frames_out++;
|
||||
|
||||
DBG();
|
||||
m_video_decode_stats.total_decode_time += LiGetMillis() - before_decode;
|
||||
|
||||
// Also count the frame-to-frame delay if the decoder is delaying frames
|
||||
// until a subsequent frame is submitted.
|
||||
m_video_decode_stats.total_decode_time += (m_frames_in - m_frames_out) * (1000 / m_stream_fps);
|
||||
m_video_decode_stats.decoded_frames++;
|
||||
|
||||
if (pthread_mutex_lock(&m_mutex) == 0) {
|
||||
DBG();
|
||||
//if (pthread_mutex_lock(&m_mutex) == 0) {
|
||||
m_frame = get_frame(true);
|
||||
|
||||
AVFrameHolder::holder()->push(m_frame);
|
||||
DBG();//
|
||||
// Push event!!
|
||||
pthread_mutex_unlock(&m_mutex);
|
||||
}
|
||||
//pthread_mutex_unlock(&m_mutex);
|
||||
//}
|
||||
}
|
||||
} else {
|
||||
Logger::error("FFmpeg", "Big buffer to decode... 2");
|
||||
}
|
||||
return DR_OK;
|
||||
}
|
||||
|
@ -196,28 +275,36 @@ int FFmpegVideoDecoder::capabilities() const {
|
|||
int FFmpegVideoDecoder::decode(char* indata, int inlen) {
|
||||
m_packet.data = (uint8_t *)indata;
|
||||
m_packet.size = inlen;
|
||||
|
||||
DBG();
|
||||
int err = avcodec_send_packet(m_decoder_context, &m_packet);
|
||||
if (err < 0) {
|
||||
DBG();
|
||||
if (err != 0) {
|
||||
DBG();
|
||||
char error[512];
|
||||
av_strerror(err, error, sizeof(error));
|
||||
Logger::error("FFmpeg", "Decode failed - %s", error);
|
||||
DBG();
|
||||
}
|
||||
|
||||
return err < 0 ? err : 0;
|
||||
DBG();
|
||||
return err != 0 ? err : 0;
|
||||
}
|
||||
|
||||
AVFrame* FFmpegVideoDecoder::get_frame(bool native_frame) {
|
||||
DBG();
|
||||
int err = avcodec_receive_frame(m_decoder_context, m_frames[m_next_frame]);
|
||||
DBG();
|
||||
if (err == 0) {
|
||||
DBG();
|
||||
m_current_frame = m_next_frame;
|
||||
m_next_frame = (m_current_frame + 1) % m_frames_count;
|
||||
|
||||
DBG();
|
||||
if (/*ffmpeg_decoder == SOFTWARE ||*/ native_frame)
|
||||
return m_frames[m_current_frame];
|
||||
} else if (err != AVERROR(EAGAIN)) {
|
||||
DBG();
|
||||
char error[512];
|
||||
av_strerror(err, error, sizeof(error));
|
||||
DBG();
|
||||
Logger::error("FFmpeg", "Receive failed - %d/%s", err, error);
|
||||
}
|
||||
return NULL;
|
||||
|
|
|
@ -2,21 +2,11 @@
|
|||
#include <pthread.h>
|
||||
#pragma once
|
||||
|
||||
class IFFmpegHardwareVideoDecoder {
|
||||
public:
|
||||
virtual ~IFFmpegHardwareVideoDecoder() {};
|
||||
virtual bool prepare_decoder_context(AVCodecContext* context, AVDictionary** options) = 0;
|
||||
};
|
||||
|
||||
class FFmpegVideoDecoder: public IFFmpegVideoDecoder {
|
||||
public:
|
||||
FFmpegVideoDecoder();
|
||||
~FFmpegVideoDecoder();
|
||||
|
||||
void set_hardware_video_decoder(IFFmpegHardwareVideoDecoder* hardware_video_decoder) {
|
||||
m_hardware_video_decoder = hardware_video_decoder;
|
||||
}
|
||||
|
||||
int setup(int video_format, int width, int height, int redraw_rate, void *context, int dr_flags) override;
|
||||
void cleanup() override;
|
||||
int submit_decode_unit(PDECODE_UNIT decode_unit) override;
|
||||
|
@ -28,8 +18,6 @@ private:
|
|||
int decode(char* indata, int inlen);
|
||||
AVFrame* get_frame(bool native_frame);
|
||||
|
||||
IFFmpegHardwareVideoDecoder* m_hardware_video_decoder = nullptr;
|
||||
|
||||
AVPacket m_packet;
|
||||
AVCodec* m_decoder = nullptr;
|
||||
AVCodecContext* m_decoder_context = nullptr;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "GLVideoRenderer.hpp"
|
||||
#include "Logger.hpp"
|
||||
|
||||
static const char *vertex_shader_string = "\
|
||||
#version 140\n\
|
||||
|
@ -92,6 +93,8 @@ static const float* gl_color_matrix(enum AVColorSpace color_space, bool color_fu
|
|||
}
|
||||
|
||||
GLVideoRenderer::~GLVideoRenderer() {
|
||||
Logger::info("GL", "Cleanup...");
|
||||
|
||||
if (m_shader_program) {
|
||||
glDeleteProgram(m_shader_program);
|
||||
}
|
||||
|
@ -109,6 +112,8 @@ GLVideoRenderer::~GLVideoRenderer() {
|
|||
glDeleteTextures(1, &m_texture_id[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Logger::info("GL", "Cleanup done!");
|
||||
}
|
||||
|
||||
void GLVideoRenderer::initialize() {
|
||||
|
@ -157,8 +162,12 @@ void GLVideoRenderer::draw(int width, int height, AVFrame *frame) {
|
|||
uint64_t before_render = LiGetMillis();
|
||||
|
||||
if (!m_is_initialized) {
|
||||
Logger::info("GL", "Init with width: %i, height: %i", width, height);
|
||||
|
||||
initialize();
|
||||
m_is_initialized = true;
|
||||
|
||||
Logger::info("GL", "Init done");
|
||||
}
|
||||
|
||||
if (m_width != frame->width || m_height != frame->height) {
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
// Some mock part of libnx stuff for import and use <switch.h> on others plaforms
|
||||
#include <stdlib.h>
|
||||
|
||||
#pragma once
|
||||
|
||||
typedef uint8_t u8; ///< 8-bit unsigned integer.
|
||||
typedef uint16_t u16; ///< 16-bit unsigned integer.
|
||||
|
@ -59,8 +62,240 @@ typedef enum {
|
|||
HidMouseButton_Back = BIT(4),
|
||||
} HidMouseButton;
|
||||
|
||||
static void hidInitializeMouse(void) {};
|
||||
static void hidInitializeMouse(void) {}
|
||||
|
||||
static size_t hidGetMouseStates(HidMouseState *states, size_t count) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hidInitializeTouchScreen(void) {}
|
||||
|
||||
/// HidTouchState
|
||||
typedef struct HidTouchState {
|
||||
u64 delta_time; ///< DeltaTime
|
||||
u32 attributes; ///< Bitfield of \ref HidTouchAttribute.
|
||||
u32 finger_id; ///< FingerId
|
||||
u32 x; ///< X
|
||||
u32 y; ///< Y
|
||||
u32 diameter_x; ///< DiameterX
|
||||
u32 diameter_y; ///< DiameterY
|
||||
u32 rotation_angle; ///< RotationAngle
|
||||
u32 reserved; ///< Reserved
|
||||
} HidTouchState;
|
||||
|
||||
/// HidTouchScreenState
|
||||
typedef struct HidTouchScreenState {
|
||||
u64 sampling_number; ///< SamplingNumber
|
||||
s32 count; ///< Number of entries in the touches array.
|
||||
u32 reserved; ///< Reserved
|
||||
HidTouchState touches[16]; ///< Array of \ref HidTouchState, with the above count.
|
||||
} HidTouchScreenState;
|
||||
|
||||
static size_t hidGetTouchScreenStates(HidTouchScreenState *states, size_t count) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// HidKeyboardScancode \deprecated
|
||||
typedef enum DEPRECATED {
|
||||
KBD_NONE = 0x00,
|
||||
KBD_ERR_OVF = 0x01,
|
||||
|
||||
KBD_A = 0x04,
|
||||
KBD_B = 0x05,
|
||||
KBD_C = 0x06,
|
||||
KBD_D = 0x07,
|
||||
KBD_E = 0x08,
|
||||
KBD_F = 0x09,
|
||||
KBD_G = 0x0a,
|
||||
KBD_H = 0x0b,
|
||||
KBD_I = 0x0c,
|
||||
KBD_J = 0x0d,
|
||||
KBD_K = 0x0e,
|
||||
KBD_L = 0x0f,
|
||||
KBD_M = 0x10,
|
||||
KBD_N = 0x11,
|
||||
KBD_O = 0x12,
|
||||
KBD_P = 0x13,
|
||||
KBD_Q = 0x14,
|
||||
KBD_R = 0x15,
|
||||
KBD_S = 0x16,
|
||||
KBD_T = 0x17,
|
||||
KBD_U = 0x18,
|
||||
KBD_V = 0x19,
|
||||
KBD_W = 0x1a,
|
||||
KBD_X = 0x1b,
|
||||
KBD_Y = 0x1c,
|
||||
KBD_Z = 0x1d,
|
||||
|
||||
KBD_1 = 0x1e,
|
||||
KBD_2 = 0x1f,
|
||||
KBD_3 = 0x20,
|
||||
KBD_4 = 0x21,
|
||||
KBD_5 = 0x22,
|
||||
KBD_6 = 0x23,
|
||||
KBD_7 = 0x24,
|
||||
KBD_8 = 0x25,
|
||||
KBD_9 = 0x26,
|
||||
KBD_0 = 0x27,
|
||||
|
||||
KBD_ENTER = 0x28,
|
||||
KBD_ESC = 0x29,
|
||||
KBD_BACKSPACE = 0x2a,
|
||||
KBD_TAB = 0x2b,
|
||||
KBD_SPACE = 0x2c,
|
||||
KBD_MINUS = 0x2d,
|
||||
KBD_EQUAL = 0x2e,
|
||||
KBD_LEFTBRACE = 0x2f,
|
||||
KBD_RIGHTBRACE = 0x30,
|
||||
KBD_BACKSLASH = 0x31,
|
||||
KBD_HASHTILDE = 0x32,
|
||||
KBD_SEMICOLON = 0x33,
|
||||
KBD_APOSTROPHE = 0x34,
|
||||
KBD_GRAVE = 0x35,
|
||||
KBD_COMMA = 0x36,
|
||||
KBD_DOT = 0x37,
|
||||
KBD_SLASH = 0x38,
|
||||
KBD_CAPSLOCK = 0x39,
|
||||
|
||||
KBD_F1 = 0x3a,
|
||||
KBD_F2 = 0x3b,
|
||||
KBD_F3 = 0x3c,
|
||||
KBD_F4 = 0x3d,
|
||||
KBD_F5 = 0x3e,
|
||||
KBD_F6 = 0x3f,
|
||||
KBD_F7 = 0x40,
|
||||
KBD_F8 = 0x41,
|
||||
KBD_F9 = 0x42,
|
||||
KBD_F10 = 0x43,
|
||||
KBD_F11 = 0x44,
|
||||
KBD_F12 = 0x45,
|
||||
|
||||
KBD_SYSRQ = 0x46,
|
||||
KBD_SCROLLLOCK = 0x47,
|
||||
KBD_PAUSE = 0x48,
|
||||
KBD_INSERT = 0x49,
|
||||
KBD_HOME = 0x4a,
|
||||
KBD_PAGEUP = 0x4b,
|
||||
KBD_DELETE = 0x4c,
|
||||
KBD_END = 0x4d,
|
||||
KBD_PAGEDOWN = 0x4e,
|
||||
KBD_RIGHT = 0x4f,
|
||||
KBD_LEFT = 0x50,
|
||||
KBD_DOWN = 0x51,
|
||||
KBD_UP = 0x52,
|
||||
|
||||
KBD_NUMLOCK = 0x53,
|
||||
KBD_KPSLASH = 0x54,
|
||||
KBD_KPASTERISK = 0x55,
|
||||
KBD_KPMINUS = 0x56,
|
||||
KBD_KPPLUS = 0x57,
|
||||
KBD_KPENTER = 0x58,
|
||||
KBD_KP1 = 0x59,
|
||||
KBD_KP2 = 0x5a,
|
||||
KBD_KP3 = 0x5b,
|
||||
KBD_KP4 = 0x5c,
|
||||
KBD_KP5 = 0x5d,
|
||||
KBD_KP6 = 0x5e,
|
||||
KBD_KP7 = 0x5f,
|
||||
KBD_KP8 = 0x60,
|
||||
KBD_KP9 = 0x61,
|
||||
KBD_KP0 = 0x62,
|
||||
KBD_KPDOT = 0x63,
|
||||
|
||||
KBD_102ND = 0x64,
|
||||
KBD_COMPOSE = 0x65,
|
||||
KBD_POWER = 0x66,
|
||||
KBD_KPEQUAL = 0x67,
|
||||
|
||||
KBD_F13 = 0x68,
|
||||
KBD_F14 = 0x69,
|
||||
KBD_F15 = 0x6a,
|
||||
KBD_F16 = 0x6b,
|
||||
KBD_F17 = 0x6c,
|
||||
KBD_F18 = 0x6d,
|
||||
KBD_F19 = 0x6e,
|
||||
KBD_F20 = 0x6f,
|
||||
KBD_F21 = 0x70,
|
||||
KBD_F22 = 0x71,
|
||||
KBD_F23 = 0x72,
|
||||
KBD_F24 = 0x73,
|
||||
|
||||
KBD_OPEN = 0x74,
|
||||
KBD_HELP = 0x75,
|
||||
KBD_PROPS = 0x76,
|
||||
KBD_FRONT = 0x77,
|
||||
KBD_STOP = 0x78,
|
||||
KBD_AGAIN = 0x79,
|
||||
KBD_UNDO = 0x7a,
|
||||
KBD_CUT = 0x7b,
|
||||
KBD_COPY = 0x7c,
|
||||
KBD_PASTE = 0x7d,
|
||||
KBD_FIND = 0x7e,
|
||||
KBD_MUTE = 0x7f,
|
||||
KBD_VOLUMEUP = 0x80,
|
||||
KBD_VOLUMEDOWN = 0x81,
|
||||
KBD_CAPSLOCK_ACTIVE = 0x82 ,
|
||||
KBD_NUMLOCK_ACTIVE = 0x83 ,
|
||||
KBD_SCROLLLOCK_ACTIVE = 0x84 ,
|
||||
KBD_KPCOMMA = 0x85,
|
||||
|
||||
KBD_KPLEFTPAREN = 0xb6,
|
||||
KBD_KPRIGHTPAREN = 0xb7,
|
||||
|
||||
KBD_LEFTCTRL = 0xe0,
|
||||
KBD_LEFTSHIFT = 0xe1,
|
||||
KBD_LEFTALT = 0xe2,
|
||||
KBD_LEFTMETA = 0xe3,
|
||||
KBD_RIGHTCTRL = 0xe4,
|
||||
KBD_RIGHTSHIFT = 0xe5,
|
||||
KBD_RIGHTALT = 0xe6,
|
||||
KBD_RIGHTMETA = 0xe7,
|
||||
|
||||
KBD_MEDIA_PLAYPAUSE = 0xe8,
|
||||
KBD_MEDIA_STOPCD = 0xe9,
|
||||
KBD_MEDIA_PREVIOUSSONG = 0xea,
|
||||
KBD_MEDIA_NEXTSONG = 0xeb,
|
||||
KBD_MEDIA_EJECTCD = 0xec,
|
||||
KBD_MEDIA_VOLUMEUP = 0xed,
|
||||
KBD_MEDIA_VOLUMEDOWN = 0xee,
|
||||
KBD_MEDIA_MUTE = 0xef,
|
||||
KBD_MEDIA_WWW = 0xf0,
|
||||
KBD_MEDIA_BACK = 0xf1,
|
||||
KBD_MEDIA_FORWARD = 0xf2,
|
||||
KBD_MEDIA_STOP = 0xf3,
|
||||
KBD_MEDIA_FIND = 0xf4,
|
||||
KBD_MEDIA_SCROLLUP = 0xf5,
|
||||
KBD_MEDIA_SCROLLDOWN = 0xf6,
|
||||
KBD_MEDIA_EDIT = 0xf7,
|
||||
KBD_MEDIA_SLEEP = 0xf8,
|
||||
KBD_MEDIA_COFFEE = 0xf9,
|
||||
KBD_MEDIA_REFRESH = 0xfa,
|
||||
KBD_MEDIA_CALC = 0xfb
|
||||
} HidKeyboardScancode;
|
||||
|
||||
/// HidKeyboardModifier
|
||||
typedef enum {
|
||||
HidKeyboardModifier_Control = BIT(0),
|
||||
HidKeyboardModifier_Shift = BIT(1),
|
||||
HidKeyboardModifier_LeftAlt = BIT(2),
|
||||
HidKeyboardModifier_RightAlt = BIT(3),
|
||||
HidKeyboardModifier_Gui = BIT(4),
|
||||
HidKeyboardModifier_CapsLock = BIT(8),
|
||||
HidKeyboardModifier_ScrollLock = BIT(9),
|
||||
HidKeyboardModifier_NumLock = BIT(10),
|
||||
HidKeyboardModifier_Katakana = BIT(11),
|
||||
HidKeyboardModifier_Hiragana = BIT(12),
|
||||
} HidKeyboardModifier;
|
||||
|
||||
/// HidKeyboardState
|
||||
typedef struct HidKeyboardState {
|
||||
u64 sampling_number; ///< SamplingNumber
|
||||
u64 modifiers; ///< Bitfield of \ref HidKeyboardModifier.
|
||||
u64 keys[4];
|
||||
} HidKeyboardState;
|
||||
|
||||
static void hidInitializeKeyboard(void) {}
|
||||
|
||||
static size_t hidGetKeyboardStates(HidKeyboardState *states, size_t count) {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ AddHostWindow::AddHostWindow(Widget *parent): ContentWindow(parent, "Add Host")
|
|||
auto text = container()->add<TextBox>("");
|
||||
text->set_placeholder("Enter the IP address of your GameStream PC");
|
||||
text->set_fixed_height(60);
|
||||
text->set_editable(true);
|
||||
|
||||
std::vector<std::string> keys {"7", "8", "9", "4", "5", "6", "1", "2", "3"};
|
||||
|
||||
|
|
|
@ -204,33 +204,32 @@ static inline Widget *most_closed_widget(Widget *target, std::vector<Widget *> w
|
|||
|
||||
static inline Widget* find_new_selectable(std::vector<Widget *> selectables, int jid, int button, int action) {
|
||||
auto current = std::find_if(selectables.begin(), selectables.end(), [](auto c) { return c->selected(); });
|
||||
|
||||
if (current != selectables.end() && (*current)->gamepad_button_event(jid, button, action)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (button >= NANOGUI_GAMEPAD_BUTTON_DPAD_UP && button <= NANOGUI_GAMEPAD_BUTTON_DPAD_LEFT) {
|
||||
if (current == selectables.end()) {
|
||||
selectables.front()->set_selected(true);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
auto current_selectable = *current;
|
||||
auto new_selectable = most_closed_widget(
|
||||
current_selectable,
|
||||
selectables,
|
||||
button == NANOGUI_GAMEPAD_BUTTON_DPAD_LEFT,
|
||||
button == NANOGUI_GAMEPAD_BUTTON_DPAD_RIGHT,
|
||||
button == NANOGUI_GAMEPAD_BUTTON_DPAD_UP,
|
||||
button == NANOGUI_GAMEPAD_BUTTON_DPAD_DOWN
|
||||
);
|
||||
|
||||
if (new_selectable) {
|
||||
current_selectable->set_selected(false);
|
||||
new_selectable->set_selected(true);
|
||||
return new_selectable;
|
||||
}
|
||||
}
|
||||
|
||||
if (current != selectables.end() && (*current)->gamepad_button_event(jid, button, action)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (button >= NANOGUI_GAMEPAD_BUTTON_DPAD_UP && button <= NANOGUI_GAMEPAD_BUTTON_DPAD_LEFT) {
|
||||
if (current == selectables.end()) {
|
||||
selectables.front()->set_selected(true);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
auto current_selectable = *current;
|
||||
auto new_selectable = most_closed_widget(current_selectable,
|
||||
selectables,
|
||||
button == NANOGUI_GAMEPAD_BUTTON_DPAD_LEFT,
|
||||
button == NANOGUI_GAMEPAD_BUTTON_DPAD_RIGHT,
|
||||
button == NANOGUI_GAMEPAD_BUTTON_DPAD_UP,
|
||||
button == NANOGUI_GAMEPAD_BUTTON_DPAD_DOWN
|
||||
);
|
||||
|
||||
if (new_selectable) {
|
||||
current_selectable->set_selected(false);
|
||||
new_selectable->set_selected(true);
|
||||
return new_selectable;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -64,10 +64,6 @@ public:
|
|||
m_container->set_layout(new nanogui::BoxLayout(orientation, alignment, margin, spacing));
|
||||
}
|
||||
|
||||
bool keyboard_event(int key, int scancode, int action, int modifiers) override {
|
||||
return true;
|
||||
};
|
||||
|
||||
bool gamepad_button_event(int jid, int button, int action) override;
|
||||
|
||||
private:
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "LoadingOverlay.hpp"
|
||||
#include "Alert.hpp"
|
||||
#include "InputController.hpp"
|
||||
#include "StreamControlsController.hpp"
|
||||
#include "FFmpegVideoDecoder.hpp"
|
||||
#include "GLVideoRenderer.hpp"
|
||||
#ifdef __SWITCH__
|
||||
|
@ -137,7 +138,8 @@ void StreamWindow::draw(NVGcontext *ctx) {
|
|||
m_draw_stats = false;
|
||||
}
|
||||
|
||||
InputController::controller()->send_to_stream();
|
||||
//InputController::controller()->send_to_stream();
|
||||
StreamControlsController::instance().send_to_stream(width(), height());
|
||||
}
|
||||
|
||||
bool StreamWindow::mouse_button_event(const nanogui::Vector2i &p, int button, bool down, int modifiers) {
|
||||
|
|
Loading…
Add table
Reference in a new issue