-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #88 from bytequill/develop
New plugin: Discord Rich presence
- Loading branch information
Showing
7 changed files
with
877 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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": {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
247 changes: 247 additions & 0 deletions
247
src/tools/ecode/plugins/discordRPC/discordRPCplugin.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.