Launch games through heroic launcher (#151)

* Add option to launch heroic games through launcher

* Add ui

* Let users choose which game to launch through Heroic
This commit is contained in:
Philip Kristoffersen
2022-05-22 22:37:09 +02:00
committed by GitHub
parent 01f051e113
commit 1da9ef8d28
6 changed files with 159 additions and 61 deletions
+1
View File
@@ -38,6 +38,7 @@ flatpak = false
[heroic] [heroic]
enabled = true enabled = true
launch_games_through_heroic =[]
[amazon] [amazon]
enabled = true enabled = true
+30 -3
View File
@@ -1,9 +1,10 @@
use std::path::Path; use std::path::Path;
use serde::{Deserialize, Serialize}; use super::heroic_platform::InstallationMode;
use serde::Deserialize;
use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut}; use steam_shortcuts_util::{shortcut::ShortcutOwned, Shortcut};
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
pub struct HeroicGame { pub struct HeroicGame {
pub app_name: String, pub app_name: String,
pub title: String, pub title: String,
@@ -11,6 +12,10 @@ 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 {
@@ -23,6 +28,26 @@ impl HeroicGame {
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 launch_parameter = format!("heroic://launch/{}", game.app_name);
let (exe, parameter) = match game.install_mode.unwrap() {
InstallationMode::FlatPak => (
"flatpak",
format!("run com.heroicgameslauncher.hgl {}", 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); let target_path = Path::new(&game.install_path).join(game.executable);
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
@@ -57,7 +82,9 @@ impl From<HeroicGame> for ShortcutOwned {
"", "",
game.launch_parameters.as_str(), game.launch_parameters.as_str(),
); );
let mut owned_shortcut = shortcut.to_owned();
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());
+41 -13
View File
@@ -13,7 +13,8 @@ pub struct HeroicPlatform {
pub settings: HeroicSettings, pub settings: HeroicSettings,
} }
enum InstallationMode { #[derive(Deserialize, Debug, Clone)]
pub enum InstallationMode {
FlatPak, FlatPak,
UserBin, UserBin,
} }
@@ -84,7 +85,7 @@ impl Platform<HeroicGameType, Box<dyn Error>> for HeroicPlatform {
fn get_shortcuts(&self) -> Result<Vec<HeroicGameType>, Box<dyn Error>> { fn get_shortcuts(&self) -> Result<Vec<HeroicGameType>, Box<dyn Error>> {
let install_modes = vec![InstallationMode::FlatPak, InstallationMode::UserBin]; let install_modes = vec![InstallationMode::FlatPak, InstallationMode::UserBin];
let mut heroic_games = get_epic_games(&install_modes)?; let mut heroic_games = self.get_epic_games(&install_modes)?;
if let Ok(gog_games) = get_gog_games(&install_modes) { if let Ok(gog_games) = get_gog_games(&install_modes) {
heroic_games.extend(gog_games); heroic_games.extend(gog_games);
} else { } else {
@@ -110,24 +111,18 @@ 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(game) => !game.launch_through_heroic,
HeroicGameType::Gog(_, is_windows) => *is_windows, HeroicGameType::Gog(_, is_windows) => *is_windows,
} }
} }
} }
fn get_epic_games( impl HeroicPlatform {
pub fn get_epic_games(
&self,
install_modes: &[InstallationMode], install_modes: &[InstallationMode],
) -> Result<Vec<HeroicGameType>, Box<dyn Error>> { ) -> Result<Vec<HeroicGameType>, Box<dyn Error>> {
let mut shortcuts: Vec<HeroicGame> = install_modes let shortcuts = self.get_heroic_games(install_modes);
.iter()
.filter_map(|install_mode| get_shortcuts_from_install_mode(install_mode).ok())
.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));
let mut epic_shortcuts = vec![]; let mut epic_shortcuts = vec![];
for shortcut in shortcuts { for shortcut in shortcuts {
epic_shortcuts.push(HeroicGameType::Epic(shortcut)); epic_shortcuts.push(HeroicGameType::Epic(shortcut));
@@ -135,6 +130,39 @@ fn get_epic_games(
Ok(epic_shortcuts) Ok(epic_shortcuts)
} }
pub fn get_heroic_games(&self, install_modes: &[InstallationMode]) -> Vec<HeroicGame> {
let mut shortcuts: Vec<HeroicGame> = install_modes
.iter()
.filter_map(|install_mode| {
let mut shortcuts = get_shortcuts_from_install_mode(install_mode).ok();
if let Some(shortcuts) = shortcuts.as_mut() {
for shortcut in shortcuts {
shortcut.install_mode = Some(install_mode.clone());
if self
.settings
.launch_games_through_heroic
.contains(&shortcut.app_name)
|| self
.settings
.launch_games_through_heroic
.contains(&shortcut.title)
{
shortcut.launch_through_heroic = true;
}
}
}
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
}
}
fn get_gog_games( fn get_gog_games(
install_modes: &[InstallationMode], install_modes: &[InstallationMode],
) -> Result<Vec<HeroicGameType>, Box<dyn Error>> { ) -> Result<Vec<HeroicGameType>, Box<dyn Error>> {
+1
View File
@@ -3,4 +3,5 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct HeroicSettings { pub struct HeroicSettings {
pub enabled: bool, pub enabled: bool,
pub launch_games_through_heroic: Vec<String>,
} }
+44 -5
View File
@@ -2,7 +2,7 @@ use copypasta::ClipboardProvider;
use eframe::egui; use eframe::egui;
use egui::ScrollArea; use egui::ScrollArea;
use crate::egs::EpicPlatform; use crate::{egs::EpicPlatform, heroic::HeroicPlatform};
use super::{ use super::{
ui_colors::{BACKGROUND_COLOR, EXTRA_BACKGROUND_COLOR}, ui_colors::{BACKGROUND_COLOR, EXTRA_BACKGROUND_COLOR},
@@ -39,6 +39,43 @@ impl MyEguiApp {
ui.heading("Heroic"); ui.heading("Heroic");
ui.checkbox(&mut self.settings.heroic.enabled, "Import from Heroic"); ui.checkbox(&mut self.settings.heroic.enabled, "Import from Heroic");
let safe_mode_header = match self.settings.heroic.launch_games_through_heroic.len() {
0 => "Force games to launch through Heroic Launcher".to_string(),
1 => "One game forced to launch through Heroic Launcher".to_string(),
x => format!("{} games forced to launch through Heroic Launcher", x),
};
egui::CollapsingHeader::new(safe_mode_header)
.id_source("Heroic_Launcher_safe_launch")
.show(ui, |ui| {
ui.label("Some games must be started from the Heroic Launcher, select those games below and BoilR will create shortcuts that opens the games through the Heroic Launcher.");
let manifests =self.heroic_games.get_or_insert_with(||{
let heroic_setting = self.settings.heroic.clone();
let install_modes = vec![crate::heroic::InstallationMode::FlatPak, crate::heroic::InstallationMode::UserBin];
let heroic_platform =HeroicPlatform{
settings:heroic_setting
};
let heroic_games = heroic_platform.get_heroic_games(&install_modes);
heroic_games
});
let safe_open_games = &mut self.settings.heroic.launch_games_through_heroic;
for manifest in manifests{
let key = &manifest.app_name;
let display_name = &manifest.title;
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.clone());
}else{
safe_open_games.retain(|m| m!= display_name && m!= key);
}
}
}
}) ;
ui.add_space(SECTION_SPACING); ui.add_space(SECTION_SPACING);
} }
@@ -263,10 +300,12 @@ impl MyEguiApp {
}); });
ui.horizontal(|ui| { ui.horizontal(|ui| {
let mut empty_string = "".to_string(); let mut empty_string = "".to_string();
let epic_location = epic_settings.launcher_exe.as_mut().unwrap_or(&mut empty_string); let epic_location = epic_settings
ui.label("Epic Launcher Location: ").on_hover_text( .launcher_exe
"The location of the Epic launcher exe", .as_mut()
); .unwrap_or(&mut empty_string);
ui.label("Epic Launcher Location: ")
.on_hover_text("The location of the Epic launcher exe");
if ui.text_edit_singleline(epic_location).changed() { if ui.text_edit_singleline(epic_location).changed() {
if epic_location.is_empty() { if epic_location.is_empty() {
epic_settings.launcher_exe = None; epic_settings.launcher_exe = None;
+3 -1
View File
@@ -8,7 +8,7 @@ use tokio::{
sync::watch::{self, Receiver}, sync::watch::{self, Receiver},
}; };
use crate::{egs::ManifestItem, settings::Settings, sync::SyncProgress}; use crate::{egs::ManifestItem, heroic::HeroicGame, settings::Settings, sync::SyncProgress};
use super::{ use super::{
ui_colors::{ ui_colors::{
@@ -36,6 +36,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) image_selected_state: ImageSelectState, pub(crate) image_selected_state: ImageSelectState,
pub(crate) backup_state: BackupState, pub(crate) backup_state: BackupState,
} }
@@ -51,6 +52,7 @@ impl MyEguiApp {
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,
heroic_games: None,
image_selected_state: ImageSelectState::default(), image_selected_state: ImageSelectState::default(),
backup_state: BackupState::default(), backup_state: BackupState::default(),
} }