Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ CMakeLists.txt.user
CMakeUserPresets.json
compile_commands.json
install/
.cache
.cache
vcpkg_installed/
5 changes: 3 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 ----

Expand All @@ -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 ----
Expand Down
9 changes: 9 additions & 0 deletions cmake/fetch-content.cmake
Original file line number Diff line number Diff line change
@@ -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)
6 changes: 3 additions & 3 deletions source/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down Expand Up @@ -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<std::string>() == "default";
}

Expand Down
16 changes: 12 additions & 4 deletions source/VolumeController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
#include <cpr/status_codes.h>
#include <fmt/core.h>
#include <nlohmann/json_fwd.hpp>
#include <winuser.h>

#ifdef _WIN32
# include <winuser.h>
#endif
#include "Client.h"
#include "Config.h"
#include "data_types.h"
Expand All @@ -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))
{
Expand Down
12 changes: 7 additions & 5 deletions source/VolumeController.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,20 @@ class VolumeController
const keycode m_volume_down_keycode;
std::optional<std::string> 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<volume_change> m_volume_queue;
std::condition_variable m_update_current_volume_cv;
std::mutex m_update_current_volume_mutex;
std::atomic<bool> 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
114 changes: 103 additions & 11 deletions source/key_hooks.cpp
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
#include <bit>
#include <memory>
#include <utility>

#include "key_hooks.h"

#include <fmt/core.h>
#include <libloaderapi.h>
#include <minwindef.h>
#include <windef.h>
#include <winuser.h>

#ifdef __linux__
# include <X11/Xlib.h>
# include <uiohook.h>
#endif

#ifdef _WIN32
# include <libloaderapi.h>
# include <minwindef.h>
# include <windef.h>
# include <winuser.h>
#endif
#include "VolumeController.h"
namespace spotify_volume_controller::key_hooks
{
namespace
{
HHOOK hook; // NOLINT
#ifdef _WIN32
std::unique_ptr<VolumeController> g_controller {}; // NOLINT
HHOOK hook; // NOLINT

LRESULT CALLBACK volume_callback(int n_code, WPARAM w_param, LPARAM l_param)
{
Expand Down Expand Up @@ -48,26 +57,109 @@ 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<VolumeController*>(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<VolumeController> 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);
}

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<VolumeController> 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
} // namespace spotify_volume_controller::key_hooks
21 changes: 14 additions & 7 deletions source/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@

#include <argparse/argparse.hpp>
#include <fmt/core.h>
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <windows.h>
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
# include <windows.h>
# include <wincon.h>
#endif

#include "Client.h"
#include "Config.h"
Expand Down Expand Up @@ -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<spotify_volume_controller::token_t> token = spotify_volume_controller::oauth::get_token(config);
if (!token.has_value()) {
fmt::println(stderr, "Failed to connect to Spotify, exiting...");
Expand All @@ -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;
}
1 change: 1 addition & 0 deletions vcpkg.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down
Loading