diff --git a/.github/BetterDummy Social Media.fig b/.github/BetterDummy Social Media.fig deleted file mode 100644 index 751ba81..0000000 Binary files a/.github/BetterDummy Social Media.fig and /dev/null differ diff --git a/.github/BetterDummy Social Media.png b/.github/BetterDummy Social Media.png deleted file mode 100644 index 2aafb9c..0000000 Binary files a/.github/BetterDummy Social Media.png and /dev/null differ diff --git a/.github/BetterDummy StatusBar.fig b/.github/BetterDummy StatusBar.fig deleted file mode 100644 index e7759d0..0000000 Binary files a/.github/BetterDummy StatusBar.fig and /dev/null differ diff --git a/.github/BetterDummy.fig b/.github/BetterDummy.fig deleted file mode 100644 index 71acd97..0000000 Binary files a/.github/BetterDummy.fig and /dev/null differ diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 6175ffe..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,12 +0,0 @@ -# These are supported funding model platforms - -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: # Replace with a single Patreon username -open_collective: betterdummy -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/Icon-1024.png b/.github/Icon-1024.png deleted file mode 100644 index 3c3cd3a..0000000 Binary files a/.github/Icon-1024.png and /dev/null differ diff --git a/.github/Icon-128.png b/.github/Icon-128.png deleted file mode 100644 index 148459c..0000000 Binary files a/.github/Icon-128.png and /dev/null differ diff --git a/.github/Icon-16.png b/.github/Icon-16.png deleted file mode 100644 index 9c6d85d..0000000 Binary files a/.github/Icon-16.png and /dev/null differ diff --git a/.github/Icon-256.png b/.github/Icon-256.png deleted file mode 100644 index b2e4fbc..0000000 Binary files a/.github/Icon-256.png and /dev/null differ diff --git a/.github/Icon-32.png b/.github/Icon-32.png deleted file mode 100644 index d553405..0000000 Binary files a/.github/Icon-32.png and /dev/null differ diff --git a/.github/Icon-512.png b/.github/Icon-512.png deleted file mode 100644 index c1282dd..0000000 Binary files a/.github/Icon-512.png and /dev/null differ diff --git a/.github/Icon-64.png b/.github/Icon-64.png deleted file mode 100644 index 271a617..0000000 Binary files a/.github/Icon-64.png and /dev/null differ diff --git a/.github/displayprefs.png b/.github/displayprefs.png deleted file mode 100644 index 70c1fb9..0000000 Binary files a/.github/displayprefs.png and /dev/null differ diff --git a/.github/hdmi_dummy.jpeg b/.github/hdmi_dummy.jpeg deleted file mode 100644 index 8f48497..0000000 Binary files a/.github/hdmi_dummy.jpeg and /dev/null differ diff --git a/.github/macos_badge_noborder.png b/.github/macos_badge_noborder.png deleted file mode 100644 index c76acf8..0000000 Binary files a/.github/macos_badge_noborder.png and /dev/null differ diff --git a/.github/manage.png b/.github/manage.png deleted file mode 100644 index ba49019..0000000 Binary files a/.github/manage.png and /dev/null differ diff --git a/.github/menu.png b/.github/menu.png deleted file mode 100644 index 9cdb73e..0000000 Binary files a/.github/menu.png and /dev/null differ diff --git a/.github/settings.png b/.github/settings.png deleted file mode 100644 index 311f7ef..0000000 Binary files a/.github/settings.png and /dev/null differ diff --git a/.swift-version b/.swift-version deleted file mode 100644 index 9ad974f..0000000 --- a/.swift-version +++ /dev/null @@ -1 +0,0 @@ -5.5 diff --git a/.swiftformat b/.swiftformat deleted file mode 100644 index 4afae51..0000000 --- a/.swiftformat +++ /dev/null @@ -1,5 +0,0 @@ ---indent 2 ---nospaceoperators ---self insert ---exponentcase lowercase ---exclude Carthage diff --git a/.swiftlint.yml b/.swiftlint.yml deleted file mode 100644 index c6f0df4..0000000 --- a/.swiftlint.yml +++ /dev/null @@ -1,11 +0,0 @@ -disabled_rules: - - line_length - - function_body_length - - identifier_name - - trailing_comma -type_body_length: 500 -file_length: 1000 -cyclomatic_complexity: - ignores_case_statements: true -opening_brace: - allow_multiline_func: true diff --git a/BetterDummy.xcodeproj/project.pbxproj b/BetterDummy.xcodeproj/project.pbxproj deleted file mode 100644 index 9b8d825..0000000 --- a/BetterDummy.xcodeproj/project.pbxproj +++ /dev/null @@ -1,735 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 55; - objects = { - -/* Begin PBXBuildFile section */ - AA162C13272C313E003CFC89 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = AA162C12272C313E003CFC89 /* .swiftlint.yml */; }; - AA162C15272C3155003CFC89 /* .swiftformat in Resources */ = {isa = PBXBuildFile; fileRef = AA162C14272C3155003CFC89 /* .swiftformat */; }; - AA162C17272C31F0003CFC89 /* .swift-version in Resources */ = {isa = PBXBuildFile; fileRef = AA162C16272C31F0003CFC89 /* .swift-version */; }; - AA162C1A272C34A8003CFC89 /* index.html in Resources */ = {isa = PBXBuildFile; fileRef = AA162C19272C34A8003CFC89 /* index.html */; }; - AA162C1C272C36AC003CFC89 /* changelog.html in Resources */ = {isa = PBXBuildFile; fileRef = AA162C1B272C36AC003CFC89 /* changelog.html */; }; - AA162C1F272C3921003CFC89 /* marked-footnotes.min.js in Resources */ = {isa = PBXBuildFile; fileRef = AA162C1E272C3921003CFC89 /* marked-footnotes.min.js */; }; - AA162C21272C396A003CFC89 /* marked.min.js in Resources */ = {isa = PBXBuildFile; fileRef = AA162C20272C396A003CFC89 /* marked.min.js */; }; - AA162C24272C3A21003CFC89 /* changelog.css in Resources */ = {isa = PBXBuildFile; fileRef = AA162C23272C3A21003CFC89 /* changelog.css */; }; - AA162C26272C3B5B003CFC89 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = AA162C25272C3B5B003CFC89 /* LICENSE */; }; - AA162C2A272C5306003CFC89 /* AppMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA162C29272C5306003CFC89 /* AppMenu.swift */; }; - AA162C2D272C63DC003CFC89 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = AA162C2C272C63DC003CFC89 /* Sparkle */; }; - AA162C2F272C69F5003CFC89 /* UpdaterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA162C2E272C69F5003CFC89 /* UpdaterDelegate.swift */; }; - AA162C31272C73D8003CFC89 /* appcast.xml in Resources */ = {isa = PBXBuildFile; fileRef = AA162C30272C73D8003CFC89 /* appcast.xml */; }; - AA457C622736DC8A002018C1 /* Display.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA457C612736DC8A002018C1 /* Display.swift */; }; - AA457C642736E1B2002018C1 /* NSScreen+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA457C632736E1B2002018C1 /* NSScreen+Extension.swift */; }; - AA457C672736E2CB002018C1 /* CoreDisplay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA457C662736E2CB002018C1 /* CoreDisplay.framework */; }; - AA457C6D2736FBDA002018C1 /* DummyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA457C6C2736FBDA002018C1 /* DummyManager.swift */; }; - AA59EAB127288E4E00B7C833 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA59EAB027288E4E00B7C833 /* AppDelegate.swift */; }; - AA59EAB327288EF700B7C833 /* Dummy.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA59EAB227288EF700B7C833 /* Dummy.swift */; }; - AA59EAB52728911900B7C833 /* DummyDefinition.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA59EAB42728911900B7C833 /* DummyDefinition.swift */; }; - AA8AA0AE272552E600F66F8A /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = AA8AA0AD272552E600F66F8A /* README.md */; }; - AA964A8C2729C67F0064F7C7 /* PrefKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA964A8B2729C67F0064F7C7 /* PrefKey.swift */; }; - AAA2D676272BF77200F71CF0 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAA2D675272BF77200F71CF0 /* main.swift */; }; - AAA2D68A272BFC7E00F71CF0 /* BetterDummyHelper.app in Copy Helper to start at Login */ = {isa = PBXBuildFile; fileRef = AAA2D673272BF77200F71CF0 /* BetterDummyHelper.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - AACBDF4827308B060087638C /* DisplayManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AACBDF4727308B060087638C /* DisplayManager.swift */; }; - BEB26A432530B2F600744C86 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEB26A422530B2F600744C86 /* main.swift */; }; - BEB26A472530B2F700744C86 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BEB26A462530B2F700744C86 /* Assets.xcassets */; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - AAA2D689272BFC6A00F71CF0 /* Copy Helper to start at Login */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = Contents/Library/LoginItems; - dstSubfolderSpec = 1; - files = ( - AAA2D68A272BFC7E00F71CF0 /* BetterDummyHelper.app in Copy Helper to start at Login */, - ); - name = "Copy Helper to start at Login"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - AA162C12272C313E003CFC89 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; - AA162C14272C3155003CFC89 /* .swiftformat */ = {isa = PBXFileReference; lastKnownFileType = text; path = .swiftformat; sourceTree = ""; }; - AA162C16272C31F0003CFC89 /* .swift-version */ = {isa = PBXFileReference; lastKnownFileType = text; path = ".swift-version"; sourceTree = ""; }; - AA162C19272C34A8003CFC89 /* index.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = index.html; sourceTree = ""; }; - AA162C1B272C36AC003CFC89 /* changelog.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = changelog.html; sourceTree = ""; }; - AA162C1E272C3921003CFC89 /* marked-footnotes.min.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "marked-footnotes.min.js"; sourceTree = ""; }; - AA162C20272C396A003CFC89 /* marked.min.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = marked.min.js; sourceTree = ""; }; - AA162C23272C3A21003CFC89 /* changelog.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = changelog.css; sourceTree = ""; }; - AA162C25272C3B5B003CFC89 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; - AA162C29272C5306003CFC89 /* AppMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppMenu.swift; sourceTree = ""; }; - AA162C2E272C69F5003CFC89 /* UpdaterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdaterDelegate.swift; sourceTree = ""; }; - AA162C30272C73D8003CFC89 /* appcast.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = appcast.xml; sourceTree = ""; }; - AA457C612736DC8A002018C1 /* Display.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Display.swift; sourceTree = ""; }; - AA457C632736E1B2002018C1 /* NSScreen+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSScreen+Extension.swift"; sourceTree = ""; }; - AA457C662736E2CB002018C1 /* CoreDisplay.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreDisplay.framework; path = ../../../../../../System/Library/Frameworks/CoreDisplay.framework; sourceTree = ""; }; - AA457C6C2736FBDA002018C1 /* DummyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyManager.swift; sourceTree = ""; }; - AA59EAB027288E4E00B7C833 /* AppDelegate.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - AA59EAB227288EF700B7C833 /* Dummy.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Dummy.swift; sourceTree = ""; }; - AA59EAB42728911900B7C833 /* DummyDefinition.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = DummyDefinition.swift; sourceTree = ""; }; - AA8AA0AD272552E600F66F8A /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - AA964A8B2729C67F0064F7C7 /* PrefKey.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = PrefKey.swift; sourceTree = ""; }; - AAA2D673272BF77200F71CF0 /* BetterDummyHelper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BetterDummyHelper.app; sourceTree = BUILT_PRODUCTS_DIR; }; - AAA2D675272BF77200F71CF0 /* main.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; - AAA2D67E272BF77300F71CF0 /* BetterDummyHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BetterDummyHelper.entitlements; sourceTree = ""; }; - AAA2D682272BF86800F71CF0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; - AACBDF4727308B060087638C /* DisplayManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayManager.swift; sourceTree = ""; }; - BEB26A3F2530B2F600744C86 /* BetterDummy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BetterDummy.app; sourceTree = BUILT_PRODUCTS_DIR; }; - BEB26A422530B2F600744C86 /* main.swift */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; - BEB26A462530B2F700744C86 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - BEB26A4E2530B2F700744C86 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - BEB26A4F2530B2F700744C86 /* BetterDummy.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BetterDummy.entitlements; sourceTree = ""; }; - BEB26A772531163C00744C86 /* Bridging-Header.h */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.h; path = "Bridging-Header.h"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - AAA2D670272BF77200F71CF0 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - BEB26A3C2530B2F600744C86 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - AA457C672736E2CB002018C1 /* CoreDisplay.framework in Frameworks */, - AA162C2D272C63DC003CFC89 /* Sparkle in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - AA162C18272C344C003CFC89 /* docs */ = { - isa = PBXGroup; - children = ( - AAA10D87272F054C008B342D /* images */, - AA162C22272C3A0C003CFC89 /* styles */, - AA162C1D272C38FA003CFC89 /* scripts */, - AA162C19272C34A8003CFC89 /* index.html */, - AA162C1B272C36AC003CFC89 /* changelog.html */, - AA162C30272C73D8003CFC89 /* appcast.xml */, - ); - path = docs; - sourceTree = ""; - }; - AA162C1D272C38FA003CFC89 /* scripts */ = { - isa = PBXGroup; - children = ( - AA162C1E272C3921003CFC89 /* marked-footnotes.min.js */, - AA162C20272C396A003CFC89 /* marked.min.js */, - ); - path = scripts; - sourceTree = ""; - }; - AA162C22272C3A0C003CFC89 /* styles */ = { - isa = PBXGroup; - children = ( - AA162C23272C3A21003CFC89 /* changelog.css */, - ); - path = styles; - sourceTree = ""; - }; - AA1861802728A46E0095590C /* BetterDummy */ = { - isa = PBXGroup; - children = ( - BEB26A4F2530B2F700744C86 /* BetterDummy.entitlements */, - BEB26A462530B2F700744C86 /* Assets.xcassets */, - BEB26A4E2530B2F700744C86 /* Info.plist */, - BEB26A772531163C00744C86 /* Bridging-Header.h */, - BEB26A422530B2F600744C86 /* main.swift */, - AA457C682736E2E5002018C1 /* Model */, - AA457C692736E2F9002018C1 /* Support */, - AA457C6A2736E30F002018C1 /* Enums */, - AA457C6B2736E321002018C1 /* Extensions */, - ); - path = BetterDummy; - sourceTree = ""; - }; - AA457C652736E2AD002018C1 /* Frameworks */ = { - isa = PBXGroup; - children = ( - AA457C662736E2CB002018C1 /* CoreDisplay.framework */, - ); - path = Frameworks; - sourceTree = ""; - }; - AA457C682736E2E5002018C1 /* Model */ = { - isa = PBXGroup; - children = ( - AA457C612736DC8A002018C1 /* Display.swift */, - AA59EAB227288EF700B7C833 /* Dummy.swift */, - AA59EAB42728911900B7C833 /* DummyDefinition.swift */, - ); - path = Model; - sourceTree = ""; - }; - AA457C692736E2F9002018C1 /* Support */ = { - isa = PBXGroup; - children = ( - AA59EAB027288E4E00B7C833 /* AppDelegate.swift */, - AA162C29272C5306003CFC89 /* AppMenu.swift */, - AACBDF4727308B060087638C /* DisplayManager.swift */, - AA457C6C2736FBDA002018C1 /* DummyManager.swift */, - AA162C2E272C69F5003CFC89 /* UpdaterDelegate.swift */, - ); - path = Support; - sourceTree = ""; - }; - AA457C6A2736E30F002018C1 /* Enums */ = { - isa = PBXGroup; - children = ( - AA964A8B2729C67F0064F7C7 /* PrefKey.swift */, - ); - path = Enums; - sourceTree = ""; - }; - AA457C6B2736E321002018C1 /* Extensions */ = { - isa = PBXGroup; - children = ( - AA457C632736E1B2002018C1 /* NSScreen+Extension.swift */, - ); - path = Extensions; - sourceTree = ""; - }; - AAA10D87272F054C008B342D /* images */ = { - isa = PBXGroup; - children = ( - ); - path = images; - sourceTree = ""; - }; - AAA2D674272BF77200F71CF0 /* BetterDummyHelper */ = { - isa = PBXGroup; - children = ( - AAA2D67E272BF77300F71CF0 /* BetterDummyHelper.entitlements */, - AAA2D682272BF86800F71CF0 /* Info.plist */, - AAA2D675272BF77200F71CF0 /* main.swift */, - ); - path = BetterDummyHelper; - sourceTree = ""; - }; - BEB26A362530B2F600744C86 = { - isa = PBXGroup; - children = ( - AA457C652736E2AD002018C1 /* Frameworks */, - AA162C18272C344C003CFC89 /* docs */, - AA8AA0AD272552E600F66F8A /* README.md */, - AA162C25272C3B5B003CFC89 /* LICENSE */, - AA162C16272C31F0003CFC89 /* .swift-version */, - AA162C14272C3155003CFC89 /* .swiftformat */, - AA162C12272C313E003CFC89 /* .swiftlint.yml */, - AA1861802728A46E0095590C /* BetterDummy */, - AAA2D674272BF77200F71CF0 /* BetterDummyHelper */, - BEB26A402530B2F600744C86 /* Products */, - ); - indentWidth = 2; - sourceTree = ""; - }; - BEB26A402530B2F600744C86 /* Products */ = { - isa = PBXGroup; - children = ( - BEB26A3F2530B2F600744C86 /* BetterDummy.app */, - AAA2D673272BF77200F71CF0 /* BetterDummyHelper.app */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - AAA2D672272BF77200F71CF0 /* BetterDummyHelper */ = { - isa = PBXNativeTarget; - buildConfigurationList = AAA2D681272BF77300F71CF0 /* Build configuration list for PBXNativeTarget "BetterDummyHelper" */; - buildPhases = ( - AAA2D66F272BF77200F71CF0 /* Sources */, - AAA2D670272BF77200F71CF0 /* Frameworks */, - AAA2D671272BF77200F71CF0 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = BetterDummyHelper; - productName = BetterDummyHelper; - productReference = AAA2D673272BF77200F71CF0 /* BetterDummyHelper.app */; - productType = "com.apple.product-type.application"; - }; - BEB26A3E2530B2F600744C86 /* BetterDummy */ = { - isa = PBXNativeTarget; - buildConfigurationList = BEB26A522530B2F700744C86 /* Build configuration list for PBXNativeTarget "BetterDummy" */; - buildPhases = ( - AA162C0E272C3072003CFC89 /* SwiftFormat */, - AA162C0F272C309C003CFC89 /* SwiftLint */, - AA162C33272C7B0E003CFC89 /* Increase Build Number */, - BEB26A3B2530B2F600744C86 /* Sources */, - BEB26A3C2530B2F600744C86 /* Frameworks */, - BEB26A3D2530B2F600744C86 /* Resources */, - AAA2D689272BFC6A00F71CF0 /* Copy Helper to start at Login */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = BetterDummy; - packageProductDependencies = ( - AA162C2C272C63DC003CFC89 /* Sparkle */, - ); - productName = BetterDummy; - productReference = BEB26A3F2530B2F600744C86 /* BetterDummy.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - BEB26A372530B2F600744C86 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 1310; - LastUpgradeCheck = 1200; - TargetAttributes = { - AAA2D672272BF77200F71CF0 = { - CreatedOnToolsVersion = 13.1; - }; - BEB26A3E2530B2F600744C86 = { - CreatedOnToolsVersion = 12.0.1; - LastSwiftMigration = 1200; - }; - }; - }; - buildConfigurationList = BEB26A3A2530B2F600744C86 /* Build configuration list for PBXProject "BetterDummy" */; - compatibilityVersion = "Xcode 13.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = BEB26A362530B2F600744C86; - packageReferences = ( - AA162C2B272C63DC003CFC89 /* XCRemoteSwiftPackageReference "Sparkle" */, - ); - productRefGroup = BEB26A402530B2F600744C86 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - BEB26A3E2530B2F600744C86 /* BetterDummy */, - AAA2D672272BF77200F71CF0 /* BetterDummyHelper */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - AAA2D671272BF77200F71CF0 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - BEB26A3D2530B2F600744C86 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AA162C15272C3155003CFC89 /* .swiftformat in Resources */, - AA162C13272C313E003CFC89 /* .swiftlint.yml in Resources */, - AA162C31272C73D8003CFC89 /* appcast.xml in Resources */, - AA162C1C272C36AC003CFC89 /* changelog.html in Resources */, - AA8AA0AE272552E600F66F8A /* README.md in Resources */, - AA162C1A272C34A8003CFC89 /* index.html in Resources */, - AA162C26272C3B5B003CFC89 /* LICENSE in Resources */, - AA162C21272C396A003CFC89 /* marked.min.js in Resources */, - BEB26A472530B2F700744C86 /* Assets.xcassets in Resources */, - AA162C1F272C3921003CFC89 /* marked-footnotes.min.js in Resources */, - AA162C24272C3A21003CFC89 /* changelog.css in Resources */, - AA162C17272C31F0003CFC89 /* .swift-version in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - AA162C0E272C3072003CFC89 /* SwiftFormat */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = SwiftFormat; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if which swiftformat >/dev/null; then\n swiftformat .\nelse\n echo \"warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat\" >&2\nfi\n"; - }; - AA162C0F272C309C003CFC89 /* SwiftLint */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = SwiftLint; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\" >&2\nfi\n"; - }; - AA162C33272C7B0E003CFC89 /* Increase Build Number */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Increase Build Number"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "buildNumber=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"$INFOPLIST_FILE\")\nbuildNumber=$(($buildNumber + 1))\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"$INFOPLIST_FILE\"\n"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - AAA2D66F272BF77200F71CF0 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AAA2D676272BF77200F71CF0 /* main.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - BEB26A3B2530B2F600744C86 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AA59EAB127288E4E00B7C833 /* AppDelegate.swift in Sources */, - AA162C2A272C5306003CFC89 /* AppMenu.swift in Sources */, - AA457C642736E1B2002018C1 /* NSScreen+Extension.swift in Sources */, - AA964A8C2729C67F0064F7C7 /* PrefKey.swift in Sources */, - AACBDF4827308B060087638C /* DisplayManager.swift in Sources */, - AA457C622736DC8A002018C1 /* Display.swift in Sources */, - AA59EAB52728911900B7C833 /* DummyDefinition.swift in Sources */, - AA457C6D2736FBDA002018C1 /* DummyManager.swift in Sources */, - AA59EAB327288EF700B7C833 /* Dummy.swift in Sources */, - BEB26A432530B2F600744C86 /* main.swift in Sources */, - AA162C2F272C69F5003CFC89 /* UpdaterDelegate.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - AAA2D67F272BF77300F71CF0 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CODE_SIGN_ENTITLEMENTS = BetterDummyHelper/BetterDummyHelper.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 299YSU96J7; - ENABLE_HARDENED_RUNTIME = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = BetterDummyHelper/Info.plist; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; - INFOPLIST_KEY_NSHumanReadableCopyright = "MIT Licensed, 2021."; - INFOPLIST_KEY_NSPrincipalClass = NSApplication; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = me.waydabber.BetterDummyHelper; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.5; - }; - name = Debug; - }; - AAA2D680272BF77300F71CF0 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CODE_SIGN_ENTITLEMENTS = BetterDummyHelper/BetterDummyHelper.entitlements; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = 299YSU96J7; - ENABLE_HARDENED_RUNTIME = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = BetterDummyHelper/Info.plist; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; - INFOPLIST_KEY_NSHumanReadableCopyright = "MIT Licensed, 2021."; - INFOPLIST_KEY_NSPrincipalClass = NSApplication; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.0.0; - PRODUCT_BUNDLE_IDENTIFIER = me.waydabber.BetterDummyHelper; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.5; - }; - name = Release; - }; - BEB26A502530B2F700744C86 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - BEB26A512530B2F700744C86 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Release; - }; - BEB26A532530B2F700744C86 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = BetterDummy/BetterDummy.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 330; - DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = 299YSU96J7; - ENABLE_HARDENED_RUNTIME = YES; - ENABLE_PREVIEWS = YES; - HEADER_SEARCH_PATHS = include; - INFOPLIST_FILE = BetterDummy/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.0.11; - OTHER_CODE_SIGN_FLAGS = "--timestamp"; - PRODUCT_BUNDLE_IDENTIFIER = me.waydabber.BetterDummy; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OBJC_BRIDGING_HEADER = "BetterDummy/Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.5; - }; - name = Debug; - }; - BEB26A542530B2F700744C86 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = BetterDummy/BetterDummy.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 330; - DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = 299YSU96J7; - ENABLE_HARDENED_RUNTIME = YES; - ENABLE_PREVIEWS = YES; - HEADER_SEARCH_PATHS = include; - INFOPLIST_FILE = BetterDummy/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 1.0.11; - OTHER_CODE_SIGN_FLAGS = "--timestamp"; - PRODUCT_BUNDLE_IDENTIFIER = me.waydabber.BetterDummy; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_OBJC_BRIDGING_HEADER = "BetterDummy/Bridging-Header.h"; - SWIFT_VERSION = 5.5; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - AAA2D681272BF77300F71CF0 /* Build configuration list for PBXNativeTarget "BetterDummyHelper" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AAA2D67F272BF77300F71CF0 /* Debug */, - AAA2D680272BF77300F71CF0 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - BEB26A3A2530B2F600744C86 /* Build configuration list for PBXProject "BetterDummy" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - BEB26A502530B2F700744C86 /* Debug */, - BEB26A512530B2F700744C86 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - BEB26A522530B2F700744C86 /* Build configuration list for PBXNativeTarget "BetterDummy" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - BEB26A532530B2F700744C86 /* Debug */, - BEB26A542530B2F700744C86 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - -/* Begin XCRemoteSwiftPackageReference section */ - AA162C2B272C63DC003CFC89 /* XCRemoteSwiftPackageReference "Sparkle" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/sparkle-project/Sparkle"; - requirement = { - branch = 2.x; - kind = branch; - }; - }; -/* End XCRemoteSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - AA162C2C272C63DC003CFC89 /* Sparkle */ = { - isa = XCSwiftPackageProductDependency; - package = AA162C2B272C63DC003CFC89 /* XCRemoteSwiftPackageReference "Sparkle" */; - productName = Sparkle; - }; -/* End XCSwiftPackageProductDependency section */ - }; - rootObject = BEB26A372530B2F600744C86 /* Project object */; -} diff --git a/BetterDummy.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/BetterDummy.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/BetterDummy.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/BetterDummy.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/BetterDummy.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/BetterDummy.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Contents.json b/BetterDummy/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 959fa98..0000000 --- a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "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 - } -} diff --git a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-1024.png b/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-1024.png deleted file mode 100644 index 3c3cd3a..0000000 Binary files a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-1024.png and /dev/null differ diff --git a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-128.png b/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-128.png deleted file mode 100644 index 148459c..0000000 Binary files a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-128.png and /dev/null differ diff --git a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-16.png b/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-16.png deleted file mode 100644 index 9c6d85d..0000000 Binary files a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-16.png and /dev/null differ diff --git a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-256.png b/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-256.png deleted file mode 100644 index b2e4fbc..0000000 Binary files a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-256.png and /dev/null differ diff --git a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-257.png b/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-257.png deleted file mode 100644 index b2e4fbc..0000000 Binary files a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-257.png and /dev/null differ diff --git a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-32.png b/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-32.png deleted file mode 100644 index d553405..0000000 Binary files a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-32.png and /dev/null differ diff --git a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-33.png b/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-33.png deleted file mode 100644 index 27ae523..0000000 Binary files a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-33.png and /dev/null differ diff --git a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-512.png b/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-512.png deleted file mode 100644 index c1282dd..0000000 Binary files a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-512.png and /dev/null differ diff --git a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-513.png b/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-513.png deleted file mode 100644 index c1282dd..0000000 Binary files a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-513.png and /dev/null differ diff --git a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-64.png b/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-64.png deleted file mode 100644 index 271a617..0000000 Binary files a/BetterDummy/Assets.xcassets/AppIcon.appiconset/Icon-64.png and /dev/null differ diff --git a/BetterDummy/Assets.xcassets/Contents.json b/BetterDummy/Assets.xcassets/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/BetterDummy/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/BetterDummy/Assets.xcassets/status.imageset/Contents.json b/BetterDummy/Assets.xcassets/status.imageset/Contents.json deleted file mode 100644 index 690debc..0000000 --- a/BetterDummy/Assets.xcassets/status.imageset/Contents.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "images" : [ - { - "filename" : "status.png", - "idiom" : "universal", - "scale" : "1x" - }, - { - "filename" : "status@2.png", - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" - } -} diff --git a/BetterDummy/Assets.xcassets/status.imageset/status.png b/BetterDummy/Assets.xcassets/status.imageset/status.png deleted file mode 100644 index 48f9308..0000000 Binary files a/BetterDummy/Assets.xcassets/status.imageset/status.png and /dev/null differ diff --git a/BetterDummy/Assets.xcassets/status.imageset/status@2.png b/BetterDummy/Assets.xcassets/status.imageset/status@2.png deleted file mode 100644 index d2d2a95..0000000 Binary files a/BetterDummy/Assets.xcassets/status.imageset/status@2.png and /dev/null differ diff --git a/BetterDummy/BetterDummy.entitlements b/BetterDummy/BetterDummy.entitlements deleted file mode 100644 index ee95ab7..0000000 --- a/BetterDummy/BetterDummy.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.network.client - - - diff --git a/BetterDummy/Bridging-Header.h b/BetterDummy/Bridging-Header.h deleted file mode 100644 index 99bcc06..0000000 --- a/BetterDummy/Bridging-Header.h +++ /dev/null @@ -1,140 +0,0 @@ -// -// BetterDummy -// -// Created by @waydabber -// - -#import -#import -#import - -extern CFDictionaryRef CoreDisplay_DisplayCreateInfoDictionary(CGDirectDisplayID); - -@interface CGVirtualDisplay : NSObject -{ - unsigned int _vendorID; - unsigned int _productID; - unsigned int _serialNum; - NSString *_name; - struct CGSize _sizeInMillimeters; - unsigned int _maxPixelsWide; - unsigned int _maxPixelsHigh; - struct CGPoint _redPrimary; - struct CGPoint _greenPrimary; - struct CGPoint _bluePrimary; - struct CGPoint _whitePoint; - id _queue; - id _terminationHandler; - void *_client; - unsigned int _displayID; - unsigned int _hiDPI; - NSArray *_modes; - unsigned int _serverRPC_port; - unsigned int _proxyRPC_port; - unsigned int _clientHandler_port; -} - -@property(readonly, nonatomic) NSArray *modes; // @synthesize modes=_modes; -@property(readonly, nonatomic) unsigned int hiDPI; // @synthesize hiDPI=_hiDPI; -@property(readonly, nonatomic) unsigned int displayID; // @synthesize displayID=_displayID; -@property(readonly, nonatomic) id terminationHandler; // @synthesize terminationHandler=_terminationHandler; -@property(readonly, nonatomic) id queue; // @synthesize queue=_queue; -@property(readonly, nonatomic) struct CGPoint whitePoint; // @synthesize whitePoint=_whitePoint; -@property(readonly, nonatomic) struct CGPoint bluePrimary; // @synthesize bluePrimary=_bluePrimary; -@property(readonly, nonatomic) struct CGPoint greenPrimary; // @synthesize greenPrimary=_greenPrimary; -@property(readonly, nonatomic) struct CGPoint redPrimary; // @synthesize redPrimary=_redPrimary; -@property(readonly, nonatomic) unsigned int maxPixelsHigh; // @synthesize maxPixelsHigh=_maxPixelsHigh; -@property(readonly, nonatomic) unsigned int maxPixelsWide; // @synthesize maxPixelsWide=_maxPixelsWide; -@property(readonly, nonatomic) struct CGSize sizeInMillimeters; // @synthesize sizeInMillimeters=_sizeInMillimeters; -@property(readonly, nonatomic) NSString *name; // @synthesize name=_name; -@property(readonly, nonatomic) unsigned int serialNum; // @synthesize serialNum=_serialNum; -@property(readonly, nonatomic) unsigned int productID; // @synthesize productID=_productID; -@property(readonly, nonatomic) unsigned int vendorID; // @synthesize vendorID=_vendorID; -- (BOOL)applySettings:(id)arg1; -- (void)dealloc; -- (id)initWithDescriptor:(id)arg1; - -@end - -@interface CGVirtualDisplayDescriptor : NSObject -{ - unsigned int _vendorID; - unsigned int _productID; - unsigned int _serialNum; - NSString *_name; - struct CGSize _sizeInMillimeters; - unsigned int _maxPixelsWide; - unsigned int _maxPixelsHigh; - struct CGPoint _redPrimary; - struct CGPoint _greenPrimary; - struct CGPoint _bluePrimary; - struct CGPoint _whitePoint; - id _queue; - id _terminationHandler; -} - -@property(retain, nonatomic) id queue; // @synthesize queue=_queue; -@property(retain, nonatomic) NSString *name; // @synthesize name=_name; -@property(nonatomic) struct CGPoint whitePoint; // @synthesize whitePoint=_whitePoint; -@property(nonatomic) struct CGPoint bluePrimary; // @synthesize bluePrimary=_bluePrimary; -@property(nonatomic) struct CGPoint greenPrimary; // @synthesize greenPrimary=_greenPrimary; -@property(nonatomic) struct CGPoint redPrimary; // @synthesize redPrimary=_redPrimary; -@property(nonatomic) unsigned int maxPixelsHigh; // @synthesize maxPixelsHigh=_maxPixelsHigh; -@property(nonatomic) unsigned int maxPixelsWide; // @synthesize maxPixelsWide=_maxPixelsWide; -@property(nonatomic) struct CGSize sizeInMillimeters; // @synthesize sizeInMillimeters=_sizeInMillimeters; -@property(nonatomic) unsigned int serialNum; // @synthesize serialNum=_serialNum; -@property(nonatomic) unsigned int productID; // @synthesize productID=_productID; -@property(nonatomic) unsigned int vendorID; // @synthesize vendorID=_vendorID; -- (void)dealloc; -- (id)init; -@property(copy, nonatomic) id terminationHandler; -- (id)dispatchQueue; -- (void)setDispatchQueue:(id)arg1; - -@end - -@interface CGVirtualDisplayMode : NSObject -{ - unsigned int _width; - unsigned int _height; - double _refreshRate; -} - -@property(readonly, nonatomic) double refreshRate; // @synthesize refreshRate=_refreshRate; -@property(readonly, nonatomic) unsigned int height; // @synthesize height=_height; -@property(readonly, nonatomic) unsigned int width; // @synthesize width=_width; -- (id)initWithWidth:(unsigned int)arg1 height:(unsigned int)arg2 refreshRate:(double)arg3; - -@end - -@interface CGVirtualDisplaySettings : NSObject -{ - NSArray *_modes; - unsigned int _hiDPI; -} - -@property(nonatomic) unsigned int hiDPI; // @synthesize hiDPI=_hiDPI; -- (void)dealloc; -- (id)init; -@property(retain, nonatomic) NSArray *modes; - -@end - -id createVirtualDisplay(int width, int height, int ppi, BOOL hiDPI, NSString *name, int ref_rate); - -typedef struct { - uint32_t modeNumber; - uint32_t flags; - uint32_t width; - uint32_t height; - uint32_t depth; - uint8_t unknown[170]; - uint16_t freq; - uint8_t more_unknown[16]; - float density; -} CGSDisplayMode; - -void CGSGetCurrentDisplayMode(CGDirectDisplayID display, int* modeNum); -void CGSConfigureDisplayMode(CGDisplayConfigRef config, CGDirectDisplayID display, int modeNum); -void CGSGetNumberOfDisplayModes(CGDirectDisplayID display, int* nModes); -void CGSGetDisplayModeDescriptionOfLength(CGDirectDisplayID display, int idx, CGSDisplayMode* mode, int length); diff --git a/BetterDummy/Enums/PrefKey.swift b/BetterDummy/Enums/PrefKey.swift deleted file mode 100644 index 915c32e..0000000 --- a/BetterDummy/Enums/PrefKey.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// BetterDummy -// -// Created by @waydabber -// - -enum PrefKey: String { - // Dummy specific - case display - case serial - case isConnected - case associatedDisplayPrefsId - case associatedDisplayName - - // General - case appAlreadyLaunched - case numOfDummyDisplays - case buildNumber - case startAtLogin - case hideMenuIcon - case reconnectAfterSleep - case disableTempSleep - case SUEnableAutomaticChecks - case isBetaChannel - case enable16K - case hideLowResolutionOption - - // Not used - case enableSliderSnap - case showTickMarks -} diff --git a/BetterDummy/Extensions/NSScreen+Extension.swift b/BetterDummy/Extensions/NSScreen+Extension.swift deleted file mode 100644 index c653dc8..0000000 --- a/BetterDummy/Extensions/NSScreen+Extension.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// BetterDummy -// -// Created by @waydabber -// - -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 - } -} diff --git a/BetterDummy/Info.plist b/BetterDummy/Info.plist deleted file mode 100644 index 1b5a781..0000000 --- a/BetterDummy/Info.plist +++ /dev/null @@ -1,42 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - $(MARKETING_VERSION) - CFBundleVersion - 1194 - LSApplicationCategoryType - public.app-category.utilities - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - LSUIElement - - NSHumanReadableCopyright - MIT Licensed, 2021. - NSMainStoryboardFile - Main - NSPrincipalClass - NSApplication - SUEnableJavaScript - - SUFeedURL - https://waydabber.github.io/BetterDummy/appcast.xml - SUPublicEDKey - ITSTMp8AypsLawojJ+UR3tm2mN18AFoNMvXf1G3t62s= - - diff --git a/BetterDummy/Model/Display.swift b/BetterDummy/Model/Display.swift deleted file mode 100644 index 15ccf6d..0000000 --- a/BetterDummy/Model/Display.swift +++ /dev/null @@ -1,114 +0,0 @@ -// -// BetterDummy -// -// Created by @waydabber -// - -import Cocoa -import Foundation -import os.log - -class Display: Equatable { - struct Resolution { - var modeNumber: UInt32 - var width: UInt32 - var height: UInt32 - var bitDepth: UInt32 - var refreshRate: UInt16 - var hiDPI: Bool - var isActive: Bool - } - - let identifier: CGDirectDisplayID - let prefsId: String - var name: String - var vendorNumber: UInt32? - var modelNumber: UInt32? - var serialNumber: UInt32? - var resolutions: [Int: Resolution] = [:] - var currentResolution: Int = 0 - var width: UInt32 = 0 - var height: UInt32 = 0 - var pixelWidth: UInt32 = 0 - var pixelHeight: UInt32 = 0 - var isHiDPI: Bool = false - - static func == (lhs: Display, rhs: Display) -> Bool { - lhs.identifier == rhs.identifier - } - - var isVirtual: Bool = false - var isDummy: Bool = false - - 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 = isVirtual - self.isDummy = isDummy - self.prefsId = "(" + String(name.filter { !$0.isWhitespace }) + String(vendorNumber ?? 0) + String(modelNumber ?? 0) + "@" + (self.isVirtual ? String(self.serialNumber ?? 9999) : String(identifier)) + ")" - os_log("Display init with prefsIdentifier %{public}@", type: .info, self.prefsId) - self.updateResolutions() - } - - func isBuiltIn() -> Bool { - if CGDisplayIsBuiltin(self.identifier) != 0 { - return true - } else { - return false - } - } - - func updateResolutions() { - self.resolutions = [:] - self.currentResolution = 0 - let numberOfDisplayModes = UnsafeMutablePointer.allocate(capacity: 1) - let currentDisplayMode = UnsafeMutablePointer.allocate(capacity: 1) - let displayModeDescription = UnsafeMutablePointer.allocate(capacity: 1) - let displayModeLength = Int32(MemoryLayout.size) - defer { - numberOfDisplayModes.deallocate() - currentDisplayMode.deallocate() - displayModeDescription.deallocate() - } - CGSGetNumberOfDisplayModes(self.identifier, numberOfDisplayModes) - CGSGetCurrentDisplayMode(self.identifier, currentDisplayMode) - for i in 0 ... numberOfDisplayModes.pointee - 1 { - CGSGetDisplayModeDescriptionOfLength(self.identifier, i, displayModeDescription, displayModeLength) - let resolution = Resolution( - modeNumber: displayModeDescription.pointee.modeNumber, - width: displayModeDescription.pointee.width, - height: displayModeDescription.pointee.height, - bitDepth: displayModeDescription.pointee.depth, - refreshRate: displayModeDescription.pointee.freq, - hiDPI: Int(displayModeDescription.pointee.density) > 1 ? true : false, - isActive: currentDisplayMode.pointee == i ? true : false - ) - if resolution.isActive { - self.currentResolution = Int(i) - self.width = resolution.width - self.height = resolution.height - self.isHiDPI = resolution.hiDPI - self.pixelWidth = self.width * (self.isHiDPI ? 2 : 1) - self.pixelHeight = self.height * (self.isHiDPI ? 2 : 1) - } - self.resolutions[Int(i)] = resolution - } - } - - func changeResolution(resolutionItemNumber: Int32) { - os_log("Changing resolution for display %{public}@ to %{public}@", type: .info, self.prefsId, "\(resolutionItemNumber)") - app.skipReconfiguration = true - let displayConfiguration = UnsafeMutablePointer.allocate(capacity: 1) - defer { - displayConfiguration.deallocate() - } - CGBeginDisplayConfiguration(displayConfiguration) - CGSConfigureDisplayMode(displayConfiguration.pointee, self.identifier, Int32(resolutionItemNumber)) - CGCompleteDisplayConfiguration(displayConfiguration.pointee, CGConfigureOption.permanently) - self.updateResolutions() - app.skipReconfiguration = false - } -} diff --git a/BetterDummy/Model/Dummy.swift b/BetterDummy/Model/Dummy.swift deleted file mode 100644 index c705b18..0000000 --- a/BetterDummy/Model/Dummy.swift +++ /dev/null @@ -1,133 +0,0 @@ -// -// BetterDummy -// -// Created by @waydabber -// - -import Cocoa -import Foundation -import os.log - -class Dummy: Equatable { - var virtualDisplay: CGVirtualDisplay? - var dummyDefinition: DummyDefinition - let serialNum: UInt32 - var isConnected: Bool = false - var isSleepDisconnected: Bool = false - var associatedDisplayPrefsId: String = "" - var associatedDisplayName: String = "" - var displayIdentifier: CGDirectDisplayID = 0 // This contains valid info only if the display is connected - - static func == (lhs: Dummy, rhs: Dummy) -> Bool { - lhs.serialNum == rhs.serialNum - } - - init(dummyDefinition: DummyDefinition, serialNum: UInt32 = 0, doConnect: Bool = true) { - var storedSerialNum: UInt32 = serialNum - if storedSerialNum == 0 { - storedSerialNum = UInt32.random(in: 0 ... UInt32.max) - } - self.dummyDefinition = dummyDefinition - self.serialNum = storedSerialNum - if doConnect { - _ = self.connect() - } - } - - func getName() -> String { - "Dummy \(self.dummyDefinition.description.components(separatedBy: " ").first ?? self.dummyDefinition.description)" - } - - func getMenuItemTitle() -> String { - "\(self.dummyDefinition.description.components(separatedBy: " ").first ?? "") - #\(String(format: "%02X", self.serialNum))" - } - - func getSerialNumber() -> String { - "\(String(format: "%02X", self.serialNum))" - } - - func connect(sleepConnect: Bool = false) -> Bool { - guard sleepConnect && self.isSleepDisconnected || !sleepConnect else { - return false - } - self.isSleepDisconnected = false - if self.virtualDisplay != nil || self.isConnected { - os_log("Attempted to connect the already connected display %{public}@. Interpreting as connect cycle.", type: .info, "\(self.getName())") - self.disconnect() - } - let name: String = self.getName() - if let virtualDisplay = Dummy.createVirtualDisplay(self.dummyDefinition, name: name, serialNum: self.serialNum) { - self.virtualDisplay = virtualDisplay - self.displayIdentifier = virtualDisplay.displayID - self.isConnected = true - os_log("Display %{public}@ successfully connected", type: .info, "\(name)") - return true - } else { - os_log("Failed to connect display %{public}@", type: .info, "\(name)") - return false - } - } - - func associateDisplay(display: Display) { - self.associatedDisplayPrefsId = display.prefsId - self.associatedDisplayName = display.name - } - - func disassociateDisplay() { - self.associatedDisplayPrefsId = "" - self.associatedDisplayName = "" - } - - func hasAssociatedDisplay() -> Bool { - self.associatedDisplayPrefsId == "" ? false : true - } - - func disconnect(sleepDisconnect: Bool = false) { - self.virtualDisplay = nil - self.isConnected = false - self.isSleepDisconnected = sleepDisconnect - os_log("Disconnected virtual display: %{public}@", type: .info, "\(self.getName())") - } - - static func createVirtualDisplay(_ definition: DummyDefinition, name: String, serialNum: UInt32, hiDPI: Bool = true) -> CGVirtualDisplay? { - os_log("Creating virtual display: %{public}@", type: .info, "\(name)") - if let descriptor = CGVirtualDisplayDescriptor() { - os_log("- Preparing descriptor...", type: .info) - descriptor.queue = DispatchQueue.global(qos: .userInteractive) - descriptor.name = name - descriptor.whitePoint = CGPoint(x: 0.950, y: 1.000) // "Taken from Generic RGB Profile.icc" - descriptor.redPrimary = CGPoint(x: 0.454, y: 0.242) // "Taken from Generic RGB Profile.icc" - descriptor.greenPrimary = CGPoint(x: 0.353, y: 0.674) // "Taken from Generic RGB Profile.icc" - descriptor.bluePrimary = CGPoint(x: 0.157, y: 0.084) // "Taken from Generic RGB Profile.icc" - descriptor.maxPixelsWide = UInt32(definition.aspectWidth * definition.multiplierStep * definition.maxMultiplier) - descriptor.maxPixelsHigh = UInt32(definition.aspectHeight * definition.multiplierStep * definition.maxMultiplier) - // Dummy will be fixed at 24" for now - let diagonalSizeRatio: Double = (24 * 25.4) / sqrt(Double(definition.aspectWidth * definition.aspectWidth + definition.aspectHeight * definition.aspectHeight)) - descriptor.sizeInMillimeters = CGSize(width: Double(definition.aspectWidth) * diagonalSizeRatio, height: Double(definition.aspectHeight) * diagonalSizeRatio) - descriptor.serialNum = serialNum - descriptor.productID = UInt32(min(definition.aspectWidth - 1, 255) * 256 + min(definition.aspectHeight - 1, 255)) - descriptor.vendorID = UInt32(0xF0F0) - if let display = CGVirtualDisplay(descriptor: descriptor) { - os_log("- Creating display, preparing modes...", type: .info) - var modes = [CGVirtualDisplayMode?](repeating: nil, count: definition.maxMultiplier - definition.minMultiplier + 1) - for multiplier in definition.minMultiplier ... definition.maxMultiplier { - for refreshRate in definition.refreshRates { - let width = UInt32(definition.aspectWidth * multiplier * definition.multiplierStep) - let height = UInt32(definition.aspectHeight * multiplier * definition.multiplierStep) - modes[multiplier - definition.minMultiplier] = CGVirtualDisplayMode(width: width, height: height, refreshRate: refreshRate)! - } - } - if let settings = CGVirtualDisplaySettings() { - os_log("- Preparing settings for display...", type: .info) - settings.hiDPI = hiDPI ? 1 : 0 - settings.modes = modes as [Any] - if display.applySettings(settings) { - os_log("- Settings are successfully applied. Dummy Display ID is %{public}@", type: .info, String(display.displayID)) - return display - } - } - } - } - return nil - } -} diff --git a/BetterDummy/Model/DummyDefinition.swift b/BetterDummy/Model/DummyDefinition.swift deleted file mode 100644 index b567eaf..0000000 --- a/BetterDummy/Model/DummyDefinition.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// BetterDummy -// -// Created by @waydabber -// - -struct DummyDefinition { - let aspectWidth, aspectHeight, multiplierStep, minMultiplier, maxMultiplier: Int - let refreshRates: [Double] - let description: String - let addSeparatorAfter: Bool - - init(_ aspectWidth: Int, _ aspectHeight: Int, _ step: Int, _ refreshRates: [Double], _ description: String, _ addSeparatorAfter: Bool = false) { - let minX: Int = 720 - let minY: Int = 720 - let maxX: Int = prefs.bool(forKey: PrefKey.enable16K.rawValue) ? 16384 : 8192 - let maxY: Int = prefs.bool(forKey: PrefKey.enable16K.rawValue) ? 16384 : 8192 - let minMultiplier = max(Int(ceil(Float(minX) / (Float(aspectWidth) * Float(step)))), Int(ceil(Float(minY) / (Float(aspectHeight) * Float(step))))) - let maxMultiplier = min(Int(floor(Float(maxX) / (Float(aspectWidth) * Float(step)))), Int(floor(Float(maxY) / (Float(aspectHeight) * Float(step))))) - self.init(aspectWidth, aspectHeight, minMultiplier, maxMultiplier, step, refreshRates, description, addSeparatorAfter) - } - - init(_ aspectWidth: Int, _ aspectHeight: Int, _ minMultiplier: Int, _ maxMultiplier: Int, _ step: Int, _ refreshRates: [Double], _ description: String, _ addSeparatorAfter: Bool = false) { - self.aspectWidth = aspectWidth - self.aspectHeight = aspectHeight - self.minMultiplier = minMultiplier - self.maxMultiplier = maxMultiplier - self.multiplierStep = step - self.refreshRates = refreshRates - self.description = description - self.addSeparatorAfter = addSeparatorAfter - } -} diff --git a/BetterDummy/Support/AppDelegate.swift b/BetterDummy/Support/AppDelegate.swift deleted file mode 100644 index ae6e3b7..0000000 --- a/BetterDummy/Support/AppDelegate.swift +++ /dev/null @@ -1,503 +0,0 @@ -// -// BetterDummy -// -// Created by @waydabber -// - -import Cocoa -import os.log -import ServiceManagement -import Sparkle - -class AppDelegate: NSObject, NSApplicationDelegate, SPUUpdaterDelegate { - var isSleep: Bool = false - var reconfigureID: Int = 0 - var skipReconfiguration = false - let updaterController = SPUStandardUpdaterController(startingUpdater: false, updaterDelegate: UpdaterDelegate(), userDriverDelegate: nil) - let menu = AppMenu() - - // MARK: *** Setup app - - func applicationDidFinishLaunching(_: Notification) { - app = self - DummyManager.updateDummyDefinitions() - self.menu.setupMenu() - self.setDefaultPrefs() - DummyManager.restoreDummiesFromPrefs() - self.updaterController.startUpdater() - self.displayReconfiguration(force: true) - - NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.sleepNotification), name: NSWorkspace.screensDidSleepNotification, 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.screensDidWakeNotification, object: nil) - NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.wakeNotification), name: NSWorkspace.didWakeNotification, object: nil) - CGDisplayRegisterReconfigurationCallback({ _, _, _ in app.displayReconfiguration() }, nil) - } - - func applicationShouldHandleReopen(_: NSApplication, hasVisibleWindows _: Bool) -> Bool { - let alert = NSAlert() - alert.alertStyle = .informational - alert.messageText = "BetterDummy is already running!" - if prefs.bool(forKey: PrefKey.hideMenuIcon.rawValue) || self.menu.statusBarItem.isVisible == false { - self.menu.statusBarItem.isVisible = true - prefs.set(true, forKey: PrefKey.hideMenuIcon.rawValue) - DummyManager.storeDummiesToPrefs() - self.menu.populateSettingsMenu() - alert.informativeText = "The menu icon was hidden but it is now set to visible. You can hide it again in Settings." - } else { - alert.informativeText = "To configure the app, use the BetterDummy menu icon in the macOS menu bar!" - } - alert.runModal() - return true - } - - func setDefaultPrefs() { - if !prefs.bool(forKey: PrefKey.appAlreadyLaunched.rawValue) { - prefs.set(true, forKey: PrefKey.appAlreadyLaunched.rawValue) - prefs.set(true, forKey: PrefKey.SUEnableAutomaticChecks.rawValue) - os_log("Setting default preferences.", type: .info) - } - prefs.set(Int(Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "1") ?? 1, forKey: PrefKey.buildNumber.rawValue) - } - - func getStartAtLogin() -> Bool { - (SMCopyAllJobDictionaries(kSMDomainUserLaunchd).takeRetainedValue() as? [[String: AnyObject]])?.first { $0["Label"] as? String == "\(Bundle.main.bundleIdentifier!)Helper" }?["OnDemand"] as? Bool ?? false - } - - // MARK: *** Handlers - Specific dummy management - - @objc func createDummy(_ sender: AnyObject?) { - if let menuItem = sender as? NSMenuItem { - os_log("Connecting dummy tagged in new menu as %{public}@", type: .info, "\(menuItem.tag)") - if let number = DummyManager.createDummyByDefinitionId(menuItem.tag) { - self.menu.populateAppMenu() - DummyManager.storeDummiesToPrefs() - if let dummy = DummyManager.getDummyByNumber(number), dummy.isConnected { - os_log("Dummy successfully created and connected.", type: .info) - } else { - os_log("There seems to be a problem with the created dummy.", type: .info) - } - let alert = NSAlert() - alert.alertStyle = .warning - alert.messageText = "Your new dummy was created and connected." - alert.informativeText = "Use Displays under System Preferences to configure your new dummy display! You can use the app menu to change the settings of your dummy." - alert.runModal() - } else { - os_log("Could not create dummy using menu item tag number.", type: .info) - } - } - } - - @objc func connectDisconnectDummy(_ sender: AnyObject?) { - if let menuItem = sender as? NSMenuItem, let dummy = DummyManager.getDummyByNumber(menuItem.tag) { - if !dummy.isConnected { - os_log("Connecting dummy tagged in menu as %{public}@", type: .info, "\(menuItem.tag)") - if dummy.hasAssociatedDisplay() { - let alert = NSAlert() - alert.alertStyle = .warning - alert.messageText = "This dummy cannot be manually connected!" - alert.informativeText = "A dummy which is associated with a display will connect automatically when the associated display is connected." - alert.runModal() - } else { - if !dummy.connect() { - let alert = NSAlert() - alert.alertStyle = .warning - alert.messageText = "Unable to connect dummy" - alert.informativeText = "An error occured during connecting the dummy." - alert.runModal() - } - } - } else { - os_log("Disconnecting dummy tagged in menu as %{public}@", type: .info, "\(menuItem.tag)") - if dummy.hasAssociatedDisplay() { - let alert = NSAlert() - alert.alertStyle = .warning - alert.messageText = "This dummy cannot be manually disconnected!" - alert.informativeText = "A dummy which is associated with a display will disconnect automatically when the associated display is disconnected." - alert.runModal() - } else { - dummy.disconnect() - } - } - self.menu.populateAppMenu() - DummyManager.storeDummiesToPrefs() - app.menu.appMenu.cancelTrackingWithoutAnimation() - } - } - - @objc func discardDummy(_ sender: AnyObject?) { - if let menuItem = sender as? NSMenuItem { - let alert = NSAlert() - alert.alertStyle = .critical - alert.messageText = "Do you want to discard the dummy?" - alert.informativeText = "If you would like to use a dummy later, use disconnect instead so macOS display configuration data is preserved." - alert.addButton(withTitle: "Cancel") - alert.addButton(withTitle: "Discard") - if alert.runModal() == .alertSecondButtonReturn { - os_log("Removing dummy tagged in manage menu as %{public}@", type: .info, "\(menuItem.tag)") - DummyManager.discardDummyByNumber(menuItem.tag) - self.menu.populateAppMenu() - DummyManager.storeDummiesToPrefs() - } - } - } - - @objc func lowResolution(_ sender: AnyObject?) { - if let menuItem = sender as? NSMenuItem, let dummy = DummyManager.getDummyByNumber(menuItem.tag), let display = DisplayManager.getDisplayById(dummy.displayIdentifier) { - let resolutions = display.resolutions - var previousWidth: UInt32 = 0 - var matchingResolutionKey: Int32 = -1 - for resolution in resolutions.sorted(by: { $0.0 < $1.0 }) where resolution.value.height >= 720 && resolution.value.hiDPI == !display.isHiDPI && resolution.value.width > previousWidth { - previousWidth = resolution.value.width - if resolution.value.width > display.width { - break - } - matchingResolutionKey = Int32(resolution.key) - } - if matchingResolutionKey != -1 { - display.changeResolution(resolutionItemNumber: matchingResolutionKey) - self.displayReconfiguration(force: true) - } - } - } - - @objc func associateDummy(_ sender: NSMenuItem) { - os_log("Received association request from tag %{public}@", type: .info, "\(sender.tag)") - guard sender.tag != 0 else { - return - } - let displayNumber = (sender.tag >> 8) & 0xFF - let dummyNumber = sender.tag & 0xFF - if let dummy = DummyManager.getDummyByNumber(dummyNumber), let display = DisplayManager.getDisplayByNumber(displayNumber) { - dummy.associateDisplay(display: display) - var askedForPermission = false - for otherDummy in DummyManager.getDummies() where otherDummy != dummy { - if otherDummy.hasAssociatedDisplay(), otherDummy.associatedDisplayPrefsId == dummy.associatedDisplayPrefsId { - if askedForPermission { - otherDummy.disassociateDisplay() - } else { - let alert = NSAlert() - alert.alertStyle = .warning - alert.messageText = "Disassociate and disconnect other associated dummies?" - alert.informativeText = "At least one other dummy is associated with this display. To avoid confusion, it is best to avoid this situation." - alert.addButton(withTitle: "Disassociate") - alert.addButton(withTitle: "No") - if alert.runModal() == .alertFirstButtonReturn { - otherDummy.disassociateDisplay() - otherDummy.disconnect() - askedForPermission = true - } else { - break - } - } - } - } - if !dummy.isConnected, DisplayManager.getDisplayByPrefsId(dummy.associatedDisplayPrefsId) != nil { - let alert = NSAlert() - alert.alertStyle = .informational - alert.messageText = "This dummy will now be connected." - alert.informativeText = "The dummy is now associated with a display that is connected therefore the dummy will automtically connect." - alert.runModal() - _ = dummy.connect() - } - self.menu.populateAppMenu() - DummyManager.storeDummiesToPrefs() - } - _ = sender.tag - } - - @objc func disassociateDummy(_ sender: NSMenuItem) { - if let dummy = DummyManager.getDummyByNumber(sender.tag), dummy.hasAssociatedDisplay() { - let associatedDisplayPrefsId = dummy.associatedDisplayPrefsId - dummy.disassociateDisplay() - if dummy.isConnected, DisplayManager.getDisplayByPrefsId(associatedDisplayPrefsId) != nil { - let alert = NSAlert() - alert.alertStyle = .warning - alert.messageText = "Do you want to disconnect the dummy as well?" - alert.informativeText = "The dummy is now disassociated from a display but the dummy is still connected." - alert.addButton(withTitle: "Disconnect") - alert.addButton(withTitle: "No") - if alert.runModal() == .alertFirstButtonReturn { - dummy.disconnect() - } - } - self.menu.populateAppMenu() - DummyManager.storeDummiesToPrefs() - } - } - - @objc func dummyResolution(_ sender: NSMenuItem) { - os_log("Received resolution change from tag %{public}@", type: .info, "\(sender.tag)") - guard sender.tag != 0 else { - return - } - let dummyNumber = (sender.tag >> 16) & 0xFFFF - let resolutionItemNumber = sender.tag & 0xFFFF - os_log("- Resolution change dummy %{public}@", type: .info, "\(dummyNumber)") - os_log("- Resolution change item %{public}@", type: .info, "\(resolutionItemNumber)") - if let dummy = DummyManager.getDummyByNumber(dummyNumber), let display = DisplayManager.getDisplayById(dummy.displayIdentifier) { - display.changeResolution(resolutionItemNumber: Int32(resolutionItemNumber)) - } - } - - // MARK: *** Handlers - General dummy management - - @objc func connectAllDummies(_: AnyObject?) { - os_log("Connecting all dummies.", type: .info) - var hasAssociated = false - for dummy in DummyManager.getDummies() where !dummy.isConnected { - if !dummy.hasAssociatedDisplay() { - _ = dummy.connect() - } else { - hasAssociated = true - } - } - if hasAssociated { - let alert = NSAlert() - alert.alertStyle = .warning - alert.messageText = "Dummies associated with displays cannot be manually connected!" - alert.informativeText = "A dummy which is associated with a display will automatically connect when the associated display is connected. All other dummies were connected." - alert.runModal() - } - self.menu.populateAppMenu() - DummyManager.storeDummiesToPrefs() - } - - @objc func disconnectAllDummies(_: AnyObject?) { - os_log("Disconnecting all dummies.", type: .info) - var hasAssociated = false - for dummy in DummyManager.getDummies() where dummy.isConnected { - if !dummy.hasAssociatedDisplay() { - dummy.disconnect() - } else { - hasAssociated = true - } - } - if hasAssociated { - let alert = NSAlert() - alert.alertStyle = .warning - alert.messageText = "Dummies associated with displays cannot be manually disconnected!" - alert.informativeText = "A dummy which is associated with a display will automatically disconnect when the associated display is disconnected." - alert.runModal() - } - self.menu.populateAppMenu() - DummyManager.storeDummiesToPrefs() - } - - @objc func discardAllDummies(_: AnyObject?) { - let alert = NSAlert() - alert.alertStyle = .critical - alert.messageText = "Do you want to discard all dummies?" - alert.informativeText = "If you would like to use the dummies later, it is better to use disconnect so macOS display configuration data is preserved." - alert.addButton(withTitle: "Cancel") - alert.addButton(withTitle: "Discard") - if alert.runModal() == .alertSecondButtonReturn { - os_log("Removing dummies.", type: .info) - DummyManager.discardAllDummies() - self.menu.populateAppMenu() - DummyManager.storeDummiesToPrefs() - } - } - - @objc func disassociateAllDummies(_: AnyObject?) { - let alert = NSAlert() - alert.alertStyle = .critical - alert.messageText = "Do you want to disassociate all dummies from all displays?" - alert.informativeText = "Connected dummies will remain connected after this operation." - alert.addButton(withTitle: "Cancel") - alert.addButton(withTitle: "Disassociate") - if alert.runModal() == .alertSecondButtonReturn { - os_log("Disassociating dummies.", type: .info) - for dummy in DummyManager.getDummies() { - dummy.disassociateDisplay() - self.menu.populateAppMenu() - DummyManager.storeDummiesToPrefs() - } - } - } - - // MARK: *** Handlers - Display reconfiguration - - @objc func displayReconfiguration(dispatchedReconfigureID: Int = 0, force: Bool = false) { - guard !self.skipReconfiguration else { - os_log("Display reconfiguration is forcefully skipped", type: .info) - return - } - if !force, dispatchedReconfigureID == 0 || self.isSleep { - self.reconfigureID += 1 - os_log("Bumping reconfigureID to %{public}@", type: .info, String(self.reconfigureID)) - if !self.isSleep { - let dispatchedReconfigureID = self.reconfigureID - os_log("Displays to be reconfigured with reconfigureID %{public}@", type: .info, String(dispatchedReconfigureID)) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { - self.displayReconfiguration(dispatchedReconfigureID: dispatchedReconfigureID) - } - } - } else if dispatchedReconfigureID == self.reconfigureID || force { - os_log("Request for display configuration with reconfigreID %{public}@", type: .info, String(dispatchedReconfigureID)) - self.reconfigureID = 0 - DisplayManager.configureDisplays() - DisplayManager.addDisplayCounterSuffixes() - DummyManager.connectDisconnectAssociatedDummies() - self.menu.populateAppMenu() - DummyManager.storeDummiesToPrefs() - } - } - - // MARK: *** Handlers - Settings - - @objc func startAtLogin(_: AnyObject?) { - let startAtLogin = (SMCopyAllJobDictionaries(kSMDomainUserLaunchd).takeRetainedValue() as? [[String: AnyObject]])?.first { $0["Label"] as? String == "\(Bundle.main.bundleIdentifier!)Helper" }?["OnDemand"] as? Bool ?? false - let identifier = "\(Bundle.main.bundleIdentifier!)Helper" as CFString - SMLoginItemSetEnabled(identifier, !startAtLogin) - self.menu.populateSettingsMenu() - } - - @objc func reset(_: AnyObject?) { - let alert = NSAlert() - alert.alertStyle = .critical - alert.messageText = "Are sure you want to reset BetterDummy?" - alert.informativeText = "This restores the default settings and discards all dummies in the process." - alert.addButton(withTitle: "Cancel") - alert.addButton(withTitle: "Reset") - if alert.runModal() == .alertSecondButtonReturn { - DummyManager.discardAllDummies() - DummyManager.dummyCounter = 0 - os_log("Cleared dummies.", type: .info) - if let bundleID = Bundle.main.bundleIdentifier { - prefs.removePersistentDomain(forName: bundleID) - } - os_log("Preferences reset complete.", type: .info) - self.setDefaultPrefs() - DummyManager.restoreDummiesFromPrefs() - self.menu.populateAppMenu() - self.menu.populateSettingsMenu() - } - } - - @objc func hideMenuIcon(_: AnyObject?) { - if prefs.bool(forKey: PrefKey.hideMenuIcon.rawValue) { - prefs.set(false, forKey: PrefKey.hideMenuIcon.rawValue) - self.menu.statusBarItem.isVisible = true - } else { - let alert = NSAlert() - alert.alertStyle = .warning - alert.messageText = "Do you want to hide the menu icon?" - alert.informativeText = "You can reveal the menu icon by launching the app again while the app is already running." - alert.addButton(withTitle: "Hide") - alert.addButton(withTitle: "No") - if alert.runModal() == .alertFirstButtonReturn { - prefs.set(true, forKey: PrefKey.hideMenuIcon.rawValue) - self.menu.statusBarItem.isVisible = false - } - } - self.menu.populateSettingsMenu() - } - - @objc func SUEnableAutomaticChecks(_: AnyObject?) { - prefs.set(!prefs.bool(forKey: PrefKey.SUEnableAutomaticChecks.rawValue), forKey: PrefKey.SUEnableAutomaticChecks.rawValue) - self.menu.populateSettingsMenu() - } - - @objc func disableTempSleep(_: AnyObject?) { - prefs.set(!prefs.bool(forKey: PrefKey.disableTempSleep.rawValue), forKey: PrefKey.disableTempSleep.rawValue) - self.menu.populateSettingsMenu() - } - - @objc func reconnectAfterSleep(_: AnyObject?) { - prefs.set(!prefs.bool(forKey: PrefKey.reconnectAfterSleep.rawValue), forKey: PrefKey.reconnectAfterSleep.rawValue) - self.menu.populateSettingsMenu() - } - - @objc func enable16K(_: AnyObject?) { - if !prefs.bool(forKey: PrefKey.enable16K.rawValue) { - let alert = NSAlert() - alert.alertStyle = .critical - alert.messageText = "Are you sure to enable 16K?" - alert.informativeText = "Using dummies over 8K can severely reduce performance on some setups." - alert.addButton(withTitle: "Cancel") - alert.addButton(withTitle: "Enable") - if alert.runModal() == .alertFirstButtonReturn { - return - } - } - prefs.set(!prefs.bool(forKey: PrefKey.enable16K.rawValue), forKey: PrefKey.enable16K.rawValue) - self.menu.populateSettingsMenu() - DummyManager.updateDummyDefinitions() - } - - @objc func hideLowResolutionOption(_: AnyObject?) { - prefs.set(!prefs.bool(forKey: PrefKey.hideLowResolutionOption.rawValue), forKey: PrefKey.hideLowResolutionOption.rawValue) - self.menu.populateSettingsMenu() - self.menu.populateAppMenu() - } - - // MARK: *** Handlers - Others - - @objc func about(_: AnyObject?) { - let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") ?? "UNKNOWN" - let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") ?? "UNKNOWN" - let year = Calendar.current.component(.year, from: Date()) - let alert = NSAlert() - alert.messageText = "About BetterDummy" - alert.informativeText = "Version \(version) Build \(build)\n\nCopyright Ⓒ \(year) @waydabber.\n\nCheck out the GitHub page for instructions or to report issues!" - alert.addButton(withTitle: "Visit GitHub page") - alert.addButton(withTitle: "OK") - alert.alertStyle = NSAlert.Style.informational - if alert.runModal() == .alertFirstButtonReturn { - if let url = URL(string: "https://github.com/waydabber/BetterDummy#readme") { - NSWorkspace.shared.open(url) - } - } - } - - @objc func donate(_: AnyObject?) { - if let url = URL(string: "https://opencollective.com/betterdummy/donate") { - NSWorkspace.shared.open(url) - } - let alert = NSAlert() - alert.messageText = "Thank you for your generousity!" - alert.informativeText = "If you find this app useful, please support the developer with your donation:\n\nopencollective.com/betterdummy\n\nWe opened the page for you in your browser. :)" - alert.runModal() - } - - // MARK: *** Handlers - Sleep and wake - - @objc func wakeNotification() { - guard self.isSleep else { - return - } - DummyManager.sleepTempVirtualDisplay = nil - os_log("Wake intercepted, removed temporary display if present.", type: .info) - self.isSleep = false - if prefs.bool(forKey: PrefKey.reconnectAfterSleep.rawValue) { - DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) { - if !self.isSleep { - os_log("Delayed reconnecting dummies after wake.", type: .info) - for dummy in DummyManager.getDummies() where !dummy.isConnected { - _ = dummy.connect(sleepConnect: true) - } - } - } - } - } - - @objc func sleepNotification() { - guard !self.isSleep else { - return - } - self.isSleep = true - if DummyManager.getNumOfDummies() > 0, !prefs.bool(forKey: PrefKey.disableTempSleep.rawValue) { - let maxWidth = 1920 - let maxHeight = 1080 - DummyManager.sleepTempVirtualDisplay = Dummy.createVirtualDisplay(DummyDefinition(maxWidth, maxHeight, 1, 1, 1, [60], "Dummy Temp", false), name: "Dummy Temp", serialNum: 0) - os_log("Sleep intercepted, created temporary display with the size of %{public}@x%{public}@", type: .info, String(maxWidth), String(maxHeight)) - } - if prefs.bool(forKey: PrefKey.reconnectAfterSleep.rawValue) { - os_log("Disconnecting dummies on sleep.", type: .info) - for dummy in DummyManager.getDummies() where dummy.isConnected { - dummy.disconnect(sleepDisconnect: true) - } - } - } -} diff --git a/BetterDummy/Support/AppMenu.swift b/BetterDummy/Support/AppMenu.swift deleted file mode 100644 index 6c9605f..0000000 --- a/BetterDummy/Support/AppMenu.swift +++ /dev/null @@ -1,260 +0,0 @@ -// -// BetterDummy -// -// Created by @waydabber -// - -import AppKit -import os.log -import ServiceManagement - -class AppMenu { - var statusBarItem: NSStatusItem! - - let appMenu = NSMenu() - let newMenu = NSMenu() - let settingsMenu = NSMenu() - - func setupMenu() { - self.statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength)) - if let button = self.statusBarItem.button { - button.image = NSImage(named: "status") - } - self.statusBarItem.menu = self.appMenu - self.statusBarItem.isVisible = !prefs.bool(forKey: PrefKey.hideMenuIcon.rawValue) - self.populateNewMenu() - self.populateSettingsMenu() - self.populateAppMenu() - } - - func emptyMenu(_ menuToEmpty: NSMenu) { - var items: [NSMenuItem] = [] - for i in 0 ..< menuToEmpty.items.count { - items.append(menuToEmpty.items[i]) - } - for item in items { - menuToEmpty.removeItem(item) - } - } - - func populateAppMenu() { - self.emptyMenu(self.appMenu) - var first = true - for key in DummyManager.definedDummies.keys.sorted(by: <) { - if let dummy = DummyManager.getDummyByNumber(key) { - if !first { - self.appMenu.addItem(NSMenuItem.separator()) - } - self.addDummyToMenu(dummy, key) - first = false - } - } - if DummyManager.dummyCounter >= 1 { - self.appMenu.addItem(NSMenuItem.separator()) - } - - let newSubmenu = NSMenuItem(title: "Create new dummy", action: nil, keyEquivalent: "") - newSubmenu.submenu = self.newMenu - self.appMenu.addItem(newSubmenu) - - self.addManageMenu() - - self.appMenu.addItem(NSMenuItem.separator()) - - let settingsSubmenu = NSMenuItem(title: "Settings", action: nil, keyEquivalent: "") - settingsSubmenu.submenu = self.settingsMenu - self.appMenu.addItem(settingsSubmenu) - let updateItem = NSMenuItem(title: "Check for updates...", action: #selector(app.updaterController.checkForUpdates(_:)), keyEquivalent: "") - updateItem.target = app.updaterController - self.appMenu.addItem(updateItem) - self.appMenu.addItem(NSMenuItem(title: "Donate...", action: #selector(app.donate(_:)), keyEquivalent: "")) - self.appMenu.addItem(NSMenuItem.separator()) - self.appMenu.addItem(NSMenuItem(title: "Quit BetterDummy", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")) - } - - func populateSettingsMenu() { - self.emptyMenu(self.settingsMenu) - - let attrs: [NSAttributedString.Key: Any] = [.foregroundColor: NSColor.headerTextColor, .font: NSFont.boldSystemFont(ofSize: 13)] - - let generalHeaderItem = NSMenuItem() - generalHeaderItem.attributedTitle = NSAttributedString(string: "General settings", attributes: attrs) - self.settingsMenu.addItem(generalHeaderItem) - - self.settingsMenu.addItem(self.checkmarkedMenuItem(checked: app.getStartAtLogin(), label: "Start at login", selector: #selector(app.startAtLogin))) - self.settingsMenu.addItem(self.checkmarkedMenuItem(checked: prefs.bool(forKey: PrefKey.SUEnableAutomaticChecks.rawValue), label: "Automatically check for updates", selector: #selector(app.SUEnableAutomaticChecks))) - self.settingsMenu.addItem(self.checkmarkedMenuItem(checked: prefs.bool(forKey: PrefKey.hideMenuIcon.rawValue), label: "Hide menu icon", selector: #selector(app.hideMenuIcon))) - - // --- - self.settingsMenu.addItem(NSMenuItem.separator()) - - let resolutionsHeaderItem = NSMenuItem() - resolutionsHeaderItem.attributedTitle = NSAttributedString(string: "Resolutions", attributes: attrs) - self.settingsMenu.addItem(resolutionsHeaderItem) - - self.settingsMenu.addItem(self.checkmarkedMenuItem(checked: prefs.bool(forKey: PrefKey.enable16K.rawValue), label: "Enable up to 16K resolutions", selector: #selector(app.enable16K))) - self.settingsMenu.addItem(self.checkmarkedMenuItem(checked: !prefs.bool(forKey: PrefKey.hideLowResolutionOption.rawValue), label: "Show low resolution (non-HiDPI) options", selector: #selector(app.hideLowResolutionOption))) - - // --- - self.settingsMenu.addItem(NSMenuItem.separator()) - - let sleepHeaderItem = NSMenuItem() - sleepHeaderItem.attributedTitle = NSAttributedString(string: "Sleep settings", attributes: attrs) - self.settingsMenu.addItem(sleepHeaderItem) - - self.settingsMenu.addItem(self.checkmarkedMenuItem(checked: !prefs.bool(forKey: PrefKey.disableTempSleep.rawValue), label: "Use mirrored dummy sleep workaround", selector: #selector(app.disableTempSleep))) - self.settingsMenu.addItem(self.checkmarkedMenuItem(checked: prefs.bool(forKey: PrefKey.reconnectAfterSleep.rawValue), label: "Disconnect and reconnect on sleep", selector: #selector(app.reconnectAfterSleep))) - - // --- - - self.settingsMenu.addItem(NSMenuItem.separator()) - self.settingsMenu.addItem(NSMenuItem(title: "About BetterDummy", action: #selector(app.about(_:)), keyEquivalent: "")) - self.settingsMenu.addItem(NSMenuItem(title: "Reset BetterDummy", action: #selector(app.reset(_:)), keyEquivalent: "")) - } - - func populateNewMenu() { - self.emptyMenu(self.newMenu) - for key in DummyManager.dummyDefinitions.keys.sorted() { - if let dummyDefinition = DummyManager.dummyDefinitions[key] { - let item = NSMenuItem(title: "\(dummyDefinition.description)", action: #selector(app.createDummy(_:)), keyEquivalent: "") - item.tag = key - self.newMenu.addItem(item) - if dummyDefinition.addSeparatorAfter { - self.newMenu.addItem(NSMenuItem.separator()) - } - } - } - os_log("New dummy menu populated.", type: .info) - } - - func addManageMenu() { - let manageMenu = NSMenu() - - if DummyManager.dummyCounter > 1 { - var isThereDisconnected = false - var isThereConnected = false - var isThereAssociated = false - var isThereAny = false - for dummy in DummyManager.getDummies() { - isThereAny = true - if dummy.isConnected { - isThereConnected = true - } - if !dummy.isConnected { - isThereDisconnected = true - } - if dummy.hasAssociatedDisplay() { - isThereAssociated = true - } - } - - if isThereDisconnected { - manageMenu.addItem(NSMenuItem(title: "Connect all dummies", action: #selector(app.connectAllDummies(_:)), keyEquivalent: "")) - } - if isThereConnected { - manageMenu.addItem(NSMenuItem(title: "Disconnect all dummies", action: #selector(app.disconnectAllDummies(_:)), keyEquivalent: "")) - } - if isThereAssociated { - manageMenu.addItem(NSMenuItem(title: "Disassociate all dummies", action: #selector(app.disassociateAllDummies(_:)), keyEquivalent: "")) - } - if isThereAny { - manageMenu.addItem(NSMenuItem(title: "Discard all dummies", action: #selector(app.discardAllDummies(_:)), keyEquivalent: "")) - let manageSubmenu = NSMenuItem(title: "Manage dummies", action: nil, keyEquivalent: "") - manageSubmenu.submenu = manageMenu - self.appMenu.addItem(manageSubmenu) - } - } - } - - func getResolutionSubmenuItem(_ dummy: Dummy, _ number: Int) -> NSMenuItem? { - let resolutionMenu = NSMenu() - if let resolutions = DisplayManager.getDisplayById(dummy.displayIdentifier)?.resolutions { - for resolution in resolutions.sorted(by: { $0.0 < $1.0 }) where resolution.value.height >= 720 && resolution.value.hiDPI == true { - let resolutionMenuItem = NSMenuItem(title: "\(resolution.value.width)x\(resolution.value.height)", action: #selector(app.dummyResolution(_:)), keyEquivalent: "") - resolutionMenuItem.tag = number * 256 * 256 + resolution.key - resolutionMenuItem.state = resolution.value.isActive ? .on : .off - resolutionMenu.addItem(resolutionMenuItem) - } - resolutionMenu.addItem(NSMenuItem.separator()) - if !prefs.bool(forKey: PrefKey.hideLowResolutionOption.rawValue) { - for resolution in resolutions.sorted(by: { $0.0 < $1.0 }) where resolution.value.height >= 720 && resolution.value.hiDPI == false { - let resolutionMenuItem = NSMenuItem(title: "\(resolution.value.width)x\(resolution.value.height)" + " (low resolution)", action: #selector(app.dummyResolution(_:)), keyEquivalent: "") - resolutionMenuItem.tag = number * 256 * 256 + resolution.key - resolutionMenuItem.state = resolution.value.isActive ? .on : .off - resolutionMenu.addItem(resolutionMenuItem) - } - } - } else { - let unavailableItem = NSMenuItem(title: "Unavailable", action: nil, keyEquivalent: "") - unavailableItem.isEnabled = false - resolutionMenu.addItem(unavailableItem) - } - let resolutionSubmenu = NSMenuItem(title: "Set resolution", action: nil, keyEquivalent: "") - resolutionSubmenu.image = NSImage(systemSymbolName: "arrow.up.backward.and.arrow.down.forward", accessibilityDescription: "icon") - resolutionSubmenu.submenu = resolutionMenu - return resolutionSubmenu - } - - func getAssociateSubmenuItem(_ dummy: Dummy, _ number: Int) -> NSMenuItem { - let associateMenu = NSMenu() - var foundAssociatedDisplay = false - for displayNumber in DisplayManager.displays.keys { - if let display = DisplayManager.displays[displayNumber], !display.isDummy { - let displayItem = NSMenuItem(title: display.name, action: #selector(app.associateDummy(_:)), keyEquivalent: "") - displayItem.tag = 0x100 * displayNumber + number // This is a composite tag identifying both the display and the dummy number - if display.prefsId == dummy.associatedDisplayPrefsId, dummy.hasAssociatedDisplay() { - displayItem.state = .on - foundAssociatedDisplay = true - } - associateMenu.addItem(displayItem) - } - } - if dummy.hasAssociatedDisplay() { - if !foundAssociatedDisplay { - let displayItem = NSMenuItem(title: "\(dummy.associatedDisplayName) (disconnected)", action: #selector(app.associateDummy(_:)), keyEquivalent: "") - displayItem.state = .on - associateMenu.addItem(displayItem) - displayItem.tag = 0 // This signifies that this is a disconnected, already associated display - } - associateMenu.addItem(NSMenuItem.separator()) - let disassociateItem = NSMenuItem(title: "Disassociate", action: #selector(app.disassociateDummy(_:)), keyEquivalent: "") - disassociateItem.tag = number - associateMenu.addItem(disassociateItem) - } - let associateSubmenu = NSMenuItem(title: dummy.hasAssociatedDisplay() ? "\(dummy.associatedDisplayName)" : "Associate with...", action: nil, keyEquivalent: "") - associateSubmenu.image = NSImage(systemSymbolName: dummy.hasAssociatedDisplay() ? "link" : "link.badge.plus", accessibilityDescription: "icon") - associateSubmenu.submenu = associateMenu - return associateSubmenu - } - - func checkmarkedMenuItem(checked: Bool, label: String, tag: Int? = nil, selector: Selector) -> NSMenuItem { - let menuItem = NSMenuItem(title: label, action: selector, keyEquivalent: "") - if let tag = tag { - menuItem.tag = tag - } - menuItem.state = checked ? .on : .off - menuItem.onStateImage = nil - menuItem.image = NSImage(systemSymbolName: checked ? "checkmark.circle" : "circle", accessibilityDescription: "icon") - return menuItem - } - - func addDummyToMenu(_ dummy: Dummy, _ number: Int) { - let dummyHeaderItem = NSMenuItem() - let attributedHeader = NSMutableAttributedString() - var attrs: [NSAttributedString.Key: Any] = [.foregroundColor: NSColor.headerTextColor, .font: NSFont.boldSystemFont(ofSize: 13)] - attributedHeader.append(NSAttributedString(string: "\(dummy.getName())", attributes: attrs)) - attrs = [.foregroundColor: NSColor.systemGray, .font: NSFont.systemFont(ofSize: 13)] - attributedHeader.append(NSAttributedString(string: " (\(dummy.getSerialNumber()))", attributes: attrs)) - dummyHeaderItem.attributedTitle = attributedHeader - self.appMenu.addItem(dummyHeaderItem) - self.appMenu.addItem(self.getAssociateSubmenuItem(dummy, number)) - if dummy.isConnected, let resolutionSubmenuItem = self.getResolutionSubmenuItem(dummy, number) { - self.appMenu.addItem(resolutionSubmenuItem) - } - self.appMenu.addItem(self.checkmarkedMenuItem(checked: dummy.isConnected, label: "Connected\(dummy.hasAssociatedDisplay() ? " (automatic)" : "")", tag: number, selector: #selector(app.connectDisconnectDummy))) - let deleteItem = NSMenuItem(title: "Discard dummy", action: #selector(app.discardDummy(_:)), keyEquivalent: "") - deleteItem.image = NSImage(systemSymbolName: "exclamationmark.triangle", accessibilityDescription: "icon") - deleteItem.tag = number - self.appMenu.addItem(deleteItem) - } -} diff --git a/BetterDummy/Support/DisplayManager.swift b/BetterDummy/Support/DisplayManager.swift deleted file mode 100644 index c375e92..0000000 --- a/BetterDummy/Support/DisplayManager.swift +++ /dev/null @@ -1,140 +0,0 @@ -// -// BetterDummy -// -// Created by @waydabber -// - -import Cocoa -import CoreGraphics -import os.log - -class DisplayManager { - static var displays: [Int: Display] = [:] - static var displayCounter: Int = 0 // This is an ever increasing temporary number, does not reflect the actual number of displays. - - static func getDisplays() -> [Display] { - var displays: [Display] = [] - for display in self.displays.values { - displays.append(display) - } - return displays - } - - static func getDisplayById(_ displayID: CGDirectDisplayID) -> Display? { - self.displays.values.first { $0.identifier == displayID } - } - - static func getDisplayByPrefsId(_ DisplayPrefsId: String) -> Display? { - self.displays.values.first { $0.prefsId == DisplayPrefsId } - } - - static func getDisplayByNumber(_ number: Int) -> Display? { - self.displays[number] - } - - static func getBuiltInDisplay() -> Display? { - self.displays.values.first { CGDisplayIsBuiltin($0.identifier) != 0 } - } - - static func addDisplay(display: Display) { - self.displayCounter += 1 - self.displays[self.displayCounter] = display - } - - static func clearDisplays() { - self.displays = [:] - self.displayCounter = 0 - } - - static 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) - let display = Display(id, name: name, vendorNumber: vendorNumber, modelNumber: modelNumber, serialNumber: serialNumber, isVirtual: isVirtual, isDummy: isDummy) - os_log("Display found -%{public}@", type: .info, "\(display.isVirtual ? " VIRTUAL" : "")\(display.isDummy ? " DUMMY" : "") id: \(display.identifier), name: \(display.name), vendor: \(display.vendorNumber ?? 0), model: \(display.modelNumber ?? 0), s/n: \(display.serialNumber ?? 0)") - self.addDisplay(display: display) - } - self.addDisplayCounterSuffixes() - } - - static func addDisplayCounterSuffixes() { - var nameDisplays: [String: [Display]] = [:] - for display in self.displays.values { - 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) + ")" - } - } - } - } - - static func isDummy(displayID: CGDirectDisplayID) -> Bool { - let rawName = DisplayManager.getDisplayNameByID(displayID: displayID) - var isDummy: Bool = false - if rawName.lowercased().contains("dummy") { - os_log("Display seems to be a BetterDummy created dummy.", type: .info) - isDummy = true - } - return isDummy - } - - static func isVirtual(displayID: CGDirectDisplayID) -> Bool { - var isVirtual: Bool = false - 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 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 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 - } - - static func getDisplayNameByID(displayID: CGDirectDisplayID) -> String { - let defaultName: String = "Unknown" - 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 - } - return defaultName - } -} diff --git a/BetterDummy/Support/DummyManager.swift b/BetterDummy/Support/DummyManager.swift deleted file mode 100644 index 98a3672..0000000 --- a/BetterDummy/Support/DummyManager.swift +++ /dev/null @@ -1,161 +0,0 @@ -// -// BetterDummy -// -// Created by @waydabber -// - -import Foundation -import os.log - -class DummyManager { - struct DefinedDummy { - var dummy: Dummy - var definitionId: Int? - } - - static var definedDummies: [Int: DefinedDummy] = [:] - static var dummyCounter: Int = 0 // This is an ever increasing temporary number, does not reflect the actual number of dummies. - static var sleepTempVirtualDisplay: CGVirtualDisplay? - static var dummyDefinitions: [Int: DummyDefinition] = [:] - - static func createDummyByDefinitionId(_ dummyDefinitionId: Int, isPortrait: Bool = false, serialNum: UInt32 = 0, doConnect: Bool = true) -> Int? { - if let dummyDefinition = self.dummyDefinitions[dummyDefinitionId] { - return self.createDummy(dummyDefinition, dummyDefinitionId: dummyDefinitionId, isPortrait: isPortrait, serialNum: serialNum, doConnect: doConnect) - } - return nil - } - - static func createDummy(_ dummyDefinition: DummyDefinition, dummyDefinitionId: Int? = nil, isPortrait _: Bool = false, serialNum: UInt32 = 0, doConnect: Bool = true) -> Int { - let dummy = Dummy(dummyDefinition: dummyDefinition, serialNum: serialNum, doConnect: doConnect) - self.dummyCounter += 1 - self.definedDummies[self.dummyCounter] = DefinedDummy(dummy: dummy, definitionId: dummyDefinitionId) - return self.dummyCounter - } - - static func getDummies() -> [Dummy] { - var dummies: [Dummy] = [] - for definedDummy in self.definedDummies.values { - dummies.append(definedDummy.dummy) - } - return dummies - } - - static func discardDummyByNumber(_ number: Int) { - self.definedDummies[number] = nil - } - - static func discardAllDummies() { - self.definedDummies = [:] - self.dummyCounter = 0 - } - - static func getDummyByNumber(_ number: Int) -> Dummy? { - self.definedDummies[number]?.dummy - } - - static func getDefinitionIdByNumber(_ number: Int) -> Int? { - self.definedDummies[number]?.definitionId - } - - static func getNumOfDummies() -> Int { - self.definedDummies.count - } - - static func connectDisconnectAssociatedDummies() { - for dummy in self.getDummies() { - if dummy.hasAssociatedDisplay() { - if DisplayManager.getDisplayByPrefsId(dummy.associatedDisplayPrefsId) != nil { - if !dummy.isConnected { - os_log("Connecting associated dummy %{public}@ for display %{public}@", type: .info, dummy.getName(), dummy.associatedDisplayPrefsId) - _ = dummy.connect() - } - } else { - if dummy.isConnected { - os_log("Disconnecting associated dummy %{public}@ for lack of display %{public}@", type: .info, dummy.getName(), dummy.associatedDisplayPrefsId) - dummy.disconnect() - } - } - } - } - } - - static func getDefinedDummyByDisplayId(_ displayID: CGDirectDisplayID) -> DefinedDummy? { - self.definedDummies.values.first { $0.dummy.displayIdentifier == displayID } - } - - static func getDummyByDisplayId(_ displayID: CGDirectDisplayID) -> Dummy? { - self.getDefinedDummyByDisplayId(displayID)?.dummy - } - - static func updateDummyDefinitions() { - let refreshRates: [Double] = [60] // [24, 25, 30, 48, 50, 60, 90, 120] -- only 60Hz seems to be useful in practice - self.dummyDefinitions = [ - 10: DummyDefinition(16, 9, 2, refreshRates, "16:9 (HD/4K/5K/6K)", false), - 20: DummyDefinition(16, 10, 2, refreshRates, "16:10 (W*XGA)", false), - 30: DummyDefinition(16, 12, 2, refreshRates, "4:3 (VGA, iPad)", false), - 40: DummyDefinition(256, 135, 2, refreshRates, "17:9 (4K-DCI)", true), - 50: DummyDefinition(64, 27, 2, refreshRates, "21.3:9 (UW-HD/4K/5K)", false), - 60: DummyDefinition(43, 18, 2, refreshRates, "21.5:9 (UW-QHD)", false), - 70: DummyDefinition(24, 10, 1, refreshRates, "24:10 (UW-QHD+)", false), - 80: DummyDefinition(32, 10, 1, refreshRates, "32:10 (D-W*XGA)", false), - 90: DummyDefinition(32, 9, 2, refreshRates, "32:9 (D-HD/QHD)", true), - 100: DummyDefinition(20, 20, 2, refreshRates, "1:1 (Square)", true), - 110: DummyDefinition(9, 16, 2, refreshRates, "9:16 (HD/4K/5K/6K - Portrait)", false), - 120: DummyDefinition(10, 16, 2, refreshRates, "10:16 (W*XGA - Portrait)", false), - 130: DummyDefinition(12, 16, 2, refreshRates, "12:16 (VGA - Portrait)", false), - 140: DummyDefinition(135, 256, 2, refreshRates, "9:17 (4K-DCI - Portrait)", true), - 210: DummyDefinition(15, 10, 2, refreshRates, "3:2 (Photography)", false), - 220: DummyDefinition(15, 12, 2, refreshRates, "5:4 (Photography)", true), - 350: DummyDefinition(152, 100, 1, refreshRates, "15.2:10 (iPad Mini 2021)", false), - 360: DummyDefinition(66, 41, 2, refreshRates, "23:16 (iPad Air 2020)", false), - 370: DummyDefinition(199, 139, 2, refreshRates, "14.3:10 (iPad Pro 11\")", false), - ] - for definedDummy in self.definedDummies.values { - if let definitionId = definedDummy.definitionId, let dummyDefinition = self.dummyDefinitions[definitionId] { - definedDummy.dummy.dummyDefinition = dummyDefinition - } - if definedDummy.dummy.isConnected { - definedDummy.dummy.disconnect() - _ = definedDummy.dummy.connect() - } - } - } - - static func restoreDummiesFromPrefs() { - os_log("Restoring dummies.", type: .info) - guard prefs.integer(forKey: "numOfDummyDisplays") > 0 else { - return - } - for i in 1 ... prefs.integer(forKey: PrefKey.numOfDummyDisplays.rawValue) where prefs.object(forKey: "\(PrefKey.display.rawValue)\(i)") != nil { - if let number = DummyManager.createDummyByDefinitionId(prefs.integer(forKey: "\(PrefKey.display.rawValue)\(i)"), serialNum: UInt32(prefs.integer(forKey: "\(PrefKey.serial.rawValue)\(i)")), doConnect: false) { - if let dummy = DummyManager.getDummyByNumber(number) { - dummy.associatedDisplayPrefsId = prefs.string(forKey: "\(PrefKey.associatedDisplayPrefsId.rawValue)\(i)") ?? "" - dummy.associatedDisplayName = prefs.string(forKey: "\(PrefKey.associatedDisplayName.rawValue)\(i)") ?? "" - if prefs.bool(forKey: "\(PrefKey.isConnected.rawValue)\(i)") { - _ = dummy.connect() - } - } - } - } - app.menu.populateAppMenu() - } - - static func storeDummiesToPrefs() { - os_log("Storing preferences.", type: .info) - prefs.set(DummyManager.getNumOfDummies(), forKey: PrefKey.numOfDummyDisplays.rawValue) - guard DummyManager.getNumOfDummies() > 0 else { - return - } - var i = 1 - for key in DummyManager.definedDummies.keys.sorted(by: <) { - if let definedDummy = DummyManager.definedDummies[key] { - prefs.set(definedDummy.definitionId, forKey: "\(PrefKey.display.rawValue)\(i)") - prefs.set(definedDummy.dummy.serialNum, forKey: "\(PrefKey.serial.rawValue)\(i)") - prefs.set(definedDummy.dummy.isConnected, forKey: "\(PrefKey.isConnected.rawValue)\(i)") - prefs.set(definedDummy.dummy.associatedDisplayPrefsId, forKey: "\(PrefKey.associatedDisplayPrefsId.rawValue)\(i)") - prefs.set(definedDummy.dummy.associatedDisplayName, forKey: "\(PrefKey.associatedDisplayName.rawValue)\(i)") - i += 1 - } - } - } -} diff --git a/BetterDummy/Support/UpdaterDelegate.swift b/BetterDummy/Support/UpdaterDelegate.swift deleted file mode 100644 index b5b9d86..0000000 --- a/BetterDummy/Support/UpdaterDelegate.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// BetterDummy -// -// Created by @waydabber -// - -import Foundation -import Sparkle - -class UpdaterDelegate: NSObject, SPUUpdaterDelegate { - func allowedChannels(for _: SPUUpdater) -> Set { - prefs.bool(forKey: PrefKey.isBetaChannel.rawValue) ? Set(["beta"]) : Set([]) - } -} diff --git a/BetterDummy/main.swift b/BetterDummy/main.swift deleted file mode 100644 index 57500da..0000000 --- a/BetterDummy/main.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// BetterDummy -// -// Created by @waydabber -// - -import Cocoa - -let prefs = UserDefaults.standard -var app: AppDelegate! - -autoreleasepool { () -> Void in - let app = NSApplication.shared - let appDelegate = AppDelegate() - app.delegate = appDelegate - app.run() -} diff --git a/BetterDummyHelper/BetterDummyHelper.entitlements b/BetterDummyHelper/BetterDummyHelper.entitlements deleted file mode 100644 index ba00d97..0000000 --- a/BetterDummyHelper/BetterDummyHelper.entitlements +++ /dev/null @@ -1,12 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.cs.allow-jit - - com.apple.security.files.user-selected.read-only - - - diff --git a/BetterDummyHelper/Info.plist b/BetterDummyHelper/Info.plist deleted file mode 100644 index bd5308a..0000000 --- a/BetterDummyHelper/Info.plist +++ /dev/null @@ -1,12 +0,0 @@ - - - - - CFBundleIconFile - - LSBackgroundOnly - - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - - diff --git a/BetterDummyHelper/main.swift b/BetterDummyHelper/main.swift deleted file mode 100644 index d8737eb..0000000 --- a/BetterDummyHelper/main.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// AppDelegate.swift -// BetterDummyHelper -// -// Created by @waydabber -// - -import Cocoa - -class AppDelegate: NSObject, NSApplicationDelegate { - @available(macOS, deprecated: 11.00) - func applicationDidFinishLaunching(_: Notification) { - let mainBundleID = Bundle.main.bundleIdentifier!.replacingOccurrences(of: "Helper", with: "") - - let bundlePath = Bundle.main.bundlePath as NSString - - guard NSRunningApplication.runningApplications(withBundleIdentifier: mainBundleID).isEmpty else { - return NSApp.terminate(self) - } - - let pathComponents = bundlePath.pathComponents - let path = NSString.path(withComponents: Array(pathComponents[0 ..< (pathComponents.count - 4)])) - - NSWorkspace.shared.launchApplication(path) - NSApp.terminate(nil) - } - - func applicationWillTerminate(_: Notification) {} -} - -let app = NSApplication.shared -let delegate = AppDelegate() -app.delegate = delegate -app.run() diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 1517290..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 Istvan T. - -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. diff --git a/README.md b/README.md index bf403b6..e7d03f9 100644 --- a/README.md +++ b/README.md @@ -1,217 +1 @@ -App icon - -
-

