#!~/.pyenv/versions/3.11.6/bin/python # # Copyright (c) 2024 Cutieguwu | Olivia Brooks # # -*- coding:utf-8 -*- # @Title: System utilities. # @Author: Cutieguwu | Olivia Brooks # @Email: owen.brooks77@gmail.com | obroo2@ocdsb.ca # @Description: Utilities for a PyGame game. # # @Script: system.py # @Date Created: 17 Apr, 2024 # @Last Modified: 14 Jun, 2024 # @Last Modified by: Cutieguwu | Olivia Brooks # ---------------------------------------------------------- """ Modules that are loaded will always be loaded globally. Some will only be loaded based on conditions, but made global. Thus, they can be checked for in globals() and be easily accessible anywhere. Even if a module were loaded and unloaded, it would remain in cache, so unloading it is not really worth it. Imports will be loaded as the program needs them, thus reducing the initial memory consumption. """ # Although they won't be reloaded and the imports will be skipped if called again, # avoid wasting processing time by keeping out of import_dependencies() from pkg_resources import working_set from subprocess import check_call, CalledProcessError from sys import executable def install_dependencies(dependencies:dict): """ Tries to install and import any missing dependencies from list. """ # Adapted from Serge Stroobandt on https://stackoverflow.com/questions/46419607/how-to-automatically-install-required-packages-from-a-python-script-as-necessary libsInst = { pkg.key for pkg in working_set # Find required and installed libraries. } libsMiss = dependencies - libsInst # Remove the already installed libraries from the dependency list. try: if len(libsMiss) != 0: check_call([executable, "-m", "pip", "install", "--upgrade", "pip"]) # Ensure pip is up-to-date. for library in libsMiss: check_call([executable, "-m", "pip", "install", library]) # Install all missing libraries. except CalledProcessError: print("Error | CANNOT FIND OR INSTALL DEPENDENCY:") print(library) raise SystemExit install_dependencies({"icecream", "pygame"}) from icecream import ic from pygame.version import vernum from pygame.display import get_driver from os import path from sys import version as pyver from platform import uname from json import load as jload DISPLAY = {} PLATFORM = {"libraries": {}} PLATFORM["kernel"] = uname().system # `system` is a variable of uname() PLATFORM["libraries"]["pygame"] = vernum # Python version cleanup. versionStr = "" for c in pyver: if c == " ": break else: versionStr = versionStr + c versionTuple = tuple(versionStr.split(".")) versionTuple = (int(versionTuple[0]), int(versionTuple[1]), int(versionTuple[2])) PLATFORM["libraries"]["python"] = versionTuple # GCC version cleanup. versionStr = "" startFlag = False for c in pyver: if startFlag: if c not in ["G", "C", "]"]: versionStr = versionStr + c elif c == "[": startFlag = True PLATFORM["libraries"]["GCC"] = versionStr del pyver, versionStr, startFlag, versionTuple # Fetch compatability settings. DIRWORKING = path.dirname(__file__) + "/.." with open(DIRWORKING + "/config/features.json") as f: FEATURES = jload(f) # All features in file default to is_active: false # Enable supported features. for feature in FEATURES.items(): is_active = False for lib in feature[1]["libs"].items(): if PLATFORM["libraries"][lib[0]] > tuple(lib[1]["ver_implemented"]): is_active = True FEATURES[feature[0]]["is_active"] = is_active class Compat(): def linux(): # Adapted from Glyph, Eevee on https://stackoverflow.com/questions/1225057/how-can-i-determine-the-monitor-refresh-rate """ if "environ" not in globals(): # Import if not already. global environ from os import environ try: if environ["WAYLAND_DISPLAY"] != "" and globals["XDG_SESSION_TYPE"] != "x11": # Check for environment variable present on Wayland systems. print("Sorry, you are running Wayland. Configuring FPS on Wayland is not yet supported.") except KeyError: # Wayland keys won't exist on X11 systems (mostly). install_dependencies({"Xlib"}) global display from Xlib import display global randr from Xlib.ext import randr displayGlobal = display.Display() # Get every display thing. Maybe a WM object? default_screen = displayGlobal.get_default_screen() # Find default display for some reason. info = displayGlobal.screen(default_screen) # Use this as a point to get information about all screens. displayConfigs = randr.get_screen_resources(info.root) # Get every screen's possible configurations. configsActive = set() # Create an empty set to add configurations to. for config in displayConfigs.crtcs: # For every display configuration, determine if it's active and add its identifier to the configurations in use. crtc_info = randr.get_crtc_info(info.root, config, displayConfigs.config_timestamp) if crtc_info.mode: configsActive.add(crtc_info.mode) for config in displayConfigs.modes: if config.id in configsActive: # For every active display mode, figure out its framerate. framerate = config.dot_clock / (config.h_total * config.v_total) """ if get_driver() == "wayland": ic("Sorry, you are running Wayland. Configuring FPS on Wayland is not yet supported.") elif get_driver() == "x11": install_dependencies({"Xlib"}) global display from Xlib import display global randr from Xlib.ext import randr displayGlobal = display.Display() # Get every display thing. Maybe a WM object? default_screen = displayGlobal.get_default_screen() # Find default display for some reason. info = displayGlobal.screen(default_screen) # Use this as a point to get information about all screens. displayConfigs = randr.get_screen_resources(info.root) # Get every screen's possible configurations. configsActive = set() # Create an empty set to add configurations to. for config in displayConfigs.crtcs: # For every display configuration, determine if it's active and add its identifier to the configurations in use. crtc_info = randr.get_crtc_info(info.root, config, displayConfigs.config_timestamp) if crtc_info.mode: configsActive.add(crtc_info.mode) framerate = 30 # With multiple displays, get the highest framerate that any one monitor can do. for config in displayConfigs.modes: if config.id in configsActive: # For every active display mode, figure out its framerate. framerateDisplay = (config.dot_clock / (config.h_total * config.v_total)) if framerateDisplay > framerate: framerate = framerateDisplay return framerate def nt(): # Cannot use pygame.display.get_driver for windows or macos in pygame < v2 # Adapted from Anurag Uniyal on https://stackoverflow.com/questions/1225057/how-can-i-determine-the-monitor-refresh-rate install_dependencies({"pywin32"}) if "EnumDisplayDevices" not in globals(): # Import if not already. global EnumDisplayDevices from win32api import EnumDisplayDevices if "EnumDisplaySettings" not in globals(): # Import if not already. global EnumDisplaySettings from win32api import EnumDisplaySettings display_info = EnumDisplayDevices() # Returns PyDISPLAY_DEVICE object display_settings = EnumDisplaySettings(display_info.DeviceName, -1) # Picks display that seems to have highest framerate?... return getattr(display_settings, "DisplayFrequency") def darwin(): # Cannot use pygame.display.get_driver for windows or macos in pygame < v2 # Adapted from Glyph, Eevee on https://stackoverflow.com/questions/1225057/how-can-i-determine-the-monitor-refresh-rate install_dependencies({"Appkit"}) if "NSScreen" not in globals(): # Import if not already. global NSScreen from AppKit import NSScreen for screen in NSScreen.screens(): if framerate < screen.maximumFramesPerSecond(): # I the framerate of this display is higher, set its as the new maximum rate. framerate = screen.maximumFramesPerSecond() return framerate def update_framerate(): """ Used for mock vsync to support Pygame < v2.0.0 """ if FEATURES["matchcase"]["is_active"]: match PLATFORM["kernel"]: case "Linux": framerate = Compat.linux() case "Windows": framerate = Compat.nt() case "Darwin": framerate = Compat.darwin() elif PLATFORM["kernel"] == "Linux": framerate = Compat.linux() elif PLATFORM["kernel"] == "Windows": framerate = Compat.nt() elif PLATFORM["kernel"] == "Darwin": # I'm trusting that the info I found for MacOS does actually still work this way. framerate = Compat.darwin() else: print("Unsupported or unable to detect system kernel. Defaulting to 30 FPS. This can be overridden in the user settings file.") framerate = int("%.0f" % framerate) DISPLAY["framerate"] = framerate