2********************************************************************************
4@brief Data module (path related constants and functions)
5********************************************************************************
12from typing
import Any, Optional, TYPE_CHECKING
13from pathlib
import Path
22from serial.tools.list_ports
import comports
23from serial.tools.list_ports_common
import ListPortInfo
27if platform.system() ==
'Windows':
33log = logging.getLogger(__title__)
38 @brief Returns the absolute path to a resource given by a relative path depending on the environment (EXE or Python)
39 @param s_relative_path : the relative path to a file or directory
40 @return absolute path to the resource
42 if hasattr(sys,
"_MEIPASS"):
44 s_base_path = sys._MEIPASS
46 s_base_path = os.path.abspath(
"../")
47 s_resource_path = os.path.join(s_base_path, s_relative_path)
48 log.debug(
"Resource Path (relative %s): %s", s_relative_path, s_resource_path)
49 return s_resource_path
54 @brief loop for threads.
55 @param obj : object of thread class
56 @param thread_name : thread name
58 threading.current_thread().name = thread_name
63 exc_type, exc_obj, exc_tb = sys.exc_info()
64 if exc_tb
is not None:
65 fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
66 lineno = exc_tb.tb_lineno
70 s_err = f
"Thread {thread_name}: {exc_type} {fname} {exc_obj} {lineno}"
72 obj.ui.qt_exception_hook.exception_caught.emit(s_err)
78 @brief Show reset configuration dialog. Enter reset PW to reset configuration.
79 @param ui : main window controller
80 @return status if PW is correct
82 l_reset_title = [
"Reset",
"Zurücksetzen"]
83 l_reset_text = [
"Enter reset code to reset configuration.",
"Reset-Code eingeben, um die Konfiguration zurückzusetzen."]
85 reset_title = ui.model.c_language.get_language_text(l_reset_title)
86 reset_text = ui.model.c_language.get_language_text(l_reset_text)
88 reset_title = l_reset_title[0]
89 reset_text = l_reset_text[0]
90 password, ok = input_dialog(ui, reset_title, reset_text, password_input=
True, icon=ICON_APP)
91 b_reset = bool(ok
and password.isdigit()
and (base64.b64encode(password.encode(
"utf-8")) == RESET_CODE))
99 @brief Get computer name.
100 @return computer name
103 hostname = socket.gethostname()
104 except BaseException:
111 @brief Open explorer.
112 @param explorer_path : path to open
113 @param b_open_input : open explorer into folder
115 s_open_folder = explorer_path.replace(
"/",
"\\")
116 mode =
"explorer " if b_open_input
else r"explorer /select, "
117 with subprocess.Popen(mode + s_open_folder):
123 @brief Check if serial device is present at this COM port
124 @param s_com_port : COM port to check
125 @return return display status: [True] serial device at this COM port available; [False] not available
129 device = serial.Serial(s_com_port)
130 except BaseException:
140 @brief Check if serial device is present at this COM port
141 @return return list of present serial ports
143 l_present_ports = sorted(comports())
144 return l_present_ports
154ICON_APP_PATH =
"Resources/app.ico"
155ICON_APP_FAVICON_PATH =
"Resources/favicon.ico"
163ICON_OPEN_FOLDER_LIGHT =
resource_path(
"Resources/Icon/open_folder_light.png")
169ICON_PRINT_PREVIEW_LIGHT =
resource_path(
"Resources/Icon/print_preview_light.png")
170ICON_PRINT_PREVIEW_DARK =
resource_path(
"Resources/Icon/print_preview_dark.png")
178ICON_USER_IMPORT_LIGHT =
resource_path(
"Resources/Icon/user_import_light.png")
180ICON_USER_EXPORT_LIGHT =
resource_path(
"Resources/Icon/user_export_light.png")
186ICON_ARTICLES_EDIT_LIGHT =
resource_path(
"Resources/Icon/articles_edit_light.png")
187ICON_ARTICLES_EDIT_DARK =
resource_path(
"Resources/Icon/articles_edit_dark.png")
188ICON_ARTICLES_IMPORT_LIGHT =
resource_path(
"Resources/Icon/articles_import_light.png")
189ICON_ARTICLES_IMPORT_DARK =
resource_path(
"Resources/Icon/articles_import_dark.png")
190ICON_ARTICLES_EXPORT_LIGHT =
resource_path(
"Resources/Icon/articles_export_light.png")
191ICON_ARTICLES_EXPORT_DARK =
resource_path(
"Resources/Icon/articles_export_dark.png")
192ICON_ARTICLES_RESET_LIGHT =
resource_path(
"Resources/Icon/articles_reset_light.png")
193ICON_ARTICLES_RESET_DARK =
resource_path(
"Resources/Icon/articles_reset_dark.png")
194ICON_CHANGE_FOLDER_LIGHT =
resource_path(
"Resources/Icon/change_folder_light.png")
195ICON_CHANGE_FOLDER_DARK =
resource_path(
"Resources/Icon/change_folder_dark.png")
227COMPANY_NAME =
"<COMPANY_NAME>"
228APP_NAME =
"<APP_NAME>"
229settings_handle = SETTINGS(COMPANY_NAME, APP_NAME)
234 @brief Supported paper widths of printer
242 @brief Available application themes
253 @brief Available application languages
264S_DATE_PRINT_FORMAT =
"%Y/%m/%d %H:%M:%S"
265L_PRINT_FILE_HEADER: list[str] = [
"Count",
"Article Name",
"Article Number",
"Group",
"Total Price",
"User",
"Date",
"Printer"]
266L_GROUP = [
"Group",
"Gruppe"]
267L_DEVICE = [
"Device",
"System"]
270S_SECTION_PRINTER =
"PRINTER"
271S_KEY_PRINTER_COM_PORT =
"com_port"
272S_DEFAULT_COM_PORT =
None
273S_KEY_PAPER_WIDTH =
"paper_width"
274I_DEFAULT_PAPER_WIDTH = EPaper.WIDTH_58_MM.value
276S_SECTION_SETTINGS =
"SETTINGS"
278B_DEFAULT_SOUND =
True
279S_KEY_SHOW_PRICE =
"show_price"
280B_DEFAULT_SHOW_PRICE =
True
281S_KEY_PRINT_REPORT =
"print_report"
282B_DEFAULT_PRINT_REPORT =
True
283S_KEY_THEME =
"darkmode"
284E_DEFAULT_THEME = ETheme.AUTO
285S_KEY_LANGUAGE =
"language"
286E_DEFAULT_LANGUAGE = ELanguages.ENGLISH
287S_KEY_VERBOSITY =
"verbosity"
288I_LOG_LEVEL_DEFAULT = logging.WARNING
289S_KEY_OUTPUT_PATH =
"output_path"
290S_DEFAULT_OUTPUT_FOLDER =
"BonPrinter"
291S_DEFAULT_OUTPUT_PATH = str(Path.home()) +
"/" + S_DEFAULT_OUTPUT_FOLDER
292S_KEY_UPDATE_VERSION =
"update_version"
293S_DEFAULT_UPDATE_VERSION =
"0.0.0"
294S_KEY_LAST_DIR_PATH =
"last_dir"
295S_DEFAULT_LAST_PATH =
"./"
298S_KEY_GEOMETRY =
"window_geometry"
299S_KEY_STATE =
"window_state"
300I_DEFAULT_WIN_WIDTH = 720
301I_DEFAULT_WIN_HEIGHT = 450
303S_SECTION_CONFIGURATION =
"CONFIGURATION"
308S_HEADER_1 =
"header1"
309S_HEADER_2 =
"header2"
315S_PRINT_ARTICLE =
"print_article"
316S_DEFAULT_PRINT_ARTICLE =
"True"
317S_PRINT_FREE_PRICE =
"print_free_price"
318S_DEFAULT_PRINT_FREE_PRICE =
"True"
322S_USER_VISIBLE =
"user_visible"
328S_DEFAULT_PRICE =
"9.00"
329LOCKED_USER_PW =
"Locked"
330S_DEFAULT_USER_PW = LOCKED_USER_PW
334S_SECTION_SALES =
"SALES"
335I_NUMBER_OF_SALES = 101
340S_USER_TIMEOUT =
"timeout"
341S_AUTO_OPEN =
"auto_open"
342S_FREE_AUTO_LOGOUT =
"free_auto_logout"
343I_DEFAULT_USER_TIMEOUT = 15
344S_DEFAULT_AUTO_OPEN =
"True"
345S_DEFAULT_FREE_AUTO_LOGOUT =
"True"
363ITEM_NUMBER_COLUMN_MIN = 5
365D_ITEM_NUMBER_COLUMN = {
366 ItemNumber.MIN: ITEM_NUMBER_COLUMN_MIN,
371I_ITEM_ARRAY_SIZE = ItemNumber.MAX
372I_NUMBER_OF_DISPLAYED_USER = 15
381 @brief Present users.
391L_CONFIG_USER = [EUser.ADMIN, EUser.HOST]
392L_SELL_USER = [EUser.LOCAL, EUser.FREE]
393L_ORDER_USER = [EUser.B + str(i)
for i
in range(1, I_NUMBER_OF_SALES)]
394L_ALLOWED_USER = L_CONFIG_USER + L_SELL_USER + L_ORDER_USER
397 S_GENERAL: {S_DEVICE:
"",
398 S_USER_TIMEOUT: str(I_DEFAULT_USER_TIMEOUT),
399 S_AUTO_OPEN: str(S_DEFAULT_AUTO_OPEN),
400 S_FREE_AUTO_LOGOUT: str(S_DEFAULT_FREE_AUTO_LOGOUT)},
401 EUser.ADMIN.value: {S_PW: base64.b64decode(RESET_CODE).decode(
"utf-8")},
402 EUser.HOST.value: {S_PW: LOCKED_USER_PW},
403 EUser.LOCAL.value: {S_PW: LOCKED_USER_PW},
404 EUser.FREE.value: {S_PW: LOCKED_USER_PW}
407for i
in range(0, I_NUMBER_OF_DISPLAYED_USER):
408 user_key = f
"{EUser.B.value}{i + 1}"
409 D_DEFAULT_USER[user_key] = {}
410 D_DEFAULT_USER[user_key][S_NAME] =
""
411 D_DEFAULT_USER[user_key][S_PW] = S_DEFAULT_USER_PW
414 S_HEADER: {S_HEADER_1:
"", S_HEADER_2:
""},
415 S_TAX: {S_GROUP_1:
"19", S_GROUP_2:
"19", S_GROUP_3:
"0", S_USER_TAX:
"0"},
417 S_PRINT_ARTICLE: S_DEFAULT_PRINT_ARTICLE,
418 S_PRINT_FREE_PRICE: S_DEFAULT_PRINT_FREE_PRICE
422for i
in range(0, ItemNumber.MAX):
423 item_key = str(i + 1)
424 D_DEFAULT_ITEM[item_key] = {}
425 D_DEFAULT_ITEM[item_key][S_NAME] =
""
426 D_DEFAULT_ITEM[item_key][S_PRICE] = S_DEFAULT_PRICE
427 D_DEFAULT_ITEM[item_key][S_GROUP] = str(I_GROUP_3)
436 @brief Get default article configuration depend on deposit name
437 @param s_deposit_name : name of deposit
438 @return default article configuration
440 d_item_dict = copy.deepcopy(D_DEFAULT_ITEM)
446 @brief Clear registry settings to write defaults at next startup
448 log.warning(
"Set default configuration settings")
451 for group
in [S_SECTION_PRINTER, S_SECTION_CONFIGURATION, S_SECTION_SETTINGS]:
452 handle.beginGroup(group)
459 @brief Returns the settings handle
460 @return settings handle
462 return settings_handle
467 @brief Reads the registry value with given handle and key.
468 @param handle : settings handle
469 @param s_key : get value for this key
470 @param b_none_err : [True] call error if None in setting [False] allowed None as setting
471 @return value that is mapped to the given key or raises a KeyError if key not found in handle.
473 value = handle.value(s_key, defaultValue=
None)
474 if b_none_err
and (value
is None):
475 raise KeyError(f
"{s_key} not found in group {handle.group()}")
481 @brief Writes the sound settings to persistent storage
482 @param b_sound_status : sound status
485 handle.beginGroup(S_SECTION_SETTINGS)
486 handle.setValue(S_KEY_SOUND, b_sound_status)
492 @brief Reads the sound settings from persistent storage
497 handle.beginGroup(S_SECTION_SETTINGS)
500 except BaseException
as e:
501 log.debug(
"Sound not found, using default values (%s)", str(e))
502 b_sound = B_DEFAULT_SOUND
509 @brief Writes the sales to persistent storage
510 @param i_user_pos : user position to write sales
511 @param f_sales : sales value to save
514 handle.beginGroup(S_SECTION_SALES)
515 handle.setValue(str(i_user_pos), f_sales)
521 @brief Reads the sales from persistent storage
525 handle.beginGroup(S_SECTION_SALES)
527 for i_user
in range(I_NUMBER_OF_SALES):
530 except KeyError
as e:
531 log.debug(
"Sales not found, using default values (%s)", str(e))
532 l_sales.append(F_DEFAULT_SALES)
533 except BaseException:
535 log.warning(
"Sales of user %s not valid: %s", str(i_user),
get_registry_value(handle, str(i_user)))
536 l_sales.append(F_DEFAULT_SALES)
543 @brief Writes the user settings to persistent storage. Passwords are stored encrypted.
544 This encryption is carried out via the Windows Data Protection API (DPAPI),
545 which enables user and machine-specific encryption.
546 @param d_user : user dictionary
548 dict_to_write = copy.deepcopy(d_user)
549 if platform.system() ==
'Windows':
550 for user_type, user_data
in dict_to_write.items():
551 if S_PW
in user_data:
552 s_password = user_data[S_PW]
553 s_password_encrypted = win32crypt.CryptProtectData(bytes(s_password,
"utf-8"))
if len(s_password) > 0
else ""
554 dict_to_write[user_type][S_PW] = s_password_encrypted
556 handle.beginGroup(S_SECTION_CONFIGURATION)
557 handle.setValue(S_KEY_USERS, dict_to_write)
563 @brief Reads the user settings from persistent storage
564 @return user settings
568 handle.beginGroup(S_SECTION_CONFIGURATION)
570 if platform.system() ==
'Windows':
571 for user_type, user_data
in d_user.items():
572 if S_PW
in user_data:
573 bytes_password_enc = user_data[S_PW]
574 _, bytes_password = win32crypt.CryptUnprotectData(bytes_password_enc)
if (len(bytes_password_enc) > 0)
else (
None, bytes(bytes_password_enc,
"utf-8"))
575 d_user[user_type][S_PW] = bytes_password.decode(
"utf-8")
577 except BaseException
as e:
578 log.debug(
"Invalid User settings, using default values (%s)", str(e))
579 d_user = D_DEFAULT_USER
581 return d_user
if isinstance(d_user, dict)
else {}
586 @brief Writes the articles settings to persistent storage
587 @param d_articles : articles dictionary
590 handle.beginGroup(S_SECTION_CONFIGURATION)
591 handle.setValue(S_KEY_ITEMS, d_articles)
597 @brief Reads the articles settings from persistent storage
598 @return user settings
602 handle.beginGroup(S_SECTION_CONFIGURATION)
605 except BaseException
as e:
606 log.debug(
"Articles settings not found, using default values (%s)", str(e))
607 d_articles = D_DEFAULT_ITEM
609 return d_articles
if isinstance(d_articles, dict)
else {}
614 @brief Writes the COM port settings to persistent storage
615 @param s_com_port : COM port
618 handle.beginGroup(S_SECTION_PRINTER)
619 handle.setValue(S_KEY_PRINTER_COM_PORT, s_com_port)
625 @brief Reads the COM port settings from persistent storage
631 handle.beginGroup(S_SECTION_PRINTER)
633 if com_port
is not None:
634 com_port = str(com_port)
636 except BaseException
as e:
637 log.debug(
"COM Port settings not found, using default values (%s)", str(e))
638 com_port = S_DEFAULT_COM_PORT
645 @brief Writes the paper width settings to persistent storage
646 @param i_paper_width : paper width
649 handle.beginGroup(S_SECTION_PRINTER)
650 handle.setValue(S_KEY_PAPER_WIDTH, i_paper_width)
656 @brief Reads the paper width settings from persistent storage
661 handle.beginGroup(S_SECTION_PRINTER)
664 except BaseException
as e:
665 log.debug(
"Paper Width settings not found, using default values (%s)", str(e))
666 i_paper_width = I_DEFAULT_PAPER_WIDTH
673 @brief Writes the output path settings to persistent storage
674 @param s_output_path : output path
677 handle.beginGroup(S_SECTION_PRINTER)
678 handle.setValue(S_KEY_OUTPUT_PATH, s_output_path)
684 @brief Reads the output path settings from persistent storage
689 handle.beginGroup(S_SECTION_PRINTER)
692 except BaseException
as e:
693 log.debug(
"Output path settings not found, using default values (%s)", str(e))
694 if not os.path.exists(S_DEFAULT_OUTPUT_PATH):
695 os.mkdir(S_DEFAULT_OUTPUT_PATH)
696 s_output_path = S_DEFAULT_OUTPUT_PATH
703 @brief Writes the theme settings to persistent storage
704 @param e_theme : current theme
707 handle.beginGroup(S_SECTION_SETTINGS)
708 handle.setValue(S_KEY_THEME, e_theme.value)
714 @brief Reads the theme settings from persistent storage
715 @return Theme settings (enum ETheme)
719 handle.beginGroup(S_SECTION_SETTINGS)
723 except BaseException
as e:
724 log.debug(
"Theme settings not found, using default values (%s)", str(e))
725 e_theme = E_DEFAULT_THEME
732 @brief Writes the language to persistent storage
733 @param e_language : current language
736 handle.beginGroup(S_SECTION_SETTINGS)
737 handle.setValue(S_KEY_LANGUAGE, e_language.value)
743 @brief Reads the language from persistent storage
744 @return Language (enum ELanguages)
748 handle.beginGroup(S_SECTION_SETTINGS)
752 except BaseException
as e:
753 log.debug(
"Language not found, using default values (%s)", str(e))
754 current_locale, _test = locale.getlocale()
755 if current_locale
is not None:
756 e_language = ELanguages.GERMAN
if current_locale.startswith(
"de")
else E_DEFAULT_LANGUAGE
758 e_language = E_DEFAULT_LANGUAGE
765 @brief Writes the verbosity settings to persistent storage
766 @param i_log_level : log verbosity
769 handle.beginGroup(S_SECTION_SETTINGS)
770 handle.setValue(S_KEY_VERBOSITY, i_log_level)
776 @brief Reads the verbosity settings from persistent storage
777 @return verbosity level
781 handle.beginGroup(S_SECTION_SETTINGS)
784 except BaseException
as e:
785 log.debug(
"Verbosity settings not found, using default values (%s)", str(e))
786 i_log_level = I_LOG_LEVEL_DEFAULT
793 @brief Saves the window state to persistent storage.
794 @param o_geometry : geometry (position, size) of the window as QByteArray
795 @param o_state : state (dock widgets etc.) of the window as QByteArray
798 handle.beginGroup(S_SECTION_SETTINGS)
799 handle.setValue(S_KEY_GEOMETRY, o_geometry)
800 handle.setValue(S_KEY_STATE, o_state)
806 @brief Reads the window geometry and state from persistent storage.
807 @return window geometry and state as QByteArray
811 handle.beginGroup(S_SECTION_SETTINGS)
815 except BaseException
as e:
816 log.debug(
"WindowsSettings not found, using default values (%s)", str(e))
817 o_geometry = o_state =
None
819 return o_geometry, o_state
824 @brief Writes the last reminded tool version for update to persistent storage.
825 @param version : last reminded version
828 handle.beginGroup(S_SECTION_SETTINGS)
829 handle.setValue(S_KEY_UPDATE_VERSION, version)
835 @brief Reads the last reminded tool version from persistent storage
836 @return last reminded version
840 handle.beginGroup(S_SECTION_SETTINGS)
843 except BaseException
as e:
844 log.debug(
"Update version settings not found, using default values (%s)", str(e))
845 version = S_DEFAULT_UPDATE_VERSION
852 @brief Writes the last directory to persistent storage
853 @param dir_path : directory path
856 handle.beginGroup(S_SECTION_SETTINGS)
857 handle.setValue(S_KEY_LAST_DIR_PATH, dir_path)
863 @brief Reads the last directory from persistent storage
864 @return directory path
868 handle.beginGroup(S_SECTION_SETTINGS)
871 except BaseException
as e:
872 log.debug(
"Last directory settings not found, using default values (%s)", str(e))
873 dir_path = S_DEFAULT_LAST_PATH
880 @brief Writes the print report settings to persistent storage
881 @param b_print_report : enable print report status
884 handle.beginGroup(S_SECTION_SETTINGS)
885 handle.setValue(S_KEY_PRINT_REPORT, b_print_report)
891 @brief Reads the print report status from persistent storage
892 @return print report status
896 handle.beginGroup(S_SECTION_SETTINGS)
899 except BaseException
as e:
900 log.debug(
"Print Report not found, using default values (%s)", str(e))
901 b_show_price = B_DEFAULT_PRINT_REPORT
908 @brief Writes the show price settings to persistent storage
909 @param b_show_price : show price status
912 handle.beginGroup(S_SECTION_SETTINGS)
913 handle.setValue(S_KEY_SHOW_PRICE, b_show_price)
919 @brief Reads the show price status from persistent storage
920 @return show price status
924 handle.beginGroup(S_SECTION_SETTINGS)
927 except BaseException
as e:
928 log.debug(
"Show Price not found, using default values (%s)", str(e))
929 b_show_price = B_DEFAULT_SHOW_PRICE
Available application languages.
Supported paper widths of printer.
Available application themes.
bool read_show_price_settings()
Reads the show price status from persistent storage.
str read_output_path_settings()
Reads the output path settings from persistent storage.
dict[str, dict[str, str]] get_default_item_config(Optional[str] s_deposit_name=None)
Get default article configuration depend on deposit name.
ELanguages read_language()
Reads the language from persistent storage.
None write_last_dir(str dir_path)
Writes the last directory to persistent storage.
None write_show_price_settings(bool b_show_price)
Writes the show price settings to persistent storage.
int read_verbosity_settings()
Reads the verbosity settings from persistent storage.
None save_window_state(BYTE_ARRAY o_geometry, BYTE_ARRAY o_state)
Saves the window state to persistent storage.
tuple[bool, bool|None] reset_config_dialog("MainWindow | None" ui)
Show reset configuration dialog.
str read_update_version()
Reads the last reminded tool version from persistent storage.
None write_com_port_settings(str s_com_port)
Writes the COM port settings to persistent storage.
None clear_settings()
Clear registry settings to write defaults at next startup.
None write_output_path_settings(str s_output_path)
Writes the output path settings to persistent storage.
ETheme read_theme_settings()
Reads the theme settings from persistent storage.
None write_paper_width_settings(int i_paper_width)
Writes the paper width settings to persistent storage.
bool read_sound_settings()
Reads the sound settings from persistent storage.
str get_computer_name()
Get computer name.
None open_explorer(str explorer_path, bool b_open_input=False)
Open explorer.
SETTINGS get_settings_handle()
Returns the settings handle.
None write_theme_settings(ETheme e_theme)
Writes the theme settings to persistent storage.
dict[str, Any] read_articles_settings()
Reads the articles settings from persistent storage.
None write_print_report_settings(bool b_print_report)
Writes the print report settings to persistent storage.
list[float] read_sales()
Reads the sales from persistent storage.
None write_language(ELanguages e_language)
Writes the language to persistent storage.
None write_user_settings(dict[str, dict[str, str]] d_user)
Writes the user settings to persistent storage.
str|None read_com_port_settings()
Reads the COM port settings from persistent storage.
None write_articles_settings(dict[str, dict[str, str]] d_articles)
Writes the articles settings to persistent storage.
list[ListPortInfo] get_serial_ports()
Check if serial device is present at this COM port.
Any get_registry_value(SETTINGS handle, str s_key, bool b_none_err=True)
Reads the registry value with given handle and key.
int read_paper_width_settings()
Reads the paper width settings from persistent storage.
bool read_print_report_settings()
Reads the print report status from persistent storage.
None write_sales(int i_user_pos, float f_sales)
Writes the sales to persistent storage.
dict[str, Any] read_user_settings()
Reads the user settings from persistent storage.
None thread_loop(Any obj, str thread_name)
loop for threads.
tuple[BYTE_ARRAY, BYTE_ARRAY] read_window_state()
Reads the window geometry and state from persistent storage.
None write_update_version(str version)
Writes the last reminded tool version for update to persistent storage.
str read_last_dir()
Reads the last directory from persistent storage.
None write_verbosity_settings(int i_log_level)
Writes the verbosity settings to persistent storage.
bool check_serial_device(str s_com_port)
Check if serial device is present at this COM port.
None write_sound_settings(bool b_sound_status)
Writes the sound settings to persistent storage.
str resource_path(str s_relative_path)
Returns the absolute path to a resource given by a relative path depending on the environment (EXE or...