2819 lines
115 KiB
Python
2819 lines
115 KiB
Python
#!/usr/bin/python3
|
|
|
|
import os
|
|
import re
|
|
import shutil
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
import webbrowser
|
|
|
|
import gi
|
|
|
|
gi.require_version('Gtk', '3.0')
|
|
gi.require_version('Gdk', '3.0')
|
|
|
|
from gi.repository import Gtk, Gdk, GdkPixbuf, GLib
|
|
|
|
|
|
class Main(Gtk.Window):
|
|
def __init__(self):
|
|
# Initialize the main window with title and default size
|
|
Gtk.Window.__init__(self, title="Faugus Launcher")
|
|
self.set_default_size(580, 580)
|
|
|
|
self.game_running = None
|
|
|
|
self.games = []
|
|
self.processos = {}
|
|
|
|
# Define the configuration path
|
|
config_path = os.path.expanduser("~/.config/faugus-launcher/")
|
|
# Create the configuration directory if it doesn't exist
|
|
if not os.path.exists(config_path):
|
|
os.makedirs(config_path)
|
|
self.working_directory = config_path
|
|
os.chdir(self.working_directory)
|
|
|
|
config_file = os.path.join(self.working_directory, 'config.ini')
|
|
if not os.path.exists(config_file):
|
|
self.save_config("False", "~/.config/faugus-launcher/prefixes", "False", "False", "False", "GE-Proton Latest (default)")
|
|
|
|
self.games = []
|
|
|
|
# Create main box and its components
|
|
box_main = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
box_top = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
|
box_left = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
box_right = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
box_right.set_border_width(10)
|
|
box_bottom = Gtk.Box()
|
|
|
|
# Create buttons for adding, editing, and deleting games
|
|
self.button_add = Gtk.Button(label="New")
|
|
self.button_add.connect("clicked", self.on_button_add_clicked)
|
|
self.button_add.set_size_request(50, 50)
|
|
self.button_add.set_margin_top(10)
|
|
self.button_add.set_margin_start(10)
|
|
self.button_add.set_margin_end(10)
|
|
|
|
self.button_edit = Gtk.Button(label="Edit")
|
|
self.button_edit.connect("clicked", self.on_button_edit_clicked)
|
|
self.button_edit.set_size_request(50, 50)
|
|
self.button_edit.set_margin_top(10)
|
|
self.button_edit.set_margin_start(10)
|
|
self.button_edit.set_margin_end(10)
|
|
|
|
self.button_delete = Gtk.Button(label="Del")
|
|
self.button_delete.connect("clicked", self.on_button_delete_clicked)
|
|
self.button_delete.set_size_request(50, 50)
|
|
self.button_delete.set_margin_top(10)
|
|
self.button_delete.set_margin_start(10)
|
|
self.button_delete.set_margin_end(10)
|
|
|
|
# Create button for killing processes
|
|
button_kill = Gtk.Button(label="Kill")
|
|
button_kill.connect("clicked", self.on_button_kill_clicked)
|
|
button_kill.set_tooltip_text("Force close all running games")
|
|
button_kill.set_size_request(50, 50)
|
|
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_size_request(50, 50)
|
|
button_settings.set_image(Gtk.Image.new_from_icon_name("open-menu-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_size_request(150, 50)
|
|
self.button_play.set_image(Gtk.Image.new_from_icon_name("media-playback-start-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)
|
|
|
|
# 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)
|
|
|
|
self.last_clicked_row = None
|
|
self.last_click_time = 0
|
|
|
|
# Create list box for displaying games
|
|
self.game_list = Gtk.ListBox(halign=Gtk.Align.START, valign=Gtk.Align.START)
|
|
self.game_list.connect("button-release-event", self.on_button_release_event)
|
|
scroll_box.add(self.game_list)
|
|
self.load_games()
|
|
|
|
# Pack buttons and other components into the bottom box
|
|
box_bottom.pack_start(button_settings, False, False, 0)
|
|
box_bottom.pack_end(self.button_play, False, False, 0)
|
|
box_bottom.pack_end(button_kill, False, False, 0)
|
|
|
|
# Pack buttons into the left box
|
|
box_left.pack_start(self.button_add, False, False, 0)
|
|
box_left.pack_start(self.button_edit, False, False, 0)
|
|
box_left.pack_start(self.button_delete, False, False, 0)
|
|
|
|
# Pack left and scrolled box into the top box
|
|
box_top.pack_start(box_left, False, True, 0)
|
|
box_top.pack_start(scroll_box, True, True, 0)
|
|
|
|
# Pack top and bottom boxes into the main box
|
|
box_main.pack_start(box_top, True, True, 0)
|
|
box_main.pack_end(box_bottom, False, True, 0)
|
|
self.add(box_main)
|
|
|
|
self.button_edit.set_sensitive(False)
|
|
self.button_delete.set_sensitive(False)
|
|
self.button_play.set_sensitive(False)
|
|
|
|
self.game_running2 = False
|
|
|
|
# Set signal handler for child process termination
|
|
signal.signal(signal.SIGCHLD, self.on_child_process_closed)
|
|
|
|
def load_close_onlaunch(self):
|
|
config_file = os.path.expanduser('~/.config/faugus-launcher/config.ini')
|
|
close_onlaunch = False
|
|
if os.path.exists(config_file):
|
|
with open(config_file, 'r') as f:
|
|
config_data = f.read().splitlines()
|
|
config_dict = dict(line.split('=') for line in config_data)
|
|
close_onlaunch_value = config_dict.get('close-onlaunch', '').strip('"')
|
|
if close_onlaunch_value.lower() == 'true':
|
|
close_onlaunch = True
|
|
return close_onlaunch
|
|
|
|
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):
|
|
|
|
self.checkbox_close_after_launch = settings_dialog.checkbox_close_after_launch
|
|
self.entry_default_prefix = settings_dialog.entry_default_prefix
|
|
|
|
self.checkbox_mangohud = settings_dialog.checkbox_mangohud
|
|
self.checkbox_gamemode = settings_dialog.checkbox_gamemode
|
|
self.checkbox_sc_controller = settings_dialog.checkbox_sc_controller
|
|
self.combo_box_runner = settings_dialog.combo_box_runner
|
|
|
|
checkbox_state = self.checkbox_close_after_launch.get_active()
|
|
default_prefix = self.entry_default_prefix.get_text()
|
|
|
|
mangohud_state = self.checkbox_mangohud.get_active()
|
|
gamemode_state = self.checkbox_gamemode.get_active()
|
|
sc_controller_state = self.checkbox_sc_controller.get_active()
|
|
default_runner = self.combo_box_runner.get_active_text()
|
|
|
|
# Handle dialog response
|
|
if response_id == Gtk.ResponseType.OK:
|
|
if default_prefix == "":
|
|
settings_dialog.entry_default_prefix.get_style_context().add_class("entry")
|
|
else:
|
|
self.save_config(checkbox_state, default_prefix, mangohud_state, gamemode_state, sc_controller_state, default_runner)
|
|
settings_dialog.destroy()
|
|
|
|
else:
|
|
settings_dialog.destroy()
|
|
|
|
# Ensure the dialog is destroyed when canceled
|
|
#settings_dialog.destroy()
|
|
|
|
def on_button_play_clicked(self, widget):
|
|
if not (listbox_row := self.game_list.get_selected_row()):
|
|
return
|
|
# Get the selected game's title
|
|
hbox = listbox_row.get_child()
|
|
game_label = hbox.get_children()[0]
|
|
title = game_label.get_text()
|
|
# Find the selected game object
|
|
game = next((j for j in self.games if j.title == title), None)
|
|
if game:
|
|
# Format the title for command execution
|
|
title_formatted = re.sub(r'[^a-zA-Z0-9\s]', '', game.title)
|
|
title_formatted = title_formatted.replace(' ', '-')
|
|
title_formatted = '-'.join(title_formatted.lower().split())
|
|
|
|
# Extract game launch information
|
|
launch_arguments = game.launch_arguments
|
|
path = game.path
|
|
prefix = game.prefix
|
|
game_arguments = game.game_arguments
|
|
mangohud = game.mangohud
|
|
sc_controller = game.sc_controller
|
|
protonfix = game.protonfix
|
|
runner = game.runner
|
|
|
|
gamemode_enabled = os.path.exists("/usr/bin/gamemoderun") or os.path.exists("/usr/games/gamemoderun")
|
|
gamemode = game.gamemode if gamemode_enabled else ""
|
|
|
|
# Get the directory containing the executable
|
|
game_directory = os.path.dirname(path)
|
|
|
|
command_parts = []
|
|
|
|
# Add command parts if they are not empty
|
|
if mangohud:
|
|
command_parts.append(mangohud)
|
|
if sc_controller:
|
|
command_parts.append(sc_controller)
|
|
if prefix:
|
|
command_parts.append(f'WINEPREFIX={prefix}')
|
|
if protonfix:
|
|
command_parts.append(f'GAMEID={protonfix}')
|
|
else:
|
|
command_parts.append(f'GAMEID={title_formatted}')
|
|
if runner:
|
|
command_parts.append(f'PROTONPATH={runner}')
|
|
if gamemode:
|
|
command_parts.append(gamemode)
|
|
if launch_arguments:
|
|
command_parts.append(launch_arguments)
|
|
|
|
# Add the fixed command and remaining arguments
|
|
command_parts.append('"/usr/bin/umu-run"')
|
|
if path:
|
|
command_parts.append(f'"{path}"')
|
|
if game_arguments:
|
|
command_parts.append(f'"{game_arguments}"')
|
|
|
|
# Join all parts into a single command
|
|
command = ' '.join(command_parts)
|
|
print(command)
|
|
|
|
# faugus-run path
|
|
faugus_run_path = "/usr/bin/faugus-run"
|
|
|
|
|
|
# Launch the game with subprocess
|
|
if self.load_close_onlaunch():
|
|
subprocess.Popen([sys.executable, faugus_run_path, command], stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL, cwd=game_directory)
|
|
sys.exit()
|
|
else:
|
|
processo = subprocess.Popen([sys.executable, faugus_run_path, command], cwd=game_directory)
|
|
self.processos[title] = processo
|
|
self.button_play.set_sensitive(False)
|
|
self.button_play.set_image(
|
|
Gtk.Image.new_from_icon_name("media-playback-stop-symbolic", Gtk.IconSize.BUTTON))
|
|
|
|
def on_button_kill_clicked(self, widget):
|
|
# Handle kill button click event
|
|
subprocess.run(r"ls -l /proc/*/exe 2>/dev/null | grep -E 'wine(64)?-preloader|wineserver' | perl "
|
|
r"-pe 's;^.*/proc/(\d+)/exe.*$;$1;g;' | xargs -n 1 kill | killall -s9 winedevice.exe tee",
|
|
shell=True)
|
|
self.game_running = None
|
|
self.game_running2 = False
|
|
|
|
def on_button_add_clicked(self, widget):
|
|
file_path=""
|
|
# Handle add button click event
|
|
add_game_dialog = AddGame(self, self.game_running2, file_path)
|
|
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=""
|
|
if not (listbox_row := self.game_list.get_selected_row()):
|
|
return
|
|
hbox = listbox_row.get_child()
|
|
game_label = hbox.get_children()[0]
|
|
title = game_label.get_text()
|
|
if game := next((j for j in self.games if j.title == title), None):
|
|
if game.title in self.processos:
|
|
self.game_running2 = True
|
|
else:
|
|
self.game_running2 = False
|
|
edit_game_dialog = AddGame(self, self.game_running2, file_path)
|
|
edit_game_dialog.connect("response", self.on_edit_dialog_response, edit_game_dialog, game)
|
|
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(f"Edit {game.title}")
|
|
edit_game_dialog.entry_protonfix.set_text(game.protonfix)
|
|
|
|
#edit_game_dialog.populate_combobox_with_runners()
|
|
model = edit_game_dialog.combo_box_runner.get_model()
|
|
index_to_activate = 0
|
|
if game.runner == "GE-Proton":
|
|
game.runner = "GE-Proton Latest (default)"
|
|
for i, row in enumerate(model):
|
|
if row[0] == game.runner:
|
|
index_to_activate = i
|
|
break
|
|
edit_game_dialog.combo_box_runner.set_active(index_to_activate)
|
|
|
|
mangohud_status = False
|
|
gamemode_status = False
|
|
sc_controller_status = False
|
|
with open("games.txt", "r") as file:
|
|
for line in file:
|
|
fields = line.strip().split(";")
|
|
if len(fields) >= 8 and fields[0] == game.title:
|
|
mangohud_status = fields[5] == "MANGOHUD=1"
|
|
gamemode_status = fields[6] == "gamemoderun"
|
|
sc_controller_status = fields[7] == "SC_CONTROLLER=1"
|
|
|
|
mangohud_enabled = os.path.exists("/usr/bin/mangohud")
|
|
if mangohud_enabled:
|
|
edit_game_dialog.checkbox_mangohud.set_active(mangohud_status)
|
|
gamemode_enabled = os.path.exists("/usr/bin/gamemoderun") or os.path.exists("/usr/games/gamemoderun")
|
|
if gamemode_enabled:
|
|
edit_game_dialog.checkbox_gamemode.set_active(gamemode_status)
|
|
sc_controller_enabled = os.path.exists("/usr/bin/sc-controller") or os.path.exists(
|
|
"/usr/local/bin/sc-controller")
|
|
if sc_controller_enabled:
|
|
edit_game_dialog.checkbox_sc_controller.set_active(sc_controller_status)
|
|
edit_game_dialog.check_existing_shortcut()
|
|
|
|
image = self.set_image_shortcut_icon(game.title)
|
|
edit_game_dialog.button_shortcut_icon.set_image(image)
|
|
edit_game_dialog.entry_title.set_sensitive(False)
|
|
|
|
if self.game_running2:
|
|
edit_game_dialog.button_winecfg.set_sensitive(False)
|
|
edit_game_dialog.button_winecfg.set_tooltip_text(f'{game.title} is running. Please close it first.')
|
|
edit_game_dialog.button_winetricks.set_sensitive(False)
|
|
edit_game_dialog.button_winetricks.set_tooltip_text(f'{game.title} is running. Please close it first.')
|
|
edit_game_dialog.button_run.set_sensitive(False)
|
|
edit_game_dialog.button_run.set_tooltip_text(f'{game.title} is running. Please close it first.')
|
|
|
|
edit_game_dialog.show()
|
|
|
|
def set_image_shortcut_icon(self, title):
|
|
|
|
# Handle the click event of the Create Shortcut button
|
|
title_formatted = re.sub(r'[^a-zA-Z0-9\s]', '', title)
|
|
title_formatted = title_formatted.replace(' ', '-')
|
|
title_formatted = '-'.join(title_formatted.lower().split())
|
|
|
|
# Check if the icon file exists
|
|
icons_path = os.path.expanduser("~/.config/faugus-launcher/icons/")
|
|
new_icon_path = os.path.join(icons_path, f"{title_formatted}.ico")
|
|
|
|
if os.path.exists(new_icon_path):
|
|
image_path = f"{icons_path}{title_formatted}.ico"
|
|
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path)
|
|
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
|
|
|
image = Gtk.Image.new_from_file(image_path)
|
|
image.set_from_pixbuf(scaled_pixbuf)
|
|
|
|
if not os.path.exists(new_icon_path):
|
|
image_path = "/usr/share/icons/faugus-launcher.png"
|
|
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path)
|
|
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
|
|
|
image = Gtk.Image.new_from_file(image_path)
|
|
image.set_from_pixbuf(scaled_pixbuf)
|
|
|
|
return image
|
|
|
|
def on_button_delete_clicked(self, widget):
|
|
if not (listbox_row := self.game_list.get_selected_row()):
|
|
return
|
|
# Retrieve the selected game's title
|
|
hbox = listbox_row.get_child()
|
|
game_label = hbox.get_children()[0]
|
|
title = game_label.get_text()
|
|
if game := next((j for j in self.games if j.title == title), None):
|
|
# Display confirmation dialog
|
|
confirmation_dialog = ConfirmationDialog(self, title)
|
|
response = confirmation_dialog.run()
|
|
|
|
if response == Gtk.ResponseType.YES:
|
|
# Remove game and associated files if required
|
|
if confirmation_dialog.get_remove_prefix_state():
|
|
game_prefix = game.prefix
|
|
prefix_path = os.path.expanduser(game_prefix)
|
|
try:
|
|
shutil.rmtree(prefix_path)
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
# Remove the shortcut
|
|
self.remove_shortcut(game)
|
|
|
|
self.games.remove(game)
|
|
self.save_games()
|
|
self.update_list()
|
|
|
|
self.button_edit.set_sensitive(False)
|
|
self.button_delete.set_sensitive(False)
|
|
self.button_play.set_sensitive(False)
|
|
|
|
confirmation_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="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
|
|
title = add_game_dialog.entry_title.get_text()
|
|
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()
|
|
prefix = add_game_dialog.entry_prefix.get_text()
|
|
protonfix = add_game_dialog.entry_protonfix.get_text()
|
|
runner = add_game_dialog.combo_box_runner.get_active_text()
|
|
|
|
# Concatenate game information
|
|
game_info = (f"{title};{path};{prefix};{launch_arguments};{game_arguments}")
|
|
|
|
# Determine mangohud and gamemode status
|
|
mangohud = "MANGOHUD=1" if add_game_dialog.checkbox_mangohud.get_active() else ""
|
|
gamemode = "gamemoderun" if add_game_dialog.checkbox_gamemode.get_active() else ""
|
|
sc_controller = "SC_CONTROLLER=1" if add_game_dialog.checkbox_sc_controller.get_active() else ""
|
|
|
|
if runner == "UMU-Proton Latest":
|
|
runner = ""
|
|
if runner == "GE-Proton Latest (default)":
|
|
runner = "GE-Proton"
|
|
|
|
game_info += f";{mangohud};{gamemode};{sc_controller};{protonfix};{runner}\n"
|
|
|
|
# Write game info to file
|
|
with open("games.txt", "a") as file:
|
|
file.write(game_info)
|
|
|
|
# Create Game object and update UI
|
|
game = Game(title, path, prefix, launch_arguments, game_arguments, mangohud, gamemode, sc_controller, protonfix, runner)
|
|
self.games.append(game)
|
|
self.add_item_list(game)
|
|
self.update_list()
|
|
|
|
# Determine the state of the shortcut checkbox
|
|
shortcut_state = add_game_dialog.checkbox_shortcut.get_active()
|
|
|
|
# Call add_remove_shortcut method
|
|
self.add_shortcut(game, shortcut_state)
|
|
|
|
# Select the added game
|
|
self.select_game_by_title(title)
|
|
|
|
else:
|
|
add_game_dialog.destroy()
|
|
|
|
# Ensure the dialog is destroyed when canceled
|
|
add_game_dialog.destroy()
|
|
|
|
def select_game_by_title(self, title):
|
|
# Select an item from the list based on title
|
|
for row in self.game_list.get_children():
|
|
hbox = row.get_child()
|
|
game_label = hbox.get_children()[0]
|
|
if game_label.get_text() == title:
|
|
self.game_list.select_row(row)
|
|
break
|
|
self.button_edit.set_sensitive(True)
|
|
self.button_delete.set_sensitive(True)
|
|
self.button_play.set_sensitive(True)
|
|
self.button_play.set_image(
|
|
Gtk.Image.new_from_icon_name("media-playback-start-symbolic", Gtk.IconSize.BUTTON))
|
|
|
|
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="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 = 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.sc_controller = edit_game_dialog.checkbox_sc_controller.get_active()
|
|
game.protonfix = edit_game_dialog.entry_protonfix.get_text()
|
|
game.runner = edit_game_dialog.combo_box_runner.get_active_text()
|
|
|
|
if game.runner == "UMU-Proton Latest":
|
|
game.runner = ""
|
|
if game.runner == "GE-Proton Latest (default)":
|
|
game.runner = "GE-Proton"
|
|
|
|
# Save changes and update UI
|
|
self.save_games()
|
|
self.update_list()
|
|
|
|
# Determine the state of the shortcut checkbox
|
|
shortcut_state = edit_game_dialog.checkbox_shortcut.get_active()
|
|
|
|
# Call add_remove_shortcut method
|
|
self.add_shortcut(game, shortcut_state)
|
|
|
|
# Select the game that was edited
|
|
self.select_game_by_title(game.title)
|
|
|
|
edit_game_dialog.destroy()
|
|
|
|
def add_shortcut(self, game, shortcut_state):
|
|
# Check if the shortcut checkbox is checked
|
|
if not shortcut_state:
|
|
# Remove existing shortcut if it exists
|
|
self.remove_shortcut(game)
|
|
return
|
|
|
|
# Handle the click event of the Create Shortcut button
|
|
title_formatted = re.sub(r'[^a-zA-Z0-9\s]', '', game.title)
|
|
title_formatted = title_formatted.replace(' ', '-')
|
|
title_formatted = '-'.join(title_formatted.lower().split())
|
|
|
|
prefix = game.prefix
|
|
path = game.path
|
|
launch_arguments = game.launch_arguments
|
|
game_arguments = game.game_arguments
|
|
protonfix = game.protonfix
|
|
runner = game.runner
|
|
|
|
mangohud = "MANGOHUD=1" if game.mangohud else ""
|
|
gamemode = "gamemoderun" if game.gamemode else ""
|
|
sc_controller = "SC_CONTROLLER=1" if game.sc_controller else ""
|
|
# Check if the icon file exists
|
|
icons_path = os.path.expanduser("~/.config/faugus-launcher/icons/")
|
|
new_icon_path = os.path.join(icons_path, f"{title_formatted}.ico")
|
|
if not os.path.exists(new_icon_path):
|
|
new_icon_path = "/usr/share/icons/faugus-launcher.png"
|
|
|
|
# Get the directory containing the executable
|
|
game_directory = os.path.dirname(path)
|
|
|
|
command_parts = []
|
|
|
|
# Add command parts if they are not empty
|
|
if mangohud:
|
|
command_parts.append(mangohud)
|
|
if sc_controller:
|
|
command_parts.append(sc_controller)
|
|
if prefix:
|
|
command_parts.append(f'WINEPREFIX={prefix}')
|
|
if protonfix:
|
|
command_parts.append(f'GAMEID={protonfix}')
|
|
else:
|
|
command_parts.append(f'GAMEID={title_formatted}')
|
|
if runner:
|
|
command_parts.append(f'PROTONPATH={runner}')
|
|
if gamemode:
|
|
command_parts.append(gamemode)
|
|
if launch_arguments:
|
|
command_parts.append(launch_arguments)
|
|
|
|
# Add the fixed command and remaining arguments
|
|
command_parts.append('"/usr/bin/umu-run"')
|
|
if path:
|
|
command_parts.append(f'"{path}"')
|
|
if game_arguments:
|
|
command_parts.append(f'"{game_arguments}"')
|
|
|
|
# Join all parts into a single command
|
|
command = ' '.join(command_parts)
|
|
|
|
# Create a .desktop file
|
|
desktop_file_content = f"""[Desktop Entry]
|
|
Name={game.title}
|
|
Exec=/usr/bin/faugus-run '{command}'
|
|
Icon={new_icon_path}
|
|
Type=Application
|
|
Categories=Game;
|
|
Path={game_directory}
|
|
"""
|
|
|
|
# Check if the destination directory exists and create if it doesn't
|
|
applications_directory = os.path.expanduser("~/.local/share/applications/")
|
|
if not os.path.exists(applications_directory):
|
|
os.makedirs(applications_directory)
|
|
|
|
desktop_directory = os.path.expanduser("~/Desktop")
|
|
if not os.path.exists(desktop_directory):
|
|
os.makedirs(desktop_directory)
|
|
|
|
applications_shortcut_path = os.path.expanduser(f"~/.local/share/applications/{title_formatted}.desktop")
|
|
|
|
with open(applications_shortcut_path, 'w') as desktop_file:
|
|
desktop_file.write(desktop_file_content)
|
|
|
|
# Make the .desktop file executable
|
|
os.chmod(applications_shortcut_path, 0o755)
|
|
|
|
# Copy the shortcut to Desktop
|
|
desktop_shortcut_path = os.path.expanduser(f"~/Desktop/{title_formatted}.desktop")
|
|
shutil.copy(applications_shortcut_path, desktop_shortcut_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_shortcut(self, game):
|
|
# Remove existing shortcut if it exists
|
|
title_formatted = re.sub(r'[^a-zA-Z0-9\s]', '', game.title)
|
|
title_formatted = title_formatted.replace(' ', '-')
|
|
title_formatted = '-'.join(title_formatted.lower().split())
|
|
|
|
desktop_file_path = os.path.expanduser(f"~/.local/share/applications/{title_formatted}.desktop")
|
|
|
|
if os.path.exists(desktop_file_path):
|
|
os.remove(desktop_file_path)
|
|
|
|
# Remove shortcut from Desktop if exists
|
|
desktop_shortcut_path = os.path.expanduser(f"~/Desktop/{title_formatted}.desktop")
|
|
if os.path.exists(desktop_shortcut_path):
|
|
os.remove(desktop_shortcut_path)
|
|
|
|
# Remove icon file
|
|
icon_file_path = os.path.expanduser(f"~/.config/faugus-launcher/icons/{title_formatted}.ico")
|
|
if os.path.exists(icon_file_path):
|
|
os.remove(icon_file_path)
|
|
|
|
def remove_desktop_entry(self, game):
|
|
# Remove the .desktop file from ~/.local/share/applications/
|
|
desktop_file_path = os.path.expanduser(f"~/.local/share/applications/{game.title}.desktop")
|
|
|
|
if os.path.exists(desktop_file_path):
|
|
os.remove(desktop_file_path)
|
|
|
|
def remove_shortcut_from_desktop(self, game):
|
|
# Remove the shortcut from the desktop if it exists
|
|
desktop_link_path = os.path.expanduser(f"~/Desktop/{game.title}.desktop")
|
|
|
|
if os.path.exists(desktop_link_path):
|
|
os.remove(desktop_link_path)
|
|
|
|
def add_item_list(self, game):
|
|
# Add a game item to the list
|
|
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
|
|
hbox.set_border_width(10)
|
|
hbox.set_size_request(500, -1)
|
|
|
|
game_label = Gtk.Label.new(game.title)
|
|
hbox.pack_start(game_label, True, True, 0)
|
|
|
|
listbox_row = Gtk.ListBoxRow()
|
|
listbox_row.add(hbox)
|
|
listbox_row.set_activatable(False)
|
|
listbox_row.set_can_focus(False)
|
|
listbox_row.set_selectable(True)
|
|
self.game_list.add(listbox_row)
|
|
|
|
hbox.set_halign(Gtk.Align.CENTER)
|
|
listbox_row.set_valign(Gtk.Align.START)
|
|
|
|
def update_list(self):
|
|
# Update the game list
|
|
for row in self.game_list.get_children():
|
|
self.game_list.remove(row)
|
|
|
|
self.games.clear()
|
|
self.load_games()
|
|
self.show_all()
|
|
|
|
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]
|
|
|
|
listbox_row = self.game_list.get_selected_row()
|
|
if listbox_row:
|
|
hbox = listbox_row.get_child()
|
|
game_label = hbox.get_children()[0]
|
|
selected_title = game_label.get_text()
|
|
|
|
if selected_title not in self.processos:
|
|
self.button_play.set_sensitive(True)
|
|
self.button_play.set_image(
|
|
Gtk.Image.new_from_icon_name("media-playback-start-symbolic", Gtk.IconSize.BUTTON))
|
|
else:
|
|
self.button_play.set_sensitive(False)
|
|
self.button_play.set_image(
|
|
Gtk.Image.new_from_icon_name("media-playback-stop-symbolic", Gtk.IconSize.BUTTON))
|
|
|
|
def load_games(self):
|
|
# Load games from file
|
|
try:
|
|
with open("games.txt", "r") as file:
|
|
for line in file:
|
|
data = line.strip().split(";")
|
|
if len(data) >= 5:
|
|
title, path, prefix, launch_arguments, game_arguments = data[:5]
|
|
if len(data) >= 10:
|
|
mangohud = data[5]
|
|
gamemode = data[6]
|
|
sc_controller = data[7]
|
|
protonfix = data[8]
|
|
runner = data[9]
|
|
else:
|
|
mangohud = ""
|
|
gamemode = ""
|
|
sc_controller = ""
|
|
protonfix = ""
|
|
runner = ""
|
|
game = Game(title, path, prefix, launch_arguments, game_arguments, mangohud, gamemode,
|
|
sc_controller, protonfix, runner)
|
|
self.games.append(game)
|
|
self.games = sorted(self.games, key=lambda x: x.title.lower())
|
|
self.game_list.foreach(Gtk.Widget.destroy)
|
|
for game in self.games:
|
|
self.add_item_list(game)
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
def save_games(self):
|
|
# Save game information to file
|
|
with open("games.txt", "w") as file:
|
|
for game in self.games:
|
|
# Determine mangohud and gamemode values
|
|
mangohud_value = "MANGOHUD=1" if game.mangohud else ""
|
|
gamemode_value = "gamemoderun" if game.gamemode else ""
|
|
sc_controller_value = "SC_CONTROLLER=1" if game.sc_controller else ""
|
|
# Construct line with game information
|
|
line = (f"{game.title};{game.path};{game.prefix};{game.launch_arguments};{game.game_arguments};"
|
|
f"{mangohud_value};{gamemode_value};{sc_controller_value};{game.protonfix};{game.runner}\n")
|
|
file.write(line)
|
|
|
|
def show_warning_message(self, message):
|
|
# Show a warning message dialog
|
|
dialog = Gtk.MessageDialog(transient_for=self, flags=0, message_type=Gtk.MessageType.WARNING,
|
|
buttons=Gtk.ButtonsType.OK, text=message, )
|
|
dialog.run()
|
|
dialog.destroy()
|
|
|
|
def save_config(self, checkbox_state, default_prefix, mangohud_state, gamemode_state, sc_controller_state, default_runner):
|
|
# Path to the configuration file
|
|
config_file = os.path.join(self.working_directory, 'config.ini')
|
|
|
|
# Dictionary to store existing configurations
|
|
config = {}
|
|
|
|
# Read the existing configuration file
|
|
if os.path.exists(config_file):
|
|
with open(config_file, 'r') as f:
|
|
for line in f:
|
|
key, value = line.strip().split('=', 1)
|
|
config[key] = value.strip('"')
|
|
|
|
default_runner = (f'"{default_runner}"')
|
|
|
|
# Update configurations with new values
|
|
config['close-onlaunch'] = checkbox_state
|
|
config['default-prefix'] = default_prefix
|
|
config['mangohud'] = mangohud_state
|
|
config['gamemode'] = gamemode_state
|
|
config['sc-controller'] = sc_controller_state
|
|
config['default-runner'] = default_runner
|
|
|
|
# Write configurations back to the file
|
|
with open(config_file, 'w') as f:
|
|
for key, value in config.items():
|
|
if key == 'default-prefix':
|
|
f.write(f'{key}="{value}"\n')
|
|
else:
|
|
f.write(f'{key}={value}\n')
|
|
|
|
def on_button_release_event(self, listbox, event):
|
|
# Handle button release event
|
|
if event.type == Gdk.EventType.BUTTON_RELEASE and event.button == Gdk.BUTTON_PRIMARY:
|
|
current_row = listbox.get_row_at_y(event.y)
|
|
current_time = event.time
|
|
if current_row == self.last_clicked_row and current_time - self.last_click_time < 500:
|
|
# Double-click detected
|
|
if current_row:
|
|
hbox = current_row.get_child()
|
|
game_label = hbox.get_children()[0]
|
|
title = game_label.get_text()
|
|
|
|
# Check if the game is already running
|
|
if title not in self.processos:
|
|
widget = self.button_play
|
|
self.on_button_play_clicked(widget)
|
|
else:
|
|
dialog = Gtk.MessageDialog(title=title, text=f"'{title}' is already running.",
|
|
buttons=Gtk.ButtonsType.OK, parent=self)
|
|
dialog.set_resizable(False)
|
|
dialog.set_modal(True)
|
|
dialog.run()
|
|
dialog.destroy()
|
|
else:
|
|
# Single-click, update last click details and enable buttons
|
|
self.last_clicked_row = current_row
|
|
self.last_click_time = current_time
|
|
|
|
self.button_edit.set_sensitive(True)
|
|
self.button_delete.set_sensitive(True)
|
|
|
|
if current_row:
|
|
hbox = current_row.get_child()
|
|
game_label = hbox.get_children()[0]
|
|
title = game_label.get_text()
|
|
|
|
if title in self.processos:
|
|
self.button_play.set_sensitive(False)
|
|
self.button_play.set_image(
|
|
Gtk.Image.new_from_icon_name("media-playback-stop-symbolic", Gtk.IconSize.BUTTON))
|
|
else:
|
|
self.button_play.set_sensitive(True)
|
|
self.button_play.set_image(
|
|
Gtk.Image.new_from_icon_name("media-playback-start-symbolic", Gtk.IconSize.BUTTON))
|
|
|
|
|
|
class Settings(Gtk.Dialog):
|
|
def __init__(self, parent):
|
|
# Initialize the Settings dialog
|
|
super().__init__(title="Settings", parent=parent)
|
|
self.set_resizable(False)
|
|
self.set_modal(True)
|
|
self.parent = parent
|
|
|
|
css_provider = Gtk.CssProvider()
|
|
css = """
|
|
.entry {
|
|
border-color: Red;
|
|
}
|
|
.paypal {
|
|
color: black;
|
|
background: white;
|
|
}
|
|
.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)
|
|
|
|
# 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.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_default_prefix_tools = Gtk.Label(label="Default prefix tools")
|
|
self.label_default_prefix_tools.set_halign(Gtk.Align.START)
|
|
|
|
# Widgets for runner
|
|
self.label_runner = Gtk.Label(label="Default Runner")
|
|
self.label_runner.set_halign(Gtk.Align.START)
|
|
self.combo_box_runner = Gtk.ComboBoxText()
|
|
|
|
self.button_proton_manager = Gtk.Button(label="GE-Proton Manager")
|
|
self.button_proton_manager.connect("clicked", self.on_button_proton_manager_clicked)
|
|
|
|
# Create checkbox for 'Close after launch' option
|
|
self.checkbox_close_after_launch = Gtk.CheckButton(label="Close when running a game")
|
|
self.checkbox_close_after_launch.set_active(False)
|
|
|
|
# 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_sc_controller = Gtk.CheckButton(label="SC Controller")
|
|
self.checkbox_sc_controller.set_tooltip_text(
|
|
"Emulates a Xbox controller if the game doesn't support yours. Put the profile at ~/.config/faugus-launcher/controller.sccprofile.")
|
|
|
|
button_kofi = Gtk.Button(label="Buy me a Coffee")
|
|
button_kofi.set_size_request(150, -1)
|
|
button_kofi.connect("clicked", self.on_button_kofi_clicked)
|
|
button_kofi.get_style_context().add_class("kofi")
|
|
button_kofi.set_halign(Gtk.Align.CENTER)
|
|
|
|
button_paypal = Gtk.Button(label="PayPal Donation")
|
|
button_paypal.set_size_request(150, -1)
|
|
button_paypal.connect("clicked", self.on_button_paypal_clicked)
|
|
button_paypal.get_style_context().add_class("paypal")
|
|
button_paypal.set_halign(Gtk.Align.CENTER)
|
|
|
|
# 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)
|
|
self.button_cancel.set_halign(Gtk.Align.CENTER)
|
|
|
|
# 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.button_ok.set_halign(Gtk.Align.CENTER)
|
|
|
|
self.box = self.get_content_area()
|
|
|
|
grid = Gtk.Grid()
|
|
grid.set_row_spacing(10)
|
|
grid.set_column_spacing(10)
|
|
grid.set_margin_start(10)
|
|
grid.set_margin_end(10)
|
|
grid.set_margin_top(10)
|
|
grid.set_margin_bottom(10)
|
|
|
|
grid2 = Gtk.Grid()
|
|
grid2.set_row_spacing(10)
|
|
grid2.set_column_spacing(10)
|
|
grid2.set_margin_start(10)
|
|
grid2.set_margin_end(10)
|
|
grid2.set_margin_top(10)
|
|
|
|
grid3 = Gtk.Grid()
|
|
grid3.set_row_spacing(10)
|
|
grid3.set_column_spacing(10)
|
|
grid3.set_margin_start(10)
|
|
grid3.set_margin_end(10)
|
|
grid3.set_margin_top(10)
|
|
grid3.set_margin_bottom(10)
|
|
|
|
grid4 = Gtk.Grid()
|
|
grid4.set_row_spacing(10)
|
|
grid4.set_column_spacing(10)
|
|
grid4.set_margin_start(10)
|
|
grid4.set_margin_end(10)
|
|
grid4.set_margin_top(10)
|
|
grid4.set_margin_bottom(10)
|
|
|
|
grid5 = Gtk.Grid()
|
|
grid5.set_row_spacing(10)
|
|
grid5.set_column_spacing(10)
|
|
grid5.set_margin_start(10)
|
|
grid5.set_margin_end(10)
|
|
grid5.set_margin_top(10)
|
|
grid5.set_margin_bottom(10)
|
|
|
|
grid6 = Gtk.Grid()
|
|
grid6.set_row_spacing(10)
|
|
grid6.set_column_spacing(10)
|
|
grid6.set_margin_start(10)
|
|
grid6.set_margin_end(10)
|
|
grid6.set_margin_top(10)
|
|
grid6.set_margin_bottom(10)
|
|
|
|
grid7 = Gtk.Grid()
|
|
grid7.set_row_spacing(10)
|
|
grid7.set_column_spacing(10)
|
|
grid7.set_margin_start(10)
|
|
grid7.set_margin_end(10)
|
|
grid7.set_margin_top(10)
|
|
grid7.set_margin_bottom(10)
|
|
|
|
|
|
# Create a frame
|
|
frame = Gtk.Frame()
|
|
frame.set_label_align(0.5, 0.5)
|
|
frame.set_margin_start(10)
|
|
frame.set_margin_end(10)
|
|
frame.set_margin_top(10)
|
|
frame.set_margin_bottom(10)
|
|
|
|
# Add grid to frame
|
|
frame.add(grid3)
|
|
|
|
# Attach widgets to the grid layout
|
|
grid.attach(self.label_default_prefix, 0, 0, 1, 1)
|
|
grid.attach(self.entry_default_prefix, 0, 1, 3, 1)
|
|
self.entry_default_prefix.set_hexpand(True)
|
|
grid.attach(self.button_search_prefix, 3, 1, 1, 1)
|
|
|
|
grid6.attach(self.label_runner, 0, 6, 1, 1)
|
|
grid6.attach(self.combo_box_runner, 0, 7, 1, 1)
|
|
grid6.attach(self.button_proton_manager, 0, 8, 1, 1)
|
|
self.combo_box_runner.set_hexpand(True)
|
|
self.button_proton_manager.set_hexpand(True)
|
|
|
|
grid7.attach(self.checkbox_close_after_launch, 0, 2, 4, 1)
|
|
|
|
grid2.attach(self.label_default_prefix_tools, 0, 0, 1, 1)
|
|
|
|
grid3.attach(self.checkbox_mangohud, 0, 0, 1, 1)
|
|
self.checkbox_mangohud.set_hexpand(True)
|
|
grid3.attach(self.checkbox_gamemode, 0, 1, 1, 1)
|
|
grid3.attach(self.checkbox_sc_controller, 0, 2, 1, 1)
|
|
grid3.attach(self.button_winetricks_default, 1, 0, 1, 1)
|
|
grid3.attach(self.button_winecfg_default, 1, 1, 1, 1)
|
|
grid3.attach(self.button_run_default, 1, 2, 1, 1)
|
|
|
|
grid4.attach(button_kofi, 0, 0, 1, 1)
|
|
grid4.attach(button_paypal, 1, 0, 1, 1)
|
|
grid4.set_halign(Gtk.Align.CENTER)
|
|
|
|
grid5.attach(self.button_cancel, 0, 0, 1, 1)
|
|
grid5.attach(self.button_ok, 1, 0, 1, 1)
|
|
grid5.set_halign(Gtk.Align.CENTER)
|
|
|
|
self.populate_combobox_with_runners()
|
|
self.load_config()
|
|
|
|
# Check if optional features are available and enable/disable accordingly
|
|
self.mangohud_enabled = os.path.exists("/usr/bin/mangohud")
|
|
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("/usr/bin/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.sc_controller_enabled = os.path.exists("/usr/bin/sc-controller") or os.path.exists(
|
|
"/usr/local/bin/sc-controller")
|
|
if not self.sc_controller_enabled:
|
|
self.checkbox_sc_controller.set_sensitive(False)
|
|
self.checkbox_sc_controller.set_active(False)
|
|
self.checkbox_sc_controller.set_tooltip_text(
|
|
"Emulates a Xbox controller if the game doesn't support yours. Put the profile at ~/.config/faugus-launcher/controller.sccprofile. NOT INSTALLED.")
|
|
|
|
self.box.add(grid)
|
|
self.box.add(grid6)
|
|
self.box.add(grid7)
|
|
self.box.add(grid2)
|
|
self.box.add(frame)
|
|
self.box.add(grid4)
|
|
self.box.add(grid5)
|
|
|
|
self.show_all()
|
|
|
|
def on_button_proton_manager_clicked(self, widget):
|
|
self.set_sensitive(False)
|
|
|
|
proton_manager = "/usr/bin/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.combo_box_runner.remove_all())
|
|
GLib.idle_add(self.populate_combobox_with_runners)
|
|
|
|
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 populate_combobox_with_runners(self):
|
|
# List of default entries
|
|
self.combo_box_runner.append_text("GE-Proton Latest (default)")
|
|
self.combo_box_runner.append_text("UMU-Proton Latest")
|
|
|
|
# Path to the directory containing the folders
|
|
runner_path = os.path.expanduser('~/.local/share/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 != "UMU-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.combo_box_runner.append_text(version)
|
|
|
|
except Exception as e:
|
|
print(f"Error accessing the directory: {e}")
|
|
|
|
# Set the active item, if desired
|
|
self.combo_box_runner.set_active(0)
|
|
|
|
def on_entry_changed(self, widget, entry):
|
|
if entry.get_text():
|
|
entry.get_style_context().remove_class("entry")
|
|
|
|
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")
|
|
else:
|
|
checkbox_state = self.checkbox_close_after_launch.get_active()
|
|
default_prefix = self.entry_default_prefix.get_text()
|
|
|
|
mangohud_state = self.checkbox_mangohud.get_active()
|
|
gamemode_state = self.checkbox_gamemode.get_active()
|
|
sc_controller_state = self.checkbox_sc_controller.get_active()
|
|
default_runner = self.combo_box_runner.get_active_text()
|
|
|
|
self.parent.save_config(checkbox_state, default_prefix, mangohud_state, gamemode_state, sc_controller_state, default_runner)
|
|
self.set_sensitive(False)
|
|
|
|
dialog = Gtk.FileChooserDialog(title="Select a file to run inside the prefix",
|
|
action=Gtk.FileChooserAction.OPEN)
|
|
dialog.set_current_folder(os.path.expanduser("~/"))
|
|
dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
|
|
|
|
file_filter = Gtk.FileFilter()
|
|
file_filter.set_name("Windows files")
|
|
file_filter.add_pattern("*.exe")
|
|
file_filter.add_pattern("*.msi")
|
|
file_filter.add_pattern("*.bat")
|
|
file_filter.add_pattern("*.lnk")
|
|
file_filter.add_pattern("*.reg")
|
|
dialog.add_filter(file_filter)
|
|
|
|
response = dialog.run()
|
|
if response == Gtk.ResponseType.OK:
|
|
runner = self.combo_box_runner.get_active_text()
|
|
|
|
if runner == "UMU-Proton Latest":
|
|
runner = ""
|
|
if runner == "GE-Proton Latest (default)":
|
|
runner = "GE-Proton"
|
|
|
|
command_parts = []
|
|
file_run = dialog.get_filename()
|
|
if not file_run.endswith(".reg"):
|
|
if file_run:
|
|
command_parts.append(f'GAMEID=default')
|
|
if runner:
|
|
command_parts.append(f'PROTONPATH={runner}')
|
|
command_parts.append(f'"/usr/bin/umu-run" "{file_run}"')
|
|
else:
|
|
if file_run:
|
|
command_parts.append(f'GAMEID=default')
|
|
if runner:
|
|
command_parts.append(f'PROTONPATH={runner}')
|
|
command_parts.append(f'"/usr/bin/umu-run" regedit "{file_run}"')
|
|
|
|
# Join all parts into a single command
|
|
command = ' '.join(command_parts)
|
|
|
|
print(command)
|
|
|
|
# faugus-run path
|
|
faugus_run_path = "/usr/bin/faugus-run"
|
|
|
|
def run_command():
|
|
process = subprocess.Popen([sys.executable, faugus_run_path, command, "winecfg"])
|
|
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)
|
|
dialog.destroy()
|
|
|
|
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:
|
|
checkbox_state = self.checkbox_close_after_launch.get_active()
|
|
default_prefix = self.entry_default_prefix.get_text()
|
|
|
|
mangohud_state = self.checkbox_mangohud.get_active()
|
|
gamemode_state = self.checkbox_gamemode.get_active()
|
|
sc_controller_state = self.checkbox_sc_controller.get_active()
|
|
default_runner = self.combo_box_runner.get_active_text()
|
|
|
|
self.parent.save_config(checkbox_state, default_prefix, mangohud_state, gamemode_state, sc_controller_state, default_runner)
|
|
self.set_sensitive(False)
|
|
|
|
runner = self.combo_box_runner.get_active_text()
|
|
|
|
if runner == "UMU-Proton Latest":
|
|
runner = ""
|
|
if runner == "GE-Proton Latest (default)":
|
|
runner = "GE-Proton"
|
|
|
|
command_parts = []
|
|
|
|
# Add command parts if they are not empty
|
|
|
|
command_parts.append(f'GAMEID=default')
|
|
if runner:
|
|
command_parts.append(f'PROTONPATH={runner}')
|
|
|
|
# Add the fixed command and remaining arguments
|
|
command_parts.append('"/usr/bin/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 = "/usr/bin/faugus-run"
|
|
|
|
def run_command():
|
|
process = subprocess.Popen([sys.executable, faugus_run_path, command, "winecfg"])
|
|
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_winetricks_default_clicked(self, widget):
|
|
if self.entry_default_prefix.get_text() == "":
|
|
self.entry_default_prefix.get_style_context().add_class("entry")
|
|
else:
|
|
checkbox_state = self.checkbox_close_after_launch.get_active()
|
|
default_prefix = self.entry_default_prefix.get_text()
|
|
|
|
mangohud_state = self.checkbox_mangohud.get_active()
|
|
gamemode_state = self.checkbox_gamemode.get_active()
|
|
sc_controller_state = self.checkbox_sc_controller.get_active()
|
|
default_runner = self.combo_box_runner.get_active_text()
|
|
|
|
self.parent.save_config(checkbox_state, default_prefix, mangohud_state, gamemode_state, sc_controller_state, default_runner)
|
|
self.set_sensitive(False)
|
|
|
|
runner = self.combo_box_runner.get_active_text()
|
|
|
|
if runner == "UMU-Proton Latest":
|
|
runner = ""
|
|
if runner == "GE-Proton Latest (default)":
|
|
runner = "GE-Proton"
|
|
|
|
command_parts = []
|
|
|
|
# Add command parts if they are not empty
|
|
|
|
command_parts.append(f'GAMEID=winetricks-gui')
|
|
command_parts.append(f'STORE=none')
|
|
if runner:
|
|
command_parts.append(f'PROTONPATH={runner}')
|
|
|
|
# Add the fixed command and remaining arguments
|
|
command_parts.append('"/usr/bin/umu-run"')
|
|
command_parts.append('""')
|
|
|
|
# Join all parts into a single command
|
|
command = ' '.join(command_parts)
|
|
|
|
print(command)
|
|
|
|
# faugus-run path
|
|
faugus_run_path = "/usr/bin/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_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&amount=5&no_recurring=0¤cy_code=USD")
|
|
|
|
def on_button_search_prefix_clicked(self, widget):
|
|
# Handle the click event of the search button to select the game's .exe
|
|
dialog = Gtk.FileChooserDialog(title="Select a prefix location", parent=self,
|
|
action=Gtk.FileChooserAction.SELECT_FOLDER)
|
|
dialog.set_current_folder(os.path.expanduser(self.default_prefix))
|
|
dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
|
|
|
|
response = dialog.run()
|
|
if response == Gtk.ResponseType.OK:
|
|
self.entry_default_prefix.set_text(dialog.get_filename())
|
|
|
|
dialog.destroy()
|
|
|
|
def load_config(self):
|
|
# Load configuration from file
|
|
config_file = os.path.join(self.parent.working_directory, 'config.ini')
|
|
if os.path.exists(config_file):
|
|
with open(config_file, 'r') as f:
|
|
config_data = f.read().splitlines()
|
|
config_dict = dict(line.split('=') for line in config_data)
|
|
close_on_launch = config_dict.get('close-onlaunch', 'False') == 'True'
|
|
self.default_prefix = config_dict.get('default-prefix', '').strip('"')
|
|
|
|
mangohud = config_dict.get('mangohud', 'False') == 'True'
|
|
gamemode = config_dict.get('gamemode', 'False') == 'True'
|
|
sc_controller = config_dict.get('sc-controller', 'False') == 'True'
|
|
self.default_runner = config_dict.get('default-runner', '').strip('"')
|
|
|
|
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_sc_controller.set_active(sc_controller)
|
|
|
|
|
|
model = self.combo_box_runner.get_model()
|
|
index_to_activate = 0
|
|
for i, row in enumerate(model):
|
|
if row[0] == self.default_runner:
|
|
index_to_activate = i
|
|
break
|
|
self.combo_box_runner.set_active(index_to_activate)
|
|
|
|
else:
|
|
# Save default configuration if file does not exist
|
|
print("else")
|
|
self.parent.save_config(False, '', "False", "False", "False", "GE-Proton Latest (default)")
|
|
|
|
|
|
class Game:
|
|
def __init__(self, title, path, prefix, launch_arguments, game_arguments, mangohud, gamemode, sc_controller, protonfix, runner):
|
|
# Initialize a Game object with various attributes
|
|
self.title = title # Title of the game
|
|
self.path = path # Path to the game executable
|
|
self.launch_arguments = launch_arguments # Arguments to launch the game
|
|
self.game_arguments = game_arguments # Arguments specific to the game
|
|
self.mangohud = mangohud # Boolean indicating whether Mangohud is enabled
|
|
self.gamemode = gamemode # Boolean indicating whether Gamemode is enabled
|
|
self.prefix = prefix # Prefix for Wine games
|
|
self.sc_controller = sc_controller # Boolean indicating whether SC Controller is enabled
|
|
self.protonfix = protonfix
|
|
self.runner = runner
|
|
|
|
|
|
class ConfirmationDialog(Gtk.Dialog):
|
|
def __init__(self, parent, title):
|
|
# Initialize the ConfirmationDialog
|
|
Gtk.Dialog.__init__(self, title=f"Delete {title}", parent=parent, modal=True)
|
|
|
|
# Configure dialog properties
|
|
self.set_resizable(False)
|
|
|
|
# Create a grid layout for the dialog content area
|
|
grid = Gtk.Grid()
|
|
grid.set_row_spacing(20)
|
|
grid.set_column_spacing(10)
|
|
grid.set_margin_start(10)
|
|
grid.set_margin_end(10)
|
|
grid.set_margin_top(10)
|
|
grid.set_margin_bottom(10)
|
|
|
|
# Add grid to dialog's content area
|
|
content_area = self.get_content_area()
|
|
content_area.set_border_width(0)
|
|
content_area.add(grid)
|
|
|
|
# Create a label
|
|
label = Gtk.Label()
|
|
label.set_label(f"Are you sure you want to delete {title}?")
|
|
label.set_halign(Gtk.Align.CENTER)
|
|
grid.attach(label, 0, 0, 2, 1)
|
|
|
|
# Create "No" button
|
|
button_no = Gtk.Button(label="Cancel")
|
|
button_no.set_size_request(150, -1)
|
|
button_no.connect("clicked", lambda x: self.response(Gtk.ResponseType.NO))
|
|
grid.attach(button_no, 0, 2, 1, 1)
|
|
|
|
# Create "Yes" button
|
|
button_yes = Gtk.Button(label="Confirm")
|
|
button_yes.set_size_request(150, -1)
|
|
button_yes.connect("clicked", lambda x: self.response(Gtk.ResponseType.YES))
|
|
grid.attach(button_yes, 1, 2, 1, 1)
|
|
|
|
# Create a checkbox to optionally remove the prefix
|
|
self.checkbox = Gtk.CheckButton(label="Also remove the prefix")
|
|
self.checkbox.set_halign(Gtk.Align.CENTER)
|
|
grid.attach(self.checkbox, 0, 1, 2, 1)
|
|
|
|
# Display all widgets
|
|
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):
|
|
# Initialize the AddGame dialog
|
|
super().__init__(title="New Game", parent=parent)
|
|
self.set_resizable(False)
|
|
self.set_modal(True)
|
|
self.parent_window = parent
|
|
|
|
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)
|
|
|
|
|
|
grid = Gtk.Grid()
|
|
grid.set_row_spacing(10)
|
|
grid.set_column_spacing(10)
|
|
grid.set_margin_start(10)
|
|
grid.set_margin_end(10)
|
|
grid.set_margin_top(10)
|
|
grid.set_margin_bottom(10)
|
|
|
|
grid2 = Gtk.Grid()
|
|
|
|
grid2.set_row_spacing(10)
|
|
grid2.set_column_spacing(10)
|
|
grid2.set_margin_start(10)
|
|
grid2.set_margin_end(10)
|
|
grid2.set_margin_top(10)
|
|
grid2.set_margin_bottom(10)
|
|
|
|
grid3 = Gtk.Grid()
|
|
grid3.set_row_spacing(10)
|
|
grid3.set_column_spacing(10)
|
|
grid3.set_margin_start(10)
|
|
grid3.set_margin_end(10)
|
|
grid3.set_margin_top(10)
|
|
grid3.set_margin_bottom(10)
|
|
|
|
grid4 = Gtk.Grid()
|
|
grid4.set_row_spacing(10)
|
|
grid4.set_column_spacing(10)
|
|
grid4.set_margin_start(10)
|
|
grid4.set_margin_end(10)
|
|
|
|
grid5 = Gtk.Grid()
|
|
grid5.set_row_spacing(10)
|
|
grid5.set_column_spacing(10)
|
|
grid5.set_margin_start(10)
|
|
grid5.set_margin_end(10)
|
|
|
|
|
|
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)
|
|
|
|
# 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.set_tooltip_text("Game Title")
|
|
|
|
# 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.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.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="Runner")
|
|
self.label_runner.set_halign(Gtk.Align.START)
|
|
self.combo_box_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.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")
|
|
|
|
# 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")
|
|
|
|
# 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_sc_controller = Gtk.CheckButton(label="SC Controller")
|
|
self.checkbox_sc_controller.set_tooltip_text(
|
|
"Emulates a Xbox controller if the game doesn't support yours. Put the profile at ~/.config/faugus-launcher/controller.sccprofile.")
|
|
|
|
# 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.checkbox_shortcut = Gtk.CheckButton(label="Create Shortcut")
|
|
self.checkbox_shortcut.connect("toggled", self.on_checkbox_toggled)
|
|
|
|
# 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")
|
|
self.button_shortcut_icon.set_sensitive(False) # Initially disable the button
|
|
|
|
# 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)
|
|
|
|
# Event handlers
|
|
self.default_prefix = self.load_default_prefix()
|
|
self.entry_title.connect("changed", self.update_prefix_entry)
|
|
|
|
self.default_runner = self.load_default_runner()
|
|
|
|
# Criação das abas
|
|
self.notebook = Gtk.Notebook()
|
|
self.box.add(self.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)
|
|
|
|
# Página 1
|
|
page1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
tab_box1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
|
tab_label1 = Gtk.Label(label="Game")
|
|
tab_box1.pack_start(tab_label1, True, True, 0)
|
|
tab_box1.set_hexpand(True)
|
|
self.notebook.append_page(page1, tab_box1)
|
|
|
|
# Página 2
|
|
page2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
tab_box2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
|
tab_label2 = Gtk.Label(label="Tools")
|
|
tab_box2.pack_start(tab_label2, True, True, 0)
|
|
tab_box2.set_hexpand(True)
|
|
self.notebook.append_page(page2, tab_box2)
|
|
|
|
# Attach widgets to the grid layout
|
|
grid.attach(self.label_title, 0, 0, 4, 1)
|
|
grid.attach(self.entry_title, 0, 1, 4, 1)
|
|
|
|
grid.attach(self.label_path, 0, 2, 1, 1)
|
|
grid.attach(self.entry_path, 0, 3, 3, 1)
|
|
self.entry_path.set_hexpand(True)
|
|
grid.attach(self.button_search, 3, 3, 1, 1)
|
|
|
|
grid.attach(self.label_prefix, 0, 4, 1, 1)
|
|
grid.attach(self.entry_prefix, 0, 5, 3, 1)
|
|
self.entry_prefix.set_hexpand(True)
|
|
grid.attach(self.button_search_prefix, 3, 5, 1, 1)
|
|
|
|
grid5.attach(self.label_runner, 0, 6, 1, 1)
|
|
grid5.attach(self.combo_box_runner, 0, 7, 1, 1)
|
|
self.combo_box_runner.set_hexpand(True)
|
|
|
|
page1.add(grid)
|
|
page1.add(grid5)
|
|
|
|
grid2.attach(self.button_shortcut_icon, 2, 6, 1, 1)
|
|
grid2.attach(self.checkbox_shortcut, 0, 6, 1, 1)
|
|
self.checkbox_shortcut.set_hexpand(True)
|
|
|
|
page1.add(grid2)
|
|
|
|
grid3.attach(self.label_protonfix, 0, 0, 1, 1)
|
|
grid3.attach(self.entry_protonfix, 0, 1, 3, 1)
|
|
self.entry_protonfix.set_hexpand(True)
|
|
grid3.attach(self.button_search_protonfix, 3, 1, 1, 1)
|
|
|
|
grid3.attach(self.label_launch_arguments, 0, 2, 1, 1)
|
|
grid3.attach(self.entry_launch_arguments, 0, 3, 4, 1)
|
|
|
|
grid3.attach(self.label_game_arguments, 0, 4, 1, 1)
|
|
grid3.attach(self.entry_game_arguments, 0, 5, 4, 1)
|
|
|
|
page2.add(grid3)
|
|
|
|
grid4.attach(self.checkbox_mangohud, 0, 6, 1, 1)
|
|
self.checkbox_mangohud.set_hexpand(True)
|
|
grid4.attach(self.checkbox_gamemode, 0, 7, 1, 1)
|
|
self.checkbox_gamemode.set_hexpand(True)
|
|
grid4.attach(self.checkbox_sc_controller, 0, 8, 1, 1)
|
|
self.checkbox_sc_controller.set_hexpand(True)
|
|
|
|
grid4.attach(self.button_winecfg, 2, 6, 1, 1)
|
|
grid4.attach(self.button_winetricks, 2, 7, 1, 1)
|
|
grid4.attach(self.button_run, 2, 8, 1, 1)
|
|
|
|
page2.add(grid4)
|
|
|
|
botton_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
|
botton_box.set_margin_start(10)
|
|
botton_box.set_margin_end(10)
|
|
#botton_box.set_margin_top(10)
|
|
botton_box.set_margin_bottom(10)
|
|
self.button_cancel.set_hexpand(True)
|
|
self.button_ok.set_hexpand(True)
|
|
|
|
botton_box.pack_start(self.button_cancel, True, True, 0)
|
|
botton_box.pack_start(self.button_ok, True, True, 0)
|
|
|
|
self.box.add(botton_box)
|
|
|
|
self.populate_combobox_with_runners()
|
|
|
|
model = self.combo_box_runner.get_model()
|
|
index_to_activate = 0
|
|
if self.default_runner == "GE-Proton":
|
|
self.default_runner == "GE-Proton Latest (default)"
|
|
for i, row in enumerate(model):
|
|
if row[0] == self.default_runner:
|
|
index_to_activate = i
|
|
break
|
|
self.combo_box_runner.set_active(index_to_activate)
|
|
|
|
# Check if optional features are available and enable/disable accordingly
|
|
self.mangohud_enabled = os.path.exists("/usr/bin/mangohud")
|
|
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("/usr/bin/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.sc_controller_enabled = os.path.exists("/usr/bin/sc-controller") or os.path.exists(
|
|
"/usr/local/bin/sc-controller")
|
|
if not self.sc_controller_enabled:
|
|
self.checkbox_sc_controller.set_sensitive(False)
|
|
self.checkbox_sc_controller.set_active(False)
|
|
self.checkbox_sc_controller.set_tooltip_text(
|
|
"Emulates a Xbox controller if the game doesn't support yours. Put the profile at ~/.config/faugus-launcher/controller.sccprofile. NOT INSTALLED.")
|
|
|
|
# self.create_remove_shortcut(self)
|
|
self.button_shortcut_icon.set_image(self.set_image_shortcut_icon())
|
|
|
|
tab_box1.show_all()
|
|
tab_box2.show_all()
|
|
self.show_all()
|
|
|
|
def populate_combobox_with_runners(self):
|
|
# List of default entries
|
|
self.combo_box_runner.append_text("GE-Proton Latest (default)")
|
|
self.combo_box_runner.append_text("UMU-Proton Latest")
|
|
|
|
# Path to the directory containing the folders
|
|
runner_path = os.path.expanduser('~/.local/share/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 != "UMU-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.combo_box_runner.append_text(version)
|
|
|
|
except Exception as e:
|
|
print(f"Error accessing the directory: {e}")
|
|
|
|
# Set the active item, if desired
|
|
self.combo_box_runner.set_active(0)
|
|
|
|
def on_entry_changed(self, widget, entry):
|
|
if entry.get_text():
|
|
entry.get_style_context().remove_class("entry")
|
|
|
|
def load_default_prefix(self):
|
|
config_file = os.path.expanduser('~/.config/faugus-launcher/config.ini')
|
|
default_prefix = ""
|
|
if os.path.exists(config_file):
|
|
with open(config_file, 'r') as f:
|
|
config_data = f.read().splitlines()
|
|
config_dict = dict(line.split('=') for line in config_data)
|
|
default_prefix = config_dict.get('default-prefix', '').strip('"')
|
|
return default_prefix
|
|
|
|
def load_default_runner(self):
|
|
config_file = os.path.expanduser('~/.config/faugus-launcher/config.ini')
|
|
default_runner = ""
|
|
if os.path.exists(config_file):
|
|
with open(config_file, 'r') as f:
|
|
config_data = f.read().splitlines()
|
|
config_dict = dict(line.split('=') for line in config_data)
|
|
default_runner = config_dict.get('default-runner', '').strip('"')
|
|
return default_runner
|
|
|
|
def on_button_run_clicked(self, widget):
|
|
self.set_sensitive(False)
|
|
# Handle the click event of the Run button
|
|
validation_result = self.validate_fields(entry="prefix")
|
|
if not validation_result:
|
|
self.set_sensitive(True)
|
|
return
|
|
|
|
dialog = Gtk.FileChooserDialog(title="Select a file to run inside the prefix",
|
|
action=Gtk.FileChooserAction.OPEN)
|
|
dialog.set_current_folder(os.path.expanduser("~/"))
|
|
dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
|
|
|
|
file_filter = Gtk.FileFilter()
|
|
file_filter.set_name("Windows files")
|
|
file_filter.add_pattern("*.exe")
|
|
file_filter.add_pattern("*.msi")
|
|
file_filter.add_pattern("*.bat")
|
|
file_filter.add_pattern("*.lnk")
|
|
file_filter.add_pattern("*.reg")
|
|
dialog.add_filter(file_filter)
|
|
|
|
response = dialog.run()
|
|
if response == Gtk.ResponseType.OK:
|
|
title = self.entry_title.get_text()
|
|
prefix = self.entry_prefix.get_text()
|
|
|
|
title_formatted = re.sub(r'[^a-zA-Z0-9\s]', '', title)
|
|
title_formatted = title_formatted.replace(' ', '-')
|
|
title_formatted = '-'.join(title_formatted.lower().split())
|
|
|
|
runner = self.combo_box_runner.get_active_text()
|
|
|
|
if runner == "UMU-Proton Latest":
|
|
runner = ""
|
|
if runner == "GE-Proton Latest (default)":
|
|
runner = "GE-Proton"
|
|
|
|
command_parts = []
|
|
|
|
# Add command parts if they are not empty
|
|
|
|
file_run = dialog.get_filename()
|
|
if not file_run.endswith(".reg"):
|
|
if prefix:
|
|
command_parts.append(f'WINEPREFIX={prefix}')
|
|
if title_formatted:
|
|
command_parts.append(f'GAMEID={title_formatted}')
|
|
if runner:
|
|
command_parts.append(f'PROTONPATH={runner}')
|
|
command_parts.append(f'"/usr/bin/umu-run" "{file_run}"')
|
|
else:
|
|
if prefix:
|
|
command_parts.append(f'WINEPREFIX={prefix}')
|
|
if title_formatted:
|
|
command_parts.append(f'GAMEID={title_formatted}')
|
|
if runner:
|
|
command_parts.append(f'PROTONPATH={runner}')
|
|
command_parts.append(f'"/usr/bin/umu-run" regedit "{file_run}"')
|
|
|
|
# Join all parts into a single command
|
|
command = ' '.join(command_parts)
|
|
|
|
print(command)
|
|
|
|
# faugus-run path
|
|
faugus_run_path = "/usr/bin/faugus-run"
|
|
|
|
def run_command():
|
|
process = subprocess.Popen([sys.executable, faugus_run_path, command, "winecfg"])
|
|
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)
|
|
dialog.destroy()
|
|
|
|
def on_button_search_protonfix_clicked(self, widget):
|
|
webbrowser.open("https://umu.openwinecomponents.org/")
|
|
|
|
def on_checkbox_toggled(self, checkbox):
|
|
# Enable or disable the button based on the checkbox state
|
|
self.button_shortcut_icon.set_sensitive(checkbox.get_active())
|
|
|
|
def set_image_shortcut_icon(self):
|
|
|
|
image_path = "/usr/share/icons/faugus-launcher.png"
|
|
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path)
|
|
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
|
|
|
image = Gtk.Image.new_from_file(image_path)
|
|
image.set_from_pixbuf(scaled_pixbuf)
|
|
|
|
return image
|
|
|
|
def on_button_shortcut_icon_clicked(self, widget):
|
|
self.set_sensitive(False)
|
|
|
|
# Handle the click event of the Winetricks button
|
|
validation_result = self.validate_fields(entry="path")
|
|
if not validation_result:
|
|
self.set_sensitive(True)
|
|
return
|
|
|
|
title = self.entry_title.get_text()
|
|
path = self.entry_path.get_text()
|
|
|
|
# Handle the click event of the Create Shortcut button
|
|
title_formatted = re.sub(r'[^a-zA-Z0-9\s]', '', title)
|
|
title_formatted = title_formatted.replace(' ', '-')
|
|
title_formatted = '-'.join(title_formatted.lower().split())
|
|
|
|
# Check if the icon file exists
|
|
icons_path = os.path.expanduser("~/.config/faugus-launcher/icons/")
|
|
|
|
# Check if the icon directory exists and create if it doesn't
|
|
icon_directory = os.path.expanduser(f"~/.config/faugus-launcher/icons/{title_formatted}/")
|
|
if not os.path.exists(icon_directory):
|
|
os.makedirs(icon_directory)
|
|
|
|
# Execute 7z command to extract icon
|
|
os.system(f'7z e "{path}" -o{icon_directory} -r -aoa')
|
|
|
|
# Open file dialog to select .ico file
|
|
dialog = Gtk.FileChooserDialog(title="Select an icon for the shortcut", action=Gtk.FileChooserAction.OPEN)
|
|
dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
|
|
|
|
# Add a filter to limit selection to .ico files
|
|
filter_ico = Gtk.FileFilter()
|
|
filter_ico.set_name("Image files")
|
|
filter_ico.add_mime_type("image/*") # Other image formats
|
|
dialog.add_filter(filter_ico)
|
|
|
|
# Set the initial directory to the icon directory
|
|
dialog.set_current_folder(icon_directory)
|
|
|
|
# Connect signal to update preview widget when file selection changes
|
|
dialog.connect("update-preview", self.update_preview)
|
|
|
|
response = dialog.run()
|
|
if response == Gtk.ResponseType.OK:
|
|
file_path = dialog.get_filename()
|
|
# Move and rename the icon file
|
|
shutil.move(file_path, os.path.expanduser(f"{icons_path}{title_formatted}.ico"))
|
|
|
|
image_path = f"{icons_path}{title_formatted}.ico"
|
|
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path)
|
|
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
|
|
|
image = Gtk.Image.new_from_file(image_path)
|
|
image.set_from_pixbuf(scaled_pixbuf)
|
|
|
|
self.button_shortcut_icon.set_image(image)
|
|
|
|
# Delete the folder after the icon is moved
|
|
shutil.rmtree(icon_directory)
|
|
dialog.destroy()
|
|
self.set_sensitive(True)
|
|
|
|
else:
|
|
# Delete the folder
|
|
shutil.rmtree(icon_directory)
|
|
dialog.destroy()
|
|
self.set_sensitive(True)
|
|
return
|
|
|
|
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 = re.sub(r'[^a-zA-Z0-9\s]', '', title).replace(' ', '-').lower()
|
|
desktop_file_path = os.path.expanduser(f"~/.local/share/applications/{title_formatted}.desktop")
|
|
|
|
# Check if the shortcut file exists
|
|
shortcut_exists = os.path.exists(desktop_file_path)
|
|
|
|
# Mark the checkbox if the shortcut exists
|
|
self.checkbox_shortcut.set_active(shortcut_exists)
|
|
|
|
def update_prefix_entry(self, entry):
|
|
# Update the prefix entry based on the title and self.default_prefix
|
|
title_formatted = re.sub(r'[^a-zA-Z0-9\s]', '', entry.get_text())
|
|
title_formatted = title_formatted.replace(' ', '-')
|
|
title_formatted = '-'.join(title_formatted.lower().split())
|
|
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 = re.sub(r'[^a-zA-Z0-9\s]', '', title)
|
|
title_formatted = title_formatted.replace(' ', '-')
|
|
title_formatted = '-'.join(title_formatted.lower().split())
|
|
|
|
runner = self.combo_box_runner.get_active_text()
|
|
|
|
if runner == "UMU-Proton Latest":
|
|
runner = ""
|
|
if runner == "GE-Proton Latest (default)":
|
|
runner = "GE-Proton"
|
|
|
|
command_parts = []
|
|
|
|
# Add command parts if they are not empty
|
|
|
|
if prefix:
|
|
command_parts.append(f'WINEPREFIX={prefix}')
|
|
if title_formatted:
|
|
command_parts.append(f'GAMEID={title_formatted}')
|
|
if runner:
|
|
command_parts.append(f'PROTONPATH={runner}')
|
|
|
|
# Add the fixed command and remaining arguments
|
|
command_parts.append('"/usr/bin/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 = "/usr/bin/faugus-run"
|
|
|
|
def run_command():
|
|
process = subprocess.Popen([sys.executable, faugus_run_path, command, "winecfg"])
|
|
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
|
|
|
|
prefix = self.entry_prefix.get_text()
|
|
|
|
runner = self.combo_box_runner.get_active_text()
|
|
|
|
if runner == "UMU-Proton Latest":
|
|
runner = ""
|
|
if runner == "GE-Proton Latest (default)":
|
|
runner = "GE-Proton"
|
|
|
|
command_parts = []
|
|
|
|
# Add command parts if they are not empty
|
|
|
|
if prefix:
|
|
command_parts.append(f'WINEPREFIX={prefix}')
|
|
command_parts.append(f'GAMEID=winetricks-gui')
|
|
command_parts.append(f'STORE=none')
|
|
if runner:
|
|
command_parts.append(f'PROTONPATH={runner}')
|
|
|
|
# Add the fixed command and remaining arguments
|
|
command_parts.append('"/usr/bin/umu-run"')
|
|
command_parts.append('""')
|
|
|
|
# Join all parts into a single command
|
|
command = ' '.join(command_parts)
|
|
|
|
print(command)
|
|
|
|
# faugus-run path
|
|
faugus_run_path = "/usr/bin/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):
|
|
# Handle the click event of the search button to select the game's .exe
|
|
dialog = Gtk.FileChooserDialog(title="Select the game's .exe", parent=self, action=Gtk.FileChooserAction.OPEN)
|
|
if not self.entry_path.get_text():
|
|
dialog.set_current_folder(os.path.expanduser("~/"))
|
|
else:
|
|
dialog.set_current_folder(os.path.dirname(self.entry_path.get_text()))
|
|
dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
|
|
|
|
response = dialog.run()
|
|
if response == Gtk.ResponseType.OK:
|
|
self.entry_path.set_text(dialog.get_filename())
|
|
|
|
dialog.destroy()
|
|
|
|
def on_button_search_prefix_clicked(self, widget):
|
|
dialog = Gtk.FileChooserDialog(title="Select a prefix location", parent=self,
|
|
action=Gtk.FileChooserAction.SELECT_FOLDER)
|
|
|
|
if not self.entry_prefix.get_text():
|
|
dialog.set_current_folder(os.path.expanduser(self.default_prefix))
|
|
else:
|
|
dialog.set_current_folder(self.entry_prefix.get_text())
|
|
dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
|
|
|
|
response = dialog.run()
|
|
if response == Gtk.ResponseType.OK:
|
|
new_prefix = dialog.get_filename()
|
|
self.default_prefix = new_prefix
|
|
self.entry_title.emit("changed")
|
|
|
|
dialog.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()
|
|
|
|
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 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
|
|
|
|
return True
|
|
|
|
class CreateShortcut(Gtk.Window):
|
|
def __init__(self, file_path):
|
|
super().__init__(title="Faugus Launcher")
|
|
self.file_path = file_path
|
|
self.set_resizable(False)
|
|
|
|
game_title = os.path.basename(file_path)
|
|
self.set_title(game_title)
|
|
print(self.file_path)
|
|
|
|
self.default_prefix = ""
|
|
|
|
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.set_tooltip_text("Game Title")
|
|
|
|
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.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)
|
|
|
|
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.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.button_shortcut_icon = Gtk.Button()
|
|
self.button_shortcut_icon.set_tooltip_text("Select an icon for the shortcut")
|
|
self.button_shortcut_icon.connect("clicked", self.on_button_shortcut_icon_clicked)
|
|
|
|
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_sc_controller = Gtk.CheckButton(label="SC Controller")
|
|
self.checkbox_sc_controller.set_tooltip_text(
|
|
"Emulates a Xbox controller if the game doesn't support yours. Put the profile at ~/.config/faugus-launcher/controller.sccprofile.")
|
|
|
|
# Button Cancel
|
|
self.button_cancel = Gtk.Button(label="Cancel")
|
|
self.button_cancel.connect("clicked", self.on_cancel_clicked)
|
|
self.button_cancel.set_size_request(120, -1)
|
|
self.button_cancel.set_halign(Gtk.Align.CENTER)
|
|
|
|
# Button Ok
|
|
self.button_ok = Gtk.Button(label="Ok")
|
|
self.button_ok.connect("clicked", self.on_ok_clicked)
|
|
self.button_ok.set_size_request(120, -1)
|
|
self.button_ok.set_halign(Gtk.Align.CENTER)
|
|
|
|
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)
|
|
|
|
# Create the Grids
|
|
self.grid1 = Gtk.Grid()
|
|
self.grid1.set_row_spacing(10)
|
|
self.grid1.set_column_spacing(10)
|
|
self.grid1.set_margin_start(10)
|
|
self.grid1.set_margin_end(10)
|
|
self.grid1.set_margin_top(10)
|
|
self.grid1.set_margin_bottom(10)
|
|
self.grid1.set_valign(Gtk.Align.CENTER)
|
|
|
|
self.grid2 = Gtk.Grid()
|
|
self.grid2.set_row_spacing(10)
|
|
self.grid2.set_column_spacing(10)
|
|
self.grid2.set_margin_start(10)
|
|
self.grid2.set_margin_end(10)
|
|
self.grid2.set_margin_top(10)
|
|
self.grid2.set_margin_bottom(10)
|
|
|
|
self.grid3 = Gtk.Grid()
|
|
self.grid3.set_row_spacing(10)
|
|
self.grid3.set_column_spacing(10)
|
|
self.grid3.set_margin_start(10)
|
|
self.grid3.set_margin_end(10)
|
|
self.grid3.set_margin_top(10)
|
|
self.grid3.set_margin_bottom(10)
|
|
self.grid3.set_valign(Gtk.Align.CENTER)
|
|
self.grid3.set_halign(Gtk.Align.END)
|
|
|
|
self.grid4 = Gtk.Grid()
|
|
self.grid4.set_row_spacing(10)
|
|
self.grid4.set_column_spacing(10)
|
|
self.grid4.set_margin_start(10)
|
|
self.grid4.set_margin_end(10)
|
|
self.grid4.set_margin_top(10)
|
|
self.grid4.set_margin_bottom(10)
|
|
|
|
self.entry_title.set_hexpand(True)
|
|
self.entry_title.set_valign(Gtk.Align.CENTER)
|
|
self.grid1.attach(self.label_title, 0, 0, 1, 1)
|
|
self.grid1.attach(self.entry_title, 0, 1, 4, 1)
|
|
|
|
self.grid1.attach(self.label_protonfix, 0, 2, 1, 1)
|
|
self.grid1.attach(self.entry_protonfix, 0, 3, 3, 1)
|
|
self.entry_protonfix.set_hexpand(True)
|
|
self.grid1.attach(self.button_search_protonfix, 3, 3, 1, 1)
|
|
|
|
self.grid1.attach(self.label_launch_arguments, 0, 4, 1, 1)
|
|
self.grid1.attach(self.entry_launch_arguments, 0, 5, 4, 1)
|
|
self.entry_launch_arguments.set_hexpand(True)
|
|
|
|
self.grid1.attach(self.label_game_arguments, 0, 6, 1, 1)
|
|
self.grid1.attach(self.entry_game_arguments, 0, 7, 4, 1)
|
|
self.entry_game_arguments.set_hexpand(True)
|
|
|
|
|
|
self.grid2.attach(self.checkbox_mangohud, 0, 0, 1, 1)
|
|
self.grid2.attach(self.checkbox_gamemode, 0, 1, 1, 1)
|
|
self.grid2.attach(self.checkbox_sc_controller, 0, 2, 1, 1)
|
|
|
|
self.grid3.attach(self.button_shortcut_icon, 0, 0, 1, 1)
|
|
|
|
|
|
self.grid4.attach(self.button_cancel, 0, 0, 1, 1)
|
|
self.grid4.attach(self.button_ok, 1, 0, 1, 1)
|
|
|
|
# Create a main grid to hold the grids
|
|
self.main_grid = Gtk.Grid()
|
|
|
|
# Attach grid1 and grid2 to the main grid in the same row
|
|
self.main_grid.attach(self.grid1, 0, 0, 2, 1)
|
|
self.main_grid.attach(self.grid2, 0, 1, 2, 1)
|
|
self.main_grid.attach(self.grid3, 1, 1, 1, 1)
|
|
|
|
# Attach grid3 to the main grid in the next row
|
|
self.main_grid.attach(self.grid4, 0, 2, 2, 1)
|
|
|
|
self.load_config()
|
|
|
|
# Check if optional features are available and enable/disable accordingly
|
|
self.mangohud_enabled = os.path.exists("/usr/bin/mangohud")
|
|
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("/usr/bin/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.sc_controller_enabled = os.path.exists("/usr/bin/sc-controller") or os.path.exists(
|
|
"/usr/local/bin/sc-controller")
|
|
if not self.sc_controller_enabled:
|
|
self.checkbox_sc_controller.set_sensitive(False)
|
|
self.checkbox_sc_controller.set_active(False)
|
|
self.checkbox_sc_controller.set_tooltip_text(
|
|
"Emulates a Xbox controller if the game doesn't support yours. Put the profile at ~/.config/faugus-launcher/controller.sccprofile. NOT INSTALLED.")
|
|
|
|
# Add the main grid to the window
|
|
self.add(self.main_grid)
|
|
|
|
# Set the image for the shortcut icon button
|
|
self.button_shortcut_icon.set_image(self.set_image_shortcut_icon())
|
|
|
|
# Connect the destroy signal to Gtk.main_quit
|
|
self.connect("destroy", Gtk.main_quit)
|
|
|
|
def on_button_search_protonfix_clicked(self, widget):
|
|
webbrowser.open("https://umu.openwinecomponents.org/")
|
|
|
|
def load_config(self):
|
|
# Load configuration from file
|
|
config_file = os.path.expanduser("~/.config/faugus-launcher/config.ini")
|
|
if os.path.isfile(config_file):
|
|
with open(config_file, 'r') as f:
|
|
config_data = f.read().splitlines()
|
|
config_dict = dict(line.split('=') for line in config_data)
|
|
self.default_prefix = config_dict.get('default-prefix', '').strip('"')
|
|
|
|
mangohud = config_dict.get('mangohud', 'False') == 'True'
|
|
gamemode = config_dict.get('gamemode', 'False') == 'True'
|
|
sc_controller = config_dict.get('sc-controller', 'False') == 'True'
|
|
self.default_runner = config_dict.get('default-runner', '').strip('"')
|
|
|
|
self.checkbox_mangohud.set_active(mangohud)
|
|
self.checkbox_gamemode.set_active(gamemode)
|
|
self.checkbox_sc_controller.set_active(sc_controller)
|
|
|
|
else:
|
|
# Save default configuration if file does not exist
|
|
self.save_config(False, '', "False", "False", "False", "GE-Proton Latest (default)")
|
|
|
|
def save_config(self, checkbox_state, default_prefix, mangohud_state, gamemode_state, sc_controller_state, default_runner):
|
|
# Path to the configuration file
|
|
config_file = os.path.expanduser("~/.config/faugus-launcher/config.ini")
|
|
|
|
config_path = os.path.expanduser("~/.config/faugus-launcher/")
|
|
# Create the configuration directory if it doesn't exist
|
|
if not os.path.exists(config_path):
|
|
os.makedirs(config_path)
|
|
|
|
default_prefix = os.path.expanduser(f"{config_path}prefixes")
|
|
self.default_prefix = os.path.expanduser(f"{config_path}prefixes")
|
|
|
|
default_runner = (f'"{default_runner}"')
|
|
|
|
# Dictionary to store existing configurations
|
|
config = {}
|
|
|
|
# Read the existing configuration file
|
|
if os.path.exists(config_file):
|
|
with open(config_file, 'r') as f:
|
|
for line in f:
|
|
key, value = line.strip().split('=', 1)
|
|
config[key] = value.strip('"')
|
|
|
|
# Update configurations with new values
|
|
config['close-onlaunch'] = checkbox_state
|
|
config['default-prefix'] = default_prefix
|
|
config['mangohud'] = mangohud_state
|
|
config['gamemode'] = gamemode_state
|
|
config['sc-controller'] = sc_controller_state
|
|
config['default-runner'] = default_runner
|
|
|
|
# Write configurations back to the file
|
|
with open(config_file, 'w') as f:
|
|
for key, value in config.items():
|
|
if key == 'default-prefix':
|
|
f.write(f'{key}="{value}"\n')
|
|
else:
|
|
f.write(f'{key}={value}\n')
|
|
|
|
|
|
def on_cancel_clicked(self, widget):
|
|
self.destroy()
|
|
|
|
def on_ok_clicked(self, widget):
|
|
|
|
validation_result = self.validate_fields()
|
|
if not validation_result:
|
|
self.set_sensitive(True)
|
|
return
|
|
|
|
title = self.entry_title.get_text()
|
|
|
|
# Handle the click event of the Create Shortcut button
|
|
title_formatted = re.sub(r'[^a-zA-Z0-9\s]', '', title)
|
|
title_formatted = title_formatted.replace(' ', '-')
|
|
title_formatted = '-'.join(title_formatted.lower().split())
|
|
|
|
# Check if the icon file exists
|
|
icons_path = os.path.expanduser("~/.config/faugus-launcher/icons/")
|
|
new_icon_path = os.path.join(icons_path, f"{title_formatted}.ico")
|
|
if not os.path.exists(new_icon_path):
|
|
new_icon_path = "/usr/share/icons/faugus-launcher.png"
|
|
|
|
protonfix = self.entry_protonfix.get_text()
|
|
launch_arguments = self.entry_launch_arguments.get_text()
|
|
game_arguments = self.entry_game_arguments.get_text()
|
|
|
|
mangohud = "MANGOHUD=1" if self.checkbox_mangohud.get_active() else ""
|
|
gamemode = "gamemoderun" if self.checkbox_gamemode.get_active() else ""
|
|
sc_controller = "SC_CONTROLLER=1" if self.checkbox_sc_controller.get_active() else ""
|
|
|
|
# Get the directory containing the executable
|
|
game_directory = os.path.dirname(self.file_path)
|
|
|
|
command_parts = []
|
|
|
|
# Add command parts if they are not empty
|
|
if mangohud:
|
|
command_parts.append(mangohud)
|
|
if sc_controller:
|
|
command_parts.append(sc_controller)
|
|
|
|
#command_parts.append(f'WINEPREFIX={self.default_prefix}/default')
|
|
|
|
if protonfix:
|
|
command_parts.append(f'GAMEID={protonfix}')
|
|
else:
|
|
command_parts.append(f'GAMEID={title_formatted}')
|
|
|
|
if gamemode:
|
|
command_parts.append(gamemode)
|
|
if launch_arguments:
|
|
command_parts.append(launch_arguments)
|
|
|
|
# Add the fixed command and remaining arguments
|
|
command_parts.append('"/usr/bin/umu-run"')
|
|
if self.file_path:
|
|
command_parts.append(f'"{self.file_path}"')
|
|
if game_arguments:
|
|
command_parts.append(f'"{game_arguments}"')
|
|
|
|
# Join all parts into a single command
|
|
command = ' '.join(command_parts)
|
|
|
|
# Create a .desktop file
|
|
desktop_file_content = f"""[Desktop Entry]
|
|
Name={title}
|
|
Exec=/usr/bin/faugus-run '{command}'
|
|
Icon={new_icon_path}
|
|
Type=Application
|
|
Categories=Game;
|
|
Path={game_directory}
|
|
"""
|
|
|
|
# Check if the destination directory exists and create if it doesn't
|
|
applications_directory = os.path.expanduser("~/.local/share/applications/")
|
|
if not os.path.exists(applications_directory):
|
|
os.makedirs(applications_directory)
|
|
|
|
desktop_directory = os.path.expanduser("~/Desktop")
|
|
if not os.path.exists(desktop_directory):
|
|
os.makedirs(desktop_directory)
|
|
|
|
applications_shortcut_path = os.path.expanduser(f"~/.local/share/applications/{title_formatted}.desktop")
|
|
|
|
with open(applications_shortcut_path, 'w') as desktop_file:
|
|
desktop_file.write(desktop_file_content)
|
|
|
|
# Make the .desktop file executable
|
|
os.chmod(applications_shortcut_path, 0o755)
|
|
|
|
# Copy the shortcut to Desktop
|
|
desktop_shortcut_path = os.path.expanduser(f"~/Desktop/{title_formatted}.desktop")
|
|
shutil.copy(applications_shortcut_path, desktop_shortcut_path)
|
|
|
|
self.destroy()
|
|
|
|
def on_entry_changed(self, widget, entry):
|
|
if entry.get_text():
|
|
entry.get_style_context().remove_class("entry")
|
|
|
|
def set_image_shortcut_icon(self):
|
|
image_path = "/usr/share/icons/faugus-launcher.png"
|
|
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path)
|
|
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
|
|
|
image = Gtk.Image.new_from_pixbuf(scaled_pixbuf)
|
|
return image
|
|
|
|
def on_button_shortcut_icon_clicked(self, widget):
|
|
self.set_sensitive(False)
|
|
|
|
# Handle the click event of the Winetricks button
|
|
validation_result = self.validate_fields()
|
|
if not validation_result:
|
|
self.set_sensitive(True)
|
|
return
|
|
|
|
title = self.entry_title.get_text()
|
|
path = self.file_path
|
|
|
|
title_formatted = re.sub(r'[^a-zA-Z0-9\s]', '', title)
|
|
title_formatted = title_formatted.replace(' ', '-')
|
|
title_formatted = '-'.join(title_formatted.lower().split())
|
|
|
|
icons_path = os.path.expanduser("~/.config/faugus-launcher/icons/")
|
|
icon_directory = os.path.expanduser(f"~/.config/faugus-launcher/icons/{title_formatted}/")
|
|
if not os.path.exists(icon_directory):
|
|
os.makedirs(icon_directory)
|
|
|
|
os.system(f'7z e "{path}" -o{icon_directory} -r -aoa')
|
|
|
|
dialog = Gtk.FileChooserDialog(title="Select an icon for the shortcut", action=Gtk.FileChooserAction.OPEN)
|
|
dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)
|
|
|
|
filter_ico = Gtk.FileFilter()
|
|
filter_ico.set_name("Image files")
|
|
filter_ico.add_mime_type("image/*")
|
|
dialog.add_filter(filter_ico)
|
|
dialog.set_current_folder(icon_directory)
|
|
dialog.connect("update-preview", self.update_preview)
|
|
|
|
response = dialog.run()
|
|
if response == Gtk.ResponseType.OK:
|
|
file_path = dialog.get_filename()
|
|
shutil.move(file_path, os.path.expanduser(f"{icons_path}{title_formatted}.ico"))
|
|
|
|
image_path = f"{icons_path}{title_formatted}.ico"
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path)
|
|
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
|
|
|
image = Gtk.Image.new_from_pixbuf(scaled_pixbuf)
|
|
self.button_shortcut_icon.set_image(image)
|
|
|
|
shutil.rmtree(icon_directory)
|
|
dialog.destroy()
|
|
self.set_sensitive(True)
|
|
|
|
else:
|
|
shutil.rmtree(icon_directory)
|
|
dialog.destroy()
|
|
self.set_sensitive(True)
|
|
|
|
def update_preview(self, dialog):
|
|
if file_path := dialog.get_preview_filename():
|
|
try:
|
|
pixbuf = GdkPixbuf.Pixbuf.new_from_file(file_path)
|
|
max_width = 400
|
|
max_height = 400
|
|
width = pixbuf.get_width()
|
|
height = pixbuf.get_height()
|
|
|
|
if width > max_width or height > max_height:
|
|
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 validate_fields(self):
|
|
|
|
title = self.entry_title.get_text()
|
|
|
|
self.entry_title.get_style_context().remove_class("entry")
|
|
|
|
if not title:
|
|
self.entry_title.get_style_context().add_class("entry")
|
|
return False
|
|
|
|
return True
|
|
|
|
def run_file(file_path):
|
|
config_file = os.path.expanduser("~/.config/faugus-launcher/config.ini")
|
|
if os.path.exists(config_file):
|
|
with open(config_file, 'r') as f:
|
|
config_data = f.read().splitlines()
|
|
config_dict = dict(line.split('=') for line in config_data)
|
|
default_prefix = config_dict.get('default-prefix', '').strip('"')
|
|
mangohud = config_dict.get('mangohud', 'False') == 'True'
|
|
gamemode = config_dict.get('gamemode', 'False') == 'True'
|
|
sc_controller = config_dict.get('sc-controller', 'False') == 'True'
|
|
default_runner = config_dict.get('default-runner', '').strip('"')
|
|
else:
|
|
# Define the configuration path
|
|
config_path = os.path.expanduser("~/.config/faugus-launcher/")
|
|
# Create the configuration directory if it doesn't exist
|
|
if not os.path.exists(config_path):
|
|
os.makedirs(config_path)
|
|
|
|
default_prefix = os.path.expanduser(f"{config_path}prefixes")
|
|
mangohud = 'False'
|
|
gamemode = 'False'
|
|
sc_controller = 'False'
|
|
default_runner = 'GE-Proton Latest (default)'
|
|
|
|
with open(config_file, 'w') as f:
|
|
f.write(f'close-onlaunch=False\n')
|
|
f.write(f'default-prefix="{default_prefix}"\n')
|
|
f.write(f'mangohud=False\n')
|
|
f.write(f'gamemode=False\n')
|
|
f.write(f'sc-controller=False\n')
|
|
f.write(f'default_runner="GE-Proton Latest (default)"\n')
|
|
|
|
if not file_path.endswith(".reg"):
|
|
mangohud = "MANGOHUD=1" if mangohud else ""
|
|
gamemode = "gamemoderun" if gamemode else ""
|
|
sc_controller = "SC_CONTROLLER=1" if sc_controller else ""
|
|
|
|
# Get the directory of the file
|
|
file_dir = os.path.dirname(os.path.abspath(file_path))
|
|
|
|
# Define paths
|
|
prefix_path = os.path.expanduser(f"{default_prefix}/default")
|
|
faugus_run_path = "/usr/bin/faugus-run"
|
|
|
|
if not file_path.endswith(".reg"):
|
|
mangohud_enabled = os.path.exists("/usr/bin/mangohud")
|
|
gamemode_enabled = os.path.exists("/usr/bin/gamemoderun") or os.path.exists("/usr/games/gamemoderun")
|
|
sc_controller_enabled = os.path.exists("/usr/bin/sc-controller") or os.path.exists("/usr/local/bin/sc-controller")
|
|
|
|
if default_runner == "UMU-Proton Latest":
|
|
default_runner = ""
|
|
if default_runner == "GE-Proton Latest (default)":
|
|
default_runner = "GE-Proton"
|
|
|
|
command_parts = []
|
|
|
|
if not file_path.endswith(".reg"):
|
|
# Add command parts if they are not empty
|
|
if mangohud_enabled and mangohud:
|
|
command_parts.append(mangohud)
|
|
if sc_controller_enabled and sc_controller:
|
|
command_parts.append(sc_controller)
|
|
command_parts.append(os.path.expanduser(f"WINEPREFIX={default_prefix}/default"))
|
|
command_parts.append('GAMEID=default')
|
|
if default_runner:
|
|
command_parts.append(f'PROTONPATH={default_runner}')
|
|
if not file_path.endswith(".reg"):
|
|
if gamemode_enabled and gamemode:
|
|
command_parts.append(gamemode)
|
|
|
|
# Add the fixed command and remaining arguments
|
|
command_parts.append('"/usr/bin/umu-run"')
|
|
if file_path.endswith(".reg"):
|
|
command_parts.append(f'regedit "{file_path}"')
|
|
else:
|
|
command_parts.append(f'"{file_path}"')
|
|
|
|
# Join all parts into a single command
|
|
command = ' '.join(command_parts)
|
|
|
|
# Run the command in the directory of the file
|
|
subprocess.run([faugus_run_path, command], cwd=file_dir)
|
|
|
|
def main():
|
|
if len(sys.argv) == 1:
|
|
# Executed without arguments
|
|
app = Main()
|
|
app.connect("destroy", Gtk.main_quit)
|
|
app.show_all()
|
|
Gtk.main()
|
|
elif len(sys.argv) == 2:
|
|
# Executed with a file
|
|
run_file(sys.argv[1])
|
|
elif len(sys.argv) == 3 and sys.argv[2] == "shortcut":
|
|
# Executed with a file and the "shortcut" argument
|
|
app = CreateShortcut(sys.argv[1])
|
|
app.show_all()
|
|
Gtk.main()
|
|
else:
|
|
print("Invalid arguments")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|