当前位置: 首页 > news >正文

Github每日精选(第31期):macOS 下的亮度和音量调节MonitorControl

MonitorControl

MonitorControl可以在 Mac 上控制显示器的亮度和音量,就好像它是原生 Apple 显示器一样。

在这里插入图片描述

主要的特点

  • 控制显示器的亮度、音量和对比度!
  • 显示亮度和音量的原生 OSD。
  • 支持多种亮度调节协议:外部显示器的 DDC(亮度、对比度、音量)、Apple 和内置显示器的原生 - Apple 协议、软件调节的 Gamma 表控制(推荐用于 OLED)、AirPlay、Sidecar 和 Display 的阴影控制链接设备。
  • 支持平滑的亮度过渡。
  • 无缝结合的硬件和软件调光将调光扩展到显示器上可用的最低亮度之外。
  • 同步内置和 Apple 屏幕的亮度 - 将环境光传感器和触控条引起的变化复制到非 Apple 外部显示器!
  • 使用单个滑块或键盘快捷键同步所有显示器。
  • 允许调暗至全黑(高级功能)。
  • 支持自定义键盘快捷键以及 Apple 键盘上的标准亮度和媒体键。
  • 数十种自定义选项可调整应用程序的内部运作以适应您的硬件和需求(不要忘记Show advanced settings在应用程序首选项中启用)。
  • 现代、时尚且高度可定制的菜单反映了 Big Sur 中引入的 Control Control 的设计。
  • 简单、不显眼的 UI 融入 macOS 的一般美学(甚至菜单图标也可以隐藏)。
  • 支持自动更新,轻松体验。

代码分析

对于在任务栏上的设计,该软件也怎么做了如下的出来,自绘了相关的关键部件。

override func drawBar(inside aRect: NSRect, flipped: Bool) {
      guard !DEBUG_MACOS10, #available(macOS 11.0, *) else {
        super.drawBar(inside: aRect, flipped: flipped)
        return
      }
      var maxValue: Float = self.floatValue
      var minValue: Float = self.floatValue

      if self.isHighlightDisplayItems {
        maxValue = max(self.displayHighlightItems.values.max() ?? 0, maxValue)
        minValue = min(self.displayHighlightItems.values.min() ?? 1, minValue)
      }

      let barRadius = aRect.height * 0.5 * (self.numOfTickmarks == 0 ? 1 : self.tickMarkKnobExtraRadiusMultiplier)
      let bar = NSBezierPath(roundedRect: aRect, xRadius: barRadius, yRadius: barRadius)
      self.barFillColor.setFill()
      bar.fill()

      let barFilledWidth = (aRect.width - aRect.height) * CGFloat(maxValue) + aRect.height
      let barFilledRect = NSRect(x: aRect.origin.x, y: aRect.origin.y, width: barFilledWidth, height: aRect.height)
      let barFilled = NSBezierPath(roundedRect: barFilledRect, xRadius: barRadius, yRadius: barRadius)
      self.barFilledFillColor.setFill()
      barFilled.fill()

      let knobMinX = aRect.origin.x + (aRect.width - aRect.height) * CGFloat(minValue)
      let knobMaxX = aRect.origin.x + (aRect.width - aRect.height) * CGFloat(maxValue)
      let knobRect = NSRect(x: knobMinX + (self.numOfTickmarks == 0 ? CGFloat(0) : self.tickMarkKnobExtraInset), y: aRect.origin.y, width: aRect.height + CGFloat(knobMaxX - knobMinX), height: aRect.height).insetBy(dx: self.numOfTickmarks == 0 ? CGFloat(0) : self.tickMarkKnobExtraInset, dy: 0)
      let knobRadius = knobRect.height * 0.5 * (self.numOfTickmarks == 0 ? 1 : self.tickMarkKnobExtraRadiusMultiplier)

      if self.numOfTickmarks > 0 {
        for i in 1 ... self.numOfTickmarks - 2 {
          let currentMarkLocation = CGFloat((Float(1) / Float(self.numOfTickmarks - 1)) * Float(i))
          let tickMarkBounds = NSRect(x: aRect.origin.x + aRect.height + self.tickMarkKnobExtraInset - knobRect.height + self.tickMarkKnobExtraInset * 2 + CGFloat(Float((aRect.width - self.tickMarkKnobExtraInset * 5) * currentMarkLocation)), y: aRect.origin.y + aRect.height * (1 / 3), width: 4, height: aRect.height / 3)
          let tickmark = NSBezierPath(roundedRect: tickMarkBounds, xRadius: 1, yRadius: 1)
          self.tickMarkColor.setFill()
          tickmark.fill()
        }
      }

      let knobAlpha = CGFloat(max(0, min(1, (minValue - 0.08) * 5)))
      for i in 1 ... 3 {
        let knobShadow = NSBezierPath(roundedRect: knobRect.offsetBy(dx: CGFloat(-1 * 2 * i), dy: 0), xRadius: knobRadius, yRadius: knobRadius)
        self.knobShadowColor.withAlphaComponent(self.knobShadowColor.alphaComponent * knobAlpha).setFill()
        knobShadow.fill()
      }

      let knob = NSBezierPath(roundedRect: knobRect, xRadius: knobRadius, yRadius: knobRadius)
      (self.isTracking ? self.knobFillColorTracking : self.knobFillColor).withAlphaComponent(knobAlpha).setFill()
      knob.fill()

      if self.isHighlightDisplayItems, self.displayHighlightItems.count > 2 {
        for currentMarkLocation in self.displayHighlightItems.values {
          let highlightKnobX = aRect.origin.x + (aRect.width - aRect.height) * CGFloat(currentMarkLocation)
          let highlightKnobRect = NSRect(x: highlightKnobX + (self.numOfTickmarks == 0 ? CGFloat(0) : self.tickMarkKnobExtraInset), y: aRect.origin.y, width: aRect.height, height: aRect.height).insetBy(dx: (self.numOfTickmarks == 0 ? CGFloat(0) : self.tickMarkKnobExtraInset) + CGFloat(self.numOfTickmarks == 0 ? 6 : 3), dy: CGFloat(self.numOfTickmarks == 0 ? 6 : 6))
          let highlightKnobRadius = highlightKnobRect.height * 0.5 * (self.numOfTickmarks == 0 ? 1 : self.tickMarkKnobExtraRadiusMultiplier)
          let highlightKnob = NSBezierPath(roundedRect: highlightKnobRect, xRadius: highlightKnobRadius, yRadius: highlightKnobRadius)
          let highlightDisplayIndicatorAlpha = CGFloat(max(0, min(1, (currentMarkLocation - 0.08) * 5)))
          self.highlightDisplayIndicatorColor.withAlphaComponent(self.highlightDisplayIndicatorColor.alphaComponent * highlightDisplayIndicatorAlpha).setFill()
          highlightKnob.fill()
        }
      }

      self.knobStrokeColor.withAlphaComponent(self.knobStrokeColor.alphaComponent * knobAlpha).setStroke()
      knob.stroke()
      self.barStrokeColor.setStroke()
      bar.stroke()
    }
  }

