Add feature to launch gog games through heroic (#168)

This commit is contained in:
Philip Kristoffersen
2022-06-08 21:26:16 +02:00
committed by GitHub
parent 9765e170eb
commit 1bcafd8207
6 changed files with 190 additions and 150 deletions
+33 -67
View File
@@ -1,7 +1,5 @@
use std::path::Path;
use super::heroic_platform::InstallationMode;
use serde::Deserialize; use serde::Deserialize;
use std::path::Path;
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut}; use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
@@ -12,86 +10,54 @@ pub struct HeroicGame {
pub install_path: String, pub install_path: String,
pub executable: String, pub executable: String,
pub launch_parameters: String, pub launch_parameters: String,
#[serde(skip_deserializing)]
pub install_mode: Option<InstallationMode>,
#[serde(skip_deserializing)]
pub launch_through_heroic: bool,
} }
impl HeroicGame { impl HeroicGame {
pub fn is_installed(&self) -> bool { pub fn is_installed(&self) -> bool {
if self.launch_through_heroic { Path::new(&self.install_path)
true .join(&self.executable)
} else { .exists()
Path::new(&self.install_path)
.join(&self.executable)
.exists()
}
} }
} }
impl From<HeroicGame> for ShortcutOwned { impl From<HeroicGame> for ShortcutOwned {
fn from(game: HeroicGame) -> Self { fn from(game: HeroicGame) -> Self {
let mut owned_shortcut = if game.launch_through_heroic && game.install_mode.is_some() { let target_path = Path::new(&game.install_path).join(game.executable);
let launch_parameter = format!("heroic://launch/{}", game.app_name);
let (exe, parameter) = match game.install_mode.unwrap() {
InstallationMode::FlatPak => (
"flatpak",
format!(
"run com.heroicgameslauncher.hgl {} --no-gui",
launch_parameter
),
),
InstallationMode::UserBin => ("heroic", launch_parameter),
};
Shortcut::new(
"0",
game.title.as_str(),
exe,
"",
"",
"",
parameter.as_str(),
)
.to_owned()
} else {
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);
}
} }
}
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
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")] }
let install_path = game.install_path.to_string(); #[cfg(target_os = "windows")]
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();
let shortcut = Shortcut::new( let shortcut = Shortcut::new(
"0", "0",
game.title.as_str(), game.title.as_str(),
&target, &target,
&install_path, &install_path,
&target, &target,
"", "",
game.launch_parameters.as_str(), game.launch_parameters.as_str(),
); );
shortcut.to_owned() let mut owned_shortcut = shortcut.to_owned();
};
owned_shortcut.tags.push("Heroic".to_owned()); owned_shortcut.tags.push("Heroic".to_owned());
owned_shortcut.tags.push("Ready TO Play".to_owned()); owned_shortcut.tags.push("Ready TO Play".to_owned());
owned_shortcut.tags.push("Installed".to_owned()); owned_shortcut.tags.push("Installed".to_owned());
+52 -2
View File
@@ -1,6 +1,6 @@
use steam_shortcuts_util::shortcut::ShortcutOwned; use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
use super::HeroicGame; use super::{HeroicGame, InstallationMode};
use crate::gog::GogShortcut; use crate::gog::GogShortcut;
#[derive(Clone)] #[derive(Clone)]
@@ -8,6 +8,38 @@ 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),
//The string is the app name
Heroic {
title: String,
app_name: String,
install_mode: InstallationMode,
},
}
impl HeroicGameType {
pub fn app_name(&self) -> &str {
match self {
HeroicGameType::Epic(g) => g.app_name.as_ref(),
HeroicGameType::Gog(g, _) => g.game_id.as_ref(),
HeroicGameType::Heroic {
title,
app_name,
install_mode,
} => &app_name,
}
}
pub(crate) fn title(&self) -> &str {
match self {
HeroicGameType::Epic(g) => g.title.as_ref(),
HeroicGameType::Gog(g, _) => g.name.as_ref(),
HeroicGameType::Heroic {
title,
app_name,
install_mode,
} => title.as_ref(),
}
}
} }
impl From<HeroicGameType> for ShortcutOwned { impl From<HeroicGameType> for ShortcutOwned {
@@ -15,6 +47,24 @@ impl From<HeroicGameType> for ShortcutOwned {
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(),
HeroicGameType::Heroic {
title,
app_name,
install_mode,
} => {
let launch_parameter = format!("heroic://launch/{}", app_name);
let (exe, parameter) = match install_mode {
InstallationMode::FlatPak => (
"flatpak",
format!(
"run com.heroicgameslauncher.hgl {} --no-gui",
launch_parameter
),
),
InstallationMode::UserBin => ("heroic", launch_parameter),
};
Shortcut::new("0", title.as_str(), exe, "", "", "", parameter.as_str()).to_owned()
}
} }
} }
} }
+80 -73
View File
@@ -13,7 +13,7 @@ pub struct HeroicPlatform {
pub settings: HeroicSettings, pub settings: HeroicSettings,
} }
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone, Copy)]
pub enum InstallationMode { pub enum InstallationMode {
FlatPak, FlatPak,
UserBin, UserBin,
@@ -74,6 +74,20 @@ fn get_shortcuts_from_location<P: AsRef<Path>>(path: P) -> Result<Vec<HeroicGame
} }
} }
impl HeroicPlatform {
pub fn get_heroic_games(&self) -> Vec<HeroicGameType> {
let install_modes = vec![InstallationMode::FlatPak, InstallationMode::UserBin];
let mut heroic_games = self.get_epic_games(&install_modes);
if let Ok(gog_games) = get_gog_games(&self.settings, &install_modes) {
heroic_games.extend(gog_games);
} else {
println!("Did not find any GOG games in heroic")
}
heroic_games
}
}
impl Platform<HeroicGameType, Box<dyn Error>> for HeroicPlatform { impl Platform<HeroicGameType, Box<dyn Error>> for HeroicPlatform {
fn enabled(&self) -> bool { fn enabled(&self) -> bool {
self.settings.enabled self.settings.enabled
@@ -82,16 +96,9 @@ impl Platform<HeroicGameType, Box<dyn Error>> for HeroicPlatform {
fn name(&self) -> &str { fn name(&self) -> &str {
"Heroic" "Heroic"
} }
fn get_shortcuts(&self) -> Result<Vec<HeroicGameType>, Box<dyn Error>> {
let install_modes = vec![InstallationMode::FlatPak, InstallationMode::UserBin];
let mut heroic_games = self.get_epic_games(&install_modes)?; fn get_shortcuts(&self) -> Result<Vec<HeroicGameType>, Box<dyn Error>> {
if let Ok(gog_games) = get_gog_games(&install_modes) { Ok(self.get_heroic_games())
heroic_games.extend(gog_games);
} else {
println!("Did not find any GOG games in heroic")
}
Ok(heroic_games)
} }
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
@@ -111,90 +118,91 @@ 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(game) => !game.launch_through_heroic, HeroicGameType::Epic(_game) => true,
HeroicGameType::Gog(_, is_windows) => *is_windows, HeroicGameType::Gog(_, is_windows) => *is_windows,
HeroicGameType::Heroic { .. } => false,
} }
} }
} }
impl HeroicPlatform { impl HeroicPlatform {
pub fn get_epic_games( pub fn get_epic_games(&self, install_modes: &[InstallationMode]) -> Vec<HeroicGameType> {
&self, let mut shortcuts = vec![];
install_modes: &[InstallationMode], for install_mode in install_modes {
) -> Result<Vec<HeroicGameType>, Box<dyn Error>> { if let Ok(mut games) = get_shortcuts_from_install_mode(install_mode) {
let shortcuts = self.get_heroic_games(install_modes); games.sort_by_key(|m| {
let mut epic_shortcuts = vec![]; format!("{}-{}-{}", m.launch_parameters, m.executable, &m.app_name)
for shortcut in shortcuts { });
epic_shortcuts.push(HeroicGameType::Epic(shortcut)); games.dedup_by_key(|m| {
} format!("{}-{}-{}", m.launch_parameters, m.executable, &m.app_name)
Ok(epic_shortcuts) });
}
pub fn get_heroic_games(&self, install_modes: &[InstallationMode]) -> Vec<HeroicGame> { for game in games {
let mut shortcuts: Vec<HeroicGame> = install_modes if self.settings.is_heroic_launch(&game.app_name) {
.iter() shortcuts.push(HeroicGameType::Heroic {
.filter_map(|install_mode| { title: game.title,
let mut shortcuts = get_shortcuts_from_install_mode(install_mode).ok(); app_name: game.app_name,
if let Some(shortcuts) = shortcuts.as_mut() { install_mode: *install_mode,
for shortcut in shortcuts { });
shortcut.install_mode = Some(install_mode.clone()); } else {
if game.is_installed() {
let game_in_list = self shortcuts.push(HeroicGameType::Epic(game));
.settings }
.launch_games_through_heroic
.contains(&shortcut.app_name)
|| self
.settings
.launch_games_through_heroic
.contains(&shortcut.title);
shortcut.launch_through_heroic =
if self.settings.default_launch_through_heroic {
!game_in_list
} else {
game_in_list
};
} }
} }
shortcuts }
}) }
.flatten()
.filter(|s| s.is_installed())
.collect();
shortcuts
.sort_by_key(|m| format!("{}-{}-{}", m.launch_parameters, m.executable, &m.app_name));
shortcuts
.dedup_by_key(|m| format!("{}-{}-{}", m.launch_parameters, m.executable, &m.app_name));
shortcuts shortcuts
} }
} }
fn get_gog_games( fn get_gog_games(
settings: &HeroicSettings,
install_modes: &[InstallationMode], install_modes: &[InstallationMode],
) -> Result<Vec<HeroicGameType>, Box<dyn Error>> { ) -> Result<Vec<HeroicGameType>, Box<dyn Error>> {
let gog_paths: Vec<HeroicGogPath> = install_modes let mut gog_paths = vec![];
.iter() for install_mode in install_modes {
.filter_map(|install_mode| { let config = get_gog_installed_location(install_mode);
let config = get_gog_installed_location(install_mode); if config.exists() {
if config.exists() { if let Ok(config_string) = std::fs::read_to_string(config) {
Some(config) if let Ok(config) = serde_json::from_str::<HeroicGogConfig>(&config_string) {
} else { for c in config.installed {
None gog_paths.push((install_mode, c));
}
}
} }
}) }
.filter_map(|config_path| std::fs::read_to_string(config_path).ok()) }
.filter_map(|config_string| serde_json::from_str::<HeroicGogConfig>(&config_string).ok())
.flat_map(|config| config.installed)
.collect();
let mut is_windows_map = HashMap::new(); let mut is_windows_map = HashMap::new();
for path in gog_paths.iter() { for (_, path) in gog_paths.iter() {
is_windows_map.insert(path.app_name.clone(), path.platform == "windows"); is_windows_map.insert(path.app_name.clone(), path.platform == "windows");
} }
let mut gog_shortcuts = vec![];
let heroic_games = gog_paths
.iter()
.filter(|(_, p)| settings.is_heroic_launch(&p.app_name))
.filter_map(|(install_mode, p)| {
let path = Path::new(&p.install_path);
if path.exists() {
let title = path.file_name();
Some(HeroicGameType::Heroic {
title: title.unwrap_or_default().to_string_lossy().to_string(),
app_name: p.app_name.clone(),
install_mode: **install_mode,
})
} else {
None
}
});
gog_shortcuts.extend(heroic_games);
let game_folders = gog_paths let game_folders = gog_paths
.iter() .iter()
.filter_map(|p| { .filter(|(_, p)| !settings.is_heroic_launch(&p.app_name))
.filter_map(|(_, p)| {
let path = Path::new(&p.install_path); let path = Path::new(&p.install_path);
if path.exists() { if path.exists() {
Some(path.to_path_buf()) Some(path.to_path_buf())
@@ -203,9 +211,8 @@ fn get_gog_games(
} }
}) })
.collect(); .collect();
let shortcuts = get_shortcuts_from_game_folders(game_folders); let direct_shortcuts = get_shortcuts_from_game_folders(game_folders);
let mut gog_shortcuts = vec![]; for shortcut in direct_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));
} }
+13
View File
@@ -6,3 +6,16 @@ pub struct HeroicSettings {
pub launch_games_through_heroic: Vec<String>, pub launch_games_through_heroic: Vec<String>,
pub default_launch_through_heroic: bool, pub default_launch_through_heroic: bool,
} }
impl HeroicSettings {
pub fn is_heroic_launch<S: AsRef<str>>(&self, app_name: S) -> bool {
let contains = self
.launch_games_through_heroic
.contains(&app_name.as_ref().to_string());
if self.default_launch_through_heroic {
!contains
} else {
contains
}
}
}
+5 -6
View File
@@ -98,21 +98,20 @@ self.settings.heroic.default_launch_through_heroic{
let manifests =self.heroic_games.get_or_insert_with(||{ let manifests =self.heroic_games.get_or_insert_with(||{
let heroic_setting = self.settings.heroic.clone(); let heroic_setting = self.settings.heroic.clone();
let install_modes = vec![crate::heroic::InstallationMode::FlatPak, crate::heroic::InstallationMode::UserBin];
let heroic_platform =HeroicPlatform{ let heroic_platform =HeroicPlatform{
settings:heroic_setting settings:heroic_setting
}; };
heroic_platform.get_heroic_games(&install_modes) heroic_platform.get_heroic_games()
}); });
let safe_open_games = &mut self.settings.heroic.launch_games_through_heroic; let safe_open_games = &mut self.settings.heroic.launch_games_through_heroic;
for manifest in manifests{ for manifest in manifests{
let key = &manifest.app_name; let key = manifest.app_name();
let display_name = &manifest.title; let display_name = manifest.title();
let mut safe_open = safe_open_games.contains(display_name) || safe_open_games.contains(key); let mut safe_open = safe_open_games.contains(&display_name.to_string()) || safe_open_games.contains(&key.to_string());
if ui.checkbox(&mut safe_open, display_name).clicked(){ if ui.checkbox(&mut safe_open, display_name).clicked(){
if safe_open{ if safe_open{
safe_open_games.push(key.clone()); safe_open_games.push(key.to_string());
}else{ }else{
safe_open_games.retain(|m| m!= display_name && m!= key); safe_open_games.retain(|m| m!= display_name && m!= key);
} }
+7 -2
View File
@@ -8,7 +8,12 @@ use tokio::{
sync::watch::{self, Receiver}, sync::watch::{self, Receiver},
}; };
use crate::{egs::ManifestItem, heroic::HeroicGame, settings::Settings, sync::SyncProgress}; use crate::{
egs::ManifestItem,
heroic::{HeroicGame, HeroicGameType},
settings::Settings,
sync::SyncProgress,
};
use super::{ use super::{
ui_colors::{ ui_colors::{
@@ -36,7 +41,7 @@ pub struct MyEguiApp {
pub(crate) games_to_sync: Receiver<FetcStatus<Vec<(String, Vec<ShortcutOwned>)>>>, pub(crate) games_to_sync: Receiver<FetcStatus<Vec<(String, Vec<ShortcutOwned>)>>>,
pub(crate) status_reciever: Receiver<SyncProgress>, pub(crate) status_reciever: Receiver<SyncProgress>,
pub(crate) epic_manifests: Option<Vec<ManifestItem>>, pub(crate) epic_manifests: Option<Vec<ManifestItem>>,
pub(crate) heroic_games: Option<Vec<HeroicGame>>, pub(crate) heroic_games: Option<Vec<HeroicGameType>>,
pub(crate) image_selected_state: ImageSelectState, pub(crate) image_selected_state: ImageSelectState,
pub(crate) backup_state: BackupState, pub(crate) backup_state: BackupState,
pub(crate) disconect_state: DiconnectState, pub(crate) disconect_state: DiconnectState,