import os import stat import sys from app.errors import Errors from app.vars import ( CONFIG_DIR, GEN_SCRIPT_PATH, PREVIOUS_MODE_PATH, Arguments, Instructions, ) from pkg import sunshine, wayland_display_manager from pkg.sunshine.structs import SunshineEnvironmentVariables from pkg.wayland_display_manager.structs import Modeline def calculate_closest_mode( sunshine_env: SunshineEnvironmentVariables, supported_modes: list[Modeline] ) -> Modeline: target_w = sunshine_env.specs.width target_h = sunshine_env.specs.height target_fps = sunshine_env.specs.fps target_ratio = target_w / target_h target_pixels = target_w * target_h def _sorting_rules(mode: Modeline): # Shape difference mode_ratio = mode.width / mode.height shape_difference = abs(mode_ratio - target_ratio) # Is refresh rate too slow? (rules out very low refresh rates) is_too_slow = mode.refresh_rate < target_fps # How off is the refresh rate? fps_difference = abs(mode.refresh_rate - target_fps) # For comparing total pixel quantities mode_pixels = mode.width * mode.height pixel_difference = abs(mode_pixels - target_pixels) return (shape_difference, is_too_slow, fps_difference, pixel_difference) return min(supported_modes, key=_sorting_rules) def main(): try: os.mkdir(CONFIG_DIR) except FileExistsError: pass sunshine_env = sunshine.load_env_variables() display_manager = wayland_display_manager.get_display() if len(sys.argv) - 1 < Arguments.CONNECTOR: raise SyntaxError(Errors.INSUFFICIENT_INSTRUCTIONS) match sys.argv[Arguments.INSTRUCTION]: case Instructions.SET: display_manager.save_current_mode(PREVIOUS_MODE_PATH) display_modes = next( ( i.modes for i in display_manager.get_modes() if i.connector == sys.argv[Arguments.CONNECTOR] ), None, ) if display_modes: modeline = calculate_closest_mode(sunshine_env, display_modes) display_manager.set_mode( modeline=modeline, requested_connector=sys.argv[Arguments.CONNECTOR] ) print(modeline) else: raise ValueError(Errors.CONNECTOR_INVALID) case Instructions.RESET: modeline = display_manager.read_saved_mode(PREVIOUS_MODE_PATH) display_manager.set_mode(modeline, sys.argv[Arguments.CONNECTOR]) print(modeline) case Instructions.CREATE: if len(sys.argv) - 1 < Arguments.TEMPLATE: raise SyntaxError(Errors.INSUFFICIENT_INSTRUCTIONS) display_modes = next( ( i.modes for i in display_manager.get_modes() if i.connector == sys.argv[Arguments.CONNECTOR] ), None, ) if not display_modes: raise ValueError(Errors.CONNECTOR_INVALID) modeline = calculate_closest_mode(sunshine_env, display_modes) with open(GEN_SCRIPT_PATH, "w") as file: _ = file.write( "#!/bin/bash\n" + sys.argv[Arguments.TEMPLATE].format( res_x=modeline.width, res_y=modeline.height, rate=modeline.refresh_rate, ) ) os.chmod( GEN_SCRIPT_PATH, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH, ) case _: raise ValueError(Errors.CONNECTOR_UNSPECIFIED) if __name__ == "__main__": main()