diff --git a/jenkins/create_build.sh b/jenkins/create_build.sh index 08ef2e3..95c148b 100755 --- a/jenkins/create_build.sh +++ b/jenkins/create_build.sh @@ -122,5 +122,5 @@ function deploy_client() { #install_npm #compile_scripts #compile_native -#package_client +package_client deploy_client diff --git a/modules/renderer/connection/VoiceConnection.ts b/modules/renderer/connection/VoiceConnection.ts index 45f181f..829d197 100644 --- a/modules/renderer/connection/VoiceConnection.ts +++ b/modules/renderer/connection/VoiceConnection.ts @@ -85,7 +85,7 @@ export class VoiceConnection extends AbstractVoiceConnection { return false; console.log(tr("Local voice ended")); - //TODO + //TODO Send end? (Or is this already an automated thing?) } private on_voice_started() { diff --git a/native/ppt/binding.cc b/native/ppt/binding.cc index fe9124c..1e2ac37 100644 --- a/native/ppt/binding.cc +++ b/native/ppt/binding.cc @@ -1,6 +1,5 @@ #include #include -#include #include #include #include diff --git a/native/ppt/src/KeyboardHook.h b/native/ppt/src/KeyboardHook.h index 1e7255e..e07dd95 100644 --- a/native/ppt/src/KeyboardHook.h +++ b/native/ppt/src/KeyboardHook.h @@ -52,6 +52,7 @@ class KeyboardHook { inline bool attached() { return this->_attached; } void detach(); + void trigger_key_event(const enum KeyEvent::type&, const std::string& /* key */); callback_event_t callback_event; private: #ifdef HAVE_X11 @@ -70,8 +71,6 @@ class KeyboardHook { bool keyboard_hook_callback(int, WPARAM, LPARAM); bool mouse_hook_callback(int, WPARAM, LPARAM); #endif - - void trigger_key_event(const enum KeyEvent::type&, const std::string& /* key */); std::map map_key; std::map map_special; diff --git a/native/ppt/src/Win32KeyboardHook.cpp b/native/ppt/src/Win32KeyboardHook.cpp index a71dd0b..3837b4b 100644 --- a/native/ppt/src/Win32KeyboardHook.cpp +++ b/native/ppt/src/Win32KeyboardHook.cpp @@ -1,15 +1,103 @@ #include #include #include +#include #include "KeyboardHook.h" using namespace std; typedef KBDLLHOOKSTRUCT KeyboardHookStruct; typedef MSLLHOOKSTRUCT MouseHookStruct; -std::map 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() { if(this->_attached) this->detach(); @@ -19,6 +107,7 @@ bool KeyboardHook::attach() { assert(!this->_attached); this->active = true; + init_global_ed(); this->poll_thread = std::thread(std::bind(&KeyboardHook::poll_events, this)); this->_attached = true; @@ -34,29 +123,45 @@ void KeyboardHook::detach() { if(this->poll_thread.joinable()) 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; } LRESULT _keyboard_hook_callback(int nCode, WPARAM event, LPARAM ptr_keyboard) { - auto handle = hook_handles[this_thread::get_id()]; - assert(handle); - auto consume = handle->keyboard_hook_callback(nCode, event, ptr_keyboard); + assert(thread_hook); + auto consume = thread_hook->keyboard_hook_callback(nCode, event, ptr_keyboard); if(consume) return 1; return CallNextHookEx(nullptr, nCode, event, ptr_keyboard); } LRESULT _mouse_hook_callback(int nCode, WPARAM event, LPARAM ptr_keyboard) { - auto handle = hook_handles[this_thread::get_id()]; - assert(handle); - auto consume = handle->mouse_hook_callback(nCode, event, ptr_keyboard); + assert(thread_hook); + auto consume = thread_hook->mouse_hook_callback(nCode, event, ptr_keyboard); + auto end = std::chrono::high_resolution_clock::now(); if(consume) return 1; return CallNextHookEx(nullptr, nCode, event, ptr_keyboard); } 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); if(!this->keyboad_hook_id) { cerr << "Failed to register keyboard hook" << endl; @@ -75,9 +180,11 @@ void KeyboardHook::poll_events() { TranslateMessage(&msg); DispatchMessage(&msg); } + std::this_thread::sleep_for(std::chrono::seconds{1}); - UnhookWindowsHookEx(this->keyboad_hook_id); - hook_handles[this_thread::get_id()] = nullptr; + UnhookWindowsHookEx(this->mouse_hook_id); + UnhookWindowsHookEx(this->keyboad_hook_id); + thread_hook = nullptr; } 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; } -bool KeyboardHook::mouse_hook_callback(int nCode, WPARAM event, LPARAM ptr_mouse) { - auto mouse = (MouseHookStruct*) ptr_mouse; - if(event == WM_RBUTTONDOWN) - this->trigger_key_event(KeyEvent::PRESS, "MOUSE1"); - else if(event == WM_RBUTTONUP) - this->trigger_key_event(KeyEvent::RELEASE, "MOUSE1"); - if(event == WM_LBUTTONDOWN) - this->trigger_key_event(KeyEvent::PRESS, "MOUSE2"); - else if(event == WM_LBUTTONUP) - this->trigger_key_event(KeyEvent::RELEASE, "MOUSE2"); - if(event == WM_MBUTTONDOWN) - this->trigger_key_event(KeyEvent::PRESS, "MOUSE3"); - else if(event == WM_MBUTTONUP) - this->trigger_key_event(KeyEvent::RELEASE, "MOUSE3"); - else if(event == WM_XBUTTONDOWN || event == WM_XBUTTONUP) { - auto x_index = GET_XBUTTON_WPARAM(mouse->mouseData); - this->trigger_key_event(event == WM_XBUTTONDOWN ? KeyEvent::PRESS : KeyEvent::RELEASE, "MOUSEX" + std::to_string(x_index)); +bool KeyboardHook::mouse_hook_callback(int nCode, WPARAM event, LPARAM ptr_mouse) { + MouseButtonEventEntry* mb_event; + switch (event) { + case WM_LBUTTONDOWN: + mb_event = allocate_mb_event(); + mb_event->type = KeyEvent::PRESS; + mb_event->key = "MOUSE1"; + break; + case WM_LBUTTONUP: + mb_event = allocate_mb_event(); + mb_event->type = KeyEvent::RELEASE; + mb_event->key = "MOUSE1"; + break; + + case WM_RBUTTONDOWN: + mb_event = allocate_mb_event(); + 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; } \ No newline at end of file diff --git a/native/serverconnection/src/connection/audio/VoiceClient.h b/native/serverconnection/src/connection/audio/VoiceClient.h index 49193d5..d0eec40 100644 --- a/native/serverconnection/src/connection/audio/VoiceClient.h +++ b/native/serverconnection/src/connection/audio/VoiceClient.h @@ -10,190 +10,188 @@ #include "../../audio/AudioOutput.h" #include "../../EventLoop.h" -namespace tc { - namespace connection { - class ServerConnection; - class VoiceConnection; - class VoiceClient; +namespace tc::connection { + class ServerConnection; + class VoiceConnection; + class VoiceClient; - namespace codec { - enum value { - MIN = 0, + namespace codec { + enum value { + MIN = 0, - SPEEX_NARROWBAND = 0, - SPEEX_WIDEBAND = 1, - SPEEX_ULTRA_WIDEBAND = 2, + SPEEX_NARROWBAND = 0, + SPEEX_WIDEBAND = 1, + SPEEX_ULTRA_WIDEBAND = 2, - CELT_MONO = 3, + CELT_MONO = 3, - OPUS_VOICE = 4, - OPUS_MUSIC = 5, + OPUS_VOICE = 4, + OPUS_MUSIC = 5, - MAX = 5, - }; + MAX = 5, + }; - struct condec_info { - bool supported; - std::string name; - std::function(std::string&)> new_converter; - }; + struct condec_info { + bool supported; + std::string name; + std::function(std::string&)> new_converter; + }; - extern const condec_info info[6]; - inline const condec_info* get_info(value codec) { - if(codec > value::MAX || codec < value::MIN) - return nullptr; - return &info[codec]; - } - } + extern const condec_info info[6]; + inline const condec_info* get_info(value codec) { + if(codec > value::MAX || codec < value::MIN) + return nullptr; + return &info[codec]; + } + } - class VoiceClient : private event::EventEntry { - friend class VoiceConnection; + class VoiceClient : private event::EventEntry { + friend class VoiceConnection; #ifdef WIN32 - template - friend _NODISCARD std::shared_ptr<_Tp> std::static_pointer_cast(std::shared_ptr<_Up>&& _Other) noexcept; + template + friend _NODISCARD std::shared_ptr<_Tp> std::static_pointer_cast(std::shared_ptr<_Up>&& _Other) noexcept; #else - template - friend inline std::shared_ptr<_Tp> std::static_pointer_cast(const std::shared_ptr<_Up>& __r) noexcept; + template + friend inline std::shared_ptr<_Tp> std::static_pointer_cast(const std::shared_ptr<_Up>& __r) noexcept; #endif - public: - struct state { - enum value { - buffering, /* this state is never active */ - playing, - stopping, - stopped - }; - }; - VoiceClient(const std::shared_ptr& /* connection */, uint16_t /* client id */); - virtual ~VoiceClient(); + public: + struct state { + enum value { + buffering, /* this state is never active */ + playing, + stopping, + stopped + }; + }; + VoiceClient(const std::shared_ptr& /* connection */, uint16_t /* client id */); + 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 finalize_js_object(); + void initialize_js_object(); + void finalize_js_object(); - v8::Local js_handle() { - assert(v8::Isolate::GetCurrent()); - return this->_js_handle.Get(Nan::GetCurrentContext()->GetIsolate()); - } + v8::Local js_handle() { + assert(v8::Isolate::GetCurrent()); + return this->_js_handle.Get(Nan::GetCurrentContext()->GetIsolate()); + } - inline std::shared_ptr ref() { return this->_ref.lock(); } + inline std::shared_ptr 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 void set_volume(float value) { this->_volume = value; } + inline float get_volume() { return this->_volume; } + 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 on_state_changed; + std::function on_state_changed; - inline std::shared_ptr output_stream() { return this->output_source; } - private: - struct EncodedBuffer { - bool head{false}; - bool reset_decoder{false}; + inline std::shared_ptr output_stream() { return this->output_source; } + private: + struct EncodedBuffer { + bool head{false}; + bool reset_decoder{false}; - std::chrono::system_clock::time_point receive_timestamp; + std::chrono::system_clock::time_point receive_timestamp; - pipes::buffer buffer; - codec::value codec{codec::MIN}; + pipes::buffer buffer; + codec::value codec{codec::MIN}; - uint16_t packet_id{0}; - EncodedBuffer* next{nullptr}; - }; + uint16_t packet_id{0}; + EncodedBuffer* next{nullptr}; + }; - struct AudioCodec { - enum struct State { - UNINITIALIZED, - INITIALIZED_SUCCESSFULLY, - INITIALIZED_FAIL, - UNSUPPORTED, - }; + struct AudioCodec { + enum struct State { + UNINITIALIZED, + INITIALIZED_SUCCESSFULLY, + INITIALIZED_FAIL, + 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 */ - std::chrono::system_clock::time_point last_packet_timestamp; + 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; - inline std::chrono::system_clock::time_point stream_timeout() { - return this->last_packet_timestamp + std::chrono::milliseconds{1000}; - } + inline std::chrono::system_clock::time_point stream_timeout() { + return this->last_packet_timestamp + std::chrono::milliseconds{1000}; + } - State state{State::UNINITIALIZED}; - std::shared_ptr converter{nullptr}; - std::shared_ptr resampler{nullptr}; + State state{State::UNINITIALIZED}; + std::shared_ptr converter{nullptr}; + std::shared_ptr resampler{nullptr}; - std::mutex pending_lock{}; - EncodedBuffer* pending_buffers{nullptr}; + std::mutex pending_lock{}; + EncodedBuffer* pending_buffers{nullptr}; - /* forces all packets which are within the next chain to replay until (inclusive) force_replay is reached */ - EncodedBuffer* force_replay{nullptr}; + /* forces all packets which are within the next chain to replay until (inclusive) force_replay is reached */ + EncodedBuffer* force_replay{nullptr}; - bool process_pending{false}; - }; + bool process_pending{false}; + }; - std::array codec{}; - void initialize_code(const codec::value& /* codec */); + std::array codec{}; + void initialize_code(const codec::value& /* codec */); - /* might be null (if audio hasn't been initialized) */ - std::shared_ptr output_source; + /* might be null (if audio hasn't been initialized) */ + std::shared_ptr output_source; - std::weak_ptr _ref; - v8::Persistent _js_handle; + std::weak_ptr _ref; + v8::Persistent _js_handle; - uint16_t _client_id{0}; - float _volume = 1.f; + uint16_t _client_id{0}; + float _volume = 1.f; - std::chrono::system_clock::time_point _last_received_packet; - state::value _state = state::stopped; - inline void set_state(state::value value) { - if(value == this->_state) - return; - this->_state = value; - if(this->on_state_changed) - this->on_state_changed(); - } + std::chrono::system_clock::time_point _last_received_packet; + state::value _state = state::stopped; + inline void set_state(state::value value) { + if(value == this->_state) + return; + this->_state = value; + if(this->on_state_changed) + this->on_state_changed(); + } - std::atomic_bool audio_decode_event_dropped{false}; - 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; + std::atomic_bool audio_decode_event_dropped{false}; + 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; - /* its recommend to call this in correct packet oder */ - std::shared_ptr decode_buffer(const codec::value& /* codec */,const pipes::buffer_view& /* buffer */); - }; + /* its recommend to call this in correct packet oder */ + std::shared_ptr decode_buffer(const codec::value& /* codec */,const pipes::buffer_view& /* buffer */); + }; - class VoiceClientWrap : public Nan::ObjectWrap { - public: - static NAN_MODULE_INIT(Init); - static NAN_METHOD(NewInstance); - static inline Nan::Persistent & constructor() { - static Nan::Persistent my_constructor; - return my_constructor; - } + class VoiceClientWrap : public Nan::ObjectWrap { + public: + static NAN_MODULE_INIT(Init); + static NAN_METHOD(NewInstance); + static inline Nan::Persistent & constructor() { + static Nan::Persistent my_constructor; + return my_constructor; + } - VoiceClientWrap(const std::shared_ptr&); - virtual ~VoiceClientWrap(); + VoiceClientWrap(const std::shared_ptr&); + virtual ~VoiceClientWrap(); - void do_wrap(const v8::Local&); - private: - static NAN_METHOD(_get_state); - static NAN_METHOD(_get_volume); - static NAN_METHOD(_set_volume); - static NAN_METHOD(_abort_replay); - static NAN_METHOD(_get_stream); + void do_wrap(const v8::Local&); + private: + static NAN_METHOD(_get_state); + static NAN_METHOD(_get_volume); + static NAN_METHOD(_set_volume); + static NAN_METHOD(_abort_replay); + static NAN_METHOD(_get_stream); - std::weak_ptr _handle; + std::weak_ptr _handle; - bool _currently_playing = false; - Nan::callback_t<> call_state_changed; - void _call_state_changed(); - }; - } + bool _currently_playing = false; + Nan::callback_t<> call_state_changed; + void _call_state_changed(); + }; } \ No newline at end of file diff --git a/package.json b/package.json index d666e34..9f589de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "TeaClient", - "version": "1.4.5-2", + "version": "1.4.5-3", "description": "", "main": "main.js", "scripts": {