tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

macos_display_configuration.py (7545B)


      1 import argparse
      2 import sys
      3 from typing import Any, NewType, Optional, Tuple
      4 
      5 from Cocoa import NSURL
      6 from ColorSync import (
      7    CGDisplayCreateUUIDFromDisplayID,
      8    ColorSyncDeviceSetCustomProfiles,
      9    kColorSyncDeviceDefaultProfileID,
     10    kColorSyncDisplayDeviceClass,
     11 )
     12 from Quartz import (
     13    CGBeginDisplayConfiguration,
     14    CGCancelDisplayConfiguration,
     15    CGCompleteDisplayConfiguration,
     16    CGConfigureDisplayWithDisplayMode,
     17    CGDisplayCopyAllDisplayModes,
     18    CGDisplayCopyDisplayMode,
     19    CGDisplayModeGetHeight,
     20    CGDisplayModeGetIOFlags,
     21    CGDisplayModeGetPixelHeight,
     22    CGDisplayModeGetPixelWidth,
     23    CGDisplayModeGetRefreshRate,
     24    CGDisplayModeGetWidth,
     25    CGDisplayModeIsUsableForDesktopGUI,
     26    CGDisplayModeRef,
     27    CGGetOnlineDisplayList,
     28    kCGConfigurePermanently,
     29    kCGErrorSuccess,
     30 )
     31 
     32 # Display mode flags
     33 kDisplayModeDefaultFlag = 0x00000004  # noqa: N816
     34 
     35 # Create a new type for display IDs
     36 CGDirectDisplayID = NewType("CGDirectDisplayID", int)
     37 
     38 
     39 def get_pixel_size(mode: CGDisplayModeRef) -> Tuple[int, int]:
     40    return (CGDisplayModeGetPixelWidth(mode), CGDisplayModeGetPixelHeight(mode))
     41 
     42 
     43 def get_size(mode: CGDisplayModeRef) -> Tuple[int, int]:
     44    return (CGDisplayModeGetWidth(mode), CGDisplayModeGetHeight(mode))
     45 
     46 
     47 def calculate_mode_similarity_score(
     48    mode: CGDisplayModeRef, current_mode: CGDisplayModeRef
     49 ) -> int:
     50    current_size = get_size(current_mode)
     51    current_pixel_size = get_pixel_size(current_mode)
     52    current_refresh_rate = CGDisplayModeGetRefreshRate(current_mode)
     53    current_flags = CGDisplayModeGetIOFlags(current_mode)
     54 
     55    size = get_size(mode)
     56    pixel_size = get_pixel_size(mode)
     57    refresh_rate = CGDisplayModeGetRefreshRate(mode)
     58    flags = CGDisplayModeGetIOFlags(mode)
     59 
     60    differences = 0
     61 
     62    if size != current_size:
     63        differences += 1
     64    if pixel_size != current_pixel_size:
     65        differences += 1
     66    if refresh_rate != current_refresh_rate:
     67        differences += 1
     68 
     69    # Count how many individual flags are changing (XOR then count bits)
     70    changed_flags = flags ^ current_flags
     71    if sys.version_info >= (3, 10):
     72        differences += changed_flags.bit_count()
     73    else:
     74        differences += bin(changed_flags).count("1")
     75 
     76    return differences
     77 
     78 
     79 def find_best_unscaled_mode(display_id: CGDirectDisplayID) -> CGDisplayModeRef:
     80    current_mode: Optional[CGDisplayModeRef] = CGDisplayCopyDisplayMode(display_id)
     81 
     82    # If we already have an unscaled mode, we're done.
     83    if current_mode and (
     84        get_size(current_mode) == get_pixel_size(current_mode)
     85    ):
     86        return current_mode
     87 
     88    all_modes = CGDisplayCopyAllDisplayModes(display_id, None)
     89    if not all_modes:
     90        raise Exception("No display modes")
     91 
     92    # If we don't have a current mode, use the default mode instead.
     93    if not current_mode:
     94        default_modes = [
     95            m for m in all_modes if CGDisplayModeGetIOFlags(m) & kDisplayModeDefaultFlag
     96        ]
     97        if not default_modes:
     98            raise Exception("No default display mode found")
     99        current_mode = default_modes[0]
    100        assert current_mode is not None
    101 
    102        if get_size(current_mode) == get_pixel_size(current_mode):
    103            return current_mode
    104 
    105    candidates = [
    106        m
    107        for m in all_modes
    108        if CGDisplayModeIsUsableForDesktopGUI(m) and get_size(m) == get_pixel_size(m)
    109    ]
    110    if not candidates:
    111        raise Exception("No suitable display modes")
    112 
    113    same_size_candidates = [
    114        m for m in candidates if get_size(m) == get_size(current_mode)
    115    ]
    116    same_pixel_size_candidates = [
    117        m for m in candidates if get_pixel_size(m) == get_pixel_size(current_mode)
    118    ]
    119 
    120    if same_size_candidates:
    121        candidates = same_size_candidates
    122    elif same_pixel_size_candidates:
    123        candidates = same_pixel_size_candidates
    124 
    125    return min(
    126        candidates,
    127        key=lambda m: calculate_mode_similarity_score(m, current_mode),
    128    )
    129 
    130 
    131 def set_color_profiles(profile_url: NSURL, *, dry_run: bool = False) -> bool:
    132    max_displays = 10
    133 
    134    (err, display_ids, display_count) = CGGetOnlineDisplayList(max_displays, None, None)
    135    if err != kCGErrorSuccess:
    136        raise ValueError(err)
    137 
    138    display_uuids = [CGDisplayCreateUUIDFromDisplayID(d) for d in display_ids]
    139 
    140    for display_id, display_uuid in zip(display_ids, display_uuids):
    141        if dry_run:
    142            print(
    143                f"Would set color profile for display {display_id} to {profile_url.path()}"
    144            )
    145        else:
    146            profile_info = {kColorSyncDeviceDefaultProfileID: profile_url}
    147            success = ColorSyncDeviceSetCustomProfiles(
    148                kColorSyncDisplayDeviceClass,
    149                display_uuid,
    150                profile_info,
    151            )
    152            if not success:
    153                raise Exception(f"failed to set profile on {display_uuid}")
    154            print(f"Set color profile for display {display_id}")
    155 
    156    return True
    157 
    158 
    159 def set_display_modes(*, dry_run: bool = False) -> bool:
    160    max_displays = 10
    161 
    162    err, display_ids, display_count = CGGetOnlineDisplayList(max_displays, None, None)
    163    if err != kCGErrorSuccess:
    164        raise ValueError(err)
    165 
    166    if dry_run:
    167        for display_id in display_ids:
    168            best_mode = find_best_unscaled_mode(display_id)
    169            best_size = get_size(best_mode)
    170            print(f"Would change display {display_id} to {best_size}")
    171        return True
    172 
    173    err, config_ref = CGBeginDisplayConfiguration(None)
    174    if err != kCGErrorSuccess:
    175        raise Exception("Failed to begin display configuration")
    176 
    177    try:
    178        for display_id in display_ids:
    179            best_mode = find_best_unscaled_mode(display_id)
    180            best_size = get_size(best_mode)
    181 
    182            err = CGConfigureDisplayWithDisplayMode(
    183                config_ref, display_id, best_mode, None
    184            )
    185            if err != kCGErrorSuccess:
    186                raise Exception(
    187                    f"Failed to configure mode for display {display_id}: {err}"
    188                )
    189 
    190            print(f"Configured display {display_id} mode to {best_size}")
    191 
    192    except Exception:
    193        CGCancelDisplayConfiguration(config_ref)
    194        raise
    195 
    196    else:
    197        err = CGCompleteDisplayConfiguration(config_ref, kCGConfigurePermanently)
    198        if err != kCGErrorSuccess:
    199            raise Exception(f"Failed to complete display configuration: {err}")
    200 
    201        print("Display configuration applied permanently")
    202 
    203    return True
    204 
    205 
    206 def create_parser() -> argparse.ArgumentParser:
    207    parser = argparse.ArgumentParser()
    208    parser.add_argument(
    209        "--dry-run",
    210        action="store_true",
    211        help="Show what would be done without making changes",
    212    )
    213    parser.add_argument(
    214        "--no-color-profile",
    215        action="store_false",
    216        dest="color_profile",
    217        help="Don't set color profiles",
    218    )
    219    parser.add_argument(
    220        "--no-display-mode",
    221        action="store_false",
    222        dest="display_mode",
    223        help="Don't set display mode",
    224    )
    225    parser.add_argument(
    226        "--profile-path",
    227        default="/System/Library/ColorSync/Profiles/sRGB Profile.icc",
    228        help="Path to color profile to use (default: sRGB)",
    229    )
    230    return parser
    231 
    232 
    233 def run(venv: Any, **kwargs: Any) -> None:
    234    profile_url = NSURL.fileURLWithPath_(kwargs["profile_path"])
    235    dry_run = kwargs["dry_run"]
    236 
    237    if kwargs["color_profile"]:
    238        set_color_profiles(profile_url, dry_run=dry_run)
    239 
    240    if kwargs["display_mode"]:
    241        set_display_modes(dry_run=dry_run)