RetroDECK/tools/configurator/scripts/class_functions.gd

527 lines
20 KiB
GDScript

class_name ClassFunctions
extends Control
@onready var main_scene = get_tree().root.get_node("Control")
var log_result: Dictionary
var log_parameters: Array
const globals_sh_file_path: String = "/app/libexec/global.sh"
const wrapper_command: String = "/app/tools/retrodeck_function_wrapper.sh"
const config_file_path = "/var/config/retrodeck/retrodeck.cfg"
const json_file_path = "/var/config/retrodeck/retrodeck.json"
var config_folder_path = "/var/config/"
const esde_file_path = "/var/config/ES-DE/settings/es_settings.xml"
var desktop_mode: String = OS.get_environment("XDG_CURRENT_DESKTOP")
var rd_conf: String
var lockfile: String
var rdhome: String
var roms_folder: String
var saves_folder: String
var states_folder: String
var bios_folder: String
var rd_log: String
var rd_log_folder: String
var rd_version: String
var gc_version: String
var sound_effects: bool
var volume_effects: int
var title: String
var quick_resume_status: bool
var update_check: bool
var abxy_state: String
var ask_to_exit_state: String
var border_state: String
var widescreen_state: String
var quick_rewind_state: String
var cheevos_state: String
var cheevos_hardcore_state: String
var cheevos_token_state: String
var font_select: int
var font_tab_size: int = 35
var font_size: int = 20
var locale: String
var button_list: Array = ["button_swap_button", "ask_to_exit_button", "border_button", "widescreen_button", "quick_rewind_button", "reset_retrodeck_button", "reset_all_emulators_button", "cheevos_button", "cheevos_hardcore_button"]
signal update_global_signal
var rekku_state: bool = false
var press_time: float = 0.0
var is_state_pressed: bool = false
var PRESS_DURATION: float = 3.0
var current_button: Button = null
var current_progress: ProgressBar = null
var current_state: String = ""
func _ready():
read_values_states()
func read_values_states() -> void:
var config = data_handler.parse_config_to_json(config_file_path)
data_handler.config_save_json(config, json_file_path)
rd_log_folder = config["paths"]["logs_folder"]
rd_log = rd_log_folder + "/retrodeck.log"
rdhome = config["paths"]["rdhome"]
roms_folder = config["paths"]["roms_folder"]
saves_folder = config["paths"]["saves_folder"]
states_folder = config["paths"]["states_folder"]
bios_folder = config["paths"]["bios_folder"]
rd_version = config["version"]
rd_conf = extract_text(globals_sh_file_path, "rd_conf")
lockfile = extract_text(globals_sh_file_path, "lockfile")
locale = extract_text(esde_file_path, "esde")
gc_version = ProjectSettings.get_setting("application/config/version")
title = "\n " + rd_version + "\nConfigurator\n " + gc_version
quick_resume_status = config["quick_resume"]["retroarch"]
update_check = config["options"]["update_check"]
abxy_state = multi_state("abxy_button_swap", abxy_state)
ask_to_exit_state = multi_state("ask_to_exit", ask_to_exit_state)
border_state = multi_state("borders", border_state)
widescreen_state = multi_state("widescreen", widescreen_state)
quick_rewind_state = multi_state("rewind", quick_rewind_state)
sound_effects = config["options"]["sound_effects"]
volume_effects = int(config["options"]["volume_effects"])
font_select = int(config["options"]["font"])
cheevos_token_state = str(config["options"]["cheevos_login"])
cheevos_state = multi_state("cheevos", cheevos_state)
cheevos_hardcore_state = multi_state("cheevos_hardcore", cheevos_hardcore_state)
func multi_state(section: String, state: String) -> String:
var config_section:Dictionary = data_handler.get_elements_in_section(config_file_path, section)
var true_count: int = 0
var false_count: int = 0
for value in config_section.values():
if value == "true":
true_count += 1
else:
false_count += 1
if true_count == config_section.size():
state = "true"
elif false_count == config_section.size():
state = "false"
else:
state = "mixed"
return state
func logger(log_type: String, log_text: String) -> void:
# Type of log messages:
# log d - debug message: maybe in the future we can decide to hide them in main builds or if an option is toggled
# log i - normal informational message
# log w - waring: something is not expected but it's not a big deal
# log e - error: something broke
var log_header_text = "gdc_"
log_header_text+=log_text
log_parameters = ["log", log_type, log_header_text]
log_result = await run_thread_command(wrapper_command,log_parameters, false)
func logger_godot(log_type: String, log_text: String) -> void:
var log_dir_path: String = "/var/config/retrodeck/logs/"
var log_path: String = '/var/config/retrodeck/logs/gd_logs.log'
var log_dir: DirAccess = DirAccess.open(log_dir_path)
var log_file: FileAccess
var log_header: String = " GD "
var datetime: Dictionary = Time.get_datetime_dict_from_system()
var unixtime: float = Time.get_unix_time_from_system()
var msec: int = (unixtime - floor(unixtime)) * 1000 # finally, real ms! Thanks, monkeyx
var timestamp: String = "[%d-%02d-%02d %02d:%02d:%02d.%03d]" % [
datetime.year, datetime.month, datetime.day,
datetime.hour, datetime.minute, datetime.second, msec] # real ms!!
var log_line: String = timestamp + log_header
match log_type:
'w':
log_line += "[Warning] "
# print("Warning, mate")
'e':
log_line += "[Error] "
# print("Error, mate")
'i':
log_line += "[Info] "
# print("Info, mate")
'd':
log_line += "[Debug] "
# print("Debug, mate")
_:
log_line += " "
print("No idea, mate")
log_line += log_text
# print(log_line)
if not log_dir:
log_dir = DirAccess.open("res://") #open something valid to create an instance
print(log_dir.make_dir_recursive(log_dir_path))
if log_dir.make_dir_recursive(log_dir_path) != OK:
print("Something wrong with log directory")
return
if not FileAccess.open(log_path, FileAccess.READ):
log_file = FileAccess.open(log_path, FileAccess.WRITE_READ) # to create a file if not there
else:
log_file = FileAccess.open(log_path, FileAccess.READ_WRITE) # to not truncate
if log_file:
log_file.seek_end()
log_file.store_line(log_line)
log_file.close()
else:
print("Something wrong with log file")
func array_to_string(arr: Array) -> String:
var text: String
for line in arr:
text = line.strip_edges() + "\n"
return text
func wait (seconds: float) -> void:
await get_tree().create_timer(seconds).timeout
# TODO This should be looked at again when GoDot 4.3 ships as has new OS.execute_with_pipe
func execute_command(command: String, parameters: Array, console: bool) -> Dictionary:
var result = {}
var output = []
var exit_code = OS.execute(command, parameters, output, console) ## add if exit == 0 etc
result["output"] = class_functions.array_to_string(output)
result["exit_code"] = exit_code
return result
func run_command_in_thread(command: String, paramaters: Array, console: bool) -> Dictionary:
var thread = Thread.new()
thread.start(execute_command.bind(command,paramaters,console))
while thread.is_alive():
await get_tree().process_frame
return thread.wait_to_finish()
func run_thread_command(command: String, parameters: Array, console: bool) -> Dictionary:
log_result = await run_command_in_thread(command, parameters, console)
return log_result
func process_url_image(body) -> Texture:
var image = Image.new()
image.load_png_from_buffer(body)
var texture = ImageTexture.create_from_image(image)
return texture
func launch_help(url: String) -> void:
#OS.unset_environment("LD_PRELOAD")
#OS.set_environment("XDG_CURRENT_DESKTOP", "KDE")
#var launch = class_functions.execute_command("xdg-open",[url], false)
#var launch = class_functions.execute_command("org.mozilla.firefox",[url], false)
OS.shell_open(url)
func import_csv_data(file_path: String) -> Dictionary:
# check if file exists
var data_dict: Dictionary
var file = FileAccess.open(file_path,FileAccess.READ)
if file:
var csv_lines: PackedStringArray = file.get_as_text().strip_edges().split("\n")
for i in range(1, csv_lines.size()): # Start from 1 to skip the header
var line = csv_lines[i]
var columns = line.split(",")
if columns.size() >= 3:
var id = columns[0]
var url = columns[1]
var description = columns[2]
data_dict[id] = {"URL": url, "Description": description}
file.close()
else:
print ("Could not open file: %s", file_path)
return data_dict
func _import_data_lists(file_path: String) -> void:
var tk_about: Dictionary = import_csv_data(file_path)
for key in tk_about.keys():
var entry = tk_about[key]
print("ID: " + key)
print("URL: " + entry["URL"])
print("Description: " + entry["Description"])
print("---")
func import_text_file(file_path: String) -> String:
var content: String = ""
var file = FileAccess.open(file_path, FileAccess.READ)
if file == null:
logger("e","Failed to open file %s" % file_path)
return content
while not file.eof_reached():
content += file.get_line() + "\n"
#file.close
return content
func map_locale_id(current_locale: String) -> int:
var int_locale: int
match current_locale:
"en":
int_locale = 0
"it":
int_locale = 1
"de":
int_locale = 2
"se":
int_locale = 3
"uk":
int_locale = 4
"jp":
int_locale = 5
"cn":
int_locale = 6
return int_locale
func environment_data() -> void:
var env_result = execute_command("printenv",[], true)
var file = FileAccess.open(OS.get_environment("HOME") + "/sdenv.txt",FileAccess.WRITE)
if file != null:
file.store_string(env_result["output"])
file.close()
func display_json_data() -> void:
var app_data := AppData.new()
app_data = data_handler.app_data
#data_handler.add_emulator()
#data_handler.modify_emulator_test()
if app_data:
#var website_data: Link = app_data.about_links["rd_web"]
#print (website_data.name,"-",website_data.url,"-",website_data.description,"-",website_data.url)
##print (app_data.about_links["rd_web"]["name"])
#var core_data: Core = app_data.cores["gambatte_libetro"]
#print (core_data.name)
#for property: CoreProperty in core_data.properties:
#print("Cheevos: ", property.cheevos)
#print("Cheevos Hardcore: ", property.cheevos_hardcore)
#print("Quick Resume: ", property.quick_resume)
#print("Rewind: ", property.rewind)
#print("Borders: ", property.borders)
#print("Widescreen: ", property.widescreen)
#print("ABXY_button:", property.abxy_button)
for key in app_data.emulators.keys():
var emulator = app_data.emulators[key]
# Display the properties of each emulator
print("System Name: ", emulator.name)
#print("Description: ", emulator.description)
#print("System: ", emulator.systen)
#print("Help URL: ", emulator.url)
#print("Properties:")
for property: EmulatorProperty in emulator.properties:
#print("Cheevos: ", property.cheevos)
#print("Borders: ", property.borders)
print("ABXY_button:", property.abxy_button)
#print("multi_user_config_dir: ", property.multi_user_config_dir)
for key in app_data.cores.keys():
var core = app_data.cores[key]
print("Core Name: ", core.name)
#print("Description: ", core.description)
#print("Properties:")
for property: CoreProperty in core.properties:
#print("Cheevos: ", property.cheevos)
#print("Cheevos Hardcore: ", property.cheevos_hardcore)
#print("Quick Resume: ", property.quick_resume)
#print("Rewind: ", property.rewind)
#print("Borders: ", property.borders)
#print("Widescreen: ", property.widescreen)
print("ABXY_button:", property.abxy_button)
else:
print ("No emulators")
func slider_function(value: float, slide: HSlider) -> void:
volume_effects = int(slide.value)
data_handler.change_cfg_value(config_file_path, "volume_effects", "options", str(slide.value))
func run_function(button: Button, preset: String) -> void:
if button.button_pressed:
update_global(button, preset, true)
else:
update_global(button, preset, false)
func update_global(button: Button, preset: String, state: bool) -> void:
#TODO pass state as an object in future version
var result: Array
result.append("build_preset_config")
var config_section:Dictionary = data_handler.get_elements_in_section(config_file_path, preset)
match button.name:
"quick_resume_button", "retroarch_quick_resume_button":
quick_resume_status = state
result.append_array(data_handler.change_cfg_value(config_file_path, "retroarch", preset, str(state)))
change_global(result, button, str(quick_resume_status))
#var resultT: Dictionary = await run_thread_command("/home/tim/bin/mult.sh", [], false)
"update_notification_button":
update_check = state
result.append_array(data_handler.change_cfg_value(config_file_path, preset, "options", str(state)))
change_global(result, button, str(result))
"sound_button":
sound_effects = state
result.append_array(data_handler.change_cfg_value(config_file_path, preset, "options", str(state)))
logger("i", "Enabled: %s" % (button.name))
update_global_signal.emit([button.name])
"cheevos_connect_button":
cheevos_token_state = str(state)
result.append_array(data_handler.change_cfg_value(config_file_path, preset, "options", str(state)))
logger("i", "Enabled: %s" % (button.name))
update_global_signal.emit([button.name])
"button_swap_button":
if abxy_state != "mixed":
abxy_state = str(state)
result.append_array(data_handler.change_all_cfg_values(config_file_path, config_section, preset, str(state)))
change_global(result, button, abxy_state)
"ask_to_exit_button":
if ask_to_exit_state != "mixed":
ask_to_exit_state = str(state)
result.append_array(data_handler.change_all_cfg_values(config_file_path, config_section, preset, str(state)))
await change_global(result, button, ask_to_exit_state)
"border_button":
if border_state != "mixed":
border_state = str(state)
result.append_array(data_handler.change_all_cfg_values(config_file_path, config_section, preset, str(state)))
await change_global(result, button, border_state)
if widescreen_state == "true" or widescreen_state == "mixed":
var button_tmp = main_scene.get_node("%widescreen_button")
#Remove last array item or tries to append again
result.clear()
result.append("build_preset_config")
config_section = data_handler.get_elements_in_section(config_file_path, "widescreen")
widescreen_state = "false"
result.append_array(data_handler.change_all_cfg_values(config_file_path, config_section, "widescreen", widescreen_state))
change_global(result, button, widescreen_state)
"widescreen_button":
if widescreen_state != "mixed":
widescreen_state = str(state)
result.append_array(data_handler.change_all_cfg_values(config_file_path, config_section, preset, str(state)))
await change_global(result, button, widescreen_state)
if border_state == "true" or border_state == "mixed":
var button_tmp = main_scene.get_node("%border_button")
#Remove last array item or tries to append again
result.clear()
result.append("build_preset_config")
config_section = data_handler.get_elements_in_section(config_file_path, "borders")
border_state = "false"
result.append_array(data_handler.change_all_cfg_values(config_file_path, config_section, "borders", border_state))
change_global(result, button_tmp, border_state)
"quick_rewind_button":
if quick_rewind_state != "mixed":
quick_rewind_state = str(state)
result.append_array(data_handler.change_all_cfg_values(config_file_path, config_section, preset, str(state)))
change_global(result, button, quick_rewind_state)
"cheevos_button":
if cheevos_state != "mixed":
cheevos_state = str(state)
result.append_array(data_handler.change_all_cfg_values(config_file_path, config_section, preset, str(state)))
change_global(result, button, cheevos_state)
if cheevos_state == "false":
cheevos_hardcore_state = "false"
result.append_array(data_handler.change_all_cfg_values(config_file_path, config_section, "cheevos_hardcore", class_functions.cheevos_hardcore_state))
change_global(result, button, cheevos_hardcore_state)
"cheevos_hardcore_button":
if cheevos_hardcore_state != "mixed":
cheevos_hardcore_state = str(state)
result.append_array(data_handler.change_all_cfg_values(config_file_path, config_section, preset, str(state)))
change_global(result, button, cheevos_hardcore_state)
func change_global(parameters: Array, button: Button, state: String) -> void:
#print (str(parameters))
match parameters[2]:
"abxy_button_swap", "ask_to_exit", "borders", "widescreen", "rewind", "cheevos", "cheevos_hardcore":
for system in parameters[1].keys():
var command_parameter: Array = [parameters[0],system, parameters[2]]
logger("d", "Change Global Multi: %s " % str(command_parameter))
var result: Dictionary = await run_thread_command(wrapper_command, command_parameter, false)
#var result = OS.execute_with_pipe(wrapper_command, command_parameter)
logger("d", "Exit code: %s" % result["exit_code"])
_:
logger("d", "Change Global Single: %s" % str(parameters))
var result: Dictionary = await run_thread_command(wrapper_command, parameters, false)
#var result = OS.execute_with_pipe(wrapper_command, parameter)
#var result = OS.create_process(wrapper_command, cparameter)
logger("d", "Exit code: %s" % result["exit_code"])
parameters.append(button)
parameters.append(state)
update_global_signal.emit(parameters)
func extract_text(file_path: String, extract: String) -> String:
var file = FileAccess.open(file_path, FileAccess.ModeFlags.READ)
var pattern: String
if file:
var regex = RegEx.new()
if extract != "esde":
pattern = extract + '="([^"]+)"'
else:
pattern = '<string name="ApplicationLanguage" value="([^"]+)" />'
regex.compile(pattern)
while not file.eof_reached():
var line: String = file.get_line().strip_edges()
var result: RegExMatch = regex.search(line)
if result:
return result.get_string(1)
file.close()
else:
logger("e", "Could not open file: %s" % file_path)
return ""
func _on_button_released(progress: ProgressBar) -> void:
is_state_pressed = false
progress.visible = false
press_time = 0.0
progress.value = 0.0
current_button = null
current_progress = null
current_state == null
func _do_action(progress: ProgressBar, button: Button, state: String) -> void:
match [class_functions.button_list]:
[class_functions.button_list]:
current_state = state
current_button = button
current_progress = progress
current_progress.visible = true
is_state_pressed = true
func _do_complete(button: Button) ->void:
#TODO use object for state
if is_state_pressed and button == current_button:
match button.name:
"button_swap_button":
class_functions.abxy_state = "false"
"ask_to_exit_button":
class_functions.ask_to_exit_state = "false"
"border_button":
class_functions.border_state = "false"
"widescreen_button":
class_functions.widescreen_state = "false"
"quick_rewind_button":
class_functions.quick_rewind_state = "false"
"cheevos_button":
class_functions.cheevos_state = "false"
"cheevos_hardcore_button":
class_functions.cheevos_hardcore_state = "false"
"reset_retrodeck_button":
var dir = DirAccess.open(class_functions.rd_conf.get_base_dir())
if dir is DirAccess:
dir.rename(class_functions.rd_conf, class_functions.rd_conf.get_base_dir() + "/retrodeck.bak")
dir.remove(class_functions.lockfile)
class_functions.change_global(["prepare_component", "reset", "retrodeck"], button, "")
button.text = "RESETTING-NOW"
await class_functions.wait(2.0)
button.text = "CONFIGURATOR WILL NOW CLOSE"
await class_functions.wait(1.0)
get_tree().quit()
"reset_all_emulators_button":
var tmp_txt = button.text
button.text = "RESETTING-NOW"
class_functions.change_global(["prepare_component", "reset", "all"], button, "")
await class_functions.wait(2.0)
button.text = "RESET COMPLETED"
await class_functions.wait(3.0)
button.text = tmp_txt
button.toggle_mode = true
func _hide_show_containers(button: Button, grid_container: GridContainer) -> void:
match button.name:
"decorations_button", "systems_button", "system_button", "cheevos_collapse_button", "future_button":
grid_container.visible = true
if button.toggle_mode:
button.toggle_mode=false
grid_container.visible = false
else:
button.toggle_mode=true