6830 lines
278 KiB
Python
6830 lines
278 KiB
Python
#!/usr/bin/python3
|
|
|
|
import json
|
|
import re
|
|
import shutil
|
|
import socket
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
import urllib.request
|
|
import webbrowser
|
|
import gi
|
|
import psutil
|
|
import requests
|
|
import vdf
|
|
import signal
|
|
import gettext
|
|
import unicodedata
|
|
|
|
gi.require_version('Gtk', '3.0')
|
|
gi.require_version('Gdk', '3.0')
|
|
gi.require_version('AyatanaAppIndicator3', '0.1')
|
|
|
|
from gi.repository import Gtk, Gdk, GdkPixbuf, GLib, AyatanaAppIndicator3, Gio, Pango
|
|
from PIL import Image
|
|
from faugus.config_manager import *
|
|
from faugus.dark_theme import *
|
|
from faugus.shortcut_utils import build_desktop_entry, get_shortcut_icon_path
|
|
from faugus.steam_setup import *
|
|
|
|
VERSION = "1.15.1"
|
|
IS_FLATPAK = 'FLATPAK_ID' in os.environ or os.path.exists('/.flatpak-info')
|
|
|
|
faugus_banner = PathManager.system_data('faugus-launcher/faugus-banner.png')
|
|
faugus_notification = PathManager.system_data('faugus-launcher/faugus-notification.ogg')
|
|
faugus_launcher_dir = PathManager.user_config('faugus-launcher')
|
|
prefixes_dir = str(Path.home() / 'Faugus')
|
|
logs_dir = PathManager.user_config('faugus-launcher/logs')
|
|
icons_dir = PathManager.user_config('faugus-launcher/icons')
|
|
banners_dir = PathManager.user_config('faugus-launcher/banners')
|
|
config_file_dir = PathManager.user_config('faugus-launcher/config.ini')
|
|
envar_dir = PathManager.user_config('faugus-launcher/envar.txt')
|
|
share_dir = PathManager.user_data()
|
|
backup_dir = PathManager.user_config("faugus-launcher/games-backup")
|
|
faugus_mono_icon = PathManager.get_icon('faugus-mono.svg')
|
|
proton_cachyos = PathManager.system_data('steam/compatibilitytools.d/proton-cachyos-slr/')
|
|
|
|
if IS_FLATPAK:
|
|
app_dir = str(Path.home() / '.local/share/applications')
|
|
faugus_png = PathManager.get_icon('io.github.Faugus.faugus-launcher.png')
|
|
tray_icon = 'io.github.Faugus.faugus-launcher'
|
|
|
|
mono_dest = Path(os.path.expanduser('~/.local/share/faugus-launcher/faugus-mono.svg'))
|
|
mono_dest.parent.mkdir(parents=True, exist_ok=True)
|
|
if not mono_dest.exists():
|
|
shutil.copy(faugus_mono_icon, mono_dest)
|
|
faugus_mono_icon = os.path.expanduser('~/.local/share/faugus-launcher/faugus-mono.svg')
|
|
|
|
lsfgvk_path = Path("/usr/lib/extensions/vulkan/lsfgvk/lib/liblsfg-vk.so")
|
|
lsfgvk_path = lsfgvk_path if lsfgvk_path.exists() else Path(os.path.expanduser('~/.local/lib/liblsfg-vk.so'))
|
|
else:
|
|
app_dir = PathManager.user_data('applications')
|
|
faugus_png = PathManager.get_icon('faugus-launcher.png')
|
|
tray_icon = PathManager.get_icon('faugus-launcher.png')
|
|
lsfgvk_possible_paths = [
|
|
Path("/usr/lib/liblsfg-vk.so"),
|
|
Path("/usr/lib64/liblsfg-vk.so"),
|
|
Path(os.path.expanduser('~/.local/lib/liblsfg-vk.so'))
|
|
]
|
|
lsfgvk_path = next((p for p in lsfgvk_possible_paths if p.exists()), lsfgvk_possible_paths[-1])
|
|
|
|
epic_icon = PathManager.get_icon('faugus-epic-games.png')
|
|
battle_icon = PathManager.get_icon('faugus-battlenet.png')
|
|
ubisoft_icon = PathManager.get_icon('faugus-ubisoft-connect.png')
|
|
ea_icon = PathManager.get_icon('faugus-ea.png')
|
|
rockstar_icon = PathManager.get_icon('faugus-rockstar.png')
|
|
|
|
faugus_run = PathManager.find_binary('faugus-run')
|
|
faugus_proton_manager = PathManager.find_binary('faugus-proton-manager')
|
|
umu_run = PathManager.user_data('faugus-launcher/umu-run')
|
|
mangohud_dir = PathManager.find_binary('mangohud')
|
|
gamemoderun = PathManager.find_binary('gamemoderun')
|
|
|
|
games_json = PathManager.user_config('faugus-launcher/games.json')
|
|
latest_games = PathManager.user_config('faugus-launcher/latest-games.txt')
|
|
faugus_launcher_share_dir = PathManager.user_data('faugus-launcher')
|
|
faugus_temp = str(Path.home() / 'faugus_temp')
|
|
running_games = PathManager.user_data('faugus-launcher/running_games.json')
|
|
|
|
faugus_backup = False
|
|
|
|
os.makedirs(faugus_launcher_share_dir, exist_ok=True)
|
|
os.makedirs(faugus_launcher_dir, exist_ok=True)
|
|
|
|
def get_desktop_dir():
|
|
try:
|
|
desktop_dir = subprocess.check_output(['xdg-user-dir', 'DESKTOP'], text=True).strip()
|
|
return desktop_dir
|
|
except (FileNotFoundError, subprocess.CalledProcessError):
|
|
print("xdg-user-dir not found or failed; falling back to ~/Desktop")
|
|
return str(Path.home() / 'Desktop')
|
|
|
|
desktop_dir = get_desktop_dir()
|
|
|
|
try:
|
|
translation = gettext.translation(
|
|
'faugus-launcher',
|
|
localedir=LOCALE_DIR,
|
|
languages=[lang] if lang else ['en_US']
|
|
)
|
|
translation.install()
|
|
globals()['_'] = translation.gettext
|
|
except FileNotFoundError:
|
|
gettext.install('faugus-launcher', localedir=LOCALE_DIR)
|
|
globals()['_'] = gettext.gettext
|
|
|
|
def format_title(title):
|
|
title = title.strip().lower()
|
|
title = re.sub(r"[^a-z0-9 ]+", "", title)
|
|
title = re.sub(r"\s+", "-", title)
|
|
return title
|
|
|
|
def convert_runner(runner):
|
|
if runner == "Proton-GE Latest":
|
|
return "GE-Proton Latest (default)"
|
|
|
|
if runner == "GE-Proton Latest (default)":
|
|
return "Proton-GE Latest"
|
|
|
|
if runner == "UMU-Proton Latest":
|
|
return ""
|
|
|
|
if runner == "":
|
|
return "UMU-Proton Latest"
|
|
|
|
return runner
|
|
|
|
def _validate_text(entry):
|
|
text = entry.get_text()
|
|
for i, c in enumerate(text):
|
|
if c.isalpha() and "LATIN" not in unicodedata.name(c, ""):
|
|
new_text = text[:i] + text[i+1:]
|
|
entry.set_text(new_text)
|
|
entry.set_position(i)
|
|
break
|
|
|
|
class FaugusApp(Gtk.Application):
|
|
def __init__(self):
|
|
super().__init__(
|
|
application_id="io.github.Faugus.faugus-launcher",
|
|
flags=Gio.ApplicationFlags.HANDLES_OPEN
|
|
)
|
|
self.window = None
|
|
|
|
def do_startup(self):
|
|
Gtk.Application.do_startup(self)
|
|
os.environ["GTK_USE_PORTAL"] = "1"
|
|
apply_dark_theme()
|
|
|
|
def do_activate(self):
|
|
if not self.window:
|
|
self.window = Main(self)
|
|
self.window.present()
|
|
|
|
def do_open(self, files, n_files, hint):
|
|
self.do_activate()
|
|
for file in files:
|
|
run_file(file.get_path())
|
|
|
|
class Main(Gtk.ApplicationWindow):
|
|
def __init__(self, app):
|
|
super().__init__(application=app, title="Faugus Launcher")
|
|
self.set_icon_from_file(faugus_png)
|
|
self.connect("delete-event", self.on_close)
|
|
print(f"Faugus Launcher {VERSION}")
|
|
|
|
self.start_maximized = False
|
|
self.start_fullscreen = False
|
|
self.fullscreen_activated = False
|
|
|
|
self.system_tray = False
|
|
self.indicator = False
|
|
self.start_boot = False
|
|
self.mono_icon = False
|
|
|
|
self.current_prefix = None
|
|
self.games = []
|
|
self.flowbox_child = None
|
|
self.updated_steam_id = None
|
|
self.game_running = False
|
|
|
|
self.last_click_time = 0
|
|
self.last_clicked_item = None
|
|
self.double_click_time_threshold = 400
|
|
|
|
self.processos = {}
|
|
self.button_locked = {}
|
|
|
|
self.working_directory = faugus_launcher_dir
|
|
os.chdir(self.working_directory)
|
|
|
|
self.provider = Gtk.CssProvider()
|
|
self.provider.load_from_data(b"""
|
|
.hbox-favorite {
|
|
background-color: alpha(@theme_selected_bg_color, 0.25);
|
|
}
|
|
.hbox-normal {
|
|
background-color: alpha(@theme_base_color, 0.5);
|
|
}
|
|
""")
|
|
Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), self.provider,
|
|
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
|
|
|
|
self.load_config()
|
|
|
|
self.context_menu = Gtk.Menu()
|
|
|
|
self.menu_item_title = Gtk.MenuItem(label="")
|
|
self.menu_item_title.set_sensitive(False)
|
|
self.context_menu.append(self.menu_item_title)
|
|
|
|
self.menu_item_playtime = Gtk.MenuItem(label="")
|
|
self.menu_item_playtime.set_sensitive(False)
|
|
self.context_menu.append(self.menu_item_playtime)
|
|
|
|
self.context_menu.append(Gtk.SeparatorMenuItem())
|
|
|
|
self.menu_item_play = Gtk.MenuItem(label=_("Play"))
|
|
self.menu_item_play.connect("activate", self.on_context_menu_play)
|
|
self.context_menu.append(self.menu_item_play)
|
|
|
|
self.menu_item_edit = Gtk.MenuItem(label=_("Edit"))
|
|
self.menu_item_edit.connect("activate", self.on_context_menu_edit)
|
|
self.context_menu.append(self.menu_item_edit)
|
|
|
|
self.menu_item_delete = Gtk.MenuItem(label=_("Delete"))
|
|
self.menu_item_delete.connect("activate", self.on_context_menu_delete)
|
|
self.context_menu.append(self.menu_item_delete)
|
|
|
|
self.menu_item_duplicate = Gtk.MenuItem(label=_("Duplicate"))
|
|
self.menu_item_duplicate.connect("activate", self.on_context_menu_duplicate)
|
|
self.context_menu.append(self.menu_item_duplicate)
|
|
|
|
self.menu_item_hide = Gtk.MenuItem(label=_("Hide"))
|
|
self.menu_item_hide.connect("activate", self.on_context_menu_hide)
|
|
self.context_menu.append(self.menu_item_hide)
|
|
|
|
self.menu_item_favorite = Gtk.MenuItem(label=_("Add to favorites"))
|
|
self.menu_item_favorite.connect("activate", self.on_context_menu_favorite)
|
|
self.context_menu.append(self.menu_item_favorite)
|
|
|
|
self.menu_item_game = Gtk.MenuItem(label=_("Open game location"))
|
|
self.menu_item_game.connect("activate", self.on_context_menu_game)
|
|
self.context_menu.append(self.menu_item_game)
|
|
|
|
self.menu_item_prefix = Gtk.MenuItem(label=_("Open prefix location"))
|
|
self.menu_item_prefix.connect("activate", self.on_context_menu_prefix)
|
|
self.context_menu.append(self.menu_item_prefix)
|
|
|
|
self.menu_show_logs = Gtk.MenuItem(label=_("Show logs"))
|
|
self.menu_show_logs.connect("activate", self.on_context_show_logs)
|
|
self.context_menu.append(self.menu_show_logs)
|
|
|
|
self.context_menu.show_all()
|
|
|
|
if self.interface_mode == "List":
|
|
self.small_interface()
|
|
if self.interface_mode == "Blocks":
|
|
if self.start_maximized:
|
|
self.maximize()
|
|
if self.start_fullscreen:
|
|
self.fullscreen()
|
|
self.fullscreen_activated = True
|
|
self.big_interface()
|
|
if self.interface_mode == "Banners":
|
|
if self.start_maximized:
|
|
self.maximize()
|
|
if self.start_fullscreen:
|
|
self.fullscreen()
|
|
self.fullscreen_activated = True
|
|
self.big_interface()
|
|
if not self.interface_mode:
|
|
self.interface_mode = "List"
|
|
self.small_interface()
|
|
|
|
self.flowbox.connect("button-press-event", self.on_item_right_click)
|
|
|
|
self.load_tray_icon()
|
|
|
|
if IS_FLATPAK:
|
|
signal.signal(signal.SIGCHLD, self.on_child_process_closed)
|
|
else:
|
|
GLib.timeout_add_seconds(1, self.check_running_processes)
|
|
|
|
def load_tray_icon(self):
|
|
if not self.system_tray:
|
|
if self.indicator:
|
|
try:
|
|
self.indicator.set_status(AyatanaAppIndicator3.IndicatorStatus.PASSIVE)
|
|
except:
|
|
pass
|
|
self.indicator = None
|
|
return
|
|
|
|
menu = Gtk.Menu()
|
|
if os.path.exists(latest_games):
|
|
with open(latest_games, "r") as f:
|
|
for line in f:
|
|
name = line.strip()
|
|
if name:
|
|
item = Gtk.MenuItem(label=name)
|
|
item.connect("activate", self.on_game_selected, name)
|
|
menu.append(item)
|
|
|
|
if menu.get_children():
|
|
menu.append(Gtk.SeparatorMenuItem())
|
|
|
|
restore_item = Gtk.MenuItem(label=_("Open Faugus Launcher"))
|
|
restore_item.connect("activate", self.restore_window)
|
|
menu.append(restore_item)
|
|
|
|
quit_item = Gtk.MenuItem(label=_("Quit"))
|
|
quit_item.connect("activate", self.on_quit)
|
|
menu.append(quit_item)
|
|
|
|
menu.show_all()
|
|
|
|
if self.indicator:
|
|
try:
|
|
old = self.indicator
|
|
self.indicator = None
|
|
old.set_status(AyatanaAppIndicator3.IndicatorStatus.PASSIVE)
|
|
del old
|
|
except:
|
|
pass
|
|
while Gtk.events_pending():
|
|
Gtk.main_iteration()
|
|
|
|
if not hasattr(self, "_tray_counter"):
|
|
self._tray_counter = 0
|
|
self._tray_counter += 1
|
|
unique_id = f"faugus-launcher-{self._tray_counter}"
|
|
|
|
icon = faugus_mono_icon if self.mono_icon else tray_icon
|
|
|
|
self.indicator = AyatanaAppIndicator3.Indicator.new(
|
|
unique_id, icon, AyatanaAppIndicator3.IndicatorCategory.APPLICATION_STATUS
|
|
)
|
|
self.indicator.set_menu(menu)
|
|
self.indicator.set_icon_full(icon, "Faugus Launcher")
|
|
self.indicator.set_status(AyatanaAppIndicator3.IndicatorStatus.ACTIVE)
|
|
self.indicator.set_icon_theme_path("")
|
|
|
|
def on_close(self, *_):
|
|
if self.system_tray:
|
|
self.hide()
|
|
return True
|
|
|
|
if self.indicator:
|
|
self.indicator.set_status(
|
|
AyatanaAppIndicator3.IndicatorStatus.PASSIVE
|
|
)
|
|
self.get_application().quit()
|
|
return False
|
|
|
|
def restore_window(self, *_):
|
|
self.show_all()
|
|
if self.interface_mode != "List":
|
|
if self.fullscreen_activated:
|
|
self.fullscreen_activated = True
|
|
self.grid_corner.set_visible(True)
|
|
self.grid_left.set_margin_start(70)
|
|
else:
|
|
self.fullscreen_activated = False
|
|
self.grid_corner.set_visible(False)
|
|
self.grid_left.set_margin_start(0)
|
|
self.present()
|
|
|
|
def on_quit(self, *_):
|
|
if self.indicator:
|
|
self.indicator.set_status(
|
|
AyatanaAppIndicator3.IndicatorStatus.PASSIVE
|
|
)
|
|
self.get_application().quit()
|
|
|
|
def select_first_child(self):
|
|
if self.flowbox.get_children():
|
|
self.flowbox.select_child(self.flowbox.get_children()[0])
|
|
self.on_item_selected(self.flowbox, self.flowbox.get_children()[0])
|
|
|
|
def on_child_process_closed(self, signum, frame):
|
|
for title, processo in list(self.processos.items()):
|
|
retcode = processo.poll()
|
|
if retcode is not None:
|
|
del self.processos[title]
|
|
|
|
selected_child = None
|
|
|
|
for child in self.flowbox.get_children():
|
|
if child.get_state_flags() & Gtk.StateFlags.SELECTED:
|
|
selected_child = child
|
|
break
|
|
|
|
if selected_child:
|
|
hbox = selected_child.get_children()[0]
|
|
game_label = hbox.get_children()[1]
|
|
selected_title = game_label.get_text()
|
|
|
|
if selected_title not in self.processos:
|
|
self.menu_item_play.set_sensitive(True)
|
|
self.button_play.set_sensitive(True)
|
|
self.button_play.set_image(
|
|
Gtk.Image.new_from_icon_name("faugus-play-symbolic", Gtk.IconSize.BUTTON))
|
|
else:
|
|
self.menu_item_play.set_sensitive(False)
|
|
self.button_play.set_sensitive(False)
|
|
self.button_play.set_image(
|
|
Gtk.Image.new_from_icon_name("faugus-stop-symbolic", Gtk.IconSize.BUTTON))
|
|
|
|
def check_running_processes(self):
|
|
processos = self.load_processes_from_file()
|
|
|
|
updated = False
|
|
to_remove = []
|
|
|
|
for title, data in processos.items():
|
|
pid_main = data.get("faugus-run") or data.get("main")
|
|
|
|
try:
|
|
proc = psutil.Process(pid_main)
|
|
if proc.status() == psutil.STATUS_ZOMBIE:
|
|
to_remove.append(title)
|
|
except psutil.NoSuchProcess:
|
|
to_remove.append(title)
|
|
except Exception:
|
|
to_remove.append(title)
|
|
|
|
for title in to_remove:
|
|
del processos[title]
|
|
updated = True
|
|
if title in self.button_locked:
|
|
del self.button_locked[title]
|
|
|
|
if updated:
|
|
with open(running_games, "w") as f:
|
|
json.dump(processos, f, indent=2)
|
|
|
|
selected_child = None
|
|
for child in self.flowbox.get_children():
|
|
if child.get_state_flags() & Gtk.StateFlags.SELECTED:
|
|
selected_child = child
|
|
break
|
|
|
|
if selected_child:
|
|
hbox = selected_child.get_children()[0]
|
|
game_label = hbox.get_children()[1]
|
|
selected_title = game_label.get_text()
|
|
self.on_item_selected(self.flowbox, selected_child)
|
|
|
|
return True
|
|
|
|
def load_processes_from_file(self):
|
|
if os.path.exists(running_games):
|
|
try:
|
|
with open(running_games, "r") as f:
|
|
return json.load(f)
|
|
except json.JSONDecodeError:
|
|
return {}
|
|
return {}
|
|
|
|
def small_interface(self):
|
|
self.set_default_size(-1, 610)
|
|
self.set_resizable(False)
|
|
self.big_interface_active = False
|
|
|
|
# Create main box and its components
|
|
self.box_main = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
self.box_top = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
self.box_bottom = Gtk.Box()
|
|
|
|
# Create buttons for adding, editing, and deleting games
|
|
self.button_add = Gtk.Button()
|
|
self.button_add.connect("clicked", self.on_button_add_clicked)
|
|
self.button_add.set_can_focus(False)
|
|
self.button_add.set_size_request(50, 50)
|
|
self.button_add.set_image(Gtk.Image.new_from_icon_name("faugus-add-symbolic", Gtk.IconSize.BUTTON))
|
|
self.button_add.set_margin_top(10)
|
|
self.button_add.set_margin_start(10)
|
|
self.button_add.set_margin_bottom(10)
|
|
|
|
# Create button for killing processes
|
|
button_kill = Gtk.Button()
|
|
button_kill.connect("clicked", self.on_button_kill_clicked)
|
|
button_kill.set_can_focus(False)
|
|
button_kill.set_tooltip_text(_("Force close all running games"))
|
|
button_kill.set_size_request(50, 50)
|
|
button_kill.set_image(Gtk.Image.new_from_icon_name("faugus-kill-symbolic", Gtk.IconSize.BUTTON))
|
|
button_kill.set_margin_top(10)
|
|
button_kill.set_margin_end(10)
|
|
button_kill.set_margin_bottom(10)
|
|
|
|
# Create button for settings
|
|
button_settings = Gtk.Button()
|
|
button_settings.connect("clicked", self.on_button_settings_clicked)
|
|
button_settings.set_can_focus(False)
|
|
button_settings.set_size_request(50, 50)
|
|
button_settings.set_image(Gtk.Image.new_from_icon_name("faugus-settings-symbolic", Gtk.IconSize.BUTTON))
|
|
button_settings.set_margin_top(10)
|
|
button_settings.set_margin_start(10)
|
|
button_settings.set_margin_bottom(10)
|
|
|
|
# Create button for launching games
|
|
self.button_play = Gtk.Button()
|
|
self.button_play.connect("clicked", self.on_button_play_clicked)
|
|
self.button_play.set_can_focus(False)
|
|
self.button_play.set_size_request(50, 50)
|
|
self.button_play.set_image(Gtk.Image.new_from_icon_name("faugus-play-symbolic", Gtk.IconSize.BUTTON))
|
|
self.button_play.set_margin_top(10)
|
|
self.button_play.set_margin_end(10)
|
|
self.button_play.set_margin_bottom(10)
|
|
|
|
self.entry_search = Gtk.Entry()
|
|
self.entry_search.set_placeholder_text(_("Search..."))
|
|
self.entry_search.connect("changed", self.on_search_changed)
|
|
|
|
self.entry_search.set_size_request(170, 50)
|
|
self.entry_search.set_margin_top(10)
|
|
self.entry_search.set_margin_start(10)
|
|
self.entry_search.set_margin_bottom(10)
|
|
self.entry_search.set_margin_end(10)
|
|
|
|
# Create scrolled window for game list
|
|
scroll_box = Gtk.ScrolledWindow()
|
|
scroll_box.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
|
scroll_box.set_margin_start(10)
|
|
scroll_box.set_margin_top(10)
|
|
scroll_box.set_margin_end(10)
|
|
|
|
self.flowbox = Gtk.FlowBox()
|
|
self.flowbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
|
self.flowbox.set_halign(Gtk.Align.FILL)
|
|
self.flowbox.set_valign(Gtk.Align.START)
|
|
self.flowbox.connect('child-activated', self.on_item_selected)
|
|
self.flowbox.connect('button-release-event', self.on_item_release_event)
|
|
|
|
def sort_games(child1, child2, _):
|
|
g1 = child1.game
|
|
g2 = child2.game
|
|
|
|
if g1.favorite and not g2.favorite:
|
|
return -1
|
|
if not g1.favorite and g2.favorite:
|
|
return 1
|
|
|
|
return (g1.title > g2.title) - (g1.title < g2.title)
|
|
|
|
self.flowbox.set_sort_func(sort_games, None)
|
|
|
|
scroll_box.add(self.flowbox)
|
|
self.load_games()
|
|
|
|
# Pack left and scrolled box into the top box
|
|
self.box_top.pack_start(scroll_box, True, True, 0)
|
|
|
|
# Pack buttons and other components into the bottom box
|
|
self.box_bottom.pack_start(self.button_add, False, False, 0)
|
|
self.box_bottom.pack_start(button_settings, False, False, 0)
|
|
self.box_bottom.pack_start(self.entry_search, True, True, 0)
|
|
self.box_bottom.pack_end(self.button_play, False, False, 0)
|
|
self.box_bottom.pack_end(button_kill, False, False, 0)
|
|
|
|
# Pack top and bottom boxes into the main box
|
|
self.box_main.pack_start(self.box_top, True, True, 0)
|
|
self.box_main.pack_end(self.box_bottom, False, True, 0)
|
|
self.add(self.box_main)
|
|
|
|
self.menu_item_edit.set_sensitive(False)
|
|
self.menu_item_delete.set_sensitive(False)
|
|
self.menu_item_play.set_sensitive(False)
|
|
self.button_play.set_sensitive(False)
|
|
|
|
self.select_first_child()
|
|
|
|
self.connect("key-press-event", self.on_key_press_event)
|
|
self.show_all()
|
|
|
|
def big_interface(self):
|
|
self.set_default_size(1280, 720)
|
|
self.set_resizable(True)
|
|
self.big_interface_active = True
|
|
|
|
# Create main box and its components
|
|
self.box_main = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
self.box_top = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
|
self.box_bottom = Gtk.Box()
|
|
|
|
# Create buttons for adding, editing, and deleting games
|
|
self.button_add = Gtk.Button()
|
|
self.button_add.connect("clicked", self.on_button_add_clicked)
|
|
self.button_add.set_can_focus(False)
|
|
self.button_add.set_size_request(50, 50)
|
|
self.button_add.set_image(Gtk.Image.new_from_icon_name("faugus-add-symbolic", Gtk.IconSize.BUTTON))
|
|
self.button_add.set_margin_top(10)
|
|
self.button_add.set_margin_start(10)
|
|
self.button_add.set_margin_bottom(10)
|
|
|
|
# Create button for killing processes
|
|
button_kill = Gtk.Button()
|
|
button_kill.connect("clicked", self.on_button_kill_clicked)
|
|
button_kill.set_can_focus(False)
|
|
button_kill.set_tooltip_text(_("Force close all running games"))
|
|
button_kill.set_size_request(50, 50)
|
|
button_kill.set_image(Gtk.Image.new_from_icon_name("faugus-kill-symbolic", Gtk.IconSize.BUTTON))
|
|
button_kill.set_margin_top(10)
|
|
button_kill.set_margin_bottom(10)
|
|
|
|
# Create button for exiting
|
|
button_bye = Gtk.Button()
|
|
button_bye.connect("clicked", self.on_button_bye_clicked)
|
|
button_bye.set_can_focus(False)
|
|
button_bye.set_size_request(50, 50)
|
|
button_bye.set_image(Gtk.Image.new_from_icon_name("faugus-exit-symbolic", Gtk.IconSize.BUTTON))
|
|
button_bye.set_margin_start(10)
|
|
button_bye.set_margin_top(10)
|
|
button_bye.set_margin_bottom(10)
|
|
button_bye.set_margin_end(10)
|
|
|
|
# Create button for settings
|
|
button_settings = Gtk.Button()
|
|
button_settings.connect("clicked", self.on_button_settings_clicked)
|
|
button_settings.set_can_focus(False)
|
|
button_settings.set_size_request(50, 50)
|
|
button_settings.set_image(Gtk.Image.new_from_icon_name("faugus-settings-symbolic", Gtk.IconSize.BUTTON))
|
|
button_settings.set_margin_top(10)
|
|
button_settings.set_margin_start(10)
|
|
button_settings.set_margin_bottom(10)
|
|
|
|
# Create button for launching games
|
|
self.button_play = Gtk.Button()
|
|
self.button_play.connect("clicked", self.on_button_play_clicked)
|
|
self.button_play.set_can_focus(False)
|
|
self.button_play.set_size_request(50, 50)
|
|
self.button_play.set_image(Gtk.Image.new_from_icon_name("faugus-play-symbolic", Gtk.IconSize.BUTTON))
|
|
self.button_play.set_margin_top(10)
|
|
self.button_play.set_margin_start(10)
|
|
self.button_play.set_margin_end(10)
|
|
self.button_play.set_margin_bottom(10)
|
|
|
|
self.entry_search = Gtk.Entry()
|
|
self.entry_search.set_placeholder_text(_("Search..."))
|
|
self.entry_search.connect("changed", self.on_search_changed)
|
|
|
|
self.entry_search.set_size_request(170, 50)
|
|
self.entry_search.set_margin_top(10)
|
|
self.entry_search.set_margin_start(10)
|
|
self.entry_search.set_margin_bottom(10)
|
|
self.entry_search.set_margin_end(10)
|
|
|
|
self.grid_left = Gtk.Grid()
|
|
self.grid_left.set_hexpand(True)
|
|
self.grid_left.set_halign(Gtk.Align.END)
|
|
|
|
self.grid_left.add(self.button_add)
|
|
self.grid_left.add(button_settings)
|
|
|
|
grid_middle = Gtk.Grid()
|
|
grid_middle.add(self.entry_search)
|
|
|
|
grid_right = Gtk.Grid()
|
|
grid_right.set_hexpand(True)
|
|
grid_right.set_halign(Gtk.Align.START)
|
|
|
|
grid_right.add(button_kill)
|
|
grid_right.add(self.button_play)
|
|
|
|
self.grid_corner = Gtk.Grid()
|
|
self.grid_corner.add(button_bye)
|
|
|
|
# Create scrolled window for game list
|
|
scroll_box = Gtk.ScrolledWindow()
|
|
scroll_box.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
|
scroll_box.set_margin_top(10)
|
|
scroll_box.set_margin_end(10)
|
|
scroll_box.set_margin_start(10)
|
|
scroll_box.set_margin_bottom(10)
|
|
|
|
self.flowbox = Gtk.FlowBox()
|
|
self.flowbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
|
self.flowbox.set_halign(Gtk.Align.CENTER)
|
|
self.flowbox.set_valign(Gtk.Align.CENTER)
|
|
self.flowbox.set_min_children_per_line(2)
|
|
self.flowbox.set_max_children_per_line(20)
|
|
self.flowbox.connect('child-activated', self.on_item_selected)
|
|
self.flowbox.connect('button-release-event', self.on_item_release_event)
|
|
|
|
def sort_games(child1, child2, _):
|
|
g1 = child1.game
|
|
g2 = child2.game
|
|
|
|
if g1.favorite and not g2.favorite:
|
|
return -1
|
|
if not g1.favorite and g2.favorite:
|
|
return 1
|
|
|
|
return (g1.title > g2.title) - (g1.title < g2.title)
|
|
|
|
self.flowbox.set_sort_func(sort_games, None)
|
|
|
|
scroll_box.add(self.flowbox)
|
|
self.load_games()
|
|
|
|
self.box_top.pack_start(scroll_box, True, True, 0)
|
|
|
|
self.box_bottom.pack_start(self.grid_left, True, True, 0)
|
|
self.box_bottom.pack_start(grid_middle, False, False, 0)
|
|
self.box_bottom.pack_start(grid_right, True, True, 0)
|
|
self.box_bottom.pack_end(self.grid_corner, False, False, 0)
|
|
|
|
self.box_main.pack_start(self.box_top, True, True, 0)
|
|
self.box_main.pack_end(self.box_bottom, False, True, 0)
|
|
self.add(self.box_main)
|
|
|
|
self.menu_item_edit.set_sensitive(False)
|
|
self.menu_item_delete.set_sensitive(False)
|
|
self.menu_item_play.set_sensitive(False)
|
|
self.button_play.set_sensitive(False)
|
|
|
|
self.select_first_child()
|
|
|
|
self.connect("key-press-event", self.on_key_press_event)
|
|
self.show_all()
|
|
if self.start_fullscreen:
|
|
self.fullscreen_activated = True
|
|
self.grid_corner.set_visible(True)
|
|
self.grid_left.set_margin_start(70)
|
|
else:
|
|
self.fullscreen_activated = False
|
|
self.grid_corner.set_visible(False)
|
|
self.grid_left.set_margin_start(0)
|
|
|
|
def on_destroy(self, *args):
|
|
self.get_application().quit()
|
|
|
|
def on_button_bye_clicked(self, widget):
|
|
menu = Gtk.Menu()
|
|
|
|
shutdown_item = Gtk.MenuItem(label=_("Shut down"))
|
|
reboot_item = Gtk.MenuItem(label=_("Reboot"))
|
|
close_item = Gtk.MenuItem(label=_("Close"))
|
|
|
|
shutdown_item.connect("activate", self.on_shutdown)
|
|
reboot_item.connect("activate", self.on_reboot)
|
|
close_item.connect("activate", self.on_close_fullscreen)
|
|
|
|
menu.append(shutdown_item)
|
|
menu.append(reboot_item)
|
|
menu.append(close_item)
|
|
|
|
menu.show_all()
|
|
menu.attach_to_widget(self, None)
|
|
menu.popup(None, None, None, None, 0, Gtk.get_current_event_time())
|
|
|
|
def on_shutdown(self, widget):
|
|
subprocess.run(["pkexec", "shutdown", "-h", "now"])
|
|
|
|
def on_reboot(self, widget):
|
|
subprocess.run(["pkexec", "reboot"])
|
|
|
|
def on_close_fullscreen(self, widget):
|
|
self.get_application().quit()
|
|
|
|
def on_item_right_click(self, widget, event):
|
|
if event.button == Gdk.BUTTON_SECONDARY:
|
|
item = self.get_item_at_event(event)
|
|
if item:
|
|
self.flowbox.emit('child-activated', item)
|
|
self.flowbox.select_child(item)
|
|
|
|
selected_children = self.flowbox.get_selected_children()
|
|
selected_child = selected_children[0]
|
|
game = selected_child.game
|
|
title = game.title
|
|
|
|
self.menu_item_title.get_child().set_text(title)
|
|
|
|
with open(games_json, "r") as f:
|
|
data = json.load(f)
|
|
|
|
for item in data:
|
|
if isinstance(item, dict) and item.get("gameid") == game.gameid:
|
|
game.playtime = item.get("playtime", 0)
|
|
formatted = self.format_playtime(game.playtime)
|
|
if not formatted:
|
|
self.menu_item_playtime.hide()
|
|
else:
|
|
self.menu_item_playtime.show()
|
|
self.menu_item_playtime.get_child().set_text(formatted)
|
|
break
|
|
|
|
if game.protonfix:
|
|
match = re.search(r"umu-(\d+)", game.protonfix)
|
|
if match:
|
|
log_id = match.group(1)
|
|
else:
|
|
log_id = "0"
|
|
self.log_file_path = f"{logs_dir}/{game.gameid}/steam-{log_id}.log"
|
|
else:
|
|
self.log_file_path = f"{logs_dir}/{game.gameid}/steam-0.log"
|
|
self.umu_log_file_path = f"{logs_dir}/{game.gameid}/umu.log"
|
|
|
|
if self.enable_logging:
|
|
self.menu_show_logs.set_visible(True)
|
|
if os.path.exists(self.log_file_path):
|
|
self.menu_show_logs.set_sensitive(True)
|
|
self.current_title = title
|
|
else:
|
|
self.menu_show_logs.set_sensitive(False)
|
|
else:
|
|
self.menu_show_logs.set_visible(False)
|
|
|
|
if game.hidden:
|
|
self.menu_item_hide.get_child().set_text(_("Remove from hidden"))
|
|
else:
|
|
self.menu_item_hide.get_child().set_text(_("Hide"))
|
|
|
|
if game.favorite:
|
|
self.menu_item_favorite.get_child().set_text(_("Remove from favorites"))
|
|
else:
|
|
self.menu_item_favorite.get_child().set_text(_("Add to favorites"))
|
|
|
|
if game.runner == "Steam":
|
|
self.menu_item_duplicate.set_visible(False)
|
|
self.menu_item_game.set_visible(False)
|
|
self.menu_item_prefix.set_visible(False)
|
|
self.menu_show_logs.set_visible(False)
|
|
elif game.runner == "Linux-Native":
|
|
self.menu_item_duplicate.set_visible(True)
|
|
self.menu_item_game.set_visible(True)
|
|
self.menu_item_prefix.set_visible(False)
|
|
self.menu_show_logs.set_visible(False)
|
|
else:
|
|
self.menu_item_duplicate.set_visible(True)
|
|
self.menu_item_game.set_visible(True)
|
|
self.menu_item_prefix.set_visible(True)
|
|
|
|
processos = self.load_processes_from_file()
|
|
if title in processos:
|
|
self.menu_item_play.get_child().set_text(_("Stop"))
|
|
else:
|
|
self.menu_item_play.get_child().set_text(_("Play"))
|
|
|
|
if os.path.dirname(game.path):
|
|
self.menu_item_game.set_sensitive(True)
|
|
self.current_game = os.path.dirname(game.path)
|
|
else:
|
|
self.menu_item_game.set_sensitive(False)
|
|
self.current_game = None
|
|
|
|
if os.path.isdir(game.prefix):
|
|
self.menu_item_prefix.set_sensitive(True)
|
|
self.current_prefix = game.prefix
|
|
else:
|
|
self.menu_item_prefix.set_sensitive(False)
|
|
self.current_prefix = None
|
|
|
|
self.context_menu.popup_at_pointer(event)
|
|
|
|
def format_playtime(self, seconds):
|
|
if not seconds:
|
|
return None
|
|
|
|
try:
|
|
seconds = int(seconds)
|
|
except (ValueError, TypeError):
|
|
seconds = 0
|
|
|
|
hours = seconds // 3600
|
|
minutes = (seconds % 3600) // 60
|
|
|
|
if hours == 0 and minutes == 0:
|
|
return None
|
|
|
|
txt_hour = _("hour")
|
|
txt_hours = _("hours")
|
|
txt_minute = _("minute")
|
|
txt_minutes = _("minutes")
|
|
|
|
parts = []
|
|
|
|
if hours > 0:
|
|
word = txt_hour if hours == 1 else txt_hours
|
|
parts.append(f"{hours} {word}")
|
|
|
|
if minutes > 0:
|
|
word = txt_minute if minutes == 1 else txt_minutes
|
|
parts.append(f"{minutes} {word}")
|
|
|
|
return " ".join(parts)
|
|
|
|
def on_context_menu_play(self, menu_item):
|
|
selected_item = self.flowbox.get_selected_children()[0]
|
|
self.on_button_play_clicked(selected_item)
|
|
|
|
def on_context_menu_edit(self, menu_item):
|
|
selected_item = self.flowbox.get_selected_children()[0]
|
|
self.on_button_edit_clicked(selected_item)
|
|
|
|
def on_context_menu_delete(self, menu_item):
|
|
selected_item = self.flowbox.get_selected_children()[0]
|
|
self.on_button_delete_clicked(selected_item)
|
|
|
|
def on_context_menu_duplicate(self, menu_item):
|
|
selected_item = self.flowbox.get_selected_children()[0]
|
|
self.on_duplicate_clicked(selected_item)
|
|
|
|
def on_context_menu_hide(self, menu_item):
|
|
selected = self.flowbox.get_selected_children()
|
|
if not selected:
|
|
return
|
|
|
|
game = selected[0].game
|
|
if not game:
|
|
return
|
|
|
|
try:
|
|
with open(games_json, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
for item in data:
|
|
if item.get("gameid") == game.gameid:
|
|
item["hidden"] = not item.get("hidden", False)
|
|
game.hidden = item["hidden"]
|
|
break
|
|
|
|
with open(games_json, "w", encoding="utf-8") as f:
|
|
json.dump(data, f, indent=4, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
return
|
|
|
|
self.update_list()
|
|
self.select_first_child()
|
|
|
|
def on_context_menu_favorite(self, menu_item):
|
|
selected = self.flowbox.get_selected_children()
|
|
if not selected:
|
|
return
|
|
|
|
game = selected[0].game
|
|
if not game:
|
|
return
|
|
|
|
selected_gameid = game.gameid
|
|
|
|
try:
|
|
with open(games_json, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
for item in data:
|
|
if item.get("gameid") == game.gameid:
|
|
item["favorite"] = not item.get("favorite", False)
|
|
game.favorite = item["favorite"]
|
|
break
|
|
|
|
with open(games_json, "w", encoding="utf-8") as f:
|
|
json.dump(data, f, indent=4, ensure_ascii=False)
|
|
|
|
except Exception as e:
|
|
return
|
|
|
|
self.update_list()
|
|
|
|
for child in self.flowbox.get_children():
|
|
if hasattr(child, "game") and child.game.gameid == selected_gameid:
|
|
self.flowbox.select_child(child)
|
|
self.flowbox.set_focus_child(child)
|
|
break
|
|
|
|
def on_context_menu_game(self, menu_item):
|
|
subprocess.run(["xdg-open", self.current_game], check=True)
|
|
|
|
def on_context_menu_prefix(self, menu_item):
|
|
subprocess.run(["xdg-open", self.current_prefix], check=True)
|
|
|
|
def on_context_show_logs(self, menu_item):
|
|
selected_item = self.flowbox.get_selected_children()[0]
|
|
self.on_show_logs_clicked(selected_item)
|
|
|
|
def on_show_logs_clicked(self, widget):
|
|
dialog = Gtk.Dialog(title=_("%s Logs") % self.current_title, parent=self, modal=True)
|
|
dialog.set_icon_from_file(faugus_png)
|
|
dialog.set_default_size(1280, 720)
|
|
|
|
scrolled_window1 = Gtk.ScrolledWindow()
|
|
scrolled_window1.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
|
|
text_view1 = Gtk.TextView()
|
|
text_view1.set_editable(False)
|
|
text_buffer1 = text_view1.get_buffer()
|
|
with open(self.log_file_path, "r") as log_file:
|
|
text_buffer1.set_text(log_file.read())
|
|
scrolled_window1.add(text_view1)
|
|
|
|
scrolled_window2 = Gtk.ScrolledWindow()
|
|
scrolled_window2.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
|
|
text_view2 = Gtk.TextView()
|
|
text_view2.set_editable(False)
|
|
text_buffer2 = text_view2.get_buffer()
|
|
with open(self.umu_log_file_path, "r") as log_file:
|
|
text_buffer2.set_text(log_file.read())
|
|
scrolled_window2.add(text_view2)
|
|
|
|
def copy_to_clipboard(button):
|
|
current_page = notebook.get_current_page()
|
|
if current_page == 0: # Tab 1: Proton
|
|
start_iter, end_iter = text_buffer1.get_bounds()
|
|
text_to_copy = text_buffer1.get_text(start_iter, end_iter, False)
|
|
elif current_page == 1: # Tab 2: UMU-Launcher
|
|
start_iter, end_iter = text_buffer2.get_bounds()
|
|
text_to_copy = text_buffer2.get_text(start_iter, end_iter, False)
|
|
else:
|
|
text_to_copy = ""
|
|
|
|
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
|
|
clipboard.set_text(text_to_copy, -1)
|
|
clipboard.store()
|
|
|
|
def open_location(button):
|
|
subprocess.run(["xdg-open", os.path.dirname(self.log_file_path)], check=True)
|
|
|
|
button_copy_clipboard = Gtk.Button(label=_("Copy to clipboard"))
|
|
button_copy_clipboard.set_size_request(150, -1)
|
|
button_copy_clipboard.connect("clicked", copy_to_clipboard)
|
|
|
|
button_open_location = Gtk.Button(label=_("Open file location"))
|
|
button_open_location.set_size_request(150, -1)
|
|
button_open_location.connect("clicked", open_location)
|
|
|
|
notebook = Gtk.Notebook()
|
|
notebook.set_margin_start(10)
|
|
notebook.set_margin_end(10)
|
|
notebook.set_margin_top(10)
|
|
notebook.set_margin_bottom(10)
|
|
notebook.set_halign(Gtk.Align.FILL)
|
|
notebook.set_valign(Gtk.Align.FILL)
|
|
notebook.set_vexpand(True)
|
|
notebook.set_hexpand(True)
|
|
|
|
tab_box1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
|
tab_label1 = Gtk.Label(label="Proton")
|
|
tab_label1.set_width_chars(15)
|
|
tab_label1.set_xalign(0.5)
|
|
tab_box1.pack_start(tab_label1, True, True, 0)
|
|
tab_box1.set_hexpand(True)
|
|
|
|
tab_box2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
|
tab_label2 = Gtk.Label(label="UMU-Launcher")
|
|
tab_label2.set_width_chars(15)
|
|
tab_label2.set_xalign(0.5)
|
|
tab_box2.pack_start(tab_label2, True, True, 0)
|
|
tab_box2.set_hexpand(True)
|
|
|
|
notebook.append_page(scrolled_window1, tab_box1)
|
|
notebook.append_page(scrolled_window2, tab_box2)
|
|
|
|
content_area = dialog.get_content_area()
|
|
box_bottom = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
|
box_bottom.set_margin_start(10)
|
|
box_bottom.set_margin_end(10)
|
|
box_bottom.set_margin_bottom(10)
|
|
box_bottom.pack_start(button_copy_clipboard, True, True, 0)
|
|
box_bottom.pack_start(button_open_location, True, True, 0)
|
|
|
|
content_area.add(notebook)
|
|
content_area.add(box_bottom)
|
|
|
|
tab_box1.show_all()
|
|
tab_box2.show_all()
|
|
dialog.show_all()
|
|
dialog.run()
|
|
dialog.destroy()
|
|
|
|
def on_duplicate_clicked(self, widget):
|
|
selected_children = self.flowbox.get_selected_children()
|
|
selected_child = selected_children[0]
|
|
game = selected_child.game
|
|
title = game.title
|
|
|
|
css_provider = Gtk.CssProvider()
|
|
css = """
|
|
.entry {
|
|
border-color: red;
|
|
}
|
|
"""
|
|
css_provider.load_from_data(css.encode("utf-8"))
|
|
Gtk.StyleContext.add_provider_for_screen(
|
|
Gdk.Screen.get_default(),
|
|
css_provider,
|
|
Gtk.STYLE_PROVIDER_PRIORITY_USER
|
|
)
|
|
|
|
duplicate_dialog = DuplicateDialog(self, title)
|
|
|
|
while True:
|
|
response = duplicate_dialog.run()
|
|
|
|
if response != Gtk.ResponseType.OK:
|
|
break
|
|
|
|
new_title = duplicate_dialog.entry_title.get_text().strip()
|
|
|
|
if not new_title:
|
|
duplicate_dialog.entry_title.get_style_context().add_class("entry")
|
|
continue
|
|
|
|
if any(new_title == g.title for g in self.games):
|
|
duplicate_dialog.show_warning_dialog(
|
|
duplicate_dialog,
|
|
_("%s already exists.") % new_title
|
|
)
|
|
continue
|
|
|
|
title_formatted_old = format_title(game.title)
|
|
title_formatted = format_title(new_title)
|
|
|
|
icon = f"{icons_dir}/{title_formatted_old}.ico"
|
|
banner = game.banner
|
|
|
|
new_icon = f"{icons_dir}/{title_formatted}.ico"
|
|
new_banner = f"{banners_dir}/{title_formatted}.png"
|
|
new_addapp_bat = f"{os.path.dirname(game.path)}/faugus-{title_formatted}.bat"
|
|
|
|
if os.path.exists(icon):
|
|
shutil.copyfile(icon, new_icon)
|
|
|
|
if os.path.exists(banner):
|
|
shutil.copyfile(banner, new_banner)
|
|
|
|
if os.path.exists(game.addapp_bat):
|
|
shutil.copyfile(game.addapp_bat, new_addapp_bat)
|
|
|
|
game.title = new_title
|
|
game.banner = new_banner
|
|
game.addapp_bat = new_addapp_bat
|
|
|
|
game_info = {
|
|
"gameid": title_formatted,
|
|
"title": game.title,
|
|
"path": game.path,
|
|
"prefix": game.prefix,
|
|
"launch_arguments": game.launch_arguments,
|
|
"game_arguments": game.game_arguments,
|
|
"mangohud": game.mangohud,
|
|
"gamemode": game.gamemode,
|
|
"disable_hidraw": game.disable_hidraw,
|
|
"protonfix": game.protonfix,
|
|
"runner": game.runner,
|
|
"addapp_checkbox": game.addapp_checkbox,
|
|
"addapp": game.addapp,
|
|
"addapp_bat": game.addapp_bat,
|
|
"banner": game.banner,
|
|
"lossless_enabled": game.lossless_enabled,
|
|
"lossless_multiplier": game.lossless_multiplier,
|
|
"lossless_flow": game.lossless_flow,
|
|
"lossless_performance": game.lossless_performance,
|
|
"lossless_hdr": game.lossless_hdr,
|
|
"playtime": game.playtime,
|
|
"hidden": game.hidden,
|
|
"prevent_sleep": game.prevent_sleep,
|
|
"favorite": game.favorite,
|
|
}
|
|
|
|
games = []
|
|
if os.path.exists("games.json"):
|
|
try:
|
|
with open("games.json", "r", encoding="utf-8") as file:
|
|
games = json.load(file)
|
|
except json.JSONDecodeError as e:
|
|
print(f"Error reading the JSON file: {e}")
|
|
|
|
games.append(game_info)
|
|
|
|
with open("games.json", "w", encoding="utf-8") as file:
|
|
json.dump(games, file, ensure_ascii=False, indent=4)
|
|
|
|
self.games.append(game)
|
|
self.add_item_list(game)
|
|
self.update_list()
|
|
self.select_game_by_title(new_title)
|
|
|
|
break
|
|
|
|
duplicate_dialog.destroy()
|
|
|
|
def on_item_release_event(self, widget, event):
|
|
if event.button == Gdk.BUTTON_PRIMARY:
|
|
current_time = event.time
|
|
current_item = self.get_item_at_event(event)
|
|
|
|
if current_item:
|
|
self.flowbox.select_child(current_item)
|
|
if current_item == self.last_clicked_item and current_time - self.last_click_time < self.double_click_time_threshold:
|
|
self.on_item_double_click(current_item)
|
|
|
|
self.last_clicked_item = current_item
|
|
self.last_click_time = current_time
|
|
|
|
def get_item_at_event(self, event):
|
|
x, y = event.x, event.y
|
|
return self.flowbox.get_child_at_pos(x, y)
|
|
|
|
def on_item_double_click(self, item):
|
|
selected_children = self.flowbox.get_selected_children()
|
|
|
|
if not selected_children:
|
|
return
|
|
|
|
selected_child = selected_children[0]
|
|
hbox = selected_child.get_child()
|
|
game_label = hbox.get_children()[1]
|
|
title = game_label.get_text()
|
|
|
|
current_focus = self.get_focus()
|
|
|
|
if IS_FLATPAK:
|
|
if title not in self.processos:
|
|
self.on_button_play_clicked(item)
|
|
else:
|
|
self.running_dialog(title)
|
|
else:
|
|
processos = self.load_processes_from_file()
|
|
if title not in processos:
|
|
self.on_button_play_clicked(selected_child)
|
|
else:
|
|
self.running_dialog(title)
|
|
|
|
def on_key_press_event(self, widget, event):
|
|
selected_children = self.flowbox.get_selected_children()
|
|
|
|
if event.keyval == Gdk.KEY_h and event.state & Gdk.ModifierType.CONTROL_MASK:
|
|
try:
|
|
with open(config_file_dir, "r", encoding="utf-8") as f:
|
|
lines = f.readlines()
|
|
|
|
new_lines = []
|
|
found = False
|
|
|
|
for line in lines:
|
|
stripped = line.strip()
|
|
|
|
if stripped.lower().startswith("show-hidden"):
|
|
sep_index = line.find("=")
|
|
if sep_index != -1:
|
|
left = line[:sep_index]
|
|
right = line[sep_index + 1:].strip()
|
|
|
|
if right.lower() == "true":
|
|
new_value = "False"
|
|
else:
|
|
new_value = "True"
|
|
|
|
new_lines.append(f"{left}={new_value}\n")
|
|
found = True
|
|
else:
|
|
new_lines.append(line)
|
|
else:
|
|
new_lines.append(line)
|
|
|
|
if not found:
|
|
new_lines.append("show-hidden=True\n")
|
|
|
|
with open(config_file_dir, "w", encoding="utf-8") as f:
|
|
f.writelines(new_lines)
|
|
|
|
self.load_config()
|
|
self.update_list()
|
|
self.button_play.set_sensitive(False)
|
|
self.select_first_child()
|
|
return True
|
|
|
|
except Exception as e:
|
|
return False
|
|
|
|
if not selected_children:
|
|
return
|
|
|
|
selected_child = selected_children[0]
|
|
hbox = selected_child.get_child()
|
|
game_label = hbox.get_children()[1]
|
|
title = game_label.get_text()
|
|
|
|
current_focus = self.get_focus()
|
|
|
|
if event.keyval in (Gdk.KEY_Up, Gdk.KEY_Down, Gdk.KEY_Left, Gdk.KEY_Right):
|
|
if current_focus not in self.flowbox.get_children():
|
|
selected_child.grab_focus()
|
|
|
|
if self.interface_mode != "List":
|
|
if event.keyval == Gdk.KEY_Return and event.state & Gdk.ModifierType.MOD1_MASK:
|
|
if self.get_window().get_state() & Gdk.WindowState.FULLSCREEN:
|
|
self.fullscreen_activated = False
|
|
self.unfullscreen()
|
|
self.grid_corner.set_visible(False)
|
|
self.grid_left.set_margin_start(0)
|
|
else:
|
|
self.fullscreen_activated = True
|
|
self.fullscreen()
|
|
self.grid_corner.set_visible(True)
|
|
self.grid_left.set_margin_start(70)
|
|
return True
|
|
|
|
if IS_FLATPAK:
|
|
if event.keyval == Gdk.KEY_Return:
|
|
if title not in self.processos:
|
|
widget = self.button_play
|
|
self.on_button_play_clicked(selected_child)
|
|
else:
|
|
self.running_dialog(title)
|
|
elif event.keyval == Gdk.KEY_Delete:
|
|
self.on_button_delete_clicked(selected_child)
|
|
else:
|
|
if event.keyval == Gdk.KEY_Return:
|
|
processos = self.load_processes_from_file()
|
|
if title not in processos:
|
|
self.on_button_play_clicked(selected_child)
|
|
else:
|
|
self.running_dialog(title)
|
|
elif event.keyval == Gdk.KEY_Delete:
|
|
self.on_button_delete_clicked(selected_child)
|
|
|
|
if event.string:
|
|
if event.string.isprintable():
|
|
self.entry_search.grab_focus()
|
|
current_text = self.entry_search.get_text()
|
|
new_text = current_text + event.string
|
|
self.entry_search.set_text(new_text)
|
|
self.entry_search.set_position(len(new_text))
|
|
elif event.keyval == Gdk.KEY_BackSpace:
|
|
self.entry_search.grab_focus()
|
|
current_text = self.entry_search.get_text()
|
|
new_text = current_text[:-1]
|
|
self.entry_search.set_text(new_text)
|
|
self.entry_search.set_position(len(new_text))
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
def running_dialog(self, title):
|
|
dialog = Gtk.Dialog(title="Faugus Launcher", parent=self, modal=True)
|
|
dialog.set_resizable(False)
|
|
dialog.set_icon_from_file(faugus_png)
|
|
subprocess.Popen(["canberra-gtk-play", "-f", faugus_notification])
|
|
|
|
label = Gtk.Label()
|
|
label.set_label(_("%s is already running.") % title)
|
|
label.set_halign(Gtk.Align.CENTER)
|
|
|
|
button_yes = Gtk.Button(label=_("Ok"))
|
|
button_yes.set_size_request(150, -1)
|
|
button_yes.connect("clicked", lambda x: dialog.response(Gtk.ResponseType.YES))
|
|
|
|
content_area = dialog.get_content_area()
|
|
content_area.set_border_width(0)
|
|
content_area.set_halign(Gtk.Align.CENTER)
|
|
content_area.set_valign(Gtk.Align.CENTER)
|
|
content_area.set_vexpand(True)
|
|
content_area.set_hexpand(True)
|
|
|
|
box_top = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
|
box_top.set_margin_start(20)
|
|
box_top.set_margin_end(20)
|
|
box_top.set_margin_top(20)
|
|
box_top.set_margin_bottom(20)
|
|
|
|
box_bottom = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
|
box_bottom.set_margin_start(10)
|
|
box_bottom.set_margin_end(10)
|
|
box_bottom.set_margin_bottom(10)
|
|
|
|
box_top.pack_start(label, True, True, 0)
|
|
box_bottom.pack_start(button_yes, True, True, 0)
|
|
|
|
content_area.add(box_top)
|
|
content_area.add(box_bottom)
|
|
|
|
dialog.show_all()
|
|
dialog.run()
|
|
dialog.destroy()
|
|
|
|
def load_config(self):
|
|
cfg = ConfigManager()
|
|
|
|
self.system_tray = cfg.config.get('system-tray', 'False') == 'True'
|
|
self.start_boot = cfg.config.get('start-boot', 'False') == 'True'
|
|
self.mono_icon = cfg.config.get('mono-icon', 'False') == 'True'
|
|
self.close_on_launch = cfg.config.get('close-onlaunch', 'False') == 'True'
|
|
self.start_maximized = cfg.config.get('start-maximized', 'False') == 'True'
|
|
self.interface_mode = cfg.config.get('interface-mode', '').strip('"')
|
|
self.start_fullscreen = cfg.config.get('start-fullscreen', 'False') == 'True'
|
|
self.show_labels = cfg.config.get('show-labels', 'False') == 'True'
|
|
self.smaller_banners = cfg.config.get('smaller-banners', 'False') == 'True'
|
|
self.enable_logging = cfg.config.get('enable-logging', 'False') == 'True'
|
|
self.wayland_driver = cfg.config.get('wayland-driver', 'False') == 'True'
|
|
self.enable_hdr = cfg.config.get('enable-hdr', 'False') == 'True'
|
|
self.enable_wow64 = cfg.config.get('enable-wow64', 'False') == 'True'
|
|
self.language = cfg.config.get('language', '')
|
|
self.show_hidden = cfg.config.get('show-hidden', 'False') == 'True'
|
|
|
|
def on_game_selected(self, widget, game_name):
|
|
# Find the game in the FlowBox by name and select it
|
|
self.flowbox.unselect_all()
|
|
for child in self.flowbox.get_children():
|
|
hbox = child.get_children()[0] # Assuming HBox structure
|
|
game_label = hbox.get_children()[1] # The label should be the second item in HBox
|
|
title = game_label.get_text()
|
|
if game_label.get_text() == game_name:
|
|
# Select this item in FlowBox
|
|
self.flowbox.select_child(child)
|
|
break
|
|
|
|
# Call the function to run the selected game
|
|
processos = self.load_processes_from_file()
|
|
if title not in processos:
|
|
self.on_button_play_clicked(widget)
|
|
else:
|
|
self.running_dialog(title)
|
|
|
|
def load_games(self):
|
|
try:
|
|
with open("games.json", "r", encoding="utf-8") as file:
|
|
games_data = json.load(file)
|
|
|
|
self.games.clear()
|
|
for game_data in games_data:
|
|
game = Game(
|
|
game_data.get("gameid", ""),
|
|
game_data.get("title", ""),
|
|
game_data.get("path", ""),
|
|
game_data.get("prefix", ""),
|
|
game_data.get("launch_arguments", ""),
|
|
game_data.get("game_arguments", ""),
|
|
game_data.get("mangohud", ""),
|
|
game_data.get("gamemode", ""),
|
|
game_data.get("disable_hidraw", ""),
|
|
game_data.get("protonfix", ""),
|
|
game_data.get("runner", ""),
|
|
game_data.get("addapp_checkbox", ""),
|
|
game_data.get("addapp", ""),
|
|
game_data.get("addapp_bat", ""),
|
|
game_data.get("banner", ""),
|
|
game_data.get("lossless_enabled", ""),
|
|
game_data.get("lossless_multiplier", ""),
|
|
game_data.get("lossless_flow", ""),
|
|
game_data.get("lossless_performance", ""),
|
|
game_data.get("lossless_hdr", ""),
|
|
game_data.get("lossless_present", ""),
|
|
game_data.get("playtime", 0),
|
|
game_data.get("hidden", False),
|
|
game_data.get("prevent_sleep", False),
|
|
game_data.get("favorite", False),
|
|
game_data.get("startup_wm_class", ""),
|
|
)
|
|
|
|
if not self.show_hidden and game.hidden:
|
|
continue
|
|
|
|
self.games.append(game)
|
|
|
|
self.games = sorted(self.games, key=lambda x: x.title.lower())
|
|
self.filtered_games = self.games[:]
|
|
|
|
self.flowbox.foreach(Gtk.Widget.destroy)
|
|
for game in self.filtered_games:
|
|
self.add_item_list(game)
|
|
|
|
except FileNotFoundError:
|
|
pass
|
|
except json.JSONDecodeError as e:
|
|
print(f"Error reading the JSON file: {e}")
|
|
|
|
def add_item_list(self, game):
|
|
if self.interface_mode == "List":
|
|
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
|
if self.interface_mode == "Blocks":
|
|
hbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
hbox.set_size_request(200, -1)
|
|
if self.interface_mode == "Banners":
|
|
hbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
|
|
|
|
if game.favorite:
|
|
hbox.get_style_context().add_class("hbox-favorite")
|
|
else:
|
|
hbox.get_style_context().add_class("hbox-normal")
|
|
|
|
game_icon = get_shortcut_icon_path(game.gameid, icons_dir, faugus_png)
|
|
game_label = Gtk.Label.new(game.title)
|
|
|
|
if self.interface_mode in ("Blocks", "Banners"):
|
|
game_label.set_line_wrap(True)
|
|
game_label.set_max_width_chars(1)
|
|
game_label.set_justify(Gtk.Justification.CENTER)
|
|
|
|
self.flowbox_child = Gtk.FlowBoxChild()
|
|
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(game_icon)
|
|
|
|
if self.interface_mode == "List":
|
|
scaled_pixbuf = pixbuf.scale_simple(40, 40, GdkPixbuf.InterpType.BILINEAR)
|
|
image = Gtk.Image.new_from_file(game_icon)
|
|
image.set_from_pixbuf(scaled_pixbuf)
|
|
|
|
image.set_margin_start(10)
|
|
image.set_margin_end(10)
|
|
image.set_margin_top(10)
|
|
image.set_margin_bottom(10)
|
|
|
|
game_label.set_margin_start(10)
|
|
game_label.set_margin_end(10)
|
|
game_label.set_margin_top(10)
|
|
game_label.set_margin_bottom(10)
|
|
|
|
hbox.pack_start(image, False, False, 0)
|
|
hbox.pack_start(game_label, False, False, 0)
|
|
|
|
self.flowbox_child.set_size_request(300, -1)
|
|
self.flowbox.set_homogeneous(True)
|
|
self.flowbox_child.set_valign(Gtk.Align.START)
|
|
self.flowbox_child.set_halign(Gtk.Align.FILL)
|
|
|
|
if self.interface_mode == "Blocks":
|
|
self.flowbox_child.set_hexpand(True)
|
|
self.flowbox_child.set_vexpand(True)
|
|
|
|
scaled_pixbuf = pixbuf.scale_simple(100, 100, GdkPixbuf.InterpType.BILINEAR)
|
|
image = Gtk.Image.new_from_file(game_icon)
|
|
image.set_from_pixbuf(scaled_pixbuf)
|
|
|
|
image.set_margin_top(10)
|
|
game_label.set_margin_top(10)
|
|
game_label.set_margin_start(10)
|
|
game_label.set_margin_end(10)
|
|
game_label.set_margin_bottom(10)
|
|
|
|
hbox.pack_start(image, False, False, 0)
|
|
hbox.pack_start(game_label, True, False, 0)
|
|
|
|
self.flowbox_child.set_valign(Gtk.Align.FILL)
|
|
self.flowbox_child.set_halign(Gtk.Align.FILL)
|
|
|
|
if self.interface_mode == "Banners":
|
|
self.flowbox_child.set_hexpand(True)
|
|
self.flowbox_child.set_vexpand(True)
|
|
|
|
image2 = Gtk.Image()
|
|
game_label.set_size_request(-1, 50)
|
|
game_label.set_margin_start(10)
|
|
game_label.set_margin_end(10)
|
|
|
|
self.flowbox_child.set_margin_start(10)
|
|
self.flowbox_child.set_margin_end(10)
|
|
self.flowbox_child.set_margin_top(10)
|
|
self.flowbox_child.set_margin_bottom(10)
|
|
|
|
self.flowbox_child.set_valign(Gtk.Align.FILL)
|
|
self.flowbox_child.set_halign(Gtk.Align.FILL)
|
|
|
|
if game.banner == "" or not os.path.isfile(game.banner):
|
|
if self.smaller_banners:
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
|
|
faugus_banner, 180, 270, False)
|
|
else:
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
|
|
faugus_banner, 230, 345, False)
|
|
else:
|
|
if self.smaller_banners:
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
|
|
game.banner, 180, 270, False)
|
|
else:
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
|
|
game.banner, 230, 345, False)
|
|
|
|
image2.set_from_pixbuf(pixbuf)
|
|
hbox.pack_start(image2, False, False, 0)
|
|
hbox.pack_start(game_label, True, False, 0)
|
|
|
|
if not self.show_labels:
|
|
game_label.set_no_show_all(True)
|
|
|
|
self.flowbox_child.add(hbox)
|
|
self.flowbox_child.game = game
|
|
self.flowbox.add(self.flowbox_child)
|
|
|
|
def on_search_changed(self, entry):
|
|
search_text = entry.get_text().lower()
|
|
first_visible = None
|
|
|
|
for child in self.flowbox.get_children():
|
|
game = child.game
|
|
is_match = search_text in game.title.lower()
|
|
|
|
child.set_visible(is_match)
|
|
if is_match and first_visible is None:
|
|
first_visible = child
|
|
|
|
if first_visible:
|
|
self.flowbox.select_child(first_visible)
|
|
self.on_item_selected(self.flowbox, first_visible)
|
|
|
|
def on_item_selected(self, flowbox, child):
|
|
if child is not None:
|
|
children = child.get_children()
|
|
hbox = children[0]
|
|
label_children = hbox.get_children()
|
|
game_label = label_children[1]
|
|
title = game_label.get_text()
|
|
|
|
self.menu_item_edit.set_sensitive(True)
|
|
self.menu_item_delete.set_sensitive(True)
|
|
|
|
if IS_FLATPAK:
|
|
if title in self.processos:
|
|
self.menu_item_play.set_sensitive(False)
|
|
self.button_play.set_sensitive(False)
|
|
self.button_play.set_image(
|
|
Gtk.Image.new_from_icon_name("faugus-stop-symbolic", Gtk.IconSize.BUTTON))
|
|
else:
|
|
self.menu_item_play.set_sensitive(True)
|
|
self.button_play.set_sensitive(True)
|
|
self.button_play.set_image(
|
|
Gtk.Image.new_from_icon_name("faugus-play-symbolic", Gtk.IconSize.BUTTON))
|
|
else:
|
|
processos = self.load_processes_from_file()
|
|
if title in self.button_locked:
|
|
self.menu_item_play.set_sensitive(False)
|
|
self.button_play.set_sensitive(False)
|
|
self.button_play.set_image(Gtk.Image.new_from_icon_name("faugus-stop-symbolic", Gtk.IconSize.BUTTON))
|
|
elif title in processos:
|
|
self.menu_item_play.set_sensitive(True)
|
|
self.button_play.set_sensitive(True)
|
|
self.button_play.set_image(Gtk.Image.new_from_icon_name("faugus-stop-symbolic", Gtk.IconSize.BUTTON))
|
|
else:
|
|
self.menu_item_play.set_sensitive(True)
|
|
self.button_play.set_sensitive(True)
|
|
self.button_play.set_image(
|
|
Gtk.Image.new_from_icon_name("faugus-play-symbolic", Gtk.IconSize.BUTTON))
|
|
|
|
else:
|
|
self.menu_item_edit.set_sensitive(False)
|
|
self.menu_item_delete.set_sensitive(False)
|
|
self.menu_item_play.set_sensitive(False)
|
|
self.button_play.set_sensitive(False)
|
|
|
|
def on_button_settings_clicked(self, widget):
|
|
# Handle add button click event
|
|
settings_dialog = Settings(self)
|
|
settings_dialog.connect("response", self.on_settings_dialog_response, settings_dialog)
|
|
|
|
settings_dialog.show()
|
|
|
|
def on_settings_dialog_response(self, dialog, response_id, settings_dialog):
|
|
if faugus_backup:
|
|
subprocess.Popen([sys.executable, __file__])
|
|
self.destroy()
|
|
self.load_config()
|
|
self.manage_autostart_file(self.start_boot)
|
|
settings_dialog.destroy()
|
|
return
|
|
|
|
# Handle dialog response
|
|
if response_id == Gtk.ResponseType.OK:
|
|
default_prefix = settings_dialog.entry_default_prefix.get_text()
|
|
validation_result = self.validate_settings_fields(settings_dialog, default_prefix)
|
|
if not validation_result:
|
|
return
|
|
|
|
if not settings_dialog.logging_warning:
|
|
if settings_dialog.checkbox_enable_logging.get_active():
|
|
self.show_warning_dialog(self, _("Proton may generate huge log files."), _("Enable logging only when debugging a problem."))
|
|
settings_dialog.logging_warning = True
|
|
|
|
settings_dialog.update_config_file()
|
|
self.manage_autostart_file(settings_dialog.checkbox_start_boot.get_active())
|
|
|
|
if settings_dialog.checkbox_system_tray.get_active():
|
|
self.system_tray = True
|
|
else:
|
|
self.system_tray = False
|
|
GLib.timeout_add(1000, self.load_tray_icon)
|
|
|
|
if validation_result:
|
|
combobox_language = settings_dialog.combobox_language.get_active_text()
|
|
if self.interface_mode != settings_dialog.combobox_interface.get_active_text():
|
|
subprocess.Popen([sys.executable, __file__])
|
|
self.destroy()
|
|
if self.show_labels != settings_dialog.checkbox_show_labels.get_active():
|
|
subprocess.Popen([sys.executable, __file__])
|
|
self.destroy()
|
|
if self.smaller_banners != settings_dialog.checkbox_smaller_banners.get_active():
|
|
subprocess.Popen([sys.executable, __file__])
|
|
self.destroy()
|
|
if self.language != settings_dialog.lang_codes.get(combobox_language, "en_US"):
|
|
subprocess.Popen([sys.executable, __file__])
|
|
self.destroy()
|
|
|
|
settings_dialog.update_envar_file()
|
|
|
|
if self.show_hidden != settings_dialog.checkbox_show_hidden.get_active():
|
|
self.load_config()
|
|
self.update_list()
|
|
self.button_play.set_sensitive(False)
|
|
|
|
self.load_config()
|
|
settings_dialog.destroy()
|
|
|
|
else:
|
|
settings_dialog.destroy()
|
|
|
|
def validate_settings_fields(self, settings_dialog, default_prefix):
|
|
settings_dialog.entry_default_prefix.get_style_context().remove_class("entry")
|
|
|
|
if settings_dialog.combobox_interface.get_active_text() == "Banners":
|
|
if not default_prefix:
|
|
if not default_prefix:
|
|
settings_dialog.entry_default_prefix.get_style_context().add_class("entry")
|
|
return False
|
|
return True
|
|
elif not default_prefix:
|
|
settings_dialog.entry_default_prefix.get_style_context().add_class("entry")
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def manage_autostart_file(self, checkbox_start_boot):
|
|
# Define the path for the autostart file
|
|
autostart_path = os.path.expanduser('~/.config/autostart/faugus-launcher.desktop')
|
|
autostart_dir = os.path.dirname(autostart_path)
|
|
|
|
# Ensure the autostart directory exists
|
|
if not os.path.exists(autostart_dir):
|
|
os.makedirs(autostart_dir)
|
|
|
|
if checkbox_start_boot:
|
|
# Create the autostart file if it does not exist
|
|
if not os.path.exists(autostart_path):
|
|
with open(autostart_path, "w") as f:
|
|
if IS_FLATPAK:
|
|
f.write(
|
|
"[Desktop Entry]\n"
|
|
"Categories=Utility;\n"
|
|
"Exec=flatpak run io.github.Faugus.faugus-launcher --hide\n"
|
|
"Icon=io.github.Faugus.faugus-launcher\n"
|
|
"MimeType=application/x-ms-dos-executable;application/x-msi;application/x-ms-shortcut;application/x-bat;text/x-ms-regedit\n"
|
|
"Name=Faugus Launcher\n"
|
|
"Type=Application\n"
|
|
)
|
|
else:
|
|
f.write(
|
|
"[Desktop Entry]\n"
|
|
"Categories=Utility;\n"
|
|
"Exec=faugus-launcher --hide\n"
|
|
"Icon=faugus-launcher\n"
|
|
"MimeType=application/x-ms-dos-executable;application/x-msi;application/x-ms-shortcut;application/x-bat;text/x-ms-regedit\n"
|
|
"Name=Faugus Launcher\n"
|
|
"Type=Application\n"
|
|
)
|
|
else:
|
|
# Delete the autostart file if it exists
|
|
if os.path.exists(autostart_path):
|
|
os.remove(autostart_path)
|
|
|
|
def on_button_play_clicked(self, widget):
|
|
selected_children = self.flowbox.get_selected_children()
|
|
if not selected_children:
|
|
return
|
|
|
|
selected_child = selected_children[0]
|
|
game = selected_child.game
|
|
title = game.title
|
|
game_directory = os.path.dirname(game.path)
|
|
cwd = game_directory if game_directory and os.path.isdir(game_directory) else None
|
|
|
|
if game.runner == "Steam":
|
|
subprocess.Popen([sys.executable, faugus_run, "--game", game.gameid],cwd=cwd)
|
|
return
|
|
|
|
processos = self.load_processes_from_file()
|
|
self.button_locked[title] = True
|
|
|
|
if title in processos:
|
|
data = processos[title]
|
|
pid_main = data.get("faugus-run")
|
|
if pid_main:
|
|
try:
|
|
os.kill(pid_main, signal.SIGUSR1)
|
|
except ProcessLookupError:
|
|
pass
|
|
|
|
return
|
|
|
|
if game:
|
|
# Save the game title to the latest_games.txt file
|
|
self.update_latest_games_file(title)
|
|
|
|
if self.close_on_launch:
|
|
if IS_FLATPAK:
|
|
subprocess.Popen([sys.executable, faugus_run, "--game", game.gameid], stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL, cwd=cwd)
|
|
sys.exit()
|
|
else:
|
|
self.processo = subprocess.Popen([sys.executable, faugus_run, "--game", game.gameid], cwd=cwd)
|
|
|
|
self.menu_item_play.set_sensitive(False)
|
|
self.button_play.set_sensitive(False)
|
|
self.button_play.set_image(Gtk.Image.new_from_icon_name("faugus-stop-symbolic", Gtk.IconSize.BUTTON))
|
|
|
|
def check_pid_timeout():
|
|
if self.find_pid(game):
|
|
sys.exit()
|
|
return True
|
|
|
|
GLib.timeout_add(1000, check_pid_timeout)
|
|
|
|
else:
|
|
self.processo = subprocess.Popen([sys.executable, faugus_run, "--game", game.gameid], cwd=cwd)
|
|
|
|
self.menu_item_play.set_sensitive(False)
|
|
self.button_play.set_sensitive(False)
|
|
self.button_play.set_image(Gtk.Image.new_from_icon_name("faugus-stop-symbolic", Gtk.IconSize.BUTTON))
|
|
|
|
GLib.child_watch_add(self.processo.pid, self.on_process_exit)
|
|
|
|
if IS_FLATPAK:
|
|
self.processos[title] = self.processo
|
|
else:
|
|
def check_pid_periodically():
|
|
if self.find_pid(game):
|
|
return False
|
|
return True
|
|
|
|
GLib.timeout_add(1000, check_pid_periodically)
|
|
|
|
def on_process_exit(self, pid, status):
|
|
if self.processo and self.processo.pid == pid:
|
|
self.processo = None
|
|
|
|
self.menu_item_play.set_sensitive(True)
|
|
self.button_play.set_sensitive(True)
|
|
self.button_play.set_image(Gtk.Image.new_from_icon_name("faugus-play-symbolic", Gtk.IconSize.BUTTON))
|
|
|
|
def find_pid(self, game):
|
|
try:
|
|
parent = psutil.Process(self.processo.pid)
|
|
except psutil.NoSuchProcess:
|
|
return False
|
|
|
|
self.save_process_to_file(
|
|
game.title,
|
|
main_pid=self.processo.pid
|
|
)
|
|
|
|
self.menu_item_play.set_sensitive(True)
|
|
self.button_play.set_sensitive(True)
|
|
self.button_play.set_image(Gtk.Image.new_from_icon_name("faugus-stop-symbolic", Gtk.IconSize.BUTTON))
|
|
|
|
if game.title in self.button_locked:
|
|
del self.button_locked[game.title]
|
|
|
|
return True
|
|
|
|
def save_process_to_file(self, title, main_pid):
|
|
os.makedirs(os.path.dirname(running_games), exist_ok=True)
|
|
|
|
try:
|
|
with open(running_games, "r") as f:
|
|
processos = json.load(f)
|
|
except (FileNotFoundError, json.JSONDecodeError):
|
|
processos = {}
|
|
|
|
processos[title] = {
|
|
"faugus-run": main_pid
|
|
}
|
|
|
|
with open(running_games, "w") as f:
|
|
json.dump(processos, f, indent=2)
|
|
|
|
def update_latest_games_file(self, title):
|
|
# Read the existing games from the file, if it exists
|
|
try:
|
|
with open(latest_games, 'r') as f:
|
|
games = f.read().splitlines()
|
|
except FileNotFoundError:
|
|
games = []
|
|
|
|
# Remove the game if it already exists in the list and add it to the top
|
|
if title in games:
|
|
games.remove(title)
|
|
games.insert(0, title)
|
|
|
|
# Keep only the 5 most recent games
|
|
games = games[:5]
|
|
|
|
# Write the updated list back to the file
|
|
with open(latest_games, 'w') as f:
|
|
f.write('\n'.join(games))
|
|
self.load_tray_icon()
|
|
|
|
def on_button_kill_clicked(self, widget):
|
|
# Handle kill button click event
|
|
subprocess.run(r"""
|
|
for pid in $(ls -l /proc/*/exe 2>/dev/null | grep -E 'wine(64)?-preloader|wineserver|winedevice.exe' | awk -F'/' '{print $3}'); do
|
|
kill -9 "$pid"
|
|
done
|
|
""", shell=True)
|
|
self.game_running = False
|
|
self.button_locked.clear()
|
|
|
|
def on_button_add_clicked(self, widget):
|
|
file_path = ""
|
|
# Handle add button click event
|
|
add_game_dialog = AddGame(self, self.game_running, file_path, self.interface_mode)
|
|
add_game_dialog.connect("response", self.on_dialog_response, add_game_dialog)
|
|
|
|
add_game_dialog.show()
|
|
|
|
def on_button_edit_clicked(self, widget):
|
|
file_path = ""
|
|
|
|
selected_children = self.flowbox.get_selected_children()
|
|
selected_child = selected_children[0]
|
|
game = selected_child.game
|
|
title = game.title
|
|
|
|
if game:
|
|
processos = self.load_processes_from_file()
|
|
if game.title in processos:
|
|
self.game_running = True
|
|
else:
|
|
self.game_running = False
|
|
edit_game_dialog = AddGame(self, self.game_running, file_path, self.interface_mode)
|
|
edit_game_dialog.connect("response", self.on_edit_dialog_response, edit_game_dialog, game)
|
|
|
|
model_steam_title = edit_game_dialog.combobox_steam_title.get_model()
|
|
for i, row in enumerate(model_steam_title):
|
|
if row[0] == title:
|
|
edit_game_dialog.combobox_steam_title.set_active(i)
|
|
break
|
|
|
|
model_runner = edit_game_dialog.combobox_runner.get_model()
|
|
index_runner = 0
|
|
game_runner = game.runner
|
|
|
|
game_runner = convert_runner(game.runner)
|
|
if game_runner == "Linux-Native":
|
|
edit_game_dialog.combobox_launcher.set_active(1)
|
|
if game_runner == "Steam":
|
|
edit_game_dialog.combobox_launcher.set_active(2)
|
|
|
|
for i, row in enumerate(model_runner):
|
|
if row[0] == game_runner:
|
|
index_runner = i
|
|
break
|
|
if not game_runner:
|
|
index_runner = 1
|
|
|
|
edit_game_dialog.combobox_runner.set_active(index_runner)
|
|
edit_game_dialog.entry_title.set_text(game.title)
|
|
edit_game_dialog.entry_path.set_text(game.path)
|
|
edit_game_dialog.entry_prefix.set_text(game.prefix)
|
|
edit_game_dialog.entry_launch_arguments.set_text(game.launch_arguments)
|
|
edit_game_dialog.entry_game_arguments.set_text(game.game_arguments)
|
|
edit_game_dialog.set_title(_("Edit %s") % game.title)
|
|
edit_game_dialog.entry_protonfix.set_text(game.protonfix)
|
|
edit_game_dialog.entry_addapp.set_text(game.addapp)
|
|
edit_game_dialog.grid_launcher.set_visible(False)
|
|
|
|
edit_game_dialog.lossless_enabled = game.lossless_enabled
|
|
edit_game_dialog.lossless_multiplier = game.lossless_multiplier
|
|
edit_game_dialog.lossless_flow = game.lossless_flow
|
|
edit_game_dialog.lossless_performance = game.lossless_performance
|
|
edit_game_dialog.lossless_hdr = game.lossless_hdr
|
|
edit_game_dialog.lossless_present = game.lossless_present
|
|
|
|
if not os.path.isfile(game.banner):
|
|
game.banner = faugus_banner
|
|
shutil.copyfile(game.banner, edit_game_dialog.banner_path_temp)
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(game.banner, 260, 390, True)
|
|
edit_game_dialog.image_banner.set_from_pixbuf(pixbuf)
|
|
edit_game_dialog.image_banner2.set_from_pixbuf(pixbuf)
|
|
|
|
mangohud_enabled = os.path.exists(mangohud_dir)
|
|
if mangohud_enabled:
|
|
if game.mangohud == True:
|
|
edit_game_dialog.checkbox_mangohud.set_active(True)
|
|
else:
|
|
edit_game_dialog.checkbox_mangohud.set_active(False)
|
|
|
|
gamemode_enabled = os.path.exists(gamemoderun) or os.path.exists("/usr/games/gamemoderun")
|
|
if gamemode_enabled:
|
|
if game.gamemode == True:
|
|
edit_game_dialog.checkbox_gamemode.set_active(True)
|
|
else:
|
|
edit_game_dialog.checkbox_gamemode.set_active(False)
|
|
|
|
if game.disable_hidraw == True:
|
|
edit_game_dialog.checkbox_disable_hidraw.set_active(True)
|
|
else:
|
|
edit_game_dialog.checkbox_disable_hidraw.set_active(False)
|
|
|
|
if game.prevent_sleep == True:
|
|
edit_game_dialog.checkbox_prevent_sleep.set_active(True)
|
|
else:
|
|
edit_game_dialog.checkbox_prevent_sleep.set_active(False)
|
|
|
|
if game.addapp_checkbox == "addapp_enabled":
|
|
edit_game_dialog.checkbox_addapp.set_active(True)
|
|
else:
|
|
edit_game_dialog.checkbox_addapp.set_active(False)
|
|
|
|
self.updated_steam_id = detect_steam_id()
|
|
if self.updated_steam_id is not None:
|
|
if self.check_steam_shortcut(title):
|
|
edit_game_dialog.checkbox_shortcut_steam.set_active(True)
|
|
else:
|
|
edit_game_dialog.checkbox_shortcut_steam.set_active(False)
|
|
else:
|
|
edit_game_dialog.checkbox_shortcut_steam.set_active(False)
|
|
edit_game_dialog.checkbox_shortcut_steam.set_sensitive(False)
|
|
edit_game_dialog.checkbox_shortcut_steam.set_tooltip_text(
|
|
_("Add or remove a shortcut from Steam. Steam needs to be restarted. NO STEAM USERS FOUND."))
|
|
|
|
edit_game_dialog.check_existing_shortcut()
|
|
|
|
image = self.set_image_shortcut_icon(game.title, edit_game_dialog.icons_path, edit_game_dialog.icon_temp)
|
|
edit_game_dialog.button_shortcut_icon.set_image(image)
|
|
edit_game_dialog.entry_title.set_sensitive(False)
|
|
edit_game_dialog.combobox_steam_title.set_sensitive(False)
|
|
|
|
if self.game_running:
|
|
edit_game_dialog.button_winecfg.set_sensitive(False)
|
|
edit_game_dialog.button_winecfg.set_tooltip_text(_("%s is running. Please close it first.") % game.title)
|
|
edit_game_dialog.button_winetricks.set_sensitive(False)
|
|
edit_game_dialog.button_winetricks.set_tooltip_text(_("%s is running. Please close it first.") % game.title)
|
|
edit_game_dialog.button_run.set_sensitive(False)
|
|
edit_game_dialog.button_run.set_tooltip_text(_("%s is running. Please close it first.") % game.title)
|
|
|
|
edit_game_dialog.show()
|
|
|
|
def check_steam_shortcut(self, title):
|
|
if os.path.exists(steam_shortcuts_path):
|
|
try:
|
|
with open(steam_shortcuts_path, 'rb') as f:
|
|
shortcuts = vdf.binary_load(f)
|
|
for game in shortcuts["shortcuts"].values():
|
|
if isinstance(game, dict) and "AppName" in game and game["AppName"] == title:
|
|
return True
|
|
return False
|
|
except SyntaxError:
|
|
return False
|
|
return False
|
|
|
|
def set_image_shortcut_icon(self, title, icons_path, icon_temp):
|
|
title_formatted = format_title(title)
|
|
|
|
icon_path = get_shortcut_icon_path(title_formatted, icons_path, faugus_png)
|
|
|
|
if icon_path != faugus_png:
|
|
shutil.copyfile(icon_path, icon_temp)
|
|
else:
|
|
icon_temp = faugus_png
|
|
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(icon_temp)
|
|
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
|
|
|
image = Gtk.Image.new_from_file(icon_temp)
|
|
image.set_from_pixbuf(scaled_pixbuf)
|
|
|
|
return image
|
|
|
|
def on_button_delete_clicked(self, widget):
|
|
self.reload_playtimes()
|
|
selected_children = self.flowbox.get_selected_children()
|
|
selected_child = selected_children[0]
|
|
game = selected_child.game
|
|
title = game.title
|
|
|
|
if game:
|
|
# Display confirmation dialog
|
|
confirmation_dialog = ConfirmationDialog(self, title, game.prefix, game.runner)
|
|
response = confirmation_dialog.run()
|
|
|
|
if response == Gtk.ResponseType.YES:
|
|
processos = self.load_processes_from_file()
|
|
if title in processos:
|
|
data = processos[title]
|
|
pid = data.get("main")
|
|
if pid:
|
|
parent = psutil.Process(pid)
|
|
children = parent.children(recursive=True)
|
|
|
|
for child in children:
|
|
child.terminate()
|
|
|
|
parent.terminate()
|
|
|
|
# Remove game and associated files if required
|
|
if confirmation_dialog.get_remove_prefix_state():
|
|
prefix_path = os.path.expanduser(game.prefix)
|
|
|
|
try:
|
|
shutil.rmtree(prefix_path)
|
|
except PermissionError as e:
|
|
try:
|
|
os.system(f'chmod -R u+rwX "{prefix_path}"')
|
|
shutil.rmtree(prefix_path)
|
|
except Exception as e2:
|
|
self.show_warning_dialog(
|
|
self,
|
|
_("Failed to remove prefix."),
|
|
str(e2)
|
|
)
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
# Remove the shortcut
|
|
self.remove_shortcut(game, "both")
|
|
self.remove_steam_shortcut(title)
|
|
self.remove_banner_icon(game)
|
|
|
|
self._deleted_gameid = game.gameid
|
|
self.save_games()
|
|
self.update_list()
|
|
|
|
# Remove the game from the latest-games file if it exists
|
|
self.remove_game_from_latest_games(title)
|
|
self.button_play.set_sensitive(False)
|
|
self.select_first_child()
|
|
|
|
confirmation_dialog.destroy()
|
|
|
|
def reload_playtimes(self):
|
|
import json
|
|
try:
|
|
with open("games.json", "r", encoding="utf-8") as f:
|
|
games_data = json.load(f)
|
|
except FileNotFoundError:
|
|
return
|
|
|
|
playtime_map = {g["gameid"]: g.get("playtime", 0) for g in games_data}
|
|
|
|
for game in self.games:
|
|
if game.gameid in playtime_map:
|
|
game.playtime = playtime_map[game.gameid]
|
|
|
|
def remove_steam_shortcut(self, title):
|
|
if os.path.exists(steam_shortcuts_path):
|
|
try:
|
|
with open(steam_shortcuts_path, 'rb') as f:
|
|
shortcuts = vdf.binary_load(f)
|
|
|
|
to_remove = [app_id for app_id, game in shortcuts["shortcuts"].items() if
|
|
isinstance(game, dict) and "AppName" in game and game["AppName"] == title]
|
|
for app_id in to_remove:
|
|
del shortcuts["shortcuts"][app_id]
|
|
|
|
with open(steam_shortcuts_path, 'wb') as f:
|
|
vdf.binary_dump(shortcuts, f)
|
|
except SyntaxError:
|
|
pass
|
|
|
|
def remove_game_from_latest_games(self, title):
|
|
try:
|
|
# Read the current list of recent games
|
|
with open(latest_games, 'r') as f:
|
|
recent_games = f.read().splitlines()
|
|
|
|
# Remove the game title if it exists in the list
|
|
if title in recent_games:
|
|
recent_games.remove(title)
|
|
|
|
# Write the updated list back, maintaining max 5 entries
|
|
with open(latest_games, 'w') as f:
|
|
f.write("\n".join(recent_games[:5]))
|
|
self.load_tray_icon()
|
|
|
|
except FileNotFoundError:
|
|
pass # Ignore if the file doesn't exist yet
|
|
|
|
def show_warning_dialog(self, parent, text1, text2):
|
|
dialog = Gtk.Dialog(title="Faugus Launcher", transient_for=parent, modal=True)
|
|
dialog.set_resizable(False)
|
|
dialog.set_icon_from_file(faugus_png)
|
|
subprocess.Popen(["canberra-gtk-play", "-f", faugus_notification])
|
|
|
|
label1 = Gtk.Label()
|
|
label1.set_label(text1)
|
|
label1.set_halign(Gtk.Align.CENTER)
|
|
|
|
label2 = Gtk.Label()
|
|
label2.set_label(text2)
|
|
label2.set_halign(Gtk.Align.CENTER)
|
|
|
|
button_yes = Gtk.Button(label=_("Ok"))
|
|
button_yes.set_size_request(150, -1)
|
|
button_yes.connect("clicked", lambda x: dialog.response(Gtk.ResponseType.YES))
|
|
|
|
content_area = dialog.get_content_area()
|
|
content_area.set_border_width(0)
|
|
content_area.set_halign(Gtk.Align.CENTER)
|
|
content_area.set_valign(Gtk.Align.CENTER)
|
|
content_area.set_vexpand(True)
|
|
content_area.set_hexpand(True)
|
|
|
|
box_top = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
|
box_top.set_margin_start(20)
|
|
box_top.set_margin_end(20)
|
|
box_top.set_margin_top(20)
|
|
box_top.set_margin_bottom(20)
|
|
|
|
box_bottom = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
|
box_bottom.set_margin_start(10)
|
|
box_bottom.set_margin_end(10)
|
|
box_bottom.set_margin_bottom(10)
|
|
|
|
box_top.pack_start(label1, True, True, 0)
|
|
if text2:
|
|
box_top.pack_start(label2, True, True, 0)
|
|
box_bottom.pack_start(button_yes, True, True, 0)
|
|
|
|
content_area.add(box_top)
|
|
content_area.add(box_bottom)
|
|
|
|
dialog.show_all()
|
|
dialog.run()
|
|
dialog.destroy()
|
|
|
|
def on_dialog_response(self, dialog, response_id, add_game_dialog):
|
|
# Handle dialog response
|
|
if response_id == Gtk.ResponseType.OK:
|
|
if not add_game_dialog.validate_fields(entry="path+prefix"):
|
|
# If fields are not validated, return and keep the dialog open
|
|
return True
|
|
|
|
# Proceed with adding the game
|
|
# Get game information from dialog fields
|
|
prefix = os.path.normpath(add_game_dialog.entry_prefix.get_text())
|
|
if add_game_dialog.combobox_launcher.get_active() == 0 or add_game_dialog.combobox_launcher.get_active() == 1 or add_game_dialog.combobox_launcher.get_active() == 2:
|
|
title = add_game_dialog.entry_title.get_text()
|
|
else:
|
|
title = add_game_dialog.combobox_launcher.get_active_text()
|
|
|
|
if os.path.exists(games_json):
|
|
with open(games_json, encoding="utf-8") as f:
|
|
games = json.load(f)
|
|
|
|
if any(game.get("title") == title for game in games):
|
|
self.show_warning_dialog(
|
|
add_game_dialog,
|
|
_("%s already exists.") % title,
|
|
""
|
|
)
|
|
return True
|
|
|
|
path = add_game_dialog.entry_path.get_text()
|
|
launch_arguments = add_game_dialog.entry_launch_arguments.get_text()
|
|
game_arguments = add_game_dialog.entry_game_arguments.get_text()
|
|
protonfix = add_game_dialog.entry_protonfix.get_text()
|
|
runner = add_game_dialog.combobox_runner.get_active_text()
|
|
addapp = add_game_dialog.entry_addapp.get_text()
|
|
lossless_enabled = add_game_dialog.lossless_enabled
|
|
lossless_multiplier = add_game_dialog.lossless_multiplier
|
|
lossless_flow = add_game_dialog.lossless_flow
|
|
lossless_performance = add_game_dialog.lossless_performance
|
|
lossless_hdr = add_game_dialog.lossless_hdr
|
|
lossless_present = add_game_dialog.lossless_present
|
|
playtime = 0
|
|
hidden = False
|
|
favorite = False
|
|
startup_wm_class = ""
|
|
|
|
if add_game_dialog.combobox_launcher.get_active() == 3:
|
|
path = f"{prefix}/drive_c/Program Files (x86)/Battle.net/Battle.net.exe"
|
|
startup_wm_class = "battle.net.exe"
|
|
if add_game_dialog.combobox_launcher.get_active() == 4:
|
|
path = f"{prefix}/drive_c/Program Files/Electronic Arts/EA Desktop/EA Desktop/EALauncher.exe"
|
|
if add_game_dialog.combobox_launcher.get_active() == 5:
|
|
path = f"{prefix}/drive_c/Program Files/Epic Games/Launcher/Portal/Binaries/Win64/EpicGamesLauncher.exe"
|
|
if add_game_dialog.combobox_launcher.get_active() == 6:
|
|
path = f"{prefix}/drive_c/Program Files (x86)/Ubisoft/Ubisoft Game Launcher/UbisoftConnect.exe"
|
|
if add_game_dialog.combobox_launcher.get_active() == 7:
|
|
path = f"{prefix}/drive_c/Program Files/Rockstar Games/Launcher/Launcher.exe"
|
|
|
|
title_formatted = format_title(title)
|
|
|
|
addapp_bat = f"{os.path.dirname(path)}/faugus-{title_formatted}.bat"
|
|
|
|
if self.interface_mode == "Banners":
|
|
banner = os.path.join(banners_dir, f"{title_formatted}.png")
|
|
temp_banner_path = add_game_dialog.banner_path_temp
|
|
try:
|
|
# Use `magick` to resize the image
|
|
command_magick = shutil.which("magick") or shutil.which("convert")
|
|
subprocess.run([command_magick, temp_banner_path, "-resize", "230x345!", banner], check=True)
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"Error resizing banner: {e}")
|
|
else:
|
|
banner = ""
|
|
|
|
runner = convert_runner(runner)
|
|
if add_game_dialog.combobox_launcher.get_active() == 1:
|
|
runner = "Linux-Native"
|
|
if add_game_dialog.combobox_launcher.get_active() == 2:
|
|
runner = "Steam"
|
|
|
|
# Determine mangohud and gamemode status
|
|
mangohud = True if add_game_dialog.checkbox_mangohud.get_active() else ""
|
|
gamemode = True if add_game_dialog.checkbox_gamemode.get_active() else ""
|
|
disable_hidraw = True if add_game_dialog.checkbox_disable_hidraw.get_active() else ""
|
|
addapp_checkbox = "addapp_enabled" if add_game_dialog.checkbox_addapp.get_active() else ""
|
|
prevent_sleep = True if add_game_dialog.checkbox_prevent_sleep.get_active() else ""
|
|
|
|
# Create Game object and update UI
|
|
game = Game(
|
|
title_formatted,
|
|
title,
|
|
path,
|
|
prefix,
|
|
launch_arguments,
|
|
game_arguments,
|
|
mangohud,
|
|
gamemode,
|
|
disable_hidraw,
|
|
protonfix,
|
|
runner,
|
|
addapp_checkbox,
|
|
addapp,
|
|
addapp_bat,
|
|
banner,
|
|
lossless_enabled,
|
|
lossless_multiplier,
|
|
lossless_flow,
|
|
lossless_performance,
|
|
lossless_hdr,
|
|
lossless_present,
|
|
playtime,
|
|
hidden,
|
|
prevent_sleep,
|
|
favorite,
|
|
startup_wm_class,
|
|
)
|
|
|
|
# Determine the state of the shortcut checkbox
|
|
desktop_shortcut_state = add_game_dialog.checkbox_shortcut_desktop.get_active()
|
|
appmenu_shortcut_state = add_game_dialog.checkbox_shortcut_appmenu.get_active()
|
|
steam_shortcut_state = add_game_dialog.checkbox_shortcut_steam.get_active()
|
|
|
|
icon_temp = os.path.expanduser(add_game_dialog.icon_temp)
|
|
icon_final = f'{add_game_dialog.icons_path}/{title_formatted}.png'
|
|
|
|
def check_internet_connection():
|
|
try:
|
|
socket.gethostbyname("github.com")
|
|
return True
|
|
except socket.gaierror:
|
|
return False
|
|
|
|
if add_game_dialog.combobox_launcher.get_active() != 0 and add_game_dialog.combobox_launcher.get_active() != 1:
|
|
if not check_internet_connection():
|
|
self.show_warning_dialog(add_game_dialog, _("No internet connection."), "")
|
|
return True
|
|
else:
|
|
if add_game_dialog.combobox_launcher.get_active() == 3:
|
|
add_game_dialog.destroy()
|
|
self.launcher_screen(title, "3", title_formatted, runner, prefix, umu_run, game, desktop_shortcut_state, appmenu_shortcut_state, steam_shortcut_state, icon_temp, icon_final)
|
|
|
|
if add_game_dialog.combobox_launcher.get_active() == 4:
|
|
add_game_dialog.destroy()
|
|
self.launcher_screen(title, "4", title_formatted, runner, prefix, umu_run, game, desktop_shortcut_state, appmenu_shortcut_state, steam_shortcut_state, icon_temp, icon_final)
|
|
|
|
if add_game_dialog.combobox_launcher.get_active() == 5:
|
|
add_game_dialog.destroy()
|
|
self.launcher_screen(title, "5", title_formatted, runner, prefix, umu_run, game, desktop_shortcut_state, appmenu_shortcut_state, steam_shortcut_state, icon_temp, icon_final)
|
|
|
|
if add_game_dialog.combobox_launcher.get_active() == 6:
|
|
add_game_dialog.destroy()
|
|
self.launcher_screen(title, "6", title_formatted, runner, prefix, umu_run, game, desktop_shortcut_state, appmenu_shortcut_state, steam_shortcut_state, icon_temp, icon_final)
|
|
|
|
if add_game_dialog.combobox_launcher.get_active() == 7:
|
|
add_game_dialog.destroy()
|
|
self.launcher_screen(title, "7", title_formatted, runner, prefix, umu_run, game, desktop_shortcut_state, appmenu_shortcut_state, steam_shortcut_state, icon_temp, icon_final)
|
|
|
|
game_info = {
|
|
"gameid": title_formatted,
|
|
"title": title,
|
|
"path": path,
|
|
"prefix": prefix,
|
|
"launch_arguments": launch_arguments,
|
|
"game_arguments": game_arguments,
|
|
"mangohud": mangohud,
|
|
"gamemode": gamemode,
|
|
"disable_hidraw": disable_hidraw,
|
|
"protonfix": protonfix,
|
|
"runner": runner,
|
|
"addapp_checkbox": addapp_checkbox,
|
|
"addapp": addapp,
|
|
"addapp_bat": addapp_bat,
|
|
"banner": banner,
|
|
"lossless_enabled": lossless_enabled,
|
|
"lossless_multiplier": lossless_multiplier,
|
|
"lossless_flow": lossless_flow,
|
|
"lossless_performance": lossless_performance,
|
|
"lossless_hdr": lossless_hdr,
|
|
"lossless_present": lossless_present,
|
|
"playtime": playtime,
|
|
"hidden": hidden,
|
|
"prevent_sleep": prevent_sleep,
|
|
"favorite": favorite,
|
|
"startup_wm_class": startup_wm_class,
|
|
}
|
|
|
|
games = []
|
|
if os.path.exists("games.json"):
|
|
try:
|
|
with open("games.json", "r", encoding="utf-8") as file:
|
|
games = json.load(file)
|
|
except json.JSONDecodeError as e:
|
|
print(f"Error reading the JSON file: {e}")
|
|
|
|
games.append(game_info)
|
|
|
|
self.backup_games()
|
|
|
|
with open("games.json", "w", encoding="utf-8") as file:
|
|
json.dump(games, file, ensure_ascii=False, indent=4)
|
|
|
|
self.games.append(game)
|
|
|
|
if add_game_dialog.combobox_launcher.get_active() == 0 or add_game_dialog.combobox_launcher.get_active() == 1 or add_game_dialog.combobox_launcher.get_active() == 2:
|
|
# Call add_remove_shortcut method
|
|
self.add_shortcut(game, desktop_shortcut_state, "desktop", icon_temp, icon_final)
|
|
self.add_shortcut(game, appmenu_shortcut_state, "appmenu", icon_temp, icon_final)
|
|
self.add_steam_shortcut(game, steam_shortcut_state, icon_temp, icon_final)
|
|
|
|
if addapp_checkbox == "addapp_enabled":
|
|
with open(addapp_bat, "w") as bat_file:
|
|
bat_file.write(f'start "" "z:{addapp}"\n')
|
|
if game_arguments:
|
|
bat_file.write(f'start "" "z:{path}" {game_arguments}\n')
|
|
else:
|
|
bat_file.write(f'start "" "z:{path}"\n')
|
|
|
|
self.add_item_list(game)
|
|
self.update_list()
|
|
|
|
# Select the added game
|
|
self.select_game_by_title(title)
|
|
|
|
else:
|
|
if os.path.isfile(add_game_dialog.icon_temp):
|
|
os.remove(add_game_dialog.icon_temp)
|
|
if os.path.isdir(add_game_dialog.icon_directory):
|
|
shutil.rmtree(add_game_dialog.icon_directory)
|
|
add_game_dialog.destroy()
|
|
if os.path.isfile(add_game_dialog.banner_path_temp):
|
|
os.remove(add_game_dialog.banner_path_temp)
|
|
# Ensure the dialog is destroyed when canceled
|
|
add_game_dialog.destroy()
|
|
|
|
def launcher_screen(self, title, launcher, title_formatted, runner, prefix, umu_run, game, desktop_shortcut_state, appmenu_shortcut_state, steam_shortcut_state, icon_temp, icon_final):
|
|
self.box_launcher = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
self.box_launcher.set_hexpand(True)
|
|
self.box_launcher.set_vexpand(True)
|
|
|
|
self.bar_download = Gtk.ProgressBar()
|
|
self.bar_download.set_margin_start(20)
|
|
self.bar_download.set_margin_end(20)
|
|
self.bar_download.set_margin_bottom(40)
|
|
|
|
grid_launcher = Gtk.Grid()
|
|
grid_launcher.set_halign(Gtk.Align.CENTER)
|
|
grid_launcher.set_valign(Gtk.Align.CENTER)
|
|
|
|
grid_labels = Gtk.Grid()
|
|
grid_labels.set_size_request(-1, 128)
|
|
|
|
self.box_launcher.pack_start(grid_launcher, True, True, 0)
|
|
|
|
self.label_download = Gtk.Label()
|
|
self.label_download.set_margin_start(20)
|
|
self.label_download.set_margin_end(20)
|
|
self.label_download.set_margin_bottom(20)
|
|
self.label_download.set_text(_("Installing %s...") % title)
|
|
self.label_download.set_size_request(256, -1)
|
|
|
|
self.label_download2 = Gtk.Label()
|
|
self.label_download2.set_margin_start(20)
|
|
self.label_download2.set_margin_end(20)
|
|
self.label_download2.set_margin_bottom(20)
|
|
self.label_download2.set_text("")
|
|
self.label_download2.set_visible(False)
|
|
self.label_download2.set_size_request(256, -1)
|
|
|
|
self.button_finish_install = Gtk.Button(label=_("Finish installation"))
|
|
self.button_finish_install.connect("clicked", self.on_button_finish_install_clicked)
|
|
self.button_finish_install.set_size_request(150, -1)
|
|
self.button_finish_install.set_halign(Gtk.Align.CENTER)
|
|
|
|
if launcher == "3":
|
|
image_path = battle_icon
|
|
self.label_download.set_text(_("Downloading Battle.net..."))
|
|
self.download_launcher("battle", title, title_formatted, runner, prefix, umu_run, game, desktop_shortcut_state, appmenu_shortcut_state, steam_shortcut_state, icon_temp, icon_final)
|
|
|
|
elif launcher == "4":
|
|
image_path = ea_icon
|
|
self.label_download.set_text(_("Downloading EA App..."))
|
|
self.download_launcher("ea", title, title_formatted, runner, prefix, umu_run, game, desktop_shortcut_state, appmenu_shortcut_state, steam_shortcut_state, icon_temp, icon_final)
|
|
|
|
elif launcher == "5":
|
|
image_path = epic_icon
|
|
self.label_download.set_text(_("Downloading Epic Games..."))
|
|
self.download_launcher("epic", title, title_formatted, runner, prefix, umu_run, game, desktop_shortcut_state, appmenu_shortcut_state, steam_shortcut_state, icon_temp, icon_final)
|
|
|
|
elif launcher == "6":
|
|
image_path = ubisoft_icon
|
|
self.label_download.set_text(_("Downloading Ubisoft Connect..."))
|
|
self.download_launcher("ubisoft", title, title_formatted, runner, prefix, umu_run, game, desktop_shortcut_state, appmenu_shortcut_state, steam_shortcut_state, icon_temp, icon_final)
|
|
|
|
elif launcher == "7":
|
|
image_path = rockstar_icon
|
|
self.label_download.set_text(_("Downloading Rockstar Launcher..."))
|
|
self.download_launcher("rockstar", title, title_formatted, runner, prefix, umu_run, game, desktop_shortcut_state, appmenu_shortcut_state, steam_shortcut_state, icon_temp, icon_final)
|
|
|
|
else:
|
|
image_path = faugus_png
|
|
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path)
|
|
pixbuf = pixbuf.scale_simple(128, 128, GdkPixbuf.InterpType.BILINEAR)
|
|
|
|
image = Gtk.Image.new_from_pixbuf(pixbuf)
|
|
image.set_margin_top(20)
|
|
image.set_margin_start(20)
|
|
image.set_margin_end(20)
|
|
image.set_margin_bottom(20)
|
|
|
|
grid_launcher.attach(image, 0, 0, 1, 1)
|
|
grid_launcher.attach(grid_labels, 0, 1, 1, 1)
|
|
|
|
grid_labels.attach(self.label_download, 0, 0, 1, 1)
|
|
grid_labels.attach(self.bar_download, 0, 1, 1, 1)
|
|
grid_labels.attach(self.label_download2, 0, 2, 1, 1)
|
|
#grid_labels.attach(self.button_finish_install, 0, 3, 1, 1)
|
|
|
|
self.box_main.add(self.box_launcher)
|
|
self.box_main.remove(self.box_top)
|
|
self.box_main.remove(self.box_bottom)
|
|
self.box_main.show_all()
|
|
#self.button_finish_install.set_visible(False)
|
|
|
|
def on_button_finish_install_clicked(self):
|
|
self.on_button_kill_clicked(widget)
|
|
|
|
def monitor_process(self, processo, game, desktop_shortcut_state, appmenu_shortcut_state, steam_shortcut_state, icon_temp, icon_final, title):
|
|
retcode = processo.poll()
|
|
|
|
if retcode is not None:
|
|
if os.path.exists(faugus_temp):
|
|
shutil.rmtree(faugus_temp)
|
|
self.box_main.pack_start(self.box_top, True, True, 0)
|
|
self.box_main.pack_end(self.box_bottom, False, True, 0)
|
|
self.box_main.remove(self.box_launcher)
|
|
self.box_launcher.destroy()
|
|
self.box_main.show_all()
|
|
|
|
if os.path.exists(game.path):
|
|
print(f"{title} installed.")
|
|
self.add_shortcut(game, desktop_shortcut_state, "desktop", icon_temp, icon_final)
|
|
self.add_shortcut(game, appmenu_shortcut_state, "appmenu", icon_temp, icon_final)
|
|
self.add_steam_shortcut(game, steam_shortcut_state, icon_temp, icon_final)
|
|
self.add_item_list(game)
|
|
self.update_list()
|
|
self.select_game_by_title(title)
|
|
else:
|
|
if os.path.exists(game.prefix):
|
|
shutil.rmtree(game.prefix)
|
|
self.remove_shortcut(game, "both")
|
|
self.remove_steam_shortcut(title)
|
|
self.remove_banner_icon(game)
|
|
self.games.remove(game)
|
|
self.save_games()
|
|
self.update_list()
|
|
self.remove_game_from_latest_games(title)
|
|
self.show_warning_dialog(self, _("%s was not installed!") % title, "")
|
|
|
|
if self.interface_mode != "List":
|
|
if self.fullscreen_activated:
|
|
self.fullscreen_activated = True
|
|
self.grid_corner.set_visible(True)
|
|
self.grid_left.set_margin_start(70)
|
|
else:
|
|
self.fullscreen_activated = False
|
|
self.grid_corner.set_visible(False)
|
|
self.grid_left.set_margin_start(0)
|
|
return False
|
|
|
|
return True
|
|
|
|
def download_launcher(self, launcher, title, title_formatted, runner, prefix, umu_run, game, desktop_shortcut_state, appmenu_shortcut_state, steam_shortcut_state, icon_temp, icon_final):
|
|
urls = {"ea": "https://origin-a.akamaihd.net/EA-Desktop-Client-Download/installer-releases/EAappInstaller.exe",
|
|
"epic": "https://github.com/Faugus/components/releases/download/v1.0.0/epic.msi",
|
|
"battle": "https://downloader.battle.net/download/getInstaller?os=win&installer=Battle.net-Setup.exe",
|
|
"ubisoft": "https://static3.cdn.ubi.com/orbit/launcher_installer/UbisoftConnectInstaller.exe",
|
|
"rockstar": "https://gamedownloads.rockstargames.com/public/installer/Rockstar-Games-Launcher.exe"}
|
|
|
|
file_name = {"ea": "EAappInstaller.exe", "epic": "EpicGamesLauncherInstaller.msi",
|
|
"battle": "Battle.net-Setup.exe", "ubisoft": "UbisoftConnectInstaller.exe", "rockstar": "Rockstar-Games-Launcher.exe"}
|
|
|
|
if launcher not in urls:
|
|
return None
|
|
|
|
os.makedirs(faugus_temp, exist_ok=True)
|
|
file_path = os.path.join(faugus_temp, file_name[launcher])
|
|
|
|
def report_progress(block_num, block_size, total_size):
|
|
if total_size > 0:
|
|
downloaded = block_num * block_size
|
|
percent = min(downloaded / total_size, 1.0)
|
|
GLib.idle_add(self.bar_download.set_fraction, percent)
|
|
GLib.idle_add(self.bar_download.set_text, f"{int(percent * 100)}%")
|
|
|
|
def start_download():
|
|
try:
|
|
urllib.request.urlretrieve(urls[launcher], file_path, reporthook=report_progress)
|
|
GLib.idle_add(self.bar_download.set_fraction, 1.0)
|
|
GLib.idle_add(self.bar_download.set_text, _("Download complete"))
|
|
GLib.idle_add(on_download_complete)
|
|
except Exception as e:
|
|
GLib.idle_add(self.show_warning_dialog, self, _("Error during download: %s") % e, "")
|
|
|
|
def on_download_complete():
|
|
self.label_download.set_text(_("Installing %s...") % title)
|
|
if launcher == "battle":
|
|
self.label_download2.set_text(_("Please close the login window and wait..."))
|
|
command = f"PROTON_ENABLE_WAYLAND=0 WINE_SIMULATE_WRITECOPY=1 WINEPREFIX='{prefix}' GAMEID={title_formatted} {umu_run} '{file_path}' --installpath='C:\\Program Files (x86)\\Battle.net' --lang=enUS"
|
|
elif launcher == "ea":
|
|
self.label_download2.set_text(_("Please close the login window and wait..."))
|
|
command = f"PROTON_ENABLE_WAYLAND=0 WINEPREFIX='{prefix}' GAMEID={title_formatted} {umu_run} '{file_path}' /S"
|
|
elif launcher == "epic":
|
|
self.label_download2.set_text("")
|
|
command = f"WINEPREFIX='{prefix}' GAMEID={title_formatted} {umu_run} msiexec /i '{file_path}' /passive"
|
|
elif launcher == "ubisoft":
|
|
self.label_download2.set_text("")
|
|
command = f"PROTON_ENABLE_WAYLAND=0 WINEPREFIX='{prefix}' GAMEID={title_formatted} {umu_run} '{file_path}' /S"
|
|
elif launcher == "rockstar":
|
|
self.label_download.set_text(_("Please don't change the installation path."))
|
|
self.label_download2.set_text(_("Please close the login window and wait..."))
|
|
command = f"PROTON_ENABLE_WAYLAND=0 WINEPREFIX='{prefix}' GAMEID={title_formatted} {umu_run} '{file_path}'"
|
|
|
|
if runner:
|
|
if runner == "Proton-CachyOS":
|
|
command = f"PROTONPATH='{proton_cachyos}' {command}"
|
|
else:
|
|
command = f"PROTONPATH='{runner}' {command}"
|
|
|
|
self.bar_download.set_visible(False)
|
|
self.label_download2.set_visible(True)
|
|
processo = subprocess.Popen([sys.executable, faugus_run, command])
|
|
GLib.timeout_add(100, self.monitor_process, processo, game, desktop_shortcut_state, appmenu_shortcut_state, steam_shortcut_state, icon_temp, icon_final, title)
|
|
|
|
threading.Thread(target=start_download).start()
|
|
|
|
return file_path
|
|
|
|
def select_game_by_title(self, title):
|
|
# Selects an item from the FlowBox based on the title
|
|
for child in self.flowbox.get_children():
|
|
hbox = child.get_children()[0] # The first item is the hbox containing the label
|
|
game_label = hbox.get_children()[1] # The second item is the title label
|
|
if game_label.get_text() == title:
|
|
# Selects the child in the FlowBox
|
|
self.flowbox.select_child(child)
|
|
break
|
|
|
|
# Calls the item selection method to ensure the buttons are updated
|
|
self.on_item_selected(self.flowbox, child)
|
|
|
|
def on_edit_dialog_response(self, dialog, response_id, edit_game_dialog, game):
|
|
# Handle edit dialog response
|
|
if response_id == Gtk.ResponseType.OK:
|
|
if not edit_game_dialog.validate_fields(entry="path+prefix"):
|
|
# If fields are not validated, return and keep the dialog open
|
|
return True
|
|
# Update game object with new information
|
|
game.title = edit_game_dialog.entry_title.get_text()
|
|
game.path = edit_game_dialog.entry_path.get_text()
|
|
game.prefix = os.path.normpath(edit_game_dialog.entry_prefix.get_text())
|
|
game.launch_arguments = edit_game_dialog.entry_launch_arguments.get_text()
|
|
game.game_arguments = edit_game_dialog.entry_game_arguments.get_text()
|
|
game.mangohud = edit_game_dialog.checkbox_mangohud.get_active()
|
|
game.gamemode = edit_game_dialog.checkbox_gamemode.get_active()
|
|
game.disable_hidraw = edit_game_dialog.checkbox_disable_hidraw.get_active()
|
|
game.protonfix = edit_game_dialog.entry_protonfix.get_text()
|
|
game.runner = edit_game_dialog.combobox_runner.get_active_text()
|
|
game.addapp_checkbox = edit_game_dialog.checkbox_addapp.get_active()
|
|
game.addapp = edit_game_dialog.entry_addapp.get_text()
|
|
game.lossless_enabled = edit_game_dialog.lossless_enabled
|
|
game.lossless_multiplier = edit_game_dialog.lossless_multiplier
|
|
game.lossless_flow = edit_game_dialog.lossless_flow
|
|
game.lossless_performance = edit_game_dialog.lossless_performance
|
|
game.lossless_hdr = edit_game_dialog.lossless_hdr
|
|
game.lossless_present = edit_game_dialog.lossless_present
|
|
game.prevent_sleep = edit_game_dialog.checkbox_prevent_sleep.get_active()
|
|
|
|
title_formatted = format_title(game.title)
|
|
|
|
game.gameid = title_formatted
|
|
game.addapp_bat = f"{os.path.dirname(game.path)}/faugus-{title_formatted}.bat"
|
|
|
|
if self.interface_mode == "Banners":
|
|
banner = os.path.join(banners_dir, f"{title_formatted}.png")
|
|
temp_banner_path = edit_game_dialog.banner_path_temp
|
|
try:
|
|
# Use `magick` to resize the image
|
|
command_magick = shutil.which("magick") or shutil.which("convert")
|
|
subprocess.run([command_magick, temp_banner_path, "-resize", "230x345!", banner], check=True)
|
|
game.banner = banner
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"Error resizing banner: {e}")
|
|
|
|
game.runner = convert_runner(game.runner)
|
|
if edit_game_dialog.combobox_launcher.get_active() == 1:
|
|
game.runner = "Linux-Native"
|
|
if edit_game_dialog.combobox_launcher.get_active() == 2:
|
|
game.runner = "Steam"
|
|
if edit_game_dialog.combobox_launcher.get_active() == 3:
|
|
game.startup_wm_class = "battle.net.exe"
|
|
elif getattr(game, "startup_wm_class", "") == "battle.net.exe":
|
|
game.startup_wm_class = ""
|
|
|
|
icon_temp = os.path.expanduser(edit_game_dialog.icon_temp)
|
|
icon_final = f'{edit_game_dialog.icons_path}/{title_formatted}.png'
|
|
|
|
# Determine the state of the shortcut checkbox
|
|
desktop_shortcut_state = edit_game_dialog.checkbox_shortcut_desktop.get_active()
|
|
appmenu_shortcut_state = edit_game_dialog.checkbox_shortcut_appmenu.get_active()
|
|
steam_shortcut_state = edit_game_dialog.checkbox_shortcut_steam.get_active()
|
|
|
|
# Call add_remove_shortcut method
|
|
self.add_shortcut(game, desktop_shortcut_state, "desktop", icon_temp, icon_final)
|
|
self.add_shortcut(game, appmenu_shortcut_state, "appmenu", icon_temp, icon_final)
|
|
self.add_steam_shortcut(game, steam_shortcut_state, icon_temp, icon_final)
|
|
|
|
if game.addapp_checkbox == True:
|
|
with open(game.addapp_bat, "w") as bat_file:
|
|
bat_file.write(f'start "" "z:{game.addapp}"\n')
|
|
if game.game_arguments:
|
|
bat_file.write(f'start "" "z:{game.path}" {game.game_arguments}\n')
|
|
else:
|
|
bat_file.write(f'start "" "z:{game.path}"\n')
|
|
|
|
# Save changes and update UI
|
|
self.save_games()
|
|
self.update_list()
|
|
|
|
# Select the game that was edited
|
|
self.select_game_by_title(game.title)
|
|
else:
|
|
if os.path.isfile(edit_game_dialog.icon_temp):
|
|
os.remove(edit_game_dialog.icon_temp)
|
|
|
|
if os.path.isdir(edit_game_dialog.icon_directory):
|
|
shutil.rmtree(edit_game_dialog.icon_directory)
|
|
os.remove(edit_game_dialog.banner_path_temp)
|
|
edit_game_dialog.destroy()
|
|
|
|
def add_shortcut(self, game, shortcut_state, shortcut, icon_temp, icon_final):
|
|
applications_shortcut_path = f"{app_dir}/{game.gameid}.desktop"
|
|
desktop_shortcut_path = f"{desktop_dir}/{game.gameid}.desktop"
|
|
|
|
# Check if the shortcut checkbox is checked
|
|
if shortcut == "desktop" and not shortcut_state:
|
|
# Remove existing shortcut if it exists
|
|
self.remove_shortcut(game, shortcut)
|
|
if os.path.isfile(os.path.expanduser(icon_temp)):
|
|
os.rename(os.path.expanduser(icon_temp), icon_final)
|
|
return
|
|
if shortcut == "appmenu" and not shortcut_state:
|
|
# Remove existing shortcut if it exists
|
|
self.remove_shortcut(game, shortcut)
|
|
if os.path.isfile(os.path.expanduser(icon_temp)):
|
|
os.rename(os.path.expanduser(icon_temp), icon_final)
|
|
return
|
|
|
|
if os.path.isfile(os.path.expanduser(icon_temp)):
|
|
os.rename(os.path.expanduser(icon_temp), icon_final)
|
|
|
|
new_icon_path = get_shortcut_icon_path(game.gameid, icons_dir, faugus_png)
|
|
|
|
# Get the directory containing the executable
|
|
game_directory = os.path.dirname(game.path)
|
|
|
|
# Create a .desktop file
|
|
if IS_FLATPAK:
|
|
desktop_file_content = build_desktop_entry(
|
|
game.title,
|
|
f"flatpak run --command={faugus_run} io.github.Faugus.faugus-launcher --game {game.gameid}",
|
|
new_icon_path,
|
|
game_directory,
|
|
getattr(game, "startup_wm_class", ""),
|
|
)
|
|
else:
|
|
desktop_file_content = build_desktop_entry(
|
|
game.title,
|
|
f"{faugus_run} --game {game.gameid}",
|
|
new_icon_path,
|
|
game_directory,
|
|
getattr(game, "startup_wm_class", ""),
|
|
)
|
|
|
|
# Check if the destination directory exists and create if it doesn't
|
|
if not os.path.exists(app_dir):
|
|
os.makedirs(app_dir)
|
|
|
|
if not os.path.exists(desktop_dir):
|
|
os.makedirs(desktop_dir)
|
|
|
|
if shortcut == "appmenu":
|
|
with open(applications_shortcut_path, 'w') as appmenu_file:
|
|
appmenu_file.write(desktop_file_content)
|
|
os.chmod(applications_shortcut_path, 0o755)
|
|
|
|
if shortcut == "desktop":
|
|
with open(desktop_shortcut_path, 'w') as desktop_file:
|
|
desktop_file.write(desktop_file_content)
|
|
os.chmod(desktop_shortcut_path, 0o755)
|
|
|
|
def add_steam_shortcut(self, game, steam_shortcut_state, icon_temp, icon_final):
|
|
def add_game_to_steam(title, game_directory, icon):
|
|
# Load existing shortcuts
|
|
shortcuts = load_shortcuts(title)
|
|
|
|
# Check if the game already exists
|
|
existing_app_id = None
|
|
for app_id, game_info in shortcuts["shortcuts"].items():
|
|
if isinstance(game_info, dict) and "AppName" in game_info and game_info["AppName"] == title:
|
|
existing_app_id = app_id
|
|
break
|
|
|
|
if existing_app_id:
|
|
# Update only the necessary fields without replacing the entire entry
|
|
game_info = shortcuts["shortcuts"][existing_app_id]
|
|
if IS_FLATPAK:
|
|
if IS_STEAM_FLATPAK:
|
|
game_info["Exe"] = f'"flatpak-spawn"'
|
|
game_info["LaunchOptions"] = f'--host flatpak run --command=/app/bin/faugus-run io.github.Faugus.faugus-launcher --game {game.gameid}'
|
|
else:
|
|
game_info["Exe"] = f'"flatpak"'
|
|
game_info["LaunchOptions"] = f'run --command=/app/bin/faugus-run io.github.Faugus.faugus-launcher --game {game.gameid}'
|
|
else:
|
|
game_info["Exe"] = f'"{faugus_run}"'
|
|
game_info["LaunchOptions"] = f'--game {game.gameid}'
|
|
game_info["StartDir"] = game_directory
|
|
game_info["icon"] = icon
|
|
else:
|
|
# Generate a new ID for the game
|
|
new_app_id = max([int(k) for k in shortcuts["shortcuts"].keys() if k.isdigit()] or [0]) + 1
|
|
|
|
# Add the new game
|
|
if IS_FLATPAK:
|
|
if IS_STEAM_FLATPAK:
|
|
shortcuts["shortcuts"][str(new_app_id)] = {
|
|
"appid": new_app_id,
|
|
"AppName": title,
|
|
"Exe": f'"flatpak-spawn"',
|
|
"StartDir": game_directory,
|
|
"icon": icon,
|
|
"ShortcutPath": "",
|
|
"LaunchOptions": f'--host flatpak run --command=/app/bin/faugus-run io.github.Faugus.faugus-launcher --game {game.gameid}',
|
|
"IsHidden": 0,
|
|
"AllowDesktopConfig": 1,
|
|
"AllowOverlay": 1,
|
|
"OpenVR": 0,
|
|
"Devkit": 0,
|
|
"DevkitGameID": "",
|
|
"LastPlayTime": 0,
|
|
"FlatpakAppID": "",
|
|
}
|
|
else:
|
|
shortcuts["shortcuts"][str(new_app_id)] = {
|
|
"appid": new_app_id,
|
|
"AppName": title,
|
|
"Exe": f'"flatpak"',
|
|
"StartDir": game_directory,
|
|
"icon": icon,
|
|
"ShortcutPath": "",
|
|
"LaunchOptions": f'run --command=/app/bin/faugus-run io.github.Faugus.faugus-launcher --game {game.gameid}',
|
|
"IsHidden": 0,
|
|
"AllowDesktopConfig": 1,
|
|
"AllowOverlay": 1,
|
|
"OpenVR": 0,
|
|
"Devkit": 0,
|
|
"DevkitGameID": "",
|
|
"LastPlayTime": 0,
|
|
"FlatpakAppID": "",
|
|
}
|
|
else:
|
|
shortcuts["shortcuts"][str(new_app_id)] = {
|
|
"appid": new_app_id,
|
|
"AppName": title,
|
|
"Exe": f'"{faugus_run}"',
|
|
"StartDir": game_directory,
|
|
"icon": icon,
|
|
"ShortcutPath": "",
|
|
"LaunchOptions": f'--game {game.gameid}',
|
|
"IsHidden": 0,
|
|
"AllowDesktopConfig": 1,
|
|
"AllowOverlay": 1,
|
|
"OpenVR": 0,
|
|
"Devkit": 0,
|
|
"DevkitGameID": "",
|
|
"LastPlayTime": 0,
|
|
"FlatpakAppID": "",
|
|
}
|
|
|
|
# Save shortcuts back to the file
|
|
save_shortcuts(shortcuts)
|
|
|
|
def remove_shortcuts(shortcuts, title):
|
|
# Find and remove existing shortcuts with the same title
|
|
if os.path.exists(steam_shortcuts_path):
|
|
to_remove = [app_id for app_id, game in shortcuts["shortcuts"].items() if
|
|
isinstance(game, dict) and "AppName" in game and game["AppName"] == title]
|
|
for app_id in to_remove:
|
|
del shortcuts["shortcuts"][app_id]
|
|
save_shortcuts(shortcuts)
|
|
|
|
def load_shortcuts(title):
|
|
# Check if the file exists
|
|
if os.path.exists(steam_shortcuts_path):
|
|
try:
|
|
# Attempt to load existing shortcuts
|
|
with open(steam_shortcuts_path, 'rb') as f:
|
|
return vdf.binary_load(f)
|
|
except SyntaxError:
|
|
# If the file is corrupted, create a new one
|
|
return {"shortcuts": {}}
|
|
else:
|
|
# If the file does not exist, create a new one
|
|
return {"shortcuts": {}}
|
|
|
|
def save_shortcuts(shortcuts):
|
|
if not os.path.exists(steam_shortcuts_path):
|
|
open(steam_shortcuts_path, 'wb').close()
|
|
|
|
with open(steam_shortcuts_path, 'wb') as f:
|
|
vdf.binary_dump(shortcuts, f)
|
|
|
|
# Check if the shortcut checkbox is checked
|
|
if not steam_shortcut_state:
|
|
# Remove existing shortcut if it exists
|
|
shortcuts = load_shortcuts(game.title)
|
|
remove_shortcuts(shortcuts, game.title)
|
|
if os.path.isfile(os.path.expanduser(icon_temp)):
|
|
os.rename(os.path.expanduser(icon_temp), icon_final)
|
|
return
|
|
|
|
if os.path.isfile(os.path.expanduser(icon_temp)):
|
|
os.rename(os.path.expanduser(icon_temp), icon_final)
|
|
|
|
new_icon_path = get_shortcut_icon_path(game.gameid, icons_dir, faugus_png)
|
|
|
|
# Get the directory containing the executable
|
|
game_directory = os.path.dirname(game.path)
|
|
|
|
add_game_to_steam(game.title, game_directory, new_icon_path)
|
|
|
|
def update_preview(self, dialog):
|
|
if file_path := dialog.get_preview_filename():
|
|
try:
|
|
# Create an image widget for the thumbnail
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(file_path)
|
|
|
|
# Resize the thumbnail if it's too large, maintaining the aspect ratio
|
|
max_width = 400
|
|
max_height = 400
|
|
width = pixbuf.get_width()
|
|
height = pixbuf.get_height()
|
|
|
|
if width > max_width or height > max_height:
|
|
# Calculate the new width and height while maintaining the aspect ratio
|
|
ratio = min(max_width / width, max_height / height)
|
|
new_width = int(width * ratio)
|
|
new_height = int(height * ratio)
|
|
pixbuf = pixbuf.scale_simple(new_width, new_height, GdkPixbuf.InterpType.BILINEAR)
|
|
|
|
image = Gtk.Image.new_from_pixbuf(pixbuf)
|
|
dialog.set_preview_widget(image)
|
|
dialog.set_preview_widget_active(True)
|
|
dialog.get_preview_widget().set_size_request(max_width, max_height)
|
|
except GLib.Error:
|
|
dialog.set_preview_widget_active(False)
|
|
else:
|
|
dialog.set_preview_widget_active(False)
|
|
|
|
def remove_banner_icon(self, game):
|
|
banner_file_path = f"{banners_dir}/{game.gameid}.png"
|
|
icon_file_path_png = f"{icons_dir}/{game.gameid}.png"
|
|
icon_file_path_ico = f"{icons_dir}/{game.gameid}.ico"
|
|
if os.path.exists(banner_file_path):
|
|
os.remove(banner_file_path)
|
|
if os.path.exists(icon_file_path_png):
|
|
os.remove(icon_file_path_png)
|
|
if os.path.exists(icon_file_path_ico):
|
|
os.remove(icon_file_path_ico)
|
|
|
|
def remove_shortcut(self, game, shortcut):
|
|
applications_shortcut_path = f"{app_dir}/{game.gameid}.desktop"
|
|
desktop_shortcut_path = f"{desktop_dir}/{game.gameid}.desktop"
|
|
if os.path.exists(game.addapp_bat):
|
|
os.remove(game.addapp_bat)
|
|
if shortcut == "appmenu":
|
|
if os.path.exists(applications_shortcut_path):
|
|
os.remove(applications_shortcut_path)
|
|
if shortcut == "desktop":
|
|
if os.path.exists(desktop_shortcut_path):
|
|
os.remove(desktop_shortcut_path)
|
|
if shortcut == "both":
|
|
if os.path.exists(applications_shortcut_path):
|
|
os.remove(applications_shortcut_path)
|
|
if os.path.exists(desktop_shortcut_path):
|
|
os.remove(desktop_shortcut_path)
|
|
|
|
def update_list(self):
|
|
for child in self.flowbox.get_children():
|
|
self.flowbox.remove(child)
|
|
|
|
self.games.clear()
|
|
self.load_games()
|
|
self.entry_search.set_text("")
|
|
self.show_all()
|
|
|
|
if self.interface_mode != "List":
|
|
if self.fullscreen_activated:
|
|
self.fullscreen_activated = True
|
|
self.grid_corner.set_visible(True)
|
|
self.grid_left.set_margin_start(70)
|
|
else:
|
|
self.fullscreen_activated = False
|
|
self.grid_corner.set_visible(False)
|
|
self.grid_left.set_margin_start(0)
|
|
|
|
def save_games(self):
|
|
try:
|
|
with open("games.json", "r", encoding="utf-8") as file:
|
|
all_games_data = json.load(file)
|
|
except (FileNotFoundError, json.JSONDecodeError):
|
|
all_games_data = []
|
|
|
|
visible_games_map = {game.gameid: game for game in self.games}
|
|
deleted_id = getattr(self, "_deleted_gameid", None)
|
|
new_games_data = []
|
|
|
|
for game_data in all_games_data:
|
|
gameid = game_data.get("gameid")
|
|
hidden = game_data.get("hidden", False)
|
|
|
|
if deleted_id and gameid == deleted_id:
|
|
continue
|
|
if not hidden and gameid not in visible_games_map:
|
|
continue
|
|
|
|
if gameid in visible_games_map:
|
|
game = visible_games_map.pop(gameid)
|
|
game_data = {
|
|
"gameid": game.gameid,
|
|
"title": game.title,
|
|
"path": game.path,
|
|
"prefix": game.prefix,
|
|
"launch_arguments": game.launch_arguments,
|
|
"game_arguments": game.game_arguments,
|
|
"mangohud": True if game.mangohud else "",
|
|
"gamemode": True if game.gamemode else "",
|
|
"disable_hidraw": True if game.disable_hidraw else "",
|
|
"protonfix": game.protonfix,
|
|
"runner": game.runner,
|
|
"addapp_checkbox": "addapp_enabled" if game.addapp_checkbox else "",
|
|
"addapp": game.addapp,
|
|
"addapp_bat": game.addapp_bat,
|
|
"banner": game.banner,
|
|
"lossless_enabled": game.lossless_enabled,
|
|
"lossless_multiplier": game.lossless_multiplier,
|
|
"lossless_flow": game.lossless_flow,
|
|
"lossless_performance": game.lossless_performance,
|
|
"lossless_hdr": game.lossless_hdr,
|
|
"lossless_present": game.lossless_present,
|
|
"playtime": game.playtime,
|
|
"hidden": hidden,
|
|
"prevent_sleep": game.prevent_sleep,
|
|
"favorite": game.favorite,
|
|
"startup_wm_class": game.startup_wm_class,
|
|
}
|
|
|
|
new_games_data.append(game_data)
|
|
|
|
if hasattr(self, "_deleted_gameid"):
|
|
del self._deleted_gameid
|
|
|
|
self.backup_games()
|
|
|
|
with open("games.json", "w", encoding="utf-8") as file:
|
|
json.dump(new_games_data, file, ensure_ascii=False, indent=4)
|
|
|
|
def backup_games(self):
|
|
if os.path.isfile(games_json):
|
|
os.makedirs(backup_dir, exist_ok=True)
|
|
|
|
now = GLib.DateTime.new_now_local()
|
|
timestamp = now.format("%Y-%m-%d_%H-%M-%S")
|
|
|
|
backup_file = os.path.join(
|
|
backup_dir,
|
|
f"games-data-{timestamp}.json"
|
|
)
|
|
|
|
shutil.copy2(games_json, backup_file)
|
|
|
|
backups = sorted(
|
|
f for f in os.listdir(backup_dir)
|
|
if f.startswith("games-data-") and f.endswith(".json")
|
|
)
|
|
|
|
while len(backups) > 20:
|
|
oldest = backups.pop(0)
|
|
os.remove(os.path.join(backup_dir, oldest))
|
|
|
|
class Settings(Gtk.Dialog):
|
|
def __init__(self, parent):
|
|
# Initialize the Settings dialog
|
|
super().__init__(title=_("Settings"), transient_for=parent, modal=True)
|
|
self.set_resizable(False)
|
|
self.set_icon_from_file(faugus_png)
|
|
self.parent = parent
|
|
self.logging_warning = False
|
|
|
|
css_provider = Gtk.CssProvider()
|
|
css = """
|
|
.entry {
|
|
border-color: Red;
|
|
}
|
|
.paypal {
|
|
color: white;
|
|
background: #001C64;
|
|
}
|
|
.kofi {
|
|
color: white;
|
|
background: #1AC0FF;
|
|
}
|
|
"""
|
|
css_provider.load_from_data(css.encode('utf-8'))
|
|
Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), css_provider,
|
|
Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
|
|
|
self.LANG_NAMES = {
|
|
"af": "Afrikaans",
|
|
"am": "Amharic",
|
|
"ar": "Arabic",
|
|
"az": "Azerbaijani",
|
|
"be": "Belarusian",
|
|
"bg": "Bulgarian",
|
|
"bn": "Bengali",
|
|
"bs": "Bosnian",
|
|
"ca": "Catalan",
|
|
"cs": "Czech",
|
|
"cy": "Welsh",
|
|
"da": "Danish",
|
|
"de": "German",
|
|
"el": "Greek",
|
|
"en_US": "English",
|
|
"eo": "Esperanto",
|
|
"es": "Spanish",
|
|
"et": "Estonian",
|
|
"eu": "Basque",
|
|
"fa": "Persian",
|
|
"fi": "Finnish",
|
|
"fil": "Filipino",
|
|
"fr": "French",
|
|
"ga": "Irish",
|
|
"gl": "Galician",
|
|
"gu": "Gujarati",
|
|
"he": "Hebrew",
|
|
"hi": "Hindi",
|
|
"hr": "Croatian",
|
|
"ht": "Haitian Creole",
|
|
"hu": "Hungarian",
|
|
"hy": "Armenian",
|
|
"id": "Indonesian",
|
|
"is": "Icelandic",
|
|
"it": "Italian",
|
|
"ja": "Japanese",
|
|
"jv": "Javanese",
|
|
"ka": "Georgian",
|
|
"kk": "Kazakh",
|
|
"km": "Khmer",
|
|
"kn": "Kannada",
|
|
"ko": "Korean",
|
|
"ku": "Kurdish (Kurmanji)",
|
|
"ky": "Kyrgyz",
|
|
"lo": "Lao",
|
|
"lt": "Lithuanian",
|
|
"lv": "Latvian",
|
|
"mg": "Malagasy",
|
|
"mi": "Maori",
|
|
"mk": "Macedonian",
|
|
"ml": "Malayalam",
|
|
"mn": "Mongolian",
|
|
"mr": "Marathi",
|
|
"ms": "Malay",
|
|
"mt": "Maltese",
|
|
"my": "Burmese",
|
|
"nb": "Norwegian (Bokmål)",
|
|
"ne": "Nepali",
|
|
"nl": "Dutch",
|
|
"nn": "Norwegian (Nynorsk)",
|
|
"pa": "Punjabi",
|
|
"pl": "Polish",
|
|
"ps": "Pashto",
|
|
"pt": "Portuguese (Portugal)",
|
|
"pt_BR": "Portuguese (Brazil)",
|
|
"ro": "Romanian",
|
|
"ru": "Russian",
|
|
"sd": "Sindhi",
|
|
"si": "Sinhala",
|
|
"sk": "Slovak",
|
|
"sl": "Slovenian",
|
|
"so": "Somali",
|
|
"sq": "Albanian",
|
|
"sr": "Serbian",
|
|
"sv": "Swedish",
|
|
"sw": "Swahili",
|
|
"ta": "Tamil",
|
|
"te": "Telugu",
|
|
"tg": "Tajik",
|
|
"th": "Thai",
|
|
"tk": "Turkmen",
|
|
"tl": "Tagalog",
|
|
"tr": "Turkish",
|
|
"tt": "Tatar",
|
|
"ug": "Uyghur",
|
|
"uk": "Ukrainian",
|
|
"ur": "Urdu",
|
|
"uz": "Uzbek",
|
|
"vi": "Vietnamese",
|
|
"xh": "Xhosa",
|
|
"yi": "Yiddish",
|
|
"zh_CN": "Chinese (Simplified)",
|
|
"zh_TW": "Chinese (Traditional)",
|
|
"zu": "Zulu",
|
|
}
|
|
|
|
self.lang_codes = {}
|
|
|
|
# Widgets for Interface mode
|
|
self.label_language = Gtk.Label(label=_("Language"))
|
|
self.label_language.set_halign(Gtk.Align.START)
|
|
self.combobox_language = Gtk.ComboBoxText()
|
|
|
|
# Widgets for Interface mode
|
|
self.label_interface = Gtk.Label(label=_("Interface Mode"))
|
|
self.label_interface.set_halign(Gtk.Align.START)
|
|
self.combobox_interface = Gtk.ComboBoxText()
|
|
self.combobox_interface.connect("changed", self.on_combobox_interface_changed)
|
|
self.combobox_interface.append_text("List")
|
|
self.combobox_interface.append_text("Blocks")
|
|
self.combobox_interface.append_text("Banners")
|
|
|
|
# Create checkbox for 'Start maximized' option
|
|
self.checkbox_start_maximized = Gtk.CheckButton(label=_("Start maximized"))
|
|
self.checkbox_start_maximized.set_active(False)
|
|
self.checkbox_start_maximized.connect("toggled", self.on_checkbox_toggled, "maximized")
|
|
|
|
# Create checkbox for 'Start fullscreen' option
|
|
self.checkbox_start_fullscreen = Gtk.CheckButton(label=_("Start in fullscreen"))
|
|
self.checkbox_start_fullscreen.set_active(False)
|
|
self.checkbox_start_fullscreen.connect("toggled", self.on_checkbox_toggled, "fullscreen")
|
|
self.checkbox_start_fullscreen.set_tooltip_text(_("Alt+Enter toggles fullscreen"))
|
|
|
|
self.checkbox_show_labels = Gtk.CheckButton(label=_("Show labels"))
|
|
self.checkbox_show_labels.set_active(False)
|
|
|
|
self.checkbox_smaller_banners = Gtk.CheckButton(label=_("Smaller banners"))
|
|
self.checkbox_smaller_banners.set_active(False)
|
|
|
|
# Widgets for prefix
|
|
self.label_default_prefix = Gtk.Label(label=_("Default Prefixes Location"))
|
|
self.label_default_prefix.set_halign(Gtk.Align.START)
|
|
|
|
self.entry_default_prefix = Gtk.Entry()
|
|
self.entry_default_prefix.set_tooltip_text(_("/path/to/the/prefix"))
|
|
self.entry_default_prefix.set_has_tooltip(True)
|
|
self.entry_default_prefix.connect("query-tooltip", self.on_entry_query_tooltip)
|
|
self.entry_default_prefix.connect("changed", self.on_entry_changed, self.entry_default_prefix)
|
|
|
|
self.button_search_prefix = Gtk.Button()
|
|
self.button_search_prefix.set_image(Gtk.Image.new_from_icon_name("system-search-symbolic", Gtk.IconSize.BUTTON))
|
|
self.button_search_prefix.connect("clicked", self.on_button_search_prefix_clicked)
|
|
self.button_search_prefix.set_size_request(50, -1)
|
|
|
|
self.label_lossless = Gtk.Label(label=_("Lossless Scaling Location"))
|
|
self.label_lossless.set_halign(Gtk.Align.START)
|
|
|
|
self.entry_lossless = Gtk.Entry()
|
|
self.entry_lossless.set_tooltip_text(_("/path/to/Lossless.dll"))
|
|
self.entry_lossless.set_has_tooltip(True)
|
|
self.entry_lossless.connect("query-tooltip", self.on_entry_query_tooltip)
|
|
|
|
self.button_search_lossless = Gtk.Button()
|
|
self.button_search_lossless.set_image(Gtk.Image.new_from_icon_name("system-search-symbolic", Gtk.IconSize.BUTTON))
|
|
self.button_search_lossless.connect("clicked", self.on_button_search_lossless_clicked)
|
|
self.button_search_lossless.set_size_request(50, -1)
|
|
|
|
self.label_default_prefix_tools = Gtk.Label(label=_("Default Prefix Tools"))
|
|
self.label_default_prefix_tools.set_halign(Gtk.Align.START)
|
|
self.label_default_prefix_tools.set_margin_start(10)
|
|
self.label_default_prefix_tools.set_margin_end(10)
|
|
self.label_default_prefix_tools.set_margin_top(10)
|
|
|
|
# Widgets for runner
|
|
self.label_runner = Gtk.Label(label=_("Default Proton"))
|
|
self.label_runner.set_halign(Gtk.Align.START)
|
|
self.combobox_runner = Gtk.ComboBoxText()
|
|
|
|
self.button_proton_manager = Gtk.Button(label=_("Proton Manager"))
|
|
self.button_proton_manager.connect("clicked", self.on_button_proton_manager_clicked)
|
|
|
|
self.label_miscellaneous = Gtk.Label(label=_("Miscellaneous"))
|
|
self.label_miscellaneous.set_halign(Gtk.Align.START)
|
|
self.label_miscellaneous.set_margin_start(10)
|
|
self.label_miscellaneous.set_margin_end(10)
|
|
#self.label_miscellaneous.set_margin_top(10)
|
|
|
|
# Create checkbox for 'Use discrete GPU' option
|
|
self.checkbox_discrete_gpu = Gtk.CheckButton(label=_("Use discrete GPU"))
|
|
self.checkbox_discrete_gpu.set_active(False)
|
|
|
|
# Create checkbox for 'Close after launch' option
|
|
self.checkbox_close_after_launch = Gtk.CheckButton(label=_("Close when running a game/app"))
|
|
self.checkbox_close_after_launch.set_active(False)
|
|
|
|
# Create checkbox for 'System tray' option
|
|
self.checkbox_system_tray = Gtk.CheckButton(label=_("System tray icon"))
|
|
self.checkbox_system_tray.set_active(False)
|
|
self.checkbox_system_tray.connect("toggled", self.on_checkbox_system_tray_toggled)
|
|
|
|
# Create checkbox for 'Start on boot' option
|
|
self.checkbox_start_boot = Gtk.CheckButton(label=_("Start on boot"))
|
|
self.checkbox_start_boot.set_active(False)
|
|
self.checkbox_start_boot.set_sensitive(False)
|
|
|
|
self.checkbox_mono_icon = Gtk.CheckButton(label=_("Monochrome icon"))
|
|
self.checkbox_mono_icon.set_active(False)
|
|
self.checkbox_mono_icon.set_sensitive(False)
|
|
|
|
# Create checkbox for 'Splash screen' option
|
|
self.checkbox_splash_disable = Gtk.CheckButton(label=_("Disable splash window"))
|
|
self.checkbox_splash_disable.set_active(False)
|
|
|
|
# Create checkbox for 'Enable logging' option
|
|
self.checkbox_enable_logging = Gtk.CheckButton(label=_("Enable logging"))
|
|
self.checkbox_enable_logging.set_active(False)
|
|
|
|
self.checkbox_show_hidden = Gtk.CheckButton(label=_("Show hidden games"))
|
|
self.checkbox_show_hidden.set_active(False)
|
|
self.checkbox_show_hidden.set_tooltip_text(_("Press Ctrl+H to show/hide games."))
|
|
|
|
self.checkbox_wayland_driver = Gtk.CheckButton(label=_("Use Wayland driver (experimental)"))
|
|
self.checkbox_wayland_driver.set_active(False)
|
|
self.checkbox_wayland_driver.set_tooltip_text(_("Only works with GE-Proton10 or Proton-EM-10."))
|
|
self.checkbox_wayland_driver.connect("toggled", self.on_checkbox_wayland_driver_toggled)
|
|
|
|
self.checkbox_enable_hdr = Gtk.CheckButton(label=_("Enable HDR (experimental)"))
|
|
self.checkbox_enable_hdr.set_active(False)
|
|
self.checkbox_enable_hdr.set_sensitive(False)
|
|
self.checkbox_enable_hdr.set_tooltip_text(_("Only works with GE-Proton10 or Proton-EM-10."))
|
|
|
|
self.checkbox_enable_wow64 = Gtk.CheckButton(label=_("Enable WOW64 (experimental)"))
|
|
self.checkbox_enable_wow64.set_active(False)
|
|
self.checkbox_enable_wow64.set_tooltip_text(_("Only works with GE-Proton10-9 or superior and Proton-EM-10-24 or superior."))
|
|
|
|
# Button Winetricks
|
|
self.button_winetricks_default = Gtk.Button(label="Winetricks")
|
|
self.button_winetricks_default.connect("clicked", self.on_button_winetricks_default_clicked)
|
|
self.button_winetricks_default.set_size_request(120, -1)
|
|
|
|
# Button Winecfg
|
|
self.button_winecfg_default = Gtk.Button(label="Winecfg")
|
|
self.button_winecfg_default.connect("clicked", self.on_button_winecfg_default_clicked)
|
|
self.button_winecfg_default.set_size_request(120, -1)
|
|
|
|
# Button for Run
|
|
self.button_run_default = Gtk.Button(label=_("Run"))
|
|
self.button_run_default.set_size_request(120, -1)
|
|
self.button_run_default.connect("clicked", self.on_button_run_default_clicked)
|
|
self.button_run_default.set_tooltip_text(_("Run a file inside the prefix"))
|
|
|
|
# Checkboxes for optional features
|
|
self.checkbox_mangohud = Gtk.CheckButton(label="MangoHud")
|
|
self.checkbox_mangohud.set_tooltip_text(
|
|
_("Shows an overlay for monitoring FPS, temperatures, CPU/GPU load and more."))
|
|
self.checkbox_gamemode = Gtk.CheckButton(label="GameMode")
|
|
self.checkbox_gamemode.set_tooltip_text(_("Tweaks your system to improve performance."))
|
|
self.checkbox_disable_hidraw = Gtk.CheckButton(label=_("Disable Hidraw"))
|
|
self.checkbox_disable_hidraw.set_tooltip_text(
|
|
_("May fix controller issues with some games. Only works with GE-Proton10 or Proton-EM-10."))
|
|
self.checkbox_prevent_sleep = Gtk.CheckButton(label=_("Prevent Sleep"))
|
|
|
|
self.label_support = Gtk.Label(label=_("Support the Project"))
|
|
self.label_support.set_halign(Gtk.Align.START)
|
|
self.label_support.set_margin_start(10)
|
|
self.label_support.set_margin_end(10)
|
|
self.label_support.set_margin_top(10)
|
|
|
|
button_kofi = Gtk.Button(label="Ko-fi")
|
|
button_kofi.connect("clicked", self.on_button_kofi_clicked)
|
|
button_kofi.get_style_context().add_class("kofi")
|
|
|
|
button_paypal = Gtk.Button(label="PayPal")
|
|
button_paypal.connect("clicked", self.on_button_paypal_clicked)
|
|
button_paypal.get_style_context().add_class("paypal")
|
|
|
|
# Button Cancel
|
|
self.button_cancel = Gtk.Button(label=_("Cancel"))
|
|
self.button_cancel.connect("clicked", lambda widget: self.response(Gtk.ResponseType.CANCEL))
|
|
self.button_cancel.set_size_request(150, -1)
|
|
|
|
# Button Ok
|
|
self.button_ok = Gtk.Button(label=_("Ok"))
|
|
self.button_ok.connect("clicked", lambda widget: self.response(Gtk.ResponseType.OK))
|
|
self.button_ok.set_size_request(150, -1)
|
|
|
|
self.label_settings = Gtk.Label(label=_("Backup/Restore Settings"))
|
|
self.label_settings.set_halign(Gtk.Align.START)
|
|
self.label_settings.set_margin_start(10)
|
|
self.label_settings.set_margin_end(10)
|
|
self.label_settings.set_margin_top(10)
|
|
|
|
# Button Backup
|
|
button_backup = Gtk.Button(label=_("Backup"))
|
|
button_backup.connect("clicked", self.on_button_backup_clicked)
|
|
|
|
# Button Restore
|
|
button_restore = Gtk.Button(label=_("Restore"))
|
|
button_restore.connect("clicked", self.on_button_restore_clicked)
|
|
|
|
self.button_clearlogs = Gtk.Button()
|
|
self.update_button_label()
|
|
self.button_clearlogs.connect("clicked", self.on_clear_logs_clicked)
|
|
|
|
self.label_envar = Gtk.Label(label=_("Global Environment Variables"))
|
|
self.label_envar.set_halign(Gtk.Align.START)
|
|
|
|
self.liststore = Gtk.ListStore(str)
|
|
self.liststore.append([""])
|
|
|
|
treeview = Gtk.TreeView(model=self.liststore)
|
|
treeview.set_has_tooltip(True)
|
|
treeview.connect("query-tooltip", self.on_query_tooltip)
|
|
treeview.connect("key-press-event", self.on_envar_key_press)
|
|
|
|
renderer = Gtk.CellRendererText()
|
|
renderer.set_property("editable", True)
|
|
renderer.set_property("ellipsize", 3)
|
|
renderer.connect("edited", self.on_cell_edited, 0)
|
|
|
|
column = Gtk.TreeViewColumn("", renderer, text=0)
|
|
treeview.set_headers_visible(False)
|
|
|
|
treeview.append_column(column)
|
|
|
|
scrolled_window = Gtk.ScrolledWindow()
|
|
scrolled_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
|
scrolled_window.set_min_content_height(130)
|
|
scrolled_window.add(treeview)
|
|
|
|
self.box = self.get_content_area()
|
|
self.box.set_margin_start(0)
|
|
self.box.set_margin_end(0)
|
|
self.box.set_margin_top(0)
|
|
self.box.set_margin_bottom(0)
|
|
self.box.set_halign(Gtk.Align.CENTER)
|
|
self.box.set_valign(Gtk.Align.CENTER)
|
|
self.box.set_vexpand(True)
|
|
self.box.set_hexpand(True)
|
|
|
|
url = f"https://github.com/Faugus/faugus-launcher/releases"
|
|
label_version = Gtk.Label()
|
|
label_version.set_markup(f'<span underline="none"><a href="{url}"> {VERSION} </a></span>')
|
|
label_version.set_use_markup(True)
|
|
|
|
frame = Gtk.Frame()
|
|
frame.set_label_widget(label_version)
|
|
frame.set_label_align(0.99, 0.5)
|
|
frame.set_margin_start(10)
|
|
frame.set_margin_end(10)
|
|
frame.set_margin_bottom(10)
|
|
|
|
box_main = Gtk.Grid()
|
|
box_main.set_column_homogeneous(True)
|
|
box_main.set_column_spacing(10)
|
|
box_left = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
box_mid = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
box_right = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
|
|
box_buttons = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
|
|
|
grid_language = Gtk.Grid()
|
|
grid_language.set_row_spacing(10)
|
|
grid_language.set_column_spacing(10)
|
|
grid_language.set_margin_start(10)
|
|
grid_language.set_margin_end(10)
|
|
grid_language.set_margin_top(10)
|
|
grid_language.set_margin_bottom(10)
|
|
|
|
grid_prefix = Gtk.Grid()
|
|
grid_prefix.set_row_spacing(10)
|
|
grid_prefix.set_column_spacing(10)
|
|
grid_prefix.set_margin_start(10)
|
|
grid_prefix.set_margin_end(10)
|
|
#grid_prefix.set_margin_top(10)
|
|
grid_prefix.set_margin_bottom(10)
|
|
|
|
grid_runner = Gtk.Grid()
|
|
grid_runner.set_row_spacing(10)
|
|
grid_runner.set_column_spacing(10)
|
|
grid_runner.set_margin_start(10)
|
|
grid_runner.set_margin_end(10)
|
|
grid_runner.set_margin_top(10)
|
|
grid_runner.set_margin_bottom(10)
|
|
|
|
grid_lossless = Gtk.Grid()
|
|
grid_lossless.set_row_spacing(10)
|
|
grid_lossless.set_column_spacing(10)
|
|
grid_lossless.set_margin_start(10)
|
|
grid_lossless.set_margin_end(10)
|
|
grid_lossless.set_margin_top(10)
|
|
grid_lossless.set_margin_bottom(10)
|
|
|
|
grid_tools = Gtk.Grid()
|
|
grid_tools.set_row_spacing(10)
|
|
grid_tools.set_column_spacing(10)
|
|
grid_tools.set_margin_start(10)
|
|
grid_tools.set_margin_end(10)
|
|
grid_tools.set_margin_top(10)
|
|
grid_tools.set_margin_bottom(10)
|
|
|
|
grid_logs = Gtk.Grid()
|
|
grid_logs.set_row_spacing(10)
|
|
grid_logs.set_column_spacing(10)
|
|
grid_logs.set_margin_start(10)
|
|
grid_logs.set_margin_end(10)
|
|
grid_logs.set_margin_top(10)
|
|
grid_logs.set_margin_bottom(10)
|
|
|
|
grid_miscellaneous = Gtk.Grid()
|
|
grid_miscellaneous.set_row_spacing(10)
|
|
grid_miscellaneous.set_column_spacing(10)
|
|
grid_miscellaneous.set_margin_start(10)
|
|
grid_miscellaneous.set_margin_end(10)
|
|
grid_miscellaneous.set_margin_top(10)
|
|
grid_miscellaneous.set_margin_bottom(10)
|
|
|
|
grid_envar = Gtk.Grid()
|
|
grid_envar.set_row_spacing(10)
|
|
grid_envar.set_column_spacing(10)
|
|
grid_envar.set_margin_start(10)
|
|
grid_envar.set_margin_end(10)
|
|
#grid_envar.set_margin_top(10)
|
|
grid_envar.set_margin_bottom(10)
|
|
|
|
grid_interface_mode = Gtk.Grid()
|
|
grid_interface_mode.set_row_spacing(10)
|
|
grid_interface_mode.set_column_spacing(10)
|
|
grid_interface_mode.set_margin_start(10)
|
|
grid_interface_mode.set_margin_end(10)
|
|
grid_interface_mode.set_margin_top(10)
|
|
grid_interface_mode.set_margin_bottom(10)
|
|
|
|
grid_support = Gtk.Grid()
|
|
grid_support.set_column_homogeneous(True)
|
|
grid_support.set_row_spacing(10)
|
|
grid_support.set_column_spacing(10)
|
|
grid_support.set_margin_start(10)
|
|
grid_support.set_margin_end(10)
|
|
grid_support.set_margin_top(10)
|
|
grid_support.set_margin_bottom(10)
|
|
|
|
grid_backup = Gtk.Grid()
|
|
grid_backup.set_column_homogeneous(True)
|
|
grid_backup.set_row_spacing(10)
|
|
grid_backup.set_column_spacing(10)
|
|
grid_backup.set_margin_start(10)
|
|
grid_backup.set_margin_end(10)
|
|
grid_backup.set_margin_top(10)
|
|
grid_backup.set_margin_bottom(10)
|
|
|
|
self.grid_big_interface = Gtk.Grid()
|
|
self.grid_big_interface.set_row_spacing(10)
|
|
self.grid_big_interface.set_column_spacing(10)
|
|
self.grid_big_interface.set_margin_start(10)
|
|
self.grid_big_interface.set_margin_end(10)
|
|
self.grid_big_interface.set_margin_bottom(10)
|
|
|
|
grid_language.attach(self.label_language, 0, 0, 1, 1)
|
|
grid_language.attach(self.combobox_language, 0, 1, 1, 1)
|
|
self.combobox_language.set_hexpand(True)
|
|
|
|
grid_prefix.attach(self.label_default_prefix, 0, 0, 1, 1)
|
|
grid_prefix.attach(self.entry_default_prefix, 0, 1, 3, 1)
|
|
self.entry_default_prefix.set_hexpand(True)
|
|
grid_prefix.attach(self.button_search_prefix, 3, 1, 1, 1)
|
|
|
|
grid_runner.attach(self.label_runner, 0, 6, 1, 1)
|
|
grid_runner.attach(self.combobox_runner, 0, 7, 1, 1)
|
|
grid_runner.attach(self.button_proton_manager, 0, 8, 1, 1)
|
|
|
|
grid_lossless.attach(self.label_lossless, 0, 0, 1, 1)
|
|
grid_lossless.attach(self.entry_lossless, 0, 1, 3, 1)
|
|
self.entry_default_prefix.set_hexpand(True)
|
|
grid_lossless.attach(self.button_search_lossless, 3, 1, 1, 1)
|
|
|
|
self.combobox_runner.set_hexpand(True)
|
|
self.button_proton_manager.set_hexpand(True)
|
|
self.entry_lossless.set_hexpand(True)
|
|
|
|
box_buttons.pack_start(self.button_winetricks_default, True, True, 0)
|
|
box_buttons.pack_start(self.button_winecfg_default, True, True, 0)
|
|
box_buttons.pack_start(self.button_run_default, True, True, 0)
|
|
|
|
grid_tools.attach(self.checkbox_mangohud, 0, 0, 1, 1)
|
|
self.checkbox_mangohud.set_hexpand(True)
|
|
grid_tools.attach(self.checkbox_gamemode, 0, 1, 1, 1)
|
|
grid_tools.attach(self.checkbox_prevent_sleep, 0, 2, 1, 1)
|
|
grid_tools.attach(self.checkbox_disable_hidraw, 0, 3, 1, 1)
|
|
grid_tools.attach(box_buttons, 2, 0, 1, 4)
|
|
|
|
grid_logs.attach(self.checkbox_enable_logging, 0, 0, 1, 1)
|
|
grid_logs.attach(self.button_clearlogs, 0, 1, 1, 1)
|
|
self.button_clearlogs.set_hexpand(True)
|
|
|
|
grid_miscellaneous.attach(self.checkbox_discrete_gpu, 0, 2, 1, 1)
|
|
grid_miscellaneous.attach(self.checkbox_splash_disable, 0, 3, 1, 1)
|
|
grid_miscellaneous.attach(self.checkbox_system_tray, 0, 4, 1, 1)
|
|
grid_miscellaneous.attach(self.checkbox_start_boot, 0, 5, 1, 1)
|
|
grid_miscellaneous.attach(self.checkbox_mono_icon, 0, 6, 1, 1)
|
|
grid_miscellaneous.attach(self.checkbox_close_after_launch, 0, 7, 1, 1)
|
|
grid_miscellaneous.attach(self.checkbox_show_hidden, 0, 8, 1, 1)
|
|
grid_miscellaneous.attach(self.checkbox_wayland_driver, 0, 9, 1, 1)
|
|
grid_miscellaneous.attach(self.checkbox_enable_hdr, 0, 10, 1, 1)
|
|
grid_miscellaneous.attach(self.checkbox_enable_wow64, 0, 11, 1, 1)
|
|
|
|
grid_interface_mode.attach(self.label_interface, 0, 0, 1, 1)
|
|
grid_interface_mode.attach(self.combobox_interface, 0, 1, 1, 1)
|
|
self.combobox_interface.set_hexpand(True)
|
|
|
|
grid_envar.attach(self.label_envar, 0, 0, 1, 1)
|
|
grid_envar.attach(scrolled_window, 0, 1, 1, 1)
|
|
scrolled_window.set_hexpand(True)
|
|
|
|
grid_backup.attach(button_backup, 0, 1, 1, 1)
|
|
grid_backup.attach(button_restore, 1, 1, 1, 1)
|
|
|
|
self.grid_big_interface.attach(self.checkbox_start_maximized, 0, 0, 1, 1)
|
|
self.grid_big_interface.attach(self.checkbox_start_fullscreen, 0, 1, 1, 1)
|
|
self.grid_big_interface.attach(self.checkbox_show_labels, 0, 2, 1, 1)
|
|
self.grid_big_interface.attach(self.checkbox_smaller_banners, 0, 3, 1, 1)
|
|
|
|
grid_support.attach(button_kofi, 0, 1, 1, 1)
|
|
grid_support.attach(button_paypal, 1, 1, 1, 1)
|
|
|
|
box_left.pack_start(grid_prefix, False, False, 0)
|
|
box_left.pack_start(grid_runner, False, False, 0)
|
|
box_left.pack_start(self.label_default_prefix_tools, False, False, 0)
|
|
box_left.pack_start(grid_tools, False, False, 0)
|
|
box_left.pack_start(grid_lossless, False, False, 0)
|
|
box_left.pack_end(grid_language, False, False, 0)
|
|
|
|
box_mid.pack_start(self.label_miscellaneous, False, False, 0)
|
|
box_mid.pack_start(grid_miscellaneous, False, False, 0)
|
|
box_mid.pack_start(grid_logs, False, False, 0)
|
|
box_mid.pack_end(grid_support, False, False, 0)
|
|
box_mid.pack_end(self.label_support, False, False, 0)
|
|
|
|
box_right.pack_start(grid_envar, False, False, 0)
|
|
box_right.pack_start(grid_interface_mode, False, False, 0)
|
|
box_right.pack_start(self.grid_big_interface, False, False, 0)
|
|
box_right.pack_end(grid_backup, False, False, 0)
|
|
box_right.pack_end(self.label_settings, False, False, 0)
|
|
|
|
box_main.attach(box_left, 0, 0, 1, 1)
|
|
box_main.attach(box_mid, 1, 0, 1, 1)
|
|
box_main.attach(box_right, 2, 0, 1, 1)
|
|
box_left.set_hexpand(True)
|
|
box_mid.set_hexpand(True)
|
|
frame.add(box_main)
|
|
|
|
box_bottom = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
|
box_bottom.set_margin_start(10)
|
|
box_bottom.set_margin_end(10)
|
|
box_bottom.set_margin_bottom(10)
|
|
self.button_cancel.set_hexpand(True)
|
|
self.button_ok.set_hexpand(True)
|
|
|
|
box_bottom.pack_start(self.button_cancel, True, True, 0)
|
|
box_bottom.pack_start(self.button_ok, True, True, 0)
|
|
|
|
self.box.add(frame)
|
|
self.box.add(box_bottom)
|
|
|
|
self.populate_combobox_with_runners()
|
|
self.populate_languages()
|
|
self.load_config()
|
|
|
|
self.show_all()
|
|
self.on_combobox_interface_changed(self.combobox_interface)
|
|
|
|
# Check if optional features are available and enable/disable accordingly
|
|
self.mangohud_enabled = os.path.exists(mangohud_dir)
|
|
if not self.mangohud_enabled:
|
|
self.checkbox_mangohud.set_sensitive(False)
|
|
self.checkbox_mangohud.set_active(False)
|
|
self.checkbox_mangohud.set_tooltip_text(
|
|
_("Shows an overlay for monitoring FPS, temperatures, CPU/GPU load and more. NOT INSTALLED."))
|
|
|
|
self.gamemode_enabled = os.path.exists(gamemoderun) or os.path.exists("/usr/games/gamemoderun")
|
|
if not self.gamemode_enabled:
|
|
self.checkbox_gamemode.set_sensitive(False)
|
|
self.checkbox_gamemode.set_active(False)
|
|
self.checkbox_gamemode.set_tooltip_text(_("Tweaks your system to improve performance. NOT INSTALLED."))
|
|
|
|
def on_envar_key_press(self, widget, event):
|
|
if event.keyval == Gdk.KEY_Delete:
|
|
selection = widget.get_selection()
|
|
model, treeiter = selection.get_selected()
|
|
|
|
if not treeiter:
|
|
return False
|
|
|
|
path = model.get_path(treeiter)
|
|
index = path.get_indices()[0]
|
|
|
|
if index == len(model) - 1:
|
|
return True
|
|
|
|
model.remove(treeiter)
|
|
return True
|
|
|
|
return False
|
|
|
|
def get_dir_size(self, path):
|
|
total = 0
|
|
for dirpath, _, filenames in os.walk(path):
|
|
for f in filenames:
|
|
fp = os.path.join(dirpath, f)
|
|
if os.path.isfile(fp):
|
|
total += os.path.getsize(fp)
|
|
for unit in ['B', 'KB', 'MB', 'GB']:
|
|
if total < 1024.0:
|
|
return f"{total:.1f} {unit}"
|
|
total /= 1024.0
|
|
return f"{total:.1f} TB"
|
|
|
|
def update_button_label(self):
|
|
if os.path.exists(logs_dir):
|
|
size = self.get_dir_size(logs_dir)
|
|
self.button_clearlogs.set_label(_("Clear Logs (%s)") % size)
|
|
self.button_clearlogs.set_sensitive(True)
|
|
else:
|
|
self.button_clearlogs.set_label(_("Clear Logs"))
|
|
self.button_clearlogs.set_sensitive(False)
|
|
|
|
def on_clear_logs_clicked(self, button):
|
|
if os.path.exists(logs_dir):
|
|
shutil.rmtree(logs_dir)
|
|
self.update_button_label()
|
|
|
|
def on_query_tooltip(self, widget, x, y, keyboard_mode, tooltip):
|
|
result = widget.get_path_at_pos(x, y)
|
|
if result is not None:
|
|
path, column, cell_x, cell_y = result
|
|
tree_iter = self.liststore.get_iter(path)
|
|
value = self.liststore.get_value(tree_iter, 0)
|
|
if value.strip():
|
|
tooltip.set_text(value)
|
|
return True
|
|
return False
|
|
|
|
def on_cell_edited(self, widget, path, text, column_index):
|
|
self.liststore[path][column_index] = text
|
|
self.adjust_rows()
|
|
|
|
def adjust_rows(self):
|
|
filled_rows = [row[0] for row in self.liststore if row[0].strip() != ""]
|
|
self.liststore.clear()
|
|
|
|
for value in filled_rows:
|
|
self.liststore.append([value])
|
|
|
|
self.liststore.append([""])
|
|
|
|
def populate_languages(self):
|
|
self.combobox_language.remove_all()
|
|
self.combobox_language.append_text("English")
|
|
|
|
if not os.path.isdir(LOCALE_DIR):
|
|
return
|
|
|
|
for lang in sorted(os.listdir(LOCALE_DIR)):
|
|
mo_file = os.path.join(LOCALE_DIR, lang, "LC_MESSAGES", "faugus-launcher.mo")
|
|
if os.path.isfile(mo_file):
|
|
lang_name = self.LANG_NAMES.get(lang, lang)
|
|
self.combobox_language.append_text(lang_name)
|
|
self.lang_codes[lang_name] = lang
|
|
|
|
self.combobox_language.set_active(0)
|
|
|
|
def on_entry_query_tooltip(self, widget, x, y, keyboard_mode, tooltip):
|
|
current_text = widget.get_text()
|
|
if current_text.strip():
|
|
tooltip.set_text(current_text)
|
|
else:
|
|
tooltip.set_text(widget.get_tooltip_text())
|
|
return True
|
|
|
|
def on_checkbox_toggled(self, checkbox, option):
|
|
if checkbox.get_active():
|
|
if option == "maximized":
|
|
self.checkbox_start_fullscreen.set_active(False)
|
|
elif option == "fullscreen":
|
|
self.checkbox_start_maximized.set_active(False)
|
|
|
|
def on_combobox_interface_changed(self, combobox):
|
|
active_index = combobox.get_active()
|
|
if active_index == 0:
|
|
self.grid_big_interface.set_visible(False)
|
|
if active_index == 1:
|
|
self.grid_big_interface.set_visible(True)
|
|
self.checkbox_show_labels.set_visible(False)
|
|
self.checkbox_smaller_banners.set_visible(False)
|
|
if active_index == 2:
|
|
self.grid_big_interface.set_visible(True)
|
|
self.checkbox_show_labels.set_visible(True)
|
|
self.checkbox_smaller_banners.set_visible(True)
|
|
|
|
def on_checkbox_system_tray_toggled(self, widget):
|
|
if not widget.get_active():
|
|
self.checkbox_start_boot.set_active(False)
|
|
self.checkbox_start_boot.set_sensitive(False)
|
|
self.checkbox_mono_icon.set_active(False)
|
|
self.checkbox_mono_icon.set_sensitive(False)
|
|
else:
|
|
self.checkbox_start_boot.set_sensitive(True)
|
|
self.checkbox_mono_icon.set_sensitive(True)
|
|
|
|
def on_checkbox_wayland_driver_toggled(self, widget):
|
|
if not widget.get_active():
|
|
self.checkbox_enable_hdr.set_active(False)
|
|
self.checkbox_enable_hdr.set_sensitive(False)
|
|
else:
|
|
self.checkbox_enable_hdr.set_sensitive(True)
|
|
|
|
def populate_combobox_with_runners(self):
|
|
# List of default entries
|
|
self.combobox_runner.append_text("GE-Proton Latest (default)")
|
|
self.combobox_runner.append_text("UMU-Proton Latest")
|
|
self.combobox_runner.append_text("Proton-EM Latest")
|
|
if os.path.exists("/usr/share/steam/compatibilitytools.d/proton-cachyos-slr/"):
|
|
self.combobox_runner.append_text("Proton-CachyOS")
|
|
|
|
# Path to the directory containing the folders
|
|
if IS_FLATPAK:
|
|
runner_path = Path(os.path.expanduser("~/.local/share/Steam/compatibilitytools.d"))
|
|
else:
|
|
runner_path = f'{share_dir}/Steam/compatibilitytools.d/'
|
|
|
|
try:
|
|
# Check if the directory exists
|
|
if os.path.exists(runner_path):
|
|
# List to hold version directories
|
|
versions = []
|
|
# Iterate over the folders in the directory
|
|
for entry in os.listdir(runner_path):
|
|
entry_path = os.path.join(runner_path, entry)
|
|
# Add to list only if it's a directory and not "UMU-Latest"
|
|
if (
|
|
os.path.isdir(entry_path)
|
|
and entry not in ("UMU-Latest", "LegacyRuntime")
|
|
and not entry.startswith("Proton-GE Latest")
|
|
and not entry.startswith("Proton-EM Latest")
|
|
):
|
|
versions.append(entry)
|
|
|
|
# Sort versions in descending order
|
|
def version_key(v):
|
|
# Remove 'GE-Proton' and split the remaining part into segments of digits and non-digits
|
|
v_parts = re.split(r'(\d+)', v.replace('GE-Proton', ''))
|
|
# Convert numeric parts to integers for proper sorting
|
|
return [int(part) if part.isdigit() else part for part in v_parts]
|
|
|
|
versions.sort(key=version_key, reverse=True)
|
|
|
|
# Add sorted versions to ComboBox
|
|
for version in versions:
|
|
self.combobox_runner.append_text(version)
|
|
|
|
except Exception as e:
|
|
print(f"Error accessing the directory: {e}")
|
|
|
|
# Set the active item, if desired
|
|
self.combobox_runner.set_active(0)
|
|
|
|
cell_renderer = self.combobox_runner.get_cells()[0]
|
|
cell_renderer.set_property("ellipsize", Pango.EllipsizeMode.END)
|
|
cell_renderer.set_property("max-width-chars", 20)
|
|
|
|
def on_entry_changed(self, widget, entry):
|
|
if entry.get_text():
|
|
entry.get_style_context().remove_class("entry")
|
|
|
|
def update_config_file(self):
|
|
combobox_language = self.combobox_language.get_active_text()
|
|
entry_default_prefix = self.entry_default_prefix.get_text()
|
|
combobox_default_runner = self.get_default_runner()
|
|
entry_lossless = self.entry_lossless.get_text()
|
|
checkbox_mangohud = self.checkbox_mangohud.get_active()
|
|
checkbox_gamemode = self.checkbox_gamemode.get_active()
|
|
checkbox_disable_hidraw = self.checkbox_disable_hidraw.get_active()
|
|
checkbox_prevent_sleep = self.checkbox_prevent_sleep.get_active()
|
|
checkbox_discrete_gpu_state = self.checkbox_discrete_gpu.get_active()
|
|
checkbox_splash_disable = self.checkbox_splash_disable.get_active()
|
|
checkbox_system_tray = self.checkbox_system_tray.get_active()
|
|
checkbox_start_boot = self.checkbox_start_boot.get_active()
|
|
checkbox_mono_icon = self.checkbox_mono_icon.get_active()
|
|
checkbox_close_after_launcher = self.checkbox_close_after_launch.get_active()
|
|
checkbox_enable_logging = self.checkbox_enable_logging.get_active()
|
|
checkbox_show_hidden = self.checkbox_show_hidden.get_active()
|
|
checkbox_wayland_driver = self.checkbox_wayland_driver.get_active()
|
|
checkbox_enable_hdr = self.checkbox_enable_hdr.get_active()
|
|
checkbox_enable_wow64 = self.checkbox_enable_wow64.get_active()
|
|
combobox_interface = self.combobox_interface.get_active_text()
|
|
checkbox_start_maximized = self.checkbox_start_maximized.get_active()
|
|
checkbox_start_fullscreen = self.checkbox_start_fullscreen.get_active()
|
|
checkbox_show_labels = self.checkbox_show_labels.get_active()
|
|
checkbox_smaller_banners = self.checkbox_smaller_banners.get_active()
|
|
|
|
language = self.lang_codes.get(combobox_language, "en_US")
|
|
logging_warning = self.logging_warning
|
|
|
|
config = ConfigManager()
|
|
config.save_with_values(
|
|
checkbox_close_after_launcher,
|
|
entry_default_prefix,
|
|
checkbox_mangohud,
|
|
checkbox_gamemode,
|
|
checkbox_disable_hidraw,
|
|
checkbox_prevent_sleep,
|
|
combobox_default_runner,
|
|
entry_lossless,
|
|
checkbox_discrete_gpu_state,
|
|
checkbox_splash_disable,
|
|
checkbox_system_tray,
|
|
checkbox_start_boot,
|
|
checkbox_mono_icon,
|
|
combobox_interface,
|
|
checkbox_start_maximized,
|
|
checkbox_start_fullscreen,
|
|
checkbox_show_labels,
|
|
checkbox_smaller_banners,
|
|
checkbox_enable_logging,
|
|
checkbox_wayland_driver,
|
|
checkbox_enable_hdr,
|
|
checkbox_enable_wow64,
|
|
language,
|
|
logging_warning,
|
|
checkbox_show_hidden
|
|
)
|
|
|
|
self.set_sensitive(False)
|
|
|
|
def get_default_runner(self):
|
|
default_runner = self.combobox_runner.get_active_text()
|
|
default_runner = convert_runner(default_runner)
|
|
return default_runner
|
|
|
|
def update_envar_file(self):
|
|
if hasattr(self, "liststore"):
|
|
values = [row[0] for row in self.liststore if row[0].strip() != ""]
|
|
with open(envar_dir, "w", encoding="utf-8") as f:
|
|
for val in values:
|
|
f.write(val + "\n")
|
|
|
|
def on_button_proton_manager_clicked(self, widget):
|
|
if self.entry_default_prefix.get_text() == "":
|
|
self.entry_default_prefix.get_style_context().add_class("entry")
|
|
else:
|
|
self.update_envar_file()
|
|
self.update_config_file()
|
|
proton_manager = faugus_proton_manager
|
|
|
|
def run_command():
|
|
process = subprocess.Popen([sys.executable, proton_manager])
|
|
process.wait()
|
|
GLib.idle_add(self.set_sensitive, True)
|
|
GLib.idle_add(self.parent.set_sensitive, True)
|
|
GLib.idle_add(self.blocking_window.destroy)
|
|
|
|
GLib.idle_add(lambda: self.combobox_runner.remove_all())
|
|
GLib.idle_add(self.populate_combobox_with_runners)
|
|
GLib.idle_add(lambda: self.load_config())
|
|
|
|
self.blocking_window = Gtk.Window()
|
|
self.blocking_window.set_transient_for(self.parent)
|
|
self.blocking_window.set_decorated(False)
|
|
self.blocking_window.set_modal(True)
|
|
|
|
command_thread = threading.Thread(target=run_command)
|
|
command_thread.start()
|
|
|
|
def on_button_winetricks_default_clicked(self, widget):
|
|
if self.entry_default_prefix.get_text() == "":
|
|
self.entry_default_prefix.get_style_context().add_class("entry")
|
|
else:
|
|
self.update_envar_file()
|
|
self.update_config_file()
|
|
self.parent.manage_autostart_file(self.checkbox_start_boot.get_active())
|
|
default_runner = self.get_default_runner()
|
|
command_parts = []
|
|
|
|
# Add command parts if they are not empty
|
|
command_parts.append(f"GAMEID=winetricks-gui")
|
|
command_parts.append(f"STORE=none")
|
|
if default_runner:
|
|
if default_runner == "Proton-CachyOS":
|
|
command_parts.append(f"PROTONPATH='{proton_cachyos}'")
|
|
else:
|
|
command_parts.append(f"PROTONPATH='{default_runner}'")
|
|
|
|
# Add the fixed command and remaining arguments
|
|
command_parts.append(f"'{umu_run}'")
|
|
command_parts.append("''")
|
|
|
|
# Join all parts into a single command
|
|
command = ' '.join(command_parts)
|
|
|
|
print(command)
|
|
|
|
# faugus-run path
|
|
faugus_run_path = faugus_run
|
|
|
|
def run_command():
|
|
process = subprocess.Popen([sys.executable, faugus_run_path, command, "winetricks"])
|
|
process.wait()
|
|
GLib.idle_add(self.set_sensitive, True)
|
|
GLib.idle_add(self.parent.set_sensitive, True)
|
|
GLib.idle_add(self.blocking_window.destroy)
|
|
|
|
self.blocking_window = Gtk.Window()
|
|
self.blocking_window.set_transient_for(self.parent)
|
|
self.blocking_window.set_decorated(False)
|
|
self.blocking_window.set_modal(True)
|
|
|
|
command_thread = threading.Thread(target=run_command)
|
|
command_thread.start()
|
|
|
|
def on_button_winecfg_default_clicked(self, widget):
|
|
if self.entry_default_prefix.get_text() == "":
|
|
self.entry_default_prefix.get_style_context().add_class("entry")
|
|
else:
|
|
self.update_envar_file()
|
|
self.update_config_file()
|
|
self.parent.manage_autostart_file(self.checkbox_start_boot.get_active())
|
|
default_runner = self.get_default_runner()
|
|
command_parts = []
|
|
|
|
# Add command parts if they are not empty
|
|
command_parts.append(f"GAMEID=default")
|
|
if default_runner:
|
|
if default_runner == "Proton-CachyOS":
|
|
command_parts.append(f"PROTONPATH='{proton_cachyos}'")
|
|
else:
|
|
command_parts.append(f"PROTONPATH='{default_runner}'")
|
|
|
|
# Add the fixed command and remaining arguments
|
|
command_parts.append(f"'{umu_run}'")
|
|
command_parts.append("'winecfg'")
|
|
|
|
# Join all parts into a single command
|
|
command = ' '.join(command_parts)
|
|
|
|
print(command)
|
|
|
|
# faugus-run path
|
|
faugus_run_path = faugus_run
|
|
|
|
def run_command():
|
|
process = subprocess.Popen([sys.executable, faugus_run_path, command])
|
|
process.wait()
|
|
GLib.idle_add(self.set_sensitive, True)
|
|
GLib.idle_add(self.parent.set_sensitive, True)
|
|
GLib.idle_add(self.blocking_window.destroy)
|
|
|
|
self.blocking_window = Gtk.Window()
|
|
self.blocking_window.set_transient_for(self.parent)
|
|
self.blocking_window.set_decorated(False)
|
|
self.blocking_window.set_modal(True)
|
|
|
|
command_thread = threading.Thread(target=run_command)
|
|
command_thread.start()
|
|
|
|
def on_button_run_default_clicked(self, widget):
|
|
if self.entry_default_prefix.get_text() == "":
|
|
self.entry_default_prefix.get_style_context().add_class("entry")
|
|
return
|
|
|
|
self.update_envar_file()
|
|
self.update_config_file()
|
|
self.parent.manage_autostart_file(self.checkbox_start_boot.get_active())
|
|
default_runner = self.get_default_runner()
|
|
|
|
filechooser = Gtk.FileChooserNative(
|
|
title=_("Select a file to run inside the prefix"),
|
|
transient_for=self,
|
|
action=Gtk.FileChooserAction.OPEN,
|
|
accept_label=_("Open"),
|
|
cancel_label=_("Cancel"),
|
|
)
|
|
|
|
windows_filter = Gtk.FileFilter()
|
|
windows_filter.set_name(_("Windows files"))
|
|
windows_filter.add_pattern("*.exe")
|
|
windows_filter.add_pattern("*.msi")
|
|
windows_filter.add_pattern("*.bat")
|
|
windows_filter.add_pattern("*.lnk")
|
|
windows_filter.add_pattern("*.reg")
|
|
|
|
all_files_filter = Gtk.FileFilter()
|
|
all_files_filter.set_name(_("All files"))
|
|
all_files_filter.add_pattern("*")
|
|
|
|
filechooser.add_filter(windows_filter)
|
|
filechooser.add_filter(all_files_filter)
|
|
filechooser.set_filter(windows_filter)
|
|
|
|
response = filechooser.run()
|
|
|
|
if response == Gtk.ResponseType.ACCEPT:
|
|
command_parts = []
|
|
file_run = filechooser.get_filename()
|
|
escaped_file_run = file_run.replace("'", "'\\''")
|
|
if not escaped_file_run.endswith(".reg"):
|
|
if escaped_file_run:
|
|
command_parts.append(f"GAMEID=default")
|
|
if default_runner:
|
|
if default_runner == "Proton-CachyOS":
|
|
command_parts.append(f"PROTONPATH='{proton_cachyos}'")
|
|
else:
|
|
command_parts.append(f"PROTONPATH='{default_runner}'")
|
|
command_parts.append(f"'{umu_run}' '{escaped_file_run}'")
|
|
else:
|
|
if escaped_file_run:
|
|
command_parts.append(f"GAMEID=default")
|
|
if default_runner:
|
|
if default_runner == "Proton-CachyOS":
|
|
command_parts.append(f"PROTONPATH='{proton_cachyos}'")
|
|
else:
|
|
command_parts.append(f"PROTONPATH='{default_runner}'")
|
|
command_parts.append(f"'{umu_run}' regedit '{escaped_file_run}'")
|
|
|
|
command = ' '.join(command_parts)
|
|
print(command)
|
|
|
|
faugus_run_path = faugus_run
|
|
|
|
def run_command():
|
|
process = subprocess.Popen([sys.executable, faugus_run_path, command])
|
|
process.wait()
|
|
GLib.idle_add(self.set_sensitive, True)
|
|
GLib.idle_add(self.parent.set_sensitive, True)
|
|
GLib.idle_add(self.blocking_window.destroy)
|
|
|
|
self.blocking_window = Gtk.Window()
|
|
self.blocking_window.set_transient_for(self.parent)
|
|
self.blocking_window.set_decorated(False)
|
|
self.blocking_window.set_modal(True)
|
|
|
|
command_thread = threading.Thread(target=run_command)
|
|
command_thread.start()
|
|
|
|
else:
|
|
self.set_sensitive(True)
|
|
|
|
filechooser.destroy()
|
|
|
|
def on_button_backup_clicked(self, widget):
|
|
self.response(Gtk.ResponseType.OK)
|
|
self.show_warning_dialog(self, _("Prefixes and runners will not be backed up!"))
|
|
|
|
items = ["banners", "games-backup", "icons", "config.ini", "envar.txt", "games.json", "latest-games.txt"]
|
|
|
|
temp_dir = os.path.join(faugus_launcher_dir, "temp-backup")
|
|
os.makedirs(temp_dir, exist_ok=True)
|
|
|
|
for item in items:
|
|
src = os.path.join(faugus_launcher_dir, item)
|
|
dst = os.path.join(temp_dir, item)
|
|
if os.path.isdir(src):
|
|
shutil.copytree(src, dst, dirs_exist_ok=True)
|
|
elif os.path.isfile(src):
|
|
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
|
shutil.copy2(src, dst)
|
|
|
|
marker_path = os.path.join(temp_dir, ".faugus_marker")
|
|
with open(marker_path, "w") as f:
|
|
f.write("faugus-launcher-backup")
|
|
|
|
current_date = os.popen("date +%Y-%m-%d").read().strip()
|
|
zip_filename = f"faugus-launcher-{current_date}"
|
|
zip_path = os.path.join(faugus_launcher_dir, zip_filename)
|
|
|
|
shutil.make_archive(zip_path, "zip", temp_dir)
|
|
shutil.rmtree(temp_dir)
|
|
|
|
filechooser = Gtk.FileChooserNative(
|
|
title=_("Save the backup file as..."),
|
|
transient_for=self,
|
|
action=Gtk.FileChooserAction.SAVE,
|
|
accept_label=_("Save"),
|
|
cancel_label=_("Cancel"),
|
|
)
|
|
|
|
filechooser.set_current_folder(os.path.expanduser("~"))
|
|
filechooser.set_current_name(f"{zip_filename}.zip")
|
|
|
|
response = filechooser.run()
|
|
|
|
if response == Gtk.ResponseType.ACCEPT:
|
|
dest = filechooser.get_filename()
|
|
|
|
if not dest.endswith(".zip"):
|
|
dest += ".zip"
|
|
|
|
try:
|
|
if os.path.exists(dest):
|
|
os.remove(dest)
|
|
|
|
shutil.copy2(zip_path + ".zip", dest)
|
|
print(f"Backup saved at: {dest}")
|
|
except Exception as e:
|
|
print(f"Error saving backup: {e}")
|
|
|
|
filechooser.destroy()
|
|
|
|
os.remove(zip_path + ".zip")
|
|
|
|
def on_button_restore_clicked(self, widget):
|
|
filechooser = Gtk.FileChooserNative(
|
|
title=_("Select a backup file to restore"),
|
|
transient_for=self,
|
|
action=Gtk.FileChooserAction.OPEN,
|
|
accept_label=_("Open"),
|
|
cancel_label=_("Cancel"),
|
|
)
|
|
|
|
zip_filter = Gtk.FileFilter()
|
|
zip_filter.set_name(_("ZIP files"))
|
|
zip_filter.add_pattern("*.zip")
|
|
filechooser.add_filter(zip_filter)
|
|
filechooser.set_filter(zip_filter)
|
|
|
|
response = filechooser.run()
|
|
|
|
if response == Gtk.ResponseType.ACCEPT:
|
|
zip_file = filechooser.get_filename()
|
|
if not os.path.isfile(zip_file):
|
|
filechooser.destroy()
|
|
self.show_warning_dialog(self, _("This is not a valid Faugus Launcher backup file."))
|
|
return
|
|
|
|
temp_dir = os.path.join(faugus_launcher_dir, "temp-restore")
|
|
shutil.unpack_archive(zip_file, temp_dir, "zip")
|
|
|
|
marker_path = os.path.join(temp_dir, ".faugus_marker")
|
|
if not os.path.exists(marker_path):
|
|
shutil.rmtree(temp_dir)
|
|
filechooser.destroy()
|
|
self.show_warning_dialog(self, _("This is not a valid Faugus Launcher backup file."))
|
|
return
|
|
|
|
if self.show_warning_dialog2(self, _("Are you sure you want to overwrite the settings?")):
|
|
for item in os.listdir(temp_dir):
|
|
if item == ".faugus_marker":
|
|
continue
|
|
src = os.path.join(temp_dir, item)
|
|
dst = os.path.join(faugus_launcher_dir, item)
|
|
|
|
if os.path.isdir(dst):
|
|
shutil.rmtree(dst)
|
|
elif os.path.isfile(dst):
|
|
os.remove(dst)
|
|
|
|
if os.path.isdir(src):
|
|
shutil.copytree(src, dst)
|
|
elif os.path.isfile(src):
|
|
shutil.copy2(src, dst)
|
|
|
|
shutil.rmtree(temp_dir)
|
|
global faugus_backup
|
|
faugus_backup = True
|
|
self.response(Gtk.ResponseType.OK)
|
|
|
|
filechooser.destroy()
|
|
|
|
def show_warning_dialog(self, parent, title):
|
|
dialog = Gtk.Dialog(title="Faugus Launcher", transient_for=parent, modal=True)
|
|
dialog.set_resizable(False)
|
|
dialog.set_icon_from_file(faugus_png)
|
|
subprocess.Popen(["canberra-gtk-play", "-f", faugus_notification])
|
|
|
|
label = Gtk.Label()
|
|
label.set_label(title)
|
|
label.set_halign(Gtk.Align.CENTER)
|
|
|
|
button_yes = Gtk.Button(label=_("Ok"))
|
|
button_yes.set_size_request(150, -1)
|
|
button_yes.connect("clicked", lambda x: dialog.destroy())
|
|
|
|
content_area = dialog.get_content_area()
|
|
content_area.set_border_width(0)
|
|
content_area.set_halign(Gtk.Align.CENTER)
|
|
content_area.set_valign(Gtk.Align.CENTER)
|
|
content_area.set_vexpand(True)
|
|
content_area.set_hexpand(True)
|
|
|
|
box_top = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
|
box_top.set_margin_start(20)
|
|
box_top.set_margin_end(20)
|
|
box_top.set_margin_top(20)
|
|
box_top.set_margin_bottom(20)
|
|
|
|
box_bottom = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
|
box_bottom.set_margin_start(10)
|
|
box_bottom.set_margin_end(10)
|
|
box_bottom.set_margin_bottom(10)
|
|
|
|
box_top.pack_start(label, True, True, 0)
|
|
box_bottom.pack_start(button_yes, True, True, 0)
|
|
|
|
content_area.add(box_top)
|
|
content_area.add(box_bottom)
|
|
|
|
dialog.show_all()
|
|
dialog.run()
|
|
dialog.destroy()
|
|
|
|
def show_warning_dialog2(self, parent, title):
|
|
dialog = Gtk.Dialog(title="Faugus Launcher", transient_for=parent, modal=True)
|
|
dialog.set_resizable(False)
|
|
dialog.set_icon_from_file(faugus_png)
|
|
subprocess.Popen(["canberra-gtk-play", "-f", faugus_notification])
|
|
|
|
label = Gtk.Label()
|
|
label.set_label(title)
|
|
label.set_halign(Gtk.Align.CENTER)
|
|
|
|
button_no = Gtk.Button(label=_("No"))
|
|
button_no.set_size_request(150, -1)
|
|
button_no.connect("clicked", lambda x: dialog.response(Gtk.ResponseType.CANCEL))
|
|
|
|
button_yes = Gtk.Button(label=_("Yes"))
|
|
button_yes.set_size_request(150, -1)
|
|
button_yes.connect("clicked", lambda x: dialog.response(Gtk.ResponseType.OK))
|
|
|
|
content_area = dialog.get_content_area()
|
|
content_area.set_border_width(0)
|
|
content_area.set_halign(Gtk.Align.CENTER)
|
|
content_area.set_valign(Gtk.Align.CENTER)
|
|
content_area.set_vexpand(True)
|
|
content_area.set_hexpand(True)
|
|
|
|
box_top = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
|
box_top.set_margin_start(20)
|
|
box_top.set_margin_end(20)
|
|
box_top.set_margin_top(20)
|
|
box_top.set_margin_bottom(20)
|
|
|
|
box_bottom = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
|
box_bottom.set_margin_start(10)
|
|
box_bottom.set_margin_end(10)
|
|
box_bottom.set_margin_bottom(10)
|
|
|
|
box_top.pack_start(label, True, True, 0)
|
|
box_bottom.pack_start(button_no, True, True, 0)
|
|
box_bottom.pack_start(button_yes, True, True, 0)
|
|
|
|
content_area.add(box_top)
|
|
content_area.add(box_bottom)
|
|
|
|
dialog.show_all()
|
|
response = dialog.run()
|
|
dialog.destroy()
|
|
|
|
return response == Gtk.ResponseType.OK
|
|
|
|
def on_button_kofi_clicked(self, widget):
|
|
webbrowser.open("https://ko-fi.com/K3K210EMDU")
|
|
|
|
def on_button_paypal_clicked(self, widget):
|
|
webbrowser.open("https://www.paypal.com/donate/?business=57PP9DVD3VWAN&no_recurring=0¤cy_code=USD")
|
|
|
|
def on_button_search_prefix_clicked(self, widget):
|
|
filechooser = Gtk.FileChooserNative(
|
|
title=_("Select a prefix location"),
|
|
transient_for=self,
|
|
action=Gtk.FileChooserAction.SELECT_FOLDER,
|
|
accept_label=_("Open"),
|
|
cancel_label=_("Cancel"),
|
|
)
|
|
|
|
if os.path.isdir(self.default_prefix):
|
|
filechooser.set_current_folder(self.default_prefix)
|
|
else:
|
|
filechooser.set_current_folder(os.path.expanduser("~"))
|
|
|
|
response = filechooser.run()
|
|
|
|
if response == Gtk.ResponseType.ACCEPT:
|
|
folder = filechooser.get_filename()
|
|
if folder:
|
|
self.entry_default_prefix.set_text(folder)
|
|
|
|
filechooser.destroy()
|
|
|
|
def on_button_search_lossless_clicked(self, widget):
|
|
filechooser = Gtk.FileChooserNative(
|
|
title=_("Select the Lossless.dll file"),
|
|
transient_for=self,
|
|
action=Gtk.FileChooserAction.OPEN,
|
|
accept_label=_("Open"),
|
|
cancel_label=_("Cancel"),
|
|
)
|
|
|
|
filter_dll = Gtk.FileFilter()
|
|
filter_dll.set_name("Lossless.dll")
|
|
filter_dll.add_pattern("Lossless.dll")
|
|
filechooser.add_filter(filter_dll)
|
|
filechooser.set_filter(filter_dll)
|
|
|
|
response = filechooser.run()
|
|
|
|
if response == Gtk.ResponseType.ACCEPT:
|
|
selected_file = filechooser.get_filename()
|
|
if selected_file and os.path.basename(selected_file) == "Lossless.dll":
|
|
self.entry_lossless.set_text(selected_file)
|
|
|
|
filechooser.destroy()
|
|
|
|
def load_config(self):
|
|
cfg = ConfigManager()
|
|
|
|
close_on_launch = cfg.config.get('close-onlaunch', 'False') == 'True'
|
|
self.default_prefix = cfg.config.get('default-prefix', '').strip('"')
|
|
mangohud = cfg.config.get('mangohud', 'False') == 'True'
|
|
gamemode = cfg.config.get('gamemode', 'False') == 'True'
|
|
disable_hidraw = cfg.config.get('disable-hidraw', 'False') == 'True'
|
|
prevent_sleep = cfg.config.get('prevent-sleep', 'False') == 'True'
|
|
self.default_runner = cfg.config.get('default-runner', '').strip('"')
|
|
lossless_location = cfg.config.get('lossless-location', '').strip('"')
|
|
discrete_gpu = cfg.config.get('discrete-gpu', 'False') == 'True'
|
|
splash_disable = cfg.config.get('splash-disable', 'False') == 'True'
|
|
system_tray = cfg.config.get('system-tray', 'False') == 'True'
|
|
self.start_boot = cfg.config.get('start-boot', 'False') == 'True'
|
|
self.mono_icon = cfg.config.get('mono-icon', 'False') == 'True'
|
|
start_maximized = cfg.config.get('start-maximized', 'False') == 'True'
|
|
self.interface_mode = cfg.config.get('interface-mode', '').strip('"')
|
|
start_fullscreen = cfg.config.get('start-fullscreen', 'False') == 'True'
|
|
show_labels = cfg.config.get('show-labels', 'False') == 'True'
|
|
smaller_banners = cfg.config.get('smaller-banners', 'False') == 'True'
|
|
enable_logging = cfg.config.get('enable-logging', 'False') == 'True'
|
|
show_hidden = cfg.config.get('show-hidden', 'False') == 'True'
|
|
wayland_driver = cfg.config.get('wayland-driver', 'False') == 'True'
|
|
enable_hdr = cfg.config.get('enable-hdr', 'False') == 'True'
|
|
enable_wow64 = cfg.config.get('enable-wow64', 'False') == 'True'
|
|
self.language = cfg.config.get('language', '')
|
|
self.logging_warning = cfg.config.get('logging-warning', 'False') == 'True'
|
|
|
|
self.checkbox_close_after_launch.set_active(close_on_launch)
|
|
self.entry_default_prefix.set_text(self.default_prefix)
|
|
|
|
self.checkbox_mangohud.set_active(mangohud)
|
|
self.checkbox_gamemode.set_active(gamemode)
|
|
self.checkbox_disable_hidraw.set_active(disable_hidraw)
|
|
self.checkbox_prevent_sleep.set_active(prevent_sleep)
|
|
|
|
lossless_dll_path = find_lossless_dll()
|
|
if not lossless_location:
|
|
if lossless_dll_path:
|
|
self.entry_lossless.set_text(str(lossless_dll_path))
|
|
else:
|
|
self.entry_lossless.set_text(lossless_location)
|
|
|
|
self.default_runner = convert_runner(self.default_runner)
|
|
model_runner = self.combobox_runner.get_model()
|
|
index_runner = 0
|
|
for i, row in enumerate(model_runner):
|
|
if row[0] == self.default_runner:
|
|
index_runner = i
|
|
break
|
|
|
|
self.combobox_runner.set_active(index_runner)
|
|
self.checkbox_discrete_gpu.set_active(discrete_gpu)
|
|
self.checkbox_splash_disable.set_active(splash_disable)
|
|
self.checkbox_system_tray.set_active(system_tray)
|
|
self.checkbox_start_boot.set_active(self.start_boot)
|
|
self.checkbox_mono_icon.set_active(self.mono_icon)
|
|
self.checkbox_start_maximized.set_active(start_maximized)
|
|
self.checkbox_start_fullscreen.set_active(start_fullscreen)
|
|
self.checkbox_show_labels.set_active(show_labels)
|
|
self.checkbox_smaller_banners.set_active(smaller_banners)
|
|
self.checkbox_enable_logging.set_active(enable_logging)
|
|
self.checkbox_show_hidden.set_active(show_hidden)
|
|
self.checkbox_wayland_driver.set_active(wayland_driver)
|
|
self.checkbox_enable_hdr.set_active(enable_hdr)
|
|
self.checkbox_enable_wow64.set_active(enable_wow64)
|
|
|
|
model_interface = self.combobox_interface.get_model()
|
|
index_interface = 0
|
|
for i, row in enumerate(model_interface):
|
|
if row[0] == self.interface_mode:
|
|
index_interface = i
|
|
break
|
|
|
|
self.combobox_interface.set_active(index_interface)
|
|
|
|
model_language = self.combobox_language.get_model()
|
|
index_language = 0
|
|
|
|
if self.language == "":
|
|
self.combobox_language.set_active(index_language)
|
|
else:
|
|
for i, row in enumerate(model_language):
|
|
lang_name = row[0]
|
|
lang_code = self.lang_codes.get(lang_name, "")
|
|
if lang_code == self.language:
|
|
index_language = i
|
|
break
|
|
|
|
self.combobox_language.set_active(index_language)
|
|
self.load_liststore_from_file(envar_dir)
|
|
|
|
def load_liststore_from_file(self, filename=envar_dir):
|
|
self.liststore.clear()
|
|
|
|
try:
|
|
with open(filename, "r", encoding="utf-8") as f:
|
|
lines = [line.strip() for line in f.readlines() if line.strip()]
|
|
except FileNotFoundError:
|
|
lines = []
|
|
|
|
for line in lines:
|
|
self.liststore.append([line])
|
|
|
|
self.liststore.append([""])
|
|
|
|
class Game:
|
|
def __init__(
|
|
self,
|
|
gameid,
|
|
title,
|
|
path,
|
|
prefix,
|
|
launch_arguments,
|
|
game_arguments,
|
|
mangohud,
|
|
gamemode,
|
|
disable_hidraw,
|
|
protonfix,
|
|
runner,
|
|
addapp_checkbox,
|
|
addapp,
|
|
addapp_bat,
|
|
banner,
|
|
lossless_enabled,
|
|
lossless_multiplier,
|
|
lossless_flow,
|
|
lossless_performance,
|
|
lossless_hdr,
|
|
lossless_present,
|
|
playtime,
|
|
hidden,
|
|
prevent_sleep,
|
|
favorite,
|
|
startup_wm_class="",
|
|
):
|
|
self.gameid = gameid
|
|
self.title = title
|
|
self.path = path
|
|
self.launch_arguments = launch_arguments
|
|
self.game_arguments = game_arguments
|
|
self.mangohud = mangohud
|
|
self.gamemode = gamemode
|
|
self.prefix = prefix
|
|
self.disable_hidraw = disable_hidraw
|
|
self.protonfix = protonfix
|
|
self.runner = runner
|
|
self.addapp_checkbox = addapp_checkbox
|
|
self.addapp = addapp
|
|
self.addapp_bat = addapp_bat
|
|
self.banner = banner
|
|
self.lossless_enabled = lossless_enabled
|
|
self.lossless_multiplier = lossless_multiplier
|
|
self.lossless_flow = lossless_flow
|
|
self.lossless_performance = lossless_performance
|
|
self.lossless_hdr = lossless_hdr
|
|
self.lossless_present = lossless_present
|
|
self.playtime = playtime
|
|
self.hidden = hidden
|
|
self.prevent_sleep = prevent_sleep
|
|
self.favorite = favorite
|
|
self.startup_wm_class = startup_wm_class
|
|
|
|
|
|
class DuplicateDialog(Gtk.Dialog):
|
|
def __init__(self, parent, title):
|
|
super().__init__(title=_("Duplicate %s") % title, transient_for=parent, modal=True)
|
|
self.set_resizable(False)
|
|
self.set_icon_from_file(faugus_png)
|
|
|
|
label_title = Gtk.Label(label=_("Title"))
|
|
label_title.set_halign(Gtk.Align.START)
|
|
self.entry_title = Gtk.Entry()
|
|
self.entry_title.set_tooltip_text(_("Game Title"))
|
|
self.entry_title.connect("changed", _validate_text)
|
|
|
|
button_cancel = Gtk.Button(label=_("Cancel"))
|
|
button_cancel.connect("clicked", lambda widget: self.response(Gtk.ResponseType.CANCEL))
|
|
button_cancel.set_size_request(150, -1)
|
|
|
|
button_ok = Gtk.Button(label=_("Ok"))
|
|
button_ok.connect("clicked", lambda widget: self.response(Gtk.ResponseType.OK))
|
|
button_ok.set_size_request(150, -1)
|
|
|
|
content_area = self.get_content_area()
|
|
content_area.set_border_width(0)
|
|
content_area.set_halign(Gtk.Align.CENTER)
|
|
content_area.set_valign(Gtk.Align.CENTER)
|
|
content_area.set_vexpand(True)
|
|
content_area.set_hexpand(True)
|
|
|
|
box_top = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
|
box_top.set_margin_start(10)
|
|
box_top.set_margin_end(10)
|
|
box_top.set_margin_top(10)
|
|
box_top.set_margin_bottom(20)
|
|
|
|
box_bottom = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
|
box_bottom.set_margin_start(10)
|
|
box_bottom.set_margin_end(10)
|
|
box_bottom.set_margin_bottom(10)
|
|
|
|
box_top.pack_start(label_title, True, True, 0)
|
|
box_top.pack_start(self.entry_title, True, True, 0)
|
|
|
|
box_bottom.pack_start(button_cancel, True, True, 0)
|
|
box_bottom.pack_start(button_ok, True, True, 0)
|
|
|
|
content_area.add(box_top)
|
|
content_area.add(box_bottom)
|
|
|
|
self.show_all()
|
|
|
|
def show_warning_dialog(self, parent, title):
|
|
dialog = Gtk.Dialog(title="Faugus Launcher", transient_for=parent, modal=True)
|
|
dialog.set_resizable(False)
|
|
dialog.set_icon_from_file(faugus_png)
|
|
subprocess.Popen(["canberra-gtk-play", "-f", faugus_notification])
|
|
|
|
label = Gtk.Label()
|
|
label.set_label(title)
|
|
label.set_halign(Gtk.Align.CENTER)
|
|
|
|
button_yes = Gtk.Button(label=_("Ok"))
|
|
button_yes.set_size_request(150, -1)
|
|
button_yes.connect("clicked", lambda x: dialog.destroy())
|
|
|
|
content_area = dialog.get_content_area()
|
|
content_area.set_border_width(0)
|
|
content_area.set_halign(Gtk.Align.CENTER)
|
|
content_area.set_valign(Gtk.Align.CENTER)
|
|
content_area.set_vexpand(True)
|
|
content_area.set_hexpand(True)
|
|
|
|
box_top = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
|
box_top.set_margin_start(20)
|
|
box_top.set_margin_end(20)
|
|
box_top.set_margin_top(20)
|
|
box_top.set_margin_bottom(20)
|
|
|
|
box_bottom = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
|
box_bottom.set_margin_start(10)
|
|
box_bottom.set_margin_end(10)
|
|
box_bottom.set_margin_bottom(10)
|
|
|
|
box_top.pack_start(label, True, True, 0)
|
|
box_bottom.pack_start(button_yes, True, True, 0)
|
|
|
|
content_area.add(box_top)
|
|
content_area.add(box_bottom)
|
|
|
|
dialog.show_all()
|
|
dialog.run()
|
|
dialog.destroy()
|
|
|
|
|
|
class ConfirmationDialog(Gtk.Dialog):
|
|
def __init__(self, parent, title, prefix, runner):
|
|
super().__init__(title=_("Delete %s") % title, transient_for=parent, modal=True)
|
|
self.set_resizable(False)
|
|
self.set_icon_from_file(faugus_png)
|
|
subprocess.Popen(["canberra-gtk-play", "-f", faugus_notification])
|
|
|
|
label = Gtk.Label()
|
|
label.set_label(_("Are you sure you want to delete %s?") % title)
|
|
label.set_halign(Gtk.Align.CENTER)
|
|
|
|
button_no = Gtk.Button(label=_("No"))
|
|
button_no.set_size_request(150, -1)
|
|
button_no.connect("clicked", lambda x: self.response(Gtk.ResponseType.NO))
|
|
|
|
button_yes = Gtk.Button(label=_("Yes"))
|
|
button_yes.set_size_request(150, -1)
|
|
button_yes.connect("clicked", lambda x: self.response(Gtk.ResponseType.YES))
|
|
|
|
self.checkbox = Gtk.CheckButton(label=_("Also remove the prefix"))
|
|
self.checkbox.set_halign(Gtk.Align.CENTER)
|
|
|
|
content_area = self.get_content_area()
|
|
content_area.set_border_width(0)
|
|
content_area.set_halign(Gtk.Align.CENTER)
|
|
content_area.set_valign(Gtk.Align.CENTER)
|
|
content_area.set_vexpand(True)
|
|
content_area.set_hexpand(True)
|
|
|
|
box_top = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=20)
|
|
box_top.set_margin_start(20)
|
|
box_top.set_margin_end(20)
|
|
box_top.set_margin_top(20)
|
|
box_top.set_margin_bottom(20)
|
|
|
|
box_bottom = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
|
box_bottom.set_margin_start(10)
|
|
box_bottom.set_margin_end(10)
|
|
box_bottom.set_margin_bottom(10)
|
|
|
|
box_top.pack_start(label, True, True, 0)
|
|
if os.path.basename(prefix) != "default" and runner != "Linux-Native" and runner != "Steam":
|
|
box_top.pack_start(self.checkbox, True, True, 0)
|
|
|
|
box_bottom.pack_start(button_no, True, True, 0)
|
|
box_bottom.pack_start(button_yes, True, True, 0)
|
|
|
|
content_area.add(box_top)
|
|
content_area.add(box_bottom)
|
|
|
|
self.show_all()
|
|
|
|
def get_remove_prefix_state(self):
|
|
# Get the state of the checkbox
|
|
return self.checkbox.get_active()
|
|
|
|
|
|
class AddGame(Gtk.Dialog):
|
|
def __init__(self, parent, game_running2, file_path, interface_mode):
|
|
# Initialize the AddGame dialog
|
|
super().__init__(title=_("New Game/App"), parent=parent)
|
|
self.set_resizable(False)
|
|
self.set_modal(True)
|
|
self.parent_window = parent
|
|
self.set_icon_from_file(faugus_png)
|
|
self.interface_mode = interface_mode
|
|
self.updated_steam_id = None
|
|
|
|
self.lossless_enabled = False
|
|
self.lossless_multiplier = 1
|
|
self.lossless_flow = 100
|
|
self.lossless_performance = False
|
|
self.lossless_hdr = False
|
|
self.lossless_present = False
|
|
|
|
if not os.path.exists(banners_dir):
|
|
os.makedirs(banners_dir)
|
|
|
|
self.banner_path_temp = os.path.join(banners_dir, "banner_temp.png")
|
|
shutil.copyfile(faugus_banner, self.banner_path_temp)
|
|
self.icon_directory = f"{icons_dir}/icon_temp/"
|
|
|
|
if not os.path.exists(self.icon_directory):
|
|
os.makedirs(self.icon_directory)
|
|
|
|
self.icons_path = icons_dir
|
|
self.icon_extracted = os.path.expanduser(f'{self.icons_path}/icon_temp/icon.ico')
|
|
self.icon_converted = os.path.expanduser(f'{self.icons_path}/icon_temp/icon.png')
|
|
self.icon_temp = f'{self.icons_path}/icon_temp.png'
|
|
|
|
self.box = self.get_content_area()
|
|
self.box.set_margin_start(0)
|
|
self.box.set_margin_end(0)
|
|
self.box.set_margin_top(0)
|
|
self.box.set_margin_bottom(0)
|
|
self.content_area = self.get_content_area()
|
|
self.content_area.set_border_width(0)
|
|
self.content_area.set_halign(Gtk.Align.CENTER)
|
|
self.content_area.set_valign(Gtk.Align.CENTER)
|
|
self.content_area.set_vexpand(True)
|
|
self.content_area.set_hexpand(True)
|
|
|
|
box_buttons = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
|
|
|
self.grid_page1 = Gtk.Grid()
|
|
self.grid_page1.set_column_homogeneous(True)
|
|
self.grid_page1.set_column_spacing(10)
|
|
self.grid_page2 = Gtk.Grid()
|
|
self.grid_page2.set_column_homogeneous(True)
|
|
self.grid_page2.set_column_spacing(10)
|
|
|
|
self.grid_launcher = Gtk.Grid()
|
|
self.grid_launcher.set_row_spacing(10)
|
|
self.grid_launcher.set_column_spacing(10)
|
|
self.grid_launcher.set_margin_start(10)
|
|
self.grid_launcher.set_margin_end(10)
|
|
self.grid_launcher.set_margin_top(10)
|
|
|
|
self.grid_title = Gtk.Grid()
|
|
self.grid_title.set_row_spacing(10)
|
|
self.grid_title.set_column_spacing(10)
|
|
self.grid_title.set_margin_start(10)
|
|
self.grid_title.set_margin_end(10)
|
|
self.grid_title.set_margin_top(10)
|
|
|
|
self.grid_steam_title = Gtk.Grid()
|
|
self.grid_steam_title.set_row_spacing(10)
|
|
self.grid_steam_title.set_column_spacing(10)
|
|
self.grid_steam_title.set_margin_start(10)
|
|
self.grid_steam_title.set_margin_end(10)
|
|
self.grid_steam_title.set_margin_top(10)
|
|
|
|
self.grid_path = Gtk.Grid()
|
|
self.grid_path.set_row_spacing(10)
|
|
self.grid_path.set_column_spacing(10)
|
|
self.grid_path.set_margin_start(10)
|
|
self.grid_path.set_margin_end(10)
|
|
self.grid_path.set_margin_top(10)
|
|
|
|
self.grid_prefix = Gtk.Grid()
|
|
self.grid_prefix.set_row_spacing(10)
|
|
self.grid_prefix.set_column_spacing(10)
|
|
self.grid_prefix.set_margin_start(10)
|
|
self.grid_prefix.set_margin_end(10)
|
|
self.grid_prefix.set_margin_top(10)
|
|
|
|
self.grid_runner = Gtk.Grid()
|
|
self.grid_runner.set_row_spacing(10)
|
|
self.grid_runner.set_column_spacing(10)
|
|
self.grid_runner.set_margin_start(10)
|
|
self.grid_runner.set_margin_end(10)
|
|
self.grid_runner.set_margin_top(10)
|
|
|
|
self.grid_shortcut = Gtk.Grid(orientation=Gtk.Orientation.VERTICAL)
|
|
self.grid_shortcut.set_row_spacing(10)
|
|
self.grid_shortcut.set_column_spacing(10)
|
|
self.grid_shortcut.set_margin_start(10)
|
|
self.grid_shortcut.set_margin_end(10)
|
|
self.grid_shortcut.set_margin_top(10)
|
|
self.grid_shortcut.set_margin_bottom(10)
|
|
|
|
self.grid_shortcut_icon = Gtk.Grid(orientation=Gtk.Orientation.VERTICAL)
|
|
self.grid_shortcut_icon.set_row_spacing(10)
|
|
self.grid_shortcut_icon.set_column_spacing(10)
|
|
self.grid_shortcut_icon.set_margin_start(10)
|
|
self.grid_shortcut_icon.set_margin_end(10)
|
|
self.grid_shortcut_icon.set_margin_top(10)
|
|
self.grid_shortcut_icon.set_margin_bottom(10)
|
|
|
|
self.grid_protonfix = Gtk.Grid()
|
|
self.grid_protonfix.set_row_spacing(10)
|
|
self.grid_protonfix.set_column_spacing(10)
|
|
self.grid_protonfix.set_margin_start(10)
|
|
self.grid_protonfix.set_margin_end(10)
|
|
self.grid_protonfix.set_margin_top(10)
|
|
|
|
self.grid_launch_arguments = Gtk.Grid()
|
|
self.grid_launch_arguments.set_row_spacing(10)
|
|
self.grid_launch_arguments.set_column_spacing(10)
|
|
self.grid_launch_arguments.set_margin_start(10)
|
|
self.grid_launch_arguments.set_margin_end(10)
|
|
self.grid_launch_arguments.set_margin_top(10)
|
|
|
|
self.grid_game_arguments = Gtk.Grid()
|
|
self.grid_game_arguments.set_row_spacing(10)
|
|
self.grid_game_arguments.set_column_spacing(10)
|
|
self.grid_game_arguments.set_margin_start(10)
|
|
self.grid_game_arguments.set_margin_end(10)
|
|
self.grid_game_arguments.set_margin_top(10)
|
|
|
|
self.grid_lossless = Gtk.Grid()
|
|
self.grid_lossless.set_row_spacing(10)
|
|
self.grid_lossless.set_column_spacing(10)
|
|
self.grid_lossless.set_margin_start(10)
|
|
self.grid_lossless.set_margin_end(10)
|
|
self.grid_lossless.set_margin_top(10)
|
|
|
|
self.grid_addapp = Gtk.Grid()
|
|
self.grid_addapp.set_row_spacing(10)
|
|
self.grid_addapp.set_column_spacing(10)
|
|
self.grid_addapp.set_margin_start(10)
|
|
self.grid_addapp.set_margin_end(10)
|
|
self.grid_addapp.set_margin_top(10)
|
|
|
|
self.grid_tools = Gtk.Grid()
|
|
self.grid_tools.set_row_spacing(10)
|
|
self.grid_tools.set_column_spacing(10)
|
|
self.grid_tools.set_margin_start(10)
|
|
self.grid_tools.set_margin_end(10)
|
|
self.grid_tools.set_margin_top(10)
|
|
self.grid_tools.set_margin_bottom(10)
|
|
|
|
css_provider = Gtk.CssProvider()
|
|
css = """
|
|
.entry {
|
|
border-color: Red;
|
|
}
|
|
.combobox {
|
|
border: 1px solid red;
|
|
}
|
|
"""
|
|
css_provider.load_from_data(css.encode('utf-8'))
|
|
Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), css_provider,
|
|
Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
|
|
|
self.combobox_launcher = Gtk.ComboBoxText()
|
|
|
|
self.label_steam_title = Gtk.Label(label=_("Title"))
|
|
self.label_steam_title.set_halign(Gtk.Align.START)
|
|
self.combobox_steam_title = Gtk.ComboBoxText()
|
|
|
|
FILTER_KEYWORDS = [
|
|
"Proton",
|
|
"Steam Linux Runtime",
|
|
"Steamworks Common Redistributables",
|
|
]
|
|
|
|
for appid, name in read_installed_games():
|
|
lname = name.lower()
|
|
if any(keyword.lower() in lname for keyword in FILTER_KEYWORDS):
|
|
continue
|
|
|
|
self.combobox_steam_title.append(appid, name)
|
|
|
|
# Widgets for title
|
|
self.label_title = Gtk.Label(label=_("Title"))
|
|
self.label_title.set_halign(Gtk.Align.START)
|
|
self.entry_title = Gtk.Entry()
|
|
self.entry_title.connect("changed", self.on_entry_changed, self.entry_title)
|
|
self.entry_title.connect("changed", _validate_text)
|
|
if interface_mode == "Banners":
|
|
self.entry_title.connect("focus-out-event", self.on_entry_focus_out)
|
|
self.entry_title.set_tooltip_text(_("Game Title"))
|
|
self.entry_title.set_has_tooltip(True)
|
|
self.entry_title.connect("query-tooltip", self.on_entry_query_tooltip)
|
|
|
|
# Widgets for path
|
|
self.label_path = Gtk.Label(label=_("Path"))
|
|
self.label_path.set_halign(Gtk.Align.START)
|
|
self.entry_path = Gtk.Entry()
|
|
self.entry_path.connect("changed", self.on_entry_changed, self.entry_path)
|
|
if file_path:
|
|
self.entry_path.set_text(file_path)
|
|
self.entry_path.set_tooltip_text(_("/path/to/the/exe"))
|
|
self.entry_path.set_has_tooltip(True)
|
|
self.entry_path.connect("query-tooltip", self.on_entry_query_tooltip)
|
|
self.button_search = Gtk.Button()
|
|
self.button_search.set_image(Gtk.Image.new_from_icon_name("system-search-symbolic", Gtk.IconSize.BUTTON))
|
|
self.button_search.connect("clicked", self.on_button_search_clicked)
|
|
self.button_search.set_size_request(50, -1)
|
|
|
|
# Widgets for prefix
|
|
self.label_prefix = Gtk.Label(label=_("Prefix"))
|
|
self.label_prefix.set_halign(Gtk.Align.START)
|
|
self.entry_prefix = Gtk.Entry()
|
|
self.entry_prefix.connect("changed", self.on_entry_changed, self.entry_prefix)
|
|
self.entry_prefix.set_tooltip_text(_("/path/to/the/prefix"))
|
|
self.entry_prefix.set_has_tooltip(True)
|
|
self.entry_prefix.connect("query-tooltip", self.on_entry_query_tooltip)
|
|
self.button_search_prefix = Gtk.Button()
|
|
self.button_search_prefix.set_image(Gtk.Image.new_from_icon_name("system-search-symbolic", Gtk.IconSize.BUTTON))
|
|
self.button_search_prefix.connect("clicked", self.on_button_search_prefix_clicked)
|
|
self.button_search_prefix.set_size_request(50, -1)
|
|
|
|
# Widgets for runner
|
|
self.label_runner = Gtk.Label(label=_("Proton"))
|
|
self.label_runner.set_halign(Gtk.Align.START)
|
|
self.combobox_runner = Gtk.ComboBoxText()
|
|
|
|
# Widgets for protonfix
|
|
self.label_protonfix = Gtk.Label(label="Protonfix")
|
|
self.label_protonfix.set_halign(Gtk.Align.START)
|
|
self.entry_protonfix = Gtk.Entry()
|
|
self.entry_protonfix.set_tooltip_text("UMU ID")
|
|
self.entry_protonfix.set_has_tooltip(True)
|
|
self.entry_protonfix.connect("query-tooltip", self.on_entry_query_tooltip)
|
|
self.button_search_protonfix = Gtk.Button()
|
|
self.button_search_protonfix.set_image(
|
|
Gtk.Image.new_from_icon_name("system-search-symbolic", Gtk.IconSize.BUTTON))
|
|
self.button_search_protonfix.connect("clicked", self.on_button_search_protonfix_clicked)
|
|
self.button_search_protonfix.set_size_request(50, -1)
|
|
|
|
# Widgets for launch arguments
|
|
self.label_launch_arguments = Gtk.Label(label=_("Launch Arguments"))
|
|
self.label_launch_arguments.set_halign(Gtk.Align.START)
|
|
self.entry_launch_arguments = Gtk.Entry()
|
|
self.entry_launch_arguments.set_tooltip_text(_("e.g.: PROTON_USE_WINED3D=1 gamescope -W 2560 -H 1440"))
|
|
self.entry_launch_arguments.set_has_tooltip(True)
|
|
self.entry_launch_arguments.connect("query-tooltip", self.on_entry_query_tooltip)
|
|
|
|
# Widgets for game arguments
|
|
self.label_game_arguments = Gtk.Label(label=_("Game Arguments"))
|
|
self.label_game_arguments.set_halign(Gtk.Align.START)
|
|
self.entry_game_arguments = Gtk.Entry()
|
|
self.entry_game_arguments.set_tooltip_text(_("e.g.: -d3d11 -fullscreen"))
|
|
self.entry_game_arguments.set_has_tooltip(True)
|
|
self.entry_game_arguments.connect("query-tooltip", self.on_entry_query_tooltip)
|
|
|
|
# Widgets for lossless scaling
|
|
self.button_lossless = Gtk.Button(label=_("Lossless Scaling Frame Generation"))
|
|
self.button_lossless.connect("clicked", self.on_button_lossless_clicked)
|
|
|
|
# Widgets for extra executable
|
|
self.checkbox_addapp = Gtk.CheckButton(label=_("Additional Application"))
|
|
self.checkbox_addapp.set_tooltip_text(
|
|
_("Additional application to run with the game, like Cheat Engine, Trainers, Mods..."))
|
|
self.checkbox_addapp.connect("toggled", self.on_checkbox_addapp_toggled)
|
|
self.entry_addapp = Gtk.Entry()
|
|
self.entry_addapp.set_tooltip_text(_("/path/to/the/app"))
|
|
self.entry_addapp.set_has_tooltip(True)
|
|
self.entry_addapp.connect("query-tooltip", self.on_entry_query_tooltip)
|
|
self.entry_addapp.set_sensitive(False)
|
|
self.button_search_addapp = Gtk.Button()
|
|
self.button_search_addapp.set_image(Gtk.Image.new_from_icon_name("system-search-symbolic", Gtk.IconSize.BUTTON))
|
|
self.button_search_addapp.connect("clicked", self.on_button_search_addapp_clicked)
|
|
self.button_search_addapp.set_size_request(50, -1)
|
|
self.button_search_addapp.set_sensitive(False)
|
|
|
|
# Checkboxes for optional features
|
|
self.checkbox_mangohud = Gtk.CheckButton(label="MangoHud")
|
|
self.checkbox_mangohud.set_tooltip_text(
|
|
_("Shows an overlay for monitoring FPS, temperatures, CPU/GPU load and more."))
|
|
self.checkbox_gamemode = Gtk.CheckButton(label="GameMode")
|
|
self.checkbox_gamemode.set_tooltip_text(_("Tweaks your system to improve performance."))
|
|
self.checkbox_disable_hidraw = Gtk.CheckButton(label=_("Disable Hidraw"))
|
|
self.checkbox_disable_hidraw.set_tooltip_text(
|
|
_("May fix controller issues with some games. Only works with GE-Proton10 or Proton-EM-10."))
|
|
self.checkbox_prevent_sleep = Gtk.CheckButton(label=_("Prevent Sleep"))
|
|
|
|
# Button for Winecfg
|
|
self.button_winecfg = Gtk.Button(label="Winecfg")
|
|
self.button_winecfg.set_size_request(120, -1)
|
|
self.button_winecfg.connect("clicked", self.on_button_winecfg_clicked)
|
|
|
|
# Button for Winetricks
|
|
self.button_winetricks = Gtk.Button(label="Winetricks")
|
|
self.button_winetricks.set_size_request(120, -1)
|
|
self.button_winetricks.connect("clicked", self.on_button_winetricks_clicked)
|
|
|
|
# Button for Run
|
|
self.button_run = Gtk.Button(label=_("Run"))
|
|
self.button_run.set_size_request(120, -1)
|
|
self.button_run.connect("clicked", self.on_button_run_clicked)
|
|
self.button_run.set_tooltip_text(_("Run a file inside the prefix"))
|
|
|
|
# Button for creating shortcut
|
|
self.label_shortcut = Gtk.Label(label=_("Shortcut"))
|
|
self.label_shortcut.set_margin_start(10)
|
|
self.label_shortcut.set_margin_end(10)
|
|
self.label_shortcut.set_margin_top(10)
|
|
self.label_shortcut.set_halign(Gtk.Align.START)
|
|
self.checkbox_shortcut_desktop = Gtk.CheckButton(label=_("Desktop"))
|
|
self.checkbox_shortcut_desktop.set_tooltip_text(
|
|
_("Add or remove a shortcut from the Desktop."))
|
|
self.checkbox_shortcut_appmenu = Gtk.CheckButton(label=_("App Menu"))
|
|
self.checkbox_shortcut_appmenu.set_tooltip_text(
|
|
_("Add or remove a shortcut from the Application Menu."))
|
|
self.checkbox_shortcut_steam = Gtk.CheckButton(label=_("Steam"))
|
|
self.checkbox_shortcut_steam.set_tooltip_text(
|
|
_("Add or remove a shortcut from Steam. Steam needs to be restarted."))
|
|
|
|
# Button for selection shortcut icon
|
|
self.button_shortcut_icon = Gtk.Button()
|
|
self.button_shortcut_icon.set_size_request(120, -1)
|
|
self.button_shortcut_icon.connect("clicked", self.on_button_shortcut_icon_clicked)
|
|
self.button_shortcut_icon.set_tooltip_text(_("Select an icon for the shortcut"))
|
|
|
|
# Button Cancel
|
|
self.button_cancel = Gtk.Button(label=_("Cancel"))
|
|
self.button_cancel.connect("clicked", lambda widget: self.response(Gtk.ResponseType.CANCEL))
|
|
self.button_cancel.set_size_request(150, -1)
|
|
|
|
# Button Ok
|
|
self.button_ok = Gtk.Button(label=_("Ok"))
|
|
self.button_ok.connect("clicked", lambda widget: self.response(Gtk.ResponseType.OK))
|
|
self.button_ok.set_size_request(150, -1)
|
|
|
|
self.load_config()
|
|
|
|
self.entry_title.connect("changed", self.update_prefix_entry)
|
|
|
|
self.notebook = Gtk.Notebook()
|
|
self.notebook.set_margin_start(10)
|
|
self.notebook.set_margin_end(10)
|
|
self.notebook.set_margin_top(10)
|
|
self.notebook.set_margin_bottom(10)
|
|
# notebook.set_show_border(False)
|
|
|
|
self.box.add(self.notebook)
|
|
|
|
self.image_banner = Gtk.Image()
|
|
self.image_banner.set_margin_top(10)
|
|
self.image_banner.set_margin_bottom(10)
|
|
self.image_banner.set_margin_start(10)
|
|
self.image_banner.set_margin_end(10)
|
|
self.image_banner.set_vexpand(True)
|
|
self.image_banner.set_valign(Gtk.Align.CENTER)
|
|
|
|
self.image_banner2 = Gtk.Image()
|
|
self.image_banner2.set_margin_top(10)
|
|
self.image_banner2.set_margin_bottom(10)
|
|
self.image_banner2.set_margin_start(10)
|
|
self.image_banner2.set_margin_end(10)
|
|
self.image_banner2.set_vexpand(True)
|
|
self.image_banner2.set_valign(Gtk.Align.CENTER)
|
|
|
|
event_box = Gtk.EventBox()
|
|
event_box.add(self.image_banner)
|
|
event_box.connect("button-press-event", self.on_image_clicked)
|
|
|
|
event_box2 = Gtk.EventBox()
|
|
event_box2.add(self.image_banner2)
|
|
event_box2.connect("button-press-event", self.on_image_clicked)
|
|
|
|
self.menu = Gtk.Menu()
|
|
|
|
refresh_item = Gtk.MenuItem(label=_("Refresh"))
|
|
refresh_item.connect("activate", self.on_refresh)
|
|
self.menu.append(refresh_item)
|
|
|
|
load_item = Gtk.MenuItem(label=_("Load from file"))
|
|
load_item.connect("activate", self.on_load_file)
|
|
self.menu.append(load_item)
|
|
|
|
load_url = Gtk.MenuItem(label=_("Load from URL"))
|
|
load_url.connect("activate", self.on_load_url)
|
|
self.menu.append(load_url)
|
|
|
|
self.menu.show_all()
|
|
|
|
page1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
self.tab_box1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
|
tab_label1 = Gtk.Label(label=_("Game/App"))
|
|
tab_label1.set_width_chars(8)
|
|
tab_label1.set_xalign(0.5)
|
|
self.tab_box1.pack_start(tab_label1, True, True, 0)
|
|
self.tab_box1.set_hexpand(True)
|
|
|
|
self.grid_page1.attach(page1, 0, 0, 1, 1)
|
|
if interface_mode == "Banners":
|
|
self.grid_page1.attach(event_box, 1, 0, 1, 1)
|
|
page1.set_hexpand(True)
|
|
event_box.set_hexpand(True)
|
|
|
|
self.notebook.append_page(self.grid_page1, self.tab_box1)
|
|
|
|
page2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
self.tab_box2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
|
tab_label2 = Gtk.Label(label=_("Tools"))
|
|
tab_label2.set_width_chars(8)
|
|
tab_label2.set_xalign(0.5)
|
|
self.tab_box2.pack_start(tab_label2, True, True, 0)
|
|
self.tab_box2.set_hexpand(True)
|
|
|
|
self.grid_page2.attach(page2, 0, 0, 1, 1)
|
|
if interface_mode == "Banners":
|
|
self.grid_page2.attach(event_box2, 1, 0, 1, 1)
|
|
page2.set_hexpand(True)
|
|
event_box2.set_hexpand(True)
|
|
|
|
self.notebook.append_page(self.grid_page2, self.tab_box2)
|
|
|
|
self.grid_launcher.attach(self.combobox_launcher, 1, 0, 1, 1)
|
|
self.combobox_launcher.set_hexpand(True)
|
|
self.combobox_launcher.set_valign(Gtk.Align.CENTER)
|
|
|
|
self.grid_steam_title.attach(self.label_steam_title, 0, 0, 4, 1)
|
|
self.grid_steam_title.attach(self.combobox_steam_title, 0, 1, 4, 1)
|
|
self.combobox_steam_title.set_hexpand(True)
|
|
|
|
self.grid_title.attach(self.label_title, 0, 0, 4, 1)
|
|
self.grid_title.attach(self.entry_title, 0, 1, 4, 1)
|
|
self.entry_title.set_hexpand(True)
|
|
|
|
self.grid_path.attach(self.label_path, 0, 0, 1, 1)
|
|
self.grid_path.attach(self.entry_path, 0, 1, 3, 1)
|
|
self.entry_path.set_hexpand(True)
|
|
self.grid_path.attach(self.button_search, 3, 1, 1, 1)
|
|
|
|
self.grid_prefix.attach(self.label_prefix, 0, 0, 1, 1)
|
|
self.grid_prefix.attach(self.entry_prefix, 0, 1, 3, 1)
|
|
self.entry_prefix.set_hexpand(True)
|
|
self.grid_prefix.attach(self.button_search_prefix, 3, 1, 1, 1)
|
|
|
|
self.grid_runner.attach(self.label_runner, 0, 0, 1, 1)
|
|
self.grid_runner.attach(self.combobox_runner, 0, 1, 1, 1)
|
|
self.combobox_runner.set_hexpand(True)
|
|
|
|
self.label_shortcut.set_hexpand(True)
|
|
self.grid_shortcut.add(self.checkbox_shortcut_desktop)
|
|
self.grid_shortcut.add(self.checkbox_shortcut_appmenu)
|
|
self.grid_shortcut.add(self.checkbox_shortcut_steam)
|
|
self.grid_shortcut_icon.add(self.button_shortcut_icon)
|
|
self.grid_shortcut_icon.set_valign(Gtk.Align.CENTER)
|
|
|
|
self.box_shortcut = Gtk.Box()
|
|
self.box_shortcut.pack_start(self.grid_shortcut, False, False, 0)
|
|
self.box_shortcut.pack_end(self.grid_shortcut_icon, False, False, 0)
|
|
|
|
page1.add(self.grid_launcher)
|
|
page1.add(self.grid_steam_title)
|
|
page1.add(self.grid_title)
|
|
page1.add(self.grid_path)
|
|
page1.add(self.grid_prefix)
|
|
page1.add(self.grid_runner)
|
|
page1.add(self.label_shortcut)
|
|
page1.add(self.box_shortcut)
|
|
|
|
self.grid_protonfix.attach(self.label_protonfix, 0, 0, 1, 1)
|
|
self.grid_protonfix.attach(self.entry_protonfix, 0, 1, 3, 1)
|
|
self.entry_protonfix.set_hexpand(True)
|
|
self.grid_protonfix.attach(self.button_search_protonfix, 3, 1, 1, 1)
|
|
|
|
self.grid_launch_arguments.attach(self.label_launch_arguments, 0, 0, 4, 1)
|
|
self.grid_launch_arguments.attach(self.entry_launch_arguments, 0, 1, 4, 1)
|
|
self.entry_launch_arguments.set_hexpand(True)
|
|
|
|
self.grid_game_arguments.attach(self.label_game_arguments, 0, 0, 4, 1)
|
|
self.grid_game_arguments.attach(self.entry_game_arguments, 0, 1, 4, 1)
|
|
self.entry_game_arguments.set_hexpand(True)
|
|
|
|
self.grid_lossless.attach(self.button_lossless, 0, 0, 1, 1)
|
|
self.button_lossless.set_hexpand(True)
|
|
|
|
self.grid_addapp.attach(self.checkbox_addapp, 0, 0, 1, 1)
|
|
self.grid_addapp.attach(self.entry_addapp, 0, 1, 3, 1)
|
|
self.entry_addapp.set_hexpand(True)
|
|
self.grid_addapp.attach(self.button_search_addapp, 3, 1, 1, 1)
|
|
|
|
box_buttons.pack_start(self.button_winetricks, True, True, 0)
|
|
box_buttons.pack_start(self.button_winecfg, True, True, 0)
|
|
box_buttons.pack_start(self.button_run, True, True, 0)
|
|
|
|
self.grid_tools.attach(self.checkbox_mangohud, 0, 0, 1, 1)
|
|
self.checkbox_mangohud.set_hexpand(True)
|
|
self.grid_tools.attach(self.checkbox_gamemode, 0, 1, 1, 1)
|
|
self.checkbox_gamemode.set_hexpand(True)
|
|
self.grid_tools.attach(self.checkbox_prevent_sleep, 0, 2, 1, 1)
|
|
self.checkbox_prevent_sleep.set_hexpand(True)
|
|
self.grid_tools.attach(self.checkbox_disable_hidraw, 0, 3, 1, 1)
|
|
self.checkbox_disable_hidraw.set_hexpand(True)
|
|
self.grid_tools.attach(box_buttons, 2, 0, 1, 4)
|
|
|
|
page2.add(self.grid_protonfix)
|
|
page2.add(self.grid_launch_arguments)
|
|
page2.add(self.grid_game_arguments)
|
|
page2.add(self.grid_addapp)
|
|
page2.add(self.grid_lossless)
|
|
page2.add(self.grid_tools)
|
|
|
|
bottom_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
|
bottom_box.set_margin_start(10)
|
|
bottom_box.set_margin_end(10)
|
|
bottom_box.set_margin_bottom(10)
|
|
self.button_cancel.set_hexpand(True)
|
|
self.button_ok.set_hexpand(True)
|
|
|
|
bottom_box.pack_start(self.button_cancel, True, True, 0)
|
|
bottom_box.pack_start(self.button_ok, True, True, 0)
|
|
|
|
self.box.add(bottom_box)
|
|
|
|
self.populate_combobox_with_launchers()
|
|
self.combobox_launcher.set_active(0)
|
|
self.combobox_launcher.connect("changed", self.on_combobox_changed)
|
|
|
|
self.combobox_steam_title.connect("changed", self.on_combobox_steam_changed)
|
|
|
|
self.populate_combobox_with_runners()
|
|
|
|
model = self.combobox_runner.get_model()
|
|
index_to_activate = 0
|
|
|
|
self.default_runner = convert_runner(self.default_runner)
|
|
|
|
for i, row in enumerate(model):
|
|
if row[0] == self.default_runner:
|
|
index_to_activate = i
|
|
break
|
|
self.combobox_runner.set_active(index_to_activate)
|
|
|
|
self.checkbox_mangohud.set_active(self.default_mangohud)
|
|
self.checkbox_gamemode.set_active(self.default_gamemode)
|
|
self.checkbox_prevent_sleep.set_active(self.default_prevent_sleep)
|
|
self.checkbox_disable_hidraw.set_active(self.default_disable_hidraw)
|
|
|
|
# Check if optional features are available and enable/disable accordingly
|
|
self.mangohud_enabled = os.path.exists(mangohud_dir)
|
|
if not self.mangohud_enabled:
|
|
self.checkbox_mangohud.set_sensitive(False)
|
|
self.checkbox_mangohud.set_active(False)
|
|
self.checkbox_mangohud.set_tooltip_text(
|
|
_("Shows an overlay for monitoring FPS, temperatures, CPU/GPU load and more. NOT INSTALLED."))
|
|
|
|
self.gamemode_enabled = os.path.exists(gamemoderun) or os.path.exists("/usr/games/gamemoderun")
|
|
if not self.gamemode_enabled:
|
|
self.checkbox_gamemode.set_sensitive(False)
|
|
self.checkbox_gamemode.set_active(False)
|
|
self.checkbox_gamemode.set_tooltip_text(_("Tweaks your system to improve performance. NOT INSTALLED."))
|
|
|
|
self.updated_steam_id = detect_steam_id()
|
|
if not self.updated_steam_id:
|
|
self.checkbox_shortcut_steam.set_sensitive(False)
|
|
self.checkbox_shortcut_steam.set_tooltip_text(
|
|
_("Add or remove a shortcut from Steam. Steam needs to be restarted. NO STEAM USERS FOUND."))
|
|
|
|
self.lossless_location = ConfigManager().config.get('lossless-location', '')
|
|
lossless_dll_path = find_lossless_dll()
|
|
if os.path.exists(lsfgvk_path):
|
|
if lossless_dll_path or os.path.exists(self.lossless_location):
|
|
self.button_lossless.set_sensitive(True)
|
|
else:
|
|
self.button_lossless.set_sensitive(False)
|
|
self.button_lossless.set_tooltip_text(_("Lossless.dll NOT FOUND. If it's installed, go to Faugus Launcher's settings and set the location."))
|
|
else:
|
|
self.button_lossless.set_sensitive(False)
|
|
self.button_lossless.set_tooltip_text(_("Lossless Scaling Vulkan Layer NOT INSTALLED."))
|
|
|
|
self.button_shortcut_icon.set_image(self.set_image_shortcut_icon())
|
|
|
|
self.tab_box1.show_all()
|
|
self.tab_box2.show_all()
|
|
self.show_all()
|
|
self.grid_steam_title.set_visible(False)
|
|
if interface_mode != "Banners":
|
|
self.image_banner.set_visible(False)
|
|
self.image_banner2.set_visible(False)
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(self.banner_path_temp, 260, 390, True)
|
|
self.image_banner.set_from_pixbuf(pixbuf)
|
|
self.image_banner2.set_from_pixbuf(pixbuf)
|
|
|
|
def on_combobox_steam_changed(self, combobox):
|
|
self.combobox_steam_title.get_style_context().remove_class("combobox")
|
|
|
|
title = self.combobox_steam_title.get_active_text()
|
|
steamid = self.combobox_steam_title.get_active_id()
|
|
|
|
if not title or not steamid:
|
|
return
|
|
|
|
self.entry_title.set_text(title)
|
|
self.entry_path.set_text(steamid)
|
|
|
|
icon_path = get_steam_icon_path(steamid)
|
|
if not icon_path:
|
|
icon_path = faugus_png
|
|
|
|
self.on_entry_focus_out(self.entry_title, None)
|
|
|
|
shutil.copyfile(icon_path, os.path.expanduser(self.icon_temp))
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(self.icon_temp)
|
|
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
|
image = Gtk.Image.new_from_file(self.icon_temp)
|
|
image.set_from_pixbuf(scaled_pixbuf)
|
|
self.button_shortcut_icon.set_image(image)
|
|
|
|
|
|
def on_button_lossless_clicked(self, widget):
|
|
dialog = Gtk.Dialog(title=_("Lossless Scaling Frame Generation"), parent=self, flags=0)
|
|
dialog.set_resizable(False)
|
|
dialog.set_icon_from_file(faugus_png)
|
|
|
|
frame = Gtk.Frame()
|
|
frame.set_margin_start(10)
|
|
frame.set_margin_end(10)
|
|
frame.set_margin_top(10)
|
|
frame.set_margin_bottom(10)
|
|
|
|
grid = Gtk.Grid()
|
|
grid.set_row_spacing(10)
|
|
grid.set_column_spacing(10)
|
|
grid.set_margin_top(10)
|
|
grid.set_margin_bottom(10)
|
|
grid.set_margin_start(10)
|
|
grid.set_margin_end(10)
|
|
|
|
enabled = val if (val := getattr(self, "lossless_enabled", False)) != "" else False
|
|
multiplier = val if (val := getattr(self, "lossless_multiplier", 1)) != "" else 1
|
|
flow = val if (val := getattr(self, "lossless_flow", 100)) != "" else 100
|
|
performance = val if (val := getattr(self, "lossless_performance", False)) != "" else False
|
|
hdr = val if (val := getattr(self, "lossless_hdr", False)) != "" else False
|
|
present = val if (val := getattr(self, "lossless_present", False)) != "" else "VSync/FIFO (default)"
|
|
|
|
checkbox_enable = Gtk.CheckButton(label="Enable")
|
|
checkbox_enable.set_active(enabled)
|
|
checkbox_enable.set_halign(Gtk.Align.START)
|
|
|
|
label_multiplier = Gtk.Label(label=_("Multiplier"))
|
|
label_multiplier.set_halign(Gtk.Align.START)
|
|
spin_multiplier = Gtk.SpinButton()
|
|
spin_multiplier.set_adjustment(Gtk.Adjustment(value=multiplier, lower=1, upper=20, step_increment=1))
|
|
spin_multiplier.set_numeric(True)
|
|
spin_multiplier.set_tooltip_text(_("Multiply the FPS."))
|
|
|
|
label_flow = Gtk.Label(label=_("Flow Scale"))
|
|
label_flow.set_halign(Gtk.Align.START)
|
|
adjustment = Gtk.Adjustment(value=flow, lower=25, upper=100, step_increment=1)
|
|
scale_flow = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL, adjustment=adjustment)
|
|
scale_flow.set_digits(0)
|
|
scale_flow.set_hexpand(True)
|
|
scale_flow.set_value_pos(Gtk.PositionType.RIGHT)
|
|
scale_flow.set_tooltip_text(_("Lower the internal motion estimation resolution."))
|
|
|
|
checkbox_performance = Gtk.CheckButton(label=_("Performance Mode"))
|
|
checkbox_performance.set_tooltip_text(_("Massively improve performance at the cost of quality."))
|
|
checkbox_performance.set_active(performance)
|
|
|
|
checkbox_hdr = Gtk.CheckButton(label=_("HDR Mode"))
|
|
checkbox_hdr.set_tooltip_text(_("Enable special HDR-only behavior."))
|
|
checkbox_hdr.set_active(hdr)
|
|
|
|
label_present = Gtk.Label(label=_("Present Mode (Experimental)"))
|
|
label_present.set_halign(Gtk.Align.START)
|
|
|
|
combobox_present = Gtk.ComboBoxText()
|
|
combobox_present.set_tooltip_text(_("Override the present mode."))
|
|
|
|
options = [
|
|
"VSync/FIFO (default)",
|
|
"Mailbox",
|
|
"Immediate",
|
|
]
|
|
|
|
for opt in options:
|
|
combobox_present.append_text(opt)
|
|
|
|
mapping = {
|
|
"fifo": "VSync/FIFO (default)",
|
|
"mailbox": "Mailbox",
|
|
"immediate": "Immediate",
|
|
}
|
|
|
|
ui_value = mapping.get(present, "VSync/FIFO (default)")
|
|
combobox_present.set_active(options.index(ui_value))
|
|
|
|
def on_enable_toggled(cb):
|
|
active = cb.get_active()
|
|
label_multiplier.set_sensitive(active)
|
|
spin_multiplier.set_sensitive(active)
|
|
label_flow.set_sensitive(active)
|
|
scale_flow.set_sensitive(active)
|
|
checkbox_performance.set_sensitive(active)
|
|
checkbox_hdr.set_sensitive(active)
|
|
label_present.set_sensitive(active)
|
|
combobox_present.set_sensitive(active)
|
|
|
|
checkbox_enable.connect("toggled", on_enable_toggled)
|
|
on_enable_toggled(checkbox_enable)
|
|
|
|
grid.attach(checkbox_enable, 0, 0, 1, 1)
|
|
grid.attach(label_multiplier, 0, 1, 1, 1)
|
|
grid.attach(spin_multiplier, 0, 2, 1, 1)
|
|
grid.attach(label_flow, 0, 3, 1, 1)
|
|
grid.attach(scale_flow, 0, 4, 1, 1)
|
|
grid.attach(checkbox_performance, 0, 5, 1, 1)
|
|
grid.attach(checkbox_hdr, 0, 6, 1, 1)
|
|
grid.attach(label_present, 0, 7, 1, 1)
|
|
grid.attach(combobox_present, 0, 8, 1, 1)
|
|
|
|
frame.add(grid)
|
|
|
|
button_cancel = Gtk.Button(label=_("Cancel"))
|
|
button_cancel.set_size_request(150, -1)
|
|
button_cancel.set_hexpand(True)
|
|
button_cancel.connect("clicked", lambda b: dialog.response(Gtk.ResponseType.CANCEL))
|
|
|
|
button_ok = Gtk.Button(label=_("Ok"))
|
|
button_ok.set_size_request(150, -1)
|
|
button_ok.set_hexpand(True)
|
|
button_ok.connect("clicked", lambda b: dialog.response(Gtk.ResponseType.OK))
|
|
|
|
bottom_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
|
bottom_box.set_margin_start(10)
|
|
bottom_box.set_margin_end(10)
|
|
bottom_box.set_margin_bottom(10)
|
|
bottom_box.pack_start(button_cancel, True, True, 0)
|
|
bottom_box.pack_start(button_ok, True, True, 0)
|
|
|
|
content_area = dialog.get_content_area()
|
|
content_area.pack_start(frame, True, True, 0)
|
|
content_area.pack_start(bottom_box, False, False, 0)
|
|
|
|
dialog.show_all()
|
|
response = dialog.run()
|
|
|
|
if response == Gtk.ResponseType.OK:
|
|
self.lossless_enabled = checkbox_enable.get_active()
|
|
self.lossless_multiplier = spin_multiplier.get_value_as_int()
|
|
self.lossless_flow = scale_flow.get_value()
|
|
self.lossless_performance = checkbox_performance.get_active()
|
|
self.lossless_hdr = checkbox_hdr.get_active()
|
|
|
|
present = combobox_present.get_active_text()
|
|
|
|
mapping = {
|
|
"VSync/FIFO (default)": "fifo",
|
|
"Mailbox": "mailbox",
|
|
"Immediate": "immediate",
|
|
}
|
|
|
|
self.lossless_present = mapping.get(present, "fifo")
|
|
|
|
dialog.destroy()
|
|
return response
|
|
|
|
def check_steam_shortcut(self, title):
|
|
if os.path.exists(steam_shortcuts_path):
|
|
try:
|
|
with open(steam_shortcuts_path, 'rb') as f:
|
|
shortcuts = vdf.binary_load(f)
|
|
for game in shortcuts["shortcuts"].values():
|
|
if isinstance(game, dict) and "AppName" in game and game["AppName"] == title:
|
|
return True
|
|
return False
|
|
except SyntaxError:
|
|
return False
|
|
return False
|
|
|
|
def on_entry_query_tooltip(self, widget, x, y, keyboard_mode, tooltip):
|
|
current_text = widget.get_text()
|
|
if current_text.strip():
|
|
tooltip.set_text(current_text)
|
|
else:
|
|
tooltip.set_text(widget.get_tooltip_text())
|
|
return True
|
|
|
|
def on_image_clicked(self, widget, event):
|
|
self.menu.popup_at_pointer(event)
|
|
|
|
def on_refresh(self, widget):
|
|
if self.entry_title.get_text() != "":
|
|
self.get_banner()
|
|
else:
|
|
shutil.copyfile(faugus_banner, self.banner_path_temp)
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(self.banner_path_temp, 260, 390, True)
|
|
self.image_banner.set_from_pixbuf(pixbuf)
|
|
self.image_banner2.set_from_pixbuf(pixbuf)
|
|
|
|
def on_load_file(self, widget):
|
|
self.set_sensitive(False)
|
|
|
|
def is_valid_image(file_path):
|
|
try:
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(file_path)
|
|
return pixbuf is not None
|
|
except Exception:
|
|
return False
|
|
|
|
filechooser = Gtk.FileChooserNative(
|
|
title=_("Select an image for the banner"),
|
|
transient_for=self,
|
|
action=Gtk.FileChooserAction.OPEN,
|
|
accept_label=_("Open"),
|
|
cancel_label=_("Cancel"),
|
|
)
|
|
|
|
filter_ico = Gtk.FileFilter()
|
|
filter_ico.set_name(_("Image files"))
|
|
filter_ico.add_pattern("*.png")
|
|
filter_ico.add_pattern("*.jpg")
|
|
filter_ico.add_pattern("*.jpeg")
|
|
filter_ico.add_pattern("*.jxl")
|
|
filter_ico.add_pattern("*.bmp")
|
|
filter_ico.add_pattern("*.gif")
|
|
filter_ico.add_pattern("*.svg")
|
|
filechooser.add_filter(filter_ico)
|
|
|
|
response = filechooser.run()
|
|
|
|
if response == Gtk.ResponseType.ACCEPT:
|
|
file_path = filechooser.get_filename()
|
|
if not file_path or not is_valid_image(file_path):
|
|
self.show_invalid_image_dialog()
|
|
else:
|
|
shutil.copyfile(file_path, self.banner_path_temp)
|
|
self.update_image_banner()
|
|
|
|
filechooser.destroy()
|
|
self.set_sensitive(True)
|
|
|
|
def on_load_url(self, widget):
|
|
self.set_sensitive(False)
|
|
|
|
dialog = Gtk.Dialog(
|
|
title=_("Enter the image URL"),
|
|
transient_for=self,
|
|
modal=True
|
|
)
|
|
dialog.set_resizable(False)
|
|
dialog.set_icon_from_file(faugus_png)
|
|
|
|
entry = Gtk.Entry()
|
|
entry.set_tooltip_text("https://example.com/banner.png")
|
|
|
|
button_ok = Gtk.Button(label=_("Ok"))
|
|
button_ok.set_size_request(120, -1)
|
|
button_ok.connect("clicked", lambda x: dialog.response(Gtk.ResponseType.OK))
|
|
|
|
button_cancel = Gtk.Button(label=_("Cancel"))
|
|
button_cancel.set_size_request(120, -1)
|
|
button_cancel.connect("clicked", lambda x: dialog.response(Gtk.ResponseType.CANCEL))
|
|
|
|
box_top = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
|
box_top.set_margin_start(10)
|
|
box_top.set_margin_end(10)
|
|
box_top.set_margin_top(10)
|
|
box_top.set_margin_bottom(10)
|
|
|
|
box_bottom = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
|
box_bottom.set_margin_start(10)
|
|
box_bottom.set_margin_end(10)
|
|
box_bottom.set_margin_bottom(10)
|
|
|
|
box_top.pack_start(entry, False, False, 0)
|
|
|
|
box_bottom.pack_start(button_cancel, True, True, 0)
|
|
box_bottom.pack_start(button_ok, True, True, 0)
|
|
|
|
dialog.get_content_area().add(box_top)
|
|
dialog.get_content_area().add(box_bottom)
|
|
|
|
dialog.show_all()
|
|
|
|
while True:
|
|
response = dialog.run()
|
|
|
|
if response == Gtk.ResponseType.CANCEL:
|
|
break
|
|
|
|
if response == Gtk.ResponseType.OK:
|
|
url = entry.get_text().strip().replace(" ", "%20")
|
|
valid_exts = (".png", ".jpg", ".jpeg", ".bmp", ".gif", ".svg")
|
|
|
|
if not url.lower().endswith(valid_exts):
|
|
self.show_invalid_image_dialog()
|
|
dialog.show_all()
|
|
continue
|
|
|
|
try:
|
|
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
|
|
with urllib.request.urlopen(req) as r, open(self.banner_path_temp, "wb") as f:
|
|
f.write(r.read())
|
|
|
|
self.update_image_banner()
|
|
break
|
|
|
|
except Exception:
|
|
self.show_invalid_image_dialog()
|
|
dialog.show_all()
|
|
continue
|
|
|
|
dialog.destroy()
|
|
self.set_sensitive(True)
|
|
|
|
def show_invalid_image_dialog(self):
|
|
dialog_image = Gtk.Dialog(title="Faugus Launcher", transient_for=self, modal=True)
|
|
dialog_image.set_resizable(False)
|
|
dialog_image.set_icon_from_file(faugus_png)
|
|
subprocess.Popen(["canberra-gtk-play", "-f", faugus_notification])
|
|
|
|
label = Gtk.Label(label=_("The selected file is not a valid image."))
|
|
label.set_halign(Gtk.Align.CENTER)
|
|
|
|
label2 = Gtk.Label(label=_("Please choose another one."))
|
|
label2.set_halign(Gtk.Align.CENTER)
|
|
|
|
button_yes = Gtk.Button(label=_("Ok"))
|
|
button_yes.set_size_request(150, -1)
|
|
button_yes.connect("clicked", lambda x: dialog_image.response(Gtk.ResponseType.YES))
|
|
|
|
content_area = dialog_image.get_content_area()
|
|
content_area.set_border_width(0)
|
|
content_area.set_halign(Gtk.Align.CENTER)
|
|
content_area.set_valign(Gtk.Align.CENTER)
|
|
content_area.set_vexpand(True)
|
|
content_area.set_hexpand(True)
|
|
|
|
box_top = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
|
box_top.set_margin_start(20)
|
|
box_top.set_margin_end(20)
|
|
box_top.set_margin_top(20)
|
|
box_top.set_margin_bottom(20)
|
|
|
|
box_bottom = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
|
box_bottom.set_margin_start(10)
|
|
box_bottom.set_margin_end(10)
|
|
box_bottom.set_margin_bottom(10)
|
|
|
|
box_top.pack_start(label, True, True, 0)
|
|
box_top.pack_start(label2, True, True, 0)
|
|
box_bottom.pack_start(button_yes, True, True, 0)
|
|
|
|
content_area.add(box_top)
|
|
content_area.add(box_bottom)
|
|
|
|
dialog_image.show_all()
|
|
dialog_image.run()
|
|
dialog_image.destroy()
|
|
|
|
def get_banner(self):
|
|
def fetch_banner():
|
|
game_name = self.entry_title.get_text().strip()
|
|
if not game_name:
|
|
return
|
|
|
|
api_url = f"https://steamgrid.usebottles.com/api/search/{game_name}"
|
|
try:
|
|
response = requests.get(api_url)
|
|
response.raise_for_status()
|
|
image_url = response.text.strip('"')
|
|
|
|
with open(self.banner_path_temp, "wb") as image_file:
|
|
image_file.write(requests.get(image_url).content)
|
|
|
|
GLib.idle_add(self.update_image_banner)
|
|
|
|
except requests.RequestException as e:
|
|
print(f"Error fetching the banner: {e}")
|
|
|
|
# Start the thread
|
|
threading.Thread(target=fetch_banner, daemon=True).start()
|
|
|
|
def update_image_banner(self):
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(self.banner_path_temp, 260, 390, True)
|
|
self.image_banner.set_from_pixbuf(pixbuf)
|
|
self.image_banner2.set_from_pixbuf(pixbuf)
|
|
|
|
def on_entry_focus_out(self, entry_title, event):
|
|
if entry_title.get_text() != "":
|
|
self.get_banner()
|
|
else:
|
|
shutil.copyfile(faugus_banner, self.banner_path_temp)
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(self.banner_path_temp, 260, 390, True)
|
|
self.image_banner.set_from_pixbuf(pixbuf)
|
|
self.image_banner2.set_from_pixbuf(pixbuf)
|
|
|
|
def on_checkbox_addapp_toggled(self, checkbox):
|
|
is_active = checkbox.get_active()
|
|
self.entry_addapp.set_sensitive(is_active)
|
|
self.button_search_addapp.set_sensitive(is_active)
|
|
|
|
def on_button_search_addapp_clicked(self, widget):
|
|
filechooser = Gtk.FileChooserNative(
|
|
title=_("Select an additional application"),
|
|
transient_for=self,
|
|
action=Gtk.FileChooserAction.OPEN,
|
|
accept_label=_("Open"),
|
|
cancel_label=_("Cancel"),
|
|
)
|
|
|
|
windows_filter = Gtk.FileFilter()
|
|
windows_filter.set_name(_("Windows files"))
|
|
windows_filter.add_pattern("*.exe")
|
|
windows_filter.add_pattern("*.msi")
|
|
windows_filter.add_pattern("*.bat")
|
|
windows_filter.add_pattern("*.lnk")
|
|
windows_filter.add_pattern("*.reg")
|
|
|
|
all_files_filter = Gtk.FileFilter()
|
|
all_files_filter.set_name(_("All files"))
|
|
all_files_filter.add_pattern("*")
|
|
|
|
filechooser.add_filter(windows_filter)
|
|
filechooser.add_filter(all_files_filter)
|
|
filechooser.set_filter(windows_filter)
|
|
|
|
response = filechooser.run()
|
|
|
|
if response == Gtk.ResponseType.ACCEPT:
|
|
selected_file = filechooser.get_filename()
|
|
if selected_file:
|
|
self.entry_addapp.set_text(selected_file)
|
|
|
|
filechooser.destroy()
|
|
|
|
def on_combobox_changed(self, combobox):
|
|
active_index = combobox.get_active()
|
|
|
|
def cleanup_fields():
|
|
self.entry_title.set_text("")
|
|
self.entry_launch_arguments.set_text("")
|
|
self.entry_path.set_text("")
|
|
self.entry_prefix.set_text("")
|
|
self.checkbox_shortcut_desktop.set_active(False)
|
|
self.checkbox_shortcut_appmenu.set_active(False)
|
|
self.checkbox_shortcut_steam.set_active(False)
|
|
self.entry_protonfix.set_text("")
|
|
self.entry_launch_arguments.set_text("")
|
|
self.entry_game_arguments.set_text("")
|
|
self.checkbox_addapp.set_active(False)
|
|
self.entry_addapp.set_text("")
|
|
self.checkbox_mangohud.set_active(self.default_mangohud)
|
|
self.checkbox_gamemode.set_active(self.default_gamemode)
|
|
self.checkbox_disable_hidraw.set_active(self.default_disable_hidraw)
|
|
self.checkbox_prevent_sleep.set_active(self.default_prevent_sleep)
|
|
self.lossless_enabled = False
|
|
self.lossless_multiplier = 1
|
|
self.lossless_flow = 100
|
|
self.lossless_performance = False
|
|
self.lossless_hdr = False
|
|
self.lossless_present = "VSync/FIFO"
|
|
self.combobox_steam_title.get_style_context().remove_class("combobox")
|
|
self.entry_title.get_style_context().remove_class("entry")
|
|
self.entry_prefix.get_style_context().remove_class("entry")
|
|
self.entry_path.get_style_context().remove_class("entry")
|
|
|
|
cleanup_fields()
|
|
|
|
if active_index == 0:
|
|
self.grid_title.set_visible(True)
|
|
self.grid_steam_title.set_visible(False)
|
|
self.grid_path.set_visible(True)
|
|
self.grid_runner.set_visible(True)
|
|
self.grid_prefix.set_visible(True)
|
|
self.button_winetricks.set_visible(True)
|
|
self.button_winecfg.set_visible(True)
|
|
self.button_run.set_visible(True)
|
|
self.grid_protonfix.set_visible(True)
|
|
self.grid_addapp.set_visible(True)
|
|
self.checkbox_disable_hidraw.set_visible(True)
|
|
self.checkbox_prevent_sleep.set_visible(True)
|
|
self.button_shortcut_icon.set_image(self.set_image_shortcut_icon())
|
|
self.checkbox_shortcut_steam.set_visible(True)
|
|
self.grid_page2.set_visible(True)
|
|
self.tab_box2.set_visible(True)
|
|
self.notebook.set_show_tabs(True)
|
|
if active_index == 1:
|
|
self.grid_title.set_visible(True)
|
|
self.grid_steam_title.set_visible(False)
|
|
self.grid_path.set_visible(True)
|
|
self.grid_runner.set_visible(False)
|
|
self.grid_prefix.set_visible(False)
|
|
self.button_winetricks.set_visible(False)
|
|
self.button_winecfg.set_visible(False)
|
|
self.button_run.set_visible(False)
|
|
self.grid_protonfix.set_visible(False)
|
|
self.grid_addapp.set_visible(False)
|
|
self.checkbox_disable_hidraw.set_visible(False)
|
|
self.checkbox_disable_hidraw.set_active(False)
|
|
self.checkbox_prevent_sleep.set_visible(True)
|
|
self.button_shortcut_icon.set_image(self.set_image_shortcut_icon())
|
|
self.checkbox_shortcut_steam.set_visible(True)
|
|
self.grid_page2.set_visible(True)
|
|
self.tab_box2.set_visible(True)
|
|
self.notebook.set_show_tabs(True)
|
|
if active_index == 2:
|
|
self.grid_title.set_visible(False)
|
|
self.grid_steam_title.set_visible(True)
|
|
self.grid_path.set_visible(False)
|
|
self.grid_runner.set_visible(False)
|
|
self.grid_prefix.set_visible(False)
|
|
self.button_winetricks.set_visible(False)
|
|
self.button_winecfg.set_visible(False)
|
|
self.button_run.set_visible(False)
|
|
self.grid_protonfix.set_visible(False)
|
|
self.grid_addapp.set_visible(False)
|
|
self.checkbox_disable_hidraw.set_visible(False)
|
|
self.checkbox_disable_hidraw.set_active(False)
|
|
self.checkbox_prevent_sleep.set_visible(True)
|
|
self.button_shortcut_icon.set_image(self.set_image_shortcut_icon())
|
|
self.checkbox_shortcut_steam.set_visible(False)
|
|
self.grid_page2.set_visible(False)
|
|
self.tab_box2.set_visible(False)
|
|
self.notebook.set_show_tabs(False)
|
|
elif active_index == 3:
|
|
self.grid_title.set_visible(False)
|
|
self.grid_steam_title.set_visible(False)
|
|
self.grid_path.set_visible(False)
|
|
self.grid_runner.set_visible(True)
|
|
self.grid_prefix.set_visible(True)
|
|
self.button_winetricks.set_visible(True)
|
|
self.button_winecfg.set_visible(True)
|
|
self.button_run.set_visible(True)
|
|
self.grid_protonfix.set_visible(True)
|
|
self.grid_addapp.set_visible(False)
|
|
self.checkbox_disable_hidraw.set_visible(True)
|
|
self.checkbox_prevent_sleep.set_visible(True)
|
|
self.entry_launch_arguments.set_text("WINE_SIMULATE_WRITECOPY=1 PROTON_ENABLE_WAYLAND=0")
|
|
self.entry_title.set_text(self.combobox_launcher.get_active_text())
|
|
self.entry_path.set_text(
|
|
f"{self.entry_prefix.get_text()}/drive_c/Program Files (x86)/Battle.net/Battle.net.exe")
|
|
shutil.copyfile(battle_icon, os.path.expanduser(self.icon_temp))
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(self.icon_temp)
|
|
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
|
image = Gtk.Image.new_from_file(self.icon_temp)
|
|
image.set_from_pixbuf(scaled_pixbuf)
|
|
self.button_shortcut_icon.set_image(image)
|
|
self.checkbox_shortcut_steam.set_visible(True)
|
|
self.grid_page2.set_visible(True)
|
|
self.tab_box2.set_visible(True)
|
|
self.notebook.set_show_tabs(True)
|
|
elif active_index == 4:
|
|
self.grid_title.set_visible(False)
|
|
self.grid_steam_title.set_visible(False)
|
|
self.grid_path.set_visible(False)
|
|
self.grid_runner.set_visible(True)
|
|
self.grid_prefix.set_visible(True)
|
|
self.button_winetricks.set_visible(True)
|
|
self.button_winecfg.set_visible(True)
|
|
self.button_run.set_visible(True)
|
|
self.grid_protonfix.set_visible(True)
|
|
self.grid_addapp.set_visible(False)
|
|
self.checkbox_disable_hidraw.set_visible(True)
|
|
self.checkbox_prevent_sleep.set_visible(True)
|
|
self.entry_launch_arguments.set_text("PROTON_ENABLE_WAYLAND=0")
|
|
self.entry_title.set_text(self.combobox_launcher.get_active_text())
|
|
self.entry_path.set_text(
|
|
f"{self.entry_prefix.get_text()}/drive_c/Program Files/Electronic Arts/EA Desktop/EA Desktop/EALauncher.exe")
|
|
shutil.copyfile(ea_icon, os.path.expanduser(self.icon_temp))
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(self.icon_temp)
|
|
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
|
image = Gtk.Image.new_from_file(self.icon_temp)
|
|
image.set_from_pixbuf(scaled_pixbuf)
|
|
self.button_shortcut_icon.set_image(image)
|
|
self.checkbox_shortcut_steam.set_visible(True)
|
|
self.grid_page2.set_visible(True)
|
|
self.tab_box2.set_visible(True)
|
|
self.notebook.set_show_tabs(True)
|
|
elif active_index == 5:
|
|
self.grid_title.set_visible(False)
|
|
self.grid_steam_title.set_visible(False)
|
|
self.grid_path.set_visible(False)
|
|
self.grid_runner.set_visible(True)
|
|
self.grid_prefix.set_visible(True)
|
|
self.button_winetricks.set_visible(True)
|
|
self.button_winecfg.set_visible(True)
|
|
self.button_run.set_visible(True)
|
|
self.grid_protonfix.set_visible(True)
|
|
self.grid_addapp.set_visible(False)
|
|
self.checkbox_disable_hidraw.set_visible(True)
|
|
self.checkbox_prevent_sleep.set_visible(True)
|
|
self.entry_title.set_text(self.combobox_launcher.get_active_text())
|
|
self.entry_path.set_text(
|
|
f"{self.entry_prefix.get_text()}/drive_c/Program Files/Epic Games/Launcher/Portal/Binaries/Win64/EpicGamesLauncher.exe")
|
|
shutil.copyfile(epic_icon, os.path.expanduser(self.icon_temp))
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(self.icon_temp)
|
|
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
|
image = Gtk.Image.new_from_file(self.icon_temp)
|
|
image.set_from_pixbuf(scaled_pixbuf)
|
|
self.button_shortcut_icon.set_image(image)
|
|
self.checkbox_shortcut_steam.set_visible(True)
|
|
self.grid_page2.set_visible(True)
|
|
self.tab_box2.set_visible(True)
|
|
self.notebook.set_show_tabs(True)
|
|
elif active_index == 6:
|
|
self.grid_title.set_visible(False)
|
|
self.grid_steam_title.set_visible(False)
|
|
self.grid_path.set_visible(False)
|
|
self.grid_runner.set_visible(True)
|
|
self.grid_prefix.set_visible(True)
|
|
self.button_winetricks.set_visible(True)
|
|
self.button_winecfg.set_visible(True)
|
|
self.button_run.set_visible(True)
|
|
self.grid_protonfix.set_visible(True)
|
|
self.grid_addapp.set_visible(False)
|
|
self.checkbox_disable_hidraw.set_visible(True)
|
|
self.checkbox_prevent_sleep.set_visible(True)
|
|
self.entry_launch_arguments.set_text("PROTON_ENABLE_WAYLAND=0")
|
|
self.entry_title.set_text(self.combobox_launcher.get_active_text())
|
|
self.entry_path.set_text(
|
|
f"{self.entry_prefix.get_text()}/drive_c/Program Files (x86)/Ubisoft/Ubisoft Game Launcher/UbisoftConnect.exe")
|
|
shutil.copyfile(ubisoft_icon, os.path.expanduser(self.icon_temp))
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(self.icon_temp)
|
|
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
|
image = Gtk.Image.new_from_file(self.icon_temp)
|
|
image.set_from_pixbuf(scaled_pixbuf)
|
|
self.button_shortcut_icon.set_image(image)
|
|
self.checkbox_shortcut_steam.set_visible(True)
|
|
self.grid_page2.set_visible(True)
|
|
self.tab_box2.set_visible(True)
|
|
self.notebook.set_show_tabs(True)
|
|
elif active_index == 7:
|
|
self.grid_title.set_visible(False)
|
|
self.grid_steam_title.set_visible(False)
|
|
self.grid_path.set_visible(False)
|
|
self.grid_runner.set_visible(True)
|
|
self.grid_prefix.set_visible(True)
|
|
self.button_winetricks.set_visible(True)
|
|
self.button_winecfg.set_visible(True)
|
|
self.button_run.set_visible(True)
|
|
self.grid_protonfix.set_visible(True)
|
|
self.grid_addapp.set_visible(False)
|
|
self.checkbox_disable_hidraw.set_visible(True)
|
|
self.checkbox_prevent_sleep.set_visible(True)
|
|
self.entry_launch_arguments.set_text("PROTON_ENABLE_WAYLAND=0")
|
|
self.entry_title.set_text(self.combobox_launcher.get_active_text())
|
|
self.entry_path.set_text(
|
|
f"{self.entry_prefix.get_text()}/drive_c/Program Files/Rockstar Games/Launcher/Launcher.exe")
|
|
shutil.copyfile(rockstar_icon, os.path.expanduser(self.icon_temp))
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(self.icon_temp)
|
|
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
|
image = Gtk.Image.new_from_file(self.icon_temp)
|
|
image.set_from_pixbuf(scaled_pixbuf)
|
|
self.button_shortcut_icon.set_image(image)
|
|
self.checkbox_shortcut_steam.set_visible(True)
|
|
self.grid_page2.set_visible(True)
|
|
self.tab_box2.set_visible(True)
|
|
self.notebook.set_show_tabs(True)
|
|
if self.interface_mode == "Banners":
|
|
if self.entry_title.get_text() != "":
|
|
self.get_banner()
|
|
else:
|
|
shutil.copyfile(faugus_banner, self.banner_path_temp)
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(self.banner_path_temp, 260, 390, True)
|
|
self.image_banner.set_from_pixbuf(pixbuf)
|
|
self.image_banner2.set_from_pixbuf(pixbuf)
|
|
|
|
def populate_combobox_with_launchers(self):
|
|
self.combobox_launcher.append_text(_("Windows Game"))
|
|
self.combobox_launcher.append_text(_("Linux Game"))
|
|
self.combobox_launcher.append_text(_("Steam Game"))
|
|
self.combobox_launcher.append_text("Battle.net")
|
|
self.combobox_launcher.append_text("EA App")
|
|
self.combobox_launcher.append_text("Epic Games")
|
|
self.combobox_launcher.append_text("Ubisoft Connect") # self.combobox_launcher.append_text("HoYoPlay")
|
|
self.combobox_launcher.append_text("Rockstar Launcher")
|
|
|
|
def populate_combobox_with_runners(self):
|
|
# List of default entries
|
|
self.combobox_runner.append_text("GE-Proton Latest (default)")
|
|
self.combobox_runner.append_text("UMU-Proton Latest")
|
|
self.combobox_runner.append_text("Proton-EM Latest")
|
|
if os.path.exists("/usr/share/steam/compatibilitytools.d/proton-cachyos-slr/"):
|
|
self.combobox_runner.append_text("Proton-CachyOS")
|
|
|
|
# Path to the directory containing the folders
|
|
if IS_FLATPAK:
|
|
runner_path = Path(os.path.expanduser("~/.local/share/Steam/compatibilitytools.d"))
|
|
else:
|
|
runner_path = f'{share_dir}/Steam/compatibilitytools.d/'
|
|
|
|
try:
|
|
# Check if the directory exists
|
|
if os.path.exists(runner_path):
|
|
# List to hold version directories
|
|
versions = []
|
|
# Iterate over the folders in the directory
|
|
for entry in os.listdir(runner_path):
|
|
entry_path = os.path.join(runner_path, entry)
|
|
# Add to list only if it's a directory and not "UMU-Latest"
|
|
if (
|
|
os.path.isdir(entry_path)
|
|
and entry not in ("UMU-Latest", "LegacyRuntime")
|
|
and not entry.startswith("Proton-GE Latest")
|
|
and not entry.startswith("Proton-EM Latest")
|
|
):
|
|
versions.append(entry)
|
|
|
|
# Sort versions in descending order
|
|
def version_key(v):
|
|
# Remove 'GE-Proton' and split the remaining part into segments of digits and non-digits
|
|
v_parts = re.split(r'(\d+)', v.replace('GE-Proton', ''))
|
|
# Convert numeric parts to integers for proper sorting
|
|
return [int(part) if part.isdigit() else part for part in v_parts]
|
|
|
|
versions.sort(key=version_key, reverse=True)
|
|
|
|
# Add sorted versions to ComboBox
|
|
for version in versions:
|
|
self.combobox_runner.append_text(version)
|
|
|
|
except Exception as e:
|
|
print(f"Error accessing the directory: {e}")
|
|
|
|
# Set the active item, if desired
|
|
self.combobox_runner.set_active(0)
|
|
|
|
cell_renderer = self.combobox_runner.get_cells()[0]
|
|
cell_renderer.set_property("ellipsize", Pango.EllipsizeMode.END)
|
|
cell_renderer.set_property("max-width-chars", 20)
|
|
|
|
def on_entry_changed(self, widget, entry):
|
|
if entry.get_text():
|
|
entry.get_style_context().remove_class("entry")
|
|
|
|
def load_config(self):
|
|
cfg = ConfigManager()
|
|
|
|
self.default_runner = cfg.config.get('default-runner', '')
|
|
self.default_prefix = cfg.config.get('default-prefix', '')
|
|
self.default_mangohud = cfg.config.get('mangohud') == 'True'
|
|
self.default_gamemode = cfg.config.get('gamemode') == 'True'
|
|
self.default_disable_hidraw = cfg.config.get('disable-hidraw') == 'True'
|
|
self.default_prevent_sleep = cfg.config.get('prevent-sleep') == 'True'
|
|
|
|
def on_button_run_clicked(self, widget):
|
|
self.set_sensitive(False)
|
|
|
|
validation_result = self.validate_fields(entry="prefix")
|
|
if not validation_result:
|
|
self.set_sensitive(True)
|
|
return
|
|
|
|
filechooser = Gtk.FileChooserNative(
|
|
title=_("Select a file to run inside the prefix"),
|
|
transient_for=self,
|
|
action=Gtk.FileChooserAction.OPEN,
|
|
accept_label=_("Open"),
|
|
cancel_label=_("Cancel"),
|
|
)
|
|
|
|
windows_filter = Gtk.FileFilter()
|
|
windows_filter.set_name(_("Windows files"))
|
|
windows_filter.add_pattern("*.exe")
|
|
windows_filter.add_pattern("*.msi")
|
|
windows_filter.add_pattern("*.bat")
|
|
windows_filter.add_pattern("*.lnk")
|
|
windows_filter.add_pattern("*.reg")
|
|
|
|
all_files_filter = Gtk.FileFilter()
|
|
all_files_filter.set_name(_("All files"))
|
|
all_files_filter.add_pattern("*")
|
|
|
|
filechooser.add_filter(windows_filter)
|
|
filechooser.add_filter(all_files_filter)
|
|
filechooser.set_filter(windows_filter)
|
|
|
|
response = filechooser.run()
|
|
|
|
if response == Gtk.ResponseType.ACCEPT:
|
|
file_run = filechooser.get_filename()
|
|
title = self.entry_title.get_text()
|
|
prefix = self.entry_prefix.get_text()
|
|
title_formatted = format_title(title)
|
|
runner = self.combobox_runner.get_active_text()
|
|
|
|
escaped_file_run = file_run.replace("'", "'\\''")
|
|
runner = convert_runner(runner)
|
|
|
|
command_parts = []
|
|
|
|
if title_formatted:
|
|
command_parts.append(f"LOG_DIR={title_formatted}")
|
|
if prefix:
|
|
command_parts.append(f"WINEPREFIX='{prefix}'")
|
|
if title_formatted:
|
|
command_parts.append(f"GAMEID={title_formatted}")
|
|
if runner:
|
|
if runner == "Proton-CachyOS":
|
|
command_parts.append(f"PROTONPATH='{proton_cachyos}'")
|
|
else:
|
|
command_parts.append(f"PROTONPATH='{runner}'")
|
|
if escaped_file_run.endswith(".reg"):
|
|
command_parts.append(f"'{umu_run}' regedit '{escaped_file_run}'")
|
|
else:
|
|
command_parts.append(f"'{umu_run}' '{escaped_file_run}'")
|
|
|
|
command = ' '.join(command_parts)
|
|
print(command)
|
|
|
|
faugus_run_path = faugus_run
|
|
|
|
def run_command():
|
|
process = subprocess.Popen([sys.executable, faugus_run_path, command])
|
|
process.wait()
|
|
GLib.idle_add(self.set_sensitive, True)
|
|
GLib.idle_add(self.parent_window.set_sensitive, True)
|
|
GLib.idle_add(self.blocking_window.destroy)
|
|
|
|
self.blocking_window = Gtk.Window()
|
|
self.blocking_window.set_transient_for(self.parent_window)
|
|
self.blocking_window.set_decorated(False)
|
|
self.blocking_window.set_modal(True)
|
|
|
|
command_thread = threading.Thread(target=run_command)
|
|
command_thread.start()
|
|
|
|
else:
|
|
self.set_sensitive(True)
|
|
|
|
filechooser.destroy()
|
|
|
|
def on_button_search_protonfix_clicked(self, widget):
|
|
webbrowser.open("https://umu.openwinecomponents.org/")
|
|
|
|
def set_image_shortcut_icon(self):
|
|
|
|
image_path = faugus_png
|
|
shutil.copyfile(image_path, self.icon_temp)
|
|
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(self.icon_temp)
|
|
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
|
|
|
image = Gtk.Image.new_from_file(self.icon_temp)
|
|
image.set_from_pixbuf(scaled_pixbuf)
|
|
|
|
return image
|
|
|
|
def on_button_shortcut_icon_clicked(self, widget):
|
|
self.set_sensitive(False)
|
|
|
|
validation_result = self.validate_fields(entry="path")
|
|
if not validation_result:
|
|
self.set_sensitive(True)
|
|
return
|
|
|
|
path = self.entry_path.get_text()
|
|
|
|
if os.path.isfile(path):
|
|
if not os.path.exists(self.icon_directory):
|
|
os.makedirs(self.icon_directory)
|
|
|
|
try:
|
|
command = f'icoextract "{path}" "{self.icon_extracted}"'
|
|
result = subprocess.run(command, shell=True, text=True, capture_output=True)
|
|
|
|
if result.returncode != 0:
|
|
if "NoIconsAvailableError" in result.stderr or "PEFormatError" in result.stderr:
|
|
print("The file does not contain icons.")
|
|
self.button_shortcut_icon.set_image(self.set_image_shortcut_icon())
|
|
else:
|
|
print(f"Error extracting icon: {result.stderr}")
|
|
else:
|
|
command_magick = shutil.which("magick") or shutil.which("convert")
|
|
os.system(f'{command_magick} "{self.icon_extracted}" "{self.icon_converted}"')
|
|
if os.path.isfile(self.icon_extracted):
|
|
os.remove(self.icon_extracted)
|
|
|
|
except Exception as e:
|
|
print(f"An error occurred: {e}")
|
|
|
|
def is_valid_image(file_path):
|
|
try:
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(file_path)
|
|
return pixbuf is not None
|
|
except Exception:
|
|
return False
|
|
|
|
filechooser = Gtk.FileChooserNative.new(
|
|
_("Select an icon for the shortcut"),
|
|
self,
|
|
Gtk.FileChooserAction.OPEN,
|
|
_("Open"),
|
|
_("Cancel")
|
|
)
|
|
|
|
filter_ico = Gtk.FileFilter()
|
|
filter_ico.set_name(_("Image files"))
|
|
filter_ico.add_pattern("*.png")
|
|
filter_ico.add_pattern("*.jpg")
|
|
filter_ico.add_pattern("*.jpeg")
|
|
filter_ico.add_pattern("*.jxl")
|
|
filter_ico.add_pattern("*.bmp")
|
|
filter_ico.add_pattern("*.gif")
|
|
filter_ico.add_pattern("*.svg")
|
|
filter_ico.add_pattern("*.ico")
|
|
filechooser.add_filter(filter_ico)
|
|
|
|
filechooser.set_current_folder(self.icon_directory)
|
|
|
|
response = filechooser.run()
|
|
if response == Gtk.ResponseType.ACCEPT:
|
|
file_path = filechooser.get_filename()
|
|
if not file_path or not is_valid_image(file_path):
|
|
dialog_image = Gtk.Dialog(title="Faugus Launcher", transient_for=self, modal=True)
|
|
dialog_image.set_resizable(False)
|
|
dialog_image.set_icon_from_file(faugus_png)
|
|
subprocess.Popen(["canberra-gtk-play", "-f", faugus_notification])
|
|
|
|
label = Gtk.Label()
|
|
label.set_label(_("The selected file is not a valid image."))
|
|
label.set_halign(Gtk.Align.CENTER)
|
|
|
|
label2 = Gtk.Label()
|
|
label2.set_label(_("Please choose another one."))
|
|
label2.set_halign(Gtk.Align.CENTER)
|
|
|
|
button_yes = Gtk.Button(label=_("Ok"))
|
|
button_yes.set_size_request(150, -1)
|
|
button_yes.connect("clicked", lambda x: dialog_image.response(Gtk.ResponseType.YES))
|
|
|
|
content_area = dialog_image.get_content_area()
|
|
content_area.set_border_width(0)
|
|
content_area.set_halign(Gtk.Align.CENTER)
|
|
content_area.set_valign(Gtk.Align.CENTER)
|
|
content_area.set_vexpand(True)
|
|
content_area.set_hexpand(True)
|
|
|
|
box_top = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
|
box_top.set_margin_start(20)
|
|
box_top.set_margin_end(20)
|
|
box_top.set_margin_top(20)
|
|
box_top.set_margin_bottom(20)
|
|
|
|
box_bottom = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
|
box_bottom.set_margin_start(10)
|
|
box_bottom.set_margin_end(10)
|
|
box_bottom.set_margin_bottom(10)
|
|
|
|
box_top.pack_start(label, True, True, 0)
|
|
box_top.pack_start(label2, True, True, 0)
|
|
box_bottom.pack_start(button_yes, True, True, 0)
|
|
|
|
content_area.add(box_top)
|
|
content_area.add(box_bottom)
|
|
|
|
dialog_image.show_all()
|
|
dialog_image.run()
|
|
dialog_image.destroy()
|
|
else:
|
|
shutil.copyfile(file_path, self.icon_temp)
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(self.icon_temp)
|
|
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
|
image = Gtk.Image.new_from_file(self.icon_temp)
|
|
image.set_from_pixbuf(scaled_pixbuf)
|
|
self.button_shortcut_icon.set_image(image)
|
|
|
|
filechooser.destroy()
|
|
|
|
if os.path.isdir(self.icon_directory):
|
|
shutil.rmtree(self.icon_directory)
|
|
self.set_sensitive(True)
|
|
|
|
def find_largest_resolution(self, directory):
|
|
largest_image = None
|
|
largest_resolution = (0, 0) # (width, height)
|
|
|
|
# Define a set of valid image extensions
|
|
valid_image_extensions = {'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff'}
|
|
|
|
for file_name in os.listdir(directory):
|
|
file_path = os.path.join(directory, file_name)
|
|
if os.path.isfile(file_path):
|
|
# Check if the file has a valid image extension
|
|
if os.path.splitext(file_name)[1].lower() in valid_image_extensions:
|
|
try:
|
|
with Image.open(file_path) as img:
|
|
width, height = img.size
|
|
if width * height > largest_resolution[0] * largest_resolution[1]:
|
|
largest_resolution = (width, height)
|
|
largest_image = file_path
|
|
except IOError:
|
|
print(f"Unable to open {file_path}")
|
|
|
|
return largest_image
|
|
|
|
def update_preview(self, dialog):
|
|
if file_path := dialog.get_preview_filename():
|
|
try:
|
|
# Create an image widget for the thumbnail
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(file_path)
|
|
|
|
# Resize the thumbnail if it's too large, maintaining the aspect ratio
|
|
max_width = 400
|
|
max_height = 400
|
|
width = pixbuf.get_width()
|
|
height = pixbuf.get_height()
|
|
|
|
if width > max_width or height > max_height:
|
|
# Calculate the new width and height while maintaining the aspect ratio
|
|
ratio = min(max_width / width, max_height / height)
|
|
new_width = int(width * ratio)
|
|
new_height = int(height * ratio)
|
|
pixbuf = pixbuf.scale_simple(new_width, new_height, GdkPixbuf.InterpType.BILINEAR)
|
|
|
|
image = Gtk.Image.new_from_pixbuf(pixbuf)
|
|
dialog.set_preview_widget(image)
|
|
dialog.set_preview_widget_active(True)
|
|
dialog.get_preview_widget().set_size_request(max_width, max_height)
|
|
except GLib.Error:
|
|
dialog.set_preview_widget_active(False)
|
|
else:
|
|
dialog.set_preview_widget_active(False)
|
|
|
|
def check_existing_shortcut(self):
|
|
# Check if the shortcut already exists and mark or unmark the checkbox
|
|
title = self.entry_title.get_text().strip()
|
|
if not title:
|
|
return # If there's no title, there's no shortcut to check
|
|
|
|
title_formatted = format_title(title)
|
|
desktop_file_path = f"{desktop_dir}/{title_formatted}.desktop"
|
|
applications_shortcut_path = f"{app_dir}/{title_formatted}.desktop"
|
|
|
|
# Mark the checkbox if the shortcut exists
|
|
self.checkbox_shortcut_desktop.set_active(os.path.exists(desktop_file_path))
|
|
self.checkbox_shortcut_appmenu.set_active(os.path.exists(applications_shortcut_path))
|
|
|
|
def update_prefix_entry(self, entry):
|
|
# Update the prefix entry based on the title and self.default_prefix
|
|
title_formatted = format_title(entry.get_text())
|
|
prefix = os.path.expanduser(self.default_prefix) + "/" + title_formatted
|
|
self.entry_prefix.set_text(prefix)
|
|
|
|
def on_button_winecfg_clicked(self, widget):
|
|
self.set_sensitive(False)
|
|
# Handle the click event of the Winetricks button
|
|
validation_result = self.validate_fields(entry="prefix")
|
|
if not validation_result:
|
|
self.set_sensitive(True)
|
|
return
|
|
|
|
title = self.entry_title.get_text()
|
|
prefix = self.entry_prefix.get_text()
|
|
title_formatted = format_title(title)
|
|
runner = self.combobox_runner.get_active_text()
|
|
|
|
runner = convert_runner(runner)
|
|
|
|
command_parts = []
|
|
|
|
# Add command parts if they are not empty
|
|
if title_formatted:
|
|
command_parts.append(f"LOG_DIR='{title_formatted}'")
|
|
if prefix:
|
|
command_parts.append(f"WINEPREFIX='{prefix}'")
|
|
if title_formatted:
|
|
command_parts.append(f"GAMEID={title_formatted}")
|
|
if runner:
|
|
if runner == "Proton-CachyOS":
|
|
command_parts.append(f"PROTONPATH='{proton_cachyos}'")
|
|
else:
|
|
command_parts.append(f"PROTONPATH='{runner}'")
|
|
|
|
# Add the fixed command and remaining arguments
|
|
command_parts.append(f"'{umu_run}'")
|
|
command_parts.append("'winecfg'")
|
|
|
|
# Join all parts into a single command
|
|
command = ' '.join(command_parts)
|
|
|
|
print(command)
|
|
|
|
# faugus-run path
|
|
faugus_run_path = faugus_run
|
|
|
|
def run_command():
|
|
process = subprocess.Popen([sys.executable, faugus_run_path, command])
|
|
process.wait()
|
|
GLib.idle_add(self.set_sensitive, True)
|
|
GLib.idle_add(self.parent_window.set_sensitive, True)
|
|
GLib.idle_add(self.blocking_window.destroy)
|
|
|
|
self.blocking_window = Gtk.Window()
|
|
self.blocking_window.set_transient_for(self.parent_window)
|
|
self.blocking_window.set_decorated(False)
|
|
self.blocking_window.set_modal(True)
|
|
|
|
command_thread = threading.Thread(target=run_command)
|
|
command_thread.start()
|
|
|
|
def on_button_winetricks_clicked(self, widget):
|
|
self.set_sensitive(False)
|
|
# Handle the click event of the Winetricks button
|
|
validation_result = self.validate_fields(entry="prefix")
|
|
if not validation_result:
|
|
self.set_sensitive(True)
|
|
return
|
|
|
|
title = self.entry_title.get_text()
|
|
prefix = self.entry_prefix.get_text()
|
|
title_formatted = format_title(title)
|
|
runner = self.combobox_runner.get_active_text()
|
|
|
|
runner = convert_runner(runner)
|
|
|
|
command_parts = []
|
|
|
|
# Add command parts if they are not empty
|
|
if title_formatted:
|
|
command_parts.append(f"LOG_DIR={title_formatted}")
|
|
if prefix:
|
|
command_parts.append(f"WINEPREFIX='{prefix}'")
|
|
command_parts.append(f"GAMEID=winetricks-gui")
|
|
command_parts.append(f"STORE=none")
|
|
if runner:
|
|
if runner == "Proton-CachyOS":
|
|
command_parts.append(f"PROTONPATH='{proton_cachyos}'")
|
|
else:
|
|
command_parts.append(f"PROTONPATH='{runner}'")
|
|
|
|
# Add the fixed command and remaining arguments
|
|
command_parts.append(f"'{umu_run}'")
|
|
command_parts.append("''")
|
|
|
|
# Join all parts into a single command
|
|
command = ' '.join(command_parts)
|
|
|
|
print(command)
|
|
|
|
# faugus-run path
|
|
faugus_run_path = faugus_run
|
|
|
|
def run_command():
|
|
process = subprocess.Popen([sys.executable, faugus_run_path, command, "winetricks"])
|
|
process.wait()
|
|
GLib.idle_add(self.set_sensitive, True)
|
|
GLib.idle_add(self.parent_window.set_sensitive, True)
|
|
GLib.idle_add(self.blocking_window.destroy)
|
|
|
|
self.blocking_window = Gtk.Window()
|
|
self.blocking_window.set_transient_for(self.parent_window)
|
|
self.blocking_window.set_decorated(False)
|
|
self.blocking_window.set_modal(True)
|
|
|
|
command_thread = threading.Thread(target=run_command)
|
|
command_thread.start()
|
|
|
|
def on_button_search_clicked(self, widget):
|
|
if not self.entry_path.get_text():
|
|
initial_folder = os.path.expanduser("~/")
|
|
else:
|
|
initial_folder = os.path.dirname(self.entry_path.get_text())
|
|
|
|
filechooser = Gtk.FileChooserNative(
|
|
title=_("Select the game's .exe"),
|
|
transient_for=self,
|
|
action=Gtk.FileChooserAction.OPEN,
|
|
accept_label=_("Open"),
|
|
cancel_label=_("Cancel"),
|
|
)
|
|
|
|
filechooser.set_current_folder(initial_folder)
|
|
|
|
if self.combobox_launcher.get_active() != 1:
|
|
windows_filter = Gtk.FileFilter()
|
|
windows_filter.set_name(_("Windows files"))
|
|
windows_filter.add_pattern("*.exe")
|
|
windows_filter.add_pattern("*.msi")
|
|
windows_filter.add_pattern("*.bat")
|
|
windows_filter.add_pattern("*.lnk")
|
|
windows_filter.add_pattern("*.reg")
|
|
|
|
all_files_filter = Gtk.FileFilter()
|
|
all_files_filter.set_name(_("All files"))
|
|
all_files_filter.add_pattern("*")
|
|
|
|
filechooser.add_filter(windows_filter)
|
|
filechooser.add_filter(all_files_filter)
|
|
filechooser.set_filter(windows_filter)
|
|
|
|
response = filechooser.run()
|
|
|
|
if response == Gtk.ResponseType.ACCEPT:
|
|
path = filechooser.get_filename()
|
|
|
|
if not os.path.exists(self.icon_directory):
|
|
os.makedirs(self.icon_directory)
|
|
|
|
try:
|
|
command = f'icoextract "{path}" "{self.icon_extracted}"'
|
|
result = subprocess.run(command, shell=True, text=True, capture_output=True)
|
|
|
|
if result.returncode != 0:
|
|
if "NoIconsAvailableError" in result.stderr or "PEFormatError" in result.stderr:
|
|
print("The file does not contain icons.")
|
|
self.button_shortcut_icon.set_image(self.set_image_shortcut_icon())
|
|
else:
|
|
print(f"Error extracting icon: {result.stderr}")
|
|
else:
|
|
command_magick = shutil.which("magick") or shutil.which("convert")
|
|
os.system(f'{command_magick} "{self.icon_extracted}" "{self.icon_converted}"')
|
|
|
|
if os.path.isfile(self.icon_extracted):
|
|
os.remove(self.icon_extracted)
|
|
|
|
largest_image = self.find_largest_resolution(self.icon_directory)
|
|
shutil.move(largest_image, os.path.expanduser(self.icon_temp))
|
|
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(self.icon_temp)
|
|
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
|
image = Gtk.Image.new_from_file(self.icon_temp)
|
|
image.set_from_pixbuf(scaled_pixbuf)
|
|
|
|
self.button_shortcut_icon.set_image(image)
|
|
|
|
except Exception as e:
|
|
print(f"An error occurred: {e}")
|
|
|
|
self.entry_path.set_text(path)
|
|
|
|
if os.path.isdir(self.icon_directory):
|
|
shutil.rmtree(self.icon_directory)
|
|
|
|
filechooser.destroy()
|
|
|
|
def on_button_search_prefix_clicked(self, widget):
|
|
filechooser = Gtk.FileChooserNative(
|
|
title=_("Select a prefix location"),
|
|
transient_for=self,
|
|
action=Gtk.FileChooserAction.SELECT_FOLDER,
|
|
accept_label=_("Open"),
|
|
cancel_label=_("Cancel"),
|
|
)
|
|
|
|
if not self.entry_prefix.get_text():
|
|
filechooser.set_current_folder(os.path.expanduser(self.default_prefix))
|
|
else:
|
|
filechooser.set_current_folder(self.entry_prefix.get_text())
|
|
|
|
response = filechooser.run()
|
|
|
|
if response == Gtk.ResponseType.ACCEPT:
|
|
new_prefix = filechooser.get_filename()
|
|
self.default_prefix = new_prefix
|
|
self.entry_prefix.set_text(self.default_prefix)
|
|
|
|
filechooser.destroy()
|
|
|
|
def validate_fields(self, entry):
|
|
# Validate the input fields for title, prefix and path
|
|
title = self.entry_title.get_text()
|
|
prefix = self.entry_prefix.get_text()
|
|
path = self.entry_path.get_text()
|
|
combobox_steam = self.combobox_steam_title.get_active_text()
|
|
|
|
self.combobox_steam_title.get_style_context().remove_class("combobox")
|
|
self.entry_title.get_style_context().remove_class("entry")
|
|
self.entry_prefix.get_style_context().remove_class("entry")
|
|
self.entry_path.get_style_context().remove_class("entry")
|
|
|
|
if self.grid_steam_title.get_visible():
|
|
if not combobox_steam:
|
|
self.combobox_steam_title.get_style_context().add_class("combobox")
|
|
self.notebook.set_current_page(0)
|
|
|
|
if entry == "prefix":
|
|
if not title or not prefix:
|
|
if not title:
|
|
self.entry_title.get_style_context().add_class("entry")
|
|
self.notebook.set_current_page(0)
|
|
|
|
if not prefix:
|
|
self.entry_prefix.get_style_context().add_class("entry")
|
|
self.notebook.set_current_page(0)
|
|
|
|
return False
|
|
|
|
if entry == "path":
|
|
if not title or not path:
|
|
if not title:
|
|
self.entry_title.get_style_context().add_class("entry")
|
|
self.notebook.set_current_page(0)
|
|
|
|
if not path:
|
|
self.entry_path.get_style_context().add_class("entry")
|
|
self.notebook.set_current_page(0)
|
|
|
|
return False
|
|
|
|
if entry == "path+prefix":
|
|
if not title or not path or not prefix:
|
|
if not title:
|
|
self.entry_title.get_style_context().add_class("entry")
|
|
self.notebook.set_current_page(0)
|
|
|
|
if not path:
|
|
self.entry_path.get_style_context().add_class("entry")
|
|
self.notebook.set_current_page(0)
|
|
|
|
if not prefix:
|
|
self.entry_prefix.get_style_context().add_class("entry")
|
|
self.notebook.set_current_page(0)
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
def run_file(file_path):
|
|
cfg = ConfigManager()
|
|
|
|
default_prefix = cfg.config.get('default-prefix', '').strip('"')
|
|
mangohud = cfg.config.get('mangohud', 'False') == 'True'
|
|
gamemode = cfg.config.get('gamemode', 'False') == 'True'
|
|
disable_hidraw = cfg.config.get('disable-hidraw', 'False') == 'True'
|
|
prevent_sleep = cfg.config.get('prevent-sleep', 'False') == 'True'
|
|
default_runner = cfg.config.get('default-runner', '').strip('"')
|
|
|
|
if file_path.endswith(".reg"):
|
|
mangohud = False
|
|
gamemode = False
|
|
disable_hidraw = False
|
|
prevent_sleep = False
|
|
|
|
file_dir = os.path.dirname(os.path.abspath(file_path))
|
|
command_parts = []
|
|
|
|
if disable_hidraw:
|
|
command_parts.append("PROTON_DISABLE_HIDRAW=1")
|
|
if prevent_sleep:
|
|
command_parts.append("PREVENT_SLEEP=1")
|
|
command_parts.append(os.path.expanduser(f'WINEPREFIX="{default_prefix}/default"'))
|
|
command_parts.append("GAMEID=default")
|
|
if default_runner:
|
|
if default_runner == "Proton-CachyOS":
|
|
command_parts.append(f'PROTONPATH="{proton_cachyos}"')
|
|
else:
|
|
command_parts.append(f'PROTONPATH="{default_runner}"')
|
|
if gamemode:
|
|
command_parts.append("gamemoderun")
|
|
if mangohud:
|
|
command_parts.append("mangohud")
|
|
command_parts.append(f'"{umu_run}"')
|
|
if file_path.endswith(".reg"):
|
|
command_parts.append(f'regedit "{file_path}"')
|
|
else:
|
|
command_parts.append(f'"{file_path}"')
|
|
|
|
command = ' '.join(command_parts)
|
|
subprocess.run([faugus_run, command], cwd=file_dir)
|
|
|
|
def update_games_and_config():
|
|
if os.path.exists(games_json):
|
|
try:
|
|
with open(games_json, "r", encoding="utf-8") as f:
|
|
games = json.load(f)
|
|
except json.JSONDecodeError:
|
|
games = []
|
|
|
|
changed = False
|
|
|
|
for game in games:
|
|
title = game.get("title", "")
|
|
if title:
|
|
formatted = format_title(title)
|
|
if game.get("gameid") != formatted:
|
|
game["gameid"] = formatted
|
|
changed = True
|
|
|
|
if game.get("playtime", "") == "":
|
|
game["playtime"] = 0
|
|
changed = True
|
|
|
|
runner = game.get("runner")
|
|
if runner == "Proton-EM":
|
|
game["runner"] = "Proton-EM Latest"
|
|
changed = True
|
|
elif runner == "GE-Proton":
|
|
game["runner"] = "Proton-GE Latest"
|
|
changed = True
|
|
|
|
if title == "Battle.net" and game.get("startup_wm_class", "") != "battle.net.exe":
|
|
game["startup_wm_class"] = "battle.net.exe"
|
|
changed = True
|
|
|
|
if changed:
|
|
with open(games_json, "w", encoding="utf-8") as f:
|
|
json.dump(games, f, indent=4, ensure_ascii=False)
|
|
|
|
config_path = Path(PathManager.user_config("faugus-launcher/config.ini"))
|
|
if not config_path.exists():
|
|
return
|
|
|
|
lines = config_path.read_text(encoding="utf-8").splitlines()
|
|
new_lines = []
|
|
changed = False
|
|
|
|
for line in lines:
|
|
if line.startswith("default-runner="):
|
|
value = line.split("=", 1)[1].strip('"')
|
|
|
|
if value == "GE-Proton":
|
|
line = 'default-runner="Proton-GE Latest"'
|
|
changed = True
|
|
elif value == "Proton-EM":
|
|
line = 'default-runner="Proton-EM Latest"'
|
|
changed = True
|
|
|
|
new_lines.append(line)
|
|
|
|
if changed:
|
|
config_path.write_text("\n".join(new_lines) + "\n", encoding="utf-8")
|
|
|
|
def faugus_launcher():
|
|
os.environ["GTK_USE_PORTAL"] = "1"
|
|
apply_dark_theme()
|
|
|
|
if len(sys.argv) == 1:
|
|
app = Main()
|
|
app.connect("destroy", app.on_destroy)
|
|
Gtk.main()
|
|
|
|
elif len(sys.argv) == 2:
|
|
if sys.argv[1] == "--hide":
|
|
app = Main()
|
|
app.hide()
|
|
app.connect("destroy", app.on_destroy)
|
|
Gtk.main()
|
|
|
|
else:
|
|
print("Invalid arguments")
|
|
|
|
def main():
|
|
app = FaugusApp()
|
|
app.run(sys.argv)
|
|
|
|
if __name__ == "__main__":
|
|
update_games_and_config()
|
|
main()
|