Some code restructuring
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Faugus Create Shortcut
|
||||
Exec=faugus-launcher --shortcut %f
|
||||
Exec=python -m faugus.shortcut %f
|
||||
MimeType=application/x-ms-dos-executable;application/x-msi;application/x-ms-shortcut;application/x-bat;text/x-ms-regedit
|
||||
Icon=@ICON@
|
||||
NoDisplay=true
|
||||
|
||||
72
faugus/config_manager.py
Normal file
72
faugus/config_manager.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from faugus.language_config import *
|
||||
|
||||
faugus_launcher_dir = PathManager.user_config('faugus-launcher')
|
||||
prefixes_dir = str(Path.home() / 'Faugus')
|
||||
|
||||
class ConfigManager:
|
||||
def __init__(self):
|
||||
self.default_config = {
|
||||
'close-onlaunch': 'False',
|
||||
'default-prefix': prefixes_dir,
|
||||
'mangohud': 'False',
|
||||
'gamemode': 'False',
|
||||
'disable-hidraw': 'False',
|
||||
'default-runner': 'GE-Proton',
|
||||
'lossless-location': '',
|
||||
'discrete-gpu': 'False',
|
||||
'splash-disable': 'False',
|
||||
'system-tray': 'False',
|
||||
'start-boot': 'False',
|
||||
'mono-icon': 'False',
|
||||
'interface-mode': 'List',
|
||||
'start-maximized': 'False',
|
||||
'start-fullscreen': 'False',
|
||||
'show-labels': 'False',
|
||||
'smaller-banners': 'False',
|
||||
'enable-logging': 'False',
|
||||
'wayland-driver': 'False',
|
||||
'enable-hdr': 'False',
|
||||
'enable-wow64': 'False',
|
||||
'language': lang,
|
||||
'logging-warning': 'False',
|
||||
'show-hidden': 'False',
|
||||
}
|
||||
|
||||
self.config = {}
|
||||
self.load_config()
|
||||
|
||||
def load_config(self):
|
||||
if os.path.isfile(config_file_dir):
|
||||
with open(config_file_dir, 'r') as f:
|
||||
for line in f.read().splitlines():
|
||||
if '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
key = key.strip()
|
||||
value = value.strip().strip('"')
|
||||
self.config[key] = value
|
||||
|
||||
updated = False
|
||||
for key, default_value in self.default_config.items():
|
||||
if key not in self.config:
|
||||
self.config[key] = default_value
|
||||
updated = True
|
||||
|
||||
if updated or not os.path.isfile(config_file_dir):
|
||||
self.save_config()
|
||||
|
||||
def save_config(self):
|
||||
if not os.path.exists(faugus_launcher_dir):
|
||||
os.makedirs(faugus_launcher_dir)
|
||||
|
||||
with open(config_file_dir, 'w') as f:
|
||||
for key, value in self.config.items():
|
||||
if key in ['default-prefix', 'default-runner']:
|
||||
f.write(f'{key}="{value}"\n')
|
||||
else:
|
||||
f.write(f'{key}={value}\n')
|
||||
|
||||
def save_with_values(self, *args):
|
||||
keys = list(self.default_config.keys())
|
||||
for key, value in zip(keys, args):
|
||||
self.config[key] = str(value)
|
||||
self.save_config()
|
||||
28
faugus/dark_theme.py
Normal file
28
faugus/dark_theme.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import os
|
||||
from gi.repository import Gtk, Gio, GLib
|
||||
IS_FLATPAK = 'FLATPAK_ID' in os.environ or os.path.exists('/.flatpak-info')
|
||||
|
||||
def apply_dark_theme():
|
||||
if IS_FLATPAK:
|
||||
if (os.environ.get("XDG_CURRENT_DESKTOP")) == "KDE":
|
||||
Gtk.Settings.get_default().set_property("gtk-theme-name", "Breeze")
|
||||
try:
|
||||
proxy = Gio.DBusProxy.new_sync(
|
||||
Gio.bus_get_sync(Gio.BusType.SESSION, None), 0, None,
|
||||
"org.freedesktop.portal.Desktop",
|
||||
"/org/freedesktop/portal/desktop",
|
||||
"org.freedesktop.portal.Settings", None)
|
||||
is_dark = proxy.call_sync(
|
||||
"Read", GLib.Variant("(ss)", ("org.freedesktop.appearance", "color-scheme")),
|
||||
0, -1, None).unpack()[0] == 1
|
||||
except:
|
||||
is_dark = False
|
||||
Gtk.Settings.get_default().set_property("gtk-application-prefer-dark-theme", is_dark)
|
||||
else:
|
||||
desktop_env = Gio.Settings.new("org.gnome.desktop.interface")
|
||||
try:
|
||||
is_dark_theme = desktop_env.get_string("color-scheme") == "prefer-dark"
|
||||
except Exception:
|
||||
is_dark_theme = "-dark" in desktop_env.get_string("gtk-theme")
|
||||
if is_dark_theme:
|
||||
Gtk.Settings.get_default().set_property("gtk-application-prefer-dark-theme", True)
|
||||
38
faugus/language_config.py
Normal file
38
faugus/language_config.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import locale
|
||||
|
||||
from faugus.path_manager import *
|
||||
|
||||
config_file_dir = PathManager.user_config('faugus-launcher/config.ini')
|
||||
|
||||
def get_system_locale():
|
||||
lang = os.environ.get('LANG') or os.environ.get('LC_MESSAGES')
|
||||
if lang:
|
||||
return lang.split('.')[0]
|
||||
|
||||
try:
|
||||
loc = locale.getdefaultlocale()[0]
|
||||
if loc:
|
||||
return loc
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return 'en_US'
|
||||
|
||||
def get_language_from_config():
|
||||
if os.path.exists(config_file_dir):
|
||||
with open(config_file_dir, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith('language='):
|
||||
return line.split('=', 1)[1].strip()
|
||||
return None
|
||||
|
||||
lang = get_language_from_config()
|
||||
if not lang:
|
||||
lang = get_system_locale()
|
||||
|
||||
LOCALE_DIR = (
|
||||
PathManager.system_data('locale')
|
||||
if os.path.isdir(PathManager.system_data('locale'))
|
||||
else os.path.join(os.path.dirname(__file__), 'locale')
|
||||
)
|
||||
10
faugus/meson.build
Normal file
10
faugus/meson.build
Normal file
@@ -0,0 +1,10 @@
|
||||
py.install_sources(
|
||||
'components.py',
|
||||
'proton_downloader.py',
|
||||
'shortcut.py',
|
||||
'config_manager.py',
|
||||
'path_manager.py',
|
||||
'language_config.py',
|
||||
'dark_theme.py',
|
||||
subdir: 'faugus',
|
||||
)
|
||||
45
faugus/path_manager.py
Normal file
45
faugus/path_manager.py
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
class PathManager:
|
||||
@staticmethod
|
||||
def system_data(*relative_paths):
|
||||
xdg_data_dirs = os.getenv('XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(':')
|
||||
for data_dir in xdg_data_dirs:
|
||||
path = Path(data_dir).joinpath(*relative_paths)
|
||||
if path.exists():
|
||||
return str(path)
|
||||
return str(Path(xdg_data_dirs[0]).joinpath(*relative_paths))
|
||||
|
||||
@staticmethod
|
||||
def user_data(*relative_paths):
|
||||
xdg_data_home = Path(os.getenv('XDG_DATA_HOME', Path.home() / '.local/share'))
|
||||
return str(xdg_data_home.joinpath(*relative_paths))
|
||||
|
||||
@staticmethod
|
||||
def user_config(*relative_paths):
|
||||
xdg_config_home = Path(os.getenv('XDG_CONFIG_HOME', Path.home() / '.config'))
|
||||
return str(xdg_config_home.joinpath(*relative_paths))
|
||||
|
||||
@staticmethod
|
||||
def find_binary(binary_name):
|
||||
paths = os.getenv('PATH', '').split(':')
|
||||
for path in paths:
|
||||
binary_path = Path(path) / binary_name
|
||||
if binary_path.exists():
|
||||
return str(binary_path)
|
||||
return f'/usr/bin/{binary_name}' # Fallback
|
||||
|
||||
@staticmethod
|
||||
def get_icon(icon_name):
|
||||
icon_paths = [
|
||||
PathManager.user_data('icons', icon_name),
|
||||
PathManager.system_data('icons/hicolor/256x256/apps', icon_name),
|
||||
PathManager.system_data('icons', icon_name)
|
||||
]
|
||||
for path in icon_paths:
|
||||
if Path(path).exists():
|
||||
return path
|
||||
return icon_paths[-1] # Fallback
|
||||
993
faugus/shortcut.py
Executable file
993
faugus/shortcut.py
Executable file
@@ -0,0 +1,993 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import gi
|
||||
import sys
|
||||
import gettext
|
||||
import shutil
|
||||
import subprocess
|
||||
import re
|
||||
import webbrowser
|
||||
import locale
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
gi.require_version('Gdk', '3.0')
|
||||
|
||||
from gi.repository import Gtk, Gdk, GdkPixbuf, GLib
|
||||
from PIL import Image
|
||||
from faugus.path_manager import *
|
||||
from faugus.dark_theme import *
|
||||
|
||||
IS_FLATPAK = 'FLATPAK_ID' in os.environ or os.path.exists('/.flatpak-info')
|
||||
if IS_FLATPAK:
|
||||
app_dir = str(Path.home() / '.local/share/applications')
|
||||
faugus_png = PathManager.get_icon('io.github.Faugus.faugus-launcher.png')
|
||||
lsfgvk_path = Path("/usr/lib/extensions/vulkan/lsfgvk/lib/liblsfg-vk.so")
|
||||
lsfgvk_path = lsfgvk_path if lsfgvk_path.exists() else Path(os.path.expanduser('~/.local/lib/liblsfg-vk.so'))
|
||||
else:
|
||||
app_dir = PathManager.user_data('applications')
|
||||
faugus_png = PathManager.get_icon('faugus-launcher.png')
|
||||
lsfgvk_possible_paths = [
|
||||
Path("/usr/lib/liblsfg-vk.so"),
|
||||
Path("/usr/lib64/liblsfg-vk.so"),
|
||||
Path(os.path.expanduser('~/.local/lib/liblsfg-vk.so'))
|
||||
]
|
||||
lsfgvk_path = next((p for p in lsfgvk_possible_paths if p.exists()), lsfgvk_possible_paths[-1])
|
||||
icons_dir = PathManager.user_config('faugus-launcher/icons')
|
||||
config_file_dir = PathManager.user_config('faugus-launcher/config.ini')
|
||||
prefixes_dir = str(Path.home() / 'Faugus')
|
||||
mangohud_dir = PathManager.find_binary('mangohud')
|
||||
gamemoderun = PathManager.find_binary('gamemoderun')
|
||||
umu_run = PathManager.user_data('faugus-launcher/umu-run')
|
||||
faugus_run = PathManager.find_binary('faugus-run')
|
||||
faugus_launcher_dir = PathManager.user_config('faugus-launcher')
|
||||
faugus_notification = PathManager.system_data('faugus-launcher/faugus-notification.ogg')
|
||||
|
||||
def get_desktop_dir():
|
||||
try:
|
||||
desktop_dir = subprocess.check_output(['xdg-user-dir', 'DESKTOP'], text=True).strip()
|
||||
return desktop_dir
|
||||
except (FileNotFoundError, subprocess.CalledProcessError):
|
||||
print("xdg-user-dir not found or failed; falling back to ~/Desktop")
|
||||
return str(Path.home() / 'Desktop')
|
||||
|
||||
desktop_dir = get_desktop_dir()
|
||||
|
||||
def find_lossless_dll():
|
||||
possible_common_locations = [
|
||||
Path.home() / '.local' / 'share' / 'Steam' / 'steamapps' / 'common',
|
||||
Path.home() / '.steam' / 'steam' / 'steamapps' / 'common',
|
||||
Path.home() / '.steam' / 'root' / 'steamapps' / 'common',
|
||||
Path.home() / 'SteamLibrary' / 'steamapps' / 'common',
|
||||
Path(os.path.expanduser('~/.var/app/com.valvesoftware.Steam/.steam/steamapps/common/'))
|
||||
]
|
||||
|
||||
for location in possible_common_locations:
|
||||
dll_candidate = location / 'Lossless Scaling' / 'Lossless.dll'
|
||||
if dll_candidate.exists():
|
||||
return str(dll_candidate)
|
||||
|
||||
return ""
|
||||
|
||||
def get_system_locale():
|
||||
lang = os.environ.get('LANG') or os.environ.get('LC_MESSAGES')
|
||||
if lang:
|
||||
return lang.split('.')[0]
|
||||
|
||||
try:
|
||||
loc = locale.getdefaultlocale()[0]
|
||||
if loc:
|
||||
return loc
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return 'en_US'
|
||||
|
||||
def get_language_from_config():
|
||||
if os.path.exists(config_file_dir):
|
||||
with open(config_file_dir, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith('language='):
|
||||
return line.split('=', 1)[1].strip()
|
||||
return None
|
||||
|
||||
lang = get_language_from_config()
|
||||
if not lang:
|
||||
lang = get_system_locale()
|
||||
|
||||
LOCALE_DIR = (
|
||||
PathManager.system_data('locale')
|
||||
if os.path.isdir(PathManager.system_data('locale'))
|
||||
else os.path.join(os.path.dirname(__file__), 'locale')
|
||||
)
|
||||
|
||||
try:
|
||||
translation = gettext.translation(
|
||||
'faugus-launcher',
|
||||
localedir=LOCALE_DIR,
|
||||
languages=[lang] if lang else ['en_US']
|
||||
)
|
||||
translation.install()
|
||||
globals()['_'] = translation.gettext
|
||||
except FileNotFoundError:
|
||||
gettext.install('faugus-launcher', localedir=LOCALE_DIR)
|
||||
globals()['_'] = gettext.gettext
|
||||
|
||||
def format_title(title):
|
||||
title = title.strip().lower()
|
||||
title = re.sub(r"['’]", "", title)
|
||||
title = re.sub(r"[^a-z0-9]+", "-", title)
|
||||
title = title.strip("-")
|
||||
return title
|
||||
|
||||
class ConfigManager:
|
||||
def __init__(self):
|
||||
self.default_config = {
|
||||
'close-onlaunch': 'False',
|
||||
'default-prefix': prefixes_dir,
|
||||
'mangohud': 'False',
|
||||
'gamemode': 'False',
|
||||
'disable-hidraw': 'False',
|
||||
'default-runner': 'GE-Proton',
|
||||
'lossless-location': '',
|
||||
'discrete-gpu': 'False',
|
||||
'splash-disable': 'False',
|
||||
'system-tray': 'False',
|
||||
'start-boot': 'False',
|
||||
'mono-icon': 'False',
|
||||
'interface-mode': 'List',
|
||||
'start-maximized': 'False',
|
||||
'start-fullscreen': 'False',
|
||||
'show-labels': 'False',
|
||||
'smaller-banners': 'False',
|
||||
'enable-logging': 'False',
|
||||
'wayland-driver': 'False',
|
||||
'enable-hdr': 'False',
|
||||
'enable-wow64': 'False',
|
||||
'language': lang,
|
||||
'logging-warning': 'False',
|
||||
'show-hidden': 'False',
|
||||
}
|
||||
|
||||
self.config = {}
|
||||
self.load_config()
|
||||
|
||||
def load_config(self):
|
||||
if os.path.isfile(config_file_dir):
|
||||
with open(config_file_dir, 'r') as f:
|
||||
for line in f.read().splitlines():
|
||||
if '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
key = key.strip()
|
||||
value = value.strip().strip('"')
|
||||
self.config[key] = value
|
||||
|
||||
updated = False
|
||||
for key, default_value in self.default_config.items():
|
||||
if key not in self.config:
|
||||
self.config[key] = default_value
|
||||
updated = True
|
||||
|
||||
if updated or not os.path.isfile(config_file_dir):
|
||||
self.save_config()
|
||||
|
||||
def save_config(self):
|
||||
if not os.path.exists(faugus_launcher_dir):
|
||||
os.makedirs(faugus_launcher_dir)
|
||||
|
||||
with open(config_file_dir, 'w') as f:
|
||||
for key, value in self.config.items():
|
||||
if key in ['default-prefix', 'default-runner']:
|
||||
f.write(f'{key}="{value}"\n')
|
||||
else:
|
||||
f.write(f'{key}={value}\n')
|
||||
|
||||
def save_with_values(self, *args):
|
||||
keys = list(self.default_config.keys())
|
||||
for key, value in zip(keys, args):
|
||||
self.config[key] = str(value)
|
||||
self.save_config()
|
||||
|
||||
class CreateShortcut(Gtk.Window):
|
||||
def __init__(self, file_path):
|
||||
super().__init__(title="Faugus Launcher")
|
||||
self.file_path = file_path
|
||||
self.set_resizable(False)
|
||||
self.set_icon_from_file(faugus_png)
|
||||
|
||||
game_title = os.path.basename(file_path)
|
||||
self.set_title(game_title)
|
||||
print(self.file_path)
|
||||
|
||||
self.icon_directory = f"{icons_dir}/icon_temp/"
|
||||
|
||||
if not os.path.exists(self.icon_directory):
|
||||
os.makedirs(self.icon_directory)
|
||||
|
||||
self.icons_path = icons_dir
|
||||
self.icon_extracted = os.path.expanduser(f'{self.icons_path}/icon_temp/icon.ico')
|
||||
self.icon_converted = os.path.expanduser(f'{self.icons_path}/icon_temp/icon.png')
|
||||
self.icon_temp = f'{self.icons_path}/icon_temp.ico'
|
||||
|
||||
self.default_prefix = ""
|
||||
|
||||
self.lossless_enabled = False
|
||||
self.lossless_multiplier = 1
|
||||
self.lossless_flow = 100
|
||||
self.lossless_performance = False
|
||||
self.lossless_hdr = False
|
||||
|
||||
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_lossless = Gtk.Button(label=_("Lossless Scaling Frame Generation"))
|
||||
self.button_lossless.connect("clicked", self.on_button_lossless_clicked)
|
||||
|
||||
self.label_addapp = Gtk.Label(label=_("Additional Application"))
|
||||
self.label_addapp.set_halign(Gtk.Align.START)
|
||||
self.entry_addapp = Gtk.Entry()
|
||||
self.entry_addapp.set_tooltip_text(_("/path/to/the/app"))
|
||||
self.button_search_addapp = Gtk.Button()
|
||||
self.button_search_addapp.set_image(Gtk.Image.new_from_icon_name("system-search-symbolic", Gtk.IconSize.BUTTON))
|
||||
self.button_search_addapp.connect("clicked", self.on_button_search_addapp_clicked)
|
||||
self.button_search_addapp.set_size_request(50, -1)
|
||||
|
||||
self.button_shortcut_icon = Gtk.Button()
|
||||
self.button_shortcut_icon.set_size_request(120, -1)
|
||||
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_disable_hidraw = Gtk.CheckButton(label=_("Disable Hidraw"))
|
||||
self.checkbox_disable_hidraw.set_tooltip_text(
|
||||
_("May fix controller issues with some games. Only works with GE-Proton10 or Proton-EM-10."))
|
||||
|
||||
# Button Cancel
|
||||
self.button_cancel = Gtk.Button(label=_("Cancel"))
|
||||
self.button_cancel.connect("clicked", self.on_cancel_clicked)
|
||||
self.button_cancel.set_size_request(150, -1)
|
||||
|
||||
# Button Ok
|
||||
self.button_ok = Gtk.Button(label=_("Ok"))
|
||||
self.button_ok.connect("clicked", self.on_ok_clicked)
|
||||
self.button_ok.set_size_request(150, -1)
|
||||
|
||||
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)
|
||||
|
||||
self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
||||
self.box.set_margin_start(0)
|
||||
self.box.set_margin_end(0)
|
||||
self.box.set_margin_top(0)
|
||||
self.box.set_margin_bottom(0)
|
||||
|
||||
frame = Gtk.Frame()
|
||||
frame.set_margin_start(10)
|
||||
frame.set_margin_end(10)
|
||||
frame.set_margin_top(10)
|
||||
frame.set_margin_bottom(10)
|
||||
|
||||
self.grid_title = Gtk.Grid()
|
||||
self.grid_title.set_row_spacing(10)
|
||||
self.grid_title.set_column_spacing(10)
|
||||
self.grid_title.set_margin_start(10)
|
||||
self.grid_title.set_margin_end(10)
|
||||
self.grid_title.set_margin_top(10)
|
||||
|
||||
self.grid_protonfix = Gtk.Grid()
|
||||
self.grid_protonfix.set_row_spacing(10)
|
||||
self.grid_protonfix.set_column_spacing(10)
|
||||
self.grid_protonfix.set_margin_start(10)
|
||||
self.grid_protonfix.set_margin_end(10)
|
||||
self.grid_protonfix.set_margin_top(10)
|
||||
|
||||
self.grid_launch_arguments = Gtk.Grid()
|
||||
self.grid_launch_arguments.set_row_spacing(10)
|
||||
self.grid_launch_arguments.set_column_spacing(10)
|
||||
self.grid_launch_arguments.set_margin_start(10)
|
||||
self.grid_launch_arguments.set_margin_end(10)
|
||||
self.grid_launch_arguments.set_margin_top(10)
|
||||
|
||||
self.grid_game_arguments = Gtk.Grid()
|
||||
self.grid_game_arguments.set_row_spacing(10)
|
||||
self.grid_game_arguments.set_column_spacing(10)
|
||||
self.grid_game_arguments.set_margin_start(10)
|
||||
self.grid_game_arguments.set_margin_end(10)
|
||||
self.grid_game_arguments.set_margin_top(10)
|
||||
|
||||
self.grid_lossless = Gtk.Grid()
|
||||
self.grid_lossless.set_row_spacing(10)
|
||||
self.grid_lossless.set_column_spacing(10)
|
||||
self.grid_lossless.set_margin_start(10)
|
||||
self.grid_lossless.set_margin_end(10)
|
||||
self.grid_lossless.set_margin_top(10)
|
||||
|
||||
self.grid_addapp = Gtk.Grid()
|
||||
self.grid_addapp.set_row_spacing(10)
|
||||
self.grid_addapp.set_column_spacing(10)
|
||||
self.grid_addapp.set_margin_start(10)
|
||||
self.grid_addapp.set_margin_end(10)
|
||||
self.grid_addapp.set_margin_top(10)
|
||||
|
||||
self.grid_title.attach(self.label_title, 0, 0, 4, 1)
|
||||
self.grid_title.attach(self.entry_title, 0, 1, 4, 1)
|
||||
self.entry_title.set_hexpand(True)
|
||||
|
||||
self.grid_protonfix.attach(self.label_protonfix, 0, 0, 1, 1)
|
||||
self.grid_protonfix.attach(self.entry_protonfix, 0, 1, 3, 1)
|
||||
self.entry_protonfix.set_hexpand(True)
|
||||
self.grid_protonfix.attach(self.button_search_protonfix, 3, 1, 1, 1)
|
||||
|
||||
self.grid_launch_arguments.attach(self.label_launch_arguments, 0, 0, 4, 1)
|
||||
self.grid_launch_arguments.attach(self.entry_launch_arguments, 0, 1, 4, 1)
|
||||
self.entry_launch_arguments.set_hexpand(True)
|
||||
|
||||
self.grid_game_arguments.attach(self.label_game_arguments, 0, 0, 4, 1)
|
||||
self.grid_game_arguments.attach(self.entry_game_arguments, 0, 1, 4, 1)
|
||||
self.entry_game_arguments.set_hexpand(True)
|
||||
|
||||
self.grid_addapp.attach(self.label_addapp, 0, 0, 1, 1)
|
||||
self.grid_addapp.attach(self.entry_addapp, 0, 1, 3, 1)
|
||||
self.entry_addapp.set_hexpand(True)
|
||||
self.grid_addapp.attach(self.button_search_addapp, 3, 1, 1, 1)
|
||||
|
||||
self.grid_lossless.attach(self.button_lossless, 0, 0, 1, 1)
|
||||
self.button_lossless.set_hexpand(True)
|
||||
|
||||
self.grid_tools = Gtk.Grid(orientation=Gtk.Orientation.VERTICAL)
|
||||
self.grid_tools.set_row_spacing(10)
|
||||
self.grid_tools.set_column_spacing(10)
|
||||
self.grid_tools.set_margin_start(10)
|
||||
self.grid_tools.set_margin_end(10)
|
||||
self.grid_tools.set_margin_top(10)
|
||||
self.grid_tools.set_margin_bottom(10)
|
||||
|
||||
self.grid_shortcut_icon = Gtk.Grid(orientation=Gtk.Orientation.VERTICAL)
|
||||
self.grid_shortcut_icon.set_row_spacing(10)
|
||||
self.grid_shortcut_icon.set_column_spacing(10)
|
||||
self.grid_shortcut_icon.set_margin_start(10)
|
||||
self.grid_shortcut_icon.set_margin_end(10)
|
||||
self.grid_shortcut_icon.set_margin_top(10)
|
||||
self.grid_shortcut_icon.set_margin_bottom(10)
|
||||
|
||||
self.grid_tools.add(self.checkbox_mangohud)
|
||||
self.grid_tools.add(self.checkbox_gamemode)
|
||||
self.grid_tools.add(self.checkbox_disable_hidraw)
|
||||
|
||||
self.grid_shortcut_icon.add(self.button_shortcut_icon)
|
||||
self.grid_shortcut_icon.set_valign(Gtk.Align.CENTER)
|
||||
|
||||
self.box_tools = Gtk.Box()
|
||||
self.box_tools.pack_start(self.grid_tools, False, False, 0)
|
||||
self.box_tools.pack_end(self.grid_shortcut_icon, False, False, 0)
|
||||
|
||||
bottom_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
||||
bottom_box.set_margin_start(10)
|
||||
bottom_box.set_margin_end(10)
|
||||
# botton_box.set_margin_top(10)
|
||||
bottom_box.set_margin_bottom(10)
|
||||
|
||||
self.button_cancel.set_hexpand(True)
|
||||
self.button_ok.set_hexpand(True)
|
||||
|
||||
bottom_box.pack_start(self.button_cancel, True, True, 0)
|
||||
bottom_box.pack_start(self.button_ok, True, True, 0)
|
||||
|
||||
self.main_grid = Gtk.Grid(orientation=Gtk.Orientation.VERTICAL)
|
||||
self.main_grid.add(self.grid_title)
|
||||
self.main_grid.add(self.grid_protonfix)
|
||||
self.main_grid.add(self.grid_launch_arguments)
|
||||
self.main_grid.add(self.grid_game_arguments)
|
||||
self.main_grid.add(self.grid_addapp)
|
||||
self.main_grid.add(self.grid_lossless)
|
||||
self.main_grid.add(self.box_tools)
|
||||
|
||||
self.load_config()
|
||||
|
||||
self.mangohud_enabled = os.path.exists(mangohud_dir)
|
||||
if not self.mangohud_enabled:
|
||||
self.checkbox_mangohud.set_sensitive(False)
|
||||
self.checkbox_mangohud.set_active(False)
|
||||
self.checkbox_mangohud.set_tooltip_text(
|
||||
_("Shows an overlay for monitoring FPS, temperatures, CPU/GPU load and more. NOT INSTALLED."))
|
||||
|
||||
self.gamemode_enabled = os.path.exists(gamemoderun) or os.path.exists("/usr/games/gamemoderun")
|
||||
if not self.gamemode_enabled:
|
||||
self.checkbox_gamemode.set_sensitive(False)
|
||||
self.checkbox_gamemode.set_active(False)
|
||||
self.checkbox_gamemode.set_tooltip_text(_("Tweaks your system to improve performance. NOT INSTALLED."))
|
||||
|
||||
lossless_dll_path = find_lossless_dll()
|
||||
if os.path.exists(lsfgvk_path):
|
||||
if lossless_dll_path or os.path.exists(self.lossless_location):
|
||||
self.button_lossless.set_sensitive(True)
|
||||
else:
|
||||
self.button_lossless.set_sensitive(False)
|
||||
self.button_lossless.set_tooltip_text(_("Lossless.dll NOT FOUND. If it's installed, go to Faugus Launcher's settings and set the location."))
|
||||
else:
|
||||
self.button_lossless.set_sensitive(False)
|
||||
self.button_lossless.set_tooltip_text(_("Lossless Scaling Vulkan Layer NOT INSTALLED."))
|
||||
|
||||
frame.add(self.main_grid)
|
||||
self.box.add(frame)
|
||||
self.box.add(bottom_box)
|
||||
self.add(self.box)
|
||||
|
||||
if not os.path.exists(self.icon_directory):
|
||||
os.makedirs(self.icon_directory)
|
||||
|
||||
try:
|
||||
# Attempt to extract the icon
|
||||
command = f'icoextract "{file_path}" "{self.icon_extracted}"'
|
||||
result = subprocess.run(command, shell=True, text=True, capture_output=True)
|
||||
|
||||
# Check if there was an error in executing the command
|
||||
if result.returncode != 0:
|
||||
if "NoIconsAvailableError" in result.stderr or "PEFormatError" in result.stderr:
|
||||
print("The file does not contain icons.")
|
||||
self.button_shortcut_icon.set_image(self.set_image_shortcut_icon())
|
||||
else:
|
||||
print(f"Error extracting icon: {result.stderr}")
|
||||
else:
|
||||
# Convert the extracted icon to PNG
|
||||
command_magick = shutil.which("magick") or shutil.which("convert")
|
||||
os.system(f'{command_magick} "{self.icon_extracted}" "{self.icon_converted}"')
|
||||
if os.path.isfile(self.icon_extracted):
|
||||
os.remove(self.icon_extracted)
|
||||
|
||||
largest_image = self.find_largest_resolution(self.icon_directory)
|
||||
shutil.move(largest_image, os.path.expanduser(self.icon_temp))
|
||||
|
||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file(self.icon_temp)
|
||||
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
||||
image = Gtk.Image.new_from_file(self.icon_temp)
|
||||
image.set_from_pixbuf(scaled_pixbuf)
|
||||
|
||||
self.button_shortcut_icon.set_image(image)
|
||||
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
|
||||
shutil.rmtree(self.icon_directory)
|
||||
|
||||
# Connect the destroy signal to Gtk.main_quit
|
||||
self.connect("destroy", Gtk.main_quit)
|
||||
|
||||
def on_button_lossless_clicked(self, widget):
|
||||
dialog = Gtk.Dialog(title=_("Lossless Scaling Frame Generation"), parent=self, flags=0)
|
||||
dialog.set_resizable(False)
|
||||
dialog.set_icon_from_file(faugus_png)
|
||||
|
||||
frame = Gtk.Frame()
|
||||
frame.set_margin_start(10)
|
||||
frame.set_margin_end(10)
|
||||
frame.set_margin_top(10)
|
||||
frame.set_margin_bottom(10)
|
||||
|
||||
grid = Gtk.Grid()
|
||||
grid.set_row_spacing(10)
|
||||
grid.set_column_spacing(10)
|
||||
grid.set_margin_top(10)
|
||||
grid.set_margin_bottom(10)
|
||||
grid.set_margin_start(10)
|
||||
grid.set_margin_end(10)
|
||||
|
||||
enabled = val if (val := getattr(self, "lossless_enabled", False)) != "" else False
|
||||
multiplier = val if (val := getattr(self, "lossless_multiplier", 1)) != "" else 1
|
||||
flow = val if (val := getattr(self, "lossless_flow", 100)) != "" else 100
|
||||
performance = val if (val := getattr(self, "lossless_performance", False)) != "" else False
|
||||
hdr = val if (val := getattr(self, "lossless_hdr", False)) != "" else False
|
||||
|
||||
checkbox_enable = Gtk.CheckButton(label="Enable")
|
||||
checkbox_enable.set_active(enabled)
|
||||
checkbox_enable.set_halign(Gtk.Align.START)
|
||||
|
||||
label_multiplier = Gtk.Label(label=_("Multiplier"))
|
||||
label_multiplier.set_halign(Gtk.Align.START)
|
||||
spin_multiplier = Gtk.SpinButton()
|
||||
spin_multiplier.set_adjustment(Gtk.Adjustment(value=multiplier, lower=1, upper=20, step_increment=1))
|
||||
spin_multiplier.set_numeric(True)
|
||||
spin_multiplier.set_tooltip_text(_("Multiply the FPS."))
|
||||
|
||||
label_flow = Gtk.Label(label=_("Flow Scale"))
|
||||
label_flow.set_halign(Gtk.Align.START)
|
||||
adjustment = Gtk.Adjustment(value=flow, lower=25, upper=100, step_increment=1)
|
||||
scale_flow = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL, adjustment=adjustment)
|
||||
scale_flow.set_digits(0)
|
||||
scale_flow.set_hexpand(True)
|
||||
scale_flow.set_value_pos(Gtk.PositionType.RIGHT)
|
||||
scale_flow.set_tooltip_text(_("Lower the internal motion estimation resolution."))
|
||||
|
||||
checkbox_performance = Gtk.CheckButton(label=_("Performance Mode"))
|
||||
checkbox_performance.set_tooltip_text(_("Massively improve performance at the cost of quality."))
|
||||
checkbox_performance.set_active(performance)
|
||||
|
||||
checkbox_hdr = Gtk.CheckButton(label=_("HDR Mode"))
|
||||
checkbox_hdr.set_tooltip_text(_("Enable special HDR-only behavior."))
|
||||
checkbox_hdr.set_active(hdr)
|
||||
|
||||
def on_enable_toggled(cb):
|
||||
active = cb.get_active()
|
||||
label_multiplier.set_sensitive(active)
|
||||
spin_multiplier.set_sensitive(active)
|
||||
label_flow.set_sensitive(active)
|
||||
scale_flow.set_sensitive(active)
|
||||
checkbox_performance.set_sensitive(active)
|
||||
checkbox_hdr.set_sensitive(active)
|
||||
|
||||
checkbox_enable.connect("toggled", on_enable_toggled)
|
||||
on_enable_toggled(checkbox_enable)
|
||||
|
||||
grid.attach(checkbox_enable, 0, 0, 1, 1)
|
||||
grid.attach(label_multiplier, 0, 1, 1, 1)
|
||||
grid.attach(spin_multiplier, 0, 2, 1, 1)
|
||||
grid.attach(label_flow, 0, 3, 1, 1)
|
||||
grid.attach(scale_flow, 0, 4, 1, 1)
|
||||
grid.attach(checkbox_performance, 0, 5, 1, 1)
|
||||
grid.attach(checkbox_hdr, 0, 6, 1, 1)
|
||||
|
||||
frame.add(grid)
|
||||
|
||||
button_cancel = Gtk.Button(label=_("Cancel"))
|
||||
button_cancel.set_size_request(150, -1)
|
||||
button_cancel.set_hexpand(True)
|
||||
button_cancel.connect("clicked", lambda b: dialog.response(Gtk.ResponseType.CANCEL))
|
||||
|
||||
button_ok = Gtk.Button(label=_("Ok"))
|
||||
button_ok.set_size_request(150, -1)
|
||||
button_ok.set_hexpand(True)
|
||||
button_ok.connect("clicked", lambda b: dialog.response(Gtk.ResponseType.OK))
|
||||
|
||||
bottom_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
||||
bottom_box.set_margin_start(10)
|
||||
bottom_box.set_margin_end(10)
|
||||
bottom_box.set_margin_bottom(10)
|
||||
bottom_box.pack_start(button_cancel, True, True, 0)
|
||||
bottom_box.pack_start(button_ok, True, True, 0)
|
||||
|
||||
content_area = dialog.get_content_area()
|
||||
content_area.pack_start(frame, True, True, 0)
|
||||
content_area.pack_start(bottom_box, False, False, 0)
|
||||
|
||||
dialog.show_all()
|
||||
response = dialog.run()
|
||||
|
||||
if response == Gtk.ResponseType.OK:
|
||||
self.lossless_enabled = checkbox_enable.get_active()
|
||||
self.lossless_multiplier = spin_multiplier.get_value_as_int()
|
||||
self.lossless_flow = scale_flow.get_value()
|
||||
self.lossless_performance = checkbox_performance.get_active()
|
||||
self.lossless_hdr = checkbox_hdr.get_active()
|
||||
|
||||
dialog.destroy()
|
||||
return response
|
||||
|
||||
def on_button_search_addapp_clicked(self, widget):
|
||||
filechooser = Gtk.FileChooserNative(
|
||||
title=_("Select an additional application"),
|
||||
transient_for=self,
|
||||
action=Gtk.FileChooserAction.OPEN,
|
||||
accept_label=_("Open"),
|
||||
cancel_label=_("Cancel"),
|
||||
)
|
||||
|
||||
filechooser.set_current_folder(os.path.expanduser("~/"))
|
||||
|
||||
windows_filter = Gtk.FileFilter()
|
||||
windows_filter.set_name(_("Windows files"))
|
||||
windows_filter.add_pattern("*.exe")
|
||||
windows_filter.add_pattern("*.msi")
|
||||
windows_filter.add_pattern("*.bat")
|
||||
windows_filter.add_pattern("*.lnk")
|
||||
windows_filter.add_pattern("*.reg")
|
||||
|
||||
all_files_filter = Gtk.FileFilter()
|
||||
all_files_filter.set_name(_("All files"))
|
||||
all_files_filter.add_pattern("*")
|
||||
|
||||
filechooser.add_filter(windows_filter)
|
||||
filechooser.add_filter(all_files_filter)
|
||||
filechooser.set_filter(windows_filter)
|
||||
|
||||
response = filechooser.run()
|
||||
|
||||
if response == Gtk.ResponseType.ACCEPT:
|
||||
self.entry_addapp.set_text(filechooser.get_filename())
|
||||
|
||||
filechooser.destroy()
|
||||
|
||||
def find_largest_resolution(self, directory):
|
||||
largest_image = None
|
||||
largest_resolution = (0, 0) # (width, height)
|
||||
|
||||
# Define a set of valid image extensions
|
||||
valid_image_extensions = {'.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff'}
|
||||
|
||||
for file_name in os.listdir(directory):
|
||||
file_path = os.path.join(directory, file_name)
|
||||
if os.path.isfile(file_path):
|
||||
# Check if the file has a valid image extension
|
||||
if os.path.splitext(file_name)[1].lower() in valid_image_extensions:
|
||||
try:
|
||||
with Image.open(file_path) as img:
|
||||
width, height = img.size
|
||||
if width * height > largest_resolution[0] * largest_resolution[1]:
|
||||
largest_resolution = (width, height)
|
||||
largest_image = file_path
|
||||
except IOError:
|
||||
print(f"Unable to open {file_path}")
|
||||
|
||||
return largest_image
|
||||
|
||||
def on_button_search_protonfix_clicked(self, widget):
|
||||
webbrowser.open("https://umu.openwinecomponents.org/")
|
||||
|
||||
def load_config(self):
|
||||
cfg = ConfigManager()
|
||||
|
||||
self.default_prefix = cfg.config.get('default-prefix', '').strip('"')
|
||||
mangohud = cfg.config.get('mangohud', 'False') == 'True'
|
||||
gamemode = cfg.config.get('gamemode', 'False') == 'True'
|
||||
disable_hidraw = cfg.config.get('disable-hidraw', 'False') == 'True'
|
||||
self.default_runner = cfg.config.get('default-runner', '').strip('"')
|
||||
self.lossless_location = cfg.config.get('lossless-location', '')
|
||||
|
||||
self.checkbox_mangohud.set_active(mangohud)
|
||||
self.checkbox_gamemode.set_active(gamemode)
|
||||
self.checkbox_disable_hidraw.set_active(disable_hidraw)
|
||||
|
||||
def on_cancel_clicked(self, widget):
|
||||
if os.path.isfile(self.icon_temp):
|
||||
os.remove(self.icon_temp)
|
||||
if os.path.isdir(self.icon_directory):
|
||||
shutil.rmtree(self.icon_directory)
|
||||
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()
|
||||
title_formatted = format_title(title)
|
||||
|
||||
addapp = self.entry_addapp.get_text()
|
||||
addapp_bat = f"{os.path.dirname(self.file_path)}/faugus-{title_formatted}.bat"
|
||||
|
||||
if self.entry_addapp.get_text():
|
||||
with open(addapp_bat, "w") as bat_file:
|
||||
bat_file.write(f'start "" "z:{addapp}"\n')
|
||||
bat_file.write(f'start "" "z:{self.file_path}"\n')
|
||||
|
||||
if os.path.isfile(os.path.expanduser(self.icon_temp)):
|
||||
os.rename(os.path.expanduser(self.icon_temp), f'{self.icons_path}/{title_formatted}.ico')
|
||||
|
||||
# Check if the icon file exists
|
||||
new_icon_path = f"{icons_dir}/{title_formatted}.ico"
|
||||
if not os.path.exists(new_icon_path):
|
||||
new_icon_path = faugus_png
|
||||
|
||||
protonfix = self.entry_protonfix.get_text()
|
||||
launch_arguments = self.entry_launch_arguments.get_text()
|
||||
game_arguments = self.entry_game_arguments.get_text()
|
||||
lossless_enabled = self.lossless_enabled
|
||||
lossless_multiplier = self.lossless_multiplier
|
||||
lossless_flow = self.lossless_flow
|
||||
lossless_performance = self.lossless_performance
|
||||
lossless_hdr = self.lossless_hdr
|
||||
|
||||
mangohud = "MANGOHUD=1" if self.checkbox_mangohud.get_active() else ""
|
||||
gamemode = "gamemoderun" if self.checkbox_gamemode.get_active() else ""
|
||||
disable_hidraw = "PROTON_DISABLE_HIDRAW=1" if self.checkbox_disable_hidraw.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 disable_hidraw:
|
||||
command_parts.append(disable_hidraw)
|
||||
|
||||
# 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)
|
||||
if lossless_enabled:
|
||||
command_parts.append("LSFG_LEGACY=1")
|
||||
if lossless_multiplier:
|
||||
command_parts.append(f"LSFG_MULTIPLIER={lossless_multiplier}")
|
||||
if lossless_flow:
|
||||
command_parts.append(f"LSFG_FLOW_SCALE={lossless_flow/100}")
|
||||
if lossless_performance:
|
||||
command_parts.append(f"LSFG_PERFORMANCE_MODE={1 if lossless_performance == 'true' else 0}")
|
||||
if lossless_hdr:
|
||||
command_parts.append(f"LSFG_HDR_MODE={1 if lossless_hdr == 'true' else 0}")
|
||||
|
||||
# Add the fixed command and remaining arguments
|
||||
command_parts.append(f"'{umu_run}'")
|
||||
if self.entry_addapp.get_text():
|
||||
command_parts.append(f"'{addapp_bat}'")
|
||||
elif 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
|
||||
if IS_FLATPAK:
|
||||
desktop_file_content = (
|
||||
f'[Desktop Entry]\n'
|
||||
f'Name={title}\n'
|
||||
f'Exec=flatpak run --command={faugus_run} io.github.Faugus.faugus-launcher "{command}"\n'
|
||||
f'Icon={new_icon_path}\n'
|
||||
f'Type=Application\n'
|
||||
f'Categories=Game;\n'
|
||||
f'Path={game_directory}\n'
|
||||
)
|
||||
else:
|
||||
desktop_file_content = (
|
||||
f'[Desktop Entry]\n'
|
||||
f'Name={title}\n'
|
||||
f'Exec={faugus_run} "{command}"\n'
|
||||
f'Icon={new_icon_path}\n'
|
||||
f'Type=Application\n'
|
||||
f'Categories=Game;\n'
|
||||
f'Path={game_directory}\n'
|
||||
)
|
||||
|
||||
# Check if the destination directory exists and create if it doesn't
|
||||
applications_directory = app_dir
|
||||
if not os.path.exists(applications_directory):
|
||||
os.makedirs(applications_directory)
|
||||
|
||||
desktop_directory = desktop_dir
|
||||
if not os.path.exists(desktop_directory):
|
||||
os.makedirs(desktop_directory)
|
||||
|
||||
applications_shortcut_path = f"{app_dir}/{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 = f"{desktop_dir}/{title_formatted}.desktop"
|
||||
shutil.copyfile(applications_shortcut_path, desktop_shortcut_path)
|
||||
os.chmod(desktop_shortcut_path, 0o755)
|
||||
|
||||
if os.path.isfile(self.icon_temp):
|
||||
os.remove(self.icon_temp)
|
||||
if os.path.isdir(self.icon_directory):
|
||||
shutil.rmtree(self.icon_directory)
|
||||
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 = faugus_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)
|
||||
|
||||
path = self.file_path
|
||||
|
||||
if not os.path.exists(self.icon_directory):
|
||||
os.makedirs(self.icon_directory)
|
||||
|
||||
try:
|
||||
command = f'icoextract "{path}" "{self.icon_extracted}"'
|
||||
result = subprocess.run(command, shell=True, text=True, capture_output=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
if "NoIconsAvailableError" in result.stderr or "PEFormatError" in result.stderr:
|
||||
print("The file does not contain icons.")
|
||||
self.button_shortcut_icon.set_image(self.set_image_shortcut_icon())
|
||||
else:
|
||||
print(f"Error extracting icon: {result.stderr}")
|
||||
else:
|
||||
command_magick = shutil.which("magick") or shutil.which("convert")
|
||||
os.system(f'{command_magick} "{self.icon_extracted}" "{self.icon_converted}"')
|
||||
if os.path.isfile(self.icon_extracted):
|
||||
os.remove(self.icon_extracted)
|
||||
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
|
||||
def is_valid_image(file_path):
|
||||
try:
|
||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file(file_path)
|
||||
return pixbuf is not None
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
filechooser = Gtk.FileChooserNative.new(
|
||||
_("Select an icon for the shortcut"),
|
||||
self,
|
||||
Gtk.FileChooserAction.OPEN,
|
||||
_("Open"),
|
||||
_("Cancel")
|
||||
)
|
||||
|
||||
filter_ico = Gtk.FileFilter()
|
||||
filter_ico.set_name(_("Image files"))
|
||||
filter_ico.add_pattern("*.png")
|
||||
filter_ico.add_pattern("*.jpg")
|
||||
filter_ico.add_pattern("*.jpeg")
|
||||
filter_ico.add_pattern("*.bmp")
|
||||
filter_ico.add_pattern("*.gif")
|
||||
filter_ico.add_pattern("*.svg")
|
||||
filter_ico.add_pattern("*.ico")
|
||||
filechooser.add_filter(filter_ico)
|
||||
|
||||
filechooser.set_current_folder(self.icon_directory)
|
||||
|
||||
response = filechooser.run()
|
||||
if response == Gtk.ResponseType.ACCEPT:
|
||||
file_path = filechooser.get_filename()
|
||||
if not file_path or not is_valid_image(file_path):
|
||||
dialog_image = Gtk.Dialog(title="Faugus Launcher", transient_for=self, modal=True)
|
||||
dialog_image.set_resizable(False)
|
||||
dialog_image.set_icon_from_file(faugus_png)
|
||||
subprocess.Popen(["canberra-gtk-play", "-f", faugus_notification])
|
||||
|
||||
label = Gtk.Label()
|
||||
label.set_label(_("The selected file is not a valid image."))
|
||||
label.set_halign(Gtk.Align.CENTER)
|
||||
|
||||
label2 = Gtk.Label()
|
||||
label2.set_label(_("Please choose another one."))
|
||||
label2.set_halign(Gtk.Align.CENTER)
|
||||
|
||||
button_yes = Gtk.Button(label=_("Ok"))
|
||||
button_yes.set_size_request(150, -1)
|
||||
button_yes.connect("clicked", lambda x: dialog_image.response(Gtk.ResponseType.YES))
|
||||
|
||||
content_area = dialog_image.get_content_area()
|
||||
content_area.set_border_width(0)
|
||||
content_area.set_halign(Gtk.Align.CENTER)
|
||||
content_area.set_valign(Gtk.Align.CENTER)
|
||||
content_area.set_vexpand(True)
|
||||
content_area.set_hexpand(True)
|
||||
|
||||
box_top = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
|
||||
box_top.set_margin_start(20)
|
||||
box_top.set_margin_end(20)
|
||||
box_top.set_margin_top(20)
|
||||
box_top.set_margin_bottom(20)
|
||||
|
||||
box_bottom = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
|
||||
box_bottom.set_margin_start(10)
|
||||
box_bottom.set_margin_end(10)
|
||||
box_bottom.set_margin_bottom(10)
|
||||
|
||||
box_top.pack_start(label, True, True, 0)
|
||||
box_top.pack_start(label2, True, True, 0)
|
||||
box_bottom.pack_start(button_yes, True, True, 0)
|
||||
|
||||
content_area.add(box_top)
|
||||
content_area.add(box_bottom)
|
||||
|
||||
dialog_image.show_all()
|
||||
dialog_image.run()
|
||||
dialog_image.destroy()
|
||||
else:
|
||||
shutil.copyfile(file_path, self.icon_temp)
|
||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file(self.icon_temp)
|
||||
scaled_pixbuf = pixbuf.scale_simple(50, 50, GdkPixbuf.InterpType.BILINEAR)
|
||||
image = Gtk.Image.new_from_file(self.icon_temp)
|
||||
image.set_from_pixbuf(scaled_pixbuf)
|
||||
self.button_shortcut_icon.set_image(image)
|
||||
|
||||
filechooser.destroy()
|
||||
|
||||
if os.path.isdir(self.icon_directory):
|
||||
shutil.rmtree(self.icon_directory)
|
||||
self.set_sensitive(True)
|
||||
|
||||
def 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 main():
|
||||
os.environ["GTK_USE_PORTAL"] = "1"
|
||||
apply_dark_theme()
|
||||
exec_path = sys.argv[1]
|
||||
|
||||
win = CreateShortcut(exec_path)
|
||||
win.connect("destroy", Gtk.main_quit)
|
||||
win.show_all()
|
||||
Gtk.main()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,47 +2,15 @@
|
||||
|
||||
import requests
|
||||
import gi
|
||||
import os
|
||||
import tarfile
|
||||
import shutil
|
||||
import gettext
|
||||
import locale
|
||||
from pathlib import Path
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
|
||||
from gi.repository import Gtk, Gio, GLib
|
||||
|
||||
class PathManager:
|
||||
@staticmethod
|
||||
def system_data(*relative_paths):
|
||||
xdg_data_dirs = os.getenv('XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(':')
|
||||
for data_dir in xdg_data_dirs:
|
||||
path = Path(data_dir).joinpath(*relative_paths)
|
||||
if path.exists():
|
||||
return str(path)
|
||||
return str(Path(xdg_data_dirs[0]).joinpath(*relative_paths))
|
||||
|
||||
@staticmethod
|
||||
def user_data(*relative_paths):
|
||||
xdg_data_home = Path(os.getenv('XDG_DATA_HOME', Path.home() / '.local/share'))
|
||||
return str(xdg_data_home.joinpath(*relative_paths))
|
||||
|
||||
@staticmethod
|
||||
def user_config(*relative_paths):
|
||||
xdg_config_home = Path(os.getenv('XDG_CONFIG_HOME', Path.home() / '.config'))
|
||||
return str(xdg_config_home.joinpath(*relative_paths))
|
||||
|
||||
@staticmethod
|
||||
def get_icon(icon_name):
|
||||
icon_paths = [
|
||||
PathManager.user_data('icons', icon_name),
|
||||
PathManager.system_data('icons/hicolor/256x256/apps', icon_name),
|
||||
PathManager.system_data('icons', icon_name)
|
||||
]
|
||||
for path in icon_paths:
|
||||
if Path(path).exists():
|
||||
return path
|
||||
return icon_paths[-1]
|
||||
from faugus.language_config import *
|
||||
from faugus.dark_theme import *
|
||||
|
||||
IS_FLATPAK = 'FLATPAK_ID' in os.environ or os.path.exists('/.flatpak-info')
|
||||
if IS_FLATPAK:
|
||||
@@ -55,33 +23,6 @@ else:
|
||||
config_file_dir = PathManager.user_config('faugus-launcher/config.ini')
|
||||
faugus_launcher_dir = PathManager.user_config('faugus-launcher')
|
||||
|
||||
def get_system_locale():
|
||||
lang = os.environ.get('LANG') or os.environ.get('LC_MESSAGES')
|
||||
if lang:
|
||||
return lang.split('.')[0]
|
||||
|
||||
try:
|
||||
return locale.getdefaultlocale()[0] or 'en_US'
|
||||
except Exception:
|
||||
return 'en_US'
|
||||
|
||||
def get_language_from_config():
|
||||
if os.path.exists(config_file_dir):
|
||||
with open(config_file_dir, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith('language='):
|
||||
return line.split('=', 1)[1].strip()
|
||||
return None
|
||||
|
||||
lang = get_language_from_config() or get_system_locale()
|
||||
|
||||
LOCALE_DIR = (
|
||||
PathManager.system_data('locale')
|
||||
if os.path.isdir(PathManager.system_data('locale'))
|
||||
else os.path.join(os.path.dirname(__file__), 'locale')
|
||||
)
|
||||
|
||||
try:
|
||||
translation = gettext.translation('faugus-proton-manager', localedir=LOCALE_DIR, languages=[lang])
|
||||
translation.install()
|
||||
@@ -444,31 +385,6 @@ class ProtonDownloader(Gtk.Dialog):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def apply_dark_theme():
|
||||
if IS_FLATPAK:
|
||||
if (os.environ.get("XDG_CURRENT_DESKTOP")) == "KDE":
|
||||
Gtk.Settings.get_default().set_property("gtk-theme-name", "Breeze")
|
||||
try:
|
||||
proxy = Gio.DBusProxy.new_sync(
|
||||
Gio.bus_get_sync(Gio.BusType.SESSION, None), 0, None,
|
||||
"org.freedesktop.portal.Desktop",
|
||||
"/org/freedesktop/portal/desktop",
|
||||
"org.freedesktop.portal.Settings", None)
|
||||
is_dark = proxy.call_sync(
|
||||
"Read", GLib.Variant("(ss)", ("org.freedesktop.appearance", "color-scheme")),
|
||||
0, -1, None).unpack()[0] == 1
|
||||
except:
|
||||
is_dark = False
|
||||
Gtk.Settings.get_default().set_property("gtk-application-prefer-dark-theme", is_dark)
|
||||
else:
|
||||
desktop_env = Gio.Settings.new("org.gnome.desktop.interface")
|
||||
try:
|
||||
is_dark_theme = desktop_env.get_string("color-scheme") == "prefer-dark"
|
||||
except Exception:
|
||||
is_dark_theme = "-dark" in desktop_env.get_string("gtk-theme")
|
||||
if is_dark_theme:
|
||||
Gtk.Settings.get_default().set_property("gtk-application-prefer-dark-theme", True)
|
||||
|
||||
def main():
|
||||
apply_dark_theme()
|
||||
win = ProtonDownloader()
|
||||
|
||||
127
faugus_run.py
127
faugus_run.py
@@ -1,74 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import gi
|
||||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk, GLib, GdkPixbuf, Gio
|
||||
from threading import Thread
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import subprocess
|
||||
import argparse
|
||||
import re
|
||||
import os
|
||||
import gettext
|
||||
import locale
|
||||
import json
|
||||
import time
|
||||
import shlex
|
||||
import gettext
|
||||
|
||||
class PathManager:
|
||||
@staticmethod
|
||||
def system_data(*relative_paths):
|
||||
xdg_data_dirs = os.getenv('XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(':')
|
||||
for data_dir in xdg_data_dirs:
|
||||
path = Path(data_dir).joinpath(*relative_paths)
|
||||
if path.exists():
|
||||
return str(path)
|
||||
return str(Path(xdg_data_dirs[0]).joinpath(*relative_paths))
|
||||
gi.require_version("Gtk", "3.0")
|
||||
|
||||
@staticmethod
|
||||
def user_data(*relative_paths):
|
||||
xdg_data_home = Path(os.getenv('XDG_DATA_HOME', Path.home() / '.local/share'))
|
||||
return str(xdg_data_home.joinpath(*relative_paths))
|
||||
|
||||
@staticmethod
|
||||
def user_config(*relative_paths):
|
||||
xdg_config_home = Path(os.getenv('XDG_CONFIG_HOME', Path.home() / '.config'))
|
||||
return str(xdg_config_home.joinpath(*relative_paths))
|
||||
|
||||
@staticmethod
|
||||
def find_binary(binary_name):
|
||||
paths = os.getenv('PATH', '').split(':')
|
||||
for path in paths:
|
||||
binary_path = Path(path) / binary_name
|
||||
if binary_path.exists():
|
||||
return str(binary_path)
|
||||
return f'/usr/bin/{binary_name}'
|
||||
|
||||
@staticmethod
|
||||
def get_icon(icon_name):
|
||||
icon_paths = [
|
||||
PathManager.user_data('icons', icon_name),
|
||||
PathManager.system_data('icons/hicolor/256x256/apps', icon_name),
|
||||
PathManager.system_data('icons', icon_name)
|
||||
]
|
||||
for path in icon_paths:
|
||||
if Path(path).exists():
|
||||
return path
|
||||
return icon_paths[-1]
|
||||
|
||||
@staticmethod
|
||||
def find_library(lib_name):
|
||||
lib_paths = [
|
||||
Path("/usr/lib") / lib_name,
|
||||
Path("/usr/lib32") / lib_name,
|
||||
Path("/usr/lib/x86_64-linux-gnu") / lib_name,
|
||||
Path("/usr/lib64") / lib_name
|
||||
]
|
||||
for path in lib_paths:
|
||||
if path.exists():
|
||||
return str(path)
|
||||
return None
|
||||
from gi.repository import Gtk, GLib, GdkPixbuf, Gio
|
||||
from threading import Thread
|
||||
from faugus.config_manager import *
|
||||
from faugus.dark_theme import *
|
||||
|
||||
IS_FLATPAK = 'FLATPAK_ID' in os.environ or os.path.exists('/.flatpak-info')
|
||||
if IS_FLATPAK:
|
||||
@@ -95,39 +42,6 @@ proton_cachyos = PathManager.system_data('steam/compatibilitytools.d/proton-cach
|
||||
compatibility_dir = os.path.expanduser("~/.local/share/Steam/compatibilitytools.d")
|
||||
os.makedirs(compatibility_dir, exist_ok=True)
|
||||
|
||||
def get_system_locale():
|
||||
lang = os.environ.get('LANG') or os.environ.get('LC_MESSAGES')
|
||||
if lang:
|
||||
return lang.split('.')[0]
|
||||
|
||||
try:
|
||||
loc = locale.getdefaultlocale()[0]
|
||||
if loc:
|
||||
return loc
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return 'en_US'
|
||||
|
||||
def get_language_from_config():
|
||||
if os.path.exists(config_file_dir):
|
||||
with open(config_file_dir, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith('language='):
|
||||
return line.split('=', 1)[1].strip()
|
||||
return None
|
||||
|
||||
lang = get_language_from_config()
|
||||
if not lang:
|
||||
lang = get_system_locale()
|
||||
|
||||
LOCALE_DIR = (
|
||||
PathManager.system_data('locale')
|
||||
if os.path.isdir(PathManager.system_data('locale'))
|
||||
else os.path.join(os.path.dirname(__file__), 'locale')
|
||||
)
|
||||
|
||||
try:
|
||||
translation = gettext.translation(
|
||||
'faugus-run',
|
||||
@@ -383,7 +297,7 @@ class FaugusRun:
|
||||
|
||||
if "Proton-EM" in self.message:
|
||||
self.process = subprocess.Popen(
|
||||
[PathManager.find_binary("bash"), "-c", f"{faugus_proton_downloader}"],
|
||||
[sys.executable, "-m", "faugus.proton_downloader"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
bufsize=8192,
|
||||
@@ -424,7 +338,7 @@ class FaugusRun:
|
||||
|
||||
def execute_final_command(self):
|
||||
if "UMU_NO_PROTON" not in self.message:
|
||||
cmd = f"{faugus_components}; {self.discrete_gpu} {eac_dir} {be_dir} {self.message}"
|
||||
cmd = f"{sys.executable} -m faugus.components; {self.discrete_gpu} {eac_dir} {be_dir} {self.message}"
|
||||
else:
|
||||
cmd = f"{self.discrete_gpu} {eac_dir} {be_dir} {self.message}"
|
||||
|
||||
@@ -754,31 +668,6 @@ def handle_command(message, command=None):
|
||||
process_thread.join()
|
||||
sys.exit(0)
|
||||
|
||||
def apply_dark_theme():
|
||||
if IS_FLATPAK:
|
||||
if (os.environ.get("XDG_CURRENT_DESKTOP")) == "KDE":
|
||||
Gtk.Settings.get_default().set_property("gtk-theme-name", "Breeze")
|
||||
try:
|
||||
proxy = Gio.DBusProxy.new_sync(
|
||||
Gio.bus_get_sync(Gio.BusType.SESSION, None), 0, None,
|
||||
"org.freedesktop.portal.Desktop",
|
||||
"/org/freedesktop/portal/desktop",
|
||||
"org.freedesktop.portal.Settings", None)
|
||||
is_dark = proxy.call_sync(
|
||||
"Read", GLib.Variant("(ss)", ("org.freedesktop.appearance", "color-scheme")),
|
||||
0, -1, None).unpack()[0] == 1
|
||||
except:
|
||||
is_dark = False
|
||||
Gtk.Settings.get_default().set_property("gtk-application-prefer-dark-theme", is_dark)
|
||||
else:
|
||||
desktop_env = Gio.Settings.new("org.gnome.desktop.interface")
|
||||
try:
|
||||
is_dark_theme = desktop_env.get_string("color-scheme") == "prefer-dark"
|
||||
except Exception:
|
||||
is_dark_theme = "-dark" in desktop_env.get_string("gtk-theme")
|
||||
if is_dark_theme:
|
||||
Gtk.Settings.get_default().set_property("gtk-application-prefer-dark-theme", True)
|
||||
|
||||
def build_launch_command(game):
|
||||
gameid = game.get("gameid", "")
|
||||
path = game.get("path", "")
|
||||
|
||||
@@ -13,19 +13,16 @@ gnome = import('gnome')
|
||||
subdir('assets')
|
||||
subdir('languages')
|
||||
subdir('data')
|
||||
subdir('faugus')
|
||||
|
||||
install_data(
|
||||
'faugus_launcher.py',
|
||||
'faugus_run.py',
|
||||
'faugus_proton_manager.py',
|
||||
'faugus_components.py',
|
||||
'faugus_proton_downloader.py',
|
||||
rename: [
|
||||
'faugus-launcher',
|
||||
'faugus-run',
|
||||
'faugus-proton-manager',
|
||||
'faugus-components',
|
||||
'faugus-proton-downloader',
|
||||
],
|
||||
install_mode: 'rwxr-xr-x',
|
||||
install_dir: get_option('bindir'),
|
||||
|
||||
Reference in New Issue
Block a user