Improved the mouse binding system

This commit is contained in:
WolverinDEV 2020-04-25 12:48:44 +02:00
parent 185f70182a
commit 383c9fbda9
7 changed files with 313 additions and 173 deletions

View File

@ -122,5 +122,5 @@ function deploy_client() {
#install_npm #install_npm
#compile_scripts #compile_scripts
#compile_native #compile_native
#package_client package_client
deploy_client deploy_client

View File

@ -85,7 +85,7 @@ export class VoiceConnection extends AbstractVoiceConnection {
return false; return false;
console.log(tr("Local voice ended")); console.log(tr("Local voice ended"));
//TODO //TODO Send end? (Or is this already an automated thing?)
} }
private on_voice_started() { private on_voice_started() {

View File

@ -1,6 +1,5 @@
#include <v8.h> #include <v8.h>
#include <nan.h> #include <nan.h>
#include <node.h>
#include <iostream> #include <iostream>
#include <mutex> #include <mutex>
#include <include/NanStrings.h> #include <include/NanStrings.h>

View File

@ -52,6 +52,7 @@ class KeyboardHook {
inline bool attached() { return this->_attached; } inline bool attached() { return this->_attached; }
void detach(); void detach();
void trigger_key_event(const enum KeyEvent::type&, const std::string& /* key */);
callback_event_t callback_event; callback_event_t callback_event;
private: private:
#ifdef HAVE_X11 #ifdef HAVE_X11
@ -70,8 +71,6 @@ class KeyboardHook {
bool keyboard_hook_callback(int, WPARAM, LPARAM); bool keyboard_hook_callback(int, WPARAM, LPARAM);
bool mouse_hook_callback(int, WPARAM, LPARAM); bool mouse_hook_callback(int, WPARAM, LPARAM);
#endif #endif
void trigger_key_event(const enum KeyEvent::type&, const std::string& /* key */);
std::map<KeyID, bool> map_key; std::map<KeyID, bool> map_key;
std::map<KeyID, KeyID> map_special; std::map<KeyID, KeyID> map_special;

View File

@ -1,15 +1,103 @@
#include <iostream> #include <iostream>
#include <cassert> #include <cassert>
#include <string> #include <string>
#include <mutex>
#include "KeyboardHook.h" #include "KeyboardHook.h"
using namespace std; using namespace std;
typedef KBDLLHOOKSTRUCT KeyboardHookStruct; typedef KBDLLHOOKSTRUCT KeyboardHookStruct;
typedef MSLLHOOKSTRUCT MouseHookStruct; typedef MSLLHOOKSTRUCT MouseHookStruct;
std::map<thread::id, KeyboardHook*> hook_handles; thread_local KeyboardHook* thread_hook{nullptr};
struct MouseButtonEventEntry {
MouseButtonEventEntry* next;
KeyboardHook* hook;
enum KeyboardHook::KeyEvent::type type;
std::string key;
};
inline MouseButtonEventEntry* allocate_mb_event() {
return new MouseButtonEventEntry{};
}
inline void delete_mb_event(MouseButtonEventEntry* event) {
delete event;
}
struct MouseButtonEventDispatcher {
bool active{true};
std::thread dispatcher{};
CRITICAL_SECTION mutex;
CONDITION_VARIABLE cv_flushed;
CONDITION_VARIABLE cv_work;
MouseButtonEventEntry* event_head{nullptr};
MouseButtonEventEntry** event_tail{&event_head};
};
MouseButtonEventDispatcher* global_event_dispatcher{};
size_t global_ed_ref_count{0};
void init_global_ed() {
if(global_event_dispatcher) {
global_ed_ref_count++;
return;
}
global_event_dispatcher = new MouseButtonEventDispatcher{};
InitializeCriticalSection(&global_event_dispatcher->mutex);
InitializeConditionVariable(&global_event_dispatcher->cv_flushed);
InitializeConditionVariable(&global_event_dispatcher->cv_work);
global_event_dispatcher->dispatcher = std::thread([]{
auto ed = global_event_dispatcher;
while(ed->active) {
MouseButtonEventEntry* entry{nullptr};
{
EnterCriticalSection(&ed->mutex);
while(!global_event_dispatcher->event_head && ed->active) {
WakeAllConditionVariable(&ed->cv_flushed);
SleepConditionVariableCS(&ed->cv_work, &ed->mutex, INFINITE);
}
entry = global_event_dispatcher->event_head;
global_event_dispatcher->event_head = nullptr;
global_event_dispatcher->event_tail = &global_event_dispatcher->event_head;
LeaveCriticalSection(&ed->mutex);
}
while(entry) {
entry->hook->trigger_key_event(entry->type, std::string{entry->key});
auto next = entry->next;
delete_mb_event(entry);
entry = next;
}
}
});
}
void shutdown_global_ed() {
if(--global_ed_ref_count > 0) return;
auto ed = std::exchange(global_event_dispatcher, nullptr);
ed->active = false;
WakeAllConditionVariable(&ed->cv_work);
if(ed->dispatcher.joinable())
ed->dispatcher.join();
DeleteCriticalSection(&ed->mutex);
delete ed;
}
KeyboardHook::KeyboardHook() = default;
KeyboardHook::KeyboardHook() {}
KeyboardHook::~KeyboardHook() { KeyboardHook::~KeyboardHook() {
if(this->_attached) if(this->_attached)
this->detach(); this->detach();
@ -19,6 +107,7 @@ bool KeyboardHook::attach() {
assert(!this->_attached); assert(!this->_attached);
this->active = true; this->active = true;
init_global_ed();
this->poll_thread = std::thread(std::bind(&KeyboardHook::poll_events, this)); this->poll_thread = std::thread(std::bind(&KeyboardHook::poll_events, this));
this->_attached = true; this->_attached = true;
@ -34,29 +123,45 @@ void KeyboardHook::detach() {
if(this->poll_thread.joinable()) if(this->poll_thread.joinable())
this->poll_thread.join(); this->poll_thread.join();
/* all events flushed */
EnterCriticalSection(&global_event_dispatcher->mutex);
WakeAllConditionVariable(&global_event_dispatcher->cv_work);
SleepConditionVariableCS(&global_event_dispatcher->cv_flushed, &global_event_dispatcher->mutex, INFINITE);
LeaveCriticalSection(&global_event_dispatcher->mutex);
shutdown_global_ed();
this->_attached = false; this->_attached = false;
} }
LRESULT _keyboard_hook_callback(int nCode, WPARAM event, LPARAM ptr_keyboard) { LRESULT _keyboard_hook_callback(int nCode, WPARAM event, LPARAM ptr_keyboard) {
auto handle = hook_handles[this_thread::get_id()]; assert(thread_hook);
assert(handle); auto consume = thread_hook->keyboard_hook_callback(nCode, event, ptr_keyboard);
auto consume = handle->keyboard_hook_callback(nCode, event, ptr_keyboard);
if(consume) if(consume)
return 1; return 1;
return CallNextHookEx(nullptr, nCode, event, ptr_keyboard); return CallNextHookEx(nullptr, nCode, event, ptr_keyboard);
} }
LRESULT _mouse_hook_callback(int nCode, WPARAM event, LPARAM ptr_keyboard) { LRESULT _mouse_hook_callback(int nCode, WPARAM event, LPARAM ptr_keyboard) {
auto handle = hook_handles[this_thread::get_id()]; assert(thread_hook);
assert(handle); auto consume = thread_hook->mouse_hook_callback(nCode, event, ptr_keyboard);
auto consume = handle->mouse_hook_callback(nCode, event, ptr_keyboard); auto end = std::chrono::high_resolution_clock::now();
if(consume) if(consume)
return 1; return 1;
return CallNextHookEx(nullptr, nCode, event, ptr_keyboard); return CallNextHookEx(nullptr, nCode, event, ptr_keyboard);
} }
void KeyboardHook::poll_events() { void KeyboardHook::poll_events() {
hook_handles[this_thread::get_id()] = this; thread_hook = this;
if(!SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS))
std::cerr << "Failed to set priority class to realtime!" << std::endl;
#if 0
int cur_priority = GetThreadPriority(GetCurrentThread());
DWORD cur_priority_class = GetPriorityClass(GetCurrentProcess());
std::cout << "P: " << cur_priority << " C: " << cur_priority_class << std::endl;
#endif
this->keyboad_hook_id = SetWindowsHookEx(WH_KEYBOARD_LL, _keyboard_hook_callback, GetModuleHandle(nullptr), 0); this->keyboad_hook_id = SetWindowsHookEx(WH_KEYBOARD_LL, _keyboard_hook_callback, GetModuleHandle(nullptr), 0);
if(!this->keyboad_hook_id) { if(!this->keyboad_hook_id) {
cerr << "Failed to register keyboard hook" << endl; cerr << "Failed to register keyboard hook" << endl;
@ -75,9 +180,11 @@ void KeyboardHook::poll_events() {
TranslateMessage(&msg); TranslateMessage(&msg);
DispatchMessage(&msg); DispatchMessage(&msg);
} }
std::this_thread::sleep_for(std::chrono::seconds{1});
UnhookWindowsHookEx(this->keyboad_hook_id); UnhookWindowsHookEx(this->mouse_hook_id);
hook_handles[this_thread::get_id()] = nullptr; UnhookWindowsHookEx(this->keyboad_hook_id);
thread_hook = nullptr;
} }
inline std::string key_code(DWORD code, bool extended = false) { inline std::string key_code(DWORD code, bool extended = false) {
@ -155,23 +262,60 @@ bool KeyboardHook::keyboard_hook_callback(int nCode, WPARAM event, LPARAM ptr_ke
return false; return false;
} }
bool KeyboardHook::mouse_hook_callback(int nCode, WPARAM event, LPARAM ptr_mouse) { bool KeyboardHook::mouse_hook_callback(int nCode, WPARAM event, LPARAM ptr_mouse) {
auto mouse = (MouseHookStruct*) ptr_mouse; MouseButtonEventEntry* mb_event;
if(event == WM_RBUTTONDOWN) switch (event) {
this->trigger_key_event(KeyEvent::PRESS, "MOUSE1"); case WM_LBUTTONDOWN:
else if(event == WM_RBUTTONUP) mb_event = allocate_mb_event();
this->trigger_key_event(KeyEvent::RELEASE, "MOUSE1"); mb_event->type = KeyEvent::PRESS;
if(event == WM_LBUTTONDOWN) mb_event->key = "MOUSE1";
this->trigger_key_event(KeyEvent::PRESS, "MOUSE2"); break;
else if(event == WM_LBUTTONUP) case WM_LBUTTONUP:
this->trigger_key_event(KeyEvent::RELEASE, "MOUSE2"); mb_event = allocate_mb_event();
if(event == WM_MBUTTONDOWN) mb_event->type = KeyEvent::RELEASE;
this->trigger_key_event(KeyEvent::PRESS, "MOUSE3"); mb_event->key = "MOUSE1";
else if(event == WM_MBUTTONUP) break;
this->trigger_key_event(KeyEvent::RELEASE, "MOUSE3");
else if(event == WM_XBUTTONDOWN || event == WM_XBUTTONUP) { case WM_RBUTTONDOWN:
auto x_index = GET_XBUTTON_WPARAM(mouse->mouseData); mb_event = allocate_mb_event();
this->trigger_key_event(event == WM_XBUTTONDOWN ? KeyEvent::PRESS : KeyEvent::RELEASE, "MOUSEX" + std::to_string(x_index)); mb_event->type = KeyEvent::PRESS;
mb_event->key = "MOUSE3";
break;
case WM_RBUTTONUP:
mb_event = allocate_mb_event();
mb_event->type = KeyEvent::RELEASE;
mb_event->key = "MOUSE3";
break;
case WM_XBUTTONDOWN: {
auto mouse = (MouseHookStruct*) ptr_mouse;
auto x_index = GET_XBUTTON_WPARAM(mouse->mouseData);
mb_event = allocate_mb_event();
mb_event->type = KeyEvent::PRESS;
mb_event->key = "MOUSEX" + std::to_string(x_index);
break;
}
case WM_XBUTTONUP: {
auto mouse = (MouseHookStruct*) ptr_mouse;
auto x_index = GET_XBUTTON_WPARAM(mouse->mouseData);
mb_event = allocate_mb_event();
mb_event->type = KeyEvent::RELEASE;
mb_event->key = "MOUSEX" + std::to_string(x_index);
break;
}
default:
return false;
} }
mb_event->next = nullptr;
mb_event->hook = thread_hook;
EnterCriticalSection(&global_event_dispatcher->mutex);
*global_event_dispatcher->event_tail = mb_event;
global_event_dispatcher->event_tail = &mb_event->next;
WakeAllConditionVariable(&global_event_dispatcher->cv_work);
LeaveCriticalSection(&global_event_dispatcher->mutex);
return false; return false;
} }

View File

@ -10,190 +10,188 @@
#include "../../audio/AudioOutput.h" #include "../../audio/AudioOutput.h"
#include "../../EventLoop.h" #include "../../EventLoop.h"
namespace tc { namespace tc::connection {
namespace connection { class ServerConnection;
class ServerConnection; class VoiceConnection;
class VoiceConnection; class VoiceClient;
class VoiceClient;
namespace codec { namespace codec {
enum value { enum value {
MIN = 0, MIN = 0,
SPEEX_NARROWBAND = 0, SPEEX_NARROWBAND = 0,
SPEEX_WIDEBAND = 1, SPEEX_WIDEBAND = 1,
SPEEX_ULTRA_WIDEBAND = 2, SPEEX_ULTRA_WIDEBAND = 2,
CELT_MONO = 3, CELT_MONO = 3,
OPUS_VOICE = 4, OPUS_VOICE = 4,
OPUS_MUSIC = 5, OPUS_MUSIC = 5,
MAX = 5, MAX = 5,
}; };
struct condec_info { struct condec_info {
bool supported; bool supported;
std::string name; std::string name;
std::function<std::shared_ptr<audio::codec::Converter>(std::string&)> new_converter; std::function<std::shared_ptr<audio::codec::Converter>(std::string&)> new_converter;
}; };
extern const condec_info info[6]; extern const condec_info info[6];
inline const condec_info* get_info(value codec) { inline const condec_info* get_info(value codec) {
if(codec > value::MAX || codec < value::MIN) if(codec > value::MAX || codec < value::MIN)
return nullptr; return nullptr;
return &info[codec]; return &info[codec];
} }
} }
class VoiceClient : private event::EventEntry { class VoiceClient : private event::EventEntry {
friend class VoiceConnection; friend class VoiceConnection;
#ifdef WIN32 #ifdef WIN32
template<typename _Tp, typename _Up> template<typename _Tp, typename _Up>
friend _NODISCARD std::shared_ptr<_Tp> std::static_pointer_cast(std::shared_ptr<_Up>&& _Other) noexcept; friend _NODISCARD std::shared_ptr<_Tp> std::static_pointer_cast(std::shared_ptr<_Up>&& _Other) noexcept;
#else #else
template<typename _Tp, typename _Up> template<typename _Tp, typename _Up>
friend inline std::shared_ptr<_Tp> std::static_pointer_cast(const std::shared_ptr<_Up>& __r) noexcept; friend inline std::shared_ptr<_Tp> std::static_pointer_cast(const std::shared_ptr<_Up>& __r) noexcept;
#endif #endif
public: public:
struct state { struct state {
enum value { enum value {
buffering, /* this state is never active */ buffering, /* this state is never active */
playing, playing,
stopping, stopping,
stopped stopped
}; };
}; };
VoiceClient(const std::shared_ptr<VoiceConnection>& /* connection */, uint16_t /* client id */); VoiceClient(const std::shared_ptr<VoiceConnection>& /* connection */, uint16_t /* client id */);
virtual ~VoiceClient(); virtual ~VoiceClient();
void initialize(); void initialize();
inline uint16_t client_id() { return this->_client_id; } inline uint16_t client_id() { return this->_client_id; }
void initialize_js_object(); void initialize_js_object();
void finalize_js_object(); void finalize_js_object();
v8::Local<v8::Object> js_handle() { v8::Local<v8::Object> js_handle() {
assert(v8::Isolate::GetCurrent()); assert(v8::Isolate::GetCurrent());
return this->_js_handle.Get(Nan::GetCurrentContext()->GetIsolate()); return this->_js_handle.Get(Nan::GetCurrentContext()->GetIsolate());
} }
inline std::shared_ptr<VoiceClient> ref() { return this->_ref.lock(); } inline std::shared_ptr<VoiceClient> ref() { return this->_ref.lock(); }
void process_packet(uint16_t packet_id, const pipes::buffer_view& /* buffer */, codec::value /* codec */, bool /* head */); void process_packet(uint16_t packet_id, const pipes::buffer_view& /* buffer */, codec::value /* codec */, bool /* head */);
inline float get_volume() { return this->_volume; } inline float get_volume() { return this->_volume; }
inline void set_volume(float value) { this->_volume = value; } inline void set_volume(float value) { this->_volume = value; }
inline state::value state() { return this->_state; } inline state::value state() { return this->_state; }
void cancel_replay(); void cancel_replay();
std::function<void()> on_state_changed; std::function<void()> on_state_changed;
inline std::shared_ptr<audio::AudioOutputSource> output_stream() { return this->output_source; } inline std::shared_ptr<audio::AudioOutputSource> output_stream() { return this->output_source; }
private: private:
struct EncodedBuffer { struct EncodedBuffer {
bool head{false}; bool head{false};
bool reset_decoder{false}; bool reset_decoder{false};
std::chrono::system_clock::time_point receive_timestamp; std::chrono::system_clock::time_point receive_timestamp;
pipes::buffer buffer; pipes::buffer buffer;
codec::value codec{codec::MIN}; codec::value codec{codec::MIN};
uint16_t packet_id{0}; uint16_t packet_id{0};
EncodedBuffer* next{nullptr}; EncodedBuffer* next{nullptr};
}; };
struct AudioCodec { struct AudioCodec {
enum struct State { enum struct State {
UNINITIALIZED, UNINITIALIZED,
INITIALIZED_SUCCESSFULLY, INITIALIZED_SUCCESSFULLY,
INITIALIZED_FAIL, INITIALIZED_FAIL,
UNSUPPORTED, UNSUPPORTED,
}; };
codec::value codec{}; codec::value codec{};
uint16_t last_packet_id{0xFFFF}; /* the first packet id is 0 so one packet before is 0xFFFF */ uint16_t last_packet_id{0xFFFF}; /* the first packet id is 0 so one packet before is 0xFFFF */
std::chrono::system_clock::time_point last_packet_timestamp; std::chrono::system_clock::time_point last_packet_timestamp;
inline std::chrono::system_clock::time_point stream_timeout() { inline std::chrono::system_clock::time_point stream_timeout() {
return this->last_packet_timestamp + std::chrono::milliseconds{1000}; return this->last_packet_timestamp + std::chrono::milliseconds{1000};
} }
State state{State::UNINITIALIZED}; State state{State::UNINITIALIZED};
std::shared_ptr<audio::codec::Converter> converter{nullptr}; std::shared_ptr<audio::codec::Converter> converter{nullptr};
std::shared_ptr<audio::AudioResampler> resampler{nullptr}; std::shared_ptr<audio::AudioResampler> resampler{nullptr};
std::mutex pending_lock{}; std::mutex pending_lock{};
EncodedBuffer* pending_buffers{nullptr}; EncodedBuffer* pending_buffers{nullptr};
/* forces all packets which are within the next chain to replay until (inclusive) force_replay is reached */ /* forces all packets which are within the next chain to replay until (inclusive) force_replay is reached */
EncodedBuffer* force_replay{nullptr}; EncodedBuffer* force_replay{nullptr};
bool process_pending{false}; bool process_pending{false};
}; };
std::array<AudioCodec, codec::MAX + 1> codec{}; std::array<AudioCodec, codec::MAX + 1> codec{};
void initialize_code(const codec::value& /* codec */); void initialize_code(const codec::value& /* codec */);
/* might be null (if audio hasn't been initialized) */ /* might be null (if audio hasn't been initialized) */
std::shared_ptr<audio::AudioOutputSource> output_source; std::shared_ptr<audio::AudioOutputSource> output_source;
std::weak_ptr<VoiceClient> _ref; std::weak_ptr<VoiceClient> _ref;
v8::Persistent<v8::Object> _js_handle; v8::Persistent<v8::Object> _js_handle;
uint16_t _client_id{0}; uint16_t _client_id{0};
float _volume = 1.f; float _volume = 1.f;
std::chrono::system_clock::time_point _last_received_packet; std::chrono::system_clock::time_point _last_received_packet;
state::value _state = state::stopped; state::value _state = state::stopped;
inline void set_state(state::value value) { inline void set_state(state::value value) {
if(value == this->_state) if(value == this->_state)
return; return;
this->_state = value; this->_state = value;
if(this->on_state_changed) if(this->on_state_changed)
this->on_state_changed(); this->on_state_changed();
} }
std::atomic_bool audio_decode_event_dropped{false}; std::atomic_bool audio_decode_event_dropped{false};
void event_execute(const std::chrono::system_clock::time_point &point) override; void event_execute(const std::chrono::system_clock::time_point &point) override;
void event_execute_dropped(const std::chrono::system_clock::time_point &point) override; void event_execute_dropped(const std::chrono::system_clock::time_point &point) override;
/* its recommend to call this in correct packet oder */ /* its recommend to call this in correct packet oder */
std::shared_ptr<audio::SampleBuffer> decode_buffer(const codec::value& /* codec */,const pipes::buffer_view& /* buffer */); std::shared_ptr<audio::SampleBuffer> decode_buffer(const codec::value& /* codec */,const pipes::buffer_view& /* buffer */);
}; };
class VoiceClientWrap : public Nan::ObjectWrap { class VoiceClientWrap : public Nan::ObjectWrap {
public: public:
static NAN_MODULE_INIT(Init); static NAN_MODULE_INIT(Init);
static NAN_METHOD(NewInstance); static NAN_METHOD(NewInstance);
static inline Nan::Persistent<v8::Function> & constructor() { static inline Nan::Persistent<v8::Function> & constructor() {
static Nan::Persistent<v8::Function> my_constructor; static Nan::Persistent<v8::Function> my_constructor;
return my_constructor; return my_constructor;
} }
VoiceClientWrap(const std::shared_ptr<VoiceClient>&); VoiceClientWrap(const std::shared_ptr<VoiceClient>&);
virtual ~VoiceClientWrap(); virtual ~VoiceClientWrap();
void do_wrap(const v8::Local<v8::Object>&); void do_wrap(const v8::Local<v8::Object>&);
private: private:
static NAN_METHOD(_get_state); static NAN_METHOD(_get_state);
static NAN_METHOD(_get_volume); static NAN_METHOD(_get_volume);
static NAN_METHOD(_set_volume); static NAN_METHOD(_set_volume);
static NAN_METHOD(_abort_replay); static NAN_METHOD(_abort_replay);
static NAN_METHOD(_get_stream); static NAN_METHOD(_get_stream);
std::weak_ptr<VoiceClient> _handle; std::weak_ptr<VoiceClient> _handle;
bool _currently_playing = false; bool _currently_playing = false;
Nan::callback_t<> call_state_changed; Nan::callback_t<> call_state_changed;
void _call_state_changed(); void _call_state_changed();
}; };
}
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "TeaClient", "name": "TeaClient",
"version": "1.4.5-2", "version": "1.4.5-3",
"description": "", "description": "",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {