chromium/ui/base/cocoa/cursor_accessibility_scale_factor.mm

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/base/cocoa/cursor_accessibility_scale_factor.h"

#include <Foundation/Foundation.h>
#include <dispatch/dispatch.h>

#include <algorithm>
#include <optional>

#include "base/apple/foundation_util.h"
#include "base/command_line.h"
#include "ui/base/cursor/cursor.h"

@interface CursorAccessibilityScaleFactorNotifier ()
@property(readonly, nonatomic) float scaleFactor;
@end

@implementation CursorAccessibilityScaleFactorNotifier {
  NSMutableDictionary<id<NSObject>, void (^)(void)>* _observers;
  NSUserDefaults* __strong _defaults;
  float _scaleFactor;
}

@synthesize scaleFactor = _scaleFactor;

+ (CursorAccessibilityScaleFactorNotifier*)sharedNotifier {
  static dispatch_once_t once;
  static CursorAccessibilityScaleFactorNotifier* instance;
  dispatch_once(&once, ^{
    instance = [[CursorAccessibilityScaleFactorNotifier alloc] init];
  });
  return instance;
}

- (instancetype)init {
  if (self = [super init]) {
    CHECK(base::CommandLine::ForCurrentProcess()
              ->GetSwitchValueASCII("type")
              .empty());

    _observers = [[NSMutableDictionary alloc] init];
    _defaults =
        [[NSUserDefaults alloc] initWithSuiteName:@"com.apple.universalaccess"];
    [_defaults addObserver:self
                forKeyPath:@"mouseDriverCursorSize"
                   options:NSKeyValueObservingOptionNew |
                           NSKeyValueObservingOptionInitial
                   context:nullptr];
  }
  return self;
}

- (void)observeValueForKeyPath:(NSString*)keyPath
                      ofObject:(id)object
                        change:(NSDictionary*)change
                       context:(void*)context {
  // If this key is present, it will be a number from [1.0, 4.0], but it might
  // not be present if it is never set, or in tests. In that case, this
  // observation method will be called with NSNull, so the following code must
  // handle that case.
  NSNumber* defaultsValue =
      base::apple::ObjCCast<NSNumber>(change[NSKeyValueChangeNewKey]);
  _scaleFactor = std::clamp(defaultsValue.floatValue, 1.0f, 4.0f);
  for (void (^observer)() in _observers.allValues) {
    observer();
  }
}

- (id<NSObject>)addObserver:(void (^)())observer {
  static int sequence;

  // An opaque token is required to represent the observation, something that is
  // quick and easy and compares differently to other values. A wrapped
  // incrementing number will do.
  id<NSObject, NSCopying> token = @(++sequence);
  _observers[token] = observer;

  return token;
}

- (void)removeObserver:(id<NSObject>)token {
  [_observers removeObjectForKey:token];
}

namespace ui {

float GetCursorAccessibilityScaleFactor() {
  return CursorAccessibilityScaleFactorNotifier.sharedNotifier.scaleFactor;
}

}  // namespace ui

@end