From 595207af91fea446bda0da517890518e119ba9ff Mon Sep 17 00:00:00 2001 From: Goldenkrew3000 Date: Mon, 6 Oct 2025 18:32:00 +1000 Subject: [PATCH] Adding dotfile support, a VERY BASIC GUI, and basic discord rpc --- src/CMakeLists.txt | 23 ++++-- src/configHandler.c | 3 +- src/gui/gui_entry.cpp | 173 ++++++++++++++++++++++++++++++++++++++++++ src/gui/gui_entry.hpp | 15 ++++ src/main.c | 53 +++++++++++++ 5 files changed, 260 insertions(+), 7 deletions(-) create mode 100644 src/gui/gui_entry.cpp create mode 100644 src/gui/gui_entry.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fbc626b..2de8561 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,15 +8,14 @@ project(ossp) find_package(CURL REQUIRED) find_package(OpenSSL REQUIRED) -find_package(Qt6 REQUIRED COMPONENTS Core Widgets) +find_package(OpenGL REQUIRED) +find_package(SDL2 REQUIRED) -qt_standard_project_setup() +add_subdirectory(external/discord-rpc) -qt_add_executable(ossp +add_executable(ossp MACOSX_BUNDLE main.c gui/gui_entry.cpp - gui/window.cpp - gui/songWidget.cpp configHandler.c libopensubsonic/crypto.c libopensubsonic/httpclient.c @@ -39,6 +38,18 @@ qt_add_executable(ossp external/cJSON_Utils.c external/libcurl_uriescape.c external/md5.c + external/imgui/imgui.cpp + external/imgui/imgui_tables.cpp + external/imgui/imgui_draw.cpp + external/imgui/imgui_widgets.cpp + external/imgui/imgui_demo.cpp + external/imgui/backends/imgui_impl_sdl2.cpp + external/imgui/backends/imgui_impl_opengl2.cpp ) -target_link_libraries(ossp PRIVATE OpenSSL::SSL OpenSSL::Crypto CURL::libcurl Qt6::Core Qt6::Widgets) +set_target_properties(ossp PROPERTIES + MACOSX_BUNDLE_BUNDLE_NAME "OSSP" + MACOSX_BUNDLE_GUI_IDENTIFIER "org.hojuix.ossp" +) + +target_link_libraries(ossp PRIVATE OpenSSL::SSL OpenSSL::Crypto CURL::libcurl SDL2::SDL2 ${OPENGL_LIBRARIES} discord-rpc) diff --git a/src/configHandler.c b/src/configHandler.c index e4664ee..784c47f 100644 --- a/src/configHandler.c +++ b/src/configHandler.c @@ -63,7 +63,8 @@ int configHandler_Read(configHandler_config_t** configObj) { printf("iOS Container Path: %s\n", config_path); #endif // DEBUG #else - rc = asprintf(&config_path, "config.json"); + char* root_path = getenv("HOME"); + rc = asprintf(&config_path, "%s/.config/ossp/config.json", root_path); #endif // defined(__APPLE__) && defined(__MACH__) && defined(XCODE) if (rc == -1) { logger_log_error(__func__, "asprintf() failed (Could not generate config path)."); diff --git a/src/gui/gui_entry.cpp b/src/gui/gui_entry.cpp new file mode 100644 index 0000000..fb06274 --- /dev/null +++ b/src/gui/gui_entry.cpp @@ -0,0 +1,173 @@ +#include "../external/imgui/imgui.h" +#include "../external/imgui/backends/imgui_impl_sdl2.h" +#include "../external/imgui/backends/imgui_impl_opengl2.h" +#include +#include +#include +#include "gui_entry.hpp" +#include "../configHandler.h" +#include "../libopensubsonic/httpclient.h" +#include "../libopensubsonic/endpoint_getStarred.h" + +extern configHandler_config_t* configObj; +bool bLikedSongsShow = false; +void showLikedSongs(); + +int qt_gui_entry(int argc, char** argv) { + // Initialize SDL + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) { + printf("SDL could not be initialized: %s\n", SDL_GetError()); + return 1; + } + + // Setup window + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); + float main_scale = ImGui_ImplSDL2_GetContentScaleForDisplay(0); + SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + SDL_Window* window = SDL_CreateWindow("OSSP v0.3a", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, (int)(1280 * main_scale), (int)(800 * main_scale), window_flags); + if (window == nullptr) { + printf("SDL could not create window: %s\n", SDL_GetError()); + return 1; + } + + // Create GL context + SDL_GLContext gl_context = SDL_GL_CreateContext(window); + SDL_GL_MakeCurrent(window, gl_context); + SDL_GL_SetSwapInterval(1); // Vsync + + // Create ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; + ImGui::StyleColorsDark(); + + // Scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); + style.FontScaleDpi = main_scale; + + // Setup platform/renderer backends + ImGui_ImplSDL2_InitForOpenGL(window, gl_context); + ImGui_ImplOpenGL2_Init(); + + // START START START + ImVec4 background_color = ImVec4(0.00f, 0.00f, 0.00f, 1.00f); + bool done = false; + + while (!done) { + // Poll events + SDL_Event event; + while (SDL_PollEvent(&event)) { + ImGui_ImplSDL2_ProcessEvent(&event); + if (event.type == SDL_QUIT) { + done = true; + } + } + if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) { + SDL_Delay(10); + continue; + } + + // Start new frame + ImGui_ImplOpenGL2_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + + { + ImGui::Begin("OSSP v0.3a"); + + ImGui::Text("Connected to server at %s://%s", configObj->opensubsonic_protocol, configObj->opensubsonic_server); + if (ImGui::Button("Liked Songs")) { + printf("Liked songs button pressed\n"); + bLikedSongsShow = true; + } + + ImGui::End(); + } + + if (bLikedSongsShow) { + showLikedSongs(); + } + + // Render + ImGui::Render(); + glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); + glClearColor(background_color.x * background_color.w, background_color.y * background_color.w, background_color.z * background_color.w, background_color.w); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); +SDL_GL_SwapWindow(window); + } + + // Cleanup + ImGui_ImplOpenGL2_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + SDL_GL_DeleteContext(gl_context); + SDL_DestroyWindow(window); + SDL_Quit(); + + return 0; +} + + + +bool haveLikedSongsInfo = false; +opensubsonic_getStarred_struct* starredStruct; +opensubsonic_httpClient_URL_t* starredUrl; + +void getLikedSongsInfo() { + // Pull liked songs + starredUrl = (opensubsonic_httpClient_URL_t*)malloc(sizeof(opensubsonic_httpClient_URL_t)); + opensubsonic_httpClient_URL_prepare(&starredUrl); + starredUrl->endpoint = OPENSUBSONIC_ENDPOINT_GETSTARRED; + opensubsonic_httpClient_formUrl(&starredUrl); + opensubsonic_httpClient_fetchResponse(&starredUrl, (void**)&starredStruct); + + if (starredStruct->errorCode != 0) { + // Error occured + } + + haveLikedSongsInfo = true; +} + +void showLikedSongs() { + if (!haveLikedSongsInfo) { getLikedSongsInfo(); } + + ImGui::Begin("Liked Songs"); + + ImGui::Text("Liked Songs"); + + if (ImGui::Button("Close")) { + bLikedSongsShow = false; + } + + if (ImGui::Button("Refresh")) { + opensubsonic_getStarred_struct_free(&starredStruct); + opensubsonic_httpClient_URL_cleanup(&starredUrl); + haveLikedSongsInfo = false; + } + + static int selectedSong = -1; + if (ImGui::BeginChild("Liked Songs", ImVec2(0, 200), ImGuiChildFlags_Border)) { + for (int i = 0; i < starredStruct->songCount; i++) { + if (ImGui::Selectable(starredStruct->songs[i].title, selectedSong == i)) { + selectedSong = i; + } + } + ImGui::EndChild(); + } + + if (selectedSong != -1) { + printf("Song: %s (%s)\n", starredStruct->songs[selectedSong].title, + starredStruct->songs[selectedSong].id); + selectedSong = -1; + } + + ImGui::End(); +} diff --git a/src/gui/gui_entry.hpp b/src/gui/gui_entry.hpp new file mode 100644 index 0000000..0341b92 --- /dev/null +++ b/src/gui/gui_entry.hpp @@ -0,0 +1,15 @@ + +#ifndef _GUI_ENTRY_HPP +#define _GUI_ENTRY_HPP + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +int qt_gui_entry(int argc, char** argv); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif diff --git a/src/main.c b/src/main.c index f059a81..f8583a2 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,7 @@ #include #include #include +#include "external/discord-rpc/include/discord_rpc.h" #include "gui/gui_entry.hpp" #include "libopensubsonic/logger.h" #include "libopensubsonic/crypto.h" @@ -8,6 +9,53 @@ #include "libopensubsonic/endpoint_ping.h" #include "configHandler.h" +const char* APPID = "1407025303779278980"; + + + +static void handleDiscordReady(const DiscordUser* connectedUser) +{ + printf("\nDiscord: connected to user %s#%s - %s\n", + connectedUser->username, + connectedUser->discriminator, + connectedUser->userId); +} + +static void handleDiscordDisconnected(int errcode, const char* message) +{ + printf("\nDiscord: disconnected (%d: %s)\n", errcode, message); +} + +static void handleDiscordError(int errcode, const char* message) +{ + printf("\nDiscord: error (%d: %s)\n", errcode, message); +} + + + +static void discordInit() +{ + DiscordEventHandlers handlers; + memset(&handlers, 0, sizeof(handlers)); + handlers.ready = handleDiscordReady; + handlers.disconnected = handleDiscordDisconnected; + handlers.errored = handleDiscordError; + Discord_Initialize(APPID, &handlers, 1, NULL); +} + +static void updatePresence() { + char buffer[256]; + DiscordRichPresence presence; + memset(&presence, 0, sizeof(presence)); + sprintf(buffer, "Idle"); // Change to snprintf + presence.details = buffer; + presence.activity_type = DISCORD_ACTIVITY_TYPE_LISTENING; + Discord_UpdatePresence(&presence); +} + + + + configHandler_config_t* configObj = NULL; int checkConfigFile(); int validateConnection(); @@ -44,6 +92,10 @@ int main(int argc, char** argv) { return 1; } + // Launch Discord RPC + discordInit(); + updatePresence(); + // Launch QT frontend qt_gui_entry(argc, argv); @@ -113,6 +165,7 @@ int validateConnection() { printf("Code %d - %s\n", OSS_ping_struct->errorCode, OSS_ping_struct->errorMessage); } + // NOTE: A little verbose to avoid making another variable to hold error code after freeing structs if (!OSS_ping_struct->error) { opensubsonic_ping_struct_free(&OSS_ping_struct); opensubsonic_httpClient_URL_cleanup(&pingUrl);