BetterDummy

-

Dummy Display for Apple Silicon Macs to Have Custom HiDPI Resolutions - an app from one of the makers of MonitorControl.

-Download for macOS -

- -
- -
- - - -downloads - - - -latest version - - - -license - - - -platform - - - -backers - -
- -
- -## About - -Some Macs tend to have issues with custom resolutions. The new Apple Silicon Macs notoriously don't allow sub-4K resolution displays to have HiDPI ("Retina") resolutions even though most 1440p display would greatly benefit from having a HiDPI "Retina" mode. On other Macs the resolution options for wide displays are too constrained. To fix these issues, some resort to buying a 4K HDMI dummy dongle to fool macOS into thinking that a 4K display is connected and then mirror the contents of this dummy display to their actual monitor in order to have HiDPI resolutions available. Others use the built in screens of their MacBooks as a mirror source. These approaches have obvious drawbacks and limits. - -BetterDummy solves the problem by creating a flexible virtual "dummy" display that supports an unprecedented range of Retina resolutions. You can then utilize this dummy display as a mirror source for your display achieving any HiDPI resolution. - -Advantages of BetterDummy over a physical 4K HDMI dummy plug or mirroring your internal display: - -- Your HDMI port will remain usable for an other display on the Mac Mini -- Your internal screen will be available as an extended space on a MacBook (or you can use clamshell mode). -- Does not suffer from issues that prevalent with the physical dummy (like jittery mouse cursor). -- Offers a much wider range of HiDPI and standard resolutions. -- Works with all aspect ratios, does not depend on what resoluations are recorded in the dummy's EDID/firmware. -- Available instantly. - -Some other uses: - -- The app is useful for anybody who is not satisfied with the offered default HiDPI resolutions offered by macOS. -- Use headless Macs (servers) with any resolution and HiDPI mode for remote access. -- Scale Sidecar resolutions. -- Better quality zooming (`System Preferences`»`Accessibility`»`Zoom`) or High Quality screenshots even on 1080p displays. -- You can use it instead of or alongside other apps that create custom native resolutions. - -## Usage - -1. Start the app -2. In the app menu choose `Create New Dummy` and select your desired aspect ratio -
-
- -
-
- -3. In `System Preferences` -> `Displays` you'll see the new Dummy display (for example `Dummy 16:9`) -4. Activate mirroring. The `Main` display should be the Dummy display -5. Set the Dummy display as `Optimize for` -6. Set the `Resolution` as `Scaled` (you should hold the `Option` key while clicking on the `Scaled` option for a full list of resolutions!) or use the app's own resolution selector located in the app menu. - -
- -
- -8. Select the desired mode. - -The app saves the dummy display configuration and automatically restores it upon next restart. - -For more information on usage, see the [the additional help section](#additional-help). - -Notes: - -- The tutorial was compiled on macOS Monterey (for Big Sur as well - steps are slightly different, see [this article](https://macfinder.co.uk/blog/how-to-mirror-specific-displays-in-os-x-mirror-some-but-not-all-of-your-monitors-on-an-apple-system/) on how to customize mirroring on Big Sur). -- For most configurations, you'll see HiDPI 'Retina' resolutions in the list by default and see and additional non-HiDPI resolutions marked with a `(low resolution)` tag in the resolution list if `Show all resolutions` is toggled. On some configurations however, you might see HiDPI (high resolution) display modes marked with a `(HiDPI)` tag and standard resolutions _without a tag_. -- You might have to fight a bit with macOS Monterey's new `Displays` tab in Preferences as the `Optimize for` setting tends to reset at random times to the physical display for unknown reasons during changing settings. If this happens, you can set it back to the Dummy. -- You may want to enable the `System Preferences`»`Notifications & Focus`»`Allow Notifications`»`When mirroring or sharing the display` option to allow notifications when mirroring is turned on. - -## Installation - -- Download the [latest release](https://github.com/waydabber/BetterDummy/releases) -- Move the app to Applications -- Start the app -- Use the app menu bar item to interact. - -## Supporting the project - -I am thankful for each of you who [contributed to the project](https://opencollective.com/betterdummy). Every little bit helps! If you find use in the app and did not contribute so far, please consider a donation so I can continue working on this app. :) Thank you! - -Super generous contributors, who donated $100 or more: - -- Riten Jaiswal - $200 -- Will_from_CA - $100 - -Notable contributors, who donated $20 or more: - -- Felix -- Emilio P Egido -- Thomas Varghese -- Reactual -- Stephen Richardson -- Peter Szombati -- NP -- David Verdonck -- Knut Holm -- Jan Behrmann -- Danilo -- Andrew Braithwaite -- Splay Display -- Guest -- Incognito -- Florian Gross -- David Richardson -- Jari Hanhela -- William Edney -- David W -- Chetan Kunte -- Martin Clayton -- Nikola Milojević -- Wolf1701 -- Arthur Müller -- Tom Dai -- Jeff Lopes - -(list updated as of 11/12/2021) - -Do you miss your name? [Join the list!](https://opencollective.com/betterdummy/donate) - -Please don't forget to star the GitHub page and spread the word about the app. :) - -## Compatibility - -- The app should be compatible with all M1 class machines running macOS Monterey (MacBook Air, Mini, 2020 and 2021 MacBook Pros). -- The app is also compatible with more recent Intel Macs and macOS Big Sur but mirroring might not work as expected (testing was limited to a single Intel Mac with Intel UHD 630 running Big Sur). -- The app is compatible with headless mode as well (this was tested on Intel). - -### Known issues - -Please take a look at the [list of known issues](https://github.com/waydabber/BetterDummy/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22known+issue%22) before using the app or submitting an Issue. - -### Enhancements - -I am continuously working on improving the app. You can check out the [planned features and their status here](https://github.com/waydabber/BetterDummy/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Aenhancement). - -## Some notable articles about BetterDummy - -BetterDummy is now famous! :) - -- https://www.theregister.com/2021/12/03/apple_m1_drivers -- https://9to5mac.com/2021/11/23/enable-1440p-retina-scaling-m1-mac/ -- https://www.macworld.com/article/549493/how-to-m1-mac-1440p-display-hidpi-retina-scaling.html - -Also the app made it to the featured news (once took the first spot) in Hacker News. - -- https://news.ycombinator.com/item?id=29064234 -- https://news.ycombinator.com/item?id=29469837 - -## Additional help - -**The following section and the screenshots are somewhat outdated as they represent app version 1.0.10.** - -For most of the time, the app is self explanatory but here is a brief overview of some of the additional functions in the app. - -### Manage your dummies - -
- -
-
- -- The `Manage dummies` submenu header contains some basic info about the dummy - its aspect ratio and unique serial number. -- `Connect dummy` / `Disconnect dummy` - You can easily connect or disconnect a dummy. Only dummes that are not associated to a display can be connected or disconnected manually. -- `Set resolution` - This allows for a quick resolution change. The list conatains all supported resolutions. If `Show low resolution modes` is enabled under `Settings`, then additional non-HiDPI resolutions are shown at the bottom of the list. -- `Associate with display` or `Change association` - You can associate a dummy to a real display. This will make the dummy automatically connect or disconnect whenever the display is connected or disconnected. It is useful when docking/undocking your macbook so you don't have to manage your dummy manually every time. -- `Discard dummy` - Destroys the dummy. Use it only if you don't need a dummy anymore - otherwise it is better to just disconnect or disassociate the dummy so macOS can remember the dummy for later use (by its unique serial number). - -### App settings - -
- -
-
- -- `Start at login`, `Automatically check for updates` - general options -- `Hide menu icon` - when the menu icon is hidden, you can still change settings - simply launch the app again while it is already running and the menu icon will reappear. -- `Enable up to 16K resolutions` - you can enable up to 16384x16384 resolution (this translates to 8192x8192 HiDPI). This is mostly useful for ultrawide and double-wide displays, where the standard 8192px max resolution might still be constraining. Use with caution as very high resolutions tax the system heavily. -- `Show low resolution modes in menu` will allow you to set low (non-HiDPI) resolutions in the app menu under `Manage dummies` -- `Use mirrored dummy sleep workaround` - macOS has a bug that makes virtual displays (like Sidecar, AirPlay, DisplayLink, BetterDummy) freeze when they act as a mirror main on some configurations. This workaround intends to solve that issue (more or less successfully). -- `Disconnect and reconnect on sleep` - This disconnects dummies upon sleeps and reconnects them after wake. -- `Reset BetterdDummy` - discard all dummies and reset all application settings to default. - -## Don't forget to check out - -**If you like BetterDummy, you'll like [MonitorControl](https://monitorcontrol.app) as well!** Control the brightness, volume of your external display like a native Apple display! The two apps are fully optimized to work togeteher. - -## Thanks - -- [@tml1024](https://github.com/tml1024)! - for FluffyDisplay as a starting point for this project -- [@w0lfschild](https://github.com/w0lfschild) - for maintaining reverse engineered macOS headers -- [@JoniVR](https://github.com/JoniVR) and [@the0neyouseek](https://github.com/the0neyouseek) - from MonitorControl - -## Discord channel - -You can join the (mostly self help) discussion on the new [BetterDummy discord channel](https://discord.gg/aKe5yCWXSp). +This branch is to hold tags for releases from an external repo. \ No newline at end of file diff --git a/docs/appcast.xml b/docs/appcast.xml deleted file mode 100644 index cea53c2..0000000 --- a/docs/appcast.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - BetterDummy - https://waydabber.github.io/BetterDummy - - 1.0.12 - - https://waydabber.github.io/BetterDummy/changelog.html?tag=v1.0.12 - - Sun, 28 Nov 2021 17:33:17 +0100 - 1195 - 1.0.12 - 11.0 - - - - 1.0.10 - - https://waydabber.github.io/BetterDummy/changelog.html?tag=v1.0.10 - - Fri, 12 Nov 2021 13:03:16 +0100 - 670 - 1.0.10 - 11.0 - - - - 1.0.9 - - https://waydabber.github.io/BetterDummy/changelog.html?tag=v1.0.9 - - Fri, 05 Nov 2021 21:04:09 +0100 - 544 - 1.0.9 - 11.0 - - - - 1.0.7 - - https://waydabber.github.io/BetterDummy/changelog.html?tag=v1.0.7 - - Sat, 30 Oct 2021 00:55:27 +0200 - 416 - 1.0.7 - 11.0 - - - - 1.0.6b2 - - https://waydabber.github.io/BetterDummy/changelog.html?tag=v1.0.6b - - Fri, 29 Oct 2021 20:28:49 +0200 - 240 - 1.0.6b2 - 11.0 - - - - diff --git a/docs/changelog.html b/docs/changelog.html deleted file mode 100644 index cfaaa3e..0000000 --- a/docs/changelog.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - Changelog - - - - - diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index da2fb36..0000000 --- a/docs/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - BetterDummy - redirect - - - - If you are not redirected automatically, follow this link!. - - diff --git a/docs/scripts/marked-footnotes.min.js b/docs/scripts/marked-footnotes.min.js deleted file mode 100644 index 76c92f6..0000000 --- a/docs/scripts/marked-footnotes.min.js +++ /dev/null @@ -1,42 +0,0 @@ -const footnoteMatch = /^\[\^([^\]]+)\]:([\s\S]*)$/; -const referenceMatch = /\[\^([^\]]+)\](?!\()/g; -const referencePrefix = "fnref"; -const footnotePrefix = "fn"; -const footnoteTemplate = (ref, text) => { - return `

${ref}. ${text}

`; -}; -const footnoteContainerTemplate = (text) => { - return `

Footnotes

${text}` -} -const referenceTemplate = ref => { - return `[${ref}]`; -}; -const interpolateReferences = (text) => { - return text.replace(referenceMatch, (_, ref) => { - return referenceTemplate(ref); - }); -} -const interpolateFootnotes = (text) => { - const found = text.match(footnoteMatch) - if (found) { - const replacedText = text.replace(footnoteMatch, (_, value, text) => { - return footnoteTemplate(value, text); - }); - return footnoteContainerTemplate(replacedText) - } - return text -} - -const renderer = { - paragraph(text) { - return marked.Renderer.prototype.paragraph.apply(null, [ - interpolateReferences(interpolateFootnotes(text)) - ]); - }, - text(text) { - return marked.Renderer.prototype.text.apply(null, [ - interpolateReferences(interpolateFootnotes(text)) - ]); - } -}; -marked.use({ renderer }); diff --git a/docs/scripts/marked.min.js b/docs/scripts/marked.min.js deleted file mode 100644 index 4bdc2fe..0000000 --- a/docs/scripts/marked.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).marked=t()}(this,function(){"use strict";function r(e,t){for(var u=0;ue.length)&&(t=e.length);for(var u=0,n=new Array(t);u=e.length?{done:!0}:{done:!1,value:e[n++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var t={exports:{}};function e(){return{baseUrl:null,breaks:!1,extensions:null,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:null,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,tokenizer:null,walkTokens:null,xhtml:!1}}t.exports={defaults:e(),getDefaults:e,changeDefaults:function(e){t.exports.defaults=e}};function u(e){return D[e]}var n=/[&<>"']/,s=/[&<>"']/g,l=/[<>"']|&(?!#?\w+;)/,a=/[<>"']|&(?!#?\w+;)/g,D={"&":"&","<":"<",">":">",'"':""","'":"'"};var c=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi;function h(e){return e.replace(c,function(e,t){return"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""})}var p=/(^|[^\[])\^/g;var f=/[^\w:]/g,g=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;var F={},A=/^[^:]+:\/*[^/]*$/,C=/^([^:]+:)[\s\S]*$/,d=/^([^:]+:\/*[^/]*)[\s\S]*$/;function k(e,t){F[" "+e]||(A.test(e)?F[" "+e]=e+"/":F[" "+e]=E(e,"/",!0));var u=-1===(e=F[" "+e]).indexOf(":");return"//"===t.substring(0,2)?u?t:e.replace(C,"$1")+t:"/"===t.charAt(0)?u?t:e.replace(d,"$1")+t:e+t}function E(e,t,u){var n=e.length;if(0===n)return"";for(var r=0;rt)u.splice(t);else for(;u.length>=1,e+=e;return u+e},T=t.exports.defaults,R=_,I=y,Z=x,q=z;function O(e,t,u,n){var r=t.href,i=t.title?Z(t.title):null,t=e[1].replace(/\\([\[\]])/g,"$1");if("!"===e[0].charAt(0))return{type:"image",raw:u,href:r,title:i,text:Z(t)};n.state.inLink=!0;t={type:"link",raw:u,href:r,title:i,text:t,tokens:n.inlineTokens(t,[])};return n.state.inLink=!1,t}_=function(){function e(e){this.options=e||T}var t=e.prototype;return t.space=function(e){e=this.rules.block.newline.exec(e);if(e)return 1=u.length?e.slice(u.length):e}).join("\n")}(u,t[3]||"");return{type:"code",raw:u,lang:t[2]&&t[2].trim(),text:e}}},t.heading=function(e){var t=this.rules.block.heading.exec(e);if(t){var u=t[2].trim();/#$/.test(u)&&(e=R(u,"#"),!this.options.pedantic&&e&&!/ $/.test(e)||(u=e.trim()));u={type:"heading",raw:t[0],depth:t[1].length,text:u,tokens:[]};return this.lexer.inline(u.text,u.tokens),u}},t.hr=function(e){e=this.rules.block.hr.exec(e);if(e)return{type:"hr",raw:e[0]}},t.blockquote=function(e){var t=this.rules.block.blockquote.exec(e);if(t){e=t[0].replace(/^ *> ?/gm,"");return{type:"blockquote",raw:t[0],tokens:this.lexer.blockTokens(e,[]),text:e}}},t.list=function(e){var t=this.rules.block.list.exec(e);if(t){var u,n,r,i,s,l,a,o,D,c=1<(p=t[1].trim()).length,h={type:"list",raw:"",ordered:c,start:c?+p.slice(0,-1):"",loose:!1,items:[]},p=c?"\\d{1,9}\\"+p.slice(-1):"\\"+p;this.options.pedantic&&(p=c?p:"[*+-]");for(var f=new RegExp("^( {0,3}"+p+")((?: [^\\n]*| *)(?:\\n[^\\n]*)*(?:\\n|$))");e&&!this.rules.block.hr.test(e)&&(t=f.exec(e));){o=t[2].split("\n"),D=this.options.pedantic?(i=2,o[0].trimLeft()):(i=t[2].search(/[^ ]/),i=t[1].length+(4=i)&&a.trim()){u=t[1]+o.slice(0,F).join("\n")+"\n";break}D+="\n"+a.slice(i)}else a.trim()||(s=!0),a.search(/[^ ]/)>=i?D+="\n"+a.slice(i):D+="\n"+a}h.loose||(l?h.loose=!0:/\n *\n *$/.test(u)&&(l=!0)),this.options.gfm&&(n=/^\[[ xX]\] /.exec(D))&&(r="[ ] "!==n[0],D=D.replace(/^\[[ xX]\] +/,"")),h.items.push({type:"list_item",raw:u,task:!!n,checked:r,loose:!1,text:D}),h.raw+=u,e=e.slice(u.length)}h.items[h.items.length-1].raw=u.trimRight(),h.items[h.items.length-1].text=D.trimRight(),h.raw=h.raw.trimRight();var A=h.items.length;for(F=0;F/i.test(e[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&/^<(pre|code|kbd|script)(\s|>)/i.test(e[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(e[0])&&(this.lexer.state.inRawBlock=!1),{type:this.options.sanitize?"text":"html",raw:e[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,text:this.options.sanitize?this.options.sanitizer?this.options.sanitizer(e[0]):Z(e[0]):e[0]}},t.link=function(e){var t=this.rules.inline.link.exec(e);if(t){var u=t[2].trim();if(!this.options.pedantic&&/^$/.test(u))return;e=R(u.slice(0,-1),"\\");if((u.length-e.length)%2==0)return}else{var n=q(t[2],"()");-1$/.test(u)?n.slice(1):n.slice(1,-1):n)&&n.replace(this.rules.inline._escapes,"$1"),title:i&&i.replace(this.rules.inline._escapes,"$1")},t[0],this.lexer)}},t.reflink=function(e,t){if((u=this.rules.inline.reflink.exec(e))||(u=this.rules.inline.nolink.exec(e))){e=(u[2]||u[1]).replace(/\s+/g," ");if((e=t[e.toLowerCase()])&&e.href)return O(u,e,u[0],this.lexer);var u=u[0].charAt(0);return{type:"text",raw:u,text:u}}},t.emStrong=function(e,t,u){void 0===u&&(u="");var n=this.rules.inline.emStrong.lDelim.exec(e);if(n&&(!n[3]||!u.match(/(?:[0-9A-Za-z\xAA\xB2\xB3\xB5\xB9\xBA\xBC-\xBE\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u0660-\u0669\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08C7\u0904-\u0939\u093D\u0950\u0958-\u0961\u0966-\u096F\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09F4-\u09F9\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AE6-\u0AEF\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71-\u0B77\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0BE6-\u0BF2\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C66-\u0C6F\u0C78-\u0C7E\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D58-\u0D61\u0D66-\u0D78\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DE6-\u0DEF\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F20-\u0F33\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F-\u1049\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1090-\u1099\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1369-\u137C\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A16\u1A20-\u1A54\u1A80-\u1A89\u1A90-\u1A99\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B50-\u1B59\u1B83-\u1BA0\u1BAE-\u1BE5\u1C00-\u1C23\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2070\u2071\u2074-\u2079\u207F-\u2089\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2150-\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2CFD\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u3192-\u3195\u31A0-\u31BF\u31F0-\u31FF\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\u3400-\u4DBF\u4E00-\u9FFC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7BF\uA7C2-\uA7CA\uA7F5-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA830-\uA835\uA840-\uA873\uA882-\uA8B3\uA8D0-\uA8D9\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA900-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF-\uA9D9\uA9E0-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD07-\uDD33\uDD40-\uDD78\uDD8A\uDD8B\uDE80-\uDE9C\uDEA0-\uDED0\uDEE1-\uDEFB\uDF00-\uDF23\uDF2D-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC58-\uDC76\uDC79-\uDC9E\uDCA7-\uDCAF\uDCE0-\uDCF2\uDCF4\uDCF5\uDCFB-\uDD1B\uDD20-\uDD39\uDD80-\uDDB7\uDDBC-\uDDCF\uDDD2-\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE40-\uDE48\uDE60-\uDE7E\uDE80-\uDE9F\uDEC0-\uDEC7\uDEC9-\uDEE4\uDEEB-\uDEEF\uDF00-\uDF35\uDF40-\uDF55\uDF58-\uDF72\uDF78-\uDF91\uDFA9-\uDFAF]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDCFA-\uDD23\uDD30-\uDD39\uDE60-\uDE7E\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF27\uDF30-\uDF45\uDF51-\uDF54\uDFB0-\uDFCB\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC52-\uDC6F\uDC83-\uDCAF\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD03-\uDD26\uDD36-\uDD3F\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDD0-\uDDDA\uDDDC\uDDE1-\uDDF4\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDEF0-\uDEF9\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC50-\uDC59\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE50-\uDE59\uDE80-\uDEAA\uDEB8\uDEC0-\uDEC9\uDF00-\uDF1A\uDF30-\uDF3B]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCF2\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDD50-\uDD59\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC50-\uDC6C\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD50-\uDD59\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDDA0-\uDDA9\uDEE0-\uDEF2\uDFB0\uDFC0-\uDFD4]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF50-\uDF59\uDF5B-\uDF61\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE96\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82C[\uDC00-\uDD1E\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD834[\uDEE0-\uDEF3\uDF60-\uDF78]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD40-\uDD49\uDD4E\uDEC0-\uDEEB\uDEF0-\uDEF9]|\uD83A[\uDC00-\uDCC4\uDCC7-\uDCCF\uDD00-\uDD43\uDD4B\uDD50-\uDD59]|\uD83B[\uDC71-\uDCAB\uDCAD-\uDCAF\uDCB1-\uDCB4\uDD01-\uDD2D\uDD2F-\uDD3D\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83C[\uDD00-\uDD0C]|\uD83E[\uDFF0-\uDFF9]|\uD869[\uDC00-\uDEDD\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])/))){var r=n[1]||n[2]||"";if(!r||""===u||this.rules.inline.punctuation.exec(u)){var i,s=n[0].length-1,l=s,a=0,o="*"===n[0][0]?this.rules.inline.emStrong.rDelimAst:this.rules.inline.emStrong.rDelimUnd;for(o.lastIndex=0,t=t.slice(-1*e.length+s);null!=(n=o.exec(t));)if(i=n[1]||n[2]||n[3]||n[4]||n[5]||n[6])if(i=i.length,n[3]||n[4])l+=i;else if(!((n[5]||n[6])&&s%3)||(s+i)%3){if(!(0<(l-=i))){if(i=Math.min(i,i+l+a),Math.min(s,i)%2){var D=e.slice(1,s+n.index+i);return{type:"em",raw:e.slice(0,s+n.index+i+1),text:D,tokens:this.lexer.inlineTokens(D,[])}}D=e.slice(2,s+n.index+i-1);return{type:"strong",raw:e.slice(0,s+n.index+i+1),text:D,tokens:this.lexer.inlineTokens(D,[])}}}else a+=i}}},t.codespan=function(e){var t=this.rules.inline.code.exec(e);if(t){var u=t[2].replace(/\n/g," "),n=/[^ ]/.test(u),e=/^ /.test(u)&&/ $/.test(u);return n&&e&&(u=u.substring(1,u.length-1)),u=Z(u,!0),{type:"codespan",raw:t[0],text:u}}},t.br=function(e){e=this.rules.inline.br.exec(e);if(e)return{type:"br",raw:e[0]}},t.del=function(e){e=this.rules.inline.del.exec(e);if(e)return{type:"del",raw:e[0],text:e[2],tokens:this.lexer.inlineTokens(e[2],[])}},t.autolink=function(e,t){e=this.rules.inline.autolink.exec(e);if(e){var u,t="@"===e[2]?"mailto:"+(u=Z(this.options.mangle?t(e[1]):e[1])):u=Z(e[1]);return{type:"link",raw:e[0],text:u,href:t,tokens:[{type:"text",raw:u,text:u}]}}},t.url=function(e,t){var u,n,r,i;if(u=this.rules.inline.url.exec(e)){if("@"===u[2])r="mailto:"+(n=Z(this.options.mangle?t(u[0]):u[0]));else{for(;i=u[0],u[0]=this.rules.inline._backpedal.exec(u[0])[0],i!==u[0];);n=Z(u[0]),r="www."===u[1]?"http://"+n:n}return{type:"link",raw:u[0],text:n,href:r,tokens:[{type:"text",raw:n,text:n}]}}},t.inlineText=function(e,t){e=this.rules.inline.text.exec(e);if(e){t=this.lexer.state.inRawBlock?this.options.sanitize?this.options.sanitizer?this.options.sanitizer(e[0]):Z(e[0]):e[0]:Z(this.options.smartypants?t(e[0]):e[0]);return{type:"text",raw:e[0],text:t}}},e}(),y=w,z=b,w=v,b={newline:/^(?: *(?:\n|$))+/,code:/^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,fences:/^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?=\n|$)|$)/,hr:/^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,blockquote:/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,list:/^( {0,3}bull)( [^\n]+?)?(?:\n|$)/,html:"^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n *)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$))",def:/^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,table:y,lheading:/^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/,_paragraph:/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html| +\n)[^\n]+)*)/,text:/^[^\n]+/,_label:/(?!\s*\])(?:\\[\[\]]|[^\[\]])+/,_title:/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/};b.def=z(b.def).replace("label",b._label).replace("title",b._title).getRegex(),b.bullet=/(?:[*+-]|\d{1,9}[.)])/,b.listItemStart=z(/^( *)(bull) */).replace("bull",b.bullet).getRegex(),b.list=z(b.list).replace(/bull/g,b.bullet).replace("hr","\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))").replace("def","\\n+(?="+b.def.source+")").getRegex(),b._tag="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",b._comment=/|$)/,b.html=z(b.html,"i").replace("comment",b._comment).replace("tag",b._tag).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),b.paragraph=z(b._paragraph).replace("hr",b.hr).replace("heading"," {0,3}#{1,6} ").replace("|lheading","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",b._tag).getRegex(),b.blockquote=z(b.blockquote).replace("paragraph",b.paragraph).getRegex(),b.normal=w({},b),b.gfm=w({},b.normal,{table:"^ *([^\\n ].*\\|.*)\\n {0,3}(?:\\| *)?(:?-+:? *(?:\\| *:?-+:? *)*)(?:\\| *)?(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)"}),b.gfm.table=z(b.gfm.table).replace("hr",b.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",b._tag).getRegex(),b.pedantic=w({},b.normal,{html:z("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",b._comment).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:y,paragraph:z(b.normal._paragraph).replace("hr",b.hr).replace("heading"," *#{1,6} *[^\n]").replace("lheading",b.lheading).replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").getRegex()});y={escape:/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,autolink:/^<(scheme:[^\s\x00-\x1f<>]*|email)>/,url:y,tag:"^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^",link:/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/,reflink:/^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,nolink:/^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,reflinkSearch:"reflink|nolink(?!\\()",emStrong:{lDelim:/^(?:\*+(?:([punct_])|[^\s*]))|^_+(?:([punct*])|([^\s_]))/,rDelimAst:/^[^_*]*?\_\_[^_*]*?\*[^_*]*?(?=\_\_)|[punct_](\*+)(?=[\s]|$)|[^punct*_\s](\*+)(?=[punct_\s]|$)|[punct_\s](\*+)(?=[^punct*_\s])|[\s](\*+)(?=[punct_])|[punct_](\*+)(?=[punct_])|[^punct*_\s](\*+)(?=[^punct*_\s])/,rDelimUnd:/^[^_*]*?\*\*[^_*]*?\_[^_*]*?(?=\*\*)|[punct*](\_+)(?=[\s]|$)|[^punct*_\s](\_+)(?=[punct*\s]|$)|[punct*\s](\_+)(?=[^punct*_\s])|[\s](\_+)(?=[punct*])|[punct*](\_+)(?=[punct*])/},code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,br:/^( {2,}|\\)\n(?!\s*$)/,del:y,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\?@\\[\\]`^{|}~"};y.punctuation=z(y.punctuation).replace(/punctuation/g,y._punctuation).getRegex(),y.blockSkip=/\[[^\]]*?\]\([^\)]*?\)|`[^`]*?`|<[^>]*?>/g,y.escapedEmSt=/\\\*|\\_/g,y._comment=z(b._comment).replace("(?:--\x3e|$)","--\x3e").getRegex(),y.emStrong.lDelim=z(y.emStrong.lDelim).replace(/punct/g,y._punctuation).getRegex(),y.emStrong.rDelimAst=z(y.emStrong.rDelimAst,"g").replace(/punct/g,y._punctuation).getRegex(),y.emStrong.rDelimUnd=z(y.emStrong.rDelimUnd,"g").replace(/punct/g,y._punctuation).getRegex(),y._escapes=/\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g,y._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,y._email=/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,y.autolink=z(y.autolink).replace("scheme",y._scheme).replace("email",y._email).getRegex(),y._attribute=/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/,y.tag=z(y.tag).replace("comment",y._comment).replace("attribute",y._attribute).getRegex(),y._label=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,y._href=/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/,y._title=/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/,y.link=z(y.link).replace("label",y._label).replace("href",y._href).replace("title",y._title).getRegex(),y.reflink=z(y.reflink).replace("label",y._label).getRegex(),y.reflinkSearch=z(y.reflinkSearch,"g").replace("reflink",y.reflink).replace("nolink",y.nolink).getRegex(),y.normal=w({},y),y.pedantic=w({},y.normal,{strong:{start:/^__|\*\*/,middle:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,endAst:/\*\*(?!\*)/g,endUnd:/__(?!_)/g},em:{start:/^_|\*/,middle:/^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/,endAst:/\*(?!\*)/g,endUnd:/_(?!_)/g},link:z(/^!?\[(label)\]\((.*?)\)/).replace("label",y._label).getRegex(),reflink:z(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",y._label).getRegex()}),y.gfm=w({},y.normal,{escape:z(y.escape).replace("])","~|])").getRegex(),_extended_email:/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,url:/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,_backpedal:/(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\'+(u?e:V(e,!0))+"\n":"
"+(u?e:V(e,!0))+"
\n"},t.blockquote=function(e){return"
\n"+e+"
\n"},t.html=function(e){return e},t.heading=function(e,t,u,n){return this.options.headerIds?"'+e+"\n":""+e+"\n"},t.hr=function(){return this.options.xhtml?"
\n":"
\n"},t.list=function(e,t,u){var n=t?"ol":"ul";return"<"+n+(t&&1!==u?' start="'+u+'"':"")+">\n"+e+"\n"},t.listitem=function(e){return"
  • "+e+"
  • \n"},t.checkbox=function(e){return" "},t.paragraph=function(e){return"

    "+e+"

    \n"},t.table=function(e,t){return"\n\n"+e+"\n"+(t=t&&""+t+"")+"
    \n"},t.tablerow=function(e){return"\n"+e+"\n"},t.tablecell=function(e,t){var u=t.header?"th":"td";return(t.align?"<"+u+' align="'+t.align+'">':"<"+u+">")+e+"\n"},t.strong=function(e){return""+e+""},t.em=function(e){return""+e+""},t.codespan=function(e){return""+e+""},t.br=function(){return this.options.xhtml?"
    ":"
    "},t.del=function(e){return""+e+""},t.link=function(e,t,u){if(null===(e=G(this.options.sanitize,this.options.baseUrl,e)))return u;e='"},t.image=function(e,t,u){if(null===(e=G(this.options.sanitize,this.options.baseUrl,e)))return u;u=''+u+'":">"},t.text=function(e){return e},e}(),S=function(){function e(){}var t=e.prototype;return t.strong=function(e){return e},t.em=function(e){return e},t.codespan=function(e){return e},t.del=function(e){return e},t.html=function(e){return e},t.text=function(e){return e},t.link=function(e,t,u){return""+u},t.image=function(e,t,u){return""+u},t.br=function(){return""},e}(),B=function(){function e(){this.seen={}}var t=e.prototype;return t.serialize=function(e){return e.toLowerCase().trim().replace(/<[!\/a-z].*?>/gi,"").replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g,"").replace(/\s/g,"-")},t.getNextSafeSlug=function(e,t){var u=e,n=0;if(this.seen.hasOwnProperty(u))for(n=this.seen[e];u=e+"-"+ ++n,this.seen.hasOwnProperty(u););return t||(this.seen[e]=n,this.seen[u]=0),u},t.slug=function(e,t){void 0===t&&(t={});var u=this.serialize(e);return this.getNextSafeSlug(u,t.dryrun)},e}(),H=b,J=S,K=B,W=t.exports.defaults,Y=m,ee=y,te=function(){function u(e){this.options=e||W,this.options.renderer=this.options.renderer||new H,this.renderer=this.options.renderer,this.renderer.options=this.options,this.textRenderer=new J,this.slugger=new K}u.parse=function(e,t){return new u(t).parse(e)},u.parseInline=function(e,t){return new u(t).parseInline(e)};var e=u.prototype;return e.parse=function(e,t){void 0===t&&(t=!0);for(var u,n,r,i,s,l,a,o,D,c,h,p,f,g,F,A,C="",d=e.length,k=0;kAn error occurred:

    "+se(e.message+"",!0)+"
    ";throw e}}return ae.options=ae.setOptions=function(e){return re(ae.defaults,e),le(ae.defaults),ae},ae.getDefaults=$,ae.defaults=x,ae.use=function(){for(var e=arguments.length,t=new Array(e),u=0;uAn error occurred:

    "+se(e.message+"",!0)+"
    ";throw e}},ae.Parser=te,ae.parser=te.parse,ae.Renderer=ne,ae.TextRenderer=S,ae.Lexer=ee,ae.lexer=ee.lex,ae.Tokenizer=ue,ae.Slugger=B,ae.parse=ae}); diff --git a/docs/styles/changelog.css b/docs/styles/changelog.css deleted file mode 100644 index bc13a03..0000000 --- a/docs/styles/changelog.css +++ /dev/null @@ -1,54 +0,0 @@ -html, -body { - font-size: 90%; - margin: 2px 10px; - line-height: 1.5; - font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, - 'Helvetica Neue', sans-serif; - text-rendering: optimizeLegibility; -} -kbd { - font-family: Consolas, 'Lucida Console', monospace; - display: inline-block; - border-radius: 3px; - padding: 0px 4px; - box-shadow: 1px 1px 1px #777; - margin: 2px; - font-size: small; - vertical-align: text-bottom; - background: #eee; - font-weight: 500; - color: #555; - letter-spacing: 1px; - /* Prevent selection */ - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -ul li { - margin-top: 0.4rem; -} -ul { - margin: 0.8rem 0 0.8rem 0.8rem; - padding: 0; - list-style: disc inside; -} -a { - color: #6fa2fa !important; - outline: 0; - text-decoration: none; -} -a:hover { - text-decoration: underline; -} -h3 { - color: inherit; - font-size: 1.4rem; - font-weight: 500; - line-height: 1.2; - margin-bottom: 0.5em; - margin-top: 0.4em; -}