diff --git a/.gitignore b/.gitignore index a4b3dbb..b923cdf 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ CMakeLists.txt.user CMakeUserPresets.json compile_commands.json install/ -.cache \ No newline at end of file +.cache +vcpkg_installed/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 17fb1fe..6fcd0f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,12 +12,14 @@ project( include(cmake/project-is-top-level.cmake) include(cmake/variables.cmake) +include(cmake/fetch-content.cmake) find_package(argparse CONFIG REQUIRED) find_package(nlohmann_json REQUIRED) find_package(fmt REQUIRED) find_package(cpr REQUIRED) find_package(httplib REQUIRED) +find_package(uiohook REQUIRED) # ---- Declare library ---- @@ -38,8 +40,7 @@ target_include_directories( target_compile_features(spotify_volume_controller_lib PUBLIC cxx_std_23) target_compile_definitions(spotify_volume_controller_lib PRIVATE _SILENCE_ALL_MS_EXT_DEPRECATION_WARNINGS=1) -target_link_libraries(spotify_volume_controller_lib PRIVATE nlohmann_json::nlohmann_json fmt::fmt cpr::cpr httplib::httplib) - +target_link_libraries(spotify_volume_controller_lib PRIVATE nlohmann_json::nlohmann_json fmt::fmt cpr::cpr httplib::httplib uiohook) set(nlohmann-json_IMPLICIT_CONVERSIONS OFF) # ---- Declare executable ---- diff --git a/cmake/fetch-content.cmake b/cmake/fetch-content.cmake new file mode 100644 index 0000000..6e5cf59 --- /dev/null +++ b/cmake/fetch-content.cmake @@ -0,0 +1,9 @@ +include(FetchContent) +FetchContent_Declare( + uiohook + GIT_REPOSITORY https://github.com/kwhat/libuiohook + GIT_TAG 90c2248503bb62f57e7fc1c0ec339053010e209d # 1.3 + OVERRIDE_FIND_PACKAGE +) + +FetchContent_MakeAvailable(uiohook) \ No newline at end of file diff --git a/source/Config.cpp b/source/Config.cpp index 80d0709..80f9304 100644 --- a/source/Config.cpp +++ b/source/Config.cpp @@ -121,7 +121,7 @@ keycode Config::get_volume_up() const if (!m_config.contains(volume_up_key)) { throw std::runtime_error(fmt::format("Missing {} config", volume_up_key)); } - json const v_up = m_config.at(volume_down_key); + json const v_up = m_config.at(volume_up_key); if (!v_up.is_number_integer()) { throw std::runtime_error(fmt::format("{} config is not a valid keycode", volume_up_key)); } @@ -152,10 +152,10 @@ bool Config::is_default_down() const bool Config::is_default_up() const { - if (!m_config.contains(volume_down_key)) { + if (!m_config.contains(volume_up_key)) { return false; } - json const v_up = m_config.at(volume_down_key); + json const v_up = m_config.at(volume_up_key); return v_up.is_string() && v_up.template get() == "default"; } diff --git a/source/VolumeController.cpp b/source/VolumeController.cpp index 3a85b79..f890eed 100644 --- a/source/VolumeController.cpp +++ b/source/VolumeController.cpp @@ -11,8 +11,9 @@ #include #include #include -#include - +#ifdef _WIN32 +# include +#endif #include "Client.h" #include "Config.h" #include "data_types.h" @@ -22,13 +23,20 @@ namespace spotify_volume_controller { // for convenience using json = nlohmann::json; +#ifdef _WIN32 +constexpr keycode default_up_keycode = VK_VOLUME_UP; +constexpr keycode default_down_keycode = VK_VOLUME_DOWN; +#else +constexpr keycode default_up_keycode = 100; +constexpr keycode default_down_keycode = 101; +#endif VolumeController::VolumeController(const Config& config, Client& client) : m_volume() , m_config(config) , m_client(client) - , m_volume_up_keycode(config.is_default_up() ? VK_VOLUME_UP : config.get_volume_up()) - , m_volume_down_keycode(config.is_default_down() ? VK_VOLUME_DOWN : config.get_volume_down()) + , m_volume_up_keycode(config.is_default_up() ? default_up_keycode : config.get_volume_up()) + , m_volume_down_keycode(config.is_default_down() ? default_down_keycode : config.get_volume_down()) , m_client_thread(std::thread(&VolumeController::set_volume_loop, this)) , m_update_current_volume_thread(std::jthread(&VolumeController::update_current_volume_loop, this)) { diff --git a/source/VolumeController.h b/source/VolumeController.h index f8872ff..4b2569b 100644 --- a/source/VolumeController.h +++ b/source/VolumeController.h @@ -71,18 +71,20 @@ class VolumeController const keycode m_volume_down_keycode; std::optional m_desktop_device_id; - std::thread m_client_thread; - std::jthread m_update_current_volume_thread; - std::jthread m_notify_timer_thread; - std::mutex m_volume_mutex; + std::mutex m_update_current_volume_mutex; + std::condition_variable m_volume_cv; std::queue m_volume_queue; std::condition_variable m_update_current_volume_cv; - std::mutex m_update_current_volume_mutex; std::atomic m_updating_current_volume; std::condition_variable m_updating_current_volume_cv; Timer m_notify_timer {}; + + // Initialize last so that we can guarantee that all member variables are initalized before the threads start + std::thread m_client_thread; + std::jthread m_update_current_volume_thread; + std::jthread m_notify_timer_thread; }; } // namespace spotify_volume_controller diff --git a/source/key_hooks.cpp b/source/key_hooks.cpp index 224ee89..17c3f6a 100644 --- a/source/key_hooks.cpp +++ b/source/key_hooks.cpp @@ -1,21 +1,30 @@ #include +#include #include #include "key_hooks.h" #include -#include -#include -#include -#include +#ifdef __linux__ +# include +# include +#endif + +#ifdef _WIN32 +# include +# include +# include +# include +#endif #include "VolumeController.h" namespace spotify_volume_controller::key_hooks { namespace { -HHOOK hook; // NOLINT +#ifdef _WIN32 std::unique_ptr g_controller {}; // NOLINT +HHOOK hook; // NOLINT LRESULT CALLBACK volume_callback(int n_code, WPARAM w_param, LPARAM l_param) { @@ -48,15 +57,48 @@ LRESULT CALLBACK print_v_key(int n_code, WPARAM w_param, LPARAM l_param) } return CallNextHookEx(nullptr, n_code, w_param, l_param); } -} // namespace +#else +void volume_callback(uiohook_event* const event, void* user_data) +{ + auto* controller = static_cast(user_data); + switch (event->type) { + case EVENT_KEY_PRESSED: { + if (event->data.keyboard.keycode == controller->volume_up_keycode()) { + controller->increase_volume(); + } else if (event->data.keyboard.keycode == controller->volume_down_keycode()) { + controller->decrease_volume(); + } + break; + } + default: { + break; + } + } +} +void print_callback(uiohook_event* const event, void* user_data) +{ + (void)user_data; + switch (event->type) { + case EVENT_KEY_PRESSED: { + fmt::println("Key pressed {}, rawcode={}", event->data.keyboard.keycode, event->data.keyboard.rawcode); + break; + } + default: { + break; + } + } +} + +#endif +} // namespace +#ifdef _WIN32 void start_volume_hook(std::unique_ptr controller) { g_controller = std::move(controller); hook = SetWindowsHookExA(WH_KEYBOARD_LL, volume_callback, GetModuleHandle(nullptr), 0); MSG msg; - while (GetMessage(&msg, nullptr, 0, 0)) { - }; + while (GetMessage(&msg, nullptr, 0, 0)) {}; UnhookWindowsHookEx(hook); } @@ -64,10 +106,60 @@ void start_print_vkey() { hook = SetWindowsHookExA(WH_KEYBOARD_LL, print_v_key, GetModuleHandle(nullptr), 0); MSG msg; - while (GetMessage(&msg, nullptr, 0, 0)) { - }; + while (GetMessage(&msg, nullptr, 0, 0)) {}; UnhookWindowsHookEx(hook); } +#else + +void print_hook_run_status(int status) +{ + switch (status) { + case UIOHOOK_SUCCESS: + // Everything is ok. + break; + + // System level errors. + case UIOHOOK_ERROR_OUT_OF_MEMORY: + fmt::println("Failed to allocate memory. ({})", status); + break; + + // X11 specific errors. + case UIOHOOK_ERROR_X_OPEN_DISPLAY: + fmt::println("Failed to open X11 display. ({})", status); + break; + + case UIOHOOK_ERROR_X_RECORD_NOT_FOUND: + fmt::println("Unable to locate XRecord extension. ({})", status); + break; + + case UIOHOOK_ERROR_X_RECORD_ALLOC_RANGE: + fmt::println("Unable to allocate XRecord range. ({})", status); + break; + + case UIOHOOK_ERROR_X_RECORD_CREATE_CONTEXT: + fmt::println("Unable to allocate XRecord context. ({})", status); + break; + + case UIOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT: + fmt::println("Failed to enable XRecord context. ({})", status); + break; + default: + fmt::println("Unhandled uiohook error. Are you on an unsupported platform? ({})", status); + break; + } +} + +void start_volume_hook(std::unique_ptr controller) +{ + hook_set_dispatch_proc(&volume_callback, controller.get()); + print_hook_run_status(hook_run()); +} +void start_print_vkey() +{ + hook_set_dispatch_proc(&print_callback, nullptr); + print_hook_run_status(hook_run()); +} +#endif -} // namespace spotify_volume_controller::key_hooks \ No newline at end of file +} // namespace spotify_volume_controller::key_hooks diff --git a/source/main.cpp b/source/main.cpp index 5d85669..09ec5d0 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -5,8 +5,11 @@ #include #include -#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers -#include +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +# include +# include +#endif #include "Client.h" #include "Config.h" @@ -40,6 +43,12 @@ int main(int argc, char* argv[]) std::cin.get(); return 1; } + + if (config.should_print_keys()) { + spotify_volume_controller::VolumeController::print_keys(); + return 0; + } + std::optional token = spotify_volume_controller::oauth::get_token(config); if (!token.has_value()) { fmt::println(stderr, "Failed to connect to Spotify, exiting..."); @@ -50,13 +59,11 @@ int main(int argc, char* argv[]) fmt::println("Connected to spotify successfully!"); if (config.hide_window()) { +#ifdef _WIN32 FreeConsole(); +#endif } spotify_volume_controller::VolumeController controller(config, client); - if (config.should_print_keys()) { - spotify_volume_controller::VolumeController::print_keys(); - } else { - controller.start(); - } + controller.start(); return 0; } \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json index c3d1a1c..3de09bf 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,4 +1,5 @@ { + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", "name": "spotify-volume-controller", "version-semver": "0.1.8", "dependencies": [