first commit

This commit is contained in:
Djkato 2022-09-08 20:33:18 +02:00
commit 75ed7336e4
32 changed files with 2105 additions and 0 deletions

11
.drp_config Normal file
View file

@ -0,0 +1,11 @@
SHOULD_EXCLUDE_BE_ANONYMOUS:{
n
}
PORTFOLIO_LINK:{
HAHA NOT REAL LINK
}
EXCLUDE_CHARACTERS_LIST:{
no_drp
}

64
.github/workflows/github-buid.yml vendored Normal file
View file

@ -0,0 +1,64 @@
on: [push, pull_request]
name: Continuous integration
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: check
test:
name: Test Suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: test
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: rustup component add clippy
- uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

4
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,4 @@
{
"discord.enabled": false,
"rust-analyzer.files.excludeDirs": [".drp_config"]
}

1018
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

23
Cargo.toml Normal file
View file

@ -0,0 +1,23 @@
[package]
name = "DRP_Creative"
version = "0.1.1"
edition = "2021"
author = "https://djkato.net"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
discord-rich-presence = "0.2.2"
regex = "1.6.0"
tray-item = "0.7.0"
[dependencies.windows]
version = "0.39.0"
features = [
"Data_Xml_Dom",
"Win32_Foundation",
"Win32_Security",
"Win32_System_Threading",
"Win32_UI_WindowsAndMessaging",
]
[build-dependencies]
windres = "*"

36
README.md Normal file
View file

