mirror of
https://github.com/djibux/BoilR.git
synced 2026-09-01 05:53:41 +02:00
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:
@@ -38,6 +38,7 @@ flatpak = false
|
|||||||
|
|
||||||
[heroic]
|
[heroic]
|
||||||
enabled = true
|
enabled = true
|
||||||
|
launch_games_through_heroic =[]
|
||||||
|
|
||||||
[amazon]
|
[amazon]
|
||||||
enabled = true
|
enabled = true
|
||||||
|
|||||||
+58
-31
@@ -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,41 +28,63 @@ impl HeroicGame {
|
|||||||
|
|
||||||
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 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);
|
||||||
|
|
||||||
#[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")]
|
||||||
#[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();
|
||||||
|
|
||||||
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(),
|
||||||
);
|
);
|
||||||
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());
|
||||||
|
|||||||
@@ -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,31 +111,58 @@ 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 {
|
||||||
install_modes: &[InstallationMode],
|
pub fn get_epic_games(
|
||||||
) -> Result<Vec<HeroicGameType>, Box<dyn Error>> {
|
&self,
|
||||||
let mut shortcuts: Vec<HeroicGame> = install_modes
|
install_modes: &[InstallationMode],
|
||||||
.iter()
|
) -> Result<Vec<HeroicGameType>, Box<dyn Error>> {
|
||||||
.filter_map(|install_mode| get_shortcuts_from_install_mode(install_mode).ok())
|
let shortcuts = self.get_heroic_games(install_modes);
|
||||||
.flatten()
|
let mut epic_shortcuts = vec![];
|
||||||
.filter(|s| s.is_installed())
|
for shortcut in shortcuts {
|
||||||
.collect();
|
epic_shortcuts.push(HeroicGameType::Epic(shortcut));
|
||||||
|
}
|
||||||
shortcuts.sort_by_key(|m| format!("{}-{}-{}", m.launch_parameters, m.executable, &m.app_name));
|
Ok(epic_shortcuts)
|
||||||
shortcuts.dedup_by_key(|m| format!("{}-{}-{}", m.launch_parameters, m.executable, &m.app_name));
|
|
||||||
let mut epic_shortcuts = vec![];
|
|
||||||
for shortcut in shortcuts {
|
|
||||||
epic_shortcuts.push(HeroicGameType::Epic(shortcut));
|
|
||||||
}
|
}
|
||||||
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>> {
|
||||||
|
|||||||
@@ -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>,
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-9
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,9 +224,9 @@ impl MyEguiApp {
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
ui.label("Authentication key: ");
|
ui.label("Authentication key: ");
|
||||||
if ui.text_edit_singleline(&mut auth_key).changed() {
|
if ui.text_edit_singleline(&mut auth_key).changed() {
|
||||||
if auth_key.is_empty(){
|
if auth_key.is_empty() {
|
||||||
self.settings.steamgrid_db.auth_key = None;
|
self.settings.steamgrid_db.auth_key = None;
|
||||||
}else{
|
} else {
|
||||||
self.settings.steamgrid_db.auth_key = Some(auth_key.to_string());
|
self.settings.steamgrid_db.auth_key = Some(auth_key.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -263,14 +300,16 @@ 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;
|
||||||
}else{
|
} else {
|
||||||
epic_settings.launcher_exe = Some(epic_location.to_string());
|
epic_settings.launcher_exe = Some(epic_location.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -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(),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user