""" windows.py - Windows config implementation. Copyright (c) EDCD, All Rights Reserved Licensed under the GNU General Public License v2 or later. See LICENSE file. """ from __future__ import annotations import pathlib import sys import os import uuid import winreg import datetime import tomli_w from config import config_logger, IS_FROZEN from win32comext.shell import shell from typing import Any, TYPE_CHECKING if TYPE_CHECKING: from . import Config if sys.platform != "win32": raise OSError("This file is for Windows only.") REG_RESERVED_ALWAYS_ZERO = 0 def known_folder_path(guid: uuid.UUID) -> str | None: """Look up a Windows GUID to actual folder path name.""" return shell.SHGetKnownFolderPath(guid, 0, 0) class WinConfigMinimal: """Minimal Windows config for exporting pre-6.0 config values to TOML.""" REGISTRY_SUBKEY = r"Software\Marginal\EDMarketConnector" def __init__(self) -> None: # Try to open the key with read access with winreg.OpenKey( winreg.HKEY_CURRENT_USER, self.REGISTRY_SUBKEY, 0, winreg.KEY_READ | winreg.KEY_WOW64_64KEY ): pass # If this fails, key doesn't exist, nothing to convert # Open or create the registry key self.__reg_handle = winreg.CreateKeyEx( winreg.HKEY_CURRENT_USER, self.REGISTRY_SUBKEY, access=winreg.KEY_ALL_ACCESS | winreg.KEY_WOW64_64KEY, ) def _convert_reg_value_for_toml(self, data, data_type) -> Any: """Convert Windows registry values into TOML-native types.""" if data_type == winreg.REG_SZ: return str(data) if data_type == winreg.REG_EXPAND_SZ: try: return str(winreg.ExpandEnvironmentStrings(data)) except Exception: return str(data) if data_type in (winreg.REG_DWORD, winreg.REG_QWORD): return int(data) if data_type == winreg.REG_MULTI_SZ: return list(data) # Fallback: force to string return str(data) def write_to_toml(self, toml_path: str) -> None: """Export all registry values under REGISTRY_SUBKEY to TOML.""" config_logger.debug("Generating New Config File") key = self.__reg_handle _, num_values, _ = winreg.QueryInfoKey(key) config_data: dict[str, Any] = { "generated": datetime.datetime.now(datetime.timezone.utc).isoformat(), "source": "windows_registry", "settings": {}, } for i in range(num_values): name, data, data_type = winreg.EnumValue(key, i) config_data["settings"][str(name).lower()] = self._convert_reg_value_for_toml( data, data_type ) path_obj = pathlib.Path(toml_path) path_obj.parent.mkdir(parents=True, exist_ok=True) tmp_path = path_obj.with_suffix(path_obj.suffix + ".tmp") config_logger.debug("Writing Config File") try: # Validate serialization BEFORE touching the real file tomli_w.dumps(config_data) with tmp_path.open("wb") as f: tomli_w.dump(config_data, f) f.flush() os.fsync(f.fileno()) tmp_path.replace(path_obj) except Exception: config_logger.exception("Failed to write TOML config file") # Ensure temp file doesn't linger try: if tmp_path.exists(): tmp_path.unlink() except Exception: pass raise def close(self): """Close registry handle.""" self.__reg_handle.Close() def win_helper(config: Config) -> Config: """Set Environment Specific Variables for Windows Config.""" config_logger.debug("Windows environment detected. Setting platform-specific variables.") if IS_FROZEN: config.respath_path = pathlib.Path(sys.executable).parent config.internal_plugin_dir_path = config.respath_path / "plugins" else: config.respath_path = pathlib.Path(__file__).parent.parent config.internal_plugin_dir_path = config.respath_path / "plugins" journal_dir_path = ( pathlib.Path(known_folder_path(shell.FOLDERID_SavedGames)) # type: ignore / "Frontier Developments" / "Elite Dangerous" ) config.default_journal_dir_path = journal_dir_path if journal_dir_path.is_dir() else None # type: ignore if (outdir_str := config.get_str("outdir")) is None or not pathlib.Path( outdir_str ).is_dir(): docs = known_folder_path(shell.FOLDERID_Documents) config.set("outdir", docs if docs is not None else config.home) return config