对于屏幕亮度的控制。

 func setSmoothBrightness(_ to: Float = -1, slow: Bool = false) -> Bool {
    guard app.sleepID == 0, app.reconfigureID == 0 else {
      self.savePref(self.smoothBrightnessTransient, for: .brightness)
      self.smoothBrightnessRunning = false
      os_log("Pushing brightness stopped for Display %{public}@ because of sleep or reconfiguration", type: .info, String(self.identifier))
      return false
    }
    if slow {
      self.smoothBrightnessSlow = true
    }
    var stepDivider: Float = 6
    if self.smoothBrightnessSlow {
      stepDivider = 16
    }
    var dontPushAgain = false
    if to != -1 {
      os_log("Pushing brightness towards goal of %{public}@ for Display  %{public}@", type: .info, String(to), String(self.identifier))
      let value = max(min(to, 1), 0)
      self.savePref(value, for: .brightness)
      self.brightnessSyncSourceValue = value
      self.smoothBrightnessSlow = slow
      if self.smoothBrightnessRunning {
        return true
      }
    }
    let brightness = self.readPrefAsFloat(for: .brightness)
    if brightness != self.smoothBrightnessTransient {
      if abs(brightness - self.smoothBrightnessTransient) < 0.01 {
        self.smoothBrightnessTransient = brightness
        os_log("Pushing brightness finished for Display  %{public}@", type: .info, String(self.identifier))
        dontPushAgain = true
        self.smoothBrightnessRunning = false
      } else if brightness > self.smoothBrightnessTransient {
        self.smoothBrightnessTransient += max((brightness - self.smoothBrightnessTransient) / stepDivider, 1 / 100)
      } else {
        self.smoothBrightnessTransient += min((brightness - self.smoothBrightnessTransient) / stepDivider, 1 / 100)
      }
      _ = self.setDirectBrightness(self.smoothBrightnessTransient, transient: true)
      if !dontPushAgain {
        self.smoothBrightnessRunning = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.02) {
          _ = self.setSmoothBrightness()
        }
      }
    } else {
      os_log("No more need to push brightness for Display  %{public}@ (setting one final time)", type: .info, String(self.identifier))
      _ = self.setDirectBrightness(self.smoothBrightnessTransient, transient: true)
      self.smoothBrightnessRunning = false
    }
    self.swBrightnessSemaphore.signal()
    return true
  }