@ -0,0 +1,36 @@
# Discord Rich Presence for Creative Apps
### Show your friends what you're working on, be it in Adobe Suite, Autodesk Suite, Cinema 4D or many more!
This app runs in the background and looks for processes, then parses the windows title and turns it into a project name to display with Discords Rich Presence.
##how to install:
1. Grab the latest version from the [Releases](https://github.com) page, then make a folder in `C:/Program Files/` and call it whatever, for example `DRP Creative`.
2. Put and extract the downloaded zip inside.
3. Do `[WindowsButton] + R`, type `Shell:StartUp`, hit enter. A folder will open.
4. Put the shortcut file of `DRC Creative.vbs` from the extracted zip inside this folder(Now the app will start on pc start)
5. If you want to run it now, double click the shortcut
6. Enjoy!
##How to use:
When it's running, you'll notice a new icon appear on your Taskbar. If you want to exclude the **currently open project** from showing up, click on the icon and click on `Don't show current project`.
![text](https://i.imgur.com/nADffGB.png)
If you want to revert these settings, you can go to the folder you put the exe in (example was `C:/Program Files/DRP Creative`)
###Currently supported programs:
**Full support:**
- Cinema 4D
- Adobe After Effects
- Autodesk Maya
**Partial support:** *(meaning it shows up on Discord, but doesn't display the project name)*
- Davinci Resolve
- Illustrator
- Photoshop
- Isotropix Clarisse
- Marvelous Designer
- Calvary
- Ableton
- FL Studio
- Autodesk 3Ds Max
- Adobe Premiere Pro

5
build.rs Normal file
View file

@ -0,0 +1,5 @@
use windres::Build;
fn main() {
Build::new().compile("./icon/tray-icon.rc").unwrap();
}

335
icon/icon.ai Normal file

File diff suppressed because one or more lines are too long

BIN
icon/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
icon/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

2
icon/tray-icon.rc Normal file
View file

@ -0,0 +1,2 @@
id ICON "icon.ico"
drp-icon ICON "icon.ico"

BIN
processes.txt Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

BIN
program icons/ableton.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
program icons/ae.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
program icons/cavalry.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
program icons/fl logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

BIN
program icons/pr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

269
src/app.rs Normal file
View file

@ -0,0 +1,269 @@
#[derive(Debug)]
pub struct App {
pub kind: AppKind,
pub default_project_name: String,
pub drp_client_id: String,
pub process_search_string: String,
pub config_search_string: String,
}
#[derive(Debug, PartialEq, Eq)]
pub enum AppKind {
C4d,
Maya,
ThreeDsMax,
AfterEffects,
PremierePro,
Illustrator,
Photoshop,
Indesign,
DavinciResolve,
Clarisse,
Marvelous,
Calvary,
Ableton,
FL,
}
pub struct Apps {
C4d: App,
Maya: App,
ThreeDsMax: App,
AfterEffects: App,
PremierePro: App,
Illustrator: App,
Photoshop: App,
Indesign: App,
DavinciResolve: App,
Clarisse: App,
Marvelous: App,
Calvary: App,
Ableton: App,
FL: App,
}
impl Apps {
pub fn as_iter(&self) -> [&App; 14] {
let APPS: [&App; 14] = [
&self.C4d,
&self.Maya,
&self.ThreeDsMax,
&self.AfterEffects,
&self.PremierePro,
&self.Illustrator,
&self.Photoshop,
&self.Indesign,
&self.DavinciResolve,
&self.Clarisse,
&self.Marvelous,
&self.Calvary,
&self.Ableton,
&self.FL,
];
APPS
}
}
impl AppKind {
pub fn as_iter() -> [AppKind; 14] {
let APPKINDS: [AppKind; 14] = [
AppKind::C4d,
AppKind::Maya,
AppKind::ThreeDsMax,
AppKind::AfterEffects,
AppKind::PremierePro,
AppKind::Illustrator,
AppKind::Photoshop,
AppKind::Indesign,
AppKind::DavinciResolve,
AppKind::Clarisse,
AppKind::Marvelous,
AppKind::Calvary,
AppKind::Ableton,
AppKind::FL,
];
APPKINDS
}
}
impl Apps {
pub fn construct_apps() -> Apps {
Apps {
C4d: App {
default_project_name: "Cinema 4D Project".to_string(),
drp_client_id: "936296341250904065".to_string(),
process_search_string: "Cinema 4D R".to_string(),
config_search_string: "c4d".to_string(),
kind: AppKind::C4d,
},
Maya: App {
default_project_name: "Autodesk Maya Project".to_string(),
drp_client_id: "1016369550276694106".to_string(),
process_search_string: "Autodesk Maya".to_string(),
config_search_string: "maya".to_string(),
kind: AppKind::Maya,
},
ThreeDsMax: App {
default_project_name: "3ds Max Project".to_string(),
drp_client_id: "1016395801402036315".to_string(),
process_search_string: " Autodesk 3ds Max".to_string(),
config_search_string: "threedsmax".to_string(),
kind: AppKind::ThreeDsMax,
},
AfterEffects: App {
default_project_name: "After Effects Project".to_string(),
drp_client_id: "1016395319522623539".to_string(),
process_search_string: "Adobe After Effects".to_string(),
config_search_string: "after_effects".to_string(),
kind: AppKind::AfterEffects,
},
PremierePro: App {
default_project_name: "Premiere Pro Project".to_string(),
drp_client_id: "1016396017832300555".to_string(),
process_search_string: "Adobe Premiere Pro".to_string(),
config_search_string: "premiere_pro".to_string(),
kind: AppKind::PremierePro,
},
Illustrator: App {
default_project_name: "Illustrator Project".to_string(),
drp_client_id: "1017491707819991071".to_string(),
process_search_string: "Adobe Illustrator".to_string(),
config_search_string: "illustrator".to_string(),
kind: AppKind::Illustrator,
},
Photoshop: App {
default_project_name: "Photoshop Project".to_string(),
drp_client_id: "1017493347415371917".to_string(),
process_search_string: "Photoshop".to_string(),
config_search_string: "photoshop".to_string(),
kind: AppKind::Photoshop,
},
Indesign: App {
default_project_name: "Indesign Project".to_string(),
drp_client_id: "1017493794456862781".to_string(),
process_search_string: "Indesign".to_string(),
config_search_string: "indesign".to_string(),
kind: AppKind::Indesign,
},
DavinciResolve: App {
default_project_name: "Davinci Resolve Project".to_string(),
drp_client_id: "1016371757722128516".to_string(),
process_search_string: "Davinci Resolve".to_string(),
config_search_string: "davinci_resolve".to_string(),
kind: AppKind::DavinciResolve,
},
Clarisse: App {
default_project_name: "Isotropix Clarisse Project".to_string(),
drp_client_id: "1016372248128532500".to_string(),
process_search_string: "Isotropix Clarisse".to_string(),
config_search_string: "clarisse".to_string(),
kind: AppKind::Clarisse,
},
Marvelous: App {
default_project_name: "Marvelous Designer Project".to_string(),
drp_client_id: "1016372646046351423".to_string(),
process_search_string: "Marvelous Designer".to_string(),
config_search_string: "marvelous".to_string(),
kind: AppKind::Marvelous,
},
Calvary: App {
default_project_name: "Cavalry Project".to_string(),
drp_client_id: "1016374130037243974".to_string(),
process_search_string: "calvary.exe".to_string(),
config_search_string: "calvary".to_string(),
kind: AppKind::Calvary,
},
Ableton: App {
default_project_name: "Ableton Project".to_string(),
drp_client_id: "1016375030227161150".to_string(),
process_search_string: "Ableton Live".to_string(),
config_search_string: "ableton".to_string(),
kind: AppKind::Ableton,
},
FL: App {
default_project_name: "FL Studio Project".to_string(),
drp_client_id: "1016393561140375712".to_string(),
process_search_string: "FL Studio".to_string(),
config_search_string: "fl".to_string(),
kind: AppKind::FL,
},
}
}
}
impl Apps {
pub fn find_app(&self, str: &String) -> Option<&App> {
for app in self.as_iter() {
if str.contains(&app.process_search_string) {
return Some(&app);
}
}
None
}
pub fn find_config_app(&self, str: &str) -> Option<&App> {
for app in self.as_iter() {
if str == app.config_search_string {
return Some(app);
}
}
return None;
}
}
impl App {
pub fn parse(&self, window_title: &String) -> String {
match self.kind {
AppKind::C4d => {
let mut end_i: usize = window_title.len();
let mut start_i: usize = 0;
for (i, char) in window_title.chars().enumerate() {
if char == '[' {
start_i = i;
match window_title.find(".c4d]") {
Some(i) => end_i = i as usize,
None => match window_title.find(" - Main") {
Some(i) => end_i = i as usize - 1,
None => end_i = window_title.len() - 1,
},
};
}
}
return window_title[start_i + 1..end_i].to_string();
}
AppKind::Maya => {
let match_index = match window_title.as_str().find(".mb* - Autodesk Maya") {
Some(i) => i,
None => match window_title.as_str().find(".mb - Autodesk Maya") {
Some(i) => i + 4,
None => {
return String::from("Autodesk Maya Project");
}
},
};
return window_title[..match_index as usize].to_string();
}
AppKind::AfterEffects => {
let mut proj_name = String::new();
for i in (0..window_title.len() - 2).rev() {
if window_title.chars().nth(i).unwrap() == '\\' {
for char in window_title.chars().skip(i + 1) {
proj_name.push(char);
}
return proj_name;
}
}
if proj_name == "" {
proj_name = window_title.clone();
}
proj_name
}
AppKind::ThreeDsMax => self.default_project_name.clone(),
AppKind::PremierePro => self.default_project_name.clone(),
AppKind::Illustrator => self.default_project_name.clone(),
AppKind::Photoshop => self.default_project_name.clone(),
AppKind::Indesign => self.default_project_name.clone(),
AppKind::DavinciResolve => self.default_project_name.clone(),
AppKind::Clarisse => self.default_project_name.clone(),
AppKind::Marvelous => self.default_project_name.clone(),
AppKind::Calvary => self.default_project_name.clone(),
AppKind::Ableton => self.default_project_name.clone(),
AppKind::FL => self.default_project_name.clone(),
}
}
}

117
src/config.rs Normal file
View file

@ -0,0 +1,117 @@
use std::fs;
#[derive(Debug)]
pub struct Config {
pub exclude_keywords_list: Vec<String>,
pub should_exclude_be_invisible: bool,
pub portfolio_link: String,
}
impl Config {
pub fn load() -> Config {
let default_config = use_default_config();
//if no config file, parse default config file and try save it, then use it
let config_file: Config = match fs::read_to_string(".drp_config") {
Result::Err(_err) => {
let def_config = parse_config_file(&default_config);
make_config_file(&def_config);
def_config
}
Result::Ok(val) => parse_config_file(&val),
};
config_file
}
}
impl Config {
pub fn exclude_project(&mut self, project_name: &String) {
if self.exclude_keywords_list.contains(&project_name) {
return;
}
self.exclude_keywords_list.push(project_name.clone());
make_config_file(&self);
}
}
fn parse_config_file(config_file: &String) -> Config {
let mut exclude_keywords_list: Vec<String> = Vec::new();
let mut should_exclude_be_invisible = false;
let mut portfolio_link = String::new();
for (i, line) in config_file.lines().enumerate() {
if line.contains("SHOULD_EXCLUDE_BE_ANONYMOUS:") {
match config_file
.lines()
.nth(i + 1)
.expect("index out of bounds")
.to_lowercase()
.as_str()
{
"n" => should_exclude_be_invisible = false,
"no" => should_exclude_be_invisible = false,
"y" => should_exclude_be_invisible = true,
"yes" => should_exclude_be_invisible = true,
_ => should_exclude_be_invisible = false,
}
}
if line.contains("EXCLUDE_CHARACTERS_LIST:") {
for arg_line in config_file.lines().skip(i + 1) {
if arg_line.trim().contains("}") {
break;
}
exclude_keywords_list.push(arg_line.to_string())
}
}
if line.contains("PORTFOLIO_LINK:") {
for arg_line in config_file.lines().skip(i) {
if arg_line.trim().contains("}") {
break;
}
portfolio_link = arg_line.to_string();
}
}
}
Config {
exclude_keywords_list,
should_exclude_be_invisible,
portfolio_link,
}
}
fn make_config_file(config: &Config) {
let mut config_file = String::from("SHOULD_EXCLUDE_BE_ANONYMOUS:{");
config_file = config_file
+ match config.should_exclude_be_invisible {
true => "\ny\n}\n",
false => "\nn\n}\n",
};
config_file = config_file + "\nPORTFOLIO_LINK:{\ndjkato.net\n}\n";
config_file = config_file + "\nEXCLUDE_CHARACTERS_LIST:{";
for exclusive_word in &config.exclude_keywords_list {
config_file = config_file + format!("\n{}", exclusive_word).as_str();
}
config_file = config_file + "\n}\n";
match fs::write(".drp_config", config_file) {
_ => (),
}
}
fn use_default_config() -> String {
let str = String::from(
"SHOULD_EXCLUDE_BE_ANONYMOUS:{
n
}
PORTFOLIO_LINK:{
djkato.net
}
EXCLUDE_CHARACTERS_LIST:{
no_drp
}
",
);
str
}

124
src/main.rs Normal file
View file

@ -0,0 +1,124 @@
#![windows_subsystem = "windows"]
pub mod app;
pub mod config;
pub mod program_status;
pub mod tray_icon;
use crate::config::Config;
use crate::program_status::*;
use app::{App, Apps};
use discord_rich_presence::{activity, DiscordIpc, DiscordIpcClient};
use std::time::{SystemTime, UNIX_EPOCH};
use std::{thread, time};
fn main() {
let apps = Apps::construct_apps();
let mut config = Config::load();
let timeout = time::Duration::from_millis(5000);
let (tray_receiver, _tray) = tray_icon::create_tray();
let mut prev_project_name = String::new();
let mut project_name = String::new();
let mut start_time = 0;
let mut drp_is_running = false;
let mut discord_client: Option<DiscordIpcClient> = None;
let mut current_app_option: Option<&App> = None;
'main: loop {
if let Some(current_app) = current_app_option {
if let Some(real_project_name) = is_program_still_running(&current_app) {
//if project name includes filtered words, use default project name
for excluded_word in &config.exclude_keywords_list {
if real_project_name.contains(excluded_word.as_str()) {
project_name = current_app.default_project_name.clone();
} else {
project_name = real_project_name.clone();
}
}
//If discord client isn't connected create and conenct it
if !drp_is_running {
//make a new client with current_app ID
discord_client =
match DiscordIpcClient::new(&current_app.drp_client_id.as_str()) {
Ok(ok) => Some(ok),
Err(_err) => None,
};
if let Some(dc_client) = discord_client.as_mut() {
match dc_client.connect() {
Ok(_ok) => drp_is_running = true,
Err(_err) => drp_is_running = false,
}
} else {
panic!();
}
//only if new app is made refresh the start time
start_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("time went backwards")
.as_secs();
}
//if project name changed, update it
if prev_project_name != project_name {
prev_project_name = project_name.clone();
let details = format!("Working on {}", &project_name.clone());
//update activity
let state = format!("Portfolio: {}", &config.portfolio_link);
let activity = activity::Activity::new()
.state(state.as_str())
.details(&details.as_str())
.assets(
activity::Assets::new().large_image(current_app.drp_client_id.as_str()),
)
.timestamps(activity::Timestamps::new().start(start_time as i64));
//if discord client exists, update status
if let Some(dc) = discord_client.as_mut() {
match dc.set_activity(activity) {
_ => (),
}
}
}
}
//if program no longer running
else {
current_app_option = None;
prev_project_name = "".to_string();
}
//respond to tray icon messages
match tray_receiver.try_recv() {
Ok(msg) => match msg {
tray_icon::Message::AnonymiseProject => {
//only exclude project name if it's not the default one
for app in apps.as_iter() {
if app.kind == current_app.kind {
if app.default_project_name != project_name {
config.exclude_project(&project_name);
}
}
}
}
tray_icon::Message::Quit => break 'main,
},
Err(_err) => (),
}
} else {
//If nothing is running try to find it
if let Some(proj_name) = get_running_program(&apps) {
let temp_curr_app: &App;
(temp_curr_app, project_name) = proj_name;
current_app_option = Some(temp_curr_app);
} else {
current_app_option = None;
}
if drp_is_running {
match discord_client
.as_mut()
.expect("Somehow it dissapeared?")
.close()
{
Ok(_ok) => drp_is_running = false,
Err(_err) => (),
}
}
}
thread::sleep(timeout);
}
}

70
src/program_status.rs Normal file
View file

@ -0,0 +1,70 @@
use crate::app::{App, Apps};
use windows::Win32::{
Foundation::{BOOL, HWND, LPARAM},
UI::WindowsAndMessaging::{self, GetWindowTextA, GetWindowTextLengthA, WNDENUMPROC},
};
pub fn get_running_program(apps: &Apps) -> Option<(&App, String)> {
let running_window_names = unsafe { get_running_windows_titles() };
for window_name in running_window_names {
if let Some(app) = apps.find_app(&window_name) {
return Some((&app, app.parse(&window_name)));
}
}
return None;
}
pub fn is_program_still_running(app: &App) -> Option<String> {
let running_window_names = unsafe { get_running_windows_titles() };
for window_name in running_window_names {
//dbg!(&window_name);
if window_name.contains(&app.process_search_string) {
return Some(app.parse(&window_name));
}
}
return None;
}
unsafe fn get_running_windows_titles() -> Vec<String> {
let mut running_windows_names: Vec<String> = Vec::new();
unsafe extern "system" fn processhwd(hwnd: HWND, lparam: LPARAM) -> BOOL {
// Get the length of the window text
let window_text_len = GetWindowTextLengthA(hwnd);
if window_text_len < 0 {
panic!("Uh oh, it went wrong.");
}
// Make a buffer for the window text (+ 1 for terminating NUL byte)
let mut window_text_buffer = vec![0_u8; window_text_len as usize + 1];
// Get the window text. For understanding how to deal with the value in
// `result`, see the MSDN documentation.
GetWindowTextA(hwnd, &mut window_text_buffer);
//turn ascii into characters
let mut window_text: Vec<char> = Vec::new();
for char in window_text_buffer {
window_text.push(char as char);
}
//turn characters into strings
let window_text = String::from_iter(window_text.iter());
//turn vector into pointer and push window_texts to it
let running_windows_names_pointer = lparam.0 as *mut Vec<String>;
running_windows_names_pointer
.as_mut()
.expect("welp the pointer failed...")
.push(window_text);
BOOL(1)
}
let lpenumfunc: WNDENUMPROC =
Some(processhwd as unsafe extern "system" fn(hwnd: HWND, lparam: LPARAM) -> BOOL);
let windows_names_ptr: *mut Vec<String> = &mut running_windows_names;
WindowsAndMessaging::EnumWindows(lpenumfunc, LPARAM(windows_names_ptr as isize));
running_windows_names
}

26
src/tray_icon.rs Normal file
View file

@ -0,0 +1,26 @@
use std::sync::mpsc::Receiver;
use {std::sync::mpsc, tray_item::TrayItem};
pub enum Message {
Quit,
AnonymiseProject,
}
pub fn create_tray() -> (Receiver<Message>, TrayItem) {
let mut tray = TrayItem::new("DRP Creative", "drp-icon").unwrap();
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();
tray.add_label("DRP Creative Options").unwrap();
tray.add_menu_item("Don't show current project", move || {
tx.send(Message::AnonymiseProject).unwrap();
})
.unwrap();
tray.add_menu_item("Quit", move || {
tx1.send(Message::Quit).unwrap();
})
.unwrap();
return (rx, tray);
}