Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
LeadRDRK committed Jul 3, 2021
0 parents commit 1ebd930
Show file tree
Hide file tree
Showing 15 changed files with 1,547 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/.cache
/build
/discord_game_sdk
/compile_commands.json
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.0.0)
project(wine-rpc)

add_subdirectory(src)
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# wine-rpc
wine-rpc is a Discord Rich Presence for any programs running under Wine.
![Sample](assets/sample1.jpg) ![Sample](assets/sample2.jpg)
## Why?
Using the Discord app itself, you can already add anything running under Wine as your current activity. However, Discord is not so good at keeping track of Wine programs; after a while, the program won't get detected anymore and you would have to add the program again.

Using a rich presence, we can literally put anything as your activity without relying on Discord to detect it. And as a bonus, supported apps could have their icon displayed on the rich presence. You could also have multiple presences for multiple apps at the same time.
## Installing
wine-rpc requires no installation. Simple download a prebuilt binary from the [Releases](https://github.com/LeadRDRK/wine-rpc/releases) page and extract it anywhere or build it yourself.

Please put it somewhere with write access; wine-rpc keeps it simple by creating the config file in the same folder.

You must also download the [app list](https://github.com/LeadRDRK/wine-rpc-app-list) yourself! It is not included in the binary packages or the source code.
## Usage
Using wine-rpc is fairly simple, simply open up a terminal and run the program. The program will now scan for any running Wine processes, check if it's in the app list, then show the rich presence. Any apps not in the app list will not be shown, but that behavior can be customized as shown in the Configuration section below.

If you want it to run as a daemon, use `wine-rpc -d` to start it and `wine-rpc -k` to kill all running daemons.

You should run the daemon at startup too. It's pretty lightweight and won't do much until a program of its interest starts, so having it in the background at all times is perfectly fine.
## Configuration
After running wine-rpc for the first time, a file named config.txt will be created. This file can be used to set a few options:
- `show_unrecognized_apps` - By default, only apps that have an entry in the app list will be shown. Setting this option to 1 will show all running processes (including any background processes that you might have, except for Wine services)
- `hide_exec_name` - If this is set to 1, when an app has an entry in the app list, the executable's name will be hidden and only the app's name will be shown.
## App Support
The names and icons of all applications are taken from the app list. If you don't see your favorite application in the app list, please create an issue in https://github.com/LeadRDRK/wine-rpc-app-list so I could add it for you. Every kind of application are supported, so please don't refrain yourself from requesting anything.

You could also add it to the app list yourself, but please prefer the former so everyone could have a rich presence for the same app!
## Compiling
wine-rpc requires the Discord Game SDK which is not included in this repository. Download it from [here](https://discord.com/developers/docs/game-sdk/sdk-starter-guide) and extract it into `discord_game_sdk` in the project's root.

CMake is used for generating the build files. Create a `build` folder in the project's root and run `cmake ..` to generate them, then run the build system.

The full procedure should look something like this:
```bash
cd wine-rpc
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make
```
## License
This project is licensed under the [GNU GPLv3](LICENSE) license.
Binary file added assets/sample1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/sample2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 46 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# detect system processor for lib folder
if("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "x86_64|ia64")
set(ARCH_DIR "x86_64")
elseif("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "x86|i686|i386")
set(ARCH_DIR "x86")
else()
message(FATAL_ERROR "Processor architecture not supported by the Discord Game SDK")
endif()

# check if it exists
set(DISCORD_LIB_PATH "${CMAKE_SOURCE_DIR}/discord_game_sdk/lib/${ARCH_DIR}/discord_game_sdk.so")
if(NOT EXISTS "${DISCORD_LIB_PATH}")
message(FATAL_ERROR "Discord Game SDK library not found!")
endif()
# copy the library to the build directory
file(COPY ${DISCORD_LIB_PATH} DESTINATION ${CMAKE_BINARY_DIR})
add_library(discord_game_sdk SHARED IMPORTED)
set_target_properties(discord_game_sdk PROPERTIES IMPORTED_LOCATION discord_game_sdk.so)

set(WINE_RPC_SOURCES
app_list.c
config.c
main.c
procs.c
)

set(WINE_RPC_HEADERS
app_list.h
config.h
procs.h
)

add_executable(wine-rpc ${WINE_RPC_SOURCES} ${WINE_RPC_HEADERS})
target_include_directories(wine-rpc PRIVATE "${CMAKE_SOURCE_DIR}/discord_game_sdk/c")
target_link_libraries(wine-rpc discord_game_sdk)
set_target_properties(wine-rpc PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
INSTALL_RPATH $ORIGIN
BUILD_WITH_INSTALL_RPATH TRUE
)
# copy the library to the binary's directory
add_custom_command(TARGET wine-rpc POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${DISCORD_LIB_PATH}
"${CMAKE_BINARY_DIR}/bin"
)
226 changes: 226 additions & 0 deletions src/app_list.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
#include "app_list.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <search.h>
#include <fcntl.h>
#include "log.h"

unsigned long djb2(const unsigned char *str)
{
unsigned long hash = 5381;
int c;

while ((c = *str++))
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */

return hash;
}

char get_escaped_char(char c)
{
// subset of c escape sequences
switch (c)
{
case '"': return '"';
case 'n': return '\n';
case 'r': return '\r';
case 't': return '\t';
case 'v': return '\v';
case '\'': return '\'';
case '\\': return '\\';
default:
LOG_ERROR("App list parser: Unknown escape sequence: \\%c", c);
return '\0';
}
}

#define BUFFER_SIZE 10
#define MAX_STR_LENGTH 128
size_t app_list_parse_file(const char* filename, AppEntry** entries)
{
FILE* fp = fopen(filename, "r");
if (fp == NULL) return -1;

// allocate a buffer of 10 entries
int cur_size = BUFFER_SIZE;
*entries = malloc(sizeof(AppEntry) * cur_size);

int depth = 0;
bool in_str = false;
bool in_entry = false;
int entry_index = 0;
int value_index = 0;
bool value_entered = false;
char str[128];
int str_index = 0;
int c;
while ((c = fgetc(fp)) != EOF)
{
switch (c)
{
case '[':
++depth;
if (depth > 2)
{
LOG_ERROR("App list parser: Array too deep (> 2)\n");
free(*entries);
return -1;
}
if (depth == 2) in_entry = true;
break;

case ']':
--depth;
if (depth < 0)
{
LOG_ERROR("App list parser: Syntax error / Trailing array closure\n");
free(*entries);
return -1;
}
if (depth == 1)
{
if (value_index < 2)
{
LOG_ERROR("App list parser: Insufficient entry values (expected 3, got %d)\n", value_index + 1);
free(*entries);
return -1;
}
in_entry = false;
}
value_index = 0;
value_entered = false;
break;

case '"':
in_str = !in_str;
if (value_entered && in_str)
{
LOG_ERROR("App list parser: Syntax error / Unexpected string opening\n");
free(*entries);
return -1;
}
if (!in_str)
{
// write the null terminator
str[str_index++] = '\0';

AppEntry* entry = &(*entries)[entry_index];
switch (value_index)
{
case 0:
entry->key = djb2((const unsigned char*)str);
break;
case 1:
memcpy(entry->name, str, str_index);
break;
case 2:
memcpy(entry->image, str, str_index);
break;
}
str_index = 0;
value_entered = true;
}
break;

case ',':
if (in_entry && !value_entered)
{
LOG_ERROR("App list parser: Syntax error / Unexpected value separator (,)\n");
free(*entries);
return -1;
}
if (depth == 1)
{
++entry_index;
if (cur_size <= entry_index)
{
cur_size += BUFFER_SIZE;
*entries = realloc(*entries, sizeof(AppEntry) * cur_size);
}
}
else if (depth == 2)
{
value_entered = false;
++value_index;
}
break;

// ignore whitespaces
case ' ':
case '\n':
case '\r':
case '\t':
if (!in_str) break;
// if in string then fallthrough...

default:
if (in_str)
{
if (c == '\\')
{
char escaped = get_escaped_char(fgetc(fp));
if (escaped == '\0')
return -1;
str[str_index++] = escaped;
}
else str[str_index++] = c;

if (str_index > MAX_STR_LENGTH)
{
LOG_ERROR("App list parser: String too long (> %d characters)\n", MAX_STR_LENGTH);
free(*entries);
return -1;
}
break;
}

LOG_ERROR("App list parser: Syntax error / Invalid character '%c'\n", c);
free(*entries);
return -1;
}
}

return entry_index + 1;
}

int compare(const void *_l, const void *_r)
{
const AppEntry* l = _l;
const AppEntry* r = _r;

if (l->key > r->key) return 1;
else if (l->key < r->key) return -1;
else return 0;
}

int app_list_init(AppList* list)
{
list->root = NULL;
size_t count = app_list_parse_file("app_list.txt", &list->entries);
if (count == -1) return 0;
LOG_INFO("App list: Loaded %lu entries\n", count);
// insert into binary tree
for (int i = 0; i < count; ++i)
{
tsearch(&list->entries[i], &list->root, compare);
}
return 1;
}

const AppEntry* app_list_get_entry(AppList* list, const char* exec_name)
{
AppEntry e;
e.key = djb2((const unsigned char*)exec_name);

void* r = tfind(&e, &list->root, compare);
if (r == NULL) return NULL;
return (*(AppEntry**)r);
}

void app_list_destroy(AppList* list)
{
tdelete(list->root, &list->root, compare);
free(list->entries);
}
21 changes: 21 additions & 0 deletions src/app_list.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once
#include <stddef.h>

typedef struct AppEntry
{
unsigned long key;
char name[128];
char image[128];
} AppEntry;

typedef struct AppList
{
void* root;
AppEntry* entries;
} AppList;


size_t app_list_parse_file(const char* filename, AppEntry** app_list);
int app_list_init(AppList* list);
const AppEntry* app_list_get_entry(AppList* list, const char* exec_name);
void app_list_destroy(AppList* list);
Loading

0 comments on commit 1ebd930

Please sign in to comment.