Skip to content

Commit

Permalink
Merge pull request #88 from bytequill/develop
Browse files Browse the repository at this point in the history
New plugin: Discord Rich presence
  • Loading branch information
SpartanJ authored Feb 15, 2025
2 parents 5dbab3b + f14e008 commit 2ed1774
Show file tree
Hide file tree
Showing 7 changed files with 877 additions and 0 deletions.
2 changes: 2 additions & 0 deletions bin/assets/i18n/en.xml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ File path is: </string>
<string name="cut">Cut</string>
<string name="dark">Dark</string>
<string name="date">Date</string>
<string name="dc_editing">Editing %s, a %s file</string>
<string name="dc_workspace">Working on %s</string>
<string name="debug_draw_boxes">Draw Boxes</string>
<string name="debug_draw_boxes_toggle">Debug Draw Boxes Toggle</string>
<string name="debug_draw_debug_data">Debug Draw Debug Data</string>
Expand Down
110 changes: 110 additions & 0 deletions bin/assets/plugins/discordRPC.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
{
"config": {
"appID": "1339026777158455336",
"doLanguageIcons": true,
"iconBindings": {
".htaccess": "https://github.com/vyfor/icons/raw/master/icons/onyx/gear.png",
".ignore file": "https://github.com/vyfor/icons/raw/master/icons/onyx/git.png",
"[x]it!": "https://github.com/vyfor/icons/raw/master/icons/onyx/notes.png",
"ada": "https://github.com/vyfor/icons/raw/master/icons/pastel/ada.png",
"adept": "https://raw.github.com/AdeptLanguage/Adept/master/.github/README_logo.png",
"angelscript": "https://github.com/vyfor/icons/raw/master/icons/onyx/text.png",
"awk script": "https://github.com/vyfor/icons/raw/master/icons/pastel/awk.png",
"bat": "https://github.com/vyfor/icons/raw/master/icons/onyx/shell.png",
"bazel": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/7d/Bazel_logo.svg/1024px-Bazel_logo.svg.png",
"bend": "https://avatars.githubusercontent.com/u/92327702",
"blueprint": "https://github.com/vyfor/icons/raw/master/icons/onyx/controller.png",
"brainfuck": "https://github.com/vyfor/icons/raw/master/icons/onyx/text.png",
"buzz": "https://github.com/vyfor/icons/raw/master/icons/onyx/text.png",
"c": "https://github.com/vyfor/icons/raw/master/icons/onyx/c.png",
"carbon": "https://upload.wikimedia.org/wikipedia/commons/e/e1/Carbon_logo.png",
"clojure": "https://github.com/vyfor/icons/raw/master/icons/onyx/clojure.png",
"cmake": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/CMake_logo.svg/1024px-CMake_logo.svg.png",
"cpp": "https://github.com/vyfor/icons/raw/master/icons/onyx/cpp.png",
"crystal": "https://github.com/vyfor/icons/raw/master/icons/onyx/crystal.png",
"csharp": "https://github.com/vyfor/icons/raw/master/icons/onyx/csharp.png",
"css": "https://github.com/vyfor/icons/raw/master/icons/onyx/css.png",
"d": "https://github.com/vyfor/icons/raw/master/icons/onyx/d.png",
"dart": "https://github.com/vyfor/icons/raw/master/icons/onyx/dart.png",
"diff": "https://github.com/vyfor/icons/raw/master/icons/onyx/diagnostics.png",
"dockerfile": "https://github.com/vyfor/icons/raw/master/icons/onyx/docker.png",
"elixir": "https://github.com/vyfor/icons/raw/master/icons/onyx/elixir.png",
"elm": "https://github.com/vyfor/icons/raw/master/icons/onyx/elm.png",
"environment file": "https://github.com/vyfor/icons/raw/master/icons/onyx/gear.png",
"fantom": "https://avatars.githubusercontent.com/u/54911692",
"fortran": "https://github.com/vyfor/icons/raw/master/icons/onyx/fortran.png",
"fstab": "https://github.com/vyfor/icons/raw/master/icons/onyx/gear.png",
"gdscript": "https://github.com/vyfor/icons/raw/master/icons/onyx/godot.png",
"glsl": "https://github.com/vyfor/icons/raw/master/icons/onyx/opengl.png",
"go": "https://github.com/vyfor/icons/raw/master/icons/onyx/go.png",
"graphql": "https://github.com/vyfor/icons/raw/master/icons/onyx/graphql.png",
"groovy": "https://github.com/vyfor/icons/raw/master/icons/onyx/groovy.png",
"hare": "https://harelang.org/mascot.png",
"haskell": "https://github.com/vyfor/icons/raw/master/icons/onyx/haskell.png",
"haxe": "https://github.com/vyfor/icons/raw/master/icons/pastel/haxe.png",
"haxe compiler arguments": "https://github.com/vyfor/icons/raw/master/icons/onyx/haxe.png",
"hlsl": "https://github.com/vyfor/icons/raw/master/icons/onyx/diagnostics.png",
"html": "https://github.com/vyfor/icons/raw/master/icons/onyx/html.png",
"ini": "https://github.com/vyfor/icons/raw/master/icons/onyx/gear.png",
"jai": "https://github.com/vyfor/icons/raw/master/icons/onyx/text.png",
"java": "https://github.com/vyfor/icons/raw/master/icons/onyx/java.png",
"javascript": "https://github.com/vyfor/icons/raw/master/icons/onyx/javascript.png",
"javascriptreact": "https://github.com/vyfor/icons/raw/master/icons/onyx/react.png",
"json": "https://github.com/vyfor/icons/raw/master/icons/onyx/json.png",
"julia": "https://github.com/vyfor/icons/raw/master/icons/onyx/julia.png",
"kotlin": "https://github.com/vyfor/icons/raw/master/icons/onyx/kotlin.png",
"latex": "https://github.com/vyfor/icons/raw/master/icons/onyx/latex.png",
"lobster": "https://github.com/vyfor/icons/raw/master/icons/onyx/text.png",
"lua": "https://github.com/vyfor/icons/raw/master/icons/onyx/latex.png",
"makefile": "https://github.com/vyfor/icons/raw/master/icons/onyx/diagnostics.png",
"markdown": "https://github.com/vyfor/icons/raw/master/icons/onyx/markdown.png",
"meson": "https://github.com/vyfor/icons/raw/master/icons/onyx/diagnostics.png",
"moonscript": "https://github.com/vyfor/icons/raw/master/icons/onyx/lua.png",
"nelua": "https://nelua.io/assets/img/nelua-logo-64px.png",
"nim": "https://github.com/vyfor/icons/raw/master/icons/onyx/nim.png",
"objeck": "https://github.com/vyfor/icons/raw/master/icons/onyx/gear.png",
"objective-c": "https://github.com/vyfor/icons/raw/master/icons/onyx/c.png",
"ocaml": "https://github.com/vyfor/icons/raw/master/icons/onyx/ocaml.png",
"odin": "https://github.com/vyfor/icons/raw/master/icons/onyx/odin.png",
"openscad": "https://upload.wikimedia.org/wikipedia/commons/thumb/e/eb/Openscad.svg/1024px-Openscad.svg.png",
"pascal": "https://github.com/vyfor/icons/raw/master/icons/onyx/pascal.png",
"perl": "https://github.com/vyfor/icons/raw/master/icons/onyx/perl.png",
"php": "https://github.com/vyfor/icons/raw/master/icons/onyx/php.png",
"pico-8": "https://www.lexaloffle.com/gfx/p8b_pico8.png",
"plaintext": "https://github.com/vyfor/icons/raw/master/icons/onyx/text.png",
"po": "https://github.com/vyfor/icons/raw/master/icons/onyx/gnu.png",
"pony": "https://avatars.githubusercontent.com/u/12997238",
"postgresql": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/29/Postgresql_elephant.svg/1024px-Postgresql_elephant.svg.png",
"powershell": "https://github.com/vyfor/icons/raw/master/icons/onyx/powershell.png",
"python": "https://github.com/vyfor/icons/raw/master/icons/onyx/python.png",
"r": "https://github.com/vyfor/icons/raw/master/icons/onyx/r.png",
"ring": "https://github.com/vyfor/icons/raw/master/icons/onyx/text.png",
"ruby": "https://github.com/vyfor/icons/raw/master/icons/onyx/ruby.png",
"rust": "https://github.com/vyfor/icons/raw/master/icons/onyx/rust.png",
"sass": "https://github.com/vyfor/icons/raw/master/icons/onyx/scss.png",
"scala": "https://github.com/vyfor/icons/raw/master/icons/onyx/scala.png",
"shellscript": "https://github.com/vyfor/icons/raw/master/icons/onyx/shell.png",
"smallbasic": "https://github.com/vyfor/icons/raw/master/icons/onyx/text.png",
"solidity": "https://github.com/vyfor/icons/raw/master/icons/onyx/text.png",
"sql": "https://github.com/vyfor/icons/raw/master/icons/onyx/sql.png",
"swift": "https://github.com/vyfor/icons/raw/master/icons/onyx/swift.png",
"tcl": "https://github.com/vyfor/icons/raw/master/icons/onyx/text.png",
"teal": "https://avatars.githubusercontent.com/u/62526430",
"toml": "https://github.com/vyfor/icons/raw/master/icons/onyx/toml.png",
"typescript": "https://github.com/vyfor/icons/raw/master/icons/onyx/typescript.png",
"typescriptreact": "https://github.com/vyfor/icons/raw/master/icons/onyx/react.png",
"v": "https://github.com/vyfor/icons/raw/master/icons/onyx/v.png",
"vala": "https://github.com/vyfor/icons/raw/master/icons/onyx/vala.png",
"verilog": "https://github.com/vyfor/icons/raw/master/icons/onyx/assembly.png",
"visual basic": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/VB.NET_Logo.svg/1024px-VB.NET_Logo.svg.png",
"vue": "https://github.com/vyfor/icons/raw/master/icons/onyx/vue.png",
"wren": "https://avatars.githubusercontent.com/u/45213573",
"x86 assembly": "https://github.com/vyfor/icons/raw/master/icons/onyx/assembly.png",
"xml": "https://github.com/vyfor/icons/raw/master/icons/onyx/xml.png",
"xtend": "https://github.com/vyfor/icons/raw/master/icons/onyx/java.png",
"yaml": "https://github.com/vyfor/icons/raw/master/icons/onyx/yaml.png",
"zig": "https://github.com/vyfor/icons/raw/master/icons/onyx/zig.png"
}
},
"keybindings": {}
}
2 changes: 2 additions & 0 deletions src/tools/ecode/ecode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "plugins/linter/linterplugin.hpp"
#include "plugins/lsp/lspclientplugin.hpp"
#include "plugins/xmltools/xmltoolsplugin.hpp"
#include "plugins/discordRPC/discordRPCplugin.hpp"

