mirror of
https://github.com/djibux/BoilR.git
synced 2026-09-01 05:53:41 +02:00
UI image selection (#97)
This commit is contained in:
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
/target
|
/target
|
||||||
auth_key.txt
|
auth_key.txt
|
||||||
cache.json
|
cache.json
|
||||||
config.toml
|
config.toml
|
||||||
|
.thumbnails
|
||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 8.4 KiB |
@@ -1,11 +1,13 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
#[cfg(windows)] use winres::WindowsResource;
|
#[cfg(windows)]
|
||||||
|
use winres::WindowsResource;
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
fn main() -> io::Result<()> {
|
||||||
#[cfg(windows)] {
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
WindowsResource::new()
|
WindowsResource::new()
|
||||||
.set_icon("resources/logo.ico")
|
.set_icon("resources/logo.ico")
|
||||||
.compile()?;
|
.compile()?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,14 @@
|
|||||||
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
|
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AmazonGame{
|
pub struct AmazonGame {
|
||||||
pub title : String,
|
pub title: String,
|
||||||
pub id : String
|
pub id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl From<AmazonGame> for ShortcutOwned {
|
impl From<AmazonGame> for ShortcutOwned {
|
||||||
fn from(game: AmazonGame) -> Self {
|
fn from(game: AmazonGame) -> Self {
|
||||||
let launch = format!("amazon-games://play/{}", game.id);
|
let launch = format!("amazon-games://play/{}", game.id);
|
||||||
Shortcut::new(
|
Shortcut::new("0", game.title.as_str(), launch.as_str(), "", "", "", "").to_owned()
|
||||||
"0",
|
|
||||||
game.title.as_str(),
|
|
||||||
launch.as_str(),
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
.to_owned()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,25 @@
|
|||||||
use std::{error::Error, path::{PathBuf, Path}};
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
use sqlite::State;
|
use sqlite::State;
|
||||||
|
|
||||||
use crate::platform::Platform;
|
use crate::platform::Platform;
|
||||||
|
|
||||||
use super::{AmazonSettings, AmazonGame};
|
use super::{AmazonGame, AmazonSettings};
|
||||||
|
|
||||||
|
pub struct AmazonPlatform {
|
||||||
pub struct AmazonPlatform{
|
pub settings: AmazonSettings,
|
||||||
pub settings:AmazonSettings
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Platform<AmazonGame, Box<dyn Error>> for AmazonPlatform{
|
impl Platform<AmazonGame, Box<dyn Error>> for AmazonPlatform {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn enabled(&self) -> bool {
|
fn enabled(&self) -> bool {
|
||||||
self.settings.enabled
|
self.settings.enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_family="unix")]
|
#[cfg(target_family = "unix")]
|
||||||
fn enabled(&self) -> bool {
|
fn enabled(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -27,14 +29,16 @@ impl Platform<AmazonGame, Box<dyn Error>> for AmazonPlatform{
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_shortcuts(&self) -> Result<Vec<AmazonGame>, Box<dyn Error>> {
|
fn get_shortcuts(&self) -> Result<Vec<AmazonGame>, Box<dyn Error>> {
|
||||||
let sqllite_path = get_sqlite_path().expect("This should enver get called if settings are invalid");
|
let sqllite_path =
|
||||||
|
get_sqlite_path().expect("This should enver get called if settings are invalid");
|
||||||
let mut result = vec![];
|
let mut result = vec![];
|
||||||
let connection = sqlite::open(sqllite_path)?;
|
let connection = sqlite::open(sqllite_path)?;
|
||||||
let mut statement = connection.prepare("SELECT Id, ProductTitle FROM DbSet WHERE Installed = 1")?;
|
let mut statement =
|
||||||
|
connection.prepare("SELECT Id, ProductTitle FROM DbSet WHERE Installed = 1")?;
|
||||||
while let State::Row = statement.next().unwrap() {
|
while let State::Row = statement.next().unwrap() {
|
||||||
let id = statement.read::<String>(0);
|
let id = statement.read::<String>(0);
|
||||||
let title = statement.read::<String>(1);
|
let title = statement.read::<String>(1);
|
||||||
if let (Ok(id),Ok(title)) = (id,title){
|
if let (Ok(id), Ok(title)) = (id, title) {
|
||||||
result.push(AmazonGame { title, id });
|
result.push(AmazonGame { title, id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,15 +47,16 @@ impl Platform<AmazonGame, Box<dyn Error>> for AmazonPlatform{
|
|||||||
|
|
||||||
fn settings_valid(&self) -> crate::platform::SettingsValidity {
|
fn settings_valid(&self) -> crate::platform::SettingsValidity {
|
||||||
let path = get_sqlite_path();
|
let path = get_sqlite_path();
|
||||||
if path.is_some(){
|
if path.is_some() {
|
||||||
crate::platform::SettingsValidity::Valid
|
crate::platform::SettingsValidity::Valid
|
||||||
}else{
|
} else {
|
||||||
crate::platform::SettingsValidity::Invalid { reason: format!("Could not find Amazon Games installation")}
|
crate::platform::SettingsValidity::Invalid {
|
||||||
|
reason: "Could not find Amazon Games installation".to_string(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_family ="unix")]
|
#[cfg(target_family = "unix")]
|
||||||
fn create_symlinks(&self) -> bool {
|
fn create_symlinks(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
@@ -61,17 +66,21 @@ impl Platform<AmazonGame, Box<dyn Error>> for AmazonPlatform{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn get_sqlite_path() -> Option<PathBuf> {
|
fn get_sqlite_path() -> Option<PathBuf> {
|
||||||
match std::env::var("LOCALAPPDATA") {
|
match std::env::var("LOCALAPPDATA") {
|
||||||
Ok(localdata) => {
|
Ok(localdata) => {
|
||||||
let path = Path::new(&localdata).join("Amazon Games").join("Data").join("Games").join("Sql").join("GameInstallInfo.sqlite");
|
let path = Path::new(&localdata)
|
||||||
|
.join("Amazon Games")
|
||||||
|
.join("Data")
|
||||||
|
.join("Games")
|
||||||
|
.join("Sql")
|
||||||
|
.join("GameInstallInfo.sqlite");
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
Some(path)
|
Some(path)
|
||||||
}else{
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_e) => None,
|
Err(_e) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct AmazonSettings{
|
pub struct AmazonSettings {
|
||||||
pub enabled:bool
|
pub enabled: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -5,4 +5,4 @@ pub mod amazon_game;
|
|||||||
pub use amazon_game::*;
|
pub use amazon_game::*;
|
||||||
|
|
||||||
pub mod amazon_settings;
|
pub mod amazon_settings;
|
||||||
pub use amazon_settings::*;
|
pub use amazon_settings::*;
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ pub struct EpicPlatform {
|
|||||||
|
|
||||||
impl EpicPlatform {
|
impl EpicPlatform {
|
||||||
pub fn new(settings: &EpicGamesLauncherSettings) -> Self {
|
pub fn new(settings: &EpicGamesLauncherSettings) -> Self {
|
||||||
EpicPlatform { settings: settings.clone() }
|
EpicPlatform {
|
||||||
|
settings: settings.clone(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,12 +40,24 @@ pub(crate) fn get_egs_manifests(
|
|||||||
.filter_map(|dir| dir.ok())
|
.filter_map(|dir| dir.ok())
|
||||||
.filter_map(get_manifest_item)
|
.filter_map(get_manifest_item)
|
||||||
.filter(is_game_installed)
|
.filter(is_game_installed)
|
||||||
.filter(is_game_launchable);
|
.filter(is_game_launchable);
|
||||||
let mut manifests : Vec<ManifestItem> = manifests.collect();
|
let mut manifests: Vec<ManifestItem> = manifests.collect();
|
||||||
manifests.sort_by_key(|m| format!("{}-{}-{}",m.install_location,m.launch_executable,m.is_managed));
|
manifests.sort_by_key(|m| {
|
||||||
manifests.dedup_by_key(|m| format!("{}-{}-{}",m.install_location,m.launch_executable,m.is_managed));
|
format!(
|
||||||
for mut manifest in &mut manifests{
|
"{}-{}-{}",
|
||||||
if settings.safe_launch.contains(&manifest.display_name) || settings.safe_launch.contains(&manifest.get_key()){
|
m.install_location, m.launch_executable, m.is_managed
|
||||||
|
)
|
||||||
|
});
|
||||||
|
manifests.dedup_by_key(|m| {
|
||||||
|
format!(
|
||||||
|
"{}-{}-{}",
|
||||||
|
m.install_location, m.launch_executable, m.is_managed
|
||||||
|
)
|
||||||
|
});
|
||||||
|
for mut manifest in &mut manifests {
|
||||||
|
if settings.safe_launch.contains(&manifest.display_name)
|
||||||
|
|| settings.safe_launch.contains(&manifest.get_key())
|
||||||
|
{
|
||||||
manifest.safe_launch = true;
|
manifest.safe_launch = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,7 +70,6 @@ pub(crate) fn get_egs_manifests(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn get_manifest_dir_path(
|
fn get_manifest_dir_path(
|
||||||
settings: &EpicGamesLauncherSettings,
|
settings: &EpicGamesLauncherSettings,
|
||||||
) -> Result<String, EpicGamesManifestsError> {
|
) -> Result<String, EpicGamesManifestsError> {
|
||||||
@@ -113,7 +124,7 @@ fn location_from_registry() -> Option<PathBuf> {
|
|||||||
if let Ok(path_string) = path_string {
|
if let Ok(path_string) = path_string {
|
||||||
let path = Path::new(&path_string).join("Manifests");
|
let path = Path::new(&path_string).join("Manifests");
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
return Some(path.to_path_buf());
|
return Some(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,7 +143,7 @@ fn guess_default_location() -> PathBuf {
|
|||||||
.join("EpicGamesLauncher")
|
.join("EpicGamesLauncher")
|
||||||
.join("Data")
|
.join("Data")
|
||||||
.join("Manifests");
|
.join("Manifests");
|
||||||
path.to_path_buf()
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_game_installed(manifest: &ManifestItem) -> bool {
|
fn is_game_installed(manifest: &ManifestItem) -> bool {
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ impl ManifestItem {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_key(&self) -> String{
|
pub fn get_key(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{}-{}-{}",
|
"{}-{}-{}",
|
||||||
self.catalog_namespace, self.catalog_item_id, self.app_name
|
self.catalog_namespace, self.catalog_item_id, self.app_name
|
||||||
@@ -108,7 +108,7 @@ impl ManifestItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn needs_launcher(&self) -> bool {
|
fn needs_launcher(&self) -> bool {
|
||||||
if self.safe_launch{
|
if self.safe_launch {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
match (&self.is_managed, &self.expected_dlc) {
|
match (&self.is_managed, &self.expected_dlc) {
|
||||||
@@ -157,7 +157,7 @@ mod tests {
|
|||||||
let mut manifest: ManifestItem = serde_json::from_str(json).unwrap();
|
let mut manifest: ManifestItem = serde_json::from_str(json).unwrap();
|
||||||
manifest.is_managed = false;
|
manifest.is_managed = false;
|
||||||
manifest.expected_dlc = None;
|
manifest.expected_dlc = None;
|
||||||
let shortcut: ShortcutOwned = manifest.clone().into();
|
let shortcut: ShortcutOwned = manifest.into();
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
assert_eq!(shortcut.exe, "C:\\Games\\MarvelGOTG\\retail/gotg.exe");
|
assert_eq!(shortcut.exe, "C:\\Games\\MarvelGOTG\\retail/gotg.exe");
|
||||||
@@ -172,7 +172,7 @@ mod tests {
|
|||||||
let json = include_str!("example_item.json");
|
let json = include_str!("example_item.json");
|
||||||
let mut manifest: ManifestItem = serde_json::from_str(json).unwrap();
|
let mut manifest: ManifestItem = serde_json::from_str(json).unwrap();
|
||||||
manifest.is_managed = false;
|
manifest.is_managed = false;
|
||||||
let shortcut: ShortcutOwned = manifest.clone().into();
|
let shortcut: ShortcutOwned = manifest.into();
|
||||||
|
|
||||||
let expected ="com.epicgames.launcher://apps/2a09fb19b47f46dfb11ebd382f132a8f%3A88f4bb0bb06e4962a2042d5e20fb6ace%3A63a665088eb1480298f1e57943b225d8?action=launch&silent=true";
|
let expected ="com.epicgames.launcher://apps/2a09fb19b47f46dfb11ebd382f132a8f%3A88f4bb0bb06e4962a2042d5e20fb6ace%3A63a665088eb1480298f1e57943b225d8?action=launch&silent=true";
|
||||||
let actual = shortcut.exe;
|
let actual = shortcut.exe;
|
||||||
|
|||||||
+1
-1
@@ -8,5 +8,5 @@ pub struct EpicGamesLauncherSettings {
|
|||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
pub create_symlinks: bool,
|
pub create_symlinks: bool,
|
||||||
|
|
||||||
pub safe_launch : Vec<String>
|
pub safe_launch: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-5
@@ -24,7 +24,6 @@ pub(crate) struct PlayTask {
|
|||||||
#[serde(alias = "workingDir")]
|
#[serde(alias = "workingDir")]
|
||||||
pub working_dir: Option<String>,
|
pub working_dir: Option<String>,
|
||||||
pub arguments: Option<String>,
|
pub arguments: Option<String>,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -48,13 +47,13 @@ impl From<GogShortcut> for ShortcutOwned {
|
|||||||
exe.to_str().unwrap_or("").to_string()
|
exe.to_str().unwrap_or("").to_string()
|
||||||
};
|
};
|
||||||
let mut exe_string = exe.to_string_lossy().to_string();
|
let mut exe_string = exe.to_string_lossy().to_string();
|
||||||
if exe_string.contains(" ") && !exe_string.starts_with("\""){
|
if exe_string.contains(' ') && !exe_string.starts_with('\"') {
|
||||||
exe_string = format!("\"{}\"",exe_string);
|
exe_string = format!("\"{}\"", exe_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut working_dir_string = gogs.working_dir;
|
let mut working_dir_string = gogs.working_dir;
|
||||||
if working_dir_string.contains(" ") && !working_dir_string.starts_with("\""){
|
if working_dir_string.contains(' ') && !working_dir_string.starts_with('\"') {
|
||||||
working_dir_string = format!("\"{}\"",working_dir_string);
|
working_dir_string = format!("\"{}\"", working_dir_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
let shortcut = Shortcut::new(
|
let shortcut = Shortcut::new(
|
||||||
|
|||||||
+21
-18
@@ -12,12 +12,13 @@ pub struct GogPlatform {
|
|||||||
pub settings: GogSettings,
|
pub settings: GogSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_shortcuts_from_config(
|
||||||
|
_wine_c_drive: Option<String>,
|
||||||
pub fn get_shortcuts_from_config(wine_c_drive : Option<String>, config_path: PathBuf) -> Result<Vec<GogShortcut>, GogErrors> {
|
config_path: PathBuf,
|
||||||
|
) -> Result<Vec<GogShortcut>, GogErrors> {
|
||||||
let install_locations = get_install_locations(config_path)?;
|
let install_locations = get_install_locations(config_path)?;
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
let install_locations = if let Some(wine_c_drive) = &wine_c_drive {
|
let install_locations = if let Some(wine_c_drive) = &_wine_c_drive {
|
||||||
fix_paths(wine_c_drive, install_locations)
|
fix_paths(wine_c_drive, install_locations)
|
||||||
} else {
|
} else {
|
||||||
install_locations
|
install_locations
|
||||||
@@ -44,14 +45,13 @@ pub fn get_shortcuts_from_config(wine_c_drive : Option<String>, config_path: Pat
|
|||||||
|
|
||||||
pub fn get_shortcuts_from_game_folders(game_folders: Vec<PathBuf>) -> Vec<GogShortcut> {
|
pub fn get_shortcuts_from_game_folders(game_folders: Vec<PathBuf>) -> Vec<GogShortcut> {
|
||||||
let games = get_games_from_game_folders(game_folders);
|
let games = get_games_from_game_folders(game_folders);
|
||||||
let shortcuts = get_shortcuts_from_games(games);
|
|
||||||
shortcuts
|
get_shortcuts_from_games(games)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_shortcuts_from_games(games: Vec<(GogGame, PathBuf)>) -> Vec<GogShortcut> {
|
fn get_shortcuts_from_games(games: Vec<(GogGame, PathBuf)>) -> Vec<GogShortcut> {
|
||||||
let mut shortcuts = vec![];
|
let mut shortcuts = vec![];
|
||||||
for (game, game_folder) in games {
|
for (game, game_folder) in games {
|
||||||
|
|
||||||
if let Some(folder_path) = game_folder.to_str() {
|
if let Some(folder_path) = game_folder.to_str() {
|
||||||
if let Some(tasks) = &game.play_tasks {
|
if let Some(tasks) = &game.play_tasks {
|
||||||
if let Some(primary_task) = tasks.iter().find(|t| {
|
if let Some(primary_task) = tasks.iter().find(|t| {
|
||||||
@@ -68,19 +68,23 @@ fn get_shortcuts_from_games(games: Vec<(GogGame, PathBuf)>) -> Vec<GogShortcut>
|
|||||||
Some(working_dir) => game_folder
|
Some(working_dir) => game_folder
|
||||||
.join(working_dir)
|
.join(working_dir)
|
||||||
.to_str()
|
.to_str()
|
||||||
.unwrap_or_else(|| folder_path.as_str())
|
.unwrap_or(folder_path.as_str())
|
||||||
.to_string(),
|
.to_string(),
|
||||||
None => folder_path.to_string(),
|
None => folder_path.to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
let working_dir = working_dir.replace("\\", "/");
|
let working_dir = working_dir.replace('\\', "/");
|
||||||
|
|
||||||
let full_path_string = full_path.to_string();
|
let full_path_string = full_path.to_string();
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
let full_path_string = full_path_string.replace("\\", "/");
|
let full_path_string = full_path_string.replace('\\', "/");
|
||||||
let arguments = primary_task.arguments.as_ref().unwrap_or(&"".to_string()).clone();
|
let arguments = primary_task
|
||||||
|
.arguments
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&"".to_string())
|
||||||
|
.clone();
|
||||||
let shortcut = GogShortcut {
|
let shortcut = GogShortcut {
|
||||||
name: game.name,
|
name: game.name,
|
||||||
game_folder: folder_path,
|
game_folder: folder_path,
|
||||||
@@ -133,7 +137,6 @@ fn get_games_from_game_folders(game_folders: Vec<PathBuf>) -> Vec<(GogGame, Path
|
|||||||
games
|
games
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Platform<GogShortcut, GogErrors> for GogPlatform {
|
impl Platform<GogShortcut, GogErrors> for GogPlatform {
|
||||||
fn enabled(&self) -> bool {
|
fn enabled(&self) -> bool {
|
||||||
self.settings.enabled
|
self.settings.enabled
|
||||||
@@ -162,7 +165,7 @@ impl Platform<GogShortcut, GogErrors> for GogPlatform {
|
|||||||
if !config_path.exists() {
|
if !config_path.exists() {
|
||||||
return Err(GogErrors::ConfigFileNotFound { path: config_path });
|
return Err(GogErrors::ConfigFileNotFound { path: config_path });
|
||||||
}
|
}
|
||||||
get_shortcuts_from_config(self.settings.wine_c_drive.clone(),config_path)
|
get_shortcuts_from_config(self.settings.wine_c_drive.clone(), config_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn settings_valid(&self) -> crate::platform::SettingsValidity {
|
fn settings_valid(&self) -> crate::platform::SettingsValidity {
|
||||||
@@ -177,10 +180,10 @@ impl Platform<GogShortcut, GogErrors> for GogPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn needs_proton(&self, _input: &GogShortcut) -> bool {
|
fn needs_proton(&self, _input: &GogShortcut) -> bool {
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
return true;
|
return true;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +194,7 @@ fn fix_paths(wine_c_drive: &str, paths: Vec<String>) -> Vec<String> {
|
|||||||
.flat_map(|path| {
|
.flat_map(|path| {
|
||||||
if let Some(stripped) = path.strip_prefix("C:\\") {
|
if let Some(stripped) = path.strip_prefix("C:\\") {
|
||||||
let path_buf = Path::new(wine_c_drive).join(stripped);
|
let path_buf = Path::new(wine_c_drive).join(stripped);
|
||||||
path_buf.to_str().map(|s| s.to_string().replace("\\", "/"))
|
path_buf.to_str().map(|s| s.to_string().replace('\\', "/"))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -3,6 +3,6 @@ mod gog_game;
|
|||||||
mod gog_platform;
|
mod gog_platform;
|
||||||
mod gog_settings;
|
mod gog_settings;
|
||||||
|
|
||||||
|
pub use gog_game::GogShortcut;
|
||||||
pub use gog_platform::*;
|
pub use gog_platform::*;
|
||||||
pub use gog_settings::*;
|
pub use gog_settings::*;
|
||||||
pub use gog_game::GogShortcut;
|
|
||||||
|
|||||||
@@ -13,23 +13,23 @@ pub struct HeroicGame {
|
|||||||
pub launch_parameters: String,
|
pub launch_parameters: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HeroicGame {
|
||||||
|
pub fn is_installed(&self) -> bool {
|
||||||
impl HeroicGame{
|
Path::new(&self.install_path)
|
||||||
pub fn is_installed(&self) -> bool{
|
.join(&self.executable)
|
||||||
Path::new(&self.install_path).join(&self.executable).exists()
|
.exists()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<HeroicGame> for ShortcutOwned {
|
impl From<HeroicGame> for ShortcutOwned {
|
||||||
fn from(game: HeroicGame) -> Self {
|
fn from(game: HeroicGame) -> Self {
|
||||||
let target_path = Path::new(&game.install_path).join(game.executable);
|
let target_path = Path::new(&game.install_path).join(game.executable);
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
let mut target = target_path.to_string_lossy().to_string();
|
let mut target = target_path.to_string_lossy().to_string();
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
{
|
{
|
||||||
if !target.starts_with("\"") && !target.ends_with("\"") {
|
if !target.starts_with('\"') && !target.ends_with('\"') {
|
||||||
target = format!("\"{}\"", target);
|
target = format!("\"{}\"", target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,12 +38,12 @@ impl From<HeroicGame> for ShortcutOwned {
|
|||||||
let mut install_path = game.install_path.to_string();
|
let mut install_path = game.install_path.to_string();
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
{
|
{
|
||||||
if !install_path.starts_with("\"") && !install_path.ends_with("\"") {
|
if !install_path.starts_with('\"') && !install_path.ends_with('\"') {
|
||||||
install_path = format!("\"{}\"", install_path);
|
install_path = format!("\"{}\"", install_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
let install_path = game.install_path.to_string();
|
let install_path = game.install_path.to_string();
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
let target = target_path.to_string_lossy().to_string();
|
let target = target_path.to_string_lossy().to_string();
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
use steam_shortcuts_util::shortcut::ShortcutOwned;
|
use steam_shortcuts_util::shortcut::ShortcutOwned;
|
||||||
|
|
||||||
use crate::gog::GogShortcut;
|
|
||||||
use super::HeroicGame;
|
use super::HeroicGame;
|
||||||
|
use crate::gog::GogShortcut;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum HeroicGameType{
|
pub enum HeroicGameType {
|
||||||
Epic(HeroicGame),
|
Epic(HeroicGame),
|
||||||
//The bool is if it is windows (true) or not (false)
|
//The bool is if it is windows (true) or not (false)
|
||||||
Gog(GogShortcut,bool)
|
Gog(GogShortcut, bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl From<HeroicGameType> for ShortcutOwned {
|
impl From<HeroicGameType> for ShortcutOwned {
|
||||||
fn from(heroic_game_type: HeroicGameType) -> Self {
|
fn from(heroic_game_type: HeroicGameType) -> Self {
|
||||||
match heroic_game_type{
|
match heroic_game_type {
|
||||||
HeroicGameType::Epic(epic) => epic.into(),
|
HeroicGameType::Epic(epic) => epic.into(),
|
||||||
HeroicGameType::Gog(gog,_) => gog.into(),
|
HeroicGameType::Gog(gog, _) => gog.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use serde::{Deserialize};
|
use serde::Deserialize;
|
||||||
|
|
||||||
use super::{HeroicGame, HeroicGameType, HeroicSettings};
|
use super::{HeroicGame, HeroicGameType, HeroicSettings};
|
||||||
use crate::gog::{ get_shortcuts_from_game_folders};
|
use crate::gog::get_shortcuts_from_game_folders;
|
||||||
use crate::platform::{Platform, SettingsValidity};
|
use crate::platform::{Platform, SettingsValidity};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
@@ -48,7 +48,7 @@ fn get_gog_installed_location(install_mode: &InstallationMode) -> PathBuf {
|
|||||||
InstallationMode::UserBin => {
|
InstallationMode::UserBin => {
|
||||||
Path::new(&home_dir).join(".config/heroic/gog_store/installed.json")
|
Path::new(&home_dir).join(".config/heroic/gog_store/installed.json")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_shortcuts_from_install_mode(
|
fn get_shortcuts_from_install_mode(
|
||||||
@@ -68,7 +68,7 @@ fn get_shortcuts_from_location<P: AsRef<Path>>(path: P) -> Result<Vec<HeroicGame
|
|||||||
games.push(game.clone());
|
games.push(game.clone());
|
||||||
}
|
}
|
||||||
Ok(games)
|
Ok(games)
|
||||||
}else{
|
} else {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,7 @@ impl Platform<HeroicGameType, Box<dyn Error>> for HeroicPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn needs_proton(&self, input: &HeroicGameType) -> bool {
|
fn needs_proton(&self, input: &HeroicGameType) -> bool {
|
||||||
match input{
|
match input {
|
||||||
HeroicGameType::Epic(_) => true,
|
HeroicGameType::Epic(_) => true,
|
||||||
HeroicGameType::Gog(_, is_windows) => *is_windows,
|
HeroicGameType::Gog(_, is_windows) => *is_windows,
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ fn get_gog_games(
|
|||||||
})
|
})
|
||||||
.filter_map(|config_path| std::fs::read_to_string(config_path).ok())
|
.filter_map(|config_path| std::fs::read_to_string(config_path).ok())
|
||||||
.filter_map(|config_string| serde_json::from_str::<HeroicGogConfig>(&config_string).ok())
|
.filter_map(|config_string| serde_json::from_str::<HeroicGogConfig>(&config_string).ok())
|
||||||
.flat_map(|config| config.installed)
|
.flat_map(|config| config.installed)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut is_windows_map = HashMap::new();
|
let mut is_windows_map = HashMap::new();
|
||||||
@@ -170,9 +170,9 @@ fn get_gog_games(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let shortcuts = get_shortcuts_from_game_folders(game_folders);
|
let shortcuts = get_shortcuts_from_game_folders(game_folders);
|
||||||
let mut gog_shortcuts = vec![];
|
let mut gog_shortcuts = vec![];
|
||||||
for shortcut in shortcuts {
|
for shortcut in shortcuts {
|
||||||
let is_windows = is_windows_map.get(&shortcut.game_id).unwrap_or(&false);
|
let is_windows = is_windows_map.get(&shortcut.game_id).unwrap_or(&false);
|
||||||
gog_shortcuts.push(HeroicGameType::Gog(shortcut, *is_windows));
|
gog_shortcuts.push(HeroicGameType::Gog(shortcut, *is_windows));
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
mod heroic_game;
|
mod heroic_game;
|
||||||
|
mod heroic_game_type;
|
||||||
mod heroic_platform;
|
mod heroic_platform;
|
||||||
mod settings;
|
mod settings;
|
||||||
mod heroic_game_type;
|
|
||||||
|
|
||||||
pub use heroic_game::*;
|
pub use heroic_game::*;
|
||||||
|
pub use heroic_game_type::*;
|
||||||
pub use heroic_platform::*;
|
pub use heroic_platform::*;
|
||||||
pub use settings::*;
|
pub use settings::*;
|
||||||
pub use heroic_game_type::*;
|
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ use nom::{
|
|||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde::{Deserialize};
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub(crate) struct DbPaths {
|
pub(crate) struct DbPaths {
|
||||||
@@ -14,31 +13,31 @@ pub(crate) struct DbPaths {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
struct Candidate{
|
struct Candidate {
|
||||||
pub path:String,
|
pub path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parse_butler_db<'a>(content: &'a [u8]) -> nom::IResult<&[u8], Vec<DbPaths>> {
|
pub(crate) fn parse_butler_db(content: &[u8]) -> nom::IResult<&[u8], Vec<DbPaths>> {
|
||||||
many0(parse_path)(content)
|
many0(parse_path)(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_path<'a>(i: &'a [u8]) -> nom::IResult<&[u8], DbPaths> {
|
fn parse_path(i: &[u8]) -> nom::IResult<&[u8], DbPaths> {
|
||||||
let prefix = "{\"basePath\":\"";
|
let prefix = "{\"basePath\":\"";
|
||||||
let suffix = "\",\"totalSize\"";
|
let suffix = "\",\"totalSize\"";
|
||||||
let (i, _taken) = take_until(prefix)(i)?;
|
let (i, _taken) = take_until(prefix)(i)?;
|
||||||
let (i, _taken) = tag(prefix)(i)?;
|
let (i, _taken) = tag(prefix)(i)?;
|
||||||
let (i, base_path) = take_until(suffix)(i)?;
|
let (i, base_path) = take_until(suffix)(i)?;
|
||||||
let base_path = String::from_utf8_lossy(base_path).to_string();
|
let base_path = String::from_utf8_lossy(base_path).to_string();
|
||||||
|
|
||||||
let prefix = "\"candidates\":[";
|
let prefix = "\"candidates\":[";
|
||||||
let suffix = "]}";
|
let suffix = "]}";
|
||||||
let (i, _taken) = take_until(prefix)(i)?;
|
let (i, _taken) = take_until(prefix)(i)?;
|
||||||
let (i, _taken) = tag(prefix)(i)?;
|
let (i, _taken) = tag(prefix)(i)?;
|
||||||
let (i, candidates_json) = take_until(suffix)(i)?;
|
let (i, candidates_json) = take_until(suffix)(i)?;
|
||||||
let candidates_json = format!("[{}]",String::from_utf8_lossy(candidates_json).to_string());
|
let candidates_json = format!("[{}]", String::from_utf8_lossy(candidates_json));
|
||||||
|
|
||||||
let candidates = serde_json::from_str::<Vec<Candidate>>(&candidates_json);
|
let candidates = serde_json::from_str::<Vec<Candidate>>(&candidates_json);
|
||||||
match candidates{
|
match candidates {
|
||||||
Ok(candidates) => {
|
Ok(candidates) => {
|
||||||
return IResult::Ok((
|
return IResult::Ok((
|
||||||
i,
|
i,
|
||||||
@@ -47,17 +46,17 @@ fn parse_path<'a>(i: &'a [u8]) -> nom::IResult<&[u8], DbPaths> {
|
|||||||
paths: candidates.iter().map(|c| c.path.clone()).collect(),
|
paths: candidates.iter().map(|c| c.path.clone()).collect(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
},
|
}
|
||||||
Err(_err) => {
|
Err(_err) => {
|
||||||
//we found a basepath, but no executables
|
//we found a basepath, but no executables
|
||||||
return IResult::Ok((
|
IResult::Ok((
|
||||||
i,
|
i,
|
||||||
DbPaths {
|
DbPaths {
|
||||||
base_path,
|
base_path,
|
||||||
paths: vec![],
|
paths: vec![],
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,12 +104,13 @@ mod tests {
|
|||||||
let (_r, paths) = result.unwrap();
|
let (_r, paths) = result.unwrap();
|
||||||
assert_eq!(paths.len(), 94);
|
assert_eq!(paths.len(), 94);
|
||||||
|
|
||||||
assert_eq!(paths[0].base_path, "/home/deck/.config/itch/apps/risetoruins");
|
assert_eq!(
|
||||||
|
paths[0].base_path,
|
||||||
|
"/home/deck/.config/itch/apps/risetoruins"
|
||||||
|
);
|
||||||
assert_eq!(paths[0].paths[0], "Core.jar");
|
assert_eq!(paths[0].paths[0], "Core.jar");
|
||||||
//The parser finds douplicates
|
//The parser finds douplicates
|
||||||
assert_eq!(paths[0], paths[1]);
|
assert_eq!(paths[0], paths[1]);
|
||||||
assert_eq!(paths[1], paths[2]);
|
assert_eq!(paths[1], paths[2]);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,13 +71,13 @@ impl Platform<ItchGame, ItchErrors> for ItchPlatform {
|
|||||||
}
|
}
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn needs_proton(&self, _input: &ItchGame) -> bool {
|
fn needs_proton(&self, _input: &ItchGame) -> bool {
|
||||||
return false;
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
fn needs_proton(&self, input: &ItchGame) -> bool {
|
fn needs_proton(&self, input: &ItchGame) -> bool {
|
||||||
//We can only really guess here
|
//We can only really guess here
|
||||||
return input.executable.ends_with("exe");
|
input.executable.ends_with("exe")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ impl Platform<LegendaryGame, Box<dyn Error>> for LegendaryPlatform {
|
|||||||
.settings
|
.settings
|
||||||
.executable
|
.executable
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or("legendary".to_string());
|
.unwrap_or_else(|| "legendary".to_string());
|
||||||
let legendary = legendary_string.as_str();
|
let legendary = legendary_string.as_str();
|
||||||
execute_legendary_command(legendary)
|
execute_legendary_command(legendary)
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ impl Platform<LegendaryGame, Box<dyn Error>> for LegendaryPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn needs_proton(&self, _input: &LegendaryGame) -> bool {
|
fn needs_proton(&self, _input: &LegendaryGame) -> bool {
|
||||||
return false;
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
use super::lutris_game::LutrisGame;
|
use super::lutris_game::LutrisGame;
|
||||||
|
|
||||||
pub fn parse_lutris_games<'a>(input: &'a str) -> Vec<LutrisGame> {
|
pub fn parse_lutris_games(input: &str) -> Vec<LutrisGame> {
|
||||||
input
|
input
|
||||||
.split("\n")
|
.split('\n')
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|s| !s.is_empty())
|
.filter(|s| !s.is_empty())
|
||||||
.filter_map(parse_line)
|
.filter_map(parse_line)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_line<'a>(input: &'a str) -> Option<LutrisGame> {
|
fn parse_line(input: &str) -> Option<LutrisGame> {
|
||||||
let mut sections = input.split("|");
|
let mut sections = input.split('|');
|
||||||
if sections.clone().count() < 4 {
|
if sections.clone().count() < 4 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -20,10 +20,10 @@ fn parse_line<'a>(input: &'a str) -> Option<LutrisGame> {
|
|||||||
let platform = sections.next().unwrap().trim();
|
let platform = sections.next().unwrap().trim();
|
||||||
|
|
||||||
Some(LutrisGame {
|
Some(LutrisGame {
|
||||||
id:id.to_string(),
|
id: id.to_string(),
|
||||||
index:index.to_string(),
|
index: index.to_string(),
|
||||||
name:name.to_string(),
|
name: name.to_string(),
|
||||||
platform:platform.to_string(),
|
platform: platform.to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ impl From<LutrisGame> for ShortcutOwned {
|
|||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
&options.as_str(),
|
options.as_str(),
|
||||||
)
|
)
|
||||||
.to_owned()
|
.to_owned()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,6 @@ impl Platform<LutrisGame, Box<dyn Error>> for LutrisPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn needs_proton(&self, _input: &LutrisGame) -> bool {
|
fn needs_proton(&self, _input: &LutrisGame) -> bool {
|
||||||
return false;
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use serde::{Serialize,Deserialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize,Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct LutrisSettings {
|
pub struct LutrisSettings {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub executable: Option<String>,
|
pub executable: Option<String>,
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -1,3 +1,4 @@
|
|||||||
|
mod amazon;
|
||||||
mod egs;
|
mod egs;
|
||||||
mod gog;
|
mod gog;
|
||||||
mod heroic;
|
mod heroic;
|
||||||
@@ -10,9 +11,8 @@ mod settings;
|
|||||||
mod steam;
|
mod steam;
|
||||||
mod steamgriddb;
|
mod steamgriddb;
|
||||||
mod sync;
|
mod sync;
|
||||||
mod uplay;
|
|
||||||
mod ui;
|
mod ui;
|
||||||
mod amazon;
|
mod uplay;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
@@ -20,7 +20,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
if args.len() > 1 && args.nth(1).unwrap_or_default() == "--no-ui" {
|
if args.len() > 1 && args.nth(1).unwrap_or_default() == "--no-ui" {
|
||||||
ui::run_sync();
|
ui::run_sync();
|
||||||
Ok(())
|
Ok(())
|
||||||
}else{
|
} else {
|
||||||
ui::run_ui()
|
ui::run_ui()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-23
@@ -6,34 +6,30 @@ use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
|
|||||||
pub struct OriginGame {
|
pub struct OriginGame {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub origin_location : Option<PathBuf>,
|
pub origin_location: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<OriginGame> for ShortcutOwned {
|
impl From<OriginGame> for ShortcutOwned {
|
||||||
fn from(game: OriginGame) -> Self {
|
fn from(game: OriginGame) -> Self {
|
||||||
let launch = format!("\"origin2://game/launch?offerIds={}&autoDownload=1&authCode=&cmdParams=\"", game.id);
|
let launch = format!(
|
||||||
|
"\"origin2://game/launch?offerIds={}&autoDownload=1&authCode=&cmdParams=\"",
|
||||||
let mut owned_shortcut = if let Some(origin_location) = game.origin_location{
|
game.id
|
||||||
let origin_location = format!("\"{}\"",origin_location.to_string_lossy());
|
);
|
||||||
|
|
||||||
|
let mut owned_shortcut = if let Some(origin_location) = game.origin_location {
|
||||||
|
let origin_location = format!("\"{}\"", origin_location.to_string_lossy());
|
||||||
Shortcut::new(
|
Shortcut::new(
|
||||||
"0",
|
"0",
|
||||||
game.title.as_str(),
|
game.title.as_str(),
|
||||||
&origin_location,
|
&origin_location,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
launch.as_str()
|
launch.as_str(),
|
||||||
).to_owned()
|
)
|
||||||
}else{
|
.to_owned()
|
||||||
Shortcut::new(
|
} else {
|
||||||
"0",
|
Shortcut::new("0", game.title.as_str(), launch.as_str(), "", "", "", "").to_owned()
|
||||||
game.title.as_str(),
|
|
||||||
launch.as_str(),
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
""
|
|
||||||
).to_owned()
|
|
||||||
};
|
};
|
||||||
owned_shortcut.tags.push("Origin".to_owned());
|
owned_shortcut.tags.push("Origin".to_owned());
|
||||||
owned_shortcut.tags.push("Ready TO Play".to_owned());
|
owned_shortcut.tags.push("Ready TO Play".to_owned());
|
||||||
|
|||||||
@@ -27,22 +27,21 @@ impl Platform<OriginGame, OriginErrors> for OriginPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_shortcuts(&self) -> Result<Vec<OriginGame>, OriginErrors> {
|
fn get_shortcuts(&self) -> Result<Vec<OriginGame>, OriginErrors> {
|
||||||
|
let origin_folders = get_default_locations();
|
||||||
let origin_folders = get_default_locations();
|
if origin_folders.local_content_path.is_none() {
|
||||||
if origin_folders.local_content_path.is_none(){
|
return Err(OriginErrors::PathNotFound {
|
||||||
return Err(OriginErrors::PathNotFound { path: "Default path".to_string() });
|
path: "Default path".to_string(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let origin_folder = origin_folders.local_content_path.unwrap();
|
let origin_folder = origin_folders.local_content_path.unwrap();
|
||||||
let origin_exe = origin_folders.exe_path;
|
let origin_exe = origin_folders.exe_path;
|
||||||
let game_folders =
|
let game_folders = origin_folder.join("LocalContent").read_dir().map_err(|e| {
|
||||||
origin_folder
|
OriginErrors::CouldNotReadGameDir {
|
||||||
.join("LocalContent")
|
path: origin_folder.join("LocalContent"),
|
||||||
.read_dir()
|
error: format!("{:?}", e),
|
||||||
.map_err(|e| OriginErrors::CouldNotReadGameDir {
|
}
|
||||||
path: origin_folder.join("LocalContent"),
|
})?;
|
||||||
error: format!("{:?}", e),
|
|
||||||
})?;
|
|
||||||
let games = game_folders
|
let games = game_folders
|
||||||
.filter_map(|folder| folder.ok())
|
.filter_map(|folder| folder.ok())
|
||||||
.filter_map(|game_folder| {
|
.filter_map(|game_folder| {
|
||||||
@@ -57,7 +56,7 @@ impl Platform<OriginGame, OriginErrors> for OriginPlatform {
|
|||||||
id.map(|id| OriginGame {
|
id.map(|id| OriginGame {
|
||||||
id,
|
id,
|
||||||
title: game_title,
|
title: game_title,
|
||||||
origin_location:origin_exe.clone()
|
origin_location: origin_exe.clone(),
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
Ok(games.collect())
|
Ok(games.collect())
|
||||||
@@ -79,12 +78,11 @@ impl Platform<OriginGame, OriginErrors> for OriginPlatform {
|
|||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
{
|
{
|
||||||
//TODO Update this when origin gets support on linux
|
//TODO Update this when origin gets support on linux
|
||||||
return true;
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn get_folder_mfst_file_content(game_folder_path: &Path) -> Option<String> {
|
fn get_folder_mfst_file_content(game_folder_path: &Path) -> Option<String> {
|
||||||
let game_folder_files = game_folder_path.read_dir();
|
let game_folder_files = game_folder_path.read_dir();
|
||||||
if let Ok(game_folder_files) = game_folder_files {
|
if let Ok(game_folder_files) = game_folder_files {
|
||||||
@@ -114,47 +112,44 @@ fn parse_id_from_file(i: &str) -> nom::IResult<&str, &str> {
|
|||||||
take_until("&")(i)
|
take_until("&")(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct OriginPathData{
|
struct OriginPathData {
|
||||||
//~/.steam/steam/steamapps/compatdata/X/pfx/drive_c/Program Files (x86)/Origin/Origin.exe
|
//~/.steam/steam/steamapps/compatdata/X/pfx/drive_c/Program Files (x86)/Origin/Origin.exe
|
||||||
exe_path:Option<PathBuf>,
|
exe_path: Option<PathBuf>,
|
||||||
//~/.steam/steam/steamapps/compatdata/X/pfx/drive_c/ProgramData/Origin/LocalContent
|
//~/.steam/steam/steamapps/compatdata/X/pfx/drive_c/ProgramData/Origin/LocalContent
|
||||||
local_content_path:Option<PathBuf>
|
local_content_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
fn get_default_locations() -> OriginPathData {
|
fn get_default_locations() -> OriginPathData {
|
||||||
let mut res = OriginPathData::default();
|
let mut res = OriginPathData::default();
|
||||||
if let Ok(home) = std::env::var("HOME"){
|
if let Ok(home) = std::env::var("HOME") {
|
||||||
let compat_folder_path = Path::new(&home)
|
let compat_folder_path = Path::new(&home)
|
||||||
.join(".steam")
|
.join(".steam")
|
||||||
.join("steam")
|
.join("steam")
|
||||||
.join("steamapps")
|
.join("steamapps")
|
||||||
.join("compatdata");
|
.join("compatdata");
|
||||||
|
|
||||||
if let Ok(compat_folder) = std::fs::read_dir(&compat_folder_path){
|
if let Ok(compat_folder) = std::fs::read_dir(&compat_folder_path) {
|
||||||
for game_folder in compat_folder {
|
for dir in compat_folder.flatten() {
|
||||||
if let Ok(dir) = game_folder{
|
let origin_exe_path = dir
|
||||||
let origin_exe_path= dir.path()
|
.path()
|
||||||
.join("pfx")
|
.join("pfx")
|
||||||
.join("drive_c")
|
.join("drive_c")
|
||||||
.join("Program Files (x86)")
|
.join("Program Files (x86)")
|
||||||
.join("Origin")
|
.join("Origin")
|
||||||
.join("Origin.exe");
|
.join("Origin.exe");
|
||||||
|
|
||||||
let origin_local_content= dir.path()
|
let origin_local_content = dir
|
||||||
|
.path()
|
||||||
.join("pfx")
|
.join("pfx")
|
||||||
.join("drive_c")
|
.join("drive_c")
|
||||||
.join("ProgramData")
|
.join("ProgramData")
|
||||||
.join("Origin");
|
.join("Origin");
|
||||||
|
|
||||||
if origin_exe_path.exists() && origin_local_content.exists(){
|
if origin_exe_path.exists() && origin_local_content.exists() {
|
||||||
res.exe_path = Some(origin_exe_path);
|
res.exe_path = Some(origin_exe_path);
|
||||||
res.local_content_path = Some(origin_local_content);
|
res.local_content_path = Some(origin_local_content);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,20 +157,16 @@ struct OriginPathData{
|
|||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn get_default_locations() -> OriginPathData {
|
fn get_default_locations() -> OriginPathData {
|
||||||
let mut res = OriginPathData::default();
|
let mut res = OriginPathData::default();
|
||||||
|
|
||||||
let key = "PROGRAMDATA";
|
let key = "PROGRAMDATA";
|
||||||
let program_data = std::env::var(key);
|
let program_data = std::env::var(key);
|
||||||
if let Ok(program_data) =program_data {
|
if let Ok(program_data) = program_data {
|
||||||
let origin_folder = Path::new(&program_data)
|
let origin_folder = Path::new(&program_data).join("Origin");
|
||||||
.join("Origin");
|
if origin_folder.exists() {
|
||||||
if origin_folder.exists(){
|
res.local_content_path = Some(origin_folder);
|
||||||
res.local_content_path = Some(origin_folder.to_owned());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res
|
res
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
use steam_shortcuts_util::shortcut::ShortcutOwned;
|
use steam_shortcuts_util::shortcut::ShortcutOwned;
|
||||||
|
|
||||||
pub trait Platform<T, E>
|
pub trait Platform<T, E>
|
||||||
where
|
where
|
||||||
|
|||||||
+4
-3
@@ -1,7 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
egs::EpicGamesLauncherSettings, gog::GogSettings, itch::ItchSettings,
|
amazon::AmazonSettings, egs::EpicGamesLauncherSettings, gog::GogSettings,
|
||||||
legendary::LegendarySettings, lutris::settings::LutrisSettings, origin::OriginSettings,
|
heroic::HeroicSettings, itch::ItchSettings, legendary::LegendarySettings,
|
||||||
steam::SteamSettings, steamgriddb::SteamGridDbSettings, uplay::UplaySettings, heroic::HeroicSettings, amazon::AmazonSettings,
|
lutris::settings::LutrisSettings, origin::OriginSettings, steam::SteamSettings,
|
||||||
|
steamgriddb::SteamGridDbSettings, uplay::UplaySettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
use config::{Config, ConfigError, Environment, File};
|
use config::{Config, ConfigError, Environment, File};
|
||||||
|
|||||||
+22
-20
@@ -42,7 +42,7 @@ struct ActualSteamCollection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ActualSteamCollection {
|
impl ActualSteamCollection {
|
||||||
fn new<B: AsRef<str>>(name: B, ids: &Vec<usize>) -> Self {
|
fn new<B: AsRef<str>>(name: B, ids: &[usize]) -> Self {
|
||||||
let name = name.as_ref();
|
let name = name.as_ref();
|
||||||
let key = format!("user-collections.{}", name_to_key(name));
|
let key = format!("user-collections.{}", name_to_key(name));
|
||||||
let value = serialize_collection_value(name, ids);
|
let value = serialize_collection_value(name, ids);
|
||||||
@@ -80,13 +80,13 @@ impl ValueCollection {
|
|||||||
fn new<S: AsRef<str>>(name: S, game_ids: &[usize]) -> Self {
|
fn new<S: AsRef<str>>(name: S, game_ids: &[usize]) -> Self {
|
||||||
let name = name.as_ref();
|
let name = name.as_ref();
|
||||||
let id = name_to_key(name);
|
let id = name_to_key(name);
|
||||||
let value = ValueCollection {
|
|
||||||
|
ValueCollection {
|
||||||
id,
|
id,
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
added: game_ids.to_vec(),
|
added: game_ids.to_vec(),
|
||||||
removed: vec![],
|
removed: vec![],
|
||||||
};
|
}
|
||||||
value
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ pub struct Collection {
|
|||||||
|
|
||||||
pub fn write_collections<S: AsRef<str>>(
|
pub fn write_collections<S: AsRef<str>>(
|
||||||
steam_user_id: S,
|
steam_user_id: S,
|
||||||
collections_to_add: &Vec<Collection>,
|
collections_to_add: &[Collection],
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let steam_user_id = steam_user_id.as_ref();
|
let steam_user_id = steam_user_id.as_ref();
|
||||||
let new_collections: Vec<(String, SteamCollection)> = collections_to_add
|
let new_collections: Vec<(String, SteamCollection)> = collections_to_add
|
||||||
@@ -134,7 +134,7 @@ pub fn write_collections<S: AsRef<str>>(
|
|||||||
let boilr_keys: Vec<String> = vdf_collections
|
let boilr_keys: Vec<String> = vdf_collections
|
||||||
.keys()
|
.keys()
|
||||||
.filter(|k| k.contains(BOILR_TAG))
|
.filter(|k| k.contains(BOILR_TAG))
|
||||||
.map(|k| k.clone())
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
for key in boilr_keys {
|
for key in boilr_keys {
|
||||||
vdf_collections.remove(&key);
|
vdf_collections.remove(&key);
|
||||||
@@ -142,12 +142,12 @@ pub fn write_collections<S: AsRef<str>>(
|
|||||||
|
|
||||||
let new_vdfs = collections_to_add.iter().map(|collection| {
|
let new_vdfs = collections_to_add.iter().map(|collection| {
|
||||||
let key = name_to_key(&collection.name);
|
let key = name_to_key(&collection.name);
|
||||||
let vd_collection = VdfCollection {
|
|
||||||
|
VdfCollection {
|
||||||
id: key,
|
id: key,
|
||||||
added: collection.game_ids.clone(),
|
added: collection.game_ids.clone(),
|
||||||
removed: vec![],
|
removed: vec![],
|
||||||
};
|
}
|
||||||
vd_collection
|
|
||||||
});
|
});
|
||||||
for new_vdf in new_vdfs {
|
for new_vdf in new_vdfs {
|
||||||
vdf_collections.insert(new_vdf.id.clone(), new_vdf.clone());
|
vdf_collections.insert(new_vdf.id.clone(), new_vdf.clone());
|
||||||
@@ -181,11 +181,11 @@ fn get_vdf_path<S: AsRef<str>>(steamid: S) -> Option<PathBuf> {
|
|||||||
.join("config")
|
.join("config")
|
||||||
.join("localconfig.vdf");
|
.join("localconfig.vdf");
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
return Some(path.to_path_buf());
|
return Some(path);
|
||||||
}
|
}
|
||||||
return None;
|
None
|
||||||
}
|
}
|
||||||
Err(_e) => return None,
|
Err(_e) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ fn get_vdf_path<S: AsRef<str>>(steamid: S) -> Option<PathBuf> {
|
|||||||
.join("config")
|
.join("config")
|
||||||
.join("localconfig.vdf");
|
.join("localconfig.vdf");
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
Some(path.to_path_buf())
|
Some(path)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -220,10 +220,12 @@ fn save_category<S: AsRef<str>>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CollectionCategories = HashMap<String, Vec<(String, SteamCollection)>>;
|
||||||
|
|
||||||
fn get_categories<S: AsRef<str>>(
|
fn get_categories<S: AsRef<str>>(
|
||||||
steamid: S,
|
steamid: S,
|
||||||
db: &mut DB,
|
db: &mut DB,
|
||||||
) -> Result<HashMap<String, Vec<(String, SteamCollection)>>, Box<dyn Error>> {
|
) -> Result<CollectionCategories, Box<dyn Error>> {
|
||||||
let namespace_keys = get_namespace_keys(steamid, db);
|
let namespace_keys = get_namespace_keys(steamid, db);
|
||||||
let mut db_iter = db.new_iter()?;
|
let mut db_iter = db.new_iter()?;
|
||||||
let mut res = HashMap::new();
|
let mut res = HashMap::new();
|
||||||
@@ -295,11 +297,11 @@ fn get_level_db_location() -> Option<PathBuf> {
|
|||||||
.join("Local Storage")
|
.join("Local Storage")
|
||||||
.join("leveldb");
|
.join("leveldb");
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
return Some(path.to_path_buf());
|
return Some(path);
|
||||||
}
|
}
|
||||||
return None;
|
None
|
||||||
}
|
}
|
||||||
Err(_e) => return None,
|
Err(_e) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,7 +316,7 @@ fn get_level_db_location() -> Option<PathBuf> {
|
|||||||
.join("leveldb");
|
.join("leveldb");
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
Some(path)
|
Some(path)
|
||||||
}else{
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -324,7 +326,7 @@ fn get_level_db_location() -> Option<PathBuf> {
|
|||||||
|
|
||||||
fn serialize_collection_value<S: AsRef<str>>(name: S, game_ids: &[usize]) -> String {
|
fn serialize_collection_value<S: AsRef<str>>(name: S, game_ids: &[usize]) -> String {
|
||||||
let value = ValueCollection::new(name, game_ids);
|
let value = ValueCollection::new(name, game_ids);
|
||||||
serde_json::to_string(&value).expect("Should be able to serialize known type")
|
serde_json::to_string(&value).expect("Should be able to serialize known type")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name_to_key<S: AsRef<str>>(name: S) -> String {
|
fn name_to_key<S: AsRef<str>>(name: S) -> String {
|
||||||
@@ -339,7 +341,7 @@ fn parse_steam_collections<S: AsRef<str>>(
|
|||||||
) -> Result<Vec<(String, SteamCollection)>, Box<dyn Error>> {
|
) -> Result<Vec<(String, SteamCollection)>, Box<dyn Error>> {
|
||||||
let input = input.as_ref();
|
let input = input.as_ref();
|
||||||
let input = input.strip_prefix('\u{1}').unwrap_or(input);
|
let input = input.strip_prefix('\u{1}').unwrap_or(input);
|
||||||
let res = serde_json::from_str::<Vec<(String, SteamCollection)>>(&input)?;
|
let res = serde_json::from_str::<Vec<(String, SteamCollection)>>(input)?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-6
@@ -1,12 +1,11 @@
|
|||||||
mod settings;
|
|
||||||
mod utils;
|
|
||||||
mod collections;
|
mod collections;
|
||||||
mod proton_vdf_util;
|
mod proton_vdf_util;
|
||||||
mod restarter;
|
mod restarter;
|
||||||
|
mod settings;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
|
||||||
pub use settings::SteamSettings;
|
|
||||||
pub use utils::*;
|
|
||||||
pub use collections::*;
|
pub use collections::*;
|
||||||
pub use proton_vdf_util::*;
|
pub use proton_vdf_util::*;
|
||||||
pub use restarter::*;
|
pub use restarter::*;
|
||||||
|
pub use settings::SteamSettings;
|
||||||
|
pub use utils::*;
|
||||||
|
|||||||
@@ -2,19 +2,16 @@ use std::path::Path;
|
|||||||
|
|
||||||
use nom::FindSubstring;
|
use nom::FindSubstring;
|
||||||
|
|
||||||
|
pub fn setup_proton_games<B: AsRef<str>>(games: &[B]) {
|
||||||
pub fn setup_proton_games<B: AsRef<str>>(games: &[B]){
|
if let Ok(home) = std::env::var("HOME") {
|
||||||
if let Ok(home) = std::env::var("HOME"){
|
|
||||||
let config_file = Path::new(&home).join(".local/share/Steam/config/config.vdf");
|
let config_file = Path::new(&home).join(".local/share/Steam/config/config.vdf");
|
||||||
if config_file.exists(){
|
if config_file.exists() {
|
||||||
if let Ok(config_content) = std::fs::read_to_string(&config_file){
|
if let Ok(config_content) = std::fs::read_to_string(&config_file) {
|
||||||
let new_string = enable_proton_games(config_content, games);
|
let new_string = enable_proton_games(config_content, games);
|
||||||
std::fs::write(config_file, new_string).unwrap();
|
std::fs::write(config_file, new_string).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enable_proton_games<S: AsRef<str>, B: AsRef<str>>(vdf_content: S, games: &[B]) -> String {
|
fn enable_proton_games<S: AsRef<str>, B: AsRef<str>>(vdf_content: S, games: &[B]) -> String {
|
||||||
@@ -44,7 +41,6 @@ fn enable_proton_games<S: AsRef<str>, B: AsRef<str>>(vdf_content: S, games: &[B]
|
|||||||
let res = res.replace("\"X\"", &format!("\"{}\"", game_id.as_ref()));
|
let res = res.replace("\"X\"", &format!("\"{}\"", game_id.as_ref()));
|
||||||
let res = res.replace('=', &base_indent_string);
|
let res = res.replace('=', &base_indent_string);
|
||||||
res.replace('+', &field_indent_string)
|
res.replace('+', &field_indent_string)
|
||||||
|
|
||||||
});
|
});
|
||||||
let mut new_section = section_str.to_string();
|
let mut new_section = section_str.to_string();
|
||||||
for game_string in games_strings_to_add {
|
for game_string in games_strings_to_add {
|
||||||
@@ -116,22 +112,21 @@ mod tests {
|
|||||||
let expected = include_str!("../testdata/vdf/compatmappingsection.vdf");
|
let expected = include_str!("../testdata/vdf/compatmappingsection.vdf");
|
||||||
assert_eq!(expected, actual);
|
assert_eq!(expected, actual);
|
||||||
assert_eq!(4, base_indentation);
|
assert_eq!(4, base_indentation);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn enable_proton_test() {
|
pub fn enable_proton_test() {
|
||||||
let input = include_str!("../testdata/vdf/testconfig.vdf");
|
let input = include_str!("../testdata/vdf/testconfig.vdf");
|
||||||
let output = enable_proton_games(input, &vec!["42", "43", "44"]);
|
let output = enable_proton_games(input, &["42", "43", "44"]);
|
||||||
let expected = include_str!("../testdata/vdf/testconfig_expected.vdf");
|
let expected = include_str!("../testdata/vdf/testconfig_expected.vdf");
|
||||||
assert_eq!(expected,output );
|
assert_eq!(expected, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn enable_proton_test_empty() {
|
pub fn enable_proton_test_empty() {
|
||||||
let input = include_str!("../testdata/vdf/testconfig.vdf");
|
let input = include_str!("../testdata/vdf/testconfig.vdf");
|
||||||
let output = enable_proton_games(input, &vec!["2719403116"]);
|
let output = enable_proton_games(input, &["2719403116"]);
|
||||||
let expected = include_str!("../testdata/vdf/testconfig.vdf");
|
let expected = include_str!("../testdata/vdf/testconfig.vdf");
|
||||||
assert_eq!(expected,output );
|
assert_eq!(expected, output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::{path::Path, process::Command, thread::sleep, time::Duration};
|
use std::{process::Command, thread::sleep, time::Duration};
|
||||||
|
|
||||||
use sysinfo::{ProcessExt, System, SystemExt};
|
use sysinfo::{ProcessExt, System, SystemExt};
|
||||||
|
|
||||||
@@ -7,9 +7,9 @@ pub fn ensure_steam_stopped() {
|
|||||||
let steam_name = "steam.exe";
|
let steam_name = "steam.exe";
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
let steam_name = "steam";
|
let steam_name = "steam";
|
||||||
|
|
||||||
let s = System::new_all();
|
let s = System::new_all();
|
||||||
let processes = s.processes_by_name(&steam_name);
|
let processes = s.processes_by_name(steam_name);
|
||||||
for process in processes {
|
for process in processes {
|
||||||
let mut s = System::new();
|
let mut s = System::new();
|
||||||
process.kill_with(sysinfo::Signal::Quit);
|
process.kill_with(sysinfo::Signal::Quit);
|
||||||
@@ -26,13 +26,13 @@ pub fn ensure_steam_stopped() {
|
|||||||
pub fn ensure_steam_started(settings: &super::SteamSettings) {
|
pub fn ensure_steam_started(settings: &super::SteamSettings) {
|
||||||
let steam_name = "steam.exe";
|
let steam_name = "steam.exe";
|
||||||
let s = System::new_all();
|
let s = System::new_all();
|
||||||
let mut processes = s.processes_by_name(&steam_name);
|
let mut processes = s.processes_by_name(steam_name);
|
||||||
if processes.next().is_none() {
|
if processes.next().is_none() {
|
||||||
//no steam, we need to start it
|
//no steam, we need to start it
|
||||||
println!("Starting steam");
|
println!("Starting steam");
|
||||||
let folder = super::get_steam_path(settings);
|
let folder = super::get_steam_path(settings);
|
||||||
if let Ok(folder) = folder {
|
if let Ok(folder) = folder {
|
||||||
let path = Path::new(&folder).join(steam_name);
|
let path = std::path::Path::new(&folder).join(steam_name);
|
||||||
let mut command = Command::new(&path);
|
let mut command = Command::new(&path);
|
||||||
if let Err(e) = command.spawn() {
|
if let Err(e) = command.spawn() {
|
||||||
println!("Failed to start steam: {:?}", e);
|
println!("Failed to start steam: {:?}", e);
|
||||||
@@ -45,7 +45,7 @@ pub fn ensure_steam_started(settings: &super::SteamSettings) {
|
|||||||
pub fn ensure_steam_started(_settings: &super::SteamSettings) {
|
pub fn ensure_steam_started(_settings: &super::SteamSettings) {
|
||||||
let steam_name = "steam";
|
let steam_name = "steam";
|
||||||
let s = System::new_all();
|
let s = System::new_all();
|
||||||
let mut processes = s.processes_by_name(&steam_name);
|
let mut processes = s.processes_by_name(steam_name);
|
||||||
if processes.next().is_none() {
|
if processes.next().is_none() {
|
||||||
//no steam, we need to start it
|
//no steam, we need to start it
|
||||||
println!("Starting steam");
|
println!("Starting steam");
|
||||||
@@ -54,4 +54,4 @@ pub fn ensure_steam_started(_settings: &super::SteamSettings) {
|
|||||||
println!("Failed to start steam: {:?}", e);
|
println!("Failed to start steam: {:?}", e);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-8
@@ -43,10 +43,11 @@ pub struct ShortcutInfo {
|
|||||||
pub shortcuts: Vec<ShortcutOwned>,
|
pub shortcuts: Vec<ShortcutOwned>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Clone)]
|
||||||
pub struct SteamUsersInfo {
|
pub struct SteamUsersInfo {
|
||||||
pub steam_user_data_folder: String,
|
pub steam_user_data_folder: String,
|
||||||
pub shortcut_path: Option<String>,
|
pub shortcut_path: Option<String>,
|
||||||
pub user_id : String,
|
pub user_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the paths to the steam users shortcuts (one for each user)
|
/// Get the paths to the steam users shortcuts (one for each user)
|
||||||
@@ -89,14 +90,14 @@ pub fn get_shortcuts_paths(
|
|||||||
return SteamUsersInfo {
|
return SteamUsersInfo {
|
||||||
steam_user_data_folder: folder_string,
|
steam_user_data_folder: folder_string,
|
||||||
shortcut_path: Some(shortcuts_path.to_str().unwrap().to_string()),
|
shortcut_path: Some(shortcuts_path.to_str().unwrap().to_string()),
|
||||||
user_id
|
user_id,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return SteamUsersInfo {
|
SteamUsersInfo {
|
||||||
steam_user_data_folder: folder_string,
|
steam_user_data_folder: folder_string,
|
||||||
shortcut_path: None,
|
shortcut_path: None,
|
||||||
user_id
|
user_id,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -203,6 +204,3 @@ pub fn get_users_images(user: &SteamUsersInfo) -> Result<Vec<String>, Box<dyn Er
|
|||||||
.collect();
|
.collect();
|
||||||
Ok(file_names)
|
Ok(file_names)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ impl<'a> CachedSearch<'a> {
|
|||||||
save_search_map(&self.search_map);
|
save_search_map(&self.search_map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_cache<S>(&mut self, app_id: u32, name:S, new_grid_id:usize)
|
||||||
|
where
|
||||||
|
S: Into<String>{
|
||||||
|
self.search_map.insert(app_id,(name.into(),new_grid_id));
|
||||||
|
self.save();
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn search<S>(
|
pub async fn search<S>(
|
||||||
&self,
|
&self,
|
||||||
app_id: u32,
|
app_id: u32,
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ use std::{collections::HashMap, path::Path};
|
|||||||
|
|
||||||
use futures::{stream, StreamExt};
|
use futures::{stream, StreamExt};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::watch::Sender;
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use steamgriddb_api::query_parameters::{GridDimentions, Nsfw}; // 0.3.1
|
use steamgriddb_api::query_parameters::{GridDimentions, Nsfw};
|
||||||
|
use tokio::sync::watch::Sender; // 0.3.1
|
||||||
|
|
||||||
use steam_shortcuts_util::shortcut::ShortcutOwned;
|
use steam_shortcuts_util::shortcut::ShortcutOwned;
|
||||||
use steamgriddb_api::Client;
|
use steamgriddb_api::Client;
|
||||||
@@ -25,7 +25,7 @@ pub async fn download_images_for_users<'b>(
|
|||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
users: &[SteamUsersInfo],
|
users: &[SteamUsersInfo],
|
||||||
download_animated: bool,
|
download_animated: bool,
|
||||||
sender:&mut Option<Sender<SyncProgress>>
|
sender: &mut Option<Sender<SyncProgress>>,
|
||||||
) {
|
) {
|
||||||
let auth_key = &settings.steamgrid_db.auth_key;
|
let auth_key = &settings.steamgrid_db.auth_key;
|
||||||
if let Some(auth_key) = auth_key {
|
if let Some(auth_key) = auth_key {
|
||||||
@@ -35,7 +35,7 @@ pub async fn download_images_for_users<'b>(
|
|||||||
let search = CachedSearch::new(&client);
|
let search = CachedSearch::new(&client);
|
||||||
let search = &search;
|
let search = &search;
|
||||||
let client = &client;
|
let client = &client;
|
||||||
if let Some(sender) = sender{
|
if let Some(sender) = sender {
|
||||||
let _ = sender.send(SyncProgress::FindingImages);
|
let _ = sender.send(SyncProgress::FindingImages);
|
||||||
}
|
}
|
||||||
let to_downloads = stream::iter(users)
|
let to_downloads = stream::iter(users)
|
||||||
@@ -62,7 +62,7 @@ pub async fn download_images_for_users<'b>(
|
|||||||
let to_downloads = to_downloads.iter().flatten().collect::<Vec<&ToDownload>>();
|
let to_downloads = to_downloads.iter().flatten().collect::<Vec<&ToDownload>>();
|
||||||
let total = to_downloads.len();
|
let total = to_downloads.len();
|
||||||
if !to_downloads.is_empty() {
|
if !to_downloads.is_empty() {
|
||||||
if let Some(sender) = sender{
|
if let Some(sender) = sender {
|
||||||
let _ = sender.send(SyncProgress::DownloadingImages { to_download: total });
|
let _ = sender.send(SyncProgress::DownloadingImages { to_download: total });
|
||||||
}
|
}
|
||||||
search.save();
|
search.save();
|
||||||
@@ -82,7 +82,7 @@ pub async fn download_images_for_users<'b>(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("Steamgrid DB Auth Key not found, please add one as described here: https://github.com/PhilipK/steam_shortcuts_sync#configuration");
|
println!("Steamgrid DB Auth Key not found, please add one as described here: https://github.com/PhilipK/steam_shortcuts_sync#configuration");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
@@ -183,7 +183,7 @@ async fn search_for_images_to_download(
|
|||||||
let shortcuts: Vec<&ShortcutOwned> = images_needed.collect();
|
let shortcuts: Vec<&ShortcutOwned> = images_needed.collect();
|
||||||
|
|
||||||
if let ImageType::Icon = image_type {
|
if let ImageType::Icon = image_type {
|
||||||
for (index,image_id) in image_ids.iter().enumerate() {
|
for (index, image_id) in image_ids.iter().enumerate() {
|
||||||
let shortcut = shortcuts[index];
|
let shortcut = shortcuts[index];
|
||||||
if let Some(url) = get_steam_icon_url(*image_id).await {
|
if let Some(url) = get_steam_icon_url(*image_id).await {
|
||||||
let path = grid_folder.join(image_type.file_name(shortcut.app_id));
|
let path = grid_folder.join(image_type.file_name(shortcut.app_id));
|
||||||
@@ -196,9 +196,9 @@ async fn search_for_images_to_download(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for image_ids in image_ids.chunks(99){
|
for image_ids in image_ids.chunks(99) {
|
||||||
let image_search_result =
|
let image_search_result =
|
||||||
get_images_for_ids(client, &image_ids, &image_type, download_animated).await;
|
get_images_for_ids(client, image_ids, &image_type, download_animated).await;
|
||||||
match image_search_result {
|
match image_search_result {
|
||||||
Ok(images) => {
|
Ok(images) => {
|
||||||
let images = images
|
let images = images
|
||||||
@@ -239,21 +239,26 @@ async fn get_images_for_ids(
|
|||||||
image_ids: &[usize],
|
image_ids: &[usize],
|
||||||
image_type: &ImageType,
|
image_type: &ImageType,
|
||||||
download_animated: bool,
|
download_animated: bool,
|
||||||
) -> Result<
|
) -> Result<Vec<steamgriddb_api::response::SteamGridDbResult<steamgriddb_api::images::Image>>, String>
|
||||||
Vec<steamgriddb_api::response::SteamGridDbResult<steamgriddb_api::images::Image>>,
|
{
|
||||||
String,
|
let query_type = get_query_type(download_animated, image_type);
|
||||||
> {
|
|
||||||
use steamgriddb_api::query_parameters::AnimtionType;
|
let image_search_result = client.get_images_for_ids(image_ids, &query_type).await;
|
||||||
use steamgriddb_api::query_parameters::QueryType::*;
|
|
||||||
|
image_search_result.map_err(|e| format!("Image search failed {:?}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
const BIG_PICTURE_DIMS : [GridDimentions;2] = [GridDimentions::D920x430, GridDimentions::D460x215];
|
||||||
|
|
||||||
|
pub fn get_query_type(download_animated: bool, image_type: &ImageType) -> steamgriddb_api::QueryType {
|
||||||
let anymation_type = if download_animated {
|
let anymation_type = if download_animated {
|
||||||
Some(&[AnimtionType::Animated][..])
|
Some(&[steamgriddb_api::query_parameters::AnimtionType::Animated][..])
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
let big_picture_dims = [GridDimentions::D920x430, GridDimentions::D460x215];
|
|
||||||
use steamgriddb_api::query_parameters::GridQueryParameters;
|
use steamgriddb_api::query_parameters::GridQueryParameters;
|
||||||
let big_picture_parameters = GridQueryParameters {
|
let big_picture_parameters = GridQueryParameters {
|
||||||
dimentions: Some(&big_picture_dims),
|
dimentions: Some(&BIG_PICTURE_DIMS),
|
||||||
types: anymation_type,
|
types: anymation_type,
|
||||||
nsfw: Some(&Nsfw::False),
|
nsfw: Some(&Nsfw::False),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -275,19 +280,15 @@ async fn get_images_for_ids(
|
|||||||
nsfw: Some(&Nsfw::False),
|
nsfw: Some(&Nsfw::False),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let query_type = match image_type {
|
let query_type = match image_type {
|
||||||
ImageType::Hero => Hero(Some(hero_parameters)),
|
ImageType::Hero => steamgriddb_api::QueryType::Hero(Some(hero_parameters)),
|
||||||
ImageType::BigPicture => Grid(Some(big_picture_parameters)),
|
ImageType::BigPicture => steamgriddb_api::QueryType::Grid(Some(big_picture_parameters)),
|
||||||
ImageType::Grid => Grid(Some(grid_parameters)),
|
ImageType::Grid => steamgriddb_api::QueryType::Grid(Some(grid_parameters)),
|
||||||
ImageType::WideGrid => Grid(Some(big_picture_parameters)),
|
ImageType::WideGrid => steamgriddb_api::QueryType::Grid(Some(big_picture_parameters)),
|
||||||
ImageType::Logo => Logo(Some(logo_parameters)),
|
ImageType::Logo => steamgriddb_api::QueryType::Logo(Some(logo_parameters)),
|
||||||
_ => panic!("Unsupported image type"),
|
_ => panic!("Unsupported image type"),
|
||||||
};
|
};
|
||||||
|
query_type
|
||||||
let image_search_result = client.get_images_for_ids(image_ids, &query_type).await;
|
|
||||||
|
|
||||||
image_search_result.map_err(|e| format!("Image search failed {:?}",e))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_steam_image_url(game_id: usize, image_type: &ImageType) -> Option<String> {
|
async fn get_steam_image_url(game_id: usize, image_type: &ImageType) -> Option<String> {
|
||||||
@@ -345,7 +346,7 @@ fn icon_url(steam_app_id: &str, icon_id: &str) -> String {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn download_to_download(to_download: &ToDownload) -> Result<(), Box<dyn Error>> {
|
pub async fn download_to_download(to_download: &ToDownload) -> Result<(), Box<dyn Error>> {
|
||||||
println!(
|
println!(
|
||||||
"Downloading {:?} for {} to {:?}",
|
"Downloading {:?} for {} to {:?}",
|
||||||
to_download.image_type, to_download.app_name, to_download.path
|
to_download.image_type, to_download.app_name, to_download.path
|
||||||
@@ -360,8 +361,11 @@ async fn download_to_download(to_download: &ToDownload) -> Result<(), Box<dyn Er
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ToDownload {
|
pub struct ToDownload {
|
||||||
path: PathBuf,
|
pub path: PathBuf,
|
||||||
url: String,
|
pub url: String,
|
||||||
app_name: String,
|
pub app_name: String,
|
||||||
image_type: ImageType,
|
pub image_type: ImageType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for ToDownload {
|
||||||
|
}
|
||||||
@@ -1,14 +1,33 @@
|
|||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy,PartialEq)]
|
||||||
pub enum ImageType {
|
pub enum ImageType {
|
||||||
Hero,
|
Hero,
|
||||||
Grid,
|
Grid,
|
||||||
WideGrid,
|
WideGrid,
|
||||||
Logo,
|
Logo,
|
||||||
BigPicture,
|
BigPicture,
|
||||||
Icon
|
Icon,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const ALL_TYPES : [ImageType;6] =
|
||||||
|
[ImageType::Hero,ImageType::Grid,ImageType::WideGrid,ImageType::Logo,ImageType::BigPicture,ImageType::Icon];
|
||||||
|
|
||||||
impl ImageType {
|
impl ImageType {
|
||||||
|
|
||||||
|
pub fn all() -> &'static [ImageType;6]{
|
||||||
|
&ALL_TYPES
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &str{
|
||||||
|
match self {
|
||||||
|
ImageType::Hero => "Hero",
|
||||||
|
ImageType::Grid => "Grid",
|
||||||
|
ImageType::WideGrid => "Wide Grid",
|
||||||
|
ImageType::Logo => "Logo",
|
||||||
|
ImageType::BigPicture => "Big Picture",
|
||||||
|
ImageType::Icon => "Icon",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn file_name(&self, app_id: u32) -> String {
|
pub fn file_name(&self, app_id: u32) -> String {
|
||||||
match self {
|
match self {
|
||||||
ImageType::Hero => format!("{}_hero.png", app_id),
|
ImageType::Hero => format!("{}_hero.png", app_id),
|
||||||
@@ -20,18 +39,31 @@ impl ImageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn steam_url<S:AsRef<str>>(&self,steam_app_id:S,mtime:u64) -> String{
|
pub fn steam_url<S: AsRef<str>>(&self, steam_app_id: S, mtime: u64) -> String {
|
||||||
let steam_app_id = steam_app_id.as_ref();
|
let steam_app_id = steam_app_id.as_ref();
|
||||||
match self{
|
match self {
|
||||||
ImageType::Hero => format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{}/library_hero.jpg?t={}",steam_app_id,mtime),
|
ImageType::Hero => format!(
|
||||||
ImageType::Grid => format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{}/library_600x900_2x.jpg?t={}",steam_app_id,mtime),
|
"https://cdn.cloudflare.steamstatic.com/steam/apps/{}/library_hero.jpg?t={}",
|
||||||
ImageType::WideGrid => format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{}/header.jpg?t={}",steam_app_id,mtime),
|
steam_app_id, mtime
|
||||||
ImageType::Logo => format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{}/logo.png?t={}",steam_app_id,mtime),
|
),
|
||||||
ImageType::BigPicture => format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{}/header.jpg?t={}",steam_app_id,mtime),
|
ImageType::Grid => format!(
|
||||||
|
"https://cdn.cloudflare.steamstatic.com/steam/apps/{}/library_600x900_2x.jpg?t={}",
|
||||||
|
steam_app_id, mtime
|
||||||
|
),
|
||||||
|
ImageType::WideGrid => format!(
|
||||||
|
"https://cdn.cloudflare.steamstatic.com/steam/apps/{}/header.jpg?t={}",
|
||||||
|
steam_app_id, mtime
|
||||||
|
),
|
||||||
|
ImageType::Logo => format!(
|
||||||
|
"https://cdn.cloudflare.steamstatic.com/steam/apps/{}/logo.png?t={}",
|
||||||
|
steam_app_id, mtime
|
||||||
|
),
|
||||||
|
ImageType::BigPicture => format!(
|
||||||
|
"https://cdn.cloudflare.steamstatic.com/steam/apps/{}/header.jpg?t={}",
|
||||||
|
steam_app_id, mtime
|
||||||
|
),
|
||||||
// This should not happen
|
// This should not happen
|
||||||
_ => "".to_string()
|
_ => "".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize,Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct SteamGridDbSettings {
|
pub struct SteamGridDbSettings {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub auth_key: Option<String>,
|
pub auth_key: Option<String>,
|
||||||
|
|||||||
+2
-2
@@ -2,8 +2,8 @@
|
|||||||
mod symlinks;
|
mod symlinks;
|
||||||
mod synchronization;
|
mod synchronization;
|
||||||
|
|
||||||
pub use synchronization::run_sync;
|
|
||||||
pub use synchronization::download_images;
|
pub use synchronization::download_images;
|
||||||
pub use synchronization::get_platform_shortcuts;
|
pub use synchronization::get_platform_shortcuts;
|
||||||
|
pub use synchronization::run_sync;
|
||||||
|
|
||||||
pub use synchronization::SyncProgress;
|
pub use synchronization::SyncProgress;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ pub fn create_sym_links(shortcut: &ShortcutOwned) -> ShortcutOwned {
|
|||||||
);
|
);
|
||||||
let mut new_shortcut = new_shortcut.to_owned();
|
let mut new_shortcut = new_shortcut.to_owned();
|
||||||
new_shortcut.tags = shortcut.tags.clone();
|
new_shortcut.tags = shortcut.tags.clone();
|
||||||
new_shortcut.dev_kit_game_id = shortcut.dev_kit_game_id.clone();
|
new_shortcut.dev_kit_game_id = shortcut.dev_kit_game_id.clone();
|
||||||
new_shortcut
|
new_shortcut
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|||||||
+36
-29
@@ -12,7 +12,7 @@ use crate::{
|
|||||||
Collection, ShortcutInfo, SteamUsersInfo,
|
Collection, ShortcutInfo, SteamUsersInfo,
|
||||||
},
|
},
|
||||||
steamgriddb::{download_images_for_users, ImageType},
|
steamgriddb::{download_images_for_users, ImageType},
|
||||||
uplay::Uplay,
|
uplay::Uplay,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
@@ -25,27 +25,24 @@ use std::{fs::File, io::Write, path::Path};
|
|||||||
|
|
||||||
const BOILR_TAG: &str = "boilr";
|
const BOILR_TAG: &str = "boilr";
|
||||||
|
|
||||||
|
pub enum SyncProgress {
|
||||||
pub enum SyncProgress{
|
|
||||||
NotStarted,
|
NotStarted,
|
||||||
Starting,
|
Starting,
|
||||||
FoundGames{
|
FoundGames { games_found: usize },
|
||||||
games_found:usize
|
|
||||||
},
|
|
||||||
FindingImages,
|
FindingImages,
|
||||||
DownloadingImages{
|
DownloadingImages { to_download: usize },
|
||||||
to_download:usize,
|
Done,
|
||||||
},
|
|
||||||
Done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn run_sync(
|
||||||
pub fn run_sync(settings: &Settings, sender: &mut Option<Sender<SyncProgress>>) -> Result<Vec<SteamUsersInfo>, String> {
|
settings: &Settings,
|
||||||
if let Some(sender) = &sender{
|
sender: &mut Option<Sender<SyncProgress>>,
|
||||||
|
) -> Result<Vec<SteamUsersInfo>, String> {
|
||||||
|
if let Some(sender) = &sender {
|
||||||
let _ = sender.send(SyncProgress::Starting);
|
let _ = sender.send(SyncProgress::Starting);
|
||||||
}
|
}
|
||||||
let mut userinfo_shortcuts = get_shortcuts_paths(&settings.steam)
|
let mut userinfo_shortcuts = get_shortcuts_paths(&settings.steam)
|
||||||
.map_err(|e| format!("Getting shortcut paths failed: {e}"))?;
|
.map_err(|e| format!("Getting shortcut paths failed: {e}"))?;
|
||||||
|
|
||||||
let platform_shortcuts = get_platform_shortcuts(settings);
|
let platform_shortcuts = get_platform_shortcuts(settings);
|
||||||
let all_shortcuts: Vec<ShortcutOwned> = platform_shortcuts
|
let all_shortcuts: Vec<ShortcutOwned> = platform_shortcuts
|
||||||
@@ -53,8 +50,10 @@ pub fn run_sync(settings: &Settings, sender: &mut Option<Sender<SyncProgress>>)
|
|||||||
.flat_map(|s| s.1.clone())
|
.flat_map(|s| s.1.clone())
|
||||||
.filter(|s| !settings.blacklisted_games.contains(&s.app_id))
|
.filter(|s| !settings.blacklisted_games.contains(&s.app_id))
|
||||||
.collect();
|
.collect();
|
||||||
if let Some(sender) = &sender{
|
if let Some(sender) = &sender {
|
||||||
let _ = sender.send(SyncProgress::FoundGames { games_found: all_shortcuts.len() });
|
let _ = sender.send(SyncProgress::FoundGames {
|
||||||
|
games_found: all_shortcuts.len(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
for shortcut in &all_shortcuts {
|
for shortcut in &all_shortcuts {
|
||||||
println!("Appid: {} name: {}", shortcut.app_id, shortcut.app_name);
|
println!("Appid: {} name: {}", shortcut.app_id, shortcut.app_name);
|
||||||
@@ -91,33 +90,36 @@ pub fn run_sync(settings: &Settings, sender: &mut Option<Sender<SyncProgress>>)
|
|||||||
|
|
||||||
let duration = start_time.elapsed();
|
let duration = start_time.elapsed();
|
||||||
println!("Finished synchronizing games in: {:?}", duration);
|
println!("Finished synchronizing games in: {:?}", duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(userinfo_shortcuts)
|
Ok(userinfo_shortcuts)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn download_images(settings: &Settings, userinfo_shortcuts: &Vec<SteamUsersInfo>,sender: &mut Option<Sender<SyncProgress>>) {
|
pub async fn download_images(
|
||||||
|
settings: &Settings,
|
||||||
|
userinfo_shortcuts: &[SteamUsersInfo],
|
||||||
|
sender: &mut Option<Sender<SyncProgress>>,
|
||||||
|
) {
|
||||||
if settings.steamgrid_db.enabled {
|
if settings.steamgrid_db.enabled {
|
||||||
if settings.steamgrid_db.prefer_animated {
|
if settings.steamgrid_db.prefer_animated {
|
||||||
println!("downloading animated images");
|
println!("downloading animated images");
|
||||||
download_images_for_users(settings, userinfo_shortcuts, true,sender).await;
|
download_images_for_users(settings, userinfo_shortcuts, true, sender).await;
|
||||||
}
|
}
|
||||||
download_images_for_users(settings, userinfo_shortcuts, false,sender).await;
|
download_images_for_users(settings, userinfo_shortcuts, false, sender).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait IsBoilRShortcut{
|
trait IsBoilRShortcut {
|
||||||
fn is_boilr_tag(&self) -> bool;
|
fn is_boilr_tag(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IsBoilRShortcut for ShortcutOwned{
|
impl IsBoilRShortcut for ShortcutOwned {
|
||||||
fn is_boilr_tag(&self) -> bool {
|
fn is_boilr_tag(&self) -> bool {
|
||||||
let boilr_tag = BOILR_TAG.to_string();
|
let boilr_tag = BOILR_TAG.to_string();
|
||||||
self.tags.contains(&boilr_tag) || self.dev_kit_game_id.starts_with(&boilr_tag)
|
self.tags.contains(&boilr_tag) || self.dev_kit_game_id.starts_with(&boilr_tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn remove_old_shortcuts(shortcut_info: &mut ShortcutInfo) {
|
fn remove_old_shortcuts(shortcut_info: &mut ShortcutInfo) {
|
||||||
shortcut_info
|
shortcut_info
|
||||||
.shortcuts
|
.shortcuts
|
||||||
@@ -138,15 +140,20 @@ fn fix_shortcut_icons(
|
|||||||
ImageType::Icon
|
ImageType::Icon
|
||||||
};
|
};
|
||||||
|
|
||||||
for shortcut in shortcuts {
|
for shortcut in shortcuts {
|
||||||
if shortcut.is_boilr_tag(){
|
if shortcut.is_boilr_tag() {
|
||||||
let replace_icon = shortcut.icon.trim().eq("") || !Path::new(shortcut.icon.trim()).exists() || shortcut.icon.eq(&shortcut.exe);
|
let replace_icon = shortcut.icon.trim().eq("")
|
||||||
|
|| !Path::new(shortcut.icon.trim()).exists()
|
||||||
|
|| shortcut.icon.eq(&shortcut.exe);
|
||||||
if replace_icon {
|
if replace_icon {
|
||||||
let app_id = steam_shortcuts_util::app_id_generator::calculate_app_id(
|
let app_id = steam_shortcuts_util::app_id_generator::calculate_app_id(
|
||||||
&shortcut.exe,
|
&shortcut.exe,
|
||||||
&shortcut.app_name,
|
&shortcut.app_name,
|
||||||
);
|
);
|
||||||
shortcut.icon = image_folder.join(image_type.file_name(app_id)).to_string_lossy().to_string();
|
shortcut.icon = image_folder
|
||||||
|
.join(image_type.file_name(app_id))
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,7 +201,7 @@ pub fn get_platform_shortcuts(settings: &Settings) -> Vec<(String, Vec<ShortcutO
|
|||||||
settings: settings.heroic.clone(),
|
settings: settings.heroic.clone(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
platform_results.push(update_platform_shortcuts(&crate::amazon::AmazonPlatform {
|
platform_results.push(update_platform_shortcuts(&crate::amazon::AmazonPlatform {
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ pub mod ui_colors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod ui_images {
|
pub mod ui_images {
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use eframe::epi::IconData;
|
use eframe::epi::IconData;
|
||||||
use egui::{ColorImage, ImageData};
|
use egui::{ColorImage, ImageData};
|
||||||
|
|
||||||
@@ -36,6 +38,14 @@ pub mod ui_images {
|
|||||||
rgba: pixels.as_slice().to_vec(),
|
rgba: pixels.as_slice().to_vec(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn load_image_from_path(path: &Path) -> Option<ColorImage> {
|
||||||
|
if path.exists() {
|
||||||
|
if let Ok(data) = std::fs::read(path) {
|
||||||
|
return load_image_from_memory(&data).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn load_image_from_memory(image_data: &[u8]) -> Result<ColorImage, image::ImageError> {
|
fn load_image_from_memory(image_data: &[u8]) -> Result<ColorImage, image::ImageError> {
|
||||||
let image = image::load_from_memory(image_data)?;
|
let image = image::load_from_memory(image_data)?;
|
||||||
|
|||||||
+9
-2
@@ -1,4 +1,11 @@
|
|||||||
mod uiapp;
|
|
||||||
mod defines;
|
mod defines;
|
||||||
pub use uiapp::*;
|
mod ui_image_download;
|
||||||
|
mod ui_import_games;
|
||||||
|
mod ui_settings;
|
||||||
|
mod uiapp;
|
||||||
|
|
||||||
pub use defines::*;
|
pub use defines::*;
|
||||||
|
pub use ui_image_download::*;
|
||||||
|
pub use ui_import_games::*;
|
||||||
|
pub use ui_settings::*;
|
||||||
|
pub use uiapp::*;
|
||||||
|
|||||||
@@ -0,0 +1,409 @@
|
|||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
steam::{get_shortcuts_paths, SteamUsersInfo},
|
||||||
|
steamgriddb::{CachedSearch, ImageType, get_query_type, ToDownload},
|
||||||
|
};
|
||||||
|
use dashmap::DashMap;
|
||||||
|
use egui::{ImageButton, ScrollArea};
|
||||||
|
use futures::executor::block_on;
|
||||||
|
use steam_shortcuts_util::shortcut::ShortcutOwned;
|
||||||
|
use tokio::sync::watch::{self, Receiver};
|
||||||
|
|
||||||
|
use super::{ui_images::load_image_from_path, FetcStatus, MyEguiApp};
|
||||||
|
|
||||||
|
pub struct ImageSelectState {
|
||||||
|
pub selected_image: Option<ShortcutOwned>,
|
||||||
|
pub grid_id: Option<usize>,
|
||||||
|
|
||||||
|
pub hero_image: Option<egui::TextureHandle>,
|
||||||
|
pub grid_image: Option<egui::TextureHandle>,
|
||||||
|
pub logo_image: Option<egui::TextureHandle>,
|
||||||
|
pub icon_image: Option<egui::TextureHandle>,
|
||||||
|
pub wide_image: Option<egui::TextureHandle>,
|
||||||
|
|
||||||
|
pub steam_user: Option<SteamUsersInfo>,
|
||||||
|
pub steam_users: Option<Vec<SteamUsersInfo>>,
|
||||||
|
|
||||||
|
pub image_to_replace: Option<ImageType>,
|
||||||
|
pub image_options: Receiver<FetcStatus<Vec<PossibleImage>>>,
|
||||||
|
|
||||||
|
pub image_handles: DashMap<String,egui::TextureHandle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PossibleImage{
|
||||||
|
thumbnail_path: PathBuf,
|
||||||
|
full_url: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ImageSelectState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
selected_image: Default::default(),
|
||||||
|
grid_id: Default::default(),
|
||||||
|
hero_image: Default::default(),
|
||||||
|
grid_image: Default::default(),
|
||||||
|
logo_image: Default::default(),
|
||||||
|
icon_image: Default::default(),
|
||||||
|
wide_image: Default::default(),
|
||||||
|
steam_user: Default::default(),
|
||||||
|
steam_users: Default::default(),
|
||||||
|
image_to_replace: Default::default(),
|
||||||
|
image_options: watch::channel(FetcStatus::NeedsFetched).1,
|
||||||
|
image_handles: DashMap::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MyEguiApp {
|
||||||
|
pub(crate) fn render_ui_images(&mut self, ui: &mut egui::Ui) {
|
||||||
|
self.ensure_games_loaded();
|
||||||
|
|
||||||
|
ui.heading("Images");
|
||||||
|
|
||||||
|
match &self.image_selected_state.steam_user {
|
||||||
|
Some(user) => {
|
||||||
|
if self.image_selected_state.selected_image.is_some(){
|
||||||
|
if ui.button("Back").clicked() {
|
||||||
|
if self.image_selected_state.image_to_replace.is_some(){
|
||||||
|
self.image_selected_state.image_to_replace = None;
|
||||||
|
}else{
|
||||||
|
self.image_selected_state.selected_image = None;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollArea::vertical()
|
||||||
|
.stick_to_right()
|
||||||
|
.auto_shrink([false, true])
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.reset_style();
|
||||||
|
let borrowed_games = &*self.games_to_sync.borrow();
|
||||||
|
match borrowed_games {
|
||||||
|
super::FetcStatus::Fetched(games_to_sync) => {
|
||||||
|
match &self.image_selected_state.selected_image {
|
||||||
|
Some(selected_image) => {
|
||||||
|
|
||||||
|
ui.heading(&selected_image.app_name);
|
||||||
|
let mut reset = false;
|
||||||
|
if let Some(selected_image_type) =
|
||||||
|
&self.image_selected_state.image_to_replace
|
||||||
|
{
|
||||||
|
let borrowed_images =
|
||||||
|
&*self.image_selected_state.image_options.borrow();
|
||||||
|
match borrowed_images {
|
||||||
|
FetcStatus::Fetched(images) => {
|
||||||
|
for image in images{
|
||||||
|
let image_key = image.thumbnail_path.as_path().to_string_lossy().to_string();
|
||||||
|
if ! self.image_selected_state.image_handles.contains_key(&image_key){
|
||||||
|
//TODO remove this unwrap
|
||||||
|
let image_data = load_image_from_path(&image.thumbnail_path).unwrap();
|
||||||
|
let handle = ui.ctx().load_texture(&image_key, image_data);
|
||||||
|
self.image_selected_state.image_handles.insert(image_key.clone(),handle);
|
||||||
|
}
|
||||||
|
if let Some(texture_handle) = self.image_selected_state.image_handles.get(&image_key){
|
||||||
|
let mut size = texture_handle.size_vec2();
|
||||||
|
clamp_to_width(&mut size,MAX_WIDTH);
|
||||||
|
let image_button = ImageButton::new(texture_handle.value(), size);
|
||||||
|
if ui.add(image_button).clicked(){
|
||||||
|
let to =
|
||||||
|
Path::new(&user.steam_user_data_folder)
|
||||||
|
.join("config")
|
||||||
|
.join("grid")
|
||||||
|
.join(selected_image_type.file_name(selected_image.app_id));
|
||||||
|
let app_name = selected_image.app_name.clone();
|
||||||
|
|
||||||
|
let to_download = ToDownload{
|
||||||
|
path: to,
|
||||||
|
url: image.full_url.clone(),
|
||||||
|
app_name: app_name.clone(),
|
||||||
|
image_type: selected_image_type.clone()
|
||||||
|
};
|
||||||
|
//TODO make this actually parallel
|
||||||
|
self.rt.spawn_blocking(move ||{
|
||||||
|
let _ = block_on(crate::steamgriddb::download_to_download(&to_download));
|
||||||
|
});
|
||||||
|
|
||||||
|
let image_ref = match selected_image_type {
|
||||||
|
ImageType::Hero => {
|
||||||
|
&mut self.image_selected_state.hero_image
|
||||||
|
}
|
||||||
|
ImageType::Grid => {
|
||||||
|
&mut self.image_selected_state.grid_image
|
||||||
|
}
|
||||||
|
ImageType::WideGrid => {
|
||||||
|
&mut self.image_selected_state.wide_image
|
||||||
|
}
|
||||||
|
ImageType::Logo => {
|
||||||
|
&mut self.image_selected_state.logo_image
|
||||||
|
}
|
||||||
|
ImageType::BigPicture => {
|
||||||
|
&mut self.image_selected_state.wide_image
|
||||||
|
}
|
||||||
|
ImageType::Icon => {
|
||||||
|
&mut self.image_selected_state.icon_image
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*image_ref = Some(texture_handle.clone());
|
||||||
|
reset = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
ui.label("Finding possible images");
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Some(grid_id) = self.image_selected_state.grid_id
|
||||||
|
{
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Grid id:");
|
||||||
|
let mut text_id = format!("{}", grid_id);
|
||||||
|
if ui
|
||||||
|
.text_edit_singleline(&mut text_id)
|
||||||
|
.changed()
|
||||||
|
{
|
||||||
|
if let Ok(grid_id) =
|
||||||
|
text_id.parse::<usize>()
|
||||||
|
{
|
||||||
|
if let Some(auth_key) =
|
||||||
|
&self.settings.steamgrid_db.auth_key
|
||||||
|
{
|
||||||
|
let client =
|
||||||
|
steamgriddb_api::Client::new(
|
||||||
|
auth_key,
|
||||||
|
);
|
||||||
|
let mut search =
|
||||||
|
CachedSearch::new(&client);
|
||||||
|
search.set_cache(
|
||||||
|
selected_image.app_id,
|
||||||
|
selected_image
|
||||||
|
.app_name
|
||||||
|
.to_string(),
|
||||||
|
grid_id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.image_selected_state.grid_id =
|
||||||
|
Some(grid_id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for image_type in ImageType::all() {
|
||||||
|
ui.label(image_type.name());
|
||||||
|
|
||||||
|
let image_ref = match image_type {
|
||||||
|
ImageType::Hero => {
|
||||||
|
&mut self.image_selected_state.hero_image
|
||||||
|
}
|
||||||
|
ImageType::Grid => {
|
||||||
|
&mut self.image_selected_state.grid_image
|
||||||
|
}
|
||||||
|
ImageType::WideGrid => {
|
||||||
|
&mut self.image_selected_state.wide_image
|
||||||
|
}
|
||||||
|
ImageType::Logo => {
|
||||||
|
&mut self.image_selected_state.logo_image
|
||||||
|
}
|
||||||
|
ImageType::BigPicture => {
|
||||||
|
&mut self.image_selected_state.wide_image
|
||||||
|
}
|
||||||
|
ImageType::Icon => {
|
||||||
|
&mut self.image_selected_state.icon_image
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if render_image(ui, image_ref) {
|
||||||
|
self.image_selected_state.image_to_replace =
|
||||||
|
Some(image_type.clone());
|
||||||
|
let (mut tx,rx)= watch::channel(FetcStatus::Fetching);
|
||||||
|
self.image_selected_state.image_options = rx;
|
||||||
|
let settings = self.settings.clone();
|
||||||
|
if let Some(auth_key) = settings.steamgrid_db.auth_key{
|
||||||
|
if let Some(grid_id) = self.image_selected_state.grid_id{
|
||||||
|
let auth_key = auth_key.clone();
|
||||||
|
let image_type = image_type.clone();
|
||||||
|
let app_name = selected_image.app_name.clone();
|
||||||
|
self.rt.spawn_blocking( move|| {
|
||||||
|
//Find somewhere else to put this
|
||||||
|
std::fs::create_dir_all(".thumbnails");
|
||||||
|
let thumbnails_folder = Path::new(".thumbnails");
|
||||||
|
let client =steamgriddb_api::Client::new(auth_key);
|
||||||
|
let query = get_query_type(false,&image_type);
|
||||||
|
let search_res = block_on(client.get_images_for_id(grid_id, &query));
|
||||||
|
|
||||||
|
if let Ok(possible_images) = search_res{
|
||||||
|
let mut result = vec![];
|
||||||
|
for possible_image in &possible_images{
|
||||||
|
let path = thumbnails_folder.join(format!("{}.png",possible_image.id));
|
||||||
|
|
||||||
|
if !&path.exists(){
|
||||||
|
let to_download = ToDownload{
|
||||||
|
path: path.clone(),
|
||||||
|
url: possible_image.thumb.clone(),
|
||||||
|
app_name: app_name.clone(),
|
||||||
|
image_type: image_type.clone()
|
||||||
|
};
|
||||||
|
//TODO make this actually parallel
|
||||||
|
block_on(crate::steamgriddb::download_to_download(&to_download));
|
||||||
|
}
|
||||||
|
result.push(PossibleImage { thumbnail_path: path, full_url: possible_image.url.clone() });
|
||||||
|
let _ = tx.send(FetcStatus::Fetched(result.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reset {
|
||||||
|
self.image_selected_state.image_to_replace = None;
|
||||||
|
self.image_selected_state.image_options = watch::channel(FetcStatus::NeedsFetched).1;
|
||||||
|
self.image_selected_state.image_handles.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
for (platform_name, shortcuts) in games_to_sync {
|
||||||
|
ui.heading(platform_name);
|
||||||
|
for shortcut in shortcuts {
|
||||||
|
if ui.button(&shortcut.app_name).clicked() {
|
||||||
|
if let Some(auth_key) =
|
||||||
|
&self.settings.steamgrid_db.auth_key
|
||||||
|
{
|
||||||
|
let client =
|
||||||
|
steamgriddb_api::Client::new(auth_key);
|
||||||
|
let search = CachedSearch::new(&client);
|
||||||
|
//TODO make this multithreaded
|
||||||
|
self.image_selected_state.grid_id = self
|
||||||
|
.rt
|
||||||
|
.block_on(search.search(
|
||||||
|
shortcut.app_id,
|
||||||
|
&shortcut.app_name,
|
||||||
|
))
|
||||||
|
.ok()
|
||||||
|
.flatten();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.image_selected_state.selected_image =
|
||||||
|
Some(shortcut.clone());
|
||||||
|
|
||||||
|
let folder =
|
||||||
|
Path::new(&user.steam_user_data_folder)
|
||||||
|
.join("config")
|
||||||
|
.join("grid");
|
||||||
|
|
||||||
|
//TODO put this in seperate thread
|
||||||
|
self.image_selected_state.hero_image =
|
||||||
|
get_image(
|
||||||
|
ui,
|
||||||
|
shortcut,
|
||||||
|
&folder,
|
||||||
|
&ImageType::Hero,
|
||||||
|
);
|
||||||
|
self.image_selected_state.grid_image =
|
||||||
|
get_image(
|
||||||
|
ui,
|
||||||
|
shortcut,
|
||||||
|
&folder,
|
||||||
|
&ImageType::Grid,
|
||||||
|
);
|
||||||
|
self.image_selected_state.icon_image =
|
||||||
|
get_image(
|
||||||
|
ui,
|
||||||
|
shortcut,
|
||||||
|
&folder,
|
||||||
|
&ImageType::Icon,
|
||||||
|
);
|
||||||
|
self.image_selected_state.logo_image =
|
||||||
|
get_image(
|
||||||
|
ui,
|
||||||
|
shortcut,
|
||||||
|
&folder,
|
||||||
|
&ImageType::Logo,
|
||||||
|
);
|
||||||
|
self.image_selected_state.wide_image =
|
||||||
|
get_image(
|
||||||
|
ui,
|
||||||
|
shortcut,
|
||||||
|
&folder,
|
||||||
|
&ImageType::WideGrid,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
ui.label("Finding installed games");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let users = self
|
||||||
|
.image_selected_state
|
||||||
|
.steam_users
|
||||||
|
.get_or_insert_with(|| {
|
||||||
|
get_shortcuts_paths(&self.settings.steam).expect("Should have steam user")
|
||||||
|
});
|
||||||
|
if users.len() == 1{
|
||||||
|
self.image_selected_state.steam_user = Some(users[0].clone())
|
||||||
|
}
|
||||||
|
for user in users {
|
||||||
|
if ui.button(&user.user_id).clicked() {
|
||||||
|
self.image_selected_state.steam_user = Some(user.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_WIDTH:f32 = 300.;
|
||||||
|
|
||||||
|
fn render_image(ui: &mut egui::Ui, image: &mut Option<egui::TextureHandle>) -> bool {
|
||||||
|
match image {
|
||||||
|
Some(texture) => {
|
||||||
|
let mut size = texture.size_vec2();
|
||||||
|
clamp_to_width(&mut size,MAX_WIDTH);
|
||||||
|
let image_button = ImageButton::new(texture, size);
|
||||||
|
ui.add(image_button)
|
||||||
|
.on_hover_text("Click to change image")
|
||||||
|
.clicked()
|
||||||
|
}
|
||||||
|
None => ui.button("Pick an image").clicked(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clamp_to_width(size: &mut egui::Vec2, max_width :f32) {
|
||||||
|
let mut x = size.x;
|
||||||
|
let mut y = size.y;
|
||||||
|
if size.x > max_width{
|
||||||
|
let ratio = size.y / size.x;
|
||||||
|
x = max_width;
|
||||||
|
y = x * ratio;
|
||||||
|
}
|
||||||
|
size.x = x;
|
||||||
|
size.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_image(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
shortcut: &ShortcutOwned,
|
||||||
|
folder: &std::path::Path,
|
||||||
|
image_type: &ImageType,
|
||||||
|
) -> Option<egui::TextureHandle> {
|
||||||
|
let file_name = ImageType::file_name(image_type, shortcut.app_id);
|
||||||
|
let file_path = folder.join(file_name);
|
||||||
|
let image = load_image_from_path(file_path.as_path()).map(|img_data| {
|
||||||
|
ui.ctx()
|
||||||
|
.load_texture(file_path.to_string_lossy().to_string(), img_data)
|
||||||
|
});
|
||||||
|
image
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
use eframe::egui;
|
||||||
|
use egui::ScrollArea;
|
||||||
|
use futures::executor::block_on;
|
||||||
|
|
||||||
|
use tokio::sync::watch;
|
||||||
|
|
||||||
|
use crate::settings::Settings;
|
||||||
|
use crate::sync;
|
||||||
|
|
||||||
|
use crate::sync::{download_images, SyncProgress};
|
||||||
|
|
||||||
|
use super::ImageSelectState;
|
||||||
|
use super::{
|
||||||
|
ui_colors::{BACKGROUND_COLOR, EXTRA_BACKGROUND_COLOR},
|
||||||
|
MyEguiApp,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SECTION_SPACING: f32 = 25.0;
|
||||||
|
|
||||||
|
pub enum FetcStatus<T> {
|
||||||
|
NeedsFetched,
|
||||||
|
Fetching,
|
||||||
|
Fetched(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> FetcStatus<T> {
|
||||||
|
pub fn is_some(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
FetcStatus::NeedsFetched => false,
|
||||||
|
FetcStatus::Fetching => false,
|
||||||
|
FetcStatus::Fetched(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn needs_fetching(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
FetcStatus::NeedsFetched => true,
|
||||||
|
FetcStatus::Fetching => false,
|
||||||
|
FetcStatus::Fetched(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MyEguiApp {
|
||||||
|
pub(crate) fn render_import_games(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Import Games");
|
||||||
|
|
||||||
|
self.ensure_games_loaded();
|
||||||
|
|
||||||
|
let mut scroll_style = ui.style_mut();
|
||||||
|
scroll_style.visuals.extreme_bg_color = BACKGROUND_COLOR;
|
||||||
|
scroll_style.visuals.widgets.inactive.bg_fill = EXTRA_BACKGROUND_COLOR;
|
||||||
|
scroll_style.visuals.widgets.active.bg_fill = EXTRA_BACKGROUND_COLOR;
|
||||||
|
scroll_style.visuals.selection.bg_fill = EXTRA_BACKGROUND_COLOR;
|
||||||
|
scroll_style.visuals.widgets.hovered.bg_fill = EXTRA_BACKGROUND_COLOR;
|
||||||
|
|
||||||
|
ScrollArea::vertical()
|
||||||
|
.stick_to_right()
|
||||||
|
.auto_shrink([false,true])
|
||||||
|
.show(ui,|ui| {
|
||||||
|
ui.reset_style();
|
||||||
|
|
||||||
|
let borrowed_games = &*self.games_to_sync.borrow();
|
||||||
|
match borrowed_games{
|
||||||
|
FetcStatus::Fetched(games_to_sync) => {
|
||||||
|
ui.label("Select the games you want to import into steam");
|
||||||
|
for (platform_name, shortcuts) in games_to_sync{
|
||||||
|
ui.heading(platform_name);
|
||||||
|
for shortcut in shortcuts {
|
||||||
|
let mut import_game = !self.settings.blacklisted_games.contains(&shortcut.app_id);
|
||||||
|
let checkbox = egui::Checkbox::new(&mut import_game,&shortcut.app_name);
|
||||||
|
let response = ui.add(checkbox);
|
||||||
|
if response.clicked(){
|
||||||
|
if !self.settings.blacklisted_games.contains(&shortcut.app_id){
|
||||||
|
self.settings.blacklisted_games.push(shortcut.app_id);
|
||||||
|
}else{
|
||||||
|
self.settings.blacklisted_games.retain(|id| *id != shortcut.app_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui.add_space(SECTION_SPACING);
|
||||||
|
ui.label("Check the settings if BoilR didn't find the game you where looking for");
|
||||||
|
},
|
||||||
|
_=> {
|
||||||
|
ui.label("Finding installed games");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ensure_games_loaded(&mut self) {
|
||||||
|
if self.games_to_sync.borrow().needs_fetching() {
|
||||||
|
self.image_selected_state = ImageSelectState::default();
|
||||||
|
let (tx, rx) = watch::channel(FetcStatus::NeedsFetched);
|
||||||
|
self.games_to_sync = rx;
|
||||||
|
let settings = self.settings.clone();
|
||||||
|
self.rt.spawn_blocking(move || {
|
||||||
|
let _ = tx.send(FetcStatus::Fetching);
|
||||||
|
let games_to_sync = sync::get_platform_shortcuts(&settings);
|
||||||
|
let _ = tx.send(FetcStatus::Fetched(games_to_sync));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_sync(&mut self) {
|
||||||
|
let (sender, reciever) = watch::channel(SyncProgress::NotStarted);
|
||||||
|
let settings = self.settings.clone();
|
||||||
|
if settings.steam.stop_steam {
|
||||||
|
crate::steam::ensure_steam_stopped();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.status_reciever = reciever;
|
||||||
|
self.rt.spawn_blocking(move || {
|
||||||
|
MyEguiApp::save_settings_to_file(&settings);
|
||||||
|
let mut some_sender = Some(sender);
|
||||||
|
let usersinfo = sync::run_sync(&settings, &mut some_sender).unwrap();
|
||||||
|
let task = download_images(&settings, &usersinfo, &mut some_sender);
|
||||||
|
block_on(task);
|
||||||
|
if let Some(sender) = some_sender {
|
||||||
|
let _ = sender.send(SyncProgress::Done);
|
||||||
|
}
|
||||||
|
if settings.steam.start_steam {
|
||||||
|
crate::steam::ensure_steam_started(&settings.steam);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_settings_to_file(settings: &Settings) {
|
||||||
|
let toml = toml::to_string(&settings).unwrap();
|
||||||
|
std::fs::write("config.toml", toml).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
use eframe::egui;
|
||||||
|
use egui::ScrollArea;
|
||||||
|
|
||||||
|
use crate::egs::EpicPlatform;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
ui_colors::{BACKGROUND_COLOR, EXTRA_BACKGROUND_COLOR},
|
||||||
|
MyEguiApp,
|
||||||
|
};
|
||||||
|
const SECTION_SPACING: f32 = 25.0;
|
||||||
|
|
||||||
|
impl MyEguiApp {
|
||||||
|
pub(crate) fn render_settings(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Settings");
|
||||||
|
|
||||||
|
let mut scroll_style = ui.style_mut();
|
||||||
|
scroll_style.visuals.extreme_bg_color = BACKGROUND_COLOR;
|
||||||
|
scroll_style.visuals.widgets.inactive.bg_fill = EXTRA_BACKGROUND_COLOR;
|
||||||
|
scroll_style.visuals.widgets.active.bg_fill = EXTRA_BACKGROUND_COLOR;
|
||||||
|
scroll_style.visuals.selection.bg_fill = EXTRA_BACKGROUND_COLOR;
|
||||||
|
scroll_style.visuals.widgets.hovered.bg_fill = EXTRA_BACKGROUND_COLOR;
|
||||||
|
|
||||||
|
ScrollArea::vertical()
|
||||||
|
.stick_to_right()
|
||||||
|
.auto_shrink([false, true])
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.reset_style();
|
||||||
|
|
||||||
|
self.render_steamgriddb_settings(ui);
|
||||||
|
|
||||||
|
self.render_steam_settings(ui);
|
||||||
|
|
||||||
|
self.render_epic_settings(ui);
|
||||||
|
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
{
|
||||||
|
ui.heading("Heroic");
|
||||||
|
ui.checkbox(&mut self.settings.heroic.enabled, "Import form Heroic");
|
||||||
|
|
||||||
|
ui.add_space(SECTION_SPACING);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.render_legendary_settings(ui);
|
||||||
|
self.render_itch_settings(ui);
|
||||||
|
self.render_origin_settings(ui);
|
||||||
|
self.render_gog_settings(ui);
|
||||||
|
self.render_uplay_settings(ui);
|
||||||
|
self.render_lutris_settings(ui);
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
self.render_amazon_settings(ui);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_lutris_settings(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Lutris");
|
||||||
|
ui.checkbox(&mut self.settings.lutris.enabled, "Import form Lutris");
|
||||||
|
if self.settings.lutris.enabled {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let mut empty_string = "".to_string();
|
||||||
|
let lutris_location = self
|
||||||
|
.settings
|
||||||
|
.lutris
|
||||||
|
.executable
|
||||||
|
.as_mut()
|
||||||
|
.unwrap_or(&mut empty_string);
|
||||||
|
ui.label("Lutris Location: ");
|
||||||
|
if ui.text_edit_singleline(lutris_location).changed() {
|
||||||
|
self.settings.lutris.executable = Some(lutris_location.to_string());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ui.add_space(SECTION_SPACING);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn render_amazon_settings(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Amazon");
|
||||||
|
ui.checkbox(&mut self.settings.amazon.enabled, "Import form Amazon");
|
||||||
|
ui.add_space(SECTION_SPACING);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_uplay_settings(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Uplay");
|
||||||
|
ui.checkbox(&mut self.settings.uplay.enabled, "Import form Uplay");
|
||||||
|
ui.add_space(SECTION_SPACING);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_gog_settings(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("GoG Galaxy");
|
||||||
|
ui.checkbox(&mut self.settings.gog.enabled, "Import form GoG Galaxy");
|
||||||
|
if self.settings.gog.enabled {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let mut empty_string = "".to_string();
|
||||||
|
let itch_location = self
|
||||||
|
.settings
|
||||||
|
.gog
|
||||||
|
.location
|
||||||
|
.as_mut()
|
||||||
|
.unwrap_or(&mut empty_string);
|
||||||
|
ui.label("GoG Galaxy Folder: ");
|
||||||
|
if ui.text_edit_singleline(itch_location).changed() {
|
||||||
|
self.settings.gog.location = Some(itch_location.to_string());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ui.add_space(SECTION_SPACING);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_origin_settings(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Origin");
|
||||||
|
ui.checkbox(&mut self.settings.origin.enabled, "Import from Origin");
|
||||||
|
ui.add_space(SECTION_SPACING);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_itch_settings(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Itch.io");
|
||||||
|
ui.checkbox(&mut self.settings.itch.enabled, "Import form Itch.io");
|
||||||
|
if self.settings.itch.enabled {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let mut empty_string = "".to_string();
|
||||||
|
let itch_location = self
|
||||||
|
.settings
|
||||||
|
.itch
|
||||||
|
.location
|
||||||
|
.as_mut()
|
||||||
|
.unwrap_or(&mut empty_string);
|
||||||
|
ui.label("Itch.io Folder: ");
|
||||||
|
if ui.text_edit_singleline(itch_location).changed() {
|
||||||
|
self.settings.itch.location = Some(itch_location.to_string());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ui.add_space(SECTION_SPACING);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_steam_settings(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Steam");
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let mut empty_string = "".to_string();
|
||||||
|
let steam_location = self
|
||||||
|
.settings
|
||||||
|
.steam
|
||||||
|
.location
|
||||||
|
.as_mut()
|
||||||
|
.unwrap_or(&mut empty_string);
|
||||||
|
ui.label("Steam Location: ");
|
||||||
|
if ui.text_edit_singleline(steam_location).changed() {
|
||||||
|
self.settings.steam.location = Some(steam_location.to_string());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ui.checkbox(
|
||||||
|
&mut self.settings.steam.create_collections,
|
||||||
|
"Create collections",
|
||||||
|
)
|
||||||
|
.on_hover_text("Tries to create a games collection for each platform");
|
||||||
|
ui.checkbox(&mut self.settings.steam.optimize_for_big_picture, "Optimize for big picture").on_hover_text("Set icons to be larger horizontal images, this looks nice in steam big picture mode, but a bit off in desktop mode");
|
||||||
|
ui.checkbox(
|
||||||
|
&mut self.settings.steam.stop_steam,
|
||||||
|
"Stop Steam before import",
|
||||||
|
)
|
||||||
|
.on_hover_text("Stops Steam if it is running when import starts");
|
||||||
|
ui.checkbox(
|
||||||
|
&mut self.settings.steam.start_steam,
|
||||||
|
"Start Steam after import",
|
||||||
|
)
|
||||||
|
.on_hover_text("Starts Steam is it is not running after the import");
|
||||||
|
ui.add_space(SECTION_SPACING);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_steamgriddb_settings(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("SteamGridDB");
|
||||||
|
ui.checkbox(&mut self.settings.steamgrid_db.enabled, "Download images");
|
||||||
|
if self.settings.steamgrid_db.enabled {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let mut empty_string = "".to_string();
|
||||||
|
let auth_key = self
|
||||||
|
.settings
|
||||||
|
.steamgrid_db
|
||||||
|
.auth_key
|
||||||
|
.as_mut()
|
||||||
|
.unwrap_or(&mut empty_string);
|
||||||
|
ui.label("Authentication key: ");
|
||||||
|
if ui.text_edit_singleline(auth_key).changed() {
|
||||||
|
self.settings.steamgrid_db.auth_key = Some(auth_key.to_string());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(
|
||||||
|
"To download images you need an API Key from SteamGridDB, you can find yours",
|
||||||
|
);
|
||||||
|
ui.hyperlink_to(
|
||||||
|
"here",
|
||||||
|
"https://www.steamgriddb.com/profile/preferences/api",
|
||||||
|
)
|
||||||
|
});
|
||||||
|
ui.checkbox(&mut self.settings.steamgrid_db.prefer_animated, "Prefer animated images").on_hover_text("Prefer downloading animated images over static images (this can slow Steam down but looks neat)");
|
||||||
|
}
|
||||||
|
ui.add_space(SECTION_SPACING);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_legendary_settings(&mut self, ui: &mut egui::Ui) {
|
||||||
|
ui.heading("Legendary & Rare");
|
||||||
|
ui.checkbox(
|
||||||
|
&mut self.settings.legendary.enabled,
|
||||||
|
"Import form Legendary & Rare",
|
||||||
|
);
|
||||||
|
if self.settings.legendary.enabled {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let mut empty_string = "".to_string();
|
||||||
|
let legendary_location = self
|
||||||
|
.settings
|
||||||
|
.legendary
|
||||||
|
.executable
|
||||||
|
.as_mut()
|
||||||
|
.unwrap_or(&mut empty_string);
|
||||||
|
ui.label("Legendary Executable: ")
|
||||||
|
.on_hover_text("The location of the legendary executable to use");
|
||||||
|
if ui.text_edit_singleline(legendary_location).changed() {
|
||||||
|
self.settings.legendary.executable = Some(legendary_location.to_string());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ui.add_space(SECTION_SPACING);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_epic_settings(&mut self, ui: &mut egui::Ui) {
|
||||||
|
let epic_settings = &mut self.settings.epic_games;
|
||||||
|
ui.heading("Epic Games");
|
||||||
|
ui.checkbox(&mut epic_settings.enabled, "Import form Epic Games");
|
||||||
|
if epic_settings.enabled {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
let mut empty_string = "".to_string();
|
||||||
|
let epic_location = epic_settings.location.as_mut().unwrap_or(&mut empty_string);
|
||||||
|
ui.label("Epic Manifests Location: ").on_hover_text(
|
||||||
|
"The location where Epic stores its manifest files that BoilR needs to read",
|
||||||
|
);
|
||||||
|
if ui.text_edit_singleline(epic_location).changed() {
|
||||||
|
epic_settings.location = Some(epic_location.to_string());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let safe_mode_header = match epic_settings.safe_launch.len() {
|
||||||
|
0 => "Force games to launch through Epic Launcher".to_string(),
|
||||||
|
1 => "One game forced to launch through Epic Launcher".to_string(),
|
||||||
|
x => format!("{} games forced to launch through Epic Launcher", x),
|
||||||
|
};
|
||||||
|
|
||||||
|
egui::CollapsingHeader::new(safe_mode_header)
|
||||||
|
.id_source("Epic_Launcher_safe_launch")
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.label("Some games must be started from the Epic Launcher, select those games below and BoilR will create shortcuts that opens the games through the Epic Launcher.");
|
||||||
|
let manifests =self.epic_manifests.get_or_insert_with(||{
|
||||||
|
let epic_platform = EpicPlatform::new(epic_settings);
|
||||||
|
let manifests = crate::platform::Platform::get_shortcuts(&epic_platform);
|
||||||
|
manifests.unwrap_or_default()
|
||||||
|
});
|
||||||
|
let mut safe_open_games = epic_settings.safe_launch.clone();
|
||||||
|
for manifest in manifests{
|
||||||
|
let key = manifest.get_key();
|
||||||
|
let display_name = &manifest.display_name;
|
||||||
|
let mut safe_open = safe_open_games.contains(display_name) || safe_open_games.contains(&key);
|
||||||
|
if ui.checkbox(&mut safe_open, display_name).clicked(){
|
||||||
|
if safe_open{
|
||||||
|
safe_open_games.push(key);
|
||||||
|
}else{
|
||||||
|
safe_open_games.retain(|m| m!= display_name && m!= &key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
epic_settings.safe_launch = safe_open_games;
|
||||||
|
}) ;
|
||||||
|
ui.add_space(SECTION_SPACING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+128
-443
@@ -1,59 +1,44 @@
|
|||||||
use eframe::{egui, epi::{self},};
|
use std::error::Error;
|
||||||
use egui::{ScrollArea, TextureHandle, Stroke, Rounding, ImageButton};
|
|
||||||
use futures::executor::block_on;
|
use eframe::{egui, epi};
|
||||||
|
use egui::{ImageButton, Rounding, Stroke, TextureHandle};
|
||||||
use steam_shortcuts_util::shortcut::ShortcutOwned;
|
use steam_shortcuts_util::shortcut::ShortcutOwned;
|
||||||
use std::{error::Error};
|
use tokio::{
|
||||||
use tokio::{runtime::Runtime, sync::watch::{Receiver, self}};
|
runtime::Runtime,
|
||||||
|
sync::watch::{self, Receiver},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{settings::Settings, sync::{download_images, self, SyncProgress}, egs::{EpicPlatform, ManifestItem}};
|
use crate::{egs::ManifestItem, settings::Settings, sync::SyncProgress};
|
||||||
|
|
||||||
use super::{ui_images::{get_import_image, get_logo, get_logo_icon}, ui_colors::{TEXT_COLOR, BACKGROUND_COLOR, BG_STROKE_COLOR, ORANGE, PURLPLE, LIGHT_ORANGE, EXTRA_BACKGROUND_COLOR}};
|
use super::{
|
||||||
|
ui_colors::{
|
||||||
|
BACKGROUND_COLOR, BG_STROKE_COLOR, EXTRA_BACKGROUND_COLOR, LIGHT_ORANGE, ORANGE, PURLPLE,
|
||||||
|
TEXT_COLOR,
|
||||||
|
},
|
||||||
|
ui_images::{get_import_image, get_logo, get_logo_icon},
|
||||||
|
ui_import_games::FetcStatus,
|
||||||
|
ImageSelectState,
|
||||||
|
};
|
||||||
|
|
||||||
const SECTION_SPACING : f32 = 25.0;
|
const SECTION_SPACING: f32 = 25.0;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct UiImages{
|
struct UiImages {
|
||||||
import_button: Option<egui::TextureHandle>,
|
import_button: Option<egui::TextureHandle>,
|
||||||
logo_32: Option<egui::TextureHandle>,
|
logo_32: Option<egui::TextureHandle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct MyEguiApp {
|
||||||
enum FetchGameStatus{
|
|
||||||
NeedsFetched,
|
|
||||||
Fetching,
|
|
||||||
Fetched(Vec<(String, Vec<ShortcutOwned>)>)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FetchGameStatus{
|
|
||||||
pub fn is_some(&self) -> bool{
|
|
||||||
match self{
|
|
||||||
FetchGameStatus::NeedsFetched => false,
|
|
||||||
FetchGameStatus::Fetching => false,
|
|
||||||
FetchGameStatus::Fetched(_) => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn needs_fetching(&self) -> bool{
|
|
||||||
match self{
|
|
||||||
FetchGameStatus::NeedsFetched => true,
|
|
||||||
FetchGameStatus::Fetching => false,
|
|
||||||
FetchGameStatus::Fetched(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MyEguiApp {
|
|
||||||
selected_menu: Menues,
|
selected_menu: Menues,
|
||||||
settings: Settings,
|
pub(crate) settings: Settings,
|
||||||
rt: Runtime,
|
pub(crate) rt: Runtime,
|
||||||
ui_images: UiImages,
|
ui_images: UiImages,
|
||||||
games_to_sync: Receiver<FetchGameStatus>,
|
pub(crate) games_to_sync: Receiver<FetcStatus<Vec<(String, Vec<ShortcutOwned>)>>>,
|
||||||
status_reciever: Receiver<SyncProgress>,
|
pub(crate) status_reciever: Receiver<SyncProgress>,
|
||||||
epic_manifests: Option<Vec<ManifestItem>>,
|
pub(crate) epic_manifests: Option<Vec<ManifestItem>>,
|
||||||
|
pub(crate) image_selected_state: ImageSelectState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl MyEguiApp {
|
impl MyEguiApp {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let runtime = Runtime::new().unwrap();
|
let runtime = Runtime::new().unwrap();
|
||||||
@@ -61,47 +46,20 @@ impl MyEguiApp {
|
|||||||
selected_menu: Menues::Import,
|
selected_menu: Menues::Import,
|
||||||
settings: Settings::new().expect("We must be able to load our settings"),
|
settings: Settings::new().expect("We must be able to load our settings"),
|
||||||
rt: runtime,
|
rt: runtime,
|
||||||
games_to_sync:watch::channel(FetchGameStatus::NeedsFetched).1,
|
games_to_sync: watch::channel(FetcStatus::NeedsFetched).1,
|
||||||
ui_images: UiImages::default(),
|
ui_images: UiImages::default(),
|
||||||
status_reciever: watch::channel(SyncProgress::NotStarted).1,
|
status_reciever: watch::channel(SyncProgress::NotStarted).1,
|
||||||
epic_manifests : None,
|
epic_manifests: None,
|
||||||
|
image_selected_state: ImageSelectState::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn run_sync(&mut self) {
|
|
||||||
let (sender,reciever ) = watch::channel(SyncProgress::NotStarted);
|
|
||||||
let settings = self.settings.clone();
|
|
||||||
if settings.steam.stop_steam{
|
|
||||||
crate::steam::ensure_steam_stopped();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.status_reciever = reciever;
|
|
||||||
self.rt.spawn_blocking(move || {
|
|
||||||
|
|
||||||
MyEguiApp::save_settings_to_file(&settings);
|
|
||||||
let mut some_sender =Some(sender);
|
|
||||||
let usersinfo = sync::run_sync(&settings,&mut some_sender).unwrap();
|
|
||||||
let task = download_images(&settings, &usersinfo,&mut some_sender);
|
|
||||||
block_on(task);
|
|
||||||
if let Some(sender) = some_sender{
|
|
||||||
let _ = sender.send(SyncProgress::Done);
|
|
||||||
}
|
|
||||||
if settings.steam.start_steam{
|
|
||||||
crate::steam::ensure_steam_started(&settings.steam);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn save_settings_to_file(settings: &Settings) {
|
|
||||||
let toml = toml::to_string(&settings).unwrap();
|
|
||||||
std::fs::write("config.toml", toml).unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
enum Menues {
|
enum Menues {
|
||||||
Import,
|
Import,
|
||||||
Settings,
|
Settings,
|
||||||
|
Images,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Menues {
|
impl Default for Menues {
|
||||||
@@ -113,22 +71,24 @@ impl Default for Menues {
|
|||||||
impl epi::App for MyEguiApp {
|
impl epi::App for MyEguiApp {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"BoilR"
|
"BoilR"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(
|
fn setup(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &egui::Context,
|
ctx: &egui::Context,
|
||||||
_frame: &epi::Frame,
|
_frame: &epi::Frame,
|
||||||
_storage: Option<&dyn epi::Storage>
|
_storage: Option<&dyn epi::Storage>,
|
||||||
) {
|
) {
|
||||||
ctx.set_pixels_per_point(1.0);
|
ctx.set_pixels_per_point(1.0);
|
||||||
let mut style: egui::Style = (*ctx.style()).clone();
|
let mut style: egui::Style = (*ctx.style()).clone();
|
||||||
create_style(&mut style);
|
create_style(&mut style);
|
||||||
ctx.set_style(style);
|
ctx.set_style(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||||
let frame = egui::Frame::default().stroke(Stroke::new(0., BACKGROUND_COLOR)).fill(BACKGROUND_COLOR);
|
let frame = egui::Frame::default()
|
||||||
|
.stroke(Stroke::new(0., BACKGROUND_COLOR))
|
||||||
|
.fill(BACKGROUND_COLOR);
|
||||||
egui::SidePanel::new(egui::panel::Side::Left, "Side Panel")
|
egui::SidePanel::new(egui::panel::Side::Left, "Side Panel")
|
||||||
.default_width(40.0)
|
.default_width(40.0)
|
||||||
.frame(frame)
|
.frame(frame)
|
||||||
@@ -136,68 +96,72 @@ impl epi::App for MyEguiApp {
|
|||||||
let texture = self.get_logo_image(ui);
|
let texture = self.get_logo_image(ui);
|
||||||
let size = texture.size_vec2();
|
let size = texture.size_vec2();
|
||||||
ui.image(texture, size);
|
ui.image(texture, size);
|
||||||
ui.add_space(SECTION_SPACING);
|
ui.add_space(SECTION_SPACING);
|
||||||
|
|
||||||
let changed = ui.selectable_value(&mut self.selected_menu, Menues::Import, "Import Games").changed();
|
let changed = ui
|
||||||
let changed = changed || ui.selectable_value(&mut self.selected_menu, Menues::Settings, "Settings").changed();
|
.selectable_value(&mut self.selected_menu, Menues::Import, "Import Games")
|
||||||
if changed{
|
.changed();
|
||||||
self.games_to_sync =watch::channel(FetchGameStatus::NeedsFetched).1;
|
let changed = changed
|
||||||
|
|| ui
|
||||||
|
.selectable_value(&mut self.selected_menu, Menues::Settings, "Settings")
|
||||||
|
.changed();
|
||||||
|
let changed = changed
|
||||||
|
|| ui
|
||||||
|
.selectable_value(&mut self.selected_menu, Menues::Images, "Images")
|
||||||
|
.changed();
|
||||||
|
if changed && self.selected_menu == Menues::Settings {
|
||||||
|
//We reset games here, since user might change settings
|
||||||
|
self.games_to_sync = watch::channel(FetcStatus::NeedsFetched).1;
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
if self.games_to_sync.borrow().is_some(){
|
if self.games_to_sync.borrow().is_some() {
|
||||||
|
|
||||||
egui::TopBottomPanel::new(egui::panel::TopBottomSide::Bottom, "Bottom Panel")
|
egui::TopBottomPanel::new(egui::panel::TopBottomSide::Bottom, "Bottom Panel")
|
||||||
.frame(frame)
|
.frame(frame)
|
||||||
.show(ctx,|ui|{
|
.show(ctx, |ui| {
|
||||||
let (status_string,syncing) = match &*self.status_reciever.borrow(){
|
let (status_string, syncing) = match &*self.status_reciever.borrow() {
|
||||||
SyncProgress::NotStarted => {
|
SyncProgress::NotStarted => ("".to_string(), false),
|
||||||
("".to_string(),false)
|
SyncProgress::Starting => ("Starting Import".to_string(), true),
|
||||||
},
|
|
||||||
SyncProgress::Starting => {
|
|
||||||
("Starting Import".to_string(),true)
|
|
||||||
},
|
|
||||||
SyncProgress::FoundGames { games_found } => {
|
SyncProgress::FoundGames { games_found } => {
|
||||||
(format!("Found {} games to import",games_found),true)
|
(format!("Found {} games to import", games_found), true)
|
||||||
},
|
}
|
||||||
SyncProgress::FindingImages => {
|
SyncProgress::FindingImages => ("Searching for images".to_string(), true),
|
||||||
(format!("Searching for images"),true)
|
SyncProgress::DownloadingImages { to_download } => {
|
||||||
},
|
(format!("Downloading {} images ", to_download), true)
|
||||||
SyncProgress::DownloadingImages { to_download } => {
|
}
|
||||||
(format!("Downloading {} images ",to_download),true)
|
SyncProgress::Done => ("Done importing games".to_string(), false),
|
||||||
},
|
};
|
||||||
SyncProgress::Done => {
|
if !status_string.is_empty() {
|
||||||
(format!("Done importing games"),false)
|
ui.label(status_string);
|
||||||
},
|
}
|
||||||
};
|
|
||||||
if status_string != "" {
|
let texture = self.get_import_image(ui);
|
||||||
ui.label(status_string);
|
let size = texture.size_vec2();
|
||||||
}
|
let image_button = ImageButton::new(texture, size * 0.5);
|
||||||
|
if ui
|
||||||
let texture = self.get_import_image(ui);
|
.add(image_button)
|
||||||
let size = texture.size_vec2();
|
.on_hover_text("Import your games into steam")
|
||||||
let image_button = ImageButton::new(texture, size * 0.5);
|
.clicked()
|
||||||
if ui.add(image_button).on_hover_text("Import your games into steam")
|
&& !syncing
|
||||||
.clicked() && !syncing{
|
{
|
||||||
self.run_sync();
|
self.run_sync();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
egui::CentralPanel::default()
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
.show(ctx, |ui| {
|
match self.selected_menu {
|
||||||
match self.selected_menu {
|
Menues::Import => {
|
||||||
Menues::Import => {
|
|
||||||
self.render_import_games(ui);
|
self.render_import_games(ui);
|
||||||
|
}
|
||||||
},
|
|
||||||
Menues::Settings => {
|
Menues::Settings => {
|
||||||
self.render_settings(ui);
|
self.render_settings(ui);
|
||||||
},
|
}
|
||||||
|
Menues::Images => {
|
||||||
|
self.render_ui_images(ui);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
});
|
||||||
});
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_style(style: &mut egui::Style) {
|
fn create_style(style: &mut egui::Style) {
|
||||||
@@ -205,339 +169,60 @@ fn create_style(style: &mut egui::Style) {
|
|||||||
style.visuals.button_frame = false;
|
style.visuals.button_frame = false;
|
||||||
style.visuals.dark_mode = true;
|
style.visuals.dark_mode = true;
|
||||||
style.visuals.override_text_color = Some(TEXT_COLOR);
|
style.visuals.override_text_color = Some(TEXT_COLOR);
|
||||||
style.visuals.widgets.noninteractive.rounding = Rounding{
|
style.visuals.widgets.noninteractive.rounding = Rounding {
|
||||||
ne:0.0,
|
ne: 0.0,
|
||||||
nw:0.0,
|
nw: 0.0,
|
||||||
se:0.0,
|
se: 0.0,
|
||||||
sw:0.0
|
sw: 0.0,
|
||||||
};
|
};
|
||||||
style.visuals.faint_bg_color = PURLPLE;
|
style.visuals.faint_bg_color = PURLPLE;
|
||||||
style.visuals.extreme_bg_color = EXTRA_BACKGROUND_COLOR;
|
style.visuals.extreme_bg_color = EXTRA_BACKGROUND_COLOR;
|
||||||
style.visuals.widgets.active.bg_fill = BACKGROUND_COLOR;
|
style.visuals.widgets.active.bg_fill = BACKGROUND_COLOR;
|
||||||
style.visuals.widgets.active.bg_stroke = Stroke::new(2.0,BG_STROKE_COLOR);
|
style.visuals.widgets.active.bg_stroke = Stroke::new(2.0, BG_STROKE_COLOR);
|
||||||
style.visuals.widgets.active.fg_stroke = Stroke::new(2.0,LIGHT_ORANGE);
|
style.visuals.widgets.active.fg_stroke = Stroke::new(2.0, LIGHT_ORANGE);
|
||||||
style.visuals.widgets.open.bg_fill = BACKGROUND_COLOR;
|
style.visuals.widgets.open.bg_fill = BACKGROUND_COLOR;
|
||||||
style.visuals.widgets.open.bg_stroke = Stroke::new(2.0,BG_STROKE_COLOR);
|
style.visuals.widgets.open.bg_stroke = Stroke::new(2.0, BG_STROKE_COLOR);
|
||||||
style.visuals.widgets.open.fg_stroke = Stroke::new(2.0,LIGHT_ORANGE);
|
style.visuals.widgets.open.fg_stroke = Stroke::new(2.0, LIGHT_ORANGE);
|
||||||
style.visuals.widgets.noninteractive.bg_fill = BACKGROUND_COLOR;
|
style.visuals.widgets.noninteractive.bg_fill = BACKGROUND_COLOR;
|
||||||
style.visuals.widgets.noninteractive.bg_stroke = Stroke::new(2.0,BG_STROKE_COLOR);
|
style.visuals.widgets.noninteractive.bg_stroke = Stroke::new(2.0, BG_STROKE_COLOR);
|
||||||
style.visuals.widgets.noninteractive.fg_stroke = Stroke::new(2.0,ORANGE);
|
style.visuals.widgets.noninteractive.fg_stroke = Stroke::new(2.0, ORANGE);
|
||||||
style.visuals.widgets.inactive.bg_fill = BACKGROUND_COLOR;
|
style.visuals.widgets.inactive.bg_fill = BACKGROUND_COLOR;
|
||||||
style.visuals.widgets.inactive.bg_stroke = Stroke::new(2.0,BG_STROKE_COLOR);
|
style.visuals.widgets.inactive.bg_stroke = Stroke::new(2.0, BG_STROKE_COLOR);
|
||||||
style.visuals.widgets.inactive.fg_stroke = Stroke::new(2.0,ORANGE);
|
style.visuals.widgets.inactive.fg_stroke = Stroke::new(2.0, ORANGE);
|
||||||
style.visuals.widgets.hovered.bg_fill = BACKGROUND_COLOR;
|
style.visuals.widgets.hovered.bg_fill = BACKGROUND_COLOR;
|
||||||
style.visuals.widgets.hovered.bg_stroke = Stroke::new(2.0,BG_STROKE_COLOR);
|
style.visuals.widgets.hovered.bg_stroke = Stroke::new(2.0, BG_STROKE_COLOR);
|
||||||
style.visuals.widgets.hovered.fg_stroke = Stroke::new(2.0,LIGHT_ORANGE);
|
style.visuals.widgets.hovered.fg_stroke = Stroke::new(2.0, LIGHT_ORANGE);
|
||||||
style.visuals.selection.bg_fill = PURLPLE;
|
style.visuals.selection.bg_fill = PURLPLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MyEguiApp{
|
impl MyEguiApp {
|
||||||
|
fn get_import_image(&mut self, ui: &mut egui::Ui) -> &mut TextureHandle {
|
||||||
fn get_import_image(&mut self, ui:&mut egui::Ui) -> &mut TextureHandle {
|
self.ui_images.import_button.get_or_insert_with(|| {
|
||||||
self.ui_images.import_button.get_or_insert_with(|| {
|
// Load the texture only once.
|
||||||
// Load the texture only once.
|
ui.ctx().load_texture("import_image", get_import_image())
|
||||||
ui.ctx().load_texture("import_image", get_import_image())
|
})
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_logo_image(&mut self, ui:&mut egui::Ui) -> &mut TextureHandle {
|
fn get_logo_image(&mut self, ui: &mut egui::Ui) -> &mut TextureHandle {
|
||||||
self.ui_images.logo_32.get_or_insert_with(|| {
|
self.ui_images.logo_32.get_or_insert_with(|| {
|
||||||
// Load the texture only once.
|
// Load the texture only once.
|
||||||
ui.ctx().load_texture("logo32", get_logo())
|
ui.ctx().load_texture("logo32", get_logo())
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_settings(&mut self, ui: &mut egui::Ui){
|
|
||||||
ui.heading("Settings");
|
|
||||||
|
|
||||||
let mut scroll_style = ui.style_mut();
|
|
||||||
scroll_style.visuals.extreme_bg_color = BACKGROUND_COLOR;
|
|
||||||
scroll_style.visuals.widgets.inactive.bg_fill = EXTRA_BACKGROUND_COLOR;
|
|
||||||
scroll_style.visuals.widgets.active.bg_fill = EXTRA_BACKGROUND_COLOR;
|
|
||||||
scroll_style.visuals.selection.bg_fill = EXTRA_BACKGROUND_COLOR;
|
|
||||||
scroll_style.visuals.widgets.hovered.bg_fill = EXTRA_BACKGROUND_COLOR;
|
|
||||||
|
|
||||||
ScrollArea::vertical()
|
|
||||||
.stick_to_right()
|
|
||||||
.auto_shrink([false,true])
|
|
||||||
.show(ui,|ui| {
|
|
||||||
ui.reset_style();
|
|
||||||
|
|
||||||
self.render_steamgriddb_settings(ui);
|
|
||||||
|
|
||||||
self.render_steam_settings(ui);
|
|
||||||
|
|
||||||
self.render_epic_settings(ui);
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
ui.heading("Heroic");
|
|
||||||
ui.checkbox(&mut self.settings.heroic.enabled, "Import form Heroic");
|
|
||||||
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.render_legendary_settings(ui);
|
|
||||||
self.render_itch_settings(ui);
|
|
||||||
self.render_origin_settings(ui);
|
|
||||||
self.render_gog_settings(ui);
|
|
||||||
self.render_uplay_settings(ui);
|
|
||||||
self.render_lutris_settings(ui);
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
self.render_amazon_settings(ui);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_lutris_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("Lutris");
|
|
||||||
ui.checkbox(&mut self.settings.lutris.enabled, "Import form Lutris");
|
|
||||||
if self.settings.lutris.enabled{
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let mut empty_string ="".to_string();
|
|
||||||
let lutris_location = self.settings.lutris.executable.as_mut().unwrap_or(&mut empty_string);
|
|
||||||
ui.label("Lutris Location: ");
|
|
||||||
if ui.text_edit_singleline(lutris_location).changed(){
|
|
||||||
self.settings.lutris.executable = Some(lutris_location.to_string());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_amazon_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("Amazon");
|
|
||||||
ui.checkbox(&mut self.settings.amazon.enabled, "Import form Amazon");
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_uplay_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("Uplay");
|
|
||||||
ui.checkbox(&mut self.settings.uplay.enabled, "Import form Uplay");
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_gog_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("GoG Galaxy");
|
|
||||||
ui.checkbox(&mut self.settings.gog.enabled, "Import form GoG Galaxy");
|
|
||||||
if self.settings.gog.enabled {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let mut empty_string ="".to_string();
|
|
||||||
let itch_location = self.settings.gog.location.as_mut().unwrap_or(&mut empty_string);
|
|
||||||
ui.label("GoG Galaxy Folder: ");
|
|
||||||
if ui.text_edit_singleline(itch_location).changed(){
|
|
||||||
self.settings.gog.location = Some(itch_location.to_string());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_origin_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("Origin");
|
|
||||||
ui.checkbox(&mut self.settings.origin.enabled, "Import from Origin");
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_itch_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("Itch.io");
|
|
||||||
ui.checkbox(&mut self.settings.itch.enabled, "Import form Itch.io");
|
|
||||||
if self.settings.itch.enabled {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let mut empty_string ="".to_string();
|
|
||||||
let itch_location = self.settings.itch.location.as_mut().unwrap_or(&mut empty_string);
|
|
||||||
ui.label("Itch.io Folder: ");
|
|
||||||
if ui.text_edit_singleline(itch_location).changed(){
|
|
||||||
self.settings.itch.location = Some(itch_location.to_string());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_steam_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("Steam");
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let mut empty_string ="".to_string();
|
|
||||||
let steam_location = self.settings.steam.location.as_mut().unwrap_or(&mut empty_string);
|
|
||||||
ui.label("Steam Location: ");
|
|
||||||
if ui.text_edit_singleline(steam_location).changed(){
|
|
||||||
self.settings.steam.location = Some(steam_location.to_string());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ui.checkbox(&mut self.settings.steam.create_collections, "Create collections").on_hover_text("Tries to create a games collection for each platform");
|
|
||||||
ui.checkbox(&mut self.settings.steam.optimize_for_big_picture, "Optimize for big picture").on_hover_text("Set icons to be larger horizontal images, this looks nice in steam big picture mode, but a bit off in desktop mode");
|
|
||||||
ui.checkbox(&mut self.settings.steam.stop_steam, "Stop Steam before import").on_hover_text("Stops Steam if it is running when import starts");
|
|
||||||
ui.checkbox(&mut self.settings.steam.start_steam, "Start Steam after import").on_hover_text("Starts Steam is it is not running after the import");
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_steamgriddb_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("SteamGridDB");
|
|
||||||
ui.checkbox(&mut self.settings.steamgrid_db.enabled, "Download images");
|
|
||||||
if self.settings.steamgrid_db.enabled{
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let mut empty_string ="".to_string();
|
|
||||||
let auth_key = self.settings.steamgrid_db.auth_key.as_mut().unwrap_or(&mut empty_string);
|
|
||||||
ui.label("Authentication key: ");
|
|
||||||
if ui.text_edit_singleline(auth_key).changed(){
|
|
||||||
self.settings.steamgrid_db.auth_key = Some(auth_key.to_string());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label("To download images you need an API Key from SteamGridDB, you can find yours");
|
|
||||||
ui.hyperlink_to("here", "https://www.steamgriddb.com/profile/preferences/api")
|
|
||||||
});
|
|
||||||
ui.checkbox(&mut self.settings.steamgrid_db.prefer_animated, "Prefer animated images").on_hover_text("Prefer downloading animated images over static images (this can slow Steam down but looks neat)");
|
|
||||||
}
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_legendary_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
ui.heading("Legendary & Rare");
|
|
||||||
ui.checkbox(&mut self.settings.legendary.enabled, "Import form Legendary & Rare");
|
|
||||||
if self.settings.legendary.enabled{
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let mut empty_string ="".to_string();
|
|
||||||
let legendary_location = self.settings.legendary.executable.as_mut().unwrap_or(&mut empty_string);
|
|
||||||
ui.label("Legendary Executable: ").on_hover_text("The location of the legendary executable to use");
|
|
||||||
if ui.text_edit_singleline(legendary_location).changed(){
|
|
||||||
self.settings.legendary.executable = Some(legendary_location.to_string());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_epic_settings(&mut self, ui: &mut egui::Ui) {
|
|
||||||
let epic_settings = &mut self.settings.epic_games;
|
|
||||||
ui.heading("Epic Games");
|
|
||||||
ui.checkbox(&mut epic_settings.enabled, "Import form Epic Games");
|
|
||||||
if epic_settings.enabled{
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let mut empty_string ="".to_string();
|
|
||||||
let epic_location = epic_settings.location.as_mut().unwrap_or(&mut empty_string);
|
|
||||||
ui.label("Epic Manifests Location: ").on_hover_text("The location where Epic stores its manifest files that BoilR needs to read");
|
|
||||||
if ui.text_edit_singleline(epic_location).changed(){
|
|
||||||
epic_settings.location = Some(epic_location.to_string());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let safe_mode_header = match epic_settings.safe_launch.len(){
|
|
||||||
0 => "Force games to launch through Epic Launcher".to_string(),
|
|
||||||
1 => "One game forced to launch through Epic Launcher".to_string(),
|
|
||||||
x => format!("{} games forced to launch through Epic Launcher",x)
|
|
||||||
};
|
|
||||||
|
|
||||||
egui::CollapsingHeader::new(safe_mode_header)
|
|
||||||
.id_source("Epic_Launcher_safe_launch")
|
|
||||||
.show(ui, |ui| {
|
|
||||||
ui.label("Some games must be started from the Epic Launcher, select those games below and BoilR will create shortcuts that opens the games through the Epic Launcher.");
|
|
||||||
let manifests =self.epic_manifests.get_or_insert_with(||{
|
|
||||||
let epic_platform = EpicPlatform::new(epic_settings);
|
|
||||||
let manifests = crate::platform::Platform::get_shortcuts(&epic_platform);
|
|
||||||
manifests.unwrap_or_default()
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut safe_open_games = epic_settings.safe_launch.clone();
|
|
||||||
for manifest in manifests{
|
|
||||||
let key = manifest.get_key();
|
|
||||||
let display_name = &manifest.display_name;
|
|
||||||
let mut safe_open = safe_open_games.contains(display_name) || safe_open_games.contains(&key);
|
|
||||||
if ui.checkbox(&mut safe_open, display_name).clicked(){
|
|
||||||
if safe_open{
|
|
||||||
safe_open_games.push(key);
|
|
||||||
}else{
|
|
||||||
safe_open_games.retain(|m| m!= display_name && m!= &key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
epic_settings.safe_launch = safe_open_games;
|
|
||||||
}) ;
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_import_games(&mut self, ui: &mut egui::Ui){
|
|
||||||
|
|
||||||
ui.heading("Import Games");
|
|
||||||
|
|
||||||
if self.games_to_sync.borrow().needs_fetching(){
|
|
||||||
let (tx, rx) = watch::channel(FetchGameStatus::NeedsFetched);
|
|
||||||
self.games_to_sync = rx;
|
|
||||||
let settings = self.settings.clone();
|
|
||||||
self.rt.spawn_blocking(move || {
|
|
||||||
let _= tx.send(FetchGameStatus::Fetching);
|
|
||||||
let games_to_sync = sync::get_platform_shortcuts(&settings);
|
|
||||||
let _= tx.send(FetchGameStatus::Fetched(games_to_sync));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut scroll_style = ui.style_mut();
|
|
||||||
scroll_style.visuals.extreme_bg_color = BACKGROUND_COLOR;
|
|
||||||
scroll_style.visuals.widgets.inactive.bg_fill = EXTRA_BACKGROUND_COLOR;
|
|
||||||
scroll_style.visuals.widgets.active.bg_fill = EXTRA_BACKGROUND_COLOR;
|
|
||||||
scroll_style.visuals.selection.bg_fill = EXTRA_BACKGROUND_COLOR;
|
|
||||||
scroll_style.visuals.widgets.hovered.bg_fill = EXTRA_BACKGROUND_COLOR;
|
|
||||||
|
|
||||||
|
|
||||||
ScrollArea::vertical()
|
|
||||||
.stick_to_right()
|
|
||||||
.auto_shrink([false,true])
|
|
||||||
.show(ui,|ui| {
|
|
||||||
ui.reset_style();
|
|
||||||
|
|
||||||
let borrowed_games = &*self.games_to_sync.borrow();
|
|
||||||
match borrowed_games{
|
|
||||||
FetchGameStatus::Fetched(games_to_sync) => {
|
|
||||||
ui.label("Select the games you want to import into steam");
|
|
||||||
for (platform_name, shortcuts) in games_to_sync{
|
|
||||||
ui.heading(platform_name);
|
|
||||||
for shortcut in shortcuts {
|
|
||||||
let mut import_game = !self.settings.blacklisted_games.contains(&shortcut.app_id);
|
|
||||||
let checkbox = egui::Checkbox::new(&mut import_game,&shortcut.app_name);
|
|
||||||
let response = ui.add(checkbox);
|
|
||||||
if response.clicked(){
|
|
||||||
if !self.settings.blacklisted_games.contains(&shortcut.app_id){
|
|
||||||
self.settings.blacklisted_games.push(shortcut.app_id);
|
|
||||||
}else{
|
|
||||||
self.settings.blacklisted_games.retain(|id| *id != shortcut.app_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ui.add_space(SECTION_SPACING);
|
|
||||||
ui.label("Check the settings if BoilR didn't find the game you where looking for");
|
|
||||||
},
|
|
||||||
_=> {
|
|
||||||
ui.label("Finding installed games");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn run_sync() {
|
pub fn run_sync() {
|
||||||
let mut app = MyEguiApp::new();
|
let mut app = MyEguiApp::new();
|
||||||
app.run_sync();
|
app.run_sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn run_ui() -> Result<(), Box<dyn Error>> {
|
pub fn run_ui() -> Result<(), Box<dyn Error>> {
|
||||||
let app = MyEguiApp::new();
|
let app = MyEguiApp::new();
|
||||||
|
|
||||||
let mut native_options = eframe::NativeOptions::default();
|
let native_options = eframe::NativeOptions {
|
||||||
native_options.initial_window_size = Some(egui::Vec2{
|
initial_window_size: Some(egui::Vec2 { x: 800., y: 500. }),
|
||||||
x:800.,
|
icon_data: Some(get_logo_icon()),
|
||||||
y:500.
|
..Default::default()
|
||||||
});
|
};
|
||||||
native_options.icon_data = Some(get_logo_icon());
|
|
||||||
eframe::run_native(Box::new(app), native_options);
|
eframe::run_native(Box::new(app), native_options);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ impl Platform<Game, Box<dyn Error>> for Uplay {
|
|||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
{
|
{
|
||||||
//TODO update this when uplay gets proton support on linux
|
//TODO update this when uplay gets proton support on linux
|
||||||
return true;
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user