view src/main.c @ 7:be4835547dd0

clean up code, convert git files to hg, etc.
author Paper
date Fri, 16 Dec 2022 20:35:06 -0500
parents 59bf702b2b21
children 42ac054c0231
line wrap: on
line source

/**
 * wgsdk - Winamp plugin for Discord's GameSDK
**/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <Winamp/wa_ipc.h>
#include "discord_game_sdk.h"
#include "timer.h"
#include "config.h"
#include "resource.h"
#include "utils.h"
#ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <windowsx.h>

#define CLIENT_ID 969367220599803955
#define DISCORD_REQUIRE(x) \
	assert(x == DiscordResult_Ok)

#define GPPHDR_VER 0x10

int  init();
void conf();
void quit();

/* Winamp-specific stuff */
struct winamp_gpp {
    int version;                   // version of the plugin structure
    char *description;             // name/title of the plugin 
    int(*init)();                 // function which will be executed on init event
    void(*conf)();              // function which will be executed on config event
    void(*quit)();                // function which will be executed on quit event
    HWND hwndParent;               // hwnd of the Winamp client main window (stored by Winamp when dll is loaded)
    HINSTANCE hDllInstance;        // hinstance of this plugin DLL. (stored by Winamp when dll is loaded) 
};

struct winamp_gpp g_plugin = {
	GPPHDR_VER,  // version of the plugin, defined in "gen_myplugin.h"
	"Discord GameSDK", // name/title of the plugin, defined in "gen_myplugin.h"
	init,        // function name which will be executed on init event
	conf,      // function name which will be executed on config event
	quit,        // function name which will be executed on quit event
	0,           // handle to Winamp main window, loaded by winamp when this dll is loaded
	0            // hinstance to this dll, loaded by winamp when this dll is loaded
};

/* Discord stuff */

struct DiscordActivity activity = {
	.application_id = CLIENT_ID,
	.name = "Winamp",
	.instance = 0
};

struct IDiscordActivityEvents activities_events;

struct application {
    struct IDiscordCore* core;
    struct IDiscordUsers* users;
	struct IDiscordActivityManager* activities;
} app;

/* Now we get to our built-in stuff */

struct timer timer_callbacks = { .interval = 16 };

struct config config = {
	.display_title = 1,
	.show_elapsed_time = 1
};

/* CallWindowProc is the *only* function that ever needs this. */
WNDPROC _old_wnd_proc = 0;

/* ------------------------------------ */

void DISCORD_CALLBACK update_activity_callback(void* data, enum EDiscordResult result) {
	DISCORD_REQUIRE(result);
}

void report_current_song_status(int playback_state) {
	assert(playback_state != 0);
	strcpy(activity.state, playback_state == 1 ? "(Playing)" : "(Paused)");

	activity.timestamps.start = (playback_state == 1 && config.show_elapsed_time)
		? get_system_time_in_milliseconds() - SendMessage(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GETOUTPUTTIME) / 1000
		: 0;

	if (config.display_title) {
		wchar_t* title = (wchar_t*)SendMessageW(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_GET_PLAYING_TITLE);
		assert(WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, title, -1, activity.details, 256, NULL, NULL));
	}

	app.activities->update_activity(app.activities, &activity, &app, update_activity_callback);
}

void report_idle_status(void)
{
	activity.timestamps.start = 0;
	strcpy(activity.state, "(Idle)");

	app.activities->update_activity(app.activities, &activity, &app, update_activity_callback);
}

void update_rich_presence_details(void)
{
	LONG is_playing = SendMessageW(g_plugin.hwndParent, WM_WA_IPC, 0, IPC_ISPLAYING);

	switch (is_playing) {
		case 0:
			report_idle_status();
			break;
		case 1:
		case 3:
			report_current_song_status(is_playing);
		default:
			break;
	}
}

void CALLBACK TimerProc(HWND, UINT, UINT_PTR, DWORD) {
	DISCORD_REQUIRE(app.core->run_callbacks(app.core));
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
	if (message == WM_WA_IPC && lParam == IPC_CB_MISC && wParam == IPC_CB_MISC_STATUS)
		update_rich_presence_details();

	return CallWindowProc(_old_wnd_proc, hwnd, message, wParam, lParam);
}

#define set_wnd_long(x) \
	(WNDPROC)SetWindowLong##x(g_plugin.hwndParent, GWLP_WNDPROC, (LONG)WndProc)
int init() {
	memset(&app, 0, sizeof(app));
	memset(&activity, 0, sizeof(activity));
	memset(&activities_events, 0, sizeof(activities_events));

	if (IsWindowUnicode(g_plugin.hwndParent))
		_old_wnd_proc = set_wnd_long(W);
	else
		_old_wnd_proc = set_wnd_long(A);

	struct DiscordCreateParams params;
	DiscordCreateParamsSetDefault(&params);
	params.client_id = CLIENT_ID;
	params.flags = DiscordCreateFlags_Default;
	params.event_data = &app;
	params.activity_events = &activities_events;

	DISCORD_REQUIRE(DiscordCreate(DISCORD_VERSION, &params, &app.core));

	app.activities = app.core->get_activity_manager(app.core);

	timer_init(&timer_callbacks, g_plugin.hwndParent, TimerProc);
	timer_set(&timer_callbacks, g_plugin.hwndParent);

	cfg_load(&config);

	strcpy(activity.assets.large_image, "winamp-logo");

	update_rich_presence_details();

	return 0;
}
#undef set_wnd_long

void quit() {
	assert(!cfg_save(config));
	app.activities->clear_activity(app.activities, &app, update_activity_callback);
}

void conf() {
	DialogBoxW(g_plugin.hDllInstance, (LPWSTR)DIALOG_CONFIG, g_plugin.hwndParent, (DLGPROC)cfg_win_proc);
}

__declspec(dllexport) struct winamp_gpp* winampGetGeneralPurposePlugin() {
	return &g_plugin;
}