对于声音的控制。

  func stepVolume(isUp: Bool, isSmallIncrement: Bool) {
    guard !self.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) else {
      OSDUtils.showOsdVolumeDisabled(displayID: self.identifier)
      return
    }
    let currentValue = self.readPrefAsFloat(for: .audioSpeakerVolume)
    var muteValue: Int?
    let volumeOSDValue = self.calcNewValue(currentValue: currentValue, isUp: isUp, isSmallIncrement: isSmallIncrement)
    if self.readPrefAsInt(for: .audioMuteScreenBlank) == 1, volumeOSDValue > 0 {
      muteValue = 2
    } else if self.readPrefAsInt(for: .audioMuteScreenBlank) != 1, volumeOSDValue == 0 {
      muteValue = 1
    }
    let isAlreadySet = volumeOSDValue == self.readPrefAsFloat(for: .audioSpeakerVolume)
    if !isAlreadySet {
      if let muteValue = muteValue, self.readPrefAsBool(key: .enableMuteUnmute) {
        self.writeDDCValues(command: .audioMuteScreenBlank, value: UInt16(muteValue))
        self.savePref(muteValue, for: .audioMuteScreenBlank)
      }
      if !self.readPrefAsBool(key: .enableMuteUnmute) || volumeOSDValue != 0 {
        self.writeDDCValues(command: .audioSpeakerVolume, value: self.convValueToDDC(for: .audioSpeakerVolume, from: volumeOSDValue))
      }
    }
    if !self.readPrefAsBool(key: .hideOsd) {
      OSDUtils.showOsd(displayID: self.identifier, command: .audioSpeakerVolume, value: volumeOSDValue, roundChiclet: !isSmallIncrement)
    }
    if !isAlreadySet {
      self.savePref(volumeOSDValue, for: .audioSpeakerVolume)
      if let slider = self.sliderHandler[.audioSpeakerVolume] {
        slider.setValue(volumeOSDValue, displayID: self.identifier)
      }
    }
  }

相关文章:

  • Vue.js核心技术解析与uni-app跨平台实战开发学习笔记 第7章 Vue.js高级进阶 7.10 路由守卫
  • 金融核心系统云原生转型的三个挑战、六个误区和四个步骤
  • zsh安装以及ROS适配
  • 猿创征文|FlexManager与阿里云MQTT通讯
  • Linux指令——crontab
  • 程序员的中秋
  • mysql数据库的安装教程
  • 新电脑的正确打开方式——(近万字图文并茂详细分步骤讲解)【包括个性锁屏,磁盘分区……】等你来解锁哦
  • .NET版Word处理控件Aspose.words功能演示:在ASP.NET MVC中创建MS Word编辑器
  • 【毕业设计】基于单片机的手势检测识别系统 - arduino 物联网嵌入式
  • 【Node.js】深度解析常用核心模块-path模块
  • C语言指针操作(六)*返回指针值的函数
  • 10. Vue 常用的修饰符的作用详解?
  • 第五篇 python 基本语法(一)
  • 猿创征文| JAVA Web的环境部署
  • Google 是如何开发 Web 框架的
  • [ 一起学React系列 -- 8 ] React中的文件上传
  • classpath对获取配置文件的影响
  • css的样式优先级
  • Essential Studio for ASP.NET Web Forms 2017 v2,新增自定义树形网格工具栏
  • iOS动画编程-View动画[ 1 ] 基础View动画
  • Java到底能干嘛?
  • JS进阶 - JS 、JS-Web-API与DOM、BOM
  • Promise面试题2实现异步串行执行
  • python 学习笔记 - Queue Pipes,进程间通讯
  • Spring Cloud Feign的两种使用姿势
  • Vue组件定义
  • 编写符合Python风格的对象
  • 将 Measurements 和 Units 应用到物理学
  • 聊聊redis的数据结构的应用
  • 漫谈开发设计中的一些“原则”及“设计哲学”
  • 前端技术周刊 2019-01-14:客户端存储
  • 前端知识点整理(待续)
  • 入口文件开始,分析Vue源码实现
  • 使用iElevator.js模拟segmentfault的文章标题导航
  • 线性表及其算法(java实现)
  • 最简单的无缝轮播
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • Android开发者必备:推荐一款助力开发的开源APP
  • Java数据解析之JSON
  • ​七周四次课(5月9日)iptables filter表案例、iptables nat表应用
  • #QT项目实战(天气预报)
  • #绘制圆心_R语言——绘制一个诚意满满的圆 祝你2021圆圆满满
  • (04)Hive的相关概念——order by 、sort by、distribute by 、cluster by
  • (11)工业界推荐系统-小红书推荐场景及内部实践【粗排三塔模型】
  • (2022 CVPR) Unbiased Teacher v2
  • (附源码)spring boot网络空间安全实验教学示范中心网站 毕业设计 111454
  • (力扣记录)1448. 统计二叉树中好节点的数目
  • (六)软件测试分工
  • (原創) 如何使用ISO C++讀寫BMP圖檔? (C/C++) (Image Processing)
  • (转)C#开发微信门户及应用(1)--开始使用微信接口
  • (转)自己动手搭建Nginx+memcache+xdebug+php运行环境绿色版 For windows版
  • .NET 4.0中的泛型协变和反变
  • .NET Core工程编译事件$(TargetDir)变量为空引发的思考
  • .Net Core和.Net Standard直观理解