├── .gitignore ├── fls-rich-presence-cs ├── packages.config ├── App.config ├── Properties │ └── AssemblyInfo.cs ├── fls-rich-presence-cs.csproj ├── Tray.cs └── Program.cs ├── LICENSE ├── fls-rich-presence-cs.sln └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | fls-rich-presence-cs/bin 2 | fls-rich-presence-cs/obj 3 | .vs 4 | packages -------------------------------------------------------------------------------- /fls-rich-presence-cs/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /fls-rich-presence-cs/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sayaka / 黒皇帝 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /fls-rich-presence-cs.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2027 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "fls-rich-presence-cs", "fls-rich-presence-cs\fls-rich-presence-cs.csproj", "{19B9525E-5254-4E1F-A045-6685B0CCB3BC}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {19B9525E-5254-4E1F-A045-6685B0CCB3BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {19B9525E-5254-4E1F-A045-6685B0CCB3BC}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {19B9525E-5254-4E1F-A045-6685B0CCB3BC}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {19B9525E-5254-4E1F-A045-6685B0CCB3BC}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {CF76AFB5-CB20-4F16-98D4-67E71BEECCFD} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fls-rich-presence-cs 2 | 3 | ![https://ci.appveyor.com/api/projects/status/github/SayakaIsBaka/fls-rich-presence-cs?svg=true](https://ci.appveyor.com/api/projects/status/github/SayakaIsBaka/fls-rich-presence-cs?svg=true) 4 | 5 | C# rewrite of [fls-rich-presence](https://github.com/SayakaIsBaka/fls-rich-presence), originally written in JavaScript (Node.js) 6 | 7 | ![](https://sayakaisbaka.s-ul.eu/vzEJx3bb.png) 8 | 9 | ## Requirements 10 | 11 | - .NET Framework 4 or above 12 | - Discord and FL Studio (do I really have to say why) 13 | 14 | ## Download 15 | 16 | Grab the lastest release [here](https://ci.appveyor.com/api/projects/SayakaIsBaka/fls-rich-presence-cs/artifacts/fls-rich-presence-cs/bin/Release.zip) or alternatively, check out the Releases tab [here!](https://github.com/SayakaIsBaka/fls-rich-presence-cs/releases) 17 | 18 | ## Setup 19 | 20 | - Download the lastest release above and extract it somewhere 21 | - Run FL Studio and wait for the main window to appear 22 | - Run `fls-rich-presence-cs.exe` 23 | 24 | When the program is running, a FL icon is showing in the system tray. Right-clicking it displays a menu with some options such as enable or disable the Rich Presence or killing the program. 25 | 26 | ## Issues 27 | 28 | - Timer may act weird sometimes 29 | - You may have to run the program as admin to get it working 30 | 31 | Please open an issue on GitHub if you encounter any problems! 32 | 33 | 34 | ## Credits 35 | 36 | - discord-rpc-csharp by Lachee: https://github.com/Lachee/discord-rpc-csharp 37 | -------------------------------------------------------------------------------- /fls-rich-presence-cs/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Les informations générales relatives à un assembly dépendent de 6 | // l'ensemble d'attributs suivant. Changez les valeurs de ces attributs pour modifier les informations 7 | // associées à un assembly. 8 | [assembly: AssemblyTitle("fls-rich-presence-cs")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("fls-rich-presence-cs")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // L'affectation de la valeur false à ComVisible rend les types invisibles dans cet assembly 18 | // aux composants COM. Si vous devez accéder à un type dans cet assembly à partir de 19 | // COM, affectez la valeur true à l'attribut ComVisible sur ce type. 20 | [assembly: ComVisible(false)] 21 | 22 | // Le GUID suivant est pour l'ID de la typelib si ce projet est exposé à COM 23 | [assembly: Guid("19b9525e-5254-4e1f-a045-6685b0ccb3bc")] 24 | 25 | // Les informations de version pour un assembly se composent des quatre valeurs suivantes : 26 | // 27 | // Version principale 28 | // Version secondaire 29 | // Numéro de build 30 | // Révision 31 | // 32 | // Vous pouvez spécifier toutes les valeurs ou indiquer les numéros de build et de révision par défaut 33 | // en utilisant '*', comme indiqué ci-dessous : 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /fls-rich-presence-cs/fls-rich-presence-cs.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {19B9525E-5254-4E1F-A045-6685B0CCB3BC} 8 | WinExe 9 | fls_rich_presence_cs 10 | fls-rich-presence-cs 11 | v4.0 12 | 512 13 | true 14 | 15 | publish\ 16 | true 17 | Disk 18 | false 19 | Foreground 20 | 7 21 | Days 22 | false 23 | false 24 | true 25 | 0 26 | 1.0.0.%2a 27 | false 28 | false 29 | true 30 | 31 | 32 | AnyCPU 33 | true 34 | full 35 | false 36 | bin\Debug\ 37 | DEBUG;TRACE 38 | prompt 39 | 4 40 | 41 | 42 | AnyCPU 43 | none 44 | true 45 | bin\Release\ 46 | TRACE 47 | prompt 48 | 4 49 | false 50 | 51 | 52 | 53 | 54 | true 55 | 56 | 57 | 58 | 59 | 60 | 61 | ..\packages\DiscordRichPresence.1.0.175\lib\net35\DiscordRPC.dll 62 | 63 | 64 | ..\packages\Newtonsoft.Json.13.0.1\lib\net40\Newtonsoft.Json.dll 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | False 88 | .NET Framework 3.5 SP1 89 | false 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /fls-rich-presence-cs/Tray.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Collections.Generic; 4 | using System.Windows.Forms; 5 | using System.Drawing; 6 | using Microsoft.Win32; 7 | using System.IO; 8 | 9 | namespace fls_rich_presence_cs 10 | { 11 | class Tray 12 | { 13 | public NotifyIcon NotifyTray { get; private set; } 14 | public bool IsPresenceActive { get; private set; } 15 | private bool RunOnStartup; 16 | 17 | public Tray() 18 | { 19 | RegistryKey rk = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true); 20 | Object tmp = rk.GetValue("FLS Rich Presence"); 21 | if (tmp == null) 22 | RunOnStartup = false; 23 | else 24 | RunOnStartup = true; 25 | NotifyTray = CreateTray(); 26 | IsPresenceActive = true; 27 | } 28 | 29 | private NotifyIcon CreateTray() 30 | { 31 | NotifyIcon tray = new NotifyIcon(); 32 | tray.Text = "FL Studio Rich Presence: Enabled"; 33 | 34 | try 35 | { 36 | RegistryKey key = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Image-Line\\Shared\\Paths"); 37 | if (key != null) 38 | { 39 | Object obj = key.GetValue("FL Studio"); 40 | if (obj != null) 41 | { 42 | tray.Icon = Icon.ExtractAssociatedIcon(obj as String); 43 | } 44 | else 45 | throw new KeyNotFoundException("FL registry key not found"); 46 | } 47 | else 48 | throw new KeyNotFoundException("FL registry key not found"); 49 | } 50 | catch (Exception) 51 | { 52 | tray.Icon = new Icon(SystemIcons.Application, 40, 40); 53 | } 54 | 55 | ContextMenu menu = new ContextMenu(); 56 | menu.MenuItems.Add("FL Studio not detected..."); 57 | menu.MenuItems[0].Enabled = false; 58 | menu.MenuItems.Add("-"); 59 | menu.MenuItems.Add("Disable Rich Presence", new EventHandler(SwitchPresence)); 60 | menu.MenuItems.Add("Run on startup", new EventHandler(SetStartup)); 61 | menu.MenuItems[3].Checked = RunOnStartup; 62 | menu.MenuItems.Add("Project repository (GitHub)", new EventHandler(ProjectURL)); 63 | menu.MenuItems.Add("-"); 64 | menu.MenuItems.Add("View log", new EventHandler(ViewLog)); 65 | menu.MenuItems.Add("-"); 66 | menu.MenuItems.Add("Exit", new EventHandler(Exit)); 67 | tray.ContextMenu = menu; 68 | tray.Visible = true; 69 | 70 | return tray; 71 | } 72 | 73 | private void ProjectURL(object sender, EventArgs e) 74 | { 75 | System.Diagnostics.Process.Start("https://github.com/SayakaIsBaka/fls-rich-presence-cs"); 76 | } 77 | 78 | public void SetExternalFunc(Action func, int menuID) 79 | { 80 | NotifyTray.ContextMenu.MenuItems[menuID].Click += delegate (object sender, EventArgs e) { func(); }; 81 | } 82 | 83 | private void ViewLog(object sender, EventArgs e) 84 | { 85 | Process.Start(Directory.GetCurrentDirectory() + "\\log.txt"); 86 | } 87 | 88 | public void Dispose() 89 | { 90 | NotifyTray.Dispose(); 91 | IsPresenceActive = false; 92 | } 93 | 94 | public void SwitchPresence(object sender, EventArgs e) 95 | { 96 | IsPresenceActive = !IsPresenceActive; 97 | if (IsPresenceActive) 98 | { 99 | NotifyTray.ContextMenu.MenuItems[2].Text = "Disable Rich Presence"; 100 | NotifyTray.Text = "FL Studio Rich Presence: Enabled"; 101 | } 102 | else 103 | { 104 | NotifyTray.ContextMenu.MenuItems[2].Text = "Enable Rich Presence"; 105 | NotifyTray.Text = "FL Studio Rich Presence: Disabled"; 106 | } 107 | } 108 | private void SetStartup(object sender, EventArgs e) 109 | { 110 | RegistryKey rk = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true); 111 | if (!RunOnStartup) 112 | { 113 | rk.SetValue("FLS Rich Presence", Application.ExecutablePath); 114 | RunOnStartup = true; 115 | } 116 | else 117 | { 118 | rk.DeleteValue("FLS Rich Presence", false); 119 | RunOnStartup = false; 120 | } 121 | 122 | NotifyTray.ContextMenu.MenuItems[3].Checked = RunOnStartup; 123 | } 124 | 125 | private void Exit(object sender, EventArgs e) 126 | { 127 | Program.Exit(); 128 | } 129 | 130 | public void Detected(bool isDetected) 131 | { 132 | if (isDetected) 133 | { 134 | NotifyTray.ContextMenu.MenuItems[0].Text = "FL Studio detected!"; 135 | } 136 | else 137 | { 138 | NotifyTray.ContextMenu.MenuItems[0].Text = "FL Studio not detected..."; 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /fls-rich-presence-cs/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | using System.Text.RegularExpressions; 5 | using System.Threading; 6 | using System.Windows.Forms; 7 | using System.IO; 8 | using DiscordRPC; 9 | using DiscordRPC.Logging; 10 | using DiscordRPC.Message; 11 | 12 | namespace fls_rich_presence_cs 13 | { 14 | class Program 15 | { 16 | [DllImport("user32.dll")] 17 | static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId); 18 | 19 | [DllImport("user32.dll")] 20 | private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam); 21 | 22 | [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 23 | static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); 24 | 25 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] 26 | static extern int GetWindowTextLength(IntPtr hWnd); 27 | 28 | [DllImport("kernel32.dll", SetLastError = true)] 29 | public static extern uint GetProcessIdOfThread(IntPtr handle); 30 | 31 | [Flags] 32 | public enum ThreadAccess : int 33 | { 34 | TERMINATE = (0x0001), 35 | SUSPEND_RESUME = (0x0002), 36 | GET_CONTEXT = (0x0008), 37 | SET_CONTEXT = (0x0010), 38 | SET_INFORMATION = (0x0020), 39 | QUERY_INFORMATION = (0x0040), 40 | SET_THREAD_TOKEN = (0x0080), 41 | IMPERSONATE = (0x0100), 42 | DIRECT_IMPERSONATION = (0x0200) 43 | } 44 | 45 | [DllImport("kernel32.dll", SetLastError = true)] 46 | static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId); 47 | 48 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] 49 | static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); 50 | 51 | public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); 52 | 53 | private static Tray tray; 54 | private static DiscordRpcClient client; 55 | private static FileStream logFile; 56 | private static StreamWriter sw; 57 | private static RichPresence presence = new RichPresence() 58 | { 59 | Details = "Editing:", 60 | State = "", 61 | Timestamps = new Timestamps(), 62 | Assets = new Assets() 63 | { 64 | LargeImageKey = "fl_icon", 65 | LargeImageText = "FL Studio", 66 | } 67 | }; 68 | 69 | static void Init() 70 | { 71 | logFile = new FileStream(Directory.GetCurrentDirectory() + "\\log.txt", FileMode.Create); 72 | sw = new StreamWriter(logFile); 73 | sw.AutoFlush = true; 74 | sw.WriteLine("Log: " + DateTime.UtcNow.ToString("ddd, dd MMM yyyy HH:mm:ss 'GMT'")); 75 | Console.SetOut(sw); 76 | Console.SetError(sw); 77 | 78 | client = new DiscordRpcClient("your_token_here") 79 | { 80 | Logger = new DiscordRPC.Logging.ConsoleLogger() { Level = LogLevel.Warning } 81 | }; 82 | 83 | client.OnReady += OnReady; 84 | 85 | presence.Timestamps.Start = DateTime.UtcNow; 86 | client.Initialize(); 87 | } 88 | 89 | static void Main(string[] args) 90 | { 91 | Init(); 92 | 93 | Thread trayThread = new Thread( 94 | delegate () 95 | { 96 | tray = new Tray(); 97 | Application.Run(); 98 | }); 99 | trayThread.Start(); 100 | 101 | MainLoop(); 102 | 103 | } 104 | 105 | static void MainLoop() 106 | { 107 | client.Invoke(); 108 | Thread.Sleep(7000); 109 | 110 | while (client != null && !client.IsDisposed) 111 | { 112 | string winTitle; 113 | if (client != null) 114 | { 115 | winTitle = GetFLTitle(); 116 | if (winTitle != null) 117 | { 118 | UpdatePresence(winTitle); 119 | tray.Detected(true); 120 | } 121 | else 122 | { 123 | UpdatePresence(null); 124 | tray.Detected(false); 125 | } 126 | Thread.Sleep(15000); 127 | } 128 | } 129 | } 130 | 131 | static void OnReady(object sender, ReadyMessage args) 132 | { 133 | Console.WriteLine("On Ready. RPC Version: {0}", args.Version); 134 | 135 | } 136 | 137 | static void UpdatePresence(string title) 138 | { 139 | if (!tray.IsPresenceActive || title == null) 140 | client.SetPresence(null); 141 | else 142 | { 143 | string[] splitTitle = title.Split(' '); 144 | if (splitTitle[0] != "Rendering:") 145 | { 146 | string version = "FL Studio " + splitTitle[splitTitle.Length - 1]; 147 | string updateTitle = ""; 148 | if (title == version) 149 | { 150 | updateTitle = "Unsaved project"; 151 | } 152 | else 153 | { 154 | updateTitle = Regex.Match(title, ".+?(?= - FL Studio [0-9]?[0-9]$)").Value; 155 | if (Regex.Match(updateTitle, "^ *$").Success) 156 | { 157 | updateTitle = null; 158 | } 159 | } 160 | if (updateTitle != presence.State) 161 | { 162 | presence.Timestamps.Start = DateTime.UtcNow; 163 | } 164 | presence.Assets.LargeImageText = version; 165 | presence.State = updateTitle; 166 | client.SetPresence(presence); 167 | } 168 | } 169 | } 170 | 171 | static string GetFLTitle() 172 | { 173 | string processName = null; 174 | uint threadID = 0; 175 | 176 | EnumWindows(delegate (IntPtr wnd, IntPtr param) 177 | { 178 | threadID = GetWindowThreadProcessId(wnd, IntPtr.Zero); 179 | 180 | StringBuilder className = new StringBuilder(256); 181 | int cint = GetClassName(wnd, className, 256); 182 | if (cint == 0) 183 | return false; 184 | 185 | IntPtr thr = OpenThread(ThreadAccess.QUERY_INFORMATION, false, threadID); 186 | uint procID = GetProcessIdOfThread(thr); 187 | 188 | if (className.ToString() == "TFruityLoopsMainForm") 189 | { 190 | int textLength = GetWindowTextLength(wnd); 191 | StringBuilder outText = new StringBuilder(textLength + 1); 192 | GetWindowText(wnd, outText, textLength + 1); 193 | processName = outText.ToString(); 194 | return false; 195 | } 196 | 197 | return true; 198 | }, IntPtr.Zero); 199 | 200 | return processName; 201 | } 202 | 203 | public static void Exit() 204 | { 205 | tray.Dispose(); 206 | client.Dispose(); 207 | Thread.Sleep(1000); 208 | Console.WriteLine("RPC closed."); 209 | sw.Close(); 210 | Application.Exit(); 211 | } 212 | } 213 | } 214 | --------------------------------------------------------------------------------