Showing preview only (1,230K chars total). Download the full file or copy to clipboard to get everything.
Repository: MonitorControl/MonitorControl
Branch: main
Commit: fc19ce2bd276
Files: 123
Total size: 1.1 MB
Directory structure:
gitextract_6yai_s7z/
├── .bartycrouch.toml
├── .github/
│ ├── .dependabot.yml
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ ├── discussion.yml
│ │ ├── feature_request.yml
│ │ ├── monitor-issue.yml
│ │ └── question.yml
│ ├── MonitorControl Icon.fig
│ └── stale.yml
├── .gitignore
├── .swift-version
├── .swiftformat
├── .swiftlint.yml
├── License.txt
├── MonitorControl/
│ ├── Assets.xcassets/
│ │ ├── AppIcon.appiconset/
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── onboarding_icon_checkmark.imageset/
│ │ │ └── Contents.json
│ │ ├── onboarding_icon_keyboard.imageset/
│ │ │ └── Contents.json
│ │ ├── onboarding_icon_person.imageset/
│ │ │ └── Contents.json
│ │ ├── onboarding_keyboard.imageset/
│ │ │ └── Contents.json
│ │ └── status.imageset/
│ │ └── Contents.json
│ ├── Enums/
│ │ ├── Command.swift
│ │ └── PrefKey.swift
│ ├── Extensions/
│ │ ├── CGDirectDisplayID+Extension.swift
│ │ ├── KeyboardShortcuts+Extension.swift
│ │ ├── NSNotification+Extension.swift
│ │ ├── NSScreen+Extension.swift
│ │ └── Preferences+Extension.swift
│ ├── Info.plist
│ ├── InternetAccessPolicy.plist
│ ├── Model/
│ │ ├── AppleDisplay.swift
│ │ ├── Display.swift
│ │ └── OtherDisplay.swift
│ ├── MonitorControl.entitlements
│ ├── MonitorControlDebug.entitlements
│ ├── Support/
│ │ ├── AppDelegate.swift
│ │ ├── Arm64DDC.swift
│ │ ├── Bridging-Header.h
│ │ ├── DisplayManager.swift
│ │ ├── IntelDDC.swift
│ │ ├── KeyboardShortcutsManager.swift
│ │ ├── MediaKeyTapManager.swift
│ │ ├── MenuHandler.swift
│ │ ├── OSDUtils.swift
│ │ ├── SliderHandler.swift
│ │ └── UpdaterDelegate.swift
│ ├── UI/
│ │ ├── Base.lproj/
│ │ │ └── Main.storyboard
│ │ ├── cs.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ ├── de.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ ├── en.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ ├── es.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ ├── fr.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ ├── hi.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ ├── hu.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ ├── it.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ ├── ja.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ ├── ko.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ ├── nl.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ ├── pl.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ ├── pt-BR.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ ├── pt-PT.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ ├── ru.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ ├── sk.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ ├── tr.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ ├── zh-Hans.lproj/
│ │ │ ├── InternetAccessPolicy.strings
│ │ │ ├── Localizable.strings
│ │ │ └── Main.strings
│ │ └── zh-Hant-TW.lproj/
│ │ ├── InternetAccessPolicy.strings
│ │ ├── Localizable.strings
│ │ └── Main.strings
│ ├── View Controllers/
│ │ ├── Onboarding/
│ │ │ └── OnboardingViewController.swift
│ │ └── Preferences/
│ │ ├── AboutPrefsViewController.swift
│ │ ├── DisplaysPrefsCellView.swift
│ │ ├── DisplaysPrefsViewController.swift
│ │ ├── KeyboardPrefsViewController.swift
│ │ ├── MainPrefsViewController.swift
│ │ └── MenuslidersPrefsViewController.swift
│ └── main.swift
├── MonitorControl.xcodeproj/
│ ├── project.pbxproj
│ ├── project.xcworkspace/
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata/
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm/
│ │ └── Package.resolved
│ └── xcshareddata/
│ └── xcschemes/
│ └── MonitorControl.xcscheme
├── MonitorControlHelper/
│ ├── Info.plist
│ ├── MonitorControlHelper.entitlements
│ └── main.swift
└── README.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .bartycrouch.toml
================================================
[update]
tasks = ["interfaces", "code", "normalize"]
[update.interfaces]
paths = ["."]
defaultToBase = true
ignoreEmptyStrings = true
unstripped = true
[update.code]
codePaths = ["./MonitorControl"]
localizablePaths = ["."]
defaultToKeys = true
additive = true
unstripped = true
plistArguments = true
[update.transform]
codePaths = ["./MonitorControl"]
localizablePaths = ["."]
transformer = "foundation"
supportedLanguageEnumPath = "."
typeName = "BartyCrouch"
translateMethodName = "translate"
[update.normalize]
paths = ["."]
sourceLocale = "en"
harmonizeWithSource = true
sortByKeys = true
[lint]
paths = ["."]
duplicateKeys = true
emptyValues = false
================================================
FILE: .github/.dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "github-actions"
directory: /
schedule:
interval: "weekly"
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
open_collective: monitorcontrol
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug report
description: Create a report to help us improve
labels: ["Bug"]
assignees: []
body:
- type: checkboxes
id: checklist
attributes:
label: Before opening the issue, have you...?
description: This is to help us minimize the amount of duplicate issues, which allows us more time for actual development.
options:
- label: Searched for existing issues
required: true
- label: Looked through [the wiki](https://github.com/MonitorControl/MonitorControl/wiki)
required: true
- label: Updated MonitorControl to the latest version (if applicable)
required: true
- type: textarea
id: description
validations:
required: true
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
placeholder: "Example: When enabling the Show contrast slider in menu option, the application crashes when clicking on the menu icon."
- type: textarea
id: reproduction
validations:
required: true
attributes:
label: Steps to reproduce
description: Please provide some steps on how we can reproduce the problem. This helps us resolve it faster.
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
- type: textarea
id: expected
validations:
required: true
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen.
placeholder: "Example: The app shows a contrast slider when clicking the icon in the menu bar and does not crash."
- type: textarea
validations:
required: false
attributes:
label: Anything else?
description: |
Screenshots? Links? References? Anything that will give us more context about the issue you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
- type: textarea
validations:
required: true
attributes:
label: Environment Information (please complete the following information)
description: |
examples:
- **macOS version**: 11.4 Big Sur
- **Mac model**: MacBook Pro (16-inch, 2019)
- **MonitorControl version**: v2.1.0
- **Monitor(s)**: LG 38GN950, LG 27UN83A
- **Apple Silicon/M1 (yes or no)**: no
value: |
- macOS version:
- Mac model:
- MonitorControl version:
- Monitor(s):
- Apple Silicon/M1 (yes or no): no
render: markdown
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
================================================
FILE: .github/ISSUE_TEMPLATE/discussion.yml
================================================
name: Discussion
description: I want to discuss something that is related to this project
labels: ["Other"]
assignees: []
body:
- type: checkboxes
id: checklist
attributes:
label: Before opening the issue, have you...?
description: This is to help us minimize the amount of duplicate issues, which allows us more time for actual development.
options:
- label: Searched for existing issues
required: true
- type: textarea
id: discussion
validations:
required: true
attributes:
label: Discussion
description: Feel free to type anything you want to discuss related to the project. Do note that we have a seperate template for questions.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature request
description: Suggest an idea for this project
labels: ["Feature Request"]
assignees: []
body:
- type: checkboxes
attributes:
label: Before opening the issue, have you...?
description: This is to help us minimize the amount of duplicate issues, which allows us more time for actual development.
options:
- label: Searched for existing issues
required: true
- type: textarea
validations:
required: true
attributes:
label: Is your feature request related to a problem? Please describe
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
- type: textarea
validations:
required: true
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
- type: textarea
validations:
required: true
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
- type: textarea
validations:
required: false
attributes:
label: Anything else?
description: |
Screenshots? Links? References? Anything that will give us more context about the issue you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
================================================
FILE: .github/ISSUE_TEMPLATE/monitor-issue.yml
================================================
name: Monitor Issue
description: MonitorControl is not working as expected on my monitor.
labels: ["Monitor Issue"]
assignees: []
body:
- type: checkboxes
attributes:
label: Before opening the issue, have you...?
description: This is to help us minimize the amount of duplicate issues, which allows us more time for actual development.
options:
- label: Searched for existing issues
required: true
- label: Read through [the Monitor-Troubleshooting Wiki](https://github.com/MonitorControl/MonitorControl/wiki/Monitor-Troubleshooting)
required: true
- label: Updated MonitorControl to the latest version (if applicable)
required: true
- type: textarea
validations:
required: true
attributes:
label: Describe the issue
description: A clear and concise description of the problem.
placeholder: Volume & Brightness controls are not working on my LG 38GN950.
- type: textarea
validations:
required: true
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen.
- type: textarea
validations:
required: false
attributes:
label: Anything else?
description: |
Screenshots? Links? References? Anything that will give us more context about the issue you are encountering!
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
- type: textarea
validations:
required: false
attributes:
label: Environment Information (please complete the following information)
description: |
examples:
- **macOS version**: 11.4 Big Sur
- **Mac model**: MacBook Pro (16-inch, 2019)
- **MonitorControl version**: v2.1.0
- **Monitor(s)**: LG 38GN950, LG 27UN83A
- **Monitor Cable(s)/Connection(s)**:
- Mac Mini -> DisplayPort 2.1 -> LG 38GN950
- Mac Mini -> Belkin Thunderbolt 3 Dock Core -> LG 27UN83A
- **Apple Silicon/M1 (yes or no)**: no
value: |
- macOS version:
- Mac model:
- MonitorControl version:
- Monitor(s):
- Monitor Cable(s)/Connection(s):
- Apple Silicon/M1 (yes or no):
render: markdown
================================================
FILE: .github/ISSUE_TEMPLATE/question.yml
================================================
name: Question
description: I have a question related to this project
labels: ["Question"]
assignees: []
body:
- type: checkboxes
attributes:
label: Before opening the issue, have you...?
description: This is to help us minimize the amount of duplicate issues, which allows us more time for actual development.
options:
- label: Searched for existing issues
required: true
- type: textarea
validations:
required: true
attributes:
label: Question
description: Ask your question, be specific. The more info you provide, the easier it will be for someone to answer!
================================================
FILE: .github/stale.yml
================================================
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 365
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- 'Priority: Major'
- 'Priority: Critical'
- 'Priority: Minor'
- Information
# Label to use when marking an issue as stale
staleLabel: 'Status: Abandoned'
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
Hey there, it looks like there has been no activity on this issue recently.
Has the issue been fixed, or does it still require attention?
This issue may be closed if no further activity occurs. Thank you for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
================================================
FILE: .gitignore
================================================
### Carthage ###
Carthage
### macOS ###
.DS_Store
### Xcode ###
xcuserdata/
================================================
FILE: .swift-version
================================================
5.5
================================================
FILE: .swiftformat
================================================
--indent 2
--nospaceoperators
--self insert
--exponentcase lowercase
--exclude Carthage
================================================
FILE: .swiftlint.yml
================================================
disabled_rules:
- line_length
- function_body_length
- identifier_name
- trailing_comma
- large_tuple
type_body_length: 500
file_length: 750
cyclomatic_complexity:
ignores_case_statements: true
opening_brace:
allow_multiline_func: true
excluded:
- .build
================================================
FILE: License.txt
================================================
MIT License
Copyright © 2017
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: MonitorControl/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"filename" : "Icon-16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "Icon-33.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "Icon-32.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "Icon-64.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "Icon-128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "Icon-257.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "Icon-256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "Icon-513.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "Icon-512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "Icon-1024.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: MonitorControl/Assets.xcassets/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: MonitorControl/Assets.xcassets/onboarding_icon_checkmark.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "checkmark.png",
"idiom" : "mac",
"scale" : "1x"
},
{
"filename" : "checkmark@2x.png",
"idiom" : "mac",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: MonitorControl/Assets.xcassets/onboarding_icon_keyboard.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "icon_keyboard.png",
"idiom" : "mac",
"scale" : "1x"
},
{
"filename" : "icon_keyboard@2x.png",
"idiom" : "mac",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: MonitorControl/Assets.xcassets/onboarding_icon_person.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "icon_person.png",
"idiom" : "mac",
"scale" : "1x"
},
{
"filename" : "icon_person@2x.png",
"idiom" : "mac",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: MonitorControl/Assets.xcassets/onboarding_keyboard.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "onboarding_keyboard.png",
"idiom" : "mac",
"scale" : "1x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "onboarding_keyboard_dark.png",
"idiom" : "mac",
"scale" : "1x"
},
{
"filename" : "onboarding_keyboard@2x.png",
"idiom" : "mac",
"scale" : "2x"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "onboarding_keyboard_dark@2x.png",
"idiom" : "mac",
"scale" : "2x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: MonitorControl/Assets.xcassets/status.imageset/Contents.json
================================================
{
"images" : [
{
"filename" : "status.pdf",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "status@2x.pdf",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
================================================
FILE: MonitorControl/Enums/Command.swift
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
enum Command: UInt8 {
case none = 0
// Display Control
case horizontalFrequency = 0xAC
case verticalFrequency = 0xAE
case sourceColorCoding = 0xB5
case displayUsageTime = 0xC0
case displayControllerId = 0xC8
case displayFirmwareLevel = 0xC9
case osdLanguage = 0xCC
case powerMode = 0xD6
case imageMode = 0xDB
case vcpVersion = 0xDF
// Geometry
case horizontalPosition = 0x20
case horizontalSize = 0x22
case horizontalPincushion = 0x24
case horizontalPincushionBalance = 0x26
case horizontalConvergenceRB = 0x28
case horizontalConvergenceMG = 0x29
case horizontalLinearity = 0x2A
case horizontalLinearityBalance = 0x2C
case verticalPosition = 0x30
case verticalSize = 0x32
case verticalPincushion = 0x34
case verticalPincushionBalance = 0x36
case verticalConvergenceRB = 0x38
case verticalConvergenceMG = 0x39
case verticalLinearity = 0x3A
case verticalLinearityBalance = 0x3C
case horizontalParallelogram = 0x40
case verticalParallelogram = 0x41
case horizontalKeystone = 0x42
case verticalKeystone = 0x43
case rotation = 0x44
case topCornerFlare = 0x46
case topCornerHook = 0x48
case bottomCornerFlare = 0x4A
case bottomCornerHook = 0x4C
case horizontalMirror = 0x82
case verticalMirror = 0x84
case displayScaling = 0x86
case windowPositionTopLeftX = 0x95
case windowPositionTopLeftY = 0x96
case windowPositionBottomRightX = 0x97
case windowPositionBottomRightY = 0x98
case scanMode = 0xDA
// Miscellaneous
case degauss = 0x01
case newControlValue = 0x02
case softControls = 0x03
case activeControl = 0x52
case performancePreservation = 0x54
case inputSelect = 0x60
case ambientLightSensor = 0x66
case remoteProcedureCall = 0x76
case displayIdentificationOnDataOperation = 0x78
case tvChannelUpDown = 0x8B
case flatPanelSubPixelLayout = 0xB2
case displayTechnologyType = 0xB6
case displayDescriptorLength = 0xC2
case transmitDisplayDescriptor = 0xC3
case enableDisplayOfDisplayDescriptor = 0xC4
case applicationEnableKey = 0xC6
case displayEnableKey = 0xC7
case statusIndicator = 0xCD
case auxiliaryDisplaySize = 0xCE
case auxiliaryDisplayData = 0xCF
case outputSelect = 0xD0
case assetTag = 0xD2
case auxiliaryPowerOutput = 0xD7
case scratchPad = 0xDE
// Audio
case audioSpeakerVolume = 0x62
case speakerSelect = 0x63
case audioMicrophoneVolume = 0x64
case audioJackConnectionStatus = 0x65
case audioMuteScreenBlank = 0x8D
case audioTreble = 0x8F
case audioBass = 0x91
case audioBalanceLR = 0x93
case audioProcessorMode = 0x94
// OSD/Button Event Control
case osd = 0xCA
// Image Adjustment
case sixAxisHueControlBlue = 0x9F
case sixAxisHueControlCyan = 0x9E
case sixAxisHueControlGreen = 0x9D
case sixAxisHueControlMagenta = 0xA0
case sixAxisHueControlRed = 0x9B
case sixAxisHueControlYellow = 0x9C
case sixAxisSaturationControlBlue = 0x5D
case sixAxisSaturationControlCyan = 0x5C
case sixAxisSaturationControlGreen = 0x5B
case sixAxisSaturationControlMagenta = 0x5E
case sixAxisSaturationControlRed = 0x59
case sixAxisSaturationControlYellow = 0x5A
case adjustZoom = 0x7C
case autoColorSetup = 0x1F
case autoSetup = 0x1E
case autoSetupOnOff = 0xA2
case backlightControlLegacy = 0x13
case backlightLevelWhite = 0x6B
case backlightLevelRed = 0x6D
case backlightLevelGreen = 0x6F
case backlightLevelBlue = 0x71
case blockLutOperation = 0x75
case clock = 0x0E
case clockPhase = 0x3E
case colorSaturation = 0x8A
case colorTemperatureIncrement = 0x0B
case colorTemperatureRequest = 0x0C
case contrast = 0x12
case displayApplication = 0xDC
case fleshToneEnhancement = 0x11
case focus = 0x1C
case gamma = 0x72
case grayScaleExpansion = 0x2E
case horizontalMoire = 0x56
case hue = 0x90
case luminance = 0x10
case lutSize = 0x73
case screenOrientation = 0xAA
case selectColorPreset = 0x14
case sharpness = 0x87
case singlePointLutOperation = 0x74
case stereoVideoMode = 0xD4
case tvBlackLevel = 0x92
case tvContrast = 0x8E
case tvSharpness = 0x8C
case userColorVisionCompensation = 0x17
case velocityScanModulation = 0x88
case verticalMoire = 0x58
case videoBlackLevelBlue = 0x70
case videoBlackLevelGreen = 0x6E
case videoBlackLevelRed = 0x6C
case videoGainBlue = 0x1A
case videoGainGreen = 0x18
case videoGainRed = 0x16
case windowBackground = 0x9A
case windowControlOnOff = 0xA4
case windowSelect = 0xA5
case windowSize = 0xA6
case windowTransparency = 0xA7
// Preset Operations
case restoreFactoryDefaults = 0x04
case restoreFactoryLuminanceContrastDefaults = 0x05
case restoreFactoryGeometryDefaults = 0x06
case restoreFactoryColorDefaults = 0x08
case restoreFactoryTvDefaults = 0x0A
case settings = 0xB0
// Manufacturer Specific
case blackStabilizer = 0xF9 // LG 38UC99-W
case colorPresetC = 0xE0
case powerControl = 0xE1
case topLeftScreenPurity = 0xE8
case topRightScreenPurity = 0xE9
case bottomLeftScreenPurity = 0xEA
case bottomRightScreenPurity = 0xEB
public static let brightness = luminance
}
================================================
FILE: MonitorControl/Enums/PrefKey.swift
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
enum PrefKey: String {
/* -- App-wide settings -- */
// Sparkle automatic checks
case SUEnableAutomaticChecks
// Receive beta updates?
case isBetaChannel // This is not added to Settings yet as it will be needed in the future only.
// Build number
case buildNumber
// Was the app launched once
case appAlreadyLaunched
// Hide menu icon
case menuIcon
// Menu item style
case menuItemStyle
// Keys listened for
case keyboardBrightness
// Keys listened for
case keyboardVolume
// Don't listen to F14/F15
case disableAltBrightnessKeys
// Hide brightness sliders
case hideBrightness
// Show volume sliders
case showContrast
// Show volume sliders
case hideVolume
// Lower via software after brightness
case disableCombinedBrightness
// Use separated OSD scale for combined brightness
case separateCombinedScale
// Do not show sliders for Apple displays (including built-in display) in menu
case hideAppleFromMenu
// Disable slider snapping
case enableSliderSnap
// Disable slider snapping
case enableSliderPercent
// Show tick marks for sliders
case showTickMarks
// Instead of assuming default values, enable read or write upon startup (according to readDDCInsteadOfRestoreValues)
case startupAction
// Show advanced options under Displays tab in Settings
case showAdvancedSettings
// Allow zero software brightness
case allowZeroSwBrightness
// Keyboard brightness control for multiple displays
case multiKeyboardBrightness
// Keyboard volume control for multiple devices
case multiKeyboardVolume
// Use fine OSD scale for brightness
case useFineScaleBrightness
// Use fine OSD scale for volume
case useFineScaleVolume
// Use smoothBrightness
case disableSmoothBrightness
// Synchronize brightness from sync source displays among all other displays
case enableBrightnessSync
// Sliders for multiple displays
case multiSliders
/* -- Display specific settings */
// Enable mute DDC for display
case enableMuteUnmute
// Hide OSD for display
case hideOsd
// Longer delay DDC for display
case longerDelay
// DDC polling mode for display
case pollingMode
// DDC polling count for display
case pollingCount
// Display should avoid gamma table manipulation and use shades instead (to coexist with other apps doing gamma manipulation)
case avoidGamma
// User assigned audio device name for display
case audioDeviceNameOverride
// Display disabled for keyboard control
case isDisabled
// Force software mode for display
case forceSw
// Software brightness for display
case SwBrightness
// Combined brightness switching point
case combinedBrightnessSwitchingPoint
// Friendly name
case friendlyName
/* -- Display+Command specific settings -- */
// Command value display
case value
// Was the setting ever changed by the user?
case isTouched
// Min command value display
case minDDCOverride
// Max command value display
case maxDDC
// Max user override command value display
case maxDDCOverride
// Max command value display
case curveDDC
// Is the specific control is set as unavailable for display?
case unavailableDDC
// Invert DDC scale?
case invertDDC
// Override DDC control command code
case remapDDC
}
enum MultiKeyboardBrightness: Int {
case mouse = 0
case allScreens = 1
case focusInsteadOfMouse = 2
}
enum MultiKeyboardVolume: Int {
case mouse = 0
case allScreens = 1
case audioDeviceNameMatching = 2
}
enum StartupAction: Int {
case doNothing = 0
case write = 1
case read = 2
}
enum MultiSliders: Int {
case separate = 0
case relevant = 1
case combine = 2
}
enum PollingMode: Int {
case none = -2
case minimal = -1
case normal = 0
case heavy = 1
case custom = 2
}
enum MenuIcon: Int {
case show = 0
case sliderOnly = 1
case hide = 2
case externalOnly = 3
}
enum MenuItemStyle: Int {
case icon = 0
case text = 1
case hide = 2
}
enum KeyboardBrightness: Int {
case media = 0
case custom = 1
case both = 2
case disabled = 3
}
enum KeyboardVolume: Int {
case media = 0
case custom = 1
case both = 2
case disabled = 3
}
================================================
FILE: MonitorControl/Extensions/CGDirectDisplayID+Extension.swift
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
import Cocoa
public extension CGDirectDisplayID {
var vendorNumber: UInt32? {
CGDisplayVendorNumber(self)
}
var modelNumber: UInt32? {
CGDisplayModelNumber(self)
}
var serialNumber: UInt32? {
CGDisplaySerialNumber(self)
}
}
================================================
FILE: MonitorControl/Extensions/KeyboardShortcuts+Extension.swift
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
import KeyboardShortcuts
extension KeyboardShortcuts.Name {
static let brightnessUp = Self("brightnessUp")
static let brightnessDown = Self("brightnessDown")
static let contrastUp = Self("contrastUp")
static let contrastDown = Self("contrastDown")
static let volumeUp = Self("volumeUp")
static let volumeDown = Self("volumeDown")
static let mute = Self("mute")
static let none = Self("none")
}
================================================
FILE: MonitorControl/Extensions/NSNotification+Extension.swift
================================================
import Cocoa
extension NSNotification.Name {
static let accessibilityApi = NSNotification.Name(rawValue: "com.apple.accessibility.api")
}
================================================
FILE: MonitorControl/Extensions/NSScreen+Extension.swift
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
import Cocoa
public extension NSScreen {
var displayID: CGDirectDisplayID {
(self.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? CGDirectDisplayID)!
}
var vendorNumber: UInt32? {
switch CGDisplayVendorNumber(self.displayID) {
case 0xFFFF_FFFF:
return nil
case let vendorNumber:
return vendorNumber
}
}
var modelNumber: UInt32? {
switch CGDisplayModelNumber(self.displayID) {
case 0xFFFF_FFFF:
return nil
case let modelNumber:
return modelNumber
}
}
var serialNumber: UInt32? {
switch CGDisplaySerialNumber(self.displayID) {
case 0x0000_0000:
return nil
case let serialNumber:
return serialNumber
}
}
var displayName: String? {
var servicePortIterator = io_iterator_t()
let status = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("IODisplayConnect"), &servicePortIterator)
guard status == KERN_SUCCESS else {
return nil
}
defer {
assert(IOObjectRelease(servicePortIterator) == KERN_SUCCESS)
}
while case let object = IOIteratorNext(servicePortIterator), object != 0 {
let dict = (IODisplayCreateInfoDictionary(object, UInt32(kIODisplayOnlyPreferredName)).takeRetainedValue() as NSDictionary as? [String: AnyObject])!
if dict[kDisplayVendorID] as? UInt32 == self.vendorNumber, dict[kDisplayProductID] as? UInt32 == self.modelNumber, dict[kDisplaySerialNumber] as? UInt32 == self.serialNumber {
if let productName = dict["DisplayProductName"] as? [String: String], let firstKey = Array(productName.keys).first {
return productName[firstKey]!
}
}
}
return nil
}
}
================================================
FILE: MonitorControl/Extensions/Preferences+Extension.swift
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
import Cocoa
import Settings
extension Settings.PaneIdentifier {
static let main = Self("Main")
static let menusliders = Self("Menu & Sliders")
static let keyboard = Self("Keyboard")
static let displays = Self("Displays")
static let about = Self("About")
}
public extension SettingsWindowController {
override func keyDown(with event: NSEvent) {
if event.modifierFlags.intersection(.deviceIndependentFlagsMask) == .command, let key = event.charactersIgnoringModifiers {
if key == "w" {
self.close()
}
}
}
}
================================================
FILE: MonitorControl/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>7141</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>MIT Licensed. 2017.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>SUEnableJavaScript</key>
<true/>
<key>SUFeedURL</key>
<string>https://monitorcontrol.app/appcast2.xml</string>
<key>SUPublicEDKey</key>
<string>ITSTMp8AypsLawojJ+UR3tm2mN18AFoNMvXf1G3t62s=</string>
</dict>
</plist>
================================================
FILE: MonitorControl/InternetAccessPolicy.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ApplicationDescription</key>
<string>ApplicationDescription</string>
<key>DeveloperName</key>
<string>MonitorControl</string>
<key>Website</key>
<string>https://monitorcontrol.app</string>
<key>Connections</key>
<array>
<dict>
<key>IsIncoming</key>
<false/>
<key>Host</key>
<string>monitorcontrol.app</string>
<key>NetworkProtocol</key>
<string>TCP</string>
<key>Port</key>
<string>443</string>
<key>Purpose</key>
<string>SoftwareUpdatePurpose</string>
<key>DenyConsequences</key>
<string>SoftwareUpdateDenyConsequences</string>
</dict>
<dict>
<key>IsIncoming</key>
<false/>
<key>Host</key>
<string>github.com</string>
<key>NetworkProtocol</key>
<string>TCP</string>
<key>Port</key>
<string>443</string>
<key>Purpose</key>
<string>SoftwareUpdatePurpose</string>
<key>DenyConsequences</key>
<string>SoftwareUpdateDenyConsequences</string>
</dict>
<dict>
<key>IsIncoming</key>
<false/>
<key>Host</key>
<string>githubusercontent.com</string>
<key>NetworkProtocol</key>
<string>TCP</string>
<key>Port</key>
<string>443</string>
<key>Purpose</key>
<string>SoftwareUpdatePurpose</string>
<key>DenyConsequences</key>
<string>SoftwareUpdateDenyConsequences</string>
</dict>
</array>
</dict>
</plist>
================================================
FILE: MonitorControl/Model/AppleDisplay.swift
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
import Foundation
import os.log
class AppleDisplay: Display {
private var displayQueue: DispatchQueue
override init(_ identifier: CGDirectDisplayID, name: String, vendorNumber: UInt32?, modelNumber: UInt32?, serialNumber: UInt32?, isVirtual: Bool = false, isDummy: Bool = false) {
self.displayQueue = DispatchQueue(label: String("displayQueue-\(identifier)"))
super.init(identifier, name: name, vendorNumber: vendorNumber, modelNumber: modelNumber, serialNumber: serialNumber, isVirtual: isVirtual, isDummy: isDummy)
}
public func getAppleBrightness() -> Float {
guard !self.isDummy else {
return 1
}
var brightness: Float = 0
DisplayServicesGetBrightness(self.identifier, &brightness)
return brightness
}
public func setAppleBrightness(value: Float) {
guard !self.isDummy else {
return
}
_ = self.displayQueue.sync {
DisplayServicesSetBrightness(self.identifier, value)
}
}
override func setDirectBrightness(_ to: Float, transient: Bool = false) -> Bool {
guard !self.isDummy else {
return false
}
let value = max(min(to, 1), 0)
self.setAppleBrightness(value: value)
if !transient {
self.savePref(value, for: .brightness)
self.brightnessSyncSourceValue = value
self.smoothBrightnessTransient = value
}
return true
}
override func getBrightness() -> Float {
guard !self.isDummy else {
return 1
}
if self.prefExists(for: .brightness) {
return self.readPrefAsFloat(for: .brightness)
} else {
return self.getAppleBrightness()
}
}
override func refreshBrightness() -> Float {
guard !self.smoothBrightnessRunning else {
return 0
}
let brightness = self.getAppleBrightness()
let oldValue = self.brightnessSyncSourceValue
self.savePref(brightness, for: .brightness)
if brightness != oldValue {
os_log("Pushing slider and reporting delta for Apple display %{public}@", type: .info, String(self.identifier))
var newValue: Float
if abs(brightness - oldValue) < 0.01 {
newValue = brightness
} else if brightness > oldValue {
newValue = oldValue + max((brightness - oldValue) / 3, 0.005)
} else {
newValue = oldValue + min((brightness - oldValue) / 3, -0.005)
}
self.brightnessSyncSourceValue = newValue
if let sliderHandler = self.sliderHandler[.brightness] {
sliderHandler.setValue(newValue, displayID: self.identifier)
}
return newValue - oldValue
}
return 0
}
}
================================================
FILE: MonitorControl/Model/Display.swift
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
import Cocoa
import Foundation
import os.log
class Display: Equatable {
let identifier: CGDirectDisplayID
let prefsId: String
var name: String
var vendorNumber: UInt32?
var modelNumber: UInt32?
var serialNumber: UInt32?
var smoothBrightnessTransient: Float = 1
var smoothBrightnessRunning: Bool = false
var smoothBrightnessSlow: Bool = false
let swBrightnessSemaphore = DispatchSemaphore(value: 1)
static func == (lhs: Display, rhs: Display) -> Bool {
lhs.identifier == rhs.identifier
}
var sliderHandler: [Command: SliderHandler] = [:]
var brightnessSyncSourceValue: Float = 1
var isVirtual: Bool = false
var isDummy: Bool = false
var defaultGammaTableRed = [CGGammaValue](repeating: 0, count: 256)
var defaultGammaTableGreen = [CGGammaValue](repeating: 0, count: 256)
var defaultGammaTableBlue = [CGGammaValue](repeating: 0, count: 256)
var defaultGammaTableSampleCount: UInt32 = 0
var defaultGammaTablePeak: Float = 1
func prefExists(key: PrefKey? = nil, for command: Command? = nil) -> Bool {
prefs.object(forKey: self.getKey(key: key, for: command)) != nil
}
func removePref(key: PrefKey, for command: Command? = nil) {
prefs.removeObject(forKey: self.getKey(key: key, for: command))
}
func savePref<T>(_ value: T, key: PrefKey? = nil, for command: Command? = nil) {
prefs.set(value, forKey: self.getKey(key: key, for: command))
}
func readPrefAsFloat(key: PrefKey? = nil, for command: Command? = nil) -> Float {
prefs.float(forKey: self.getKey(key: key, for: command))
}
func readPrefAsInt(key: PrefKey? = nil, for command: Command? = nil) -> Int {
prefs.integer(forKey: self.getKey(key: key, for: command))
}
func readPrefAsBool(key: PrefKey? = nil, for command: Command? = nil) -> Bool {
prefs.bool(forKey: self.getKey(key: key, for: command))
}
func readPrefAsString(key: PrefKey? = nil, for command: Command? = nil) -> String {
prefs.string(forKey: self.getKey(key: key, for: command)) ?? ""
}
private func getKey(key: PrefKey? = nil, for command: Command? = nil) -> String {
(key ?? PrefKey.value).rawValue + (command != nil ? String((command ?? Command.none).rawValue) : "") + self.prefsId
}
init(_ identifier: CGDirectDisplayID, name: String, vendorNumber: UInt32?, modelNumber: UInt32?, serialNumber: UInt32?, isVirtual: Bool = false, isDummy: Bool = false) {
self.identifier = identifier
self.name = name
self.vendorNumber = vendorNumber
self.modelNumber = modelNumber
self.serialNumber = serialNumber
self.isVirtual = DEBUG_VIRTUAL ? true : isVirtual
self.isDummy = isDummy
self.prefsId = "(\(name.filter { !$0.isWhitespace })\(vendorNumber ?? 0)\(modelNumber ?? 0)@\(self.isVirtual ? (self.serialNumber ?? 9999) : identifier))"
os_log("Display init with prefsIdentifier %{public}@", type: .info, self.prefsId)
self.swUpdateDefaultGammaTable()
self.smoothBrightnessTransient = self.getBrightness()
if self.isVirtual || self.readPrefAsBool(key: PrefKey.avoidGamma), !self.isDummy {
os_log("Creating or updating shade for display %{public}@", type: .info, String(self.identifier))
_ = DisplayManager.shared.updateShade(displayID: self.identifier)
} else {
os_log("Destroying shade (if exists) for display %{public}@", type: .info, String(self.identifier))
_ = DisplayManager.shared.destroyShade(displayID: self.identifier)
}
self.brightnessSyncSourceValue = self.getBrightness()
}
func calcNewBrightness(isUp: Bool, isSmallIncrement: Bool) -> Float {
var step: Float = (isUp ? 1 : -1) / 16.0
let delta = step / 4
if isSmallIncrement {
step = delta
}
return min(max(0, ceil((self.getBrightness() + delta) / step) * step), 1)
}
func stepBrightness(isUp: Bool, isSmallIncrement: Bool) {
guard !self.readPrefAsBool(key: .unavailableDDC, for: .brightness) else {
return
}
let value = self.calcNewBrightness(isUp: isUp, isSmallIncrement: isSmallIncrement)
if self.setBrightness(value) {
OSDUtils.showOsd(displayID: self.identifier, command: .brightness, value: value * 64, maxValue: 64)
if let slider = self.sliderHandler[.brightness] {
slider.setValue(value, displayID: self.identifier)
self.brightnessSyncSourceValue = value
}
}
}
func setBrightness(_ to: Float = -1, slow: Bool = false) -> Bool {
if !prefs.bool(forKey: PrefKey.disableSmoothBrightness.rawValue) {
return self.setSmoothBrightness(to, slow: slow)
} else {
return self.setDirectBrightness(to)
}
}
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 setDirectBrightness(_ to: Float, transient: Bool = false) -> Bool {
let value = max(min(to, 1), 0)
if self.setSwBrightness(value) {
if !transient {
self.savePref(value, for: .brightness)
self.brightnessSyncSourceValue = value
self.smoothBrightnessTransient = value
}
return true
}
return false
}
func getBrightness() -> Float {
if self.prefExists(for: .brightness) {
return self.readPrefAsFloat(for: .brightness)
} else {
return self.getSwBrightness()
}
}
func swUpdateDefaultGammaTable() {
guard !self.isDummy else {
return
}
CGGetDisplayTransferByTable(self.identifier, 256, &self.defaultGammaTableRed, &self.defaultGammaTableGreen, &self.defaultGammaTableBlue, &self.defaultGammaTableSampleCount)
let redPeak = self.defaultGammaTableRed.max() ?? 0
let greenPeak = self.defaultGammaTableGreen.max() ?? 0
let bluePeak = self.defaultGammaTableBlue.max() ?? 0
self.defaultGammaTablePeak = max(redPeak, greenPeak, bluePeak)
}
func swBrightnessTransform(value: Float, reverse: Bool = false) -> Float {
let lowTreshold: Float = prefs.bool(forKey: PrefKey.allowZeroSwBrightness.rawValue) ? 0.0 : 0.15
if !reverse {
return value * (1 - lowTreshold) + lowTreshold
} else {
return (value - lowTreshold) / (1 - lowTreshold)
}
}
func setSwBrightness(_ value: Float, smooth: Bool = false, noPrefSave: Bool = false) -> Bool {
self.swBrightnessSemaphore.wait()
let brightnessValue = min(1, value)
var currentValue = self.readPrefAsFloat(key: .SwBrightness)
if !noPrefSave {
self.savePref(brightnessValue, key: .SwBrightness)
}
guard !self.isDummy else {
self.swBrightnessSemaphore.signal()
return true
}
var newValue = brightnessValue
currentValue = self.swBrightnessTransform(value: currentValue)
newValue = self.swBrightnessTransform(value: newValue)
if smooth {
DispatchQueue.global(qos: .userInteractive).async {
for transientValue in stride(from: currentValue, to: newValue, by: 0.005 * (currentValue > newValue ? -1 : 1)) {
guard app.reconfigureID == 0 else {
self.swBrightnessSemaphore.signal()
return
}
if self.isVirtual || self.readPrefAsBool(key: .avoidGamma) {
_ = DisplayManager.shared.setShadeAlpha(value: 1 - transientValue, displayID: DisplayManager.resolveEffectiveDisplayID(self.identifier))
} else {
let gammaTableRed = self.defaultGammaTableRed.map { $0 * transientValue }
let gammaTableGreen = self.defaultGammaTableGreen.map { $0 * transientValue }
let gammaTableBlue = self.defaultGammaTableBlue.map { $0 * transientValue }
CGSetDisplayTransferByTable(self.identifier, self.defaultGammaTableSampleCount, gammaTableRed, gammaTableGreen, gammaTableBlue)
}
Thread.sleep(forTimeInterval: 0.001) // Let's make things quick if not performed in the background
}
}
} else {
if self.isVirtual || self.readPrefAsBool(key: .avoidGamma) {
self.swBrightnessSemaphore.signal()
return DisplayManager.shared.setShadeAlpha(value: 1 - newValue, displayID: DisplayManager.resolveEffectiveDisplayID(self.identifier))
} else {
let gammaTableRed = self.defaultGammaTableRed.map { $0 * newValue }
let gammaTableGreen = self.defaultGammaTableGreen.map { $0 * newValue }
let gammaTableBlue = self.defaultGammaTableBlue.map { $0 * newValue }
DisplayManager.shared.moveGammaActivityEnforcer(displayID: self.identifier)
CGSetDisplayTransferByTable(self.identifier, self.defaultGammaTableSampleCount, gammaTableRed, gammaTableGreen, gammaTableBlue)
DisplayManager.shared.enforceGammaActivity()
}
}
self.swBrightnessSemaphore.signal()
return true
}
func getSwBrightness() -> Float {
guard !self.isDummy else {
if self.prefExists(key: .SwBrightness) {
return self.readPrefAsFloat(key: .SwBrightness)
} else {
return 1
}
}
self.swBrightnessSemaphore.wait()
if self.isVirtual || self.readPrefAsBool(key: .avoidGamma) {
let rawBrightnessValue = 1 - (DisplayManager.shared.getShadeAlpha(displayID: DisplayManager.resolveEffectiveDisplayID(self.identifier)) ?? 1)
self.swBrightnessSemaphore.signal()
return self.swBrightnessTransform(value: rawBrightnessValue, reverse: true)
}
var gammaTableRed = [CGGammaValue](repeating: 0, count: 256)
var gammaTableGreen = [CGGammaValue](repeating: 0, count: 256)
var gammaTableBlue = [CGGammaValue](repeating: 0, count: 256)
var gammaTableSampleCount: UInt32 = 0
var brightnessValue: Float = 1
if CGGetDisplayTransferByTable(self.identifier, 256, &gammaTableRed, &gammaTableGreen, &gammaTableBlue, &gammaTableSampleCount) == CGError.success {
let redPeak = gammaTableRed.max() ?? 0
let greenPeak = gammaTableGreen.max() ?? 0
let bluePeak = gammaTableBlue.max() ?? 0
let gammaTablePeak = max(redPeak, greenPeak, bluePeak)
let peakRatio = gammaTablePeak / self.defaultGammaTablePeak
brightnessValue = round(self.swBrightnessTransform(value: peakRatio, reverse: true) * 256) / 256
}
self.swBrightnessSemaphore.signal()
return brightnessValue
}
func checkGammaInterference() {
let currentSwBrightness = self.getSwBrightness()
guard !self.isDummy, !DisplayManager.shared.gammaInterferenceWarningShown, !(prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue)), !self.readPrefAsBool(key: .avoidGamma), !self.isVirtual, !self.smoothBrightnessRunning, self.prefExists(key: .SwBrightness), abs(currentSwBrightness - self.readPrefAsFloat(key: .SwBrightness)) > 0.02 else {
return
}
DisplayManager.shared.gammaInterferenceCounter += 1
_ = self.setSwBrightness(1)
os_log("Gamma table interference detected, number of events: %{public}@", type: .info, String(DisplayManager.shared.gammaInterferenceCounter))
if DisplayManager.shared.gammaInterferenceCounter >= 3 {
DisplayManager.shared.gammaInterferenceWarningShown = true
let alert = NSAlert()
alert.messageText = NSLocalizedString("Is f.lux or similar running?", comment: "Shown in the alert dialog")
alert.informativeText = NSLocalizedString("An other app seems to change the brightness or colors which causes issues.\n\nTo solve this, you need to quit the other app or disable gamma control for your displays in MonitorControl!", comment: "Shown in the alert dialog")
alert.addButton(withTitle: NSLocalizedString("I'll quit the other app", comment: "Shown in the alert dialog"))
alert.addButton(withTitle: NSLocalizedString("Disable gamma control for my displays", comment: "Shown in the alert dialog"))
alert.alertStyle = NSAlert.Style.critical
if alert.runModal() != .alertFirstButtonReturn {
for otherDisplay in DisplayManager.shared.getOtherDisplays() {
_ = otherDisplay.setSwBrightness(1)
_ = otherDisplay.setDirectBrightness(1)
otherDisplay.savePref(true, key: .avoidGamma)
_ = otherDisplay.setSwBrightness(1)
DisplayManager.shared.gammaInterferenceWarningShown = false
DisplayManager.shared.gammaInterferenceCounter = 0
displaysPrefsVc?.loadDisplayList()
}
} else {
os_log("We won't watch for gamma table interference anymore", type: .info)
}
}
}
func resetSwBrightness() -> Bool {
self.setSwBrightness(1)
}
func isSwBrightnessNotDefault() -> Bool {
guard !self.isVirtual, !self.isDummy else {
return false
}
if self.getSwBrightness() < 1 {
return true
}
return false
}
func refreshBrightness() -> Float {
0
}
func isBuiltIn() -> Bool {
if CGDisplayIsBuiltin(self.identifier) != 0 {
return true
} else {
return false
}
}
}
================================================
FILE: MonitorControl/Model/OtherDisplay.swift
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
import Cocoa
import IOKit
import os.log
class OtherDisplay: Display {
var ddc: IntelDDC?
var arm64ddc: Bool = false
var arm64avService: IOAVService?
var isDiscouraged: Bool = false
let writeDDCQueue = DispatchQueue(label: "Local write DDC queue")
var writeDDCNextValue: [Command: UInt16] = [:]
var writeDDCLastSavedValue: [Command: UInt16] = [:]
var pollingCount: Int {
get {
switch self.readPrefAsInt(key: .pollingMode) {
case PollingMode.none.rawValue: return 0
case PollingMode.minimal.rawValue: return 1
case PollingMode.normal.rawValue: return 5
case PollingMode.heavy.rawValue: return 20
case PollingMode.custom.rawValue: return prefs.integer(forKey: PrefKey.pollingCount.rawValue + self.prefsId)
default: return PollingMode.none.rawValue
}
}
set { prefs.set(newValue, forKey: PrefKey.pollingCount.rawValue + self.prefsId) }
}
override init(_ identifier: CGDirectDisplayID, name: String, vendorNumber: UInt32?, modelNumber: UInt32?, serialNumber: UInt32?, isVirtual: Bool = false, isDummy: Bool = false) {
super.init(identifier, name: name, vendorNumber: vendorNumber, modelNumber: modelNumber, serialNumber: serialNumber, isVirtual: isVirtual, isDummy: isDummy)
if !isVirtual, !Arm64DDC.isArm64 {
self.ddc = IntelDDC(for: identifier)
}
}
func processCurrentDDCValue(isReadFromDisplay: Bool, command: Command, firstrun: Bool, currentDDCValue: UInt16) {
if isReadFromDisplay {
var currentValue = self.convDDCToValue(for: command, from: currentDDCValue)
if !prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue), command == .brightness {
os_log("- Combined brightness mapping on DDC data.", type: .info)
if currentValue > 0 {
currentValue = self.combinedBrightnessSwitchingValue() + currentValue * (1 - self.combinedBrightnessSwitchingValue())
} else if currentValue == 0, firstrun, prefs.integer(forKey: PrefKey.startupAction.rawValue) != StartupAction.write.rawValue {
currentValue = self.combinedBrightnessSwitchingValue()
} else if self.prefExists(for: command), self.readPrefAsFloat(for: command) <= self.combinedBrightnessSwitchingValue() {
currentValue = self.readPrefAsFloat(for: command)
} else {
currentValue = self.combinedBrightnessSwitchingValue()
}
}
self.savePref(currentValue, for: command)
if command == .brightness {
self.smoothBrightnessTransient = currentValue
}
} else {
var currentValue: Float = self.readPrefAsFloat(for: command)
if !prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue), command == .brightness {
os_log("- Combined brightness mapping on saved data.", type: .info)
if !self.prefExists(for: command) {
currentValue = self.combinedBrightnessSwitchingValue() + self.convDDCToValue(for: command, from: currentDDCValue) * (1 - self.combinedBrightnessSwitchingValue())
} else if firstrun, currentValue < self.combinedBrightnessSwitchingValue(), prefs.integer(forKey: PrefKey.startupAction.rawValue) != StartupAction.write.rawValue {
currentValue = self.combinedBrightnessSwitchingValue()
}
} else {
currentValue = self.prefExists(for: command) ? self.readPrefAsFloat(for: command) : self.convDDCToValue(for: command, from: currentDDCValue)
}
self.savePref(currentValue, for: command)
if command == .brightness {
self.smoothBrightnessTransient = currentValue
}
}
}
func getDDCValueFromPrefs(_ command: Command) -> UInt16 {
self.convValueToDDC(for: command, from: (!prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue) && command == .brightness) ? max(0, self.readPrefAsFloat(for: command) - self.combinedBrightnessSwitchingValue()) * (1 / (1 - self.combinedBrightnessSwitchingValue())) : self.readPrefAsFloat(for: command))
}
func restoreDDCSettingsToDisplay(command: Command) {
if !self.smoothBrightnessRunning, !self.isSw(), !self.readPrefAsBool(key: .unavailableDDC, for: command), self.readPrefAsBool(key: .isTouched, for: command), prefs.integer(forKey: PrefKey.startupAction.rawValue) == StartupAction.write.rawValue, !app.safeMode {
let restoreValue = self.getDDCValueFromPrefs(command)
os_log("Restoring %{public}@ DDC value %{public}@ for %{public}@", type: .info, String(reflecting: command), String(restoreValue), self.name)
self.writeDDCValues(command: command, value: restoreValue)
if command == .audioSpeakerVolume, self.readPrefAsBool(key: .enableMuteUnmute) {
let currentMuteValue = self.readPrefAsInt(for: .audioMuteScreenBlank) == 0 ? 2 : self.readPrefAsInt(for: .audioMuteScreenBlank)
os_log("- Writing last saved DDC value for Mute: %{public}@", type: .info, String(currentMuteValue))
self.writeDDCValues(command: .audioMuteScreenBlank, value: UInt16(currentMuteValue))
}
}
}
func setupCurrentAndMaxValues(command: Command, firstrun: Bool = false) {
var ddcValues: (UInt16, UInt16)?
var maxDDCValue = UInt16(DDC_MAX_DETECT_LIMIT)
var currentDDCValue: UInt16
switch command {
case .audioSpeakerVolume: currentDDCValue = UInt16(Float(DDC_MAX_DETECT_LIMIT) * 0.125)
case .contrast: currentDDCValue = UInt16(Float(DDC_MAX_DETECT_LIMIT) * 0.750)
default: currentDDCValue = UInt16(Float(DDC_MAX_DETECT_LIMIT) * 1.000)
}
if command == .audioSpeakerVolume {
currentDDCValue = UInt16(Float(DDC_MAX_DETECT_LIMIT) * 0.125) // lower default audio value as high volume might rattle the user.
}
os_log("Setting up display %{public}@ for %{public}@", type: .info, String(self.identifier), String(reflecting: command))
if !self.isSw() {
if prefs.integer(forKey: PrefKey.startupAction.rawValue) == StartupAction.read.rawValue, self.pollingCount != 0, !app.safeMode {
os_log("- Reading DDC from display %{public}@ times", type: .info, String(self.pollingCount))
let delay = self.readPrefAsBool(key: .longerDelay) ? UInt64(40 * kMillisecondScale) : nil
ddcValues = self.readDDCValues(for: command, tries: UInt(self.pollingCount), minReplyDelay: delay)
if ddcValues != nil {
(currentDDCValue, maxDDCValue) = ddcValues ?? (currentDDCValue, maxDDCValue)
self.processCurrentDDCValue(isReadFromDisplay: true, command: command, firstrun: firstrun, currentDDCValue: currentDDCValue)
os_log("- DDC read successful.", type: .info)
} else {
os_log("- DDC read failed.", type: .info)
}
} else {
os_log("- DDC read disabled.", type: .info)
}
if self.readPrefAsInt(key: .maxDDCOverride, for: command) > self.readPrefAsInt(key: .minDDCOverride, for: command) {
self.savePref(self.readPrefAsInt(key: .maxDDCOverride, for: command), key: .maxDDC, for: command)
} else {
self.savePref(min(Int(maxDDCValue), DDC_MAX_DETECT_LIMIT), key: .maxDDC, for: command)
}
if ddcValues == nil {
self.processCurrentDDCValue(isReadFromDisplay: false, command: command, firstrun: firstrun, currentDDCValue: currentDDCValue)
currentDDCValue = self.getDDCValueFromPrefs(command)
}
os_log("- Current DDC value: %{public}@", type: .info, String(currentDDCValue))
os_log("- Minimum DDC value: %{public}@ (overrides 0)", type: .info, String(self.readPrefAsInt(key: .minDDCOverride, for: command)))
os_log("- Maximum DDC value: %{public}@ (overrides %{public}@)", type: .info, String(self.readPrefAsInt(key: .maxDDC, for: command)), String(maxDDCValue))
os_log("- Current internal value: %{public}@", type: .info, String(self.readPrefAsFloat(for: command)))
os_log("- Command status: %{public}@", type: .info, self.readPrefAsBool(key: .isTouched, for: command) ? "Touched" : "Untouched")
if command == .audioSpeakerVolume {
self.setupMuteUnMute()
}
self.restoreDDCSettingsToDisplay(command: command)
} else {
self.savePref(self.prefExists(for: command) ? self.readPrefAsFloat(for: command) : Float(1), for: command)
self.savePref(self.readPrefAsFloat(for: command), key: .SwBrightness)
self.brightnessSyncSourceValue = self.readPrefAsFloat(for: command)
self.smoothBrightnessTransient = self.readPrefAsFloat(for: command)
os_log("- Software controlled display current internal value: %{public}@", type: .info, String(self.readPrefAsFloat(for: command)))
}
}
func setupMuteUnMute() {
guard !self.isSw(), !self.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume), self.readPrefAsBool(key: .enableMuteUnmute) else {
return
}
var currentMuteValue = self.readPrefAsInt(for: .audioMuteScreenBlank) == 0 ? 2 : self.readPrefAsInt(for: .audioMuteScreenBlank)
if self.pollingCount != 0, !app.safeMode, prefs.integer(forKey: PrefKey.startupAction.rawValue) == StartupAction.read.rawValue {
os_log("- Reading DDC from display %{public}@ times for Mute", type: .info, String(self.pollingCount))
let delay = self.readPrefAsBool(key: .longerDelay) ? UInt64(40 * kMillisecondScale) : nil
if let muteValues: (current: UInt16, max: UInt16) = self.readDDCValues(for: .audioMuteScreenBlank, tries: UInt(self.pollingCount), minReplyDelay: delay) {
os_log("- Success, current Mute setting: %{public}@", type: .info, String(muteValues.current))
currentMuteValue = Int(muteValues.current)
} else {
os_log("- Mute read failed", type: .info)
}
}
self.savePref(Int(currentMuteValue), for: .audioMuteScreenBlank)
}
func setupSliderCurrentValue(command: Command) -> Float {
(command == .audioSpeakerVolume && self.readPrefAsBool(key: .enableMuteUnmute) && self.readPrefAsInt(for: .audioMuteScreenBlank) == 1) ? 0 : self.readPrefAsFloat(for: command)
}
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)
}
}
}
func stepContrast(isUp: Bool, isSmallIncrement: Bool) {
guard !self.readPrefAsBool(key: .unavailableDDC, for: .contrast), !self.isSw() else {
return
}
let currentValue = self.readPrefAsFloat(for: .contrast)
let contrastOSDValue = self.calcNewValue(currentValue: currentValue, isUp: isUp, isSmallIncrement: isSmallIncrement)
let isAlreadySet = contrastOSDValue == self.readPrefAsFloat(for: .contrast)
if !isAlreadySet {
self.writeDDCValues(command: .contrast, value: self.convValueToDDC(for: .contrast, from: contrastOSDValue))
}
OSDUtils.showOsd(displayID: self.identifier, command: .contrast, value: contrastOSDValue, roundChiclet: !isSmallIncrement)
if !isAlreadySet {
self.savePref(contrastOSDValue, for: .contrast)
if let slider = self.sliderHandler[.contrast] {
slider.setValue(contrastOSDValue, displayID: self.identifier)
}
}
}
func toggleMute(fromVolumeSlider: Bool = false) {
guard !self.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) else {
OSDUtils.showOsdMuteDisabled(displayID: self.identifier)
return
}
var muteValue: Int
var volumeOSDValue: Float
if self.readPrefAsInt(for: .audioMuteScreenBlank) != 1 {
muteValue = 1
volumeOSDValue = 0
} else {
muteValue = 2
volumeOSDValue = self.readPrefAsFloat(for: .audioSpeakerVolume)
// The volume that will be set immediately after setting unmute while the old set volume was 0 is unpredictable. Hence, just set it to a single filled chiclet
if volumeOSDValue == 0 {
volumeOSDValue = 1 / OSDUtils.chicletCount
self.savePref(volumeOSDValue, for: .audioSpeakerVolume)
}
}
if 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 !fromVolumeSlider {
if !self.readPrefAsBool(key: .hideOsd) {
OSDUtils.showOsd(displayID: self.identifier, command: volumeOSDValue > 0 ? .audioSpeakerVolume : .audioMuteScreenBlank, value: volumeOSDValue, roundChiclet: true)
}
if let slider = self.sliderHandler[.audioSpeakerVolume] {
slider.setValue(volumeOSDValue)
}
}
}
func isSwOnly() -> Bool {
(!self.arm64ddc && self.ddc == nil) || self.isVirtual || self.isDummy
}
func isSw() -> Bool {
if prefs.bool(forKey: PrefKey.forceSw.rawValue + self.prefsId) || self.isSwOnly() {
return true
} else {
return false
}
}
let swAfterOsdAnimationSemaphore = DispatchSemaphore(value: 1)
var lastAnimationStartedTime: CFTimeInterval = CACurrentMediaTime()
func doSwAfterOsdAnimation() {
self.lastAnimationStartedTime = CACurrentMediaTime()
DispatchQueue.global(qos: .userInteractive).async {
self.swAfterOsdAnimationSemaphore.wait()
guard CACurrentMediaTime() < self.lastAnimationStartedTime + 0.05 else {
self.swAfterOsdAnimationSemaphore.signal()
return
}
for value: Int in stride(from: 1, to: 6, by: 1) {
guard self.readPrefAsFloat(for: .brightness) <= self.combinedBrightnessSwitchingValue() else {
self.swAfterOsdAnimationSemaphore.signal()
return
}
OSDUtils.showOsd(displayID: self.identifier, command: .brightness, value: Float(value), maxValue: 100, roundChiclet: false)
Thread.sleep(forTimeInterval: Double(value * 2) / 300)
}
for value: Int in stride(from: 5, to: 0, by: -1) {
guard self.readPrefAsFloat(for: .brightness) <= self.combinedBrightnessSwitchingValue() else {
self.swAfterOsdAnimationSemaphore.signal()
return
}
OSDUtils.showOsd(displayID: self.identifier, command: .brightness, value: Float(value), maxValue: 100, roundChiclet: false)
Thread.sleep(forTimeInterval: Double(value * 2) / 300)
}
OSDUtils.showOsd(displayID: self.identifier, command: .brightness, value: 0, roundChiclet: true)
self.swAfterOsdAnimationSemaphore.signal()
}
}
override func stepBrightness(isUp: Bool, isSmallIncrement: Bool) {
if self.isSw() {
super.stepBrightness(isUp: isUp, isSmallIncrement: isSmallIncrement)
return
}
guard !self.readPrefAsBool(key: .unavailableDDC, for: .brightness) else {
return
}
let currentValue = self.readPrefAsFloat(for: .brightness)
var osdValue: Float = 1
if !prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue), prefs.bool(forKey: PrefKey.separateCombinedScale.rawValue) {
osdValue = self.calcNewValue(currentValue: currentValue, isUp: isUp, isSmallIncrement: isSmallIncrement, half: true)
_ = self.setBrightness(osdValue)
if osdValue > self.combinedBrightnessSwitchingValue() {
OSDUtils.showOsd(displayID: self.identifier, command: .brightness, value: osdValue - self.combinedBrightnessSwitchingValue(), maxValue: self.combinedBrightnessSwitchingValue(), roundChiclet: !isSmallIncrement)
} else {
self.doSwAfterOsdAnimation()
}
} else {
osdValue = self.calcNewValue(currentValue: currentValue, isUp: isUp, isSmallIncrement: isSmallIncrement)
_ = self.setBrightness(osdValue)
OSDUtils.showOsd(displayID: self.identifier, command: .brightness, value: osdValue, roundChiclet: !isSmallIncrement)
}
if let slider = self.sliderHandler[.brightness] {
slider.setValue(osdValue, displayID: self.identifier)
self.brightnessSyncSourceValue = osdValue
}
}
override func setBrightness(_ to: Float = -1, slow: Bool = false) -> Bool {
self.checkGammaInterference()
return super.setBrightness(to, slow: slow)
}
override func setDirectBrightness(_ to: Float, transient: Bool = false) -> Bool {
let value = max(min(to, 1), 0)
if !self.isSw() {
if !prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue) {
var brightnessValue: Float = 0
var brightnessSwValue: Float = 1
if value >= self.combinedBrightnessSwitchingValue() {
brightnessValue = (value - self.combinedBrightnessSwitchingValue()) * (1 / (1 - self.combinedBrightnessSwitchingValue()))
brightnessSwValue = 1
} else {
brightnessValue = 0
brightnessSwValue = (value / self.combinedBrightnessSwitchingValue())
}
self.writeDDCValues(command: .brightness, value: self.convValueToDDC(for: .brightness, from: brightnessValue))
if self.readPrefAsFloat(key: .SwBrightness) != brightnessSwValue {
_ = self.setSwBrightness(brightnessSwValue)
}
} else {
self.writeDDCValues(command: .brightness, value: self.convValueToDDC(for: .brightness, from: value))
}
if !transient {
self.savePref(value, for: .brightness)
self.smoothBrightnessTransient = value
}
} else {
_ = super.setDirectBrightness(to, transient: transient)
}
return true
}
override func getBrightness() -> Float {
self.prefExists(for: .brightness) ? self.readPrefAsFloat(for: .brightness) : 1
}
func getRemapControlCodes(command: Command) -> [UInt8] {
let codes = self.readPrefAsString(key: PrefKey.remapDDC, for: command).components(separatedBy: ",")
var intCodes: [UInt8] = []
for code in codes {
let trimmedCode = code.trimmingCharacters(in: CharacterSet(charactersIn: " "))
if !trimmedCode.isEmpty, let intCode = UInt8(trimmedCode, radix: 16), intCode != 0 {
intCodes.append(intCode)
}
}
return intCodes
}
public func writeDDCValues(command: Command, value: UInt16) {
guard app.sleepID == 0, app.reconfigureID == 0, !self.readPrefAsBool(key: .forceSw), !self.readPrefAsBool(key: .unavailableDDC, for: command) else {
return
}
self.writeDDCQueue.async(flags: .barrier) {
self.writeDDCNextValue[command] = value
}
DisplayManager.shared.globalDDCQueue.async(flags: .barrier) {
self.asyncPerformWriteDDCValues(command: command)
}
}
func asyncPerformWriteDDCValues(command: Command) {
var value = UInt16.max
var lastValue = UInt16.max
self.writeDDCQueue.sync {
value = self.writeDDCNextValue[command] ?? UInt16.max
lastValue = self.writeDDCLastSavedValue[command] ?? UInt16.max
}
guard value != UInt16.max, value != lastValue else {
return
}
self.writeDDCQueue.async(flags: .barrier) {
self.writeDDCLastSavedValue[command] = value
self.savePref(true, key: PrefKey.isTouched, for: command)
}
var controlCodes = self.getRemapControlCodes(command: command)
if controlCodes.count == 0 {
controlCodes.append(command.rawValue)
}
for controlCode in controlCodes {
if Arm64DDC.isArm64 {
if self.arm64ddc {
_ = Arm64DDC.write(service: self.arm64avService, command: controlCode, value: value)
}
} else {
_ = self.ddc?.write(command: controlCode, value: value, errorRecoveryWaitTime: 2000) ?? false
}
}
}
func readDDCValues(for command: Command, tries: UInt, minReplyDelay delay: UInt64?) -> (current: UInt16, max: UInt16)? {
var values: (UInt16, UInt16)?
guard app.sleepID == 0, app.reconfigureID == 0, !self.readPrefAsBool(key: .forceSw), !self.readPrefAsBool(key: .unavailableDDC, for: command) else {
return values
}
let controlCodes = self.getRemapControlCodes(command: command)
let controlCode = controlCodes.count == 0 ? command.rawValue : controlCodes[0]
if Arm64DDC.isArm64 {
guard self.arm64ddc else {
return nil
}
DisplayManager.shared.globalDDCQueue.sync {
if let unwrappedDelay = delay {
values = Arm64DDC.read(service: self.arm64avService, command: controlCode, readSleepTime: UInt32(unwrappedDelay / 1000), numOfRetryAttemps: UInt8(min(tries, 255)))
} else {
values = Arm64DDC.read(service: self.arm64avService, command: controlCode, numOfRetryAttemps: UInt8(min(tries, 255)))
}
}
} else {
DisplayManager.shared.globalDDCQueue.sync {
values = self.ddc?.read(command: controlCode, tries: tries, minReplyDelay: delay)
}
}
return values
}
func calcNewValue(currentValue: Float, isUp: Bool, isSmallIncrement: Bool, half: Bool = false) -> Float {
let nextValue: Float
if isSmallIncrement {
nextValue = currentValue + (isUp ? 0.01 : -0.01)
} else {
let osdChicletFromValue = OSDUtils.chiclet(fromValue: currentValue, maxValue: 1, half: half)
let distance = OSDUtils.getDistance(fromNearestChiclet: osdChicletFromValue)
var nextFilledChiclet = isUp ? ceil(osdChicletFromValue) : floor(osdChicletFromValue)
let distanceThreshold: Float = 0.25 // 25% of the distance between the edges of an osd box
if distance == 0 {
nextFilledChiclet += (isUp ? 1 : -1)
} else if !isUp, distance < distanceThreshold {
nextFilledChiclet -= 1
} else if isUp, distance > (1 - distanceThreshold) {
nextFilledChiclet += 1
}
nextValue = OSDUtils.value(fromChiclet: nextFilledChiclet, maxValue: 1, half: half)
}
return max(0, min(1, nextValue))
}
func getCurveMultiplier(_ curveDDC: Int) -> Float {
switch curveDDC {
case 1: return 0.6
case 2: return 0.7
case 3: return 0.8
case 4: return 0.9
case 6: return 1.3
case 7: return 1.5
case 8: return 1.7
case 9: return 1.88
default: return 1.0
}
}
func convValueToDDC(for command: Command, from: Float) -> UInt16 {
var value = from
if self.readPrefAsBool(key: .invertDDC, for: command) {
value = 1 - value
}
let curveMultiplier = self.getCurveMultiplier(self.readPrefAsInt(key: .curveDDC, for: command))
let minDDCValue = Float(self.readPrefAsInt(key: .minDDCOverride, for: command))
let maxDDCValue = Float(self.readPrefAsInt(key: .maxDDC, for: command))
let curvedValue = pow(max(min(value, 1), 0), curveMultiplier)
let deNormalizedValue = (maxDDCValue - minDDCValue) * curvedValue + minDDCValue
var intDDCValue = UInt16(min(max(deNormalizedValue, minDDCValue), maxDDCValue))
if from > 0, command == Command.audioSpeakerVolume {
intDDCValue = max(1, intDDCValue) // Never let sound to mute accidentally, keep it digitally to at digital 1 if needed as muting breaks some displays
}
return intDDCValue
}
func convDDCToValue(for command: Command, from: UInt16) -> Float {
let curveMultiplier = self.getCurveMultiplier(self.readPrefAsInt(key: .curveDDC, for: command))
let minDDCValue = Float(self.readPrefAsInt(key: .minDDCOverride, for: command))
let maxDDCValue = Float(self.readPrefAsInt(key: .maxDDC, for: command))
let normalizedValue = ((min(max(Float(from), minDDCValue), maxDDCValue) - minDDCValue) / (maxDDCValue - minDDCValue))
let deCurvedValue = pow(normalizedValue, 1.0 / curveMultiplier)
var value = deCurvedValue
if self.readPrefAsBool(key: .invertDDC, for: command) {
value = 1 - value
}
return max(min(value, 1), 0)
}
func combinedBrightnessSwitchingValue() -> Float {
Float(self.readPrefAsInt(key: .combinedBrightnessSwitchingPoint) + 8) / 16
}
}
================================================
FILE: MonitorControl/MonitorControl.entitlements
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
</dict>
</plist>
================================================
FILE: MonitorControl/MonitorControlDebug.entitlements
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
================================================
FILE: MonitorControl/Support/AppDelegate.swift
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
import AVFoundation
import Cocoa
import Foundation
import MediaKeyTap
import os.log
import ServiceManagement
import Settings
import SimplyCoreAudio
import Sparkle
class AppDelegate: NSObject, NSApplicationDelegate {
let statusItem: NSStatusItem = {
let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
item.behavior = .removalAllowed
return item
}()
var mediaKeyTap = MediaKeyTapManager()
var keyboardShortcuts = KeyboardShortcutsManager()
let coreAudio = SimplyCoreAudio()
var accessibilityObserver: NSObjectProtocol!
var statusItemObserver: NSObjectProtocol!
var statusItemVisibilityChangedByUser = true
var reconfigureID: Int = 0 // dispatched reconfigure command ID
var sleepID: Int = 0 // sleep event ID
var safeMode = false
var jobRunning = false
var startupActionWriteCounter: Int = 0
var audioPlayer: AVAudioPlayer?
let updaterController = SPUStandardUpdaterController(startingUpdater: false, updaterDelegate: UpdaterDelegate(), userDriverDelegate: nil)
var settingsPaneStyle: Settings.Style {
if !DEBUG_MACOS10, #available(macOS 11.0, *) {
return Settings.Style.toolbarItems
} else {
return Settings.Style.segmentedControl
}
}
lazy var settingsWindowController: SettingsWindowController = .init(
panes: [
mainPrefsVc!,
menuslidersPrefsVc!,
keyboardPrefsVc!,
displaysPrefsVc!,
aboutPrefsVc!,
],
style: self.settingsPaneStyle,
animated: true
)
func applicationDidFinishLaunching(_: Notification) {
app = self
self.subscribeEventListeners()
self.showSafeModeAlertIfNeeded()
if !prefs.bool(forKey: PrefKey.appAlreadyLaunched.rawValue) {
self.showOnboardingWindow()
} else {
self.checkPermissions()
}
self.setPrefsBuildNumber()
self.setDefaultPrefs()
self.setMenu()
CGDisplayRegisterReconfigurationCallback({ _, _, _ in app.displayReconfigured() }, nil)
self.configure(firstrun: true)
DisplayManager.shared.createGammaActivityEnforcer()
self.updaterController.startUpdater()
}
@objc func quitClicked(_: AnyObject) {
os_log("Quit clicked", type: .info)
menu.closeMenu()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
NSApplication.shared.terminate(self)
}
}
@objc func prefsClicked(_: AnyObject) {
os_log("Settings clicked", type: .info)
self.settingsWindowController.show()
}
func applicationShouldHandleReopen(_: NSApplication, hasVisibleWindows _: Bool) -> Bool {
app.prefsClicked(self)
return true
}
func applicationWillTerminate(_: Notification) {
os_log("Goodbye!", type: .info)
DisplayManager.shared.resetSwBrightnessForAllDisplays(noPrefSave: true)
self.updateStatusItemVisibility(true)
}
private func setPrefsBuildNumber() {
let currentBuildNumber = Int(Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "1") ?? 1
let previousBuildNumber: Int = (Int(prefs.string(forKey: PrefKey.buildNumber.rawValue) ?? "0") ?? 0)
if self.safeMode || ((previousBuildNumber < MIN_PREVIOUS_BUILD_NUMBER) && previousBuildNumber > 0) || (previousBuildNumber > currentBuildNumber), let bundleID = Bundle.main.bundleIdentifier {
if !self.safeMode {
let alert = NSAlert()
alert.messageText = NSLocalizedString("Incompatible previous version", comment: "Shown in the alert dialog")
alert.informativeText = NSLocalizedString("Settings for an incompatible previous app version detected. Default settings are reloaded.", comment: "Shown in the alert dialog")
alert.runModal()
}
prefs.removePersistentDomain(forName: bundleID)
}
prefs.set(currentBuildNumber, forKey: PrefKey.buildNumber.rawValue)
}
func setDefaultPrefs() {
if !prefs.bool(forKey: PrefKey.appAlreadyLaunched.rawValue) {
// Only settings that are not false, 0 or "" by default are set here. Assumes pre-wiped database.
prefs.set(true, forKey: PrefKey.appAlreadyLaunched.rawValue)
prefs.set(true, forKey: PrefKey.SUEnableAutomaticChecks.rawValue)
}
}
@objc func displayReconfigured() {
DisplayManager.shared.resetSwBrightnessForAllDisplays(noPrefSave: true)
CGDisplayRestoreColorSyncSettings()
self.reconfigureID += 1
self.updateMediaKeyTap()
os_log("Bumping reconfigureID to %{public}@", type: .info, String(self.reconfigureID))
_ = DisplayManager.shared.destroyAllShades()
if self.sleepID == 0 {
let dispatchedReconfigureID = self.reconfigureID
os_log("Display to be reconfigured with reconfigureID %{public}@", type: .info, String(dispatchedReconfigureID))
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.configure(dispatchedReconfigureID: dispatchedReconfigureID)
}
}
}
func configure(dispatchedReconfigureID: Int = 0, firstrun: Bool = false) {
guard self.sleepID == 0, dispatchedReconfigureID == self.reconfigureID else {
return
}
os_log("Request for configuration with reconfigreID %{public}@", type: .info, String(dispatchedReconfigureID))
self.reconfigureID = 0
DisplayManager.shared.gammaInterferenceCounter = 0
DisplayManager.shared.configureDisplays()
DisplayManager.shared.addDisplayCounterSuffixes()
DisplayManager.shared.updateArm64AVServices()
if firstrun && prefs.integer(forKey: PrefKey.startupAction.rawValue) != StartupAction.write.rawValue {
DisplayManager.shared.resetSwBrightnessForAllDisplays(prefsOnly: true)
}
DisplayManager.shared.setupOtherDisplays(firstrun: firstrun)
self.updateMenusAndKeys()
if !firstrun || prefs.integer(forKey: PrefKey.startupAction.rawValue) == StartupAction.write.rawValue {
if !prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue) {
DisplayManager.shared.restoreSwBrightnessForAllDisplays(async: !prefs.bool(forKey: PrefKey.disableSmoothBrightness.rawValue))
}
}
displaysPrefsVc?.loadDisplayList()
self.job(start: true)
}
func updateMenusAndKeys() {
menu.updateMenus()
self.updateMediaKeyTap()
}
func checkPermissions(firstAsk: Bool = false) {
let permissionsRequired: Bool = [KeyboardVolume.media.rawValue, KeyboardVolume.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardVolume.rawValue)) || [KeyboardBrightness.media.rawValue, KeyboardBrightness.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardBrightness.rawValue))
if !MediaKeyTapManager.readPrivileges(prompt: false), permissionsRequired {
MediaKeyTapManager.acquirePrivileges(firstAsk: firstAsk)
}
}
private func subscribeEventListeners() {
NotificationCenter.default.addObserver(self, selector: #selector(self.audioDeviceChanged), name: Notification.Name.defaultOutputDeviceChanged, object: nil) // subscribe Audio output detector (SimplyCoreAudio)
DistributedNotificationCenter.default.addObserver(self, selector: #selector(self.displayReconfigured), name: NSNotification.Name(rawValue: kColorSyncDisplayDeviceProfilesNotification.takeRetainedValue() as String), object: nil) // ColorSync change
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.sleepNotification), name: NSWorkspace.screensDidSleepNotification, object: nil) // sleep and wake listeners
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.wakeNotification), name: NSWorkspace.screensDidWakeNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.sleepNotification), name: NSWorkspace.willSleepNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.wakeNotification), name: NSWorkspace.didWakeNotification, object: nil)
_ = DistributedNotificationCenter.default().addObserver(forName: NSNotification.Name(rawValue: NSNotification.Name.accessibilityApi.rawValue), object: nil, queue: nil) { _ in DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.updateMediaKeyTap() } } // listen for accessibility status changes
self.statusItemObserver = statusItem.observe(\.isVisible, options: [.old, .new]) { _, _ in self.statusItemVisibilityChanged() }
}
@objc private func sleepNotification() {
self.sleepID += 1
os_log("Sleeping with sleep %{public}@", type: .info, String(self.sleepID))
self.updateMediaKeyTap()
}
@objc private func wakeNotification() {
if self.sleepID != 0 {
os_log("Waking up from sleep %{public}@", type: .info, String(self.sleepID))
let dispatchedSleepID = self.sleepID
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { // Some displays take time to recover...
self.soberNow(dispatchedSleepID: dispatchedSleepID)
}
}
}
private func soberNow(dispatchedSleepID: Int) {
if self.sleepID == dispatchedSleepID {
os_log("Sober from sleep %{public}@", type: .info, String(self.sleepID))
self.sleepID = 0
if self.reconfigureID != 0 {
let dispatchedReconfigureID = self.reconfigureID
os_log("Displays need reconfig after sober with reconfigureID %{public}@", type: .info, String(dispatchedReconfigureID))
self.configure(dispatchedReconfigureID: dispatchedReconfigureID)
} else if Arm64DDC.isArm64 {
os_log("Displays don't need reconfig after sober but might need AVServices update", type: .info)
DisplayManager.shared.updateArm64AVServices()
self.job(start: true)
}
self.startupActionWriteRepeatAfterSober()
self.updateMediaKeyTap()
}
}
private func startupActionWriteRepeatAfterSober(dispatchedCounter: Int = 0) {
let counter = dispatchedCounter == 0 ? 10 : dispatchedCounter
self.startupActionWriteCounter = dispatchedCounter == 0 ? counter : self.startupActionWriteCounter
guard prefs.integer(forKey: PrefKey.startupAction.rawValue) == StartupAction.write.rawValue, self.startupActionWriteCounter == counter else {
return
}
os_log("Sober write action repeat for DDC - %{public}@", type: .info, String(counter))
DisplayManager.shared.restoreOtherDisplays()
self.startupActionWriteCounter = counter - 1
if counter > 1 {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.startupActionWriteRepeatAfterSober(dispatchedCounter: counter - 1)
}
}
}
private func job(start: Bool = false) {
guard !(self.jobRunning && start) else {
return
}
if self.sleepID == 0, self.reconfigureID == 0 {
if !self.jobRunning {
os_log("MonitorControl job started.", type: .info)
self.jobRunning = true
}
var refreshedSomething = false
for display in DisplayManager.shared.displays {
let delta = display.refreshBrightness()
if delta != 0 {
refreshedSomething = true
if prefs.bool(forKey: PrefKey.enableBrightnessSync.rawValue) {
for targetDisplay in DisplayManager.shared.displays where targetDisplay != display {
os_log("Updating delta from display %{public}@ to display %{public}@", type: .info, String(display.identifier), String(targetDisplay.identifier))
let newValue = max(0, min(1, targetDisplay.getBrightness() + delta))
_ = targetDisplay.setBrightness(newValue)
if let slider = targetDisplay.sliderHandler[.brightness] {
slider.setValue(newValue, displayID: targetDisplay.identifier)
}
}
}
}
}
let nextRefresh = refreshedSomething ? 0.1 : 1.0
DispatchQueue.main.asyncAfter(deadline: .now() + nextRefresh) {
self.job()
}
} else {
self.jobRunning = false
os_log("MonitorControl job died because of sleep or reconfiguration.", type: .info)
}
}
func handleListenForChanged() {
self.checkPermissions()
self.updateMediaKeyTap()
}
func settingsReset() {
os_log("Resetting all settings.")
if !prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue) {
DisplayManager.shared.resetSwBrightnessForAllDisplays(async: false)
}
if let bundleID = Bundle.main.bundleIdentifier {
prefs.removePersistentDomain(forName: bundleID)
}
app.updateStatusItemVisibility(true)
self.setDefaultPrefs()
self.checkPermissions()
self.updateMediaKeyTap()
self.configure(firstrun: true)
}
@objc func audioDeviceChanged() {
if let defaultDevice = self.coreAudio.defaultOutputDevice {
os_log("Default output device changed to “%{public}@”.", type: .info, defaultDevice.name)
os_log("Can device set its own volume? %{public}@", type: .info, defaultDevice.canSetVirtualMainVolume(scope: .output).description)
}
self.updateMediaKeyTap()
}
func updateMediaKeyTap() {
MediaKeyTap.useAlternateBrightnessKeys = !prefs.bool(forKey: PrefKey.disableAltBrightnessKeys.rawValue)
self.mediaKeyTap.updateMediaKeyTap()
}
func setStartAtLogin(enabled: Bool) {
let identifier = "\(Bundle.main.bundleIdentifier!)Helper" as CFString
SMLoginItemSetEnabled(identifier, enabled)
}
func getSystemSettings() -> [String: AnyObject]? {
var propertyListFormat = PropertyListSerialization.PropertyListFormat.xml
let plistPath = NSString(string: "~/Library/Preferences/.GlobalPreferences.plist").expandingTildeInPath
guard let plistXML = FileManager.default.contents(atPath: plistPath) else {
return nil
}
do {
return try PropertyListSerialization.propertyList(from: plistXML, options: .mutableContainersAndLeaves, format: &propertyListFormat) as? [String: AnyObject]
} catch {
os_log("Error reading system prefs plist: %{public}@", type: .info, error.localizedDescription)
return nil
}
}
func macOS10() -> Bool {
if !DEBUG_MACOS10, #available(macOS 11.0, *) {
return false
} else {
return true
}
}
func playVolumeChangedSound() {
guard let settings = app.getSystemSettings(), let hasSoundEnabled = settings["com.apple.sound.beep.feedback"] as? Int, hasSoundEnabled == 1 else {
return
}
do {
self.audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: "/System/Library/LoginPlugins/BezelServices.loginPlugin/Contents/Resources/volume.aiff"))
self.audioPlayer?.volume = 1
self.audioPlayer?.play()
} catch {
os_log("%{public}@", type: .error, error.localizedDescription)
}
}
private func setMenu() {
menu = MenuHandler()
menu.delegate = menu
self.statusItem.button?.image = NSImage(named: "status")
self.statusItem.menu = menu
}
private func showSafeModeAlertIfNeeded() {
if NSEvent.modifierFlags.contains(NSEvent.ModifierFlags.shift) {
self.safeMode = true
let alert = NSAlert()
alert.messageText = NSLocalizedString("Safe Mode Activated", comment: "Shown in the alert dialog")
alert.informativeText = NSLocalizedString("Shift was pressed during launch. MonitorControl started in safe mode. Default settings are reloaded, DDC read is blocked.", comment: "Shown in the alert dialog")
alert.runModal()
}
}
private func showOnboardingWindow() {
onboardingVc?.showWindow(self)
onboardingVc?.window?.center()
NSApp.activate(ignoringOtherApps: true)
}
private func statusItemVisibilityChanged() {
if !self.statusItem.isVisible, self.statusItemVisibilityChangedByUser {
prefs.set(MenuIcon.hide.rawValue, forKey: PrefKey.menuIcon.rawValue)
}
}
func updateStatusItemVisibility(_ visible: Bool) {
statusItemVisibilityChangedByUser = false
statusItem.isVisible = visible
statusItemVisibilityChangedByUser = true
}
}
================================================
FILE: MonitorControl/Support/Arm64DDC.swift
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
import Foundation
import IOKit
let ARM64_DDC_7BIT_ADDRESS: UInt8 = 0x37 // This works with DisplayPort devices
let ARM64_DDC_DATA_ADDRESS: UInt8 = 0x51
class Arm64DDC: NSObject {
#if arch(arm64)
public static let isArm64: Bool = true
#else
public static let isArm64: Bool = false
#endif
static let MAX_MATCH_SCORE: Int = 20
struct IOregService {
var edidUUID: String = ""
var manufacturerID: String = ""
var productName: String = ""
var serialNumber: Int64 = 0
var alphanumericSerialNumber: String = ""
var location: String = ""
var ioDisplayLocation: String = ""
var transportUpstream: String = ""
var transportDownstream: String = ""
var service: IOAVService?
var serviceLocation: Int = 0
var displayAttributes: NSDictionary?
}
struct Arm64Service {
var displayID: CGDirectDisplayID = 0
var service: IOAVService?
var serviceLocation: Int = 0
var discouraged: Bool = false
var dummy: Bool = false
var serviceDetails: IOregService
var matchScore: Int = 0
}
static func getServiceMatches(displayIDs: [CGDirectDisplayID]) -> [Arm64Service] {
let ioregServicesForMatching = self.getIoregServicesForMatching()
var matchedDisplayServices: [Arm64Service] = []
var scoredCandidateDisplayServices: [Int: [Arm64Service]] = [:]
for displayID in displayIDs {
for ioregServiceForMatching in ioregServicesForMatching {
let score = self.ioregMatchScore(displayID: displayID, ioregEdidUUID: ioregServiceForMatching.edidUUID, ioDisplayLocation: ioregServiceForMatching.ioDisplayLocation, ioregProductName: ioregServiceForMatching.productName, ioregSerialNumber: ioregServiceForMatching.serialNumber, serviceLocation: ioregServiceForMatching.serviceLocation)
let discouraged = self.checkIfDiscouraged(ioregService: ioregServiceForMatching)
let dummy = self.checkIfDummy(ioregService: ioregServiceForMatching)
let displayService = Arm64Service(displayID: displayID, service: ioregServiceForMatching.service, serviceLocation: ioregServiceForMatching.serviceLocation, discouraged: discouraged, dummy: dummy, serviceDetails: ioregServiceForMatching, matchScore: score)
if scoredCandidateDisplayServices[score] == nil {
scoredCandidateDisplayServices[score] = []
}
scoredCandidateDisplayServices[score]?.append(displayService)
}
}
var takenServiceLocations: [Int] = []
var takenDisplayIDs: [CGDirectDisplayID] = []
for score in stride(from: self.MAX_MATCH_SCORE, to: 0, by: -1) {
if let scoredCandidateDisplayService = scoredCandidateDisplayServices[score] {
for candidateDisplayService in scoredCandidateDisplayService where !(takenDisplayIDs.contains(candidateDisplayService.displayID) || takenServiceLocations.contains(candidateDisplayService.serviceLocation)) {
takenDisplayIDs.append(candidateDisplayService.displayID)
takenServiceLocations.append(candidateDisplayService.serviceLocation)
matchedDisplayServices.append(candidateDisplayService)
}
}
}
return matchedDisplayServices
}
static func read(service: IOAVService?, command: UInt8, writeSleepTime: UInt32? = nil, numOfWriteCycles: UInt8? = nil, readSleepTime: UInt32? = nil, numOfRetryAttemps: UInt8? = nil, retrySleepTime: UInt32? = nil) -> (current: UInt16, max: UInt16)? {
var values: (UInt16, UInt16)?
var send: [UInt8] = [command]
var reply = [UInt8](repeating: 0, count: 11)
if Self.performDDCCommunication(service: service, send: &send, reply: &reply, writeSleepTime: writeSleepTime, numOfWriteCycles: numOfWriteCycles, readSleepTime: readSleepTime, numOfRetryAttemps: numOfRetryAttemps, retrySleepTime: retrySleepTime) {
let max = UInt16(reply[6]) * 256 + UInt16(reply[7])
let current = UInt16(reply[8]) * 256 + UInt16(reply[9])
values = (current, max)
} else {
values = nil
}
return values
}
static func write(service: IOAVService?, command: UInt8, value: UInt16, writeSleepTime: UInt32? = nil, numOfWriteCycles: UInt8? = nil, numOfRetryAttemps: UInt8? = nil, retrySleepTime: UInt32? = nil) -> Bool {
var send: [UInt8] = [command, UInt8(value >> 8), UInt8(value & 255)]
var reply: [UInt8] = []
return Self.performDDCCommunication(service: service, send: &send, reply: &reply, writeSleepTime: writeSleepTime, numOfWriteCycles: numOfWriteCycles, numOfRetryAttemps: numOfRetryAttemps, retrySleepTime: retrySleepTime)
}
static func performDDCCommunication(service: IOAVService?, send: inout [UInt8], reply: inout [UInt8], writeSleepTime: UInt32? = nil, numOfWriteCycles: UInt8? = nil, readSleepTime: UInt32? = nil, numOfRetryAttemps: UInt8? = nil, retrySleepTime: UInt32? = nil) -> Bool {
let dataAddress = ARM64_DDC_DATA_ADDRESS
var success = false
guard service != nil else {
return success
}
var packet: [UInt8] = [UInt8(0x80 | (send.count + 1)), UInt8(send.count)] + send + [0] // Note: the last byte is the place of the checksum, see next line!
packet[packet.count - 1] = self.checksum(chk: send.count == 1 ? ARM64_DDC_7BIT_ADDRESS << 1 : ARM64_DDC_7BIT_ADDRESS << 1 ^ dataAddress, data: &packet, start: 0, end: packet.count - 2)
for _ in 1 ... (numOfRetryAttemps ?? 4) + 1 {
for _ in 1 ... max((numOfWriteCycles ?? 2) + 0, 1) {
usleep(writeSleepTime ?? 10000)
success = IOAVServiceWriteI2C(service, UInt32(ARM64_DDC_7BIT_ADDRESS), UInt32(dataAddress), &packet, UInt32(packet.count)) == 0
}
if !reply.isEmpty {
usleep(readSleepTime ?? 50000)
if IOAVServiceReadI2C(service, UInt32(ARM64_DDC_7BIT_ADDRESS), 0, &reply, UInt32(reply.count)) == 0 {
success = self.checksum(chk: 0x50, data: &reply, start: 0, end: reply.count - 2) == reply[reply.count - 1]
}
}
if success {
return success
}
usleep(retrySleepTime ?? 20000)
}
return success
}
// DDC checksum calculator
static func checksum(chk: UInt8, data: inout [UInt8], start: Int, end: Int) -> UInt8 {
var chkd: UInt8 = chk
for i in start ... end {
chkd ^= data[i]
}
return chkd
}
static func ioregMatchScore(displayID: CGDirectDisplayID, ioregEdidUUID: String, ioDisplayLocation: String = "", ioregProductName: String = "", ioregSerialNumber: Int64 = 0, serviceLocation _: Int = 0) -> Int {
var matchScore = 0
if let dictionary = CoreDisplay_DisplayCreateInfoDictionary(displayID)?.takeRetainedValue() as NSDictionary? {
if let kDisplayYearOfManufacture = dictionary[kDisplayYearOfManufacture] as? Int64, let kDisplayWeekOfManufacture = dictionary[kDisplayWeekOfManufacture] as? Int64, let kDisplayVendorID = dictionary[kDisplayVendorID] as? Int64, let kDisplayProductID = dictionary[kDisplayProductID] as? Int64, let kDisplayVerticalImageSize = dictionary[kDisplayVerticalImageSize] as? Int64, let kDisplayHorizontalImageSize = dictionary[kDisplayHorizontalImageSize] as? Int64 {
struct KeyLoc {
var key: String
var loc: Int
}
let edidUUIDSearchKeys: [KeyLoc] = [
// Vendor ID
KeyLoc(key: String(format: "%04x", UInt16(max(0, min(kDisplayVendorID, 256 * 256 - 1)))).uppercased(), loc: 0),
// Product ID
KeyLoc(key: String(format: "%02x", UInt8((UInt16(max(0, min(kDisplayProductID, 256 * 256 - 1))) >> (0 * 8)) & 0xFF)).uppercased()
+ String(format: "%02x", UInt8((UInt16(max(0, min(kDisplayProductID, 256 * 256 - 1))) >> (1 * 8)) & 0xFF)).uppercased(), loc: 4),
// Manufacture date
KeyLoc(key: String(format: "%02x", UInt8(max(0, min(kDisplayWeekOfManufacture, 256 - 1)))).uppercased()
+ String(format: "%02x", UInt8(max(0, min(kDisplayYearOfManufacture - 1990, 256 - 1)))).uppercased(), loc: 19),
// Image size
KeyLoc(key: String(format: "%02x", UInt8(max(0, min(kDisplayHorizontalImageSize / 10, 256 - 1)))).uppercased()
+ String(format: "%02x", UInt8(max(0, min(kDisplayVerticalImageSize / 10, 256 - 1)))).uppercased(), loc: 30),
]
for searchKey in edidUUIDSearchKeys where searchKey.key != "0000" && searchKey.key == ioregEdidUUID.prefix(searchKey.loc + 4).suffix(4) {
matchScore += 1
}
}
if ioDisplayLocation != "", let kIODisplayLocation = dictionary[kIODisplayLocationKey] as? String, ioDisplayLocation == kIODisplayLocation {
matchScore += 10
}
if ioregProductName != "", let nameList = dictionary["DisplayProductName"] as? [String: String], let name = nameList["en_US"] ?? nameList.first?.value, name.lowercased() == ioregProductName.lowercased() {
matchScore += 1
}
if ioregSerialNumber != 0, let serial = dictionary[kDisplaySerialNumber] as? Int64, serial == ioregSerialNumber {
matchScore += 1
}
}
return matchScore
}
static func ioregIterateToNextObjectOfInterest(interests: [String], iterator: inout io_iterator_t) -> (name: String, entry: io_service_t, preceedingEntry: io_service_t)? {
var entry: io_service_t = IO_OBJECT_NULL
var preceedingEntry: io_service_t = IO_OBJECT_NULL
let name = UnsafeMutablePointer<CChar>.allocate(capacity: MemoryLayout<io_name_t>.size)
defer {
name.deallocate()
}
while true {
preceedingEntry = entry
entry = IOIteratorNext(iterator)
guard IORegistryEntryGetName(entry, name) == KERN_SUCCESS, entry != MACH_PORT_NULL else {
break
}
let nameString = String(cString: name)
for interest in interests where entry != IO_OBJECT_NULL && nameString.contains(interest) {
return (nameString, entry, preceedingEntry)
}
}
return nil
}
static func getIORegServiceAppleCDC2Properties(entry: io_service_t) -> IOregService {
var ioregService = IOregService()
if let unmanagedEdidUUID = IORegistryEntryCreateCFProperty(entry, "EDID UUID" as CFString, kCFAllocatorDefault, IOOptionBits(kIORegistryIterateRecursively)), let edidUUID = unmanagedEdidUUID.takeRetainedValue() as? String {
ioregService.edidUUID = edidUUID
}
let cpath = UnsafeMutablePointer<CChar>.allocate(capacity: MemoryLayout<io_string_t>.size)
IORegistryEntryGetPath(entry, kIOServicePlane, cpath)
ioregService.ioDisplayLocation = String(cString: cpath)
if let unmanagedDisplayAttrs = IORegistryEntryCreateCFProperty(entry, "DisplayAttributes" as CFString, kCFAllocatorDefault, IOOptionBits(kIORegistryIterateRecursively)), let displayAttrs = unmanagedDisplayAttrs.takeRetainedValue() as? NSDictionary {
ioregService.displayAttributes = displayAttrs
if let productAttrs = displayAttrs.value(forKey: "ProductAttributes") as? NSDictionary {
if let manufacturerID = productAttrs.value(forKey: "ManufacturerID") as? String {
ioregService.manufacturerID = manufacturerID
}
if let productName = productAttrs.value(forKey: "ProductName") as? String {
ioregService.productName = productName
}
if let serialNumber = productAttrs.value(forKey: "SerialNumber") as? Int64 {
ioregService.serialNumber = serialNumber
}
if let alphanumericSerialNumber = productAttrs.value(forKey: "AlphanumericSerialNumber") as? String {
ioregService.alphanumericSerialNumber = alphanumericSerialNumber
}
}
}
if let unmanagedTransport = IORegistryEntryCreateCFProperty(entry, "Transport" as CFString, kCFAllocatorDefault, IOOptionBits(kIORegistryIterateRecursively)), let transport = unmanagedTransport.takeRetainedValue() as? NSDictionary {
if let upstream = transport.value(forKey: "Upstream") as? String {
ioregService.transportUpstream = upstream
}
if let downstream = transport.value(forKey: "Downstream") as? String {
ioregService.transportDownstream = downstream
}
}
return ioregService
}
static func setIORegServiceDCPAVServiceProxy(entry: io_service_t, ioregService: inout IOregService) {
if let unmanagedLocation = IORegistryEntryCreateCFProperty(entry, "Location" as CFString, kCFAllocatorDefault, IOOptionBits(kIORegistryIterateRecursively)), let location = unmanagedLocation.takeRetainedValue() as? String {
ioregService.location = location
if location == "External" {
ioregService.service = IOAVServiceCreateWithService(kCFAllocatorDefault, entry)?.takeRetainedValue() as IOAVService
}
}
}
static func getIoregServicesForMatching() -> [IOregService] {
var serviceLocation = 0
var ioregServicesForMatching: [IOregService] = []
let ioregRoot: io_registry_entry_t = IORegistryGetRootEntry(kIOMasterPortDefault)
defer {
IOObjectRelease(ioregRoot)
}
var iterator = io_iterator_t()
defer {
IOObjectRelease(iterator)
}
var ioregService = IOregService()
guard IORegistryEntryCreateIterator(ioregRoot, "IOService", IOOptionBits(kIORegistryIterateRecursively), &iterator) == KERN_SUCCESS else {
return ioregServicesForMatching
}
let keyDCPAVServiceProxy = "DCPAVServiceProxy"
let keysFramebuffer = ["AppleCLCD2", "IOMobileFramebufferShim"]
while true {
guard let objectOfInterest = ioregIterateToNextObjectOfInterest(interests: [keyDCPAVServiceProxy] + keysFramebuffer, iterator: &iterator) else {
break
}
if keysFramebuffer.contains(objectOfInterest.name) {
ioregService = self.getIORegServiceAppleCDC2Properties(entry: objectOfInterest.entry)
serviceLocation += 1
ioregService.serviceLocation = serviceLocation
} else if objectOfInterest.name == keyDCPAVServiceProxy {
self.setIORegServiceDCPAVServiceProxy(entry: objectOfInterest.entry, ioregService: &ioregService)
ioregServicesForMatching.append(ioregService)
}
}
return ioregServicesForMatching
}
static func checkIfDummy(ioregService: IOregService) -> Bool {
if ioregService.manufacturerID == "AOC", ioregService.productName == "28E850" {
return true
}
return false
}
static func checkIfDiscouraged(ioregService _: IOregService) -> Bool {
false
}
}
================================================
FILE: MonitorControl/Support/Bridging-Header.h
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
#pragma once
#import <Foundation/Foundation.h>
#import <IOKit/i2c/IOI2CInterface.h>
#import <CoreGraphics/CoreGraphics.h>
typedef CFTypeRef IOAVService;
extern IOAVService IOAVServiceCreate(CFAllocatorRef allocator);
extern IOAVService IOAVServiceCreateWithService(CFAllocatorRef allocator, io_service_t service);
extern IOReturn IOAVServiceReadI2C(IOAVService service, uint32_t chipAddress, uint32_t offset, void* outputBuffer, uint32_t outputBufferSize);
extern IOReturn IOAVServiceWriteI2C(IOAVService service, uint32_t chipAddress, uint32_t dataAddress, void* inputBuffer, uint32_t inputBufferSize);
extern CFDictionaryRef CoreDisplay_DisplayCreateInfoDictionary(CGDirectDisplayID);
extern int DisplayServicesGetBrightness(CGDirectDisplayID display, float *brightness);
extern int DisplayServicesSetBrightness(CGDirectDisplayID display, float brightness);
extern int DisplayServicesGetLinearBrightness(CGDirectDisplayID display, float *brightness);
extern int DisplayServicesSetLinearBrightness(CGDirectDisplayID display, float brightness);
extern void CGSServiceForDisplayNumber(CGDirectDisplayID display, io_service_t* service);
bool CGSIsHDREnabled(CGDirectDisplayID display) __attribute__((weak_import));
bool CGSIsHDRSupported(CGDirectDisplayID display) __attribute__((weak_import));
@class NSString;
@protocol OSDUIHelperProtocol
- (void)showFullScreenImage:(long long)arg1 onDisplayID:(unsigned int)arg2 priority:(unsigned int)arg3 msecToAnimate:(unsigned int)arg4;
- (void)fadeClassicImageOnDisplay:(unsigned int)arg1;
- (void)showImageAtPath:(NSString *)arg1 onDisplayID:(unsigned int)arg2 priority:(unsigned int)arg3 msecUntilFade:(unsigned int)arg4 withText:(NSString *)arg5;
- (void)showImage:(long long)arg1 onDisplayID:(unsigned int)arg2 priority:(unsigned int)arg3 msecUntilFade:(unsigned int)arg4 filledChiclets:(unsigned int)arg5 totalChiclets:(unsigned int)arg6 locked:(BOOL)arg7;
- (void)showImage:(long long)arg1 onDisplayID:(unsigned int)arg2 priority:(unsigned int)arg3 msecUntilFade:(unsigned int)arg4 withText:(NSString *)arg5;
- (void)showImage:(long long)arg1 onDisplayID:(unsigned int)arg2 priority:(unsigned int)arg3 msecUntilFade:(unsigned int)arg4;
@end
@class NSXPCConnection;
@interface OSDManager : NSObject <OSDUIHelperProtocol>
{
id <OSDUIHelperProtocol> _proxyObject;
NSXPCConnection *connection;
}
+ (id)sharedManager;
@property(retain) NSXPCConnection *connection; // @synthesize connection;
- (void)showFullScreenImage:(long long)arg1 onDisplayID:(unsigned int)arg2 priority:(unsigned int)arg3 msecToAnimate:(unsigned int)arg4;
- (void)fadeClassicImageOnDisplay:(unsigned int)arg1;
- (void)showImageAtPath:(id)arg1 onDisplayID:(unsigned int)arg2 priority:(unsigned int)arg3 msecUntilFade:(unsigned int)arg4 withText:(id)arg5;
- (void)showImage:(long long)arg1 onDisplayID:(unsigned int)arg2 priority:(unsigned int)arg3 msecUntilFade:(unsigned int)arg4 filledChiclets:(unsigned int)arg5 totalChiclets:(unsigned int)arg6 locked:(BOOL)arg7;
- (void)showImage:(long long)arg1 onDisplayID:(unsigned int)arg2 priority:(unsigned int)arg3 msecUntilFade:(unsigned int)arg4 withText:(id)arg5;
- (void)showImage:(long long)arg1 onDisplayID:(unsigned int)arg2 priority:(unsigned int)arg3 msecUntilFade:(unsigned int)arg4;
@property(readonly) id <OSDUIHelperProtocol> remoteObjectProxy; // @dynamic remoteObjectProxy;
@end
================================================
FILE: MonitorControl/Support/DisplayManager.swift
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
import Cocoa
import CoreGraphics
import os.log
class DisplayManager {
public static let shared = DisplayManager()
var displays: [Display] = []
var audioControlTargetDisplays: [OtherDisplay] = []
let globalDDCQueue = DispatchQueue(label: "Global DDC queue")
let gammaActivityEnforcer = NSWindow(contentRect: .init(origin: NSPoint(x: 0, y: 0), size: .init(width: DEBUG_GAMMA_ENFORCER ? 15 : 1, height: DEBUG_GAMMA_ENFORCER ? 15 : 1)), styleMask: [], backing: .buffered, defer: false)
var gammaInterferenceCounter = 0
var gammaInterferenceWarningShown = false
func createGammaActivityEnforcer() {
self.gammaActivityEnforcer.title = "Monitor Control Gamma Activity Enforcer"
self.gammaActivityEnforcer.isMovableByWindowBackground = false
self.gammaActivityEnforcer.backgroundColor = DEBUG_GAMMA_ENFORCER ? .red : .black
self.gammaActivityEnforcer.alphaValue = 1 * (DEBUG_GAMMA_ENFORCER ? 0.5 : 0.01)
self.gammaActivityEnforcer.ignoresMouseEvents = true
self.gammaActivityEnforcer.level = .screenSaver
self.gammaActivityEnforcer.orderFrontRegardless()
self.gammaActivityEnforcer.collectionBehavior = [.stationary, .canJoinAllSpaces]
os_log("Gamma activity enforcer created.", type: .info)
}
func enforceGammaActivity() {
if self.gammaActivityEnforcer.alphaValue == 1 * (DEBUG_GAMMA_ENFORCER ? 0.5 : 0.01) {
self.gammaActivityEnforcer.alphaValue = 2 * (DEBUG_GAMMA_ENFORCER ? 0.5 : 0.01)
} else {
self.gammaActivityEnforcer.alphaValue = 1 * (DEBUG_GAMMA_ENFORCER ? 0.5 : 0.01)
}
}
func moveGammaActivityEnforcer(displayID: CGDirectDisplayID) {
if let screen = DisplayManager.getByDisplayID(displayID: DisplayManager.resolveEffectiveDisplayID(displayID)) {
self.gammaActivityEnforcer.setFrameOrigin(screen.frame.origin)
}
self.gammaActivityEnforcer.orderFrontRegardless()
}
var shades: [CGDirectDisplayID: NSWindow] = [:]
var shadeGrave: [NSWindow] = []
func isDisqualifiedFromShade(_ displayID: CGDirectDisplayID) -> Bool {
if CGDisplayIsInHWMirrorSet(displayID) != 0 || CGDisplayIsInMirrorSet(displayID) != 0 {
if displayID == DisplayManager.resolveEffectiveDisplayID(displayID), DisplayManager.isVirtual(displayID: displayID) || DisplayManager.isDummy(displayID: displayID) {
var displayIDs = [CGDirectDisplayID](repeating: 0, count: 16)
var displayCount: UInt32 = 0
guard CGGetOnlineDisplayList(16, &displayIDs, &displayCount) == .success else {
return true
}
for displayId in displayIDs where CGDisplayMirrorsDisplay(displayId) == displayID && !DisplayManager.isVirtual(displayID: displayID) {
return true
}
return false
}
return true
}
return false
}
func createShadeOnDisplay(displayID: CGDirectDisplayID) -> NSWindow? {
if let screen = DisplayManager.getByDisplayID(displayID: displayID) {
let shade = NSWindow(contentRect: .init(origin: NSPoint(x: 0, y: 0), size: .init(width: 10, height: 1)), styleMask: [], backing: .buffered, defer: false)
shade.title = "Monitor Control Window Shade for Display " + String(displayID)
shade.isMovableByWindowBackground = false
shade.backgroundColor = .clear
shade.ignoresMouseEvents = true
shade.level = NSWindow.Level(rawValue: Int(CGShieldingWindowLevel()))
shade.orderFrontRegardless()
shade.collectionBehavior = [.stationary, .canJoinAllSpaces, .ignoresCycle]
shade.setFrame(screen.frame, display: true)
shade.contentView?.wantsLayer = true
shade.contentView?.alphaValue = 0.0
shade.contentView?.layer?.backgroundColor = .black
shade.contentView?.setNeedsDisplay(shade.frame)
os_log("Window shade created for display %{public}@", type: .info, String(displayID))
return shade
}
return nil
}
func getShade(displayID: CGDirectDisplayID) -> NSWindow? {
guard !self.isDisqualifiedFromShade(displayID) else {
return nil
}
if let shade = shades[displayID] {
return shade
} else {
if let shade = self.createShadeOnDisplay(displayID: displayID) {
self.shades[displayID] = shade
return shade
}
}
return nil
}
func destroyAllShades() -> Bool {
var ret = false
for displayID in self.shades.keys {
os_log("Attempting to destory shade for display %{public}@", type: .info, String(displayID))
if self.destroyShade(displayID: displayID) {
ret = true
}
}
if ret {
os_log("Destroyed all shades.", type: .info)
} else {
os_log("No shades were found to be destroyed.", type: .info)
}
return ret
}
func destroyShade(displayID: CGDirectDisplayID) -> Bool {
if let shade = shades[displayID] {
os_log("Destroying shade for display %{public}@", type: .info, String(displayID))
self.shadeGrave.append(shade)
self.shades.removeValue(forKey: displayID)
shade.close()
return true
}
return false
}
func updateShade(displayID: CGDirectDisplayID) -> Bool {
guard !self.isDisqualifiedFromShade(displayID) else {
return false
}
if let screen = DisplayManager.getByDisplayID(displayID: displayID) {
if let shade = getShade(displayID: displayID) {
shade.setFrame(screen.frame, display: true)
return true
}
}
return false
}
func getShadeAlpha(displayID: CGDirectDisplayID) -> Float? {
guard !self.isDisqualifiedFromShade(displayID) else {
return 1
}
if let shade = getShade(displayID: displayID) {
return Float(shade.contentView?.alphaValue ?? 1)
} else {
return 1
}
}
func setShadeAlpha(value: Float, displayID: CGDirectDisplayID) -> Bool {
guard !self.isDisqualifiedFromShade(displayID) else {
return false
}
if let shade = getShade(displayID: displayID) {
shade.contentView?.alphaValue = CGFloat(value)
return true
}
return false
}
func configureDisplays() {
self.clearDisplays()
var onlineDisplayIDs = [CGDirectDisplayID](repeating: 0, count: 16)
var displayCount: UInt32 = 0
guard CGGetOnlineDisplayList(16, &onlineDisplayIDs, &displayCount) == .success else {
os_log("Unable to get display list.", type: .info)
return
}
for onlineDisplayID in onlineDisplayIDs where onlineDisplayID != 0 {
let name = DisplayManager.getDisplayNameByID(displayID: onlineDisplayID)
let id = onlineDisplayID
let vendorNumber = CGDisplayVendorNumber(onlineDisplayID)
let modelNumber = CGDisplayModelNumber(onlineDisplayID)
let serialNumber = CGDisplaySerialNumber(onlineDisplayID)
let isDummy: Bool = DisplayManager.isDummy(displayID: onlineDisplayID)
let isVirtual: Bool = DisplayManager.isVirtual(displayID: onlineDisplayID)
if !DEBUG_SW, DisplayManager.isAppleDisplay(displayID: onlineDisplayID) { // MARK: (point of interest for testing)
let appleDisplay = AppleDisplay(id, name: name, vendorNumber: vendorNumber, modelNumber: modelNumber, serialNumber: serialNumber, isVirtual: isVirtual, isDummy: isDummy)
os_log("Apple display found - %{public}@", type: .info, "ID: \(appleDisplay.identifier), Name: \(appleDisplay.name) (Vendor: \(appleDisplay.vendorNumber ?? 0), Model: \(appleDisplay.modelNumber ?? 0))")
self.addDisplay(display: appleDisplay)
} else {
let otherDisplay = OtherDisplay(id, name: name, vendorNumber: vendorNumber, modelNumber: modelNumber, serialNumber: serialNumber, isVirtual: isVirtual, isDummy: isDummy)
os_log("Other display found - %{public}@", type: .info, "ID: \(otherDisplay.identifier), Name: \(otherDisplay.name) (Vendor: \(otherDisplay.vendorNumber ?? 0), Model: \(otherDisplay.modelNumber ?? 0))")
self.addDisplay(display: otherDisplay)
}
}
}
func setupOtherDisplays(firstrun: Bool = false) {
for otherDisplay in self.getOtherDisplays() {
for command in [Command.audioSpeakerVolume, Command.contrast] where !otherDisplay.readPrefAsBool(key: .unavailableDDC, for: command) && !otherDisplay.isSw() {
otherDisplay.setupCurrentAndMaxValues(command: command, firstrun: firstrun)
}
if (!otherDisplay.isSw() && !otherDisplay.readPrefAsBool(key: .unavailableDDC, for: .brightness)) || otherDisplay.isSw() {
otherDisplay.setupCurrentAndMaxValues(command: .brightness, firstrun: firstrun)
otherDisplay.brightnessSyncSourceValue = otherDisplay.readPrefAsFloat(for: .brightness)
}
}
}
func restoreOtherDisplays() {
for otherDisplay in self.getDdcCapableDisplays() {
for command in [Command.contrast, Command.brightness] where !otherDisplay.readPrefAsBool(key: .unavailableDDC, for: command) {
otherDisplay.restoreDDCSettingsToDisplay(command: command)
}
}
}
func normalizedName(_ name: String) -> String {
var normalizedName = name.replacingOccurrences(of: "(", with: "")
normalizedName = normalizedName.replacingOccurrences(of: ")", with: "")
normalizedName = normalizedName.replacingOccurrences(of: " ", with: "")
for i in 0 ... 9 {
normalizedName = normalizedName.replacingOccurrences(of: String(i), with: "")
}
return normalizedName
}
func updateAudioControlTargetDisplays(deviceName: String) -> Int {
self.audioControlTargetDisplays.removeAll()
os_log("Detecting displays for audio control via audio device name matching...", type: .info)
var numOfAddedDisplays = 0
for ddcCapableDisplay in self.getDdcCapableDisplays() {
var displayAudioDeviceName = ddcCapableDisplay.readPrefAsString(key: .audioDeviceNameOverride)
if displayAudioDeviceName == "" {
displayAudioDeviceName = DisplayManager.getDisplayRawNameByID(displayID: ddcCapableDisplay.identifier)
}
if self.normalizedName(displayAudioDeviceName) == self.normalizedName(deviceName) {
self.audioControlTargetDisplays.append(ddcCapableDisplay)
numOfAddedDisplays += 1
os_log("Added display for audio control - %{public}@", type: .info, ddcCapableDisplay.name)
}
}
return numOfAddedDisplays
}
func getOtherDisplays() -> [OtherDisplay] {
self.displays.compactMap { $0 as? OtherDisplay }
}
func sortDisplays() {
// Opsiyonel: sıralamadan önce log al
let before = displays.map { $0.name }
os_log("Displays before sorting: %{public}@", before)
// In‑place sıralama
displays.sort { lhs, rhs in
lhs.name.localizedStandardCompare(rhs.name) == .orderedAscending
}
// Opsiyonel: sıralamadan sonra log al
let after = displays.map { $0.name }
os_log("Displays after sorting: %{public}@", after)
}
func sortDisplaysByFriendlyName() -> [Display] {
return displays.sorted { lhs, rhs in
let lhsTitle = lhs.readPrefAsString(key: .friendlyName).isEmpty
? lhs.name
: lhs.readPrefAsString(key: .friendlyName)
let rhsTitle = rhs.readPrefAsString(key: .friendlyName).isEmpty
? rhs.name
: rhs.readPrefAsString(key: .friendlyName)
return lhsTitle.localizedStandardCompare(rhsTitle) == .orderedDescending
}
}
/// displays dizisini sıralar ve döner
func getAllDisplays() -> [Display] {
return displays
}
func getDdcCapableDisplays() -> [OtherDisplay] {
self.displays.compactMap { display -> OtherDisplay? in
if let otherDisplay = display as? OtherDisplay, !otherDisplay.isSw() {
return otherDisplay
} else { return nil }
}
}
func getAppleDisplays() -> [AppleDisplay] {
self.displays.compactMap { $0 as? AppleDisplay }
}
func getBuiltInDisplay() -> Display? {
self.displays.first { CGDisplayIsBuiltin($0.identifier) != 0 }
}
func getCurrentDisplay(byFocus: Bool = false) -> Display? {
if byFocus {
guard let mainDisplayID = NSScreen.main?.displayID else {
return nil
}
return self.displays.first { $0.identifier == mainDisplayID }
} else {
let mouseLocation = NSEvent.mouseLocation
let screens = NSScreen.screens
if let screenWithMouse = (screens.first { NSMouseInRect(mouseLocation, $0.frame, false) }) {
return self.displays.first { $0.identifier == screenWithMouse.displayID }
}
return nil
}
}
func addDisplay(display: Display) {
self.displays.append(display)
}
func clearDisplays() {
self.displays = []
}
func addDisplayCounterSuffixes() {
var nameDisplays: [String: [Display]] = [:]
for display in self.displays {
if nameDisplays[display.name] != nil {
nameDisplays[display.name]?.append(display)
} else {
nameDisplays[display.name] = [display]
}
}
for nameDisplayKey in nameDisplays.keys where nameDisplays[nameDisplayKey]?.count ?? 0 > 1 {
for i in 0 ... (nameDisplays[nameDisplayKey]?.count ?? 1) - 1 {
if let display = nameDisplays[nameDisplayKey]?[i] {
display.name = "" + display.name + " (" + String(i + 1) + ")"
}
}
}
}
func updateArm64AVServices() {
if Arm64DDC.isArm64 {
os_log("arm64 AVService update requested", type: .info)
var displayIDs: [CGDirectDisplayID] = []
for otherDisplay in self.getOtherDisplays() {
displayIDs.append(otherDisplay.identifier)
}
for serviceMatch in Arm64DDC.getServiceMatches(displayIDs: displayIDs) {
for otherDisplay in self.getOtherDisplays() where otherDisplay.identifier == serviceMatch.displayID && serviceMatch.service != nil {
otherDisplay.arm64avService = serviceMatch.service
os_log("Display service match successful for display %{public}@", type: .info, String(serviceMatch.displayID))
if serviceMatch.discouraged {
os_log("Display %{public}@ is flagged as discouraged by Arm64DDC.", type: .info, String(serviceMatch.displayID))
otherDisplay.isDiscouraged = true
} else if serviceMatch.dummy {
os_log("Display %{public}@ is flagged as dummy by Arm64DDC.", type: .info, String(serviceMatch.displayID))
otherDisplay.isDiscouraged = true
otherDisplay.isDummy = true
} else {
otherDisplay.arm64ddc = DEBUG_SW ? false : true // MARK: (point of interest when testing)
}
}
}
os_log("AVService update done", type: .info)
}
}
func resetSwBrightnessForAllDisplays(prefsOnly: Bool = false, noPrefSave: Bool = false, async: Bool = false) {
for otherDisplay in self.getOtherDisplays() {
if !prefsOnly {
_ = otherDisplay.setSwBrightness(1, smooth: async, noPrefSave: noPrefSave)
if !noPrefSave {
otherDisplay.smoothBrightnessTransient = 1
}
} else if !noPrefSave {
otherDisplay.savePref(1, key: .SwBrightness)
otherDisplay.smoothBrightnessTransient = 1
}
if otherDisplay.isSw(), !noPrefSave {
otherDisplay.savePref(1, for: .brightness)
}
}
}
func restoreSwBrightnessForAllDisplays(async: Bool = false) {
for otherDisplay in self.getOtherDisplays() {
if (otherDisplay.readPrefAsFloat(for: .brightness) == 0 && !prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue)) || (otherDisplay.readPrefAsFloat(for: .brightness) < otherDisplay.combinedBrightnessSwitchingValue() && !prefs.bool(forKey: PrefKey.separateCombinedScale.rawValue) && !prefs.bool(forKey: PrefKey.disableCombinedBrightness.rawValue)) || otherDisplay.isSw() {
let savedPrefValue = otherDisplay.readPrefAsFloat(key: .SwBrightness)
if otherDisplay.getSwBrightness() != savedPrefValue {
OSDUtils.popEmptyOsd(displayID: otherDisplay.identifier, command: Command.brightness) // This will give the user a hint why is the brightness suddenly changes.
}
otherDisplay.savePref(otherDisplay.getSwBrightness(), key: .SwBrightness)
os_log("Restoring sw brightness to %{public}@ on other display %{public}@", type: .info, String(savedPrefValue), String(otherDisplay.identifier))
_ = otherDisplay.setSwBrightness(savedPrefValue, smooth: async)
if otherDisplay.isSw(), let slider = otherDisplay.sliderHandler[.brightness] {
os_log("Restoring sw slider to %{public}@ for other display %{public}@", type: .info, String(savedPrefValue), String(otherDisplay.identifier))
slider.setValue(savedPrefValue, displayID: otherDisplay.identifier)
}
} else {
_ = otherDisplay.setSwBrightness(1)
}
}
}
func getAffectedDisplays(isBrightness: Bool = false, isVolume: Bool = false) -> [Display]? {
var affectedDisplays: [Display]
let allDisplays = self.getAllDisplays()
var currentDisplay: Display?
if isBrightness {
if prefs.integer(forKey: PrefKey.multiKeyboardBrightness.rawValue) == MultiKeyboardBrightness.allScreens.rawValue {
affectedDisplays = allDisplays
return affectedDisplays
}
currentDisplay = self.getCurrentDisplay(byFocus: prefs.integer(forKey: PrefKey.multiKeyboardBrightness.rawValue) == MultiKeyboardBrightness.focusInsteadOfMouse.rawValue)
}
if isVolume {
if prefs.integer(forKey: PrefKey.multiKeyboardVolume.rawValue) == MultiKeyboardVolume.allScreens.rawValue {
affectedDisplays = allDisplays
return affectedDisplays
} else if prefs.integer(forKey: PrefKey.multiKeyboardVolume.rawValue) == MultiKeyboardVolume.audioDeviceNameMatching.rawValue {
return self.audioControlTargetDisplays
}
currentDisplay = self.getCurrentDisplay(byFocus: false)
}
if let currentDisplay = currentDisplay {
affectedDisplays = [currentDisplay]
if CGDisplayIsInHWMirrorSet(currentDisplay.identifier) != 0 || CGDisplayIsInMirrorSet(currentDisplay.identifier) != 0, CGDisplayMirrorsDisplay(currentDisplay.identifier) == 0 {
for display in allDisplays where CGDisplayMirrorsDisplay(display.identifier) == currentDisplay.identifier {
affectedDisplays.append(display)
}
}
} else {
affectedDisplays = []
}
return affectedDisplays
}
static func isDummy(displayID: CGDirectDisplayID) -> Bool {
let vendorNumber = CGDisplayVendorNumber(displayID)
let rawName = DisplayManager.getDisplayRawNameByID(displayID: displayID)
if rawName.lowercased().contains("dummy") || (self.isVirtual(displayID: displayID) && vendorNumber == UInt32(0xF0F0)) {
os_log("NOTE: Display is a dummy!", type: .info)
return true
}
return false
}
static func isVirtual(displayID: CGDirectDisplayID) -> Bool {
var isVirtual = false
if !DEBUG_MACOS10, #available(macOS 11.0, *) {
if let dictionary = (CoreDisplay_DisplayCreateInfoDictionary(displayID)?.takeRetainedValue() as NSDictionary?) {
let isVirtualDevice = dictionary["kCGDisplayIsVirtualDevice"] as? Bool
let displayIsAirplay = dictionary["kCGDisplayIsAirPlay"] as? Bool
if isVirtualDevice ?? displayIsAirplay ?? false {
isVirtual = true
}
}
}
return isVirtual
}
static func engageMirror() -> Bool {
var onlineDisplayIDs = [CGDirectDisplayID](repeating: 0, count: 16)
var displayCount: UInt32 = 0
guard CGGetOnlineDisplayList(16, &onlineDisplayIDs, &displayCount) == .success, displayCount > 1 else {
return false
}
// Break display mirror if there is any
var mirrorBreak = false
var displayConfigRef: CGDisplayConfigRef?
for onlineDisplayID in onlineDisplayIDs where onlineDisplayID != 0 {
if CGDisplayIsInHWMirrorSet(onlineDisplayID) != 0 || CGDisplayIsInMirrorSet(onlineDisplayID) != 0 {
if mirrorBreak == false {
CGBeginDisplayConfiguration(&displayConfigRef)
}
CGConfigureDisplayMirrorOfDisplay(displayConfigRef, onlineDisplayID, kCGNullDirectDisplay)
mirrorBreak = true
}
}
if mirrorBreak {
CGCompleteDisplayConfiguration(displayConfigRef, CGConfigureOption.permanently)
return true
}
// Build display mirror
var mainDisplayId = kCGNullDirectDisplay
for onlineDisplayID in onlineDisplayIDs where onlineDisplayID != 0 {
if CGDisplayIsBuiltin(onlineDisplayID) == 0, mainDisplayId == kCGNullDirectDisplay {
mainDisplayId = onlineDisplayID
}
}
guard mainDisplayId != kCGNullDirectDisplay else {
return false
}
CGBeginDisplayConfiguration(&displayConfigRef)
for onlineDisplayID in onlineDisplayIDs where onlineDisplayID != 0 && onlineDisplayID != mainDisplayId {
CGConfigureDisplayMirrorOfDisplay(displayConfigRef, onlineDisplayID, mainDisplayId)
}
CGCompleteDisplayConfiguration(displayConfigRef, CGConfigureOption.permanently)
return true
}
static func resolveEffectiveDisplayID(_ displayID: CGDirectDisplayID) -> CGDirectDisplayID {
var realDisplayID = displayID
if CGDisplayIsInHWMirrorSet(displayID) != 0 || CGDisplayIsInMirrorSet(displayID) != 0 {
let mirroredDisplayID = CGDisplayMirrorsDisplay(displayID)
if mirroredDisplayID != 0 {
realDisplayID = mirroredDisplayID
}
}
return realDisplayID
}
static func isAppleDisplay(displayID: CGDirectDisplayID) -> Bool {
if #available(macOS 15.0, *) {
if CGDisplayVendorNumber(displayID) != 1552, CGSIsHDRSupported(displayID), CGSIsHDREnabled(displayID) {
return CGDisplayIsBuiltin(displayID) != 0
}
}
var brightness: Float = -1
let ret = DisplayServicesGetBrightness(displayID, &brightness)
if ret == 0, brightness >= 0 { // If brightness read appears to be successful using DisplayServices then it should be an Apple display
return true
}
return CGDisplayIsBuiltin(displayID) != 0 // If built-in display, it should be Apple
}
static func getByDisplayID(displayID: CGDirectDisplayID) -> NSScreen? {
NSScreen.screens.first { $0.displayID == displayID }
}
static func getDisplayRawNameByID(displayID: CGDirectDisplayID) -> String {
let defaultName = ""
if !DEBUG_MACOS10, #available(macOS 11.0, *) {
if let dictionary = (CoreDisplay_DisplayCreateInfoDictionary(displayID)?.takeRetainedValue() as NSDictionary?), let nameList = dictionary["DisplayProductName"] as? [String: String], let name = nameList["en_US"] ?? nameList.first?.value {
return name
}
}
if let screen = getByDisplayID(displayID: displayID) {
return screen.displayName ?? defaultName
}
return defaultName
}
static func getDisplayNameByID(displayID: CGDirectDisplayID) -> String {
let defaultName: String = NSLocalizedString("Unknown", comment: "Unknown display name")
if !DEBUG_MACOS10, #available(macOS 11.0, *) {
if let dictionary = (CoreDisplay_DisplayCreateInfoDictionary(displayID)?.takeRetainedValue() as NSDictionary?), let nameList = dictionary["DisplayProductName"] as? [String: String], var name = nameList[Locale.current.identifier] ?? nameList["en_US"] ?? nameList.first?.value {
if CGDisplayIsInHWMirrorSet(displayID) != 0 || CGDisplayIsInMirrorSet(displayID) != 0 {
let mirroredDisplayID = CGDisplayMirrorsDisplay(displayID)
if mirroredDisplayID != 0, let dictionary = (CoreDisplay_DisplayCreateInfoDictionary(mirroredDisplayID)?.takeRetainedValue() as NSDictionary?), let nameList = dictionary["DisplayProductName"] as? [String: String], let mirroredName = nameList[Locale.current.identifier] ?? nameList["en_US"] ?? nameList.first?.value {
name.append(" | " + mirroredName)
}
}
return name
}
}
if let screen = getByDisplayID(displayID: displayID) { // MARK: This, and NSScreen+Extension.swift will not be needed when we drop MacOS 10 support.
if #available(macOS 10.15, *) {
return screen.localizedName
} else {
return screen.displayName ?? defaultName
}
}
return defaultName
}
}
================================================
FILE: MonitorControl/Support/IntelDDC.swift
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
// Adapted from IntelDDC.swift, @reitermarkus
import Foundation
import IOKit.i2c
import os.log
public class IntelDDC {
let displayId: CGDirectDisplayID
let framebuffer: io_service_t
let replyTransactionType: IOOptionBits
var enabled: Bool = false
deinit {
assert(IOObjectRelease(self.framebuffer) == KERN_SUCCESS)
}
public init?(for displayId: CGDirectDisplayID, withReplyTransactionType replyTransactionType: IOOptionBits? = nil) {
self.displayId = displayId
guard let framebuffer = IntelDDC.ioFramebufferPortFromDisplayId(displayId: displayId) else {
return nil
}
self.framebuffer = framebuffer
if let replyTransactionType = replyTransactionType {
self.replyTransactionType = replyTransactionType
} else if let replyTransactionType = IntelDDC.supportedTransactionType() {
self.replyTransactionType = replyTransactionType
} else {
os_log("No supported reply transaction type found for display with ID %u.", type: .error, displayId)
return nil
}
}
public func write(command: UInt8, value: UInt16, errorRecoveryWaitTime: UInt32? = nil, writeSleepTime: UInt32 = 10000, numofWriteCycles: UInt8 = 2) -> Bool {
var success = false
var data: [UInt8] = Array(repeating: 0, count: 7)
data[0] = 0x51
data[1] = 0x84
data[2] = 0x03
data[3] = command
data[4] = UInt8(value >> 8)
data[5] = UInt8(value & 255)
data[6] = 0x6E ^ data[0] ^ data[1] ^ data[2] ^ data[3] ^ data[4] ^ data[5]
for _ in 1 ... numofWriteCycles {
usleep(writeSleepTime)
var request = IOI2CRequest()
request.commFlags = 0
request.sendAddress = 0x6E
request.sendTransactionType = IOOptionBits(kIOI2CSimpleTransactionType)
request.sendBuffer = withUnsafePointer(to: &data[0]) { vm_address_t(bitPattern: $0) }
request.sendBytes = UInt32(data.count)
request.replyTransactionType = IOOptionBits(kIOI2CNoTransactionType)
request.replyBytes = 0
if IntelDDC.send(request: &request, to: self.framebuffer, errorRecoveryWaitTime: errorRecoveryWaitTime) {
success = true
}
}
return success
}
public func read(command: UInt8, tries: UInt = 1, replyTransactionType _: IOOptionBits? = nil, minReplyDelay: UInt64? = nil, errorRecoveryWaitTime: UInt32? = nil, writeSleepTime: UInt32 = 10000) -> (UInt16, UInt16)? {
var data: [UInt8] = Array(repeating: 0, count: 5)
var replyData: [UInt8] = Array(repeating: 0, count: 11)
data[0] = 0x51
data[1] = 0x82
data[2] = 0x01
data[3] = command
data[4] = 0x6E ^ data[0] ^ data[1] ^ data[2] ^ data[3]
for i in 1 ... tries {
usleep(writeSleepTime)
usleep(errorRecoveryWaitTime ?? 0)
var request = IOI2CRequest()
request.commFlags = 0
request.sendAddress = 0x6E
request.sendTransactionType = IOOptionBits(kIOI2CSimpleTransactionType)
request.sendBuffer = withUnsafePointer(to: &data[0]) { vm_address_t(bitPattern: $0) }
request.sendBytes = UInt32(data.count)
request.minReplyDelay = minReplyDelay ?? 10
request.replyAddress = 0x6F
request.replySubAddress = 0x51
request.replyTransactionType = self.replyTransactionType
request.replyBytes = UInt32(replyData.count)
request.replyBuffer = withUnsafePointer(to: &replyData[0]) { vm_address_t(bitPattern: $0) }
if IntelDDC.send(request: &request, to: self.framebuffer, errorRecoveryWaitTime: errorRecoveryWaitTime) {
if replyData.count > 0 {
let checksum = replyData.last!
var calculated = UInt8(0x50)
for i in 0 ..< (replyData.count - 1) {
calculated ^= replyData[i]
}
guard checksum == calculated else {
os_log("Checksum of reply does not match. Expected %u, got %u.", type: .info, checksum, calculated)
os_log("Response was: %{public}@", type: .info, replyData.map { String(format: "%02X", $0) }.joined(separator: " "))
continue
}
}
guard replyData[2] == 0x02 else {
os_log("Got wrong response type for %{public}@. Expected %u, got %u.", type: .info, String(reflecting: command), 0x02, replyData[2])
os_log("Response was: %{public}@", type: .info, replyData.map { String(format: "%02X", $0) }.joined(separator: " "))
continue
}
guard replyData[3] == 0x00 else {
os_log("Reading %{public}@ is not supported.", type: .info, String(reflecting: command))
return nil
}
if i > 1 {
os_log("Reading %{public}@ took %u tries.", type: .info, String(reflecting: command), i)
}
let (mh, ml, sh, sl) = (replyData[6], replyData[7], replyData[8], replyData[9])
let maxValue = UInt16(mh << 8) + UInt16(ml)
let currentValue = UInt16(sh << 8) + UInt16(sl)
return (currentValue, maxValue)
}
}
return nil
}
private static func supportedTransactionType() -> IOOptionBits? {
var ioIterator = io_iterator_t()
guard IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceNameMatching("IOFramebufferI2CInterface"), &ioIterator) == KERN_SUCCESS else {
return nil
}
defer {
assert(IOObjectRelease(ioIterator) == KERN_SUCCESS)
}
while case let ioService = IOIteratorNext(ioIterator), ioService != 0 {
var serviceProperties: Unmanaged<CFMutableDictionary>?
guard IORegistryEntryCreateCFProperties(ioService, &serviceProperties, kCFAllocatorDefault, IOOptionBits()) == KERN_SUCCESS, serviceProperties != nil else {
continue
}
let dict = serviceProperties!.takeRetainedValue() as NSDictionary
if let types = dict[kIOI2CTransactionTypesKey] as? UInt64 {
if (1 << kIOI2CDDCciReplyTransactionType) & types != 0 {
os_log("kIOI2CDDCciReplyTransactionType is supported.", type: .info)
return IOOptionBits(kIOI2CDDCciReplyTransactionType)
}
if (1 << kIOI2CSimpleTransactionType) & types != 0 {
os_log("kIOI2CSimpleTransactionType is supported.", type: .info)
return IOOptionBits(kIOI2CSimpleTransactionType)
}
}
}
return nil
}
static func send(request: inout IOI2CRequest, to framebuffer: io_service_t, errorRecoveryWaitTime: UInt32? = nil) -> Bool {
if let errorRecoveryWaitTime = errorRecoveryWaitTime {
usleep(errorRecoveryWaitTime)
}
var busCount: IOItemCount = 0
guard IOFBGetI2CInterfaceCount(framebuffer, &busCount) == KERN_SUCCESS else {
os_log("Failed to get interface count for framebuffer with ID %u.", type: .error, framebuffer)
return false
}
for bus: IOOptionBits in 0 ..< busCount {
var interface = io_service_t()
guard IOFBCopyI2CInterfaceForBus(framebuffer, bus, &interface) == KERN_SUCCESS else {
os_log("Failed to get interface %u for framebuffer with ID %u.", type: .error, bus, framebuffer)
continue
}
var connect: IOI2CConnectRef?
guard IOI2CInterfaceOpen(interface, IOOptionBits(), &connect) == KERN_SUCCESS else {
os_log("Failed to connect to interface %u for framebuffer with ID %u.", type: .error, bus, framebuffer)
continue
}
defer { IOI2CInterfaceClose(connect, IOOptionBits()) }
guard IOI2CSendRequest(connect, IOOptionBits(), &request) == KERN_SUCCESS else {
os_log("Failed to send request to interface %u for framebuffer with ID %u.", type: .error, bus, framebuffer)
continue
}
guard request.result == KERN_SUCCESS else {
os_log("Request to interface %u for framebuffer with ID %u failed.", type: .error, bus, framebuffer)
continue
}
return true
}
return false
}
static func servicePortUsingDisplayPropertiesMatching(from displayId: CGDirectDisplayID) -> io_object_t? {
var portIterator = io_iterator_t()
let status: kern_return_t = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching(IOFRAMEBUFFER_CONFORMSTO), &portIterator)
guard status == KERN_SUCCESS else {
os_log("No matching services found for display with ID %u.", type: .error, displayId)
return nil
}
defer {
assert(IOObjectRelease(portIterator) == KERN_SUCCESS)
}
while case let port = IOIteratorNext(portIterator), port != 0 {
let dict = IODisplayCreateInfoDictionary(port, IOOptionBits(kIODisplayOnlyPreferredName)).takeRetainedValue() as NSDictionary
let valueForKey = { (k: String) in
(dict[k] as? CFIndex).flatMap { Int32(exactly: $0) }.flatMap { UInt32(bitPattern: $0) } ?? 0
}
let portVendorId = valueForKey(kDisplayVendorID)
let displayVendorId = CGDisplayVendorNumber(displayId)
guard portVendorId == displayVendorId else {
os_log("Service port vendor ID %u differs from display product ID %u.", type: .info,
portVendorId, displayVendorId)
continue
}
let portProductId = valueForKey(kDisplayProductID)
let displayProductId = CGDisplayModelNumber(displayId)
guard portProductId == displayProductId else {
os_log("Service port product ID %u differs from display product ID %u.", type: .info,
portProductId, displayProductId)
continue
}
let portSerialNumber = valueForKey(kDisplaySerialNumber)
let displaySerialNumber = CGDisplaySerialNumber(displayId)
guard portSerialNumber == displaySerialNumber else {
os_log("Service port serial number %u differs from display serial number %u.", type: .info, portSerialNumber, displaySerialNumber)
continue
}
if let displayLocation = dict[kIODisplayLocationKey] as? NSString {
// the unit number is the number right after the last "@" sign in the display location
// swiftlint:disable:next force_try
let regex = try! NSRegularExpression(pattern: "@([0-9]+)[^@]+$", options: [])
if let match = regex.firstMatch(in: displayLocation as String, options: [], range: NSRange(location: 0, length: displayLocation.length)) {
let unitNumber = UInt32(displayLocation.substring(with: match.range(at: 1)))
guard unitNumber == CGDisplayUnitNumber(displayId) else {
continue
}
}
}
os_log("Vendor ID: %u, Product ID: %u, Serial Number: %u", type: .info, portVendorId, portProductId, portSerialNumber)
os_log("Unit Number: %u", type: .info, CGDisplayUnitNumber(displayId))
os_log("Service Port: %u", type: .info, port)
return port
}
os_log("No service port found for display with ID %u.", type: .error, displayId)
return nil
}
static func ioFramebufferPortFromDisplayId(displayId: CGDirectDisplayID) -> io_service_t? {
if CGDisplayIsBuiltin(displayId) == boolean_t(truncating: true) {
return nil
}
var servicePortUsingCGSServiceForDisplayNumber: io_service_t = 0
CGSServiceForDisplayNumber(displayId, &servicePortUsingCGSServiceForDisplayNumber)
if servicePortUsingCGSServiceForDisplayNumber != 0 {
os_log("Using CGSServiceForDisplayNumber to acquire framebuffer port for %u.", type: .info, displayId)
return servicePortUsingCGSServiceForDisplayNumber
}
guard let servicePort = self.servicePortUsingDisplayPropertiesMatching(from: displayId) else {
return nil
}
var busCount: IOItemCount = 0
guard IOFBGetI2CInterfaceCount(servicePort, &busCount) == KERN_SUCCESS, busCount >= 1 else {
os_log("No framebuffer port found for display with ID %u.", type: .error, displayId)
return nil
}
return servicePort
}
}
================================================
FILE: MonitorControl/Support/KeyboardShortcutsManager.swift
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
import Foundation
import KeyboardShortcuts
import os.log
class KeyboardShortcutsManager {
var initialKeyRepeat = 0.21
var keyRepeat = 0.028
var keyRepeatCount = 0
var currentCommand = KeyboardShortcuts.Name.none
var isFirstKeypress = false
var currentEventId = 0
var isHold = false
init() {
KeyboardShortcuts.onKeyDown(for: .brightnessUp) { [self] in
self.engage(KeyboardShortcuts.Name.brightnessUp)
}
KeyboardShortcuts.onKeyDown(for: .brightnessDown) { [self] in
self.engage(KeyboardShortcuts.Name.brightnessDown)
}
KeyboardShortcuts.onKeyDown(for: .contrastUp) { [self] in
self.engage(KeyboardShortcuts.Name.contrastUp)
}
KeyboardShortcuts.onKeyDown(for: .contrastDown) { [self] in
self.engage(KeyboardShortcuts.Name.contrastDown)
}
KeyboardShortcuts.onKeyDown(for: .volumeUp) { [self] in
self.engage(KeyboardShortcuts.Name.volumeUp)
}
KeyboardShortcuts.onKeyDown(for: .volumeDown) { [self] in
self.engage(KeyboardShortcuts.Name.volumeDown)
}
KeyboardShortcuts.onKeyDown(for: .mute) { [self] in
self.mute()
}
KeyboardShortcuts.onKeyUp(for: .brightnessUp) { [self] in
self.disengage()
}
KeyboardShortcuts.onKeyUp(for: .brightnessDown) { [self] in
self.disengage()
}
KeyboardShortcuts.onKeyUp(for: .contrastUp) { [self] in
self.disengage()
}
KeyboardShortcuts.onKeyUp(for: .contrastDown) { [self] in
self.disengage()
}
KeyboardShortcuts.onKeyUp(for: .volumeUp) { [self] in
self.disengage()
}
KeyboardShortcuts.onKeyUp(for: .volumeDown) { [self] in
self.disengage()
}
}
func engage(_ shortcut: KeyboardShortcuts.Name) {
self.initialKeyRepeat = max(15, UserDefaults.standard.double(forKey: "InitialKeyRepeat")) * 0.014
self.keyRepeat = max(2, UserDefaults.standard.double(forKey: "KeyRepeat")) * 0.014
self.currentCommand = shortcut
self.isFirstKeypress = true
self.isHold = true
self.currentEventId += 1
self.keyRepeatCount = 0
self.apply(shortcut, eventId: self.currentEventId)
}
func disengage() {
self.isHold = false
self.isFirstKeypress = false
self.currentCommand = KeyboardShortcuts.Name.none
self.keyRepeatCount = 0
}
func apply(_ shortcut: KeyboardShortcuts.Name, eventId: Int) {
guard app.sleepID == 0, app.reconfigureID == 0, self.keyRepeatCount <= 100 else {
self.disengage()
return
}
guard self.currentCommand == shortcut, self.isHold, eventId == self.currentEventId else {
if [KeyboardShortcuts.Name.volumeUp, KeyboardShortcuts.Name.volumeDown].contains(shortcut) {
self.volume(isUp: true, isPressed: false)
}
return
}
if self.isFirstKeypress {
self.isFirstKeypress = false
DispatchQueue.main.asyncAfter(deadline: .now() + self.initialKeyRepeat) {
self.apply(shortcut, eventId: eventId)
}
} else {
DispatchQueue.main.asyncAfter(deadline: .now() + self.keyRepeat) {
self.apply(shortcut, eventId: eventId)
}
}
self.keyRepeatCount += 1
switch shortcut {
case KeyboardShortcuts.Name.brightnessUp: self.brightness(isUp: true)
case KeyboardShortcuts.Name.brightnessDown: self.brightness(isUp: false)
case KeyboardShortcuts.Name.contrastUp: self.contrast(isUp: true)
case KeyboardShortcuts.Name.contrastDown: self.contrast(isUp: false)
case KeyboardShortcuts.Name.volumeUp: self.volume(isUp: true, isPressed: true)
case KeyboardShortcuts.Name.volumeDown: self.volume(isUp: false, isPressed: true)
default: break
}
}
func brightness(isUp: Bool) {
guard let affectedDisplays = DisplayManager.shared.getAffectedDisplays(isBrightness: true, isVolume: false), [KeyboardBrightness.custom.rawValue, KeyboardBrightness.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardBrightness.rawValue)) else {
self.disengage()
return
}
for display in affectedDisplays where !display.readPrefAsBool(key: .isDisabled) {
var isAnyDisplayInSwAfterBrightnessMode = false
for display in affectedDisplays where ((display as? OtherDisplay)?.isSwBrightnessNotDefault() ?? false) && !((display as? OtherDisplay)?.isSw() ?? false) && prefs.bool(forKey: PrefKey.separateCombinedScale.rawValue) {
isAnyDisplayInSwAfterBrightnessMode = true
}
if !(isAnyDisplayInSwAfterBrightnessMode && !(((display as? OtherDisplay)?.isSwBrightnessNotDefault() ?? false) && !((display as? OtherDisplay)?.isSw() ?? false))) {
display.stepBrightness(isUp: isUp, isSmallIncrement: prefs.bool(forKey: PrefKey.useFineScaleBrightness.rawValue))
}
}
}
func contrast(isUp: Bool) {
guard let affectedDisplays = DisplayManager.shared.getAffectedDisplays(isBrightness: true, isVolume: false), [KeyboardBrightness.custom.rawValue, KeyboardBrightness.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardBrightness.rawValue)) else {
self.disengage()
return
}
for display in affectedDisplays where !display.readPrefAsBool(key: .isDisabled) {
if let otherDisplay = display as? OtherDisplay {
otherDisplay.stepContrast(isUp: isUp, isSmallIncrement: prefs.bool(forKey: PrefKey.useFineScaleBrightness.rawValue))
}
}
}
func volume(isUp: Bool, isPressed: Bool) {
guard let affectedDisplays = DisplayManager.shared.getAffectedDisplays(isBrightness: false, isVolume: true), [KeyboardVolume.custom.rawValue, KeyboardVolume.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardVolume.rawValue)) else {
self.disengage()
return
}
var wasNotIsPressedVolumeSentAlready = false
for display in affectedDisplays where !display.readPrefAsBool(key: .isDisabled) {
if let display = display as? OtherDisplay {
if isPressed {
display.stepVolume(isUp: isUp, isSmallIncrement: prefs.bool(forKey: PrefKey.useFineScaleVolume.rawValue))
} else if !wasNotIsPressedVolumeSentAlready, !display.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) {
app.playVolumeChangedSound()
wasNotIsPressedVolumeSentAlready = true
}
}
}
}
func mute() {
guard app.sleepID == 0, app.reconfigureID == 0, [KeyboardVolume.custom.rawValue, KeyboardVolume.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardVolume.rawValue)), let affectedDisplays = DisplayManager.shared.getAffectedDisplays(isBrightness: false, isVolume: true) else {
return
}
var wasNotIsPressedVolumeSentAlready = false
for display in affectedDisplays where !display.readPrefAsBool(key: .isDisabled) {
if let display = display as? OtherDisplay {
display.toggleMute()
if !wasNotIsPressedVolumeSentAlready, display.readPrefAsInt(for: .audioMuteScreenBlank) != 1, !display.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) {
app.playVolumeChangedSound()
wasNotIsPressedVolumeSentAlready = true
}
}
}
}
}
================================================
FILE: MonitorControl/Support/MediaKeyTapManager.swift
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
import AudioToolbox
import Cocoa
import Foundation
import MediaKeyTap
import os.log
class MediaKeyTapManager: MediaKeyTapDelegate {
var mediaKeyTap: MediaKeyTap?
var keyRepeatTimers: [MediaKey: Timer] = [:]
func handle(mediaKey: MediaKey, event: KeyEvent?, modifiers: NSEvent.ModifierFlags?) {
let isPressed = event?.keyPressed ?? true
let isRepeat = event?.keyRepeat ?? false
let isControl = modifiers?.isSuperset(of: NSEvent.ModifierFlags([.control])) ?? false
let isCommand = modifiers?.isSuperset(of: NSEvent.ModifierFlags([.command])) ?? false
let isOption = modifiers?.isSuperset(of: NSEvent.ModifierFlags([.option])) ?? false
let isShift = modifiers?.isSuperset(of: NSEvent.ModifierFlags([.shift])) ?? false
if isPressed, isCommand, !isControl, mediaKey == .brightnessDown, DisplayManager.engageMirror() {
return
}
guard app.sleepID == 0, app.reconfigureID == 0 else {
return
}
if isPressed, self.handleOpenPrefPane(mediaKey: mediaKey, event: event, modifiers: modifiers) {
return
}
var isSmallIncrement = isOption && isShift
let isContrast = isControl && isOption && isCommand
if [.brightnessUp, .brightnessDown].contains(mediaKey), prefs.bool(forKey: PrefKey.useFineScaleBrightness.rawValue) {
isSmallIncrement = !isSmallIncrement
}
if [.volumeUp, .volumeDown, .mute].contains(mediaKey), prefs.bool(forKey: PrefKey.useFineScaleVolume.rawValue) {
isSmallIncrement = !isSmallIncrement
}
if isPressed, isControl, !isOption, mediaKey == .brightnessUp || mediaKey == .brightnessDown {
self.handleDirectedBrightness(isCommandModifier: isCommand, isUp: mediaKey == .brightnessUp, isSmallIncrement: isSmallIncrement)
return
}
let oppositeKey: MediaKey? = self.oppositeMediaKey(mediaKey: mediaKey)
// If the opposite key to the one being held has an active timer, cancel it - we'll be going in the opposite direction
if let oppositeKey = oppositeKey, let oppositeKeyTimer = self.keyRepeatTimers[oppositeKey], oppositeKeyTimer.isValid {
oppositeKeyTimer.invalidate()
} else if let mediaKeyTimer = self.keyRepeatTimers[mediaKey], mediaKeyTimer.isValid {
// If there's already an active timer for the key being held down, let it run rather than executing it again
if isRepeat {
return
}
mediaKeyTimer.invalidate()
}
self.sendDisplayCommand(mediaKey: mediaKey, isRepeat: isRepeat, isSmallIncrement: isSmallIncrement, isPressed: isPressed, isContrast: isContrast)
}
func handleDirectedBrightness(isCommandModifier: Bool, isUp: Bool, isSmallIncrement: Bool) {
if isCommandModifier {
for otherDisplay in DisplayManager.shared.getOtherDisplays() {
otherDisplay.stepBrightness(isUp: isUp, isSmallIncrement: isSmallIncrement)
}
for appleDisplay in DisplayManager.shared.getAppleDisplays() where !appleDisplay.isBuiltIn() {
appleDisplay.stepBrightness(isUp: isUp, isSmallIncrement: isSmallIncrement)
}
return
} else if let internalDisplay = DisplayManager.shared.getBuiltInDisplay() as? AppleDisplay {
internalDisplay.stepBrightness(isUp: isUp, isSmallIncrement: isSmallIncrement)
return
}
}
private func sendDisplayCommand(mediaKey: MediaKey, isRepeat: Bool, isSmallIncrement: Bool, isPressed: Bool, isContrast: Bool = false) {
self.sendDisplayCommandVolumeMute(mediaKey: mediaKey, isRepeat: isRepeat, isSmallIncrement: isSmallIncrement, isPressed: isPressed)
self.sendDisplayCommandBrightnessContrast(mediaKey: mediaKey, isRepeat: isRepeat, isSmallIncrement: isSmallIncrement, isPressed: isPressed, isContrast: isContrast)
}
private func sendDisplayCommandVolumeMute(mediaKey: MediaKey, isRepeat: Bool, isSmallIncrement: Bool, isPressed: Bool) {
guard [.volumeUp, .volumeDown, .mute].contains(mediaKey), app.sleepID == 0, app.reconfigureID == 0, let affectedDisplays = DisplayManager.shared.getAffectedDisplays(isBrightness: false, isVolume: true) else {
return
}
var wasNotIsPressedVolumeSentAlready = false
for display in affectedDisplays where !display.readPrefAsBool(key: .isDisabled) {
switch mediaKey {
case .mute:
// The mute key should not respond to press + hold or keyup
if !isRepeat, isPressed, let display = display as? OtherDisplay {
display.toggleMute()
if !wasNotIsPressedVolumeSentAlready, display.readPrefAsInt(for: .audioMuteScreenBlank) != 1, !display.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) {
app.playVolumeChangedSound()
wasNotIsPressedVolumeSentAlready = true
}
}
case .volumeUp, .volumeDown:
// volume only matters for other displays
if let display = display as? OtherDisplay {
if isPressed {
display.stepVolume(isUp: mediaKey == .volumeUp, isSmallIncrement: isSmallIncrement)
} else if !wasNotIsPressedVolumeSentAlready, !display.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume) {
app.playVolumeChangedSound()
wasNotIsPressedVolumeSentAlready = true
}
}
default: continue
}
}
}
private func sendDisplayCommandBrightnessContrast(mediaKey: MediaKey, isRepeat _: Bool, isSmallIncrement: Bool, isPressed: Bool, isContrast: Bool = false) {
guard [.brightnessUp, .brightnessDown].contains(mediaKey), app.sleepID == 0, app.reconfigureID == 0, isPressed, let affectedDisplays = DisplayManager.shared.getAffectedDisplays(isBrightness: true, isVolume: false) else {
return
}
for display in affectedDisplays where !display.readPrefAsBool(key: .isDisabled) {
switch mediaKey {
case .brightnessUp:
if isContrast, let otherDisplay = display as? OtherDisplay {
otherDisplay.stepContrast(isUp: mediaKey == .brightnessUp, isSmallIncrement: isSmallIncrement)
} else {
var isAnyDisplayInSwAfterBrightnessMode = false
for display in affectedDisplays where ((display as? OtherDisplay)?.isSwBrightnessNotDefault() ?? false) && !((display as? OtherDisplay)?.isSw() ?? false) && prefs.bool(forKey: PrefKey.separateCombinedScale.rawValue) {
isAnyDisplayInSwAfterBrightnessMode = true
}
if !(isAnyDisplayInSwAfterBrightnessMode && !(((display as? OtherDisplay)?.isSwBrightnessNotDefault() ?? false) && !((display as? OtherDisplay)?.isSw() ?? false))) {
display.stepBrightness(isUp: mediaKey == .brightnessUp, isSmallIncrement: isSmallIncrement)
}
}
case .brightnessDown:
if isContrast, let otherDisplay = display as? OtherDisplay {
otherDisplay.stepContrast(isUp: mediaKey == .brightnessUp, isSmallIncrement: isSmallIncrement)
} else {
display.stepBrightness(isUp: mediaKey == .brightnessUp, isSmallIncrement: isSmallIncrement)
}
default: continue
}
}
}
private func oppositeMediaKey(mediaKey: MediaKey) -> MediaKey? {
if mediaKey == .brightnessUp {
return .brightnessDown
} else if mediaKey == .brightnessDown {
return .brightnessUp
} else if mediaKey == .volumeUp {
return .volumeDown
} else if mediaKey == .volumeDown {
return .volumeUp
}
return nil
}
func updateMediaKeyTap() {
var keys: [MediaKey] = []
if [KeyboardBrightness.media.rawValue, KeyboardBrightness.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardBrightness.rawValue)) {
keys.append(contentsOf: [.brightnessUp, .brightnessDown])
}
if [KeyboardVolume.media.rawValue, KeyboardVolume.both.rawValue].contains(prefs.integer(forKey: PrefKey.keyboardVolume.rawValue)) {
keys.append(contentsOf: [.mute, .volumeUp, .volumeDown])
}
// Remove brightness keys if no external displays are connected, but only if brightness fine control is not active
var disengageBrightness = true
for display in DisplayManager.shared.getAllDisplays() where !display.isBuiltIn() {
disengageBrightness = false
}
// Disengage brightness keys on sleep so MacBook native screen can be controlled meanwhile
if app.sleepID != 0 || app.reconfigureID != 0 {
disengageBrightness = true
}
if disengageBrightness, !prefs.bool(forKey: PrefKey.useFineScaleBrightness.rawValue) {
let keysToDelete: [MediaKey] = [.brightnessUp, .brightnessDown]
keys.removeAll { keysToDelete.contains($0) }
}
// Remove volume related keys if audio device is controllable
if let defaultAudioDevice = app.coreAudio.defaultOutputDevice {
let keysToDelete: [MediaKey] = [.volumeUp, .volumeDown, .mute]
if prefs.integer(forKey: PrefKey.multiKeyboardVolume.rawValue) == MultiKeyboardVolume.audioDeviceNameMatching.rawValue {
if DisplayManager.shared.updateAudioControlTargetDisplays(deviceName: defaultAudioDevice.name) == 0 {
keys.removeAll { keysToDelete.contains($0) }
}
} else if defaultAudioDevice.canSetVirtualMainVolume(scope: .output) == true {
keys.removeAll { keysToDelete.contains($0) }
}
}
self.mediaKeyTap?.stop()
// returning an empty array listens for all mediakeys in MediaKeyTap
if keys.count > 0 {
self.mediaKeyTap = MediaKeyTap(delegate: self, on: KeyPressMode.keyDownAndUp, for: keys, observeBuiltIn: true)
self.mediaKeyTap?.start()
}
}
func handleOpenPrefPane(mediaKey: MediaKey, event: KeyEvent?, modifiers: NSEvent.ModifierFlags?) -> Bool {
guard let modifiers = modifiers else { return false }
if !(modifiers.contains(.option) && !modifiers.contains(.shift) && !modifiers.contains(.control) && !modifiers.contains(.command)) {
return false
}
if event?.keyRepeat == true {
return false
}
switch mediaKey {
case .brightnessUp, .brightnessDown:
NSWorkspace.shared.open(URL(fileURLWithPath: "/System/Library/PreferencePanes/Displays.prefPane"))
case .mute, .volumeUp, .volumeDown:
NSWorkspace.shared.open(URL(fileURLWithPath: "/System/Library/PreferencePanes/Sound.prefPane"))
default:
return false
}
return true
}
static func acquirePrivileges(firstAsk: Bool = false) {
if !self.readPrivileges(prompt: true), !firstAsk {
let alert = NSAlert()
alert.messageText = NSLocalizedString("Shortcuts not available", comment: "Shown in the alert dialog")
alert.informativeText = NSLocalizedString("You need to enable MonitorControl in System Settings > Security and Privacy > Accessibility for the keyboard shortcuts to work", comment: "Shown in the alert dialog")
alert.runModal()
}
}
static func readPrivileges(prompt: Bool) -> Bool {
let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as NSString: prompt]
let status = AXIsProcessTrustedWithOptions(options)
os_log("Reading Accessibility privileges - Current access status %{public}@", type: .info, String(status))
return status
}
}
================================================
FILE: MonitorControl/Support/MenuHandler.swift
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
import AppKit
import os.log
class MenuHandler: NSMenu, NSMenuDelegate {
var combinedSliderHandler: [Command: SliderHandler] = [:]
var lastMenuRelevantDisplayId: CGDirectDisplayID = 0
func clearMenu() {
var items: [NSMenuItem] = []
for i in 0 ..< self.items.count {
items.append(self.items[i])
}
for item in items {
self.removeItem(item)
}
self.combinedSliderHandler.removeAll()
}
func menuWillOpen(_: NSMenu) {
self.updateMenuRelevantDisplay()
app.keyboardShortcuts.disengage()
}
func closeMenu() {
self.cancelTrackingWithoutAnimation()
}
func updateMenus(dontClose: Bool = false) {
os_log("Menu update initiated", type: .info)
if !dontClose {
self.cancelTrackingWithoutAnimation()
}
let menuIconPref = prefs.integer(forKey: PrefKey.menuIcon.rawValue)
var showIcon = false
if menuIconPref == MenuIcon.show.rawValue {
showIcon = true
} else if menuIconPref == MenuIcon.externalOnly.rawValue {
let externalDisplays = DisplayManager.shared.displays.filter {
CGDisplayIsBuiltin($0.identifier) == 0
}
if externalDisplays.count > 0 {
showIcon = true
}
}
app.updateStatusItemVisibility(showIcon)
self.clearMenu()
let currentDisplay = DisplayManager.shared.getCurrentDisplay()
var displays: [Display] = []
if !prefs.bool(forKey: PrefKey.hideAppleFromMenu.rawValue) {
displays.append(contentsOf: DisplayManager.shared.getAppleDisplays())
}
displays.append(contentsOf: DisplayManager.shared.getOtherDisplays())
displays = DisplayManager.shared.sortDisplaysByFriendlyName()
let relevant = prefs.integer(forKey: PrefKey.multiSliders.rawValue) == MultiSliders.relevant.rawValue
let combine = prefs.integer(forKey: PrefKey.multiSliders.rawValue) == MultiSliders.combine.rawValue
let numOfDisplays = displays.filter { !$0.isDummy }.count
if numOfDisplays != 0 {
let asSubMenu: Bool = (displays.count > 3 && !relevant && !combine && app.macOS10()) ? true : false
var iterator = 0
for display in displays where (!relevant || DisplayManager.resolveEffectiveDisplayID(display.identifier) == DisplayManager.resolveEffectiveDisplayID(currentDisplay!.identifier)) && !display.isDummy {
iterator += 1
if !relevant, !combine, iterator != 1, app.macOS10() {
self.insertItem(NSMenuItem.separator(), at: 0)
}
self.updateDisplayMenu(display: display, asSubMenu: asSubMenu, numOfDisplays: numOfDisplays)
}
if combine {
self.addCombinedDisplayMenuBlock()
}
}
self.addDefaultMenuOptions()
}
func addSliderItem(monitorSubMenu: NSMenu, sliderHandler: SliderHandler) {
let item = NSMenuItem()
item.view = sliderHandler.view
monitorSubMenu.insertItem(item, at: 0)
if app.macOS10() {
let sliderHeaderItem = NSMenuItem()
let attrs: [NSAttributedString.Key: Any] = [.foregroundColor: NSColor.systemGray, .font: NSFont.systemFont(ofSize: 12)]
sliderHeaderItem.attributedTitle = NSAttributedString(string: sliderHandler.title, attributes: attrs)
monitorSubMenu.insertItem(sliderHeaderItem, at: 0)
}
}
func setupMenuSliderHandler(command: Command, display: Display, title: String) -> SliderHandler {
if prefs.integer(forKey: PrefKey.multiSliders.rawValue) == MultiSliders.combine.rawValue, let combinedHandler = self.combinedSliderHandler[command] {
combinedHandler.addDisplay(display)
display.sliderHandler[command] = combinedHandler
return combinedHandler
} else {
let sliderHandler = SliderHandler(display: display, command: command, title: title)
if prefs.integer(forKey: PrefKey.multiSliders.rawValue) == MultiSliders.combine.rawValue {
self.combinedSliderHandler[command] = sliderHandler
}
display.sliderHandler[command] = sliderHandler
return sliderHandler
}
}
func addDisplayMenuBlock(addedSliderHandlers: [SliderHandler], blockName: String, monitorSubMenu: NSMenu, numOfDisplays: Int, asSubMenu: Bool) {
if numOfDisplays > 1, prefs.integer(forKey: PrefKey.multiSliders.rawValue) != MultiSliders.relevant.rawValue, !DEBUG_MACOS10, #available(macOS 11.0, *) {
class BlockView: NSView {
override func draw(_: NSRect) {
let radius = prefs.bool(forKey: PrefKey.showTickMarks.rawValue) ? CGFloat(4) : CGFloat(11)
let outerMargin = CGFloat(15)
let blockRect = self.frame.insetBy(dx: outerMargin, dy: outerMargin / 2 + 2).offsetBy(dx: 0, dy: outerMargin / 2 * -1 + 7)
for i in 1 ... 5 {
let blockPath = NSBezierPath(roundedRect: blockRect.insetBy(dx: CGFloat(i) * -1, dy: CGFloat(i) * -1), xRadius: radius + CGFloat(i) * 0.5, yRadius: radius + CGFloat(i) * 0.5)
NSColor.black.withAlphaComponent(0.1 / CGFloat(i)).setStroke()
blockPath.stroke()
}
let blockPath = NSBezierPath(roundedRect: blockRect, xRadius: radius, yRadius: radius)
if [NSAppearance.Name.darkAqua, NSAppearance.Name.vibrantDark].contains(effectiveAppearance.name) {
NSColor.systemGray.withAlphaComponent(0.3).setStroke()
blockPath.stroke()
}
if ![NSAppearance.Name.darkAqua, NSAppearance.Name.vibrantDark].contains(effectiveAppearance.name) {
NSColor.white.withAlphaComponent(0.5).setFill()
blockPath.fill()
}
}
}
var contentWidth: CGFloat = 0
var contentHeight: CGFloat = 0
for addedSliderHandler in addedSliderHandlers {
contentWidth = max(addedSliderHandler.view!.frame.width, contentWidth)
contentHeight += addedSliderHandler.view!.frame.height
}
let margin = CGFloat(13)
var blockNameView: NSTextField?
if blockName != "" {
contentHeight += 21
let attrs: [NSAttributedString.Key: Any] = [.foregroundColor: NSColor.textColor, .font: NSFont.boldSystemFont(ofSize: 12)]
blockNameView = NSTextField(labelWithAttributedString: NSAttributedString(string: blockName, attributes: attrs))
blockNameView?.frame.size.width = contentWidth - margin * 2
blockNameView?.alphaValue = 0.5
}
let itemView = BlockView(frame: NSRect(x: 0, y: 0, width: contentWidth + margin * 2, height: contentHeight + margin * 2))
var sliderPosition = CGFloat(margin * -1 + 1)
for addedSliderHandler in addedSliderHandlers {
addedSliderHandler.view!.setFrameOrigin(NSPoint(x: margin, y: margin + sliderPosition + 13))
itemView.addSubview(addedSliderHandler.view!)
sliderPosition += addedSliderHandler.view!.frame.height
}
if let blockNameView = blockNameView {
blockNameView.setFrameOrigin(NSPoint(x: margin + 13, y: contentHeight - 8))
itemView.addSubview(blockNameView)
}
let item = NSMenuItem()
item.view = itemView
if addedSliderHandlers.count != 0 {
monitorSubMenu.insertItem(item, at: 0)
}
} else {
for addedSliderHandler in addedSliderHandlers {
self.addSliderItem(monitorSubMenu: monitorSubMenu, sliderHandler: addedSliderHandler)
}
}
self.appendMenuHeader(friendlyName: blockName, monitorSubMenu: monitorSubMenu, asSubMenu: asSubMenu, numOfDisplays: numOfDisplays)
}
func addCombinedDisplayMenuBlock() {
if let sliderHandler = self.combinedSliderHandler[.audioSpeakerVolume] {
self.addSliderItem(monitorSubMenu: self, sliderHandler: sliderHandler)
}
if let sliderHandler = self.combinedSliderHandler[.contrast] {
self.addSliderItem(monitorSubMenu: self, sliderHandler: sliderHandler)
}
if let sliderHandler = self.combinedSliderHandler[.brightness] {
self.addSliderItem(monitorSubMenu: self, sliderHandler: sliderHandler)
}
}
func updateDisplayMenu(display: Display, asSubMenu: Bool, numOfDisplays: Int) {
os_log("Addig menu items for display %{public}@", type: .info, "\(display.identifier)")
let monitorSubMenu: NSMenu = asSubMenu ? NSMenu() : self
var addedSliderHandlers: [SliderHandler] = []
display.sliderHandler[.audioSpeakerVolume] = nil
if let otherDisplay = display as? OtherDisplay, !otherDisplay.isSw(), !display.readPrefAsBool(key: .unavailableDDC, for: .audioSpeakerVolume), !prefs.bool(forKey: PrefKey.hideVolume.rawValue) {
let title = NSLocalizedString("Volume", comment: "Shown in menu")
addedSliderHandlers.append(self.setupMenuSliderHandler(command: .audioSpeakerVolume, display: display, title: title))
}
display.sliderHandler[.contrast] = nil
if let otherDisplay = display as? OtherDisplay, !otherDisplay.isSw(), !display.readPrefAsBool(key: .unavailableDDC, for: .contrast), prefs.bool(forKey: PrefKey.showContrast.rawValue) {
let title = NSLocalizedString("Contrast", comment: "Shown in menu")
addedSliderHandlers.append(self.setupMenuSliderHandler(command: .contrast, display: display, title: title))
}
display.sliderHandler[.brightness] = nil
if !display.readPrefAsBool(key: .unavailableDDC, for: .brightness), !prefs.bool(forKey: PrefKey.hideBrightness.rawValue) {
let title = NSLocalizedString("Brightness", comment: "Shown in menu")
addedSliderHandlers.append(self.setupMenuSliderHandler(command: .brightness, display: display, title: title))
}
if prefs.integer(forKey: PrefKey.multiSliders.rawValue) != MultiSliders.combine.rawValue {
self.addDisplayMenuBlock(addedSliderHandlers: addedSliderHandlers, blockName: display.readPrefAsString(key: .friendlyName) != "" ? display.readPrefAsString(key: .friendlyName) : display.name, monitorSubMenu: monitorSubMenu, numOfDisplays: numOfDisplays, asSubMenu: asSubMenu)
}
if addedSliderHandlers.count > 0, prefs.integer(forKey: PrefKey.menuIcon.rawValue) == MenuIcon.sliderOnly.rawValue {
app.updateStatusItemVisibility(true)
}
}
private func appendMenuHeader(friendlyName: String, monitorSubMenu: NSMenu, asSubMenu: Bool, numOfDisplays: Int) {
let monitorMenuItem = NSMenuItem()
if asSubMenu {
monitorMenuItem.title = "\(friendlyName)"
monitorMenuItem.submenu = monitorSubMenu
self.insertItem(monitorMenuItem, at: 0)
} else if app.macOS10(), numOfDisplays > 1 {
let attrs: [NSAttributedString.Key: Any] = [.foregroundColor: NSColor.systemGray, .font: NSFont.boldSystemFont(ofSize: 12)]
monitorMenuItem.attributedTitle = NSAttributedString(string: "\(friendlyName)", attributes: attrs)
self.insertItem(monitorMenuItem, at: 0)
}
}
func updateMenuRelevantDisplay() {
if prefs.integer(forKey: PrefKey.multiSliders.rawValue) == MultiSliders.relevant.rawValue {
if let display = DisplayManager.shared.getCurrentDisplay(), display.identifier != self.lastMenuRelevantDisplayId {
os_log("Menu must be refreshed as relevant display changed since last time.")
self.lastMenuRelevantDisplayId = display.identifier
self.updateMenus(dontClose: true)
}
}
}
func addDefaultMenuOptions() {
if !DEBUG_MACOS10, #available(macOS 11.0, *), prefs.integer(forKey: PrefKey.menuItemStyle.rawValue) == MenuItemStyle.icon.rawValue {
let iconSize = CGFloat(18)
let viewWidth = max(130, self.size.width)
var compensateForBlock: CGFloat = 0
if viewWidth > 230 { // if there are display blocks, we need to compensate a bit for the negative inset of the blocks
compensateForBlock = 4
}
let menuItemView = NSView(frame: NSRect(x: 0, y: 0, width: viewWidth, height: iconSize + 10))
let settingsIcon = NSButton()
settingsIcon.bezelStyle = .regularSquare
settingsIcon.isBordered = false
settingsIcon.setButtonType(.momentaryChange)
settingsIcon.image = NSImage(systemSymbolName: "gearshape", accessibilityDescription: NSLocalizedString("Settings…", comment: "Shown in menu"))
settingsIcon.alternateImage = NSImage(systemSymbolName: "gearshape.fill", accessibilityDescription: NSLocalizedString("Settings…", comment: "Shown in menu"))
settingsIcon.alphaValue = 0.3
settingsIcon.frame = NSRect(x: menuItemView.frame.maxX - iconSize * 3 - 20 - 17 + compensateForBlock, y: menuItemView.frame.origin.y + 5, width: iconSize, height: iconSize)
settingsIcon.imageScaling = .scaleProportionallyUpOrDown
settingsIcon.action = #selector(app.prefsClicked)
let updateIcon = NSButton()
updateIcon.bezelStyle = .regularSquare
updateIcon.isBordered = false
updateIcon.setButtonType(.momentaryChange)
var symbolName = prefs.bool(forKey: PrefKey.showTickMarks.rawValue) ? "arrow.left.arrow.right.square" : "arrow.triangle.2.circlepath.circle"
updateIcon.image = NSImage(systemSymbolName: symbolName, accessibilityDescription: NSLocalizedString("Check for updates…", comment: "Shown in menu"))
updateIcon.alternateImage = NSImage(systemSymbolName: symbolName + ".fill", accessibilityDescription: NSLocalizedString("Check for updates…", comment: "Shown in menu"))
updateIcon.alphaValue = 0.3
updateIcon.frame = NSRect(x: menuItemView.frame.maxX - iconSize * 2 - 14 - 17 + compensateForBlock, y: menuItemView.frame.origin.y + 5, width: iconSize, height: iconSize)
updateIcon.imageScaling = .scaleProportionallyUpOrDown
updateIcon.action = #selector(app.updaterController.checkForUpdates(_:))
updateIcon.target = app.updaterController
let quitIcon = NSButton()
quitIcon.bezelStyle = .regularSquare
quitIcon.isBordered = false
quitIcon.setButtonType(.momentaryChange)
symbolName = prefs.bool(forKey: PrefKey.showTickMarks.rawValue) ? "multiply.square" : "xmark.circle"
quitIcon.image = NSImage(systemSymbolName: symbolName, accessibilityDescription: NSLocalizedString("Quit", comment: "Shown in menu"))
quitIcon.alternateImage = NSImage(systemSymbolName: symbolName + ".fill", accessibilityDescription: NSLocalizedString("Quit", comment: "Shown in menu"))
quitIcon.alphaValue = 0.3
quitIcon.frame = NSRect(x: menuItemView.frame.maxX - iconSize - 17 + compensateForBlock, y: menuItemView.frame.origin.y + 5, width: iconSize, height: iconSize)
quitIcon.imageScaling = .scaleProportionallyUpOrDown
quitIcon.action = #selector(app.quitClicked)
menuItemView.addSubview(settingsIcon)
menuItemView.addSubview(updateIcon)
menuItemView.addSubview(quitIcon)
let item = NSMenuItem()
item.view = menuItemView
self.insertItem(item, at: self.items.count)
} else if prefs.integer(forKey: PrefKey.menuItemStyle.rawValue) != MenuItemStyle.hide.rawValue {
if app.macOS10() {
self.insertItem(NSMenuItem.separator(), at: self.items.count)
}
self.insertItem(withTitle: NSLocalizedString("Settings…", comment: "Shown in menu"), action: #selector(app.prefsClicked), keyEquivalent: ",", at: self.items.count)
let updateItem = NSMenuItem(title: NSLocalizedString("Check for updates…", comment: "Shown in menu"), action: #selector(app.updaterController.checkForUpdates(_:)), keyEquivalent: "")
updateItem.target = app.updaterController
self.insertItem(updateItem, at: self.items.count)
self.insertItem(withTitle: NSLocalizedString("Quit", comment: "Shown in menu"), action: #selector(app.quitClicked), keyEquivalent: "q", at: self.items.count)
}
}
}
================================================
FILE: MonitorControl/Support/OSDUtils.swift
================================================
// Copyright © MonitorControl. @victorchabbert, @JoniVR, @theOneyouseek, @waydabber and others
import Cocoa
class OSDUtils: NSObject {
enum OSDImage: Int64 {
case brightness = 1
case audioSpeaker = 3
case audioSpeakerMuted = 4
case contrast = 0
}
static func getOSDImageByCommand(command: Command, value: Float = 1) -> OSDImage {
var osdImage: OSDImage
switch command {
case .audioSpeakerVolume: osdImage = value > 0 ? .audioSpeaker : .audioSpeakerMuted
case .audioMuteScreenBlank: osdImage = .audioSpeakerMuted
case .contrast: osdImage = .contrast
default: osdImage = .brightness
}
return osdImage
}
static func showOsd(displayID: CGDirectDisplayID, command: Command, value: Float, maxValue: Float = 1, roundChiclet: Bool = false, lock: Bool = false) {
guard let manager = OSDManager.sharedManager() as? OSDManager else {
return
}
let osdImage = self.getOSDImageByCommand(command: command, value: value)
let filledChiclets: Int
let totalChiclets: Int
if roundChiclet {
let osdChiclet = OSDUtils.chiclet(fromValue: value, maxValue: maxValue)
filledChiclets = Int(round(osdChiclet))
totalChiclets = 16
} else {
filledChiclets = Int(value * 100)
totalChiclets = Int(maxValue * 100)
}
manager.showImage(osdImage.rawValue, onDisplayID: displayID, priority: 0x1F4, msecUntilFade: 1000, filledChiclets: UInt32(filledChiclets), totalChiclets: UInt32(totalChiclets), locked: lock)
}
static func showOsdVolumeDisabled(displayID: CGDirectDisplayID) {
guard let manager = OSDManager.sharedManager() as? OSDManager else {
return
}
manager.showImage(22, onDisplayID: displayID, priority: 0x1F4, msecUntilFade: 1000)
}
static func showOsdMuteDisabled(displayID: CGDirectDisplayID) {
guard let manager = OSDManager.sharedManager() as? OSDManager else {
return
}
manager.showImage(21, onDisplayID: displayID, priority: 0x1F4, msecUntilFade: 1000)
}
static func popEmptyOsd(displayID: CGDirectDisplayID, command: Command) {
guard let manager = OSDManager.sharedManager() as? OSDManager else {
return
}
let osdImage = self.getOSDImageByCommand(command: command)
manager.showImage(osdImage.rawValue, onDisplayID: displayID, priority: 0x1F4, msecUntilFade: 0)
}
static let chicletCount: Float = 16
static func chiclet(fromValue value: Float, maxValue: Float, half: Bool = false) -> Float {
(value * self.chicletCount * (half ? 2 : 1)) / maxValue
}
static func value(fromChiclet chiclet: Float, maxValue: Float, half: Bool = false) -> Float {
(chiclet * maxValue) / (self.chicletCount * (half ? 2 : 1))
}
static func getDistance(fromNearestChiclet chiclet: Float) -> Float {
abs(chiclet.rounded(.towardZero) - chiclet)
}
}
================================================
FILE: MonitorControl/Support/SliderHandler.swift
================================================
// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others
import Cocoa
import os.log
class SliderHandler {
var slider: MCSlider?
var view: NSView?
var percentageBox: NSTextField?
var displays: [Display] = []
var values: [CGDirectDisplayID: Float] = [:]
var title: String
let command: Command
var icon: ClickThroughImageView?
class MCSliderCell: NSSliderCell {
let knobFillColor = NSColor(white: 1, alpha: 1)
let knobFillColorTracking = NSColor(white: 0.8, alpha: 1)
let knobStrokeColor = NSColor.systemGray.withAlphaComponent(0.5)
let knobShadowColor = NSColor(white: 0, alpha: 0.03)
let barFillColor = NSColor.systemGray.withAlphaComponent(0.2)
let barStrokeColor = NSColor.systemGray.withAlphaComponent(0.5)
let barFilledFillColor = NSColor(white: 1, alpha: 1)
let highlightDisplayIndicatorColor = NSColor(white: 0.85, alpha: 1) // This is visible if there is more the 2 displays
let tickMarkColor = NSColor.systemGray.withAlphaComponent(0.5)
let inset: CGFloat = 3.5
let offsetX: CGFloat = -1.5
let offsetY: CGFloat = -1.5
let tickMarkKnobExtraInset: CGFloat = 4
let tickMarkKnobExtraRadiusMultiplier: CGFloat = 0.25
var numOfTickmarks: Int = 0
var isHighlightDisplayItems: Bool = false
var displayHighlightItems: [CGDirectDisplayID: Float] = [:]
var isTracking: Bool = false
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init() {
super.init()
}
override func barRect(flipped: Bool) -> NSRect {
let bar = super.barRect(flipped: flipped)
let knob = super.knobRect(flipped: flipped)
return NSRect(x: bar.origin.x, y: knob.origin.y, width: bar.width, height: knob.height).insetBy(dx: 0, dy: self.inset).offsetBy(dx: self.offsetX, dy: self.offsetY)
}
override func startTracking(at startPoint: NSPoint, in controlView: NSView) -> Bool {
self.isTracking = true
return super.startTracking(at: startPoint, in: controlView)
}
override func stopTracking(last lastPoint: NSPoint, current stopPoint: NSPoint, in controlView: NSView, mouseIsUp flag: Bool) {
self.isTracking = false
return super.stopTracking(last: lastPoint, current: stopPoint, in: controlView, mouseIsUp: flag)
}
override func drawKnob(_ knobRect: NSRect) {
guard !DEBUG_MACOS10, #available(macOS 11.0, *) else {
super.drawKnob(knobRect)
return
}
// This is intentionally empty as the knob is inside the bar. Please leave it like this!
}
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()
}
}
class MCSlider: NSSlider {
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.cell = MCSliderCell()
}
func setNumOfCustomTickmarks(_ numOfCustomTickmarks: Int) {
if let cell = self.cell as? MCSliderCell {
cell.numOfTickmarks = numOfCustomTickmarks
}
}
func setDisplayHighlightItems(_ isHighlightDisplayItems: Bool) {
if let cell = self.cell as? MCSliderCell {
cell.isHighlightDisplayItems = isHighlightDisplayItems
}
}
func setHighlightItem(_ displayID: CGDirectDisplayID, value: Float) {
if let cell = self.cell as? MCSliderCell {
cell.displayHighlightItems[displayID] = value
}
}
func removeHighlightItem(_ displayID: CGDirectDisplayID) {
if let cell = self.cell as? MCSliderCell {
if cell.displayHighlightItems[displayID] != nil {
cell.displayHighlightItems[displayID] = nil
}
}
}
func resetHighlightItems() {
if let cell = self.cell as? MCSliderCell {
cell.displayHighlightItems.removeAll()
}
}
// Credits for this class go to @thompsonate - https://github.com/thompsonate/Scrollable-NSSlider
override func scrollWheel(with event: NSEvent) {
guard self.isEnabled else { return }
let range = Float(self.maxValue - self.minValue)
var delta = Float(0)
if self.isVertical, self.sliderType == .linear {
delta = Float(event.deltaY)
} else if self.userInterfaceLayoutDirection == .rightToLeft {
delta = Float(event.deltaY + event.deltaX)
} else {
delta = Float(event.deltaY - event.deltaX)
}
if event.isDirectionInvertedFromDevice {
delta *= -1
}
let increment = range * delta / 100
let value = self.floatValue + increment
self.floatValue = value
self.sendAction(self.action, to: self.target)
}
}
class ClickThroughImageView: NSImageView {
override func hitTest(_ point: NSPoint) -> NSView? {
subviews.first { subview in subview.hitTest(point) != nil
}
}
}
public init(display: Display?, command: Command, title: String = "", position _: Int = 0) {
self.command = command
self.title = title
let slider = SliderHandler.MCSlider(value: 0, minValue: 0, maxValue: 1, target: self, action: #selector(SliderHandler.valueChanged))
let showPercent = prefs.bool(forKey: PrefKey.enableSliderPercent.rawValue)
slider.isEnabled = true
slider.setNumOfCustomTickmarks(prefs.bool(forKey: PrefKey.showTickMarks.rawValue) ? 5 : 0)
self.slider = slider
if !DEBUG_MACOS10, #available(macOS 11.0, *) {
slider.frame.size.width = 180
slider.frame.origin = NSPoint(x: 15, y: 5)
let view = NSView(frame: NSRect(x: 0, y: 0, width: slider.frame.width + 30 + (showPercent ? 38 : 0), height: slider.frame.height + 14))
view.frame.origin = NSPoint(x: 12, y: 0)
var iconName = "circle.dashed"
switch command {
case .audioSpeakerVolume: iconName = "speaker.wave.2.fill"
case .brightness: iconName = "sun.max.fill"
case .contrast: iconName = "circle.lefthalf.fill"
default: break
}
let icon = SliderHandler.ClickThroughImageView()
icon.image = NSImage(systemSymbolName: iconName, accessibilityDescription: title)
icon.contentTintColor = NSColor.black.withAlphaComponent(0.6)
icon.frame = NSRect(x: view.frame.origin.x + 6.5, y: view.frame.origin.y + 13, width: 15, height: 15)
icon.imageAlignment = .alignCenter
view.addSubview(slider)
view.addSubview(icon)
self.icon = icon
if showPercent {
let percentageBox = NSTextField(frame: NSRect(x: 15 + slider.frame.size.width - 2, y: 17, width: 40, height: 12))
self.setupPercentageBox(percentageBox)
self.percentageBox = percentageBox
view.addSubview(percentageBox)
}
self.view = view
} else {
slider.frame.size.width = 180
slider.frame.origin = NSPoint(x: 15, y: 5)
let view = NSView(frame: NSRect(x: 0, y: 0, width: slider.frame.width + 30 + (showPercent ? 38 : 0), height: slider.frame.height + 10))
view.addSubview(slider)
if showPercent {
let percentageBox = NSTextField(frame: NSRect(x: 15 + slider.frame.size.width - 2, y: 18, width: 40, height: 12))
self.setupPercentageBox(percentageBox)
self.percentageBox = percentageBox
view.addSubview(percentageBox)
}
self.view = view
}
slider.maxValue = 1
if let displayToAppend = display {
self.addDisplay(displayToAppend)
}
}
func addDisplay(_ display: Display) {
self.displays.append(display)
if let otherDisplay = display as? OtherDisplay {
let value = otherDisplay.setupSliderCurrentValue(command: self.command)
self.setValue(value, displayID: otherDisplay.identifier)
} else if let appleDisplay = display as? AppleDisplay {
if self.command == .brightness {
self.setValue(appleDisplay.getAppleBrightness(), displayID: appleDisplay.identifier)
}
}
}
func setupPercentageBox(_ percentageBox: NSTextField) {
percentageBox.font = NSFont.systemFont(ofSize: 12)
percentageBox.isEditable = false
percentageBox.isBordered = false
percentageBox.drawsBackground = false
percentageBox.alignment = .right
percentageBox.alphaValue = 0.7
}
func valueChangedOtherDisplay(otherDisplay: OtherDisplay, value: Float) {
// For the speaker volume slider, also set/unset the mute command when the value is changed from/to 0
if self.command == .audioSpeakerVolume, (otherDisplay.readPrefAsInt(for: .audioMuteScreenBlank) == 1 && value > 0) || (otherDisplay.readPrefAsInt(for: .audioMuteScreenBlank) != 1 && value == 0) {
otherDisplay.toggleMute(fromVolumeSlider: true)
}
if self.command == Command.brightness {
_ = otherDisplay.setBrightness(value)
return
} else if !otherDisplay.isSw() {
if self.command == Command.audioSpeakerVolume {
if !otherDisplay.readPrefAsBool(key: .enableMuteUnmute) || value != 0 {
otherDisplay.writeDDCValues(command: self.command, value: otherDisplay.convValueToDDC(for: self.command, from: value))
}
} else {
otherDisplay.writeDDCValues(command: self.command, value: otherDisplay.convValueToDDC(for: self.command, from: value))
}
otherDisplay.savePref(value, for: s
gitextract_6yai_s7z/ ├── .bartycrouch.toml ├── .github/ │ ├── .dependabot.yml │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── discussion.yml │ │ ├── feature_request.yml │ │ ├── monitor-issue.yml │ │ └── question.yml │ ├── MonitorControl Icon.fig │ └── stale.yml ├── .gitignore ├── .swift-version ├── .swiftformat ├── .swiftlint.yml ├── License.txt ├── MonitorControl/ │ ├── Assets.xcassets/ │ │ ├── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── onboarding_icon_checkmark.imageset/ │ │ │ └── Contents.json │ │ ├── onboarding_icon_keyboard.imageset/ │ │ │ └── Contents.json │ │ ├── onboarding_icon_person.imageset/ │ │ │ └── Contents.json │ │ ├── onboarding_keyboard.imageset/ │ │ │ └── Contents.json │ │ └── status.imageset/ │ │ └── Contents.json │ ├── Enums/ │ │ ├── Command.swift │ │ └── PrefKey.swift │ ├── Extensions/ │ │ ├── CGDirectDisplayID+Extension.swift │ │ ├── KeyboardShortcuts+Extension.swift │ │ ├── NSNotification+Extension.swift │ │ ├── NSScreen+Extension.swift │ │ └── Preferences+Extension.swift │ ├── Info.plist │ ├── InternetAccessPolicy.plist │ ├── Model/ │ │ ├── AppleDisplay.swift │ │ ├── Display.swift │ │ └── OtherDisplay.swift │ ├── MonitorControl.entitlements │ ├── MonitorControlDebug.entitlements │ ├── Support/ │ │ ├── AppDelegate.swift │ │ ├── Arm64DDC.swift │ │ ├── Bridging-Header.h │ │ ├── DisplayManager.swift │ │ ├── IntelDDC.swift │ │ ├── KeyboardShortcutsManager.swift │ │ ├── MediaKeyTapManager.swift │ │ ├── MenuHandler.swift │ │ ├── OSDUtils.swift │ │ ├── SliderHandler.swift │ │ └── UpdaterDelegate.swift │ ├── UI/ │ │ ├── Base.lproj/ │ │ │ └── Main.storyboard │ │ ├── cs.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ ├── de.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ ├── en.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ ├── es.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ ├── fr.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ ├── hi.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ ├── hu.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ ├── it.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ ├── ja.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ ├── ko.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ ├── nl.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ ├── pl.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ ├── pt-BR.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ ├── pt-PT.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ ├── ru.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ ├── sk.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ ├── tr.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ ├── zh-Hans.lproj/ │ │ │ ├── InternetAccessPolicy.strings │ │ │ ├── Localizable.strings │ │ │ └── Main.strings │ │ └── zh-Hant-TW.lproj/ │ │ ├── InternetAccessPolicy.strings │ │ ├── Localizable.strings │ │ └── Main.strings │ ├── View Controllers/ │ │ ├── Onboarding/ │ │ │ └── OnboardingViewController.swift │ │ └── Preferences/ │ │ ├── AboutPrefsViewController.swift │ │ ├── DisplaysPrefsCellView.swift │ │ ├── DisplaysPrefsViewController.swift │ │ ├── KeyboardPrefsViewController.swift │ │ ├── MainPrefsViewController.swift │ │ └── MenuslidersPrefsViewController.swift │ └── main.swift ├── MonitorControl.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm/ │ │ └── Package.resolved │ └── xcshareddata/ │ └── xcschemes/ │ └── MonitorControl.xcscheme ├── MonitorControlHelper/ │ ├── Info.plist │ ├── MonitorControlHelper.entitlements │ └── main.swift └── README.md
SYMBOL INDEX (2 symbols across 1 files) FILE: MonitorControl/Support/Bridging-Header.h type CFTypeRef (line 9) | typedef CFTypeRef IOAVService; function interface (line 39) | interface OSDManager : NSObject <OSDUIHelperProtocol>
Condensed preview — 123 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,286K chars).
[
{
"path": ".bartycrouch.toml",
"chars": 661,
"preview": "[update]\ntasks = [\"interfaces\", \"code\", \"normalize\"]\n\n[update.interfaces]\npaths = [\".\"]\ndefaultToBase = true\nignoreEmpty"
},
{
"path": ".github/.dependabot.yml",
"chars": 115,
"preview": "version: 2\nupdates:\n - package-ecosystem: \"github-actions\"\n directory: /\n schedule:\n interval: \"weekly\""
},
{
"path": ".github/FUNDING.yml",
"chars": 79,
"preview": "# These are supported funding model platforms\n\nopen_collective: monitorcontrol\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 2663,
"preview": "name: Bug report\ndescription: Create a report to help us improve\nlabels: [\"Bug\"]\nassignees: []\nbody:\n - type: checkboxe"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 28,
"preview": "blank_issues_enabled: false\n"
},
{
"path": ".github/ISSUE_TEMPLATE/discussion.yml",
"chars": 712,
"preview": "name: Discussion\ndescription: I want to discuss something that is related to this project\nlabels: [\"Other\"]\nassignees: ["
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.yml",
"chars": 1471,
"preview": "name: Feature request\ndescription: Suggest an idea for this project\nlabels: [\"Feature Request\"]\nassignees: []\nbody:\n - "
},
{
"path": ".github/ISSUE_TEMPLATE/monitor-issue.yml",
"chars": 2384,
"preview": "name: Monitor Issue\ndescription: MonitorControl is not working as expected on my monitor.\nlabels: [\"Monitor Issue\"]\nassi"
},
{
"path": ".github/ISSUE_TEMPLATE/question.yml",
"chars": 633,
"preview": "name: Question\ndescription: I have a question related to this project\nlabels: [\"Question\"]\nassignees: []\nbody:\n - type:"
},
{
"path": ".github/stale.yml",
"chars": 815,
"preview": "# Number of days of inactivity before an issue becomes stale\ndaysUntilStale: 365\n# Number of days of inactivity before a"
},
{
"path": ".gitignore",
"chars": 77,
"preview": "### Carthage ###\nCarthage\n\n### macOS ###\n.DS_Store\n\n### Xcode ###\nxcuserdata/"
},
{
"path": ".swift-version",
"chars": 4,
"preview": "5.5\n"
},
{
"path": ".swiftformat",
"chars": 88,
"preview": "--indent 2\n--nospaceoperators\n--self insert\n--exponentcase lowercase\n--exclude Carthage\n"
},
{
"path": ".swiftlint.yml",
"chars": 271,
"preview": "disabled_rules:\n - line_length\n - function_body_length\n - identifier_name\n - trailing_comma\n - large_tuple\ntype_bod"
},
{
"path": "License.txt",
"chars": 1053,
"preview": "MIT License\n\nCopyright © 2017\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this soft"
},
{
"path": "MonitorControl/Assets.xcassets/AppIcon.appiconset/Contents.json",
"chars": 1251,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"Icon-16.png\",\n \"idiom\" : \"mac\",\n \"scale\" : \"1x\",\n \"size\" : \"16"
},
{
"path": "MonitorControl/Assets.xcassets/Contents.json",
"chars": 63,
"preview": "{\n \"info\" : {\n \"author\" : \"xcode\",\n \"version\" : 1\n }\n}\n"
},
{
"path": "MonitorControl/Assets.xcassets/onboarding_icon_checkmark.imageset/Contents.json",
"chars": 271,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"checkmark.png\",\n \"idiom\" : \"mac\",\n \"scale\" : \"1x\"\n },\n {\n "
},
{
"path": "MonitorControl/Assets.xcassets/onboarding_icon_keyboard.imageset/Contents.json",
"chars": 279,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"icon_keyboard.png\",\n \"idiom\" : \"mac\",\n \"scale\" : \"1x\"\n },\n {\n"
},
{
"path": "MonitorControl/Assets.xcassets/onboarding_icon_person.imageset/Contents.json",
"chars": 275,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"icon_person.png\",\n \"idiom\" : \"mac\",\n \"scale\" : \"1x\"\n },\n {\n "
},
{
"path": "MonitorControl/Assets.xcassets/onboarding_keyboard.imageset/Contents.json",
"chars": 748,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"onboarding_keyboard.png\",\n \"idiom\" : \"mac\",\n \"scale\" : \"1x\"\n },\n"
},
{
"path": "MonitorControl/Assets.xcassets/status.imageset/Contents.json",
"chars": 409,
"preview": "{\n \"images\" : [\n {\n \"filename\" : \"status.pdf\",\n \"idiom\" : \"universal\",\n \"scale\" : \"1x\"\n },\n {\n "
},
{
"path": "MonitorControl/Enums/Command.swift",
"chars": 5208,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nenum Command: UInt8 {\n case none = 0\n\n "
},
{
"path": "MonitorControl/Enums/PrefKey.swift",
"chars": 4317,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nenum PrefKey: String {\n /* -- App-wide "
},
{
"path": "MonitorControl/Extensions/CGDirectDisplayID+Extension.swift",
"chars": 331,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Cocoa\n\npublic extension CGDirectD"
},
{
"path": "MonitorControl/Extensions/KeyboardShortcuts+Extension.swift",
"chars": 492,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport KeyboardShortcuts\n\nextension Keyb"
},
{
"path": "MonitorControl/Extensions/NSNotification+Extension.swift",
"chars": 141,
"preview": "import Cocoa\n\nextension NSNotification.Name {\n static let accessibilityApi = NSNotification.Name(rawValue: \"com.apple.a"
},
{
"path": "MonitorControl/Extensions/NSScreen+Extension.swift",
"chars": 1789,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Cocoa\n\npublic extension NSScreen "
},
{
"path": "MonitorControl/Extensions/Preferences+Extension.swift",
"chars": 632,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Cocoa\n\nimport Settings\n\nextension"
},
{
"path": "MonitorControl/Info.plist",
"chars": 1314,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "MonitorControl/InternetAccessPolicy.plist",
"chars": 1497,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "MonitorControl/Model/AppleDisplay.swift",
"chars": 2650,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Foundation\nimport os.log\n\nclass A"
},
{
"path": "MonitorControl/Model/Display.swift",
"chars": 15057,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Cocoa\nimport Foundation\nimport os"
},
{
"path": "MonitorControl/Model/OtherDisplay.swift",
"chars": 25175,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Cocoa\nimport IOKit\nimport os.log\n"
},
{
"path": "MonitorControl/MonitorControl.entitlements",
"chars": 241,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "MonitorControl/MonitorControlDebug.entitlements",
"chars": 258,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "MonitorControl/Support/AppDelegate.swift",
"chars": 15857,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport AVFoundation\nimport Cocoa\nimport "
},
{
"path": "MonitorControl/Support/Arm64DDC.swift",
"chars": 14330,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Foundation\nimport IOKit\n\nlet ARM6"
},
{
"path": "MonitorControl/Support/Bridging-Header.h",
"chars": 3453,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\n#pragma once\n\n#import <Foundation/Founda"
},
{
"path": "MonitorControl/Support/DisplayManager.swift",
"chars": 24152,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Cocoa\nimport CoreGraphics\nimport "
},
{
"path": "MonitorControl/Support/IntelDDC.swift",
"chars": 11741,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n// Adapted from IntelDDC.swift, @reiterm"
},
{
"path": "MonitorControl/Support/KeyboardShortcutsManager.swift",
"chars": 7145,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Foundation\nimport KeyboardShortcu"
},
{
"path": "MonitorControl/Support/MediaKeyTapManager.swift",
"chars": 11148,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport AudioToolbox\nimport Cocoa\nimport "
},
{
"path": "MonitorControl/Support/MenuHandler.swift",
"chars": 15536,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport AppKit\nimport os.log\n\nclass MenuH"
},
{
"path": "MonitorControl/Support/OSDUtils.swift",
"chars": 2842,
"preview": "// Copyright © MonitorControl. @victorchabbert, @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Cocoa\n\nclass OSD"
},
{
"path": "MonitorControl/Support/SliderHandler.swift",
"chars": 16437,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Cocoa\nimport os.log\n\nclass Slider"
},
{
"path": "MonitorControl/Support/UpdaterDelegate.swift",
"chars": 314,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Foundation\nimport Sparkle\n\nclass "
},
{
"path": "MonitorControl/UI/Base.lproj/Main.storyboard",
"chars": 267551,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB\" version=\"3.0\" t"
},
{
"path": "MonitorControl/UI/cs.lproj/InternetAccessPolicy.strings",
"chars": 546,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"MonitorControl umožňuje ovládat jas a hlasitost extern"
},
{
"path": "MonitorControl/UI/cs.lproj/Localizable.strings",
"chars": 6782,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"O aplikaci\";\n\n/* Shown in the alert dialog */\n\"An other app seems to cha"
},
{
"path": "MonitorControl/UI/cs.lproj/Main.strings",
"chars": 24735,
"preview": "/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\"; "
},
{
"path": "MonitorControl/UI/de.lproj/InternetAccessPolicy.strings",
"chars": 570,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"MonitorControl ermöglicht die Steuerung von Helligkeit"
},
{
"path": "MonitorControl/UI/de.lproj/Localizable.strings",
"chars": 7243,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"Über\";\n\n/* Shown in the alert dialog */\n\"An other app seems to change th"
},
{
"path": "MonitorControl/UI/de.lproj/Main.strings",
"chars": 25760,
"preview": "/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\"; "
},
{
"path": "MonitorControl/UI/en.lproj/InternetAccessPolicy.strings",
"chars": 516,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"MonitorControl allows you to control external displays"
},
{
"path": "MonitorControl/UI/en.lproj/Localizable.strings",
"chars": 6680,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"About\";\n\n/* Shown in the alert dialog */\n\"An other app seems to change t"
},
{
"path": "MonitorControl/UI/en.lproj/Main.strings",
"chars": 24334,
"preview": "/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\"; "
},
{
"path": "MonitorControl/UI/es.lproj/InternetAccessPolicy.strings",
"chars": 571,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"MonitorControl te permite controlar el brillo y el vol"
},
{
"path": "MonitorControl/UI/es.lproj/Localizable.strings",
"chars": 7135,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"Acerca de MonitorControl\";\n\n/* Shown in the alert dialog */\n\"An other ap"
},
{
"path": "MonitorControl/UI/es.lproj/Main.strings",
"chars": 25538,
"preview": "/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\"; "
},
{
"path": "MonitorControl/UI/fr.lproj/InternetAccessPolicy.strings",
"chars": 598,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"MonitorControl vous permet de contrôler la luminosité "
},
{
"path": "MonitorControl/UI/fr.lproj/Localizable.strings",
"chars": 7129,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"À Propos\";\n\n/* Shown in the alert dialog */\n\"An other app seems to chang"
},
{
"path": "MonitorControl/UI/fr.lproj/Main.strings",
"chars": 26060,
"preview": "/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\"; "
},
{
"path": "MonitorControl/UI/hi.lproj/InternetAccessPolicy.strings",
"chars": 537,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"मॉनिटर कंट्रोल आपको बाहरी प्रदर्शन की चमक और मात्रा को"
},
{
"path": "MonitorControl/UI/hi.lproj/Localizable.strings",
"chars": 7062,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"के बारे में\";\n\n/* Shown in the alert dialog */\n\"An other app seems to ch"
},
{
"path": "MonitorControl/UI/hi.lproj/Main.strings",
"chars": 26295,
"preview": "\n/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\";"
},
{
"path": "MonitorControl/UI/hu.lproj/InternetAccessPolicy.strings",
"chars": 545,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"A MonitorControl a külső kijelző fényerejének és hange"
},
{
"path": "MonitorControl/UI/hu.lproj/Localizable.strings",
"chars": 7114,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"Névjegy\";\n\n/* Shown in the alert dialog */\n\"An other app seems to change"
},
{
"path": "MonitorControl/UI/hu.lproj/Main.strings",
"chars": 24448,
"preview": "/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\"; "
},
{
"path": "MonitorControl/UI/it.lproj/InternetAccessPolicy.strings",
"chars": 551,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"MonitorControl ti permette di controllare la luminosit"
},
{
"path": "MonitorControl/UI/it.lproj/Localizable.strings",
"chars": 6976,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"Informazioni\";\n\n/* Shown in the alert dialog */\n\"An other app seems to c"
},
{
"path": "MonitorControl/UI/it.lproj/Main.strings",
"chars": 25171,
"preview": "/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\"; "
},
{
"path": "MonitorControl/UI/ja.lproj/InternetAccessPolicy.strings",
"chars": 378,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"MonitorControlを使用すると、外部ディスプレイの明るさと音量を制御できます\";\n\n/* Sofw"
},
{
"path": "MonitorControl/UI/ja.lproj/Localizable.strings",
"chars": 5809,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"アプリについて\";\n\n/* Shown in the alert dialog */\n\"An other app seems to change"
},
{
"path": "MonitorControl/UI/ja.lproj/Main.strings",
"chars": 23170,
"preview": "\n/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\";"
},
{
"path": "MonitorControl/UI/ko.lproj/InternetAccessPolicy.strings",
"chars": 371,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"MonitorControl를 통해 외부 디스플레이의 밝기와 음량을 제어할 수 있습니다.\";\n\n/*"
},
{
"path": "MonitorControl/UI/ko.lproj/Localizable.strings",
"chars": 5765,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"정보\";\n\n/* Shown in the alert dialog */\n\"An other app seems to change the "
},
{
"path": "MonitorControl/UI/ko.lproj/Main.strings",
"chars": 21986,
"preview": "/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\"; "
},
{
"path": "MonitorControl/UI/nl.lproj/InternetAccessPolicy.strings",
"chars": 546,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"Met MonitorControl kunt u de helderheid en het volume "
},
{
"path": "MonitorControl/UI/nl.lproj/Localizable.strings",
"chars": 7030,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"Over\";\n\n/* Shown in the alert dialog */\n\"An other app seems to change th"
},
{
"path": "MonitorControl/UI/nl.lproj/Main.strings",
"chars": 25308,
"preview": "/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\"; "
},
{
"path": "MonitorControl/UI/pl.lproj/InternetAccessPolicy.strings",
"chars": 583,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"MonitorControl umożliwia sterowanie jasnością i głośno"
},
{
"path": "MonitorControl/UI/pl.lproj/Localizable.strings",
"chars": 7172,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"O programie\";\n\n/* Shown in the alert dialog */\n\"An other app seems to ch"
},
{
"path": "MonitorControl/UI/pl.lproj/Main.strings",
"chars": 25311,
"preview": "/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\"; "
},
{
"path": "MonitorControl/UI/pt-BR.lproj/InternetAccessPolicy.strings",
"chars": 485,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"MonitorControl permite controlar o brilho e o volume d"
},
{
"path": "MonitorControl/UI/pt-BR.lproj/Localizable.strings",
"chars": 6671,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"Sobre\";\n\n/* Shown in the alert dialog */\n\"An other app seems to change t"
},
{
"path": "MonitorControl/UI/pt-BR.lproj/Main.strings",
"chars": 25385,
"preview": "/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\"; "
},
{
"path": "MonitorControl/UI/pt-PT.lproj/InternetAccessPolicy.strings",
"chars": 497,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"O MonitorControl permite controlar o brilho e o volume"
},
{
"path": "MonitorControl/UI/pt-PT.lproj/Localizable.strings",
"chars": 6821,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"Sobre\";\n\n/* Shown in the alert dialog */\n\"An other app seems to change t"
},
{
"path": "MonitorControl/UI/pt-PT.lproj/Main.strings",
"chars": 25548,
"preview": "/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\"; "
},
{
"path": "MonitorControl/UI/ru.lproj/InternetAccessPolicy.strings",
"chars": 529,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"MonitorControl позволяет управлять яркостью и громкост"
},
{
"path": "MonitorControl/UI/ru.lproj/Localizable.strings",
"chars": 7173,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"О программе\";\n\n/* Shown in the alert dialog */\n\"An other app seems to ch"
},
{
"path": "MonitorControl/UI/ru.lproj/Main.strings",
"chars": 25803,
"preview": "/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\"; "
},
{
"path": "MonitorControl/UI/sk.lproj/InternetAccessPolicy.strings",
"chars": 658,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"MonitorControl umožňuje ovládať jas a hlasitosť extern"
},
{
"path": "MonitorControl/UI/sk.lproj/Localizable.strings",
"chars": 7066,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"O aplikácii\";\n\n/* Shown in the alert dialog */\n\"An other app seems to ch"
},
{
"path": "MonitorControl/UI/sk.lproj/Main.strings",
"chars": 24969,
"preview": "\n/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\";"
},
{
"path": "MonitorControl/UI/tr.lproj/InternetAccessPolicy.strings",
"chars": 591,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"MonitorControl, harici ekranların parlaklığını ve ses "
},
{
"path": "MonitorControl/UI/tr.lproj/Localizable.strings",
"chars": 6949,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"Hakkında\";\n\n/* Shown in the alert dialog */\n\"An other app seems to chang"
},
{
"path": "MonitorControl/UI/tr.lproj/Main.strings",
"chars": 25183,
"preview": "/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\"; "
},
{
"path": "MonitorControl/UI/zh-Hans.lproj/InternetAccessPolicy.strings",
"chars": 320,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"MonitorControl让您可以控制外接显示器的亮度与音量\";\n\n/* Sofware update d"
},
{
"path": "MonitorControl/UI/zh-Hans.lproj/Localizable.strings",
"chars": 5261,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"关于\";\n\n/* Shown in the alert dialog */\n\"An other app seems to change the "
},
{
"path": "MonitorControl/UI/zh-Hans.lproj/Main.strings",
"chars": 20681,
"preview": "/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\"; "
},
{
"path": "MonitorControl/UI/zh-Hant-TW.lproj/InternetAccessPolicy.strings",
"chars": 320,
"preview": "/* General application description */\n\"ApplicationDescription\" = \"MonitorControl讓您可以控制外接螢幕的亮度與音量\";\n\n/* Sofware update de"
},
{
"path": "MonitorControl/UI/zh-Hant-TW.lproj/Localizable.strings",
"chars": 5241,
"preview": "/* Shown in the main prefs window */\n\"About\" = \"關於\";\n\n/* Shown in the alert dialog */\n\"An other app seems to change the "
},
{
"path": "MonitorControl/UI/zh-Hant-TW.lproj/Main.strings",
"chars": 20677,
"preview": "/* Class = \"NSButtonCell\"; title = \"Sync brightness changes from Built-in and Apple displays\"; ObjectID = \"0ca-DG-AgB\"; "
},
{
"path": "MonitorControl/View Controllers/Onboarding/OnboardingViewController.swift",
"chars": 1318,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Cocoa\n\nclass OnboardingViewContro"
},
{
"path": "MonitorControl/View Controllers/Preferences/AboutPrefsViewController.swift",
"chars": 2137,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Cocoa\nimport ServiceManagement\nim"
},
{
"path": "MonitorControl/View Controllers/Preferences/DisplaysPrefsCellView.swift",
"chars": 14692,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Cocoa\nimport os.log\n\nclass Displa"
},
{
"path": "MonitorControl/View Controllers/Preferences/DisplaysPrefsViewController.swift",
"chars": 16722,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Cocoa\nimport os.log\nimport Settin"
},
{
"path": "MonitorControl/View Controllers/Preferences/KeyboardPrefsViewController.swift",
"chars": 9785,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Cocoa\nimport KeyboardShortcuts\nim"
},
{
"path": "MonitorControl/View Controllers/Preferences/MainPrefsViewController.swift",
"chars": 6747,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Cocoa\nimport os.log\nimport Servic"
},
{
"path": "MonitorControl/View Controllers/Preferences/MenuslidersPrefsViewController.swift",
"chars": 7607,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Cocoa\nimport os.log\nimport Servic"
},
{
"path": "MonitorControl/main.swift",
"chars": 1330,
"preview": "// Copyright © MonitorControl. @JoniVR, @theOneyouseek, @waydabber and others\n\nimport Cocoa\nimport Foundation\n\n// Debug"
},
{
"path": "MonitorControl.xcodeproj/project.pbxproj",
"chars": 61652,
"preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 55;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
},
{
"path": "MonitorControl.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": "MonitorControl.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
"chars": 238,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "MonitorControl.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved",
"chars": 1713,
"preview": "{\n \"originHash\" : \"df195b12c1f6bd471af1cacb709842cb7eb355cce32fee1413f0f7d7aa5c4eb2\",\n \"pins\" : [\n {\n \"identit"
},
{
"path": "MonitorControl.xcodeproj/xcshareddata/xcschemes/MonitorControl.xcscheme",
"chars": 3281,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1610\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "MonitorControlHelper/Info.plist",
"chars": 1110,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "MonitorControlHelper/MonitorControlHelper.entitlements",
"chars": 363,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "MonitorControlHelper/main.swift",
"chars": 803,
"preview": "import Cocoa\n\nclass AppDelegate: NSObject, NSApplicationDelegate {\n func applicationDidFinishLaunching(_: Notification)"
},
{
"path": "README.md",
"chars": 8853,
"preview": "<img src=\".github/Icon-cropped.png\" width=\"200\" alt=\"App icon\" align=\"left\"/>\n\n<div>\n<h3>MonitorControl</h3>\n<p>Controls"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the MonitorControl/MonitorControl GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 123 files (1.1 MB), approximately 336.0k tokens, and a symbol index with 2 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.