first commit
11
.drp_config
Normal 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
|
@ -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
|
@ -0,0 +1 @@
|
||||||
|
/target
|
4
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"discord.enabled": false,
|
||||||
|
"rust-analyzer.files.excludeDirs": [".drp_config"]
|
||||||
|
}
|
1018
Cargo.lock
generated
Normal file
23
Cargo.toml
Normal 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
|
@ -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`.
|
||||||
|

|
||||||
|
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
|
@ -0,0 +1,5 @@
|
||||||
|
use windres::Build;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
Build::new().compile("./icon/tray-icon.rc").unwrap();
|
||||||
|
}
|
335
icon/icon.ai
Normal file
BIN
icon/icon.ico
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
icon/icon.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
2
icon/tray-icon.rc
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
id ICON "icon.ico"
|
||||||
|
drp-icon ICON "icon.ico"
|
BIN
processes.txt
Normal file
BIN
program icons/Adobe_Illustrator_CC_icon.svg.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
program icons/Adobe_InDesign_CC_icon.svg.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
program icons/Adobe_Photoshop_CC_icon.svg.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
program icons/DaVinci_Resolve_17_logo.svg.png
Normal file
After Width: | Height: | Size: 128 KiB |
BIN
program icons/Isotropix-Clarisse.png
Normal file
After Width: | Height: | Size: 116 KiB |
BIN
program icons/ableton.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
program icons/ae.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
program icons/autodesk-maya-logo.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
program icons/cavalry.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
program icons/download (1).png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
program icons/fl logo.png
Normal file
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 74 KiB |
BIN
program icons/pngwing.com (1).png
Normal file
After Width: | Height: | Size: 184 KiB |
BIN
program icons/pr.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
269
src/app.rs
Normal 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
|
@ -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
|
@ -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(¤t_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(¤t_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
|
@ -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
|
@ -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);
|
||||||
|
}
|