#if EE_PLATFORM == EE_PLATFORM_LINUX
// For malloc_trim, which is a GNU extension
Expand Down Expand Up @@ -605,6 +606,7 @@ void App::initPluginManager() {
mPluginManager->registerPlugin( LSPClientPlugin::Definition() );
mPluginManager->registerPlugin( XMLToolsPlugin::Definition() );
mPluginManager->registerPlugin( GitPlugin::Definition() );
mPluginManager->registerPlugin( DiscordRPCplugin::Definition() );
}

std::pair<bool, std::string> App::generateConfigPath() {
Expand Down
247 changes: 247 additions & 0 deletions src/tools/ecode/plugins/discordRPC/discordRPCplugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
#include "discordRPCplugin.hpp"

using json = nlohmann::json;
#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ )
#define dcRPC_THREADED 1
#else
#define dcRPC_THREADED 0
#endif

namespace ecode {

static const auto DEFAULT_CLIENT_ID = "1339026777158455336";
static const auto DebounceUniqueId = String::hash( "DiscordRPCplugin::debounce" );
static const std::vector<std::string> DiscordRPCCommandList = { "discordrpc-reconnect" };

static const auto DISCORDRPC_DEFAULT_ICON =
"https://github.com/SpartanJ/eepp/blob/develop/bin/assets/icon/ecode.png?raw=true";

Plugin* DiscordRPCplugin::New( PluginManager* pluginManager ) {
return eeNew( DiscordRPCplugin, ( pluginManager, false ) );
}

Plugin* DiscordRPCplugin::NewSync( PluginManager* pluginManager ) {
return eeNew( DiscordRPCplugin, ( pluginManager, true ) );
}

DiscordRPCplugin::DiscordRPCplugin( PluginManager* pluginManager, bool sync ) :
PluginBase( pluginManager ) {
if ( sync ) {
load( pluginManager );
} else {
#if defined( dcRPC_THREADED ) && dcRPC_THREADED == 1
mThreadPool->run( [this, pluginManager] { load( pluginManager ); } );
#else
load( pluginManager );
#endif
}
}

DiscordRPCplugin::~DiscordRPCplugin() {
waitUntilLoaded();
if ( mIPC.isConnected() )
mIPC.clearActivity();
mShuttingDown = true;

for ( auto editor : mEditors ) {
onBeforeUnregister( editor.first );
onUnregisterEditor( editor.first );
}
}

void DiscordRPCplugin::load( PluginManager* pluginManager ) {
Clock clock;
AtomicBoolScopedOp loading( mLoading, true );

pluginManager->subscribeMessages( this,
[this]( const auto& notification ) -> PluginRequestHandle {
return processMessage( notification );
} );

std::vector<std::string> paths;
std::string path( pluginManager->getResourcesPath() + "plugins/discordRPC.json" );
if ( FileSystem::fileExists( path ) )
paths.emplace_back( path );
path = pluginManager->getPluginsPath() + "discordRPC.json";
if ( FileSystem::fileExists( path ) ||
FileSystem::fileWrite( path, "{\n \"config\":{},\n \"keybindings\":{}\n}\n" ) ) {
mConfigPath = path;
paths.emplace_back( path );
}

if ( paths.empty() )
return;

for ( const auto& ipath : paths ) {
try {
loadDiscordRPCConfig( ipath, mConfigPath == ipath );
} catch ( const json::exception& e ) {
Log::error( "Parsing DiscordRPCplugin config \"%s\" failed:\n%s", ipath.c_str(),
e.what() );
}
}

if ( getUISceneNode() ) {
mIPC.UIReady = true;
initIPC();
} else {
mIPC.IsReconnectScheduled = true;
}

mReady = true;
fireReadyCbs();
setReady( clock.getElapsedTime() );
}

void DiscordRPCplugin::loadDiscordRPCConfig( const std::string& path, bool updateConfigFile ) {
std::string data;
if ( !FileSystem::fileGet( path, data ) )
return;

if ( updateConfigFile )
mConfigHash = String::hash( data );

json j;
try {
j = json::parse( data, nullptr, true, true );
} catch ( const json::exception& e ) {
Log::error( "DiscordRPCplugin::load - Error parsing config from path %s, error: %s, config "
"file content:\n%s",
path.c_str(), e.what(), data.c_str() );
// Recreate it
j = json::parse( "{\n \"config\":{},\n \"keybindings\":{},\n}\n", nullptr, true, true );
}

if ( j.contains( "config" ) ) {
auto& config = j["config"];

if ( config.contains( "iconBindings" ) ) {
auto newConfig = config["iconBindings"];
if ( mLangBindings.empty() )
mLangBindings = std::move( newConfig );
else if ( !newConfig.empty() )
mLangBindings.update( newConfig );
} else
config["iconBindings"] = mLangBindings;

if ( config.contains( "appID" ) )
mIPC.ClientID = config.value( "appID", DEFAULT_CLIENT_ID );
else
config["appID"] = DEFAULT_CLIENT_ID;

if ( config.contains( "doLanguageIcons" ) )
mDoLangIcon = config.value( "doLanguageIcons", true );
else
config["doLanguageIcons"] = true;

if ( updateConfigFile && config.contains( "iconBindings" ) &&
config["iconBindings"].is_null() )
config["iconBindings"] = nlohmann::json::object();
}

if ( updateConfigFile ) {
std::string newData = j.dump( 2 );
if ( newData != data ) {
FileSystem::fileWrite( path, newData );
mConfigHash = String::hash( newData );
}
}
}

void DiscordRPCplugin::initIPC() {
if ( mIPC.tryConnect() ) {
DiscordIPCActivity a = mIPC.getActivity();
a.largeImage = DISCORDRPC_DEFAULT_ICON;
mIPC.setActivity( std::move( a ) );
}
}

PluginRequestHandle DiscordRPCplugin::processMessage( const PluginMessage& msg ) {
switch ( msg.type ) {
case PluginMessageType::WorkspaceFolderChanged: {
std::string rpath = FileSystem::getRealPath( msg.asJSON()["folder"] );
FileSystem::dirAddSlashAtEnd( rpath );
mProjectName = FileSystem::fileNameFromPath( rpath );
Log::debug( "Loaded new workspace: %s ; %s", rpath, mProjectName );
}
case PluginMessageType::UIReady: {
mIPC.UIReady = true;

if ( mIPC.IsReconnectScheduled ) {
Log::debug( "Running scheduled reconnect" );

getUISceneNode()->getThreadPool()->run( [this] { initIPC(); } );
}
}
default:
break;
}

return PluginRequestHandle::empty();
}

void DiscordRPCplugin::onRegisterEditor( UICodeEditor* editor ) {
editor->addUnlockedCommands( DiscordRPCCommandList );
editor->getDocument().setCommand( "discordrpc-reconnect", [this] { mIPC.reconnect(); } );

PluginBase::onRegisterEditor( editor );
}

void DiscordRPCplugin::onUnregisterEditor( UICodeEditor* editor ) {
editor->removeUnlockedCommands( DiscordRPCCommandList );
editor->removeActionsByTag( DebounceUniqueId );
}

void DiscordRPCplugin::udpateActivity( DiscordIPCActivity& a ) {
Lock l( mDataMutex );
Log::debug( "dcIPC: Activity in new file. lang = %s", mLastLang );

if ( !mProjectName.empty() ) {
a.details =
String::format( i18n( "dc_workspace", "Working on %s" ).toUtf8(), mProjectName );
}

a.state = String::format( i18n( "dc_editing", "Editing %s, a %s file" ).toUtf8(), mLastFile,
mLastLang );

a.start = time( nullptr ); // Time spent in this specific file

// TODO: Implement github/gitlab remote button (integrate with git plugin)
// a.buttons[0].label = "Repository";
// a.buttons[0].url = "https://github.com/name/repo";

if ( !mLastLangName.empty() && mDoLangIcon && mLangBindings.contains( mLastLangName ) ) {
a.largeImage = mLangBindings.value( mLastLangName, DISCORDRPC_DEFAULT_ICON );
}
}

void DiscordRPCplugin::onRegisterListeners( UICodeEditor* editor, std::vector<Uint32>& listeners ) {
listeners.push_back( editor->on( Event::OnFocus, [this, editor]( const Event* ) {
auto& doc = editor->getDocument();
if ( !doc.hasFilepath() )
return;

auto filename = doc.getFilename();
if ( filename == mLastFile )
return;

{
Lock l( mDataMutex );
mLastFile = std::move( filename );
mLastLang = doc.getSyntaxDefinition().getLanguageName();
mLastLangName = doc.getSyntaxDefinition().getLSPName();
}

getUISceneNode()->debounce(
[this] {
getUISceneNode()->getThreadPool()->run( [this] {
DiscordIPCActivity a = mIPC.getActivity();
udpateActivity( a );
mIPC.setActivity( std::move( a ) );
} );
},
Seconds( 2 ), DebounceUniqueId );
} ) );
}

} // namespace ecode
Loading

0 comments on commit 2ed1774

Please sign in to comment.