├── .editorconfig ├── .gitattributes ├── .gitignore ├── .swiftlint.yml ├── Stuff ├── AppIcon-readme.png ├── AppIcon.png ├── AppIcon.sketch └── MenuBarIcon.sketch ├── Touch Bar Simulator.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── Touch Bar Simulator.xcscheme ├── Touch Bar Simulator ├── App.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png │ ├── Contents.json │ └── MenuBarIcon.imageset │ │ ├── Contents.json │ │ ├── MenuBarIcon.png │ │ └── MenuBarIcon@2x.png ├── Constants.swift ├── Glue.swift ├── Info.plist ├── KeyboardShortcutsView.swift ├── ToolbarSlider.swift ├── Touch Bar Simulator-Bridging-Header.h ├── Touch Bar Simulator.entitlements ├── TouchBarView.swift ├── TouchBarWindow.swift ├── Utilities.swift └── main.swift ├── build ├── export-options.plist ├── license ├── readme.md ├── screenshot-menu-bar.png └── screenshot.png /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.pdf binary 3 | *.ai binary 4 | *.psd binary 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata 2 | project.xcworkspace 3 | Carthage/Checkouts 4 | Carthage/Build 5 | Release 6 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | only_rules: 2 | - anyobject_protocol 3 | - array_init 4 | - block_based_kvo 5 | - class_delegate_protocol 6 | - closing_brace 7 | - closure_end_indentation 8 | - closure_parameter_position 9 | - closure_spacing 10 | - collection_alignment 11 | - colon 12 | - comma 13 | - compiler_protocol_init 14 | - computed_accessors_order 15 | - conditional_returns_on_newline 16 | - contains_over_filter_count 17 | - contains_over_filter_is_empty 18 | - contains_over_first_not_nil 19 | - contains_over_range_nil_comparison 20 | - control_statement 21 | - custom_rules 22 | - deployment_target 23 | - discarded_notification_center_observer 24 | - discouraged_direct_init 25 | - discouraged_object_literal 26 | - discouraged_optional_boolean 27 | - discouraged_optional_collection 28 | - duplicate_enum_cases 29 | - duplicate_imports 30 | - dynamic_inline 31 | - empty_collection_literal 32 | - empty_count 33 | - empty_enum_arguments 34 | - empty_parameters 35 | - empty_parentheses_with_trailing_closure 36 | - empty_string 37 | - empty_xctest_method 38 | - enum_case_associated_values_count 39 | - explicit_init 40 | - fallthrough 41 | - fatal_error_message 42 | - first_where 43 | - flatmap_over_map_reduce 44 | - for_where 45 | - generic_type_name 46 | - ibinspectable_in_extension 47 | - identical_operands 48 | - identifier_name 49 | - implicit_getter 50 | - implicit_return 51 | - inclusive_language 52 | - inert_defer 53 | - is_disjoint 54 | - joined_default_parameter 55 | - last_where 56 | - leading_whitespace 57 | - legacy_cggeometry_functions 58 | - legacy_constant 59 | - legacy_constructor 60 | - legacy_hashing 61 | - legacy_multiple 62 | - legacy_nsgeometry_functions 63 | - legacy_random 64 | - literal_expression_end_indentation 65 | - lower_acl_than_parent 66 | - mark 67 | - modifier_order 68 | - multiline_arguments 69 | - multiline_function_chains 70 | - multiline_literal_brackets 71 | - multiline_parameters 72 | - multiline_parameters_brackets 73 | - nimble_operator 74 | - no_extension_access_modifier 75 | - no_fallthrough_only 76 | - no_space_in_method_call 77 | - notification_center_detachment 78 | - nsobject_prefer_isequal 79 | - number_separator 80 | - opening_brace 81 | - operator_usage_whitespace 82 | - operator_whitespace 83 | - orphaned_doc_comment 84 | - overridden_super_call 85 | - prefer_self_type_over_type_of_self 86 | - prefer_zero_over_explicit_init 87 | - private_action 88 | - private_outlet 89 | - private_unit_test 90 | - prohibited_super_call 91 | - protocol_property_accessors_order 92 | - reduce_boolean 93 | - reduce_into 94 | - redundant_discardable_let 95 | - redundant_nil_coalescing 96 | - redundant_objc_attribute 97 | - redundant_optional_initialization 98 | - redundant_set_access_control 99 | - redundant_string_enum_value 100 | - redundant_type_annotation 101 | - redundant_void_return 102 | - required_enum_case 103 | - return_arrow_whitespace 104 | - shorthand_operator 105 | - sorted_first_last 106 | - statement_position 107 | - static_operator 108 | - strong_iboutlet 109 | - superfluous_disable_command 110 | - switch_case_alignment 111 | - switch_case_on_newline 112 | - syntactic_sugar 113 | - test_case_accessibility 114 | - toggle_bool 115 | - trailing_closure 116 | - trailing_comma 117 | - trailing_newline 118 | - trailing_semicolon 119 | - trailing_whitespace 120 | - unavailable_function 121 | - unneeded_break_in_switch 122 | - unneeded_parentheses_in_closure_argument 123 | - unowned_variable_capture 124 | - untyped_error_in_catch 125 | - unused_capture_list 126 | - unused_closure_parameter 127 | - unused_control_flow_label 128 | - unused_enumerated 129 | - unused_optional_binding 130 | - unused_setter_value 131 | - valid_ibinspectable 132 | - vertical_parameter_alignment 133 | - vertical_parameter_alignment_on_call 134 | - vertical_whitespace_closing_braces 135 | - vertical_whitespace_opening_braces 136 | - void_return 137 | - weak_delegate 138 | - xct_specific_matcher 139 | - xctfail_message 140 | - yoda_condition 141 | analyzer_rules: 142 | - unused_declaration 143 | - unused_import 144 | number_separator: 145 | minimum_length: 5 146 | identifier_name: 147 | max_length: 148 | warning: 100 149 | error: 100 150 | min_length: 151 | warning: 2 152 | error: 2 153 | validates_start_with_lowercase: false 154 | allowed_symbols: 155 | - '_' 156 | excluded: 157 | - 'x' 158 | - 'y' 159 | - 'a' 160 | - 'b' 161 | - 'x1' 162 | - 'x2' 163 | - 'y1' 164 | - 'y2' 165 | deployment_target: 166 | macOS_deployment_target: '10.15' 167 | custom_rules: 168 | no_nsrect: 169 | regex: '\bNSRect\b' 170 | match_kinds: typeidentifier 171 | message: 'Use CGRect instead of NSRect' 172 | no_nssize: 173 | regex: '\bNSSize\b' 174 | match_kinds: typeidentifier 175 | message: 'Use CGSize instead of NSSize' 176 | no_nspoint: 177 | regex: '\bNSPoint\b' 178 | match_kinds: typeidentifier 179 | message: 'Use CGPoint instead of NSPoint' 180 | swiftui_state_private: 181 | regex: '@(State|StateObject)\s+var' 182 | message: "SwiftUI @State/@StateObject properties should be private" 183 | final_class: 184 | regex: '^class [a-zA-Z\d]+[^{]+\{' 185 | message: "Classes should be marked as final whenever possible. If you actually need it to be subclassable, just add `// swiftlint:disable:next final_class`." 186 | excluded: 187 | - Carthage 188 | -------------------------------------------------------------------------------- /Stuff/AppIcon-readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/Stuff/AppIcon-readme.png -------------------------------------------------------------------------------- /Stuff/AppIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/Stuff/AppIcon.png -------------------------------------------------------------------------------- /Stuff/AppIcon.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/Stuff/AppIcon.sketch -------------------------------------------------------------------------------- /Stuff/MenuBarIcon.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/Stuff/MenuBarIcon.sketch -------------------------------------------------------------------------------- /Touch Bar Simulator.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 60; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3C48E6A6247C370E00D22565 /* KeyboardShortcuts in Frameworks */ = {isa = PBXBuildFile; productRef = 3C48E6A5247C370E00D22565 /* KeyboardShortcuts */; }; 11 | 3C8493AD21FD3B7F00F12966 /* Glue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C8493AC21FD3B7F00F12966 /* Glue.swift */; }; 12 | 3CF6C771247CC3DF007554B9 /* KeyboardShortcutsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF6C770247CC3DF007554B9 /* KeyboardShortcutsView.swift */; }; 13 | AF6C7BC61E7FAF38004A27E0 /* ToolbarSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF6C7BC51E7FAF38004A27E0 /* ToolbarSlider.swift */; }; 14 | E31957F923F140D500856B40 /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = E31957F823F140D500856B40 /* Defaults */; }; 15 | E3445091251573A4004A5FC1 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = E3445090251573A4004A5FC1 /* LaunchAtLogin */; }; 16 | E35579EB21595EDC001CB642 /* TouchBarWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E35579EA21595EDC001CB642 /* TouchBarWindow.swift */; }; 17 | E356A16321028D81000148AD /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = E356A16221028D81000148AD /* main.swift */; }; 18 | E35831A41F4D7EE0003BE371 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = E35831A31F4D7EE0003BE371 /* Utilities.swift */; }; 19 | E3930B10216625BE00F66410 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3930B0F216625BE00F66410 /* Constants.swift */; }; 20 | E39A157E214CFE7100F86D5D /* DFRFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E39A157D214CFE7100F86D5D /* DFRFoundation.framework */; }; 21 | E39A157F214CFE7100F86D5D /* DFRFoundation.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E39A157D214CFE7100F86D5D /* DFRFoundation.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 22 | E39A158A214D011F00F86D5D /* SkyLight.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E39A1589214D011F00F86D5D /* SkyLight.framework */; }; 23 | E39A158B214D011F00F86D5D /* SkyLight.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E39A1589214D011F00F86D5D /* SkyLight.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 24 | E39A158E214D0C4F00F86D5D /* TouchBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E39A158D214D0C4F00F86D5D /* TouchBarView.swift */; }; 25 | E3D07F1327947D6C006320B7 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = E3D07F1227947D6C006320B7 /* Sparkle */; }; 26 | E3FE2CC31E726CE800C6713A /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3FE2CC21E726CE800C6713A /* App.swift */; }; 27 | E3FE2CC51E726CE800C6713A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E3FE2CC41E726CE800C6713A /* Assets.xcassets */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXCopyFilesBuildPhase section */ 31 | E3FA3A901E784C5F00A7F2EA /* Embed Frameworks */ = { 32 | isa = PBXCopyFilesBuildPhase; 33 | buildActionMask = 2147483647; 34 | dstPath = ""; 35 | dstSubfolderSpec = 10; 36 | files = ( 37 | E39A158B214D011F00F86D5D /* SkyLight.framework in Embed Frameworks */, 38 | E39A157F214CFE7100F86D5D /* DFRFoundation.framework in Embed Frameworks */, 39 | ); 40 | name = "Embed Frameworks"; 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXCopyFilesBuildPhase section */ 44 | 45 | /* Begin PBXFileReference section */ 46 | 3C8493AC21FD3B7F00F12966 /* Glue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Glue.swift; sourceTree = ""; usesTabs = 1; }; 47 | 3CF6C770247CC3DF007554B9 /* KeyboardShortcutsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardShortcutsView.swift; sourceTree = ""; }; 48 | AF6C7BC51E7FAF38004A27E0 /* ToolbarSlider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ToolbarSlider.swift; sourceTree = ""; usesTabs = 1; }; 49 | E34D6548214BBDAE00786C24 /* Touch Bar Simulator.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Touch Bar Simulator.entitlements"; sourceTree = ""; }; 50 | E35579EA21595EDC001CB642 /* TouchBarWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = TouchBarWindow.swift; sourceTree = ""; usesTabs = 1; }; 51 | E356A16221028D81000148AD /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = main.swift; sourceTree = ""; usesTabs = 1; }; 52 | E35831A31F4D7EE0003BE371 /* Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Utilities.swift; sourceTree = ""; usesTabs = 1; }; 53 | E3930B0F216625BE00F66410 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Constants.swift; sourceTree = ""; usesTabs = 1; }; 54 | E39A157D214CFE7100F86D5D /* DFRFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DFRFoundation.framework; path = ../../../../../System/Library/PrivateFrameworks/DFRFoundation.framework; sourceTree = ""; }; 55 | E39A1589214D011F00F86D5D /* SkyLight.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SkyLight.framework; path = ../../../../../System/Library/PrivateFrameworks/SkyLight.framework; sourceTree = ""; }; 56 | E39A158D214D0C4F00F86D5D /* TouchBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = TouchBarView.swift; sourceTree = ""; usesTabs = 1; }; 57 | E3FE2CBF1E726CE800C6713A /* Touch Bar Simulator.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Touch Bar Simulator.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | E3FE2CC21E726CE800C6713A /* App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = App.swift; sourceTree = ""; usesTabs = 1; }; 59 | E3FE2CC41E726CE800C6713A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 60 | E3FE2CC91E726CE800C6713A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 61 | E3FE2CD21E726EA100C6713A /* Touch Bar Simulator-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "Touch Bar Simulator-Bridging-Header.h"; sourceTree = ""; usesTabs = 1; }; 62 | /* End PBXFileReference section */ 63 | 64 | /* Begin PBXFrameworksBuildPhase section */ 65 | E3FE2CBC1E726CE800C6713A /* Frameworks */ = { 66 | isa = PBXFrameworksBuildPhase; 67 | buildActionMask = 2147483647; 68 | files = ( 69 | E3D07F1327947D6C006320B7 /* Sparkle in Frameworks */, 70 | E3445091251573A4004A5FC1 /* LaunchAtLogin in Frameworks */, 71 | E31957F923F140D500856B40 /* Defaults in Frameworks */, 72 | E39A157E214CFE7100F86D5D /* DFRFoundation.framework in Frameworks */, 73 | E39A158A214D011F00F86D5D /* SkyLight.framework in Frameworks */, 74 | 3C48E6A6247C370E00D22565 /* KeyboardShortcuts in Frameworks */, 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | /* End PBXFrameworksBuildPhase section */ 79 | 80 | /* Begin PBXGroup section */ 81 | E356A16421028DAB000148AD /* Other */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | E356A16221028D81000148AD /* main.swift */, 85 | E3FE2CD21E726EA100C6713A /* Touch Bar Simulator-Bridging-Header.h */, 86 | E3FE2CC91E726CE800C6713A /* Info.plist */, 87 | E34D6548214BBDAE00786C24 /* Touch Bar Simulator.entitlements */, 88 | ); 89 | name = Other; 90 | sourceTree = ""; 91 | }; 92 | E3FE2CB61E726CE800C6713A = { 93 | isa = PBXGroup; 94 | children = ( 95 | E3FE2CC11E726CE800C6713A /* Touch Bar Simulator */, 96 | E3FE2CC01E726CE800C6713A /* Products */, 97 | E39A1589214D011F00F86D5D /* SkyLight.framework */, 98 | E39A157D214CFE7100F86D5D /* DFRFoundation.framework */, 99 | ); 100 | sourceTree = ""; 101 | usesTabs = 1; 102 | }; 103 | E3FE2CC01E726CE800C6713A /* Products */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | E3FE2CBF1E726CE800C6713A /* Touch Bar Simulator.app */, 107 | ); 108 | name = Products; 109 | sourceTree = ""; 110 | }; 111 | E3FE2CC11E726CE800C6713A /* Touch Bar Simulator */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | E3930B0F216625BE00F66410 /* Constants.swift */, 115 | E3FE2CC21E726CE800C6713A /* App.swift */, 116 | E35579EA21595EDC001CB642 /* TouchBarWindow.swift */, 117 | E39A158D214D0C4F00F86D5D /* TouchBarView.swift */, 118 | AF6C7BC51E7FAF38004A27E0 /* ToolbarSlider.swift */, 119 | 3CF6C770247CC3DF007554B9 /* KeyboardShortcutsView.swift */, 120 | 3C8493AC21FD3B7F00F12966 /* Glue.swift */, 121 | E35831A31F4D7EE0003BE371 /* Utilities.swift */, 122 | E3FE2CC41E726CE800C6713A /* Assets.xcassets */, 123 | E356A16421028DAB000148AD /* Other */, 124 | ); 125 | path = "Touch Bar Simulator"; 126 | sourceTree = ""; 127 | }; 128 | /* End PBXGroup section */ 129 | 130 | /* Begin PBXNativeTarget section */ 131 | E3FE2CBE1E726CE800C6713A /* Touch Bar Simulator */ = { 132 | isa = PBXNativeTarget; 133 | buildConfigurationList = E3FE2CCC1E726CE800C6713A /* Build configuration list for PBXNativeTarget "Touch Bar Simulator" */; 134 | buildPhases = ( 135 | E3679C9A215A1ECA0080270F /* SwiftLint */, 136 | E3FE2CBB1E726CE800C6713A /* Sources */, 137 | E3FE2CBC1E726CE800C6713A /* Frameworks */, 138 | E3FE2CBD1E726CE800C6713A /* Resources */, 139 | E3FA3A901E784C5F00A7F2EA /* Embed Frameworks */, 140 | 3C906EEF231C60060099B139 /* Copy "Launch at Login Helper" */, 141 | ); 142 | buildRules = ( 143 | ); 144 | dependencies = ( 145 | ); 146 | name = "Touch Bar Simulator"; 147 | packageProductDependencies = ( 148 | E31957F823F140D500856B40 /* Defaults */, 149 | 3C48E6A5247C370E00D22565 /* KeyboardShortcuts */, 150 | E3445090251573A4004A5FC1 /* LaunchAtLogin */, 151 | E3D07F1227947D6C006320B7 /* Sparkle */, 152 | ); 153 | productName = "Touch Bar Simulator"; 154 | productReference = E3FE2CBF1E726CE800C6713A /* Touch Bar Simulator.app */; 155 | productType = "com.apple.product-type.application"; 156 | }; 157 | /* End PBXNativeTarget section */ 158 | 159 | /* Begin PBXProject section */ 160 | E3FE2CB71E726CE800C6713A /* Project object */ = { 161 | isa = PBXProject; 162 | attributes = { 163 | BuildIndependentTargetsInParallel = YES; 164 | LastSwiftUpdateCheck = 0820; 165 | LastUpgradeCheck = 1500; 166 | ORGANIZATIONNAME = "Sindre Sorhus"; 167 | TargetAttributes = { 168 | E3FE2CBE1E726CE800C6713A = { 169 | CreatedOnToolsVersion = 8.2.1; 170 | LastSwiftMigration = 1020; 171 | SystemCapabilities = { 172 | com.apple.HardenedRuntime = { 173 | enabled = 0; 174 | }; 175 | }; 176 | }; 177 | }; 178 | }; 179 | buildConfigurationList = E3FE2CBA1E726CE800C6713A /* Build configuration list for PBXProject "Touch Bar Simulator" */; 180 | compatibilityVersion = "Xcode 15.0"; 181 | developmentRegion = en; 182 | hasScannedForEncodings = 0; 183 | knownRegions = ( 184 | en, 185 | Base, 186 | ); 187 | mainGroup = E3FE2CB61E726CE800C6713A; 188 | packageReferences = ( 189 | E31957F723F140D500856B40 /* XCRemoteSwiftPackageReference "Defaults" */, 190 | 3C48E6A4247C370E00D22565 /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */, 191 | E344508F251573A4004A5FC1 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */, 192 | E3D07F1127947D6C006320B7 /* XCRemoteSwiftPackageReference "Sparkle" */, 193 | ); 194 | productRefGroup = E3FE2CC01E726CE800C6713A /* Products */; 195 | projectDirPath = ""; 196 | projectRoot = ""; 197 | targets = ( 198 | E3FE2CBE1E726CE800C6713A /* Touch Bar Simulator */, 199 | ); 200 | }; 201 | /* End PBXProject section */ 202 | 203 | /* Begin PBXResourcesBuildPhase section */ 204 | E3FE2CBD1E726CE800C6713A /* Resources */ = { 205 | isa = PBXResourcesBuildPhase; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | E3FE2CC51E726CE800C6713A /* Assets.xcassets in Resources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXResourcesBuildPhase section */ 213 | 214 | /* Begin PBXShellScriptBuildPhase section */ 215 | 3C906EEF231C60060099B139 /* Copy "Launch at Login Helper" */ = { 216 | isa = PBXShellScriptBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | ); 220 | inputFileListPaths = ( 221 | ); 222 | inputPaths = ( 223 | ); 224 | name = "Copy \"Launch at Login Helper\""; 225 | outputFileListPaths = ( 226 | ); 227 | outputPaths = ( 228 | ); 229 | runOnlyForDeploymentPostprocessing = 0; 230 | shellPath = /bin/sh; 231 | shellScript = "\"${BUILT_PRODUCTS_DIR}/LaunchAtLogin_LaunchAtLogin.bundle/Contents/Resources/copy-helper-swiftpm.sh\"\n"; 232 | showEnvVarsInLog = 0; 233 | }; 234 | E3679C9A215A1ECA0080270F /* SwiftLint */ = { 235 | isa = PBXShellScriptBuildPhase; 236 | alwaysOutOfDate = 1; 237 | buildActionMask = 2147483647; 238 | files = ( 239 | ); 240 | inputFileListPaths = ( 241 | ); 242 | inputPaths = ( 243 | ); 244 | name = SwiftLint; 245 | outputFileListPaths = ( 246 | ); 247 | outputPaths = ( 248 | ); 249 | runOnlyForDeploymentPostprocessing = 0; 250 | shellPath = /bin/sh; 251 | shellScript = "PATH=\"/opt/homebrew/bin/:${PATH}\"\nswiftlint\n"; 252 | showEnvVarsInLog = 0; 253 | }; 254 | /* End PBXShellScriptBuildPhase section */ 255 | 256 | /* Begin PBXSourcesBuildPhase section */ 257 | E3FE2CBB1E726CE800C6713A /* Sources */ = { 258 | isa = PBXSourcesBuildPhase; 259 | buildActionMask = 2147483647; 260 | files = ( 261 | E35831A41F4D7EE0003BE371 /* Utilities.swift in Sources */, 262 | AF6C7BC61E7FAF38004A27E0 /* ToolbarSlider.swift in Sources */, 263 | 3C8493AD21FD3B7F00F12966 /* Glue.swift in Sources */, 264 | 3CF6C771247CC3DF007554B9 /* KeyboardShortcutsView.swift in Sources */, 265 | E356A16321028D81000148AD /* main.swift in Sources */, 266 | E35579EB21595EDC001CB642 /* TouchBarWindow.swift in Sources */, 267 | E3930B10216625BE00F66410 /* Constants.swift in Sources */, 268 | E3FE2CC31E726CE800C6713A /* App.swift in Sources */, 269 | E39A158E214D0C4F00F86D5D /* TouchBarView.swift in Sources */, 270 | ); 271 | runOnlyForDeploymentPostprocessing = 0; 272 | }; 273 | /* End PBXSourcesBuildPhase section */ 274 | 275 | /* Begin XCBuildConfiguration section */ 276 | E3FE2CCA1E726CE800C6713A /* Debug */ = { 277 | isa = XCBuildConfiguration; 278 | buildSettings = { 279 | ALWAYS_SEARCH_USER_PATHS = NO; 280 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 281 | CLANG_ANALYZER_NONNULL = YES; 282 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 283 | CLANG_CXX_LIBRARY = "libc++"; 284 | CLANG_ENABLE_MODULES = YES; 285 | CLANG_ENABLE_OBJC_ARC = YES; 286 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 287 | CLANG_WARN_BOOL_CONVERSION = YES; 288 | CLANG_WARN_COMMA = YES; 289 | CLANG_WARN_CONSTANT_CONVERSION = YES; 290 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 291 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 292 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 293 | CLANG_WARN_EMPTY_BODY = YES; 294 | CLANG_WARN_ENUM_CONVERSION = YES; 295 | CLANG_WARN_INFINITE_RECURSION = YES; 296 | CLANG_WARN_INT_CONVERSION = YES; 297 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 298 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 299 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 300 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 301 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 302 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 303 | CLANG_WARN_STRICT_PROTOTYPES = YES; 304 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 305 | CLANG_WARN_UNREACHABLE_CODE = YES; 306 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 307 | CODE_SIGN_IDENTITY = "-"; 308 | COPY_PHASE_STRIP = NO; 309 | DEAD_CODE_STRIPPING = YES; 310 | DEBUG_INFORMATION_FORMAT = dwarf; 311 | ENABLE_STRICT_OBJC_MSGSEND = YES; 312 | ENABLE_TESTABILITY = YES; 313 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 314 | GCC_C_LANGUAGE_STANDARD = gnu99; 315 | GCC_DYNAMIC_NO_PIC = NO; 316 | GCC_NO_COMMON_BLOCKS = YES; 317 | GCC_OPTIMIZATION_LEVEL = 0; 318 | GCC_PREPROCESSOR_DEFINITIONS = ( 319 | "DEBUG=1", 320 | "$(inherited)", 321 | ); 322 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 323 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 324 | GCC_WARN_UNDECLARED_SELECTOR = YES; 325 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 326 | GCC_WARN_UNUSED_FUNCTION = YES; 327 | GCC_WARN_UNUSED_VARIABLE = YES; 328 | MACOSX_DEPLOYMENT_TARGET = 13.3; 329 | MTL_ENABLE_DEBUG_INFO = YES; 330 | ONLY_ACTIVE_ARCH = YES; 331 | SDKROOT = macosx; 332 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 333 | SWIFT_COMPILATION_MODE = singlefile; 334 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 335 | SWIFT_VERSION = 5.0; 336 | }; 337 | name = Debug; 338 | }; 339 | E3FE2CCB1E726CE800C6713A /* Release */ = { 340 | isa = XCBuildConfiguration; 341 | buildSettings = { 342 | ALWAYS_SEARCH_USER_PATHS = NO; 343 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 344 | CLANG_ANALYZER_NONNULL = YES; 345 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 346 | CLANG_CXX_LIBRARY = "libc++"; 347 | CLANG_ENABLE_MODULES = YES; 348 | CLANG_ENABLE_OBJC_ARC = YES; 349 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 350 | CLANG_WARN_BOOL_CONVERSION = YES; 351 | CLANG_WARN_COMMA = YES; 352 | CLANG_WARN_CONSTANT_CONVERSION = YES; 353 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 354 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 355 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 356 | CLANG_WARN_EMPTY_BODY = YES; 357 | CLANG_WARN_ENUM_CONVERSION = YES; 358 | CLANG_WARN_INFINITE_RECURSION = YES; 359 | CLANG_WARN_INT_CONVERSION = YES; 360 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 361 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 362 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 363 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 364 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 365 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 366 | CLANG_WARN_STRICT_PROTOTYPES = YES; 367 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 368 | CLANG_WARN_UNREACHABLE_CODE = YES; 369 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 370 | CODE_SIGN_IDENTITY = "-"; 371 | COPY_PHASE_STRIP = NO; 372 | DEAD_CODE_STRIPPING = YES; 373 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 374 | ENABLE_NS_ASSERTIONS = NO; 375 | ENABLE_STRICT_OBJC_MSGSEND = YES; 376 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 377 | GCC_C_LANGUAGE_STANDARD = gnu99; 378 | GCC_NO_COMMON_BLOCKS = YES; 379 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 380 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 381 | GCC_WARN_UNDECLARED_SELECTOR = YES; 382 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 383 | GCC_WARN_UNUSED_FUNCTION = YES; 384 | GCC_WARN_UNUSED_VARIABLE = YES; 385 | MACOSX_DEPLOYMENT_TARGET = 13.3; 386 | MTL_ENABLE_DEBUG_INFO = NO; 387 | SDKROOT = macosx; 388 | SWIFT_COMPILATION_MODE = wholemodule; 389 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 390 | SWIFT_VERSION = 5.0; 391 | }; 392 | name = Release; 393 | }; 394 | E3FE2CCD1E726CE800C6713A /* Debug */ = { 395 | isa = XCBuildConfiguration; 396 | buildSettings = { 397 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 398 | CLANG_ENABLE_MODULES = YES; 399 | CODE_SIGN_IDENTITY = "Apple Development"; 400 | CODE_SIGN_STYLE = Automatic; 401 | COMBINE_HIDPI_IMAGES = YES; 402 | CURRENT_PROJECT_VERSION = 8; 403 | DEAD_CODE_STRIPPING = YES; 404 | DEVELOPMENT_TEAM = YG56YK5RN5; 405 | ENABLE_HARDENED_RUNTIME = YES; 406 | FRAMEWORK_SEARCH_PATHS = ( 407 | "$(inherited)", 408 | "$(PROJECT_DIR)", 409 | /Applications/Xcode.app/Contents/SharedFrameworks, 410 | /Applications/Xcode.app/Contents/Frameworks, 411 | /Applications/Xcode.app/Contents/PlugIns, 412 | /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks, 413 | ); 414 | INFOPLIST_FILE = "Touch Bar Simulator/Info.plist"; 415 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; 416 | LD_RUNPATH_SEARCH_PATHS = ( 417 | "$(inherited)", 418 | "@executable_path/../Frameworks", 419 | "@loader_path/../Frameworks", 420 | ); 421 | MARKETING_VERSION = 4.2.0; 422 | PRODUCT_BUNDLE_IDENTIFIER = "com.sindresorhus.Touch-Bar-Simulator"; 423 | PRODUCT_NAME = "$(TARGET_NAME)"; 424 | PROVISIONING_PROFILE_SPECIFIER = ""; 425 | REEXPORTED_LIBRARY_PATHS = ""; 426 | SWIFT_COMPILATION_MODE = singlefile; 427 | SWIFT_OBJC_BRIDGING_HEADER = "Touch Bar Simulator/Touch Bar Simulator-Bridging-Header.h"; 428 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 429 | SYSTEM_FRAMEWORK_SEARCH_PATHS = ( 430 | "$(inherited)", 431 | "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", 432 | ); 433 | }; 434 | name = Debug; 435 | }; 436 | E3FE2CCE1E726CE800C6713A /* Release */ = { 437 | isa = XCBuildConfiguration; 438 | buildSettings = { 439 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 440 | CLANG_ENABLE_MODULES = YES; 441 | CODE_SIGN_IDENTITY = "Apple Development"; 442 | CODE_SIGN_STYLE = Automatic; 443 | COMBINE_HIDPI_IMAGES = YES; 444 | CURRENT_PROJECT_VERSION = 8; 445 | DEAD_CODE_STRIPPING = YES; 446 | DEVELOPMENT_TEAM = YG56YK5RN5; 447 | ENABLE_HARDENED_RUNTIME = YES; 448 | FRAMEWORK_SEARCH_PATHS = ( 449 | "$(inherited)", 450 | "$(PROJECT_DIR)", 451 | /Applications/Xcode.app/Contents/SharedFrameworks, 452 | /Applications/Xcode.app/Contents/Frameworks, 453 | /Applications/Xcode.app/Contents/PlugIns, 454 | /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks, 455 | ); 456 | INFOPLIST_FILE = "Touch Bar Simulator/Info.plist"; 457 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; 458 | LD_RUNPATH_SEARCH_PATHS = ( 459 | "$(inherited)", 460 | "@executable_path/../Frameworks", 461 | "@loader_path/../Frameworks", 462 | ); 463 | MARKETING_VERSION = 4.2.0; 464 | PRODUCT_BUNDLE_IDENTIFIER = "com.sindresorhus.Touch-Bar-Simulator"; 465 | PRODUCT_NAME = "$(TARGET_NAME)"; 466 | PROVISIONING_PROFILE_SPECIFIER = ""; 467 | REEXPORTED_LIBRARY_PATHS = ""; 468 | SWIFT_OBJC_BRIDGING_HEADER = "Touch Bar Simulator/Touch Bar Simulator-Bridging-Header.h"; 469 | SYSTEM_FRAMEWORK_SEARCH_PATHS = ( 470 | "$(inherited)", 471 | "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", 472 | ); 473 | }; 474 | name = Release; 475 | }; 476 | /* End XCBuildConfiguration section */ 477 | 478 | /* Begin XCConfigurationList section */ 479 | E3FE2CBA1E726CE800C6713A /* Build configuration list for PBXProject "Touch Bar Simulator" */ = { 480 | isa = XCConfigurationList; 481 | buildConfigurations = ( 482 | E3FE2CCA1E726CE800C6713A /* Debug */, 483 | E3FE2CCB1E726CE800C6713A /* Release */, 484 | ); 485 | defaultConfigurationIsVisible = 0; 486 | defaultConfigurationName = Release; 487 | }; 488 | E3FE2CCC1E726CE800C6713A /* Build configuration list for PBXNativeTarget "Touch Bar Simulator" */ = { 489 | isa = XCConfigurationList; 490 | buildConfigurations = ( 491 | E3FE2CCD1E726CE800C6713A /* Debug */, 492 | E3FE2CCE1E726CE800C6713A /* Release */, 493 | ); 494 | defaultConfigurationIsVisible = 0; 495 | defaultConfigurationName = Release; 496 | }; 497 | /* End XCConfigurationList section */ 498 | 499 | /* Begin XCRemoteSwiftPackageReference section */ 500 | 3C48E6A4247C370E00D22565 /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */ = { 501 | isa = XCRemoteSwiftPackageReference; 502 | repositoryURL = "https://github.com/sindresorhus/KeyboardShortcuts"; 503 | requirement = { 504 | kind = upToNextMinorVersion; 505 | minimumVersion = 1.3.0; 506 | }; 507 | }; 508 | E31957F723F140D500856B40 /* XCRemoteSwiftPackageReference "Defaults" */ = { 509 | isa = XCRemoteSwiftPackageReference; 510 | repositoryURL = "https://github.com/sindresorhus/Defaults"; 511 | requirement = { 512 | kind = upToNextMajorVersion; 513 | minimumVersion = 6.1.0; 514 | }; 515 | }; 516 | E344508F251573A4004A5FC1 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */ = { 517 | isa = XCRemoteSwiftPackageReference; 518 | repositoryURL = "https://github.com/sindresorhus/LaunchAtLogin"; 519 | requirement = { 520 | kind = upToNextMajorVersion; 521 | minimumVersion = 4.2.0; 522 | }; 523 | }; 524 | E3D07F1127947D6C006320B7 /* XCRemoteSwiftPackageReference "Sparkle" */ = { 525 | isa = XCRemoteSwiftPackageReference; 526 | repositoryURL = "https://github.com/sparkle-project/Sparkle"; 527 | requirement = { 528 | kind = upToNextMajorVersion; 529 | minimumVersion = 2.0.0; 530 | }; 531 | }; 532 | /* End XCRemoteSwiftPackageReference section */ 533 | 534 | /* Begin XCSwiftPackageProductDependency section */ 535 | 3C48E6A5247C370E00D22565 /* KeyboardShortcuts */ = { 536 | isa = XCSwiftPackageProductDependency; 537 | package = 3C48E6A4247C370E00D22565 /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */; 538 | productName = KeyboardShortcuts; 539 | }; 540 | E31957F823F140D500856B40 /* Defaults */ = { 541 | isa = XCSwiftPackageProductDependency; 542 | package = E31957F723F140D500856B40 /* XCRemoteSwiftPackageReference "Defaults" */; 543 | productName = Defaults; 544 | }; 545 | E3445090251573A4004A5FC1 /* LaunchAtLogin */ = { 546 | isa = XCSwiftPackageProductDependency; 547 | package = E344508F251573A4004A5FC1 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */; 548 | productName = LaunchAtLogin; 549 | }; 550 | E3D07F1227947D6C006320B7 /* Sparkle */ = { 551 | isa = XCSwiftPackageProductDependency; 552 | package = E3D07F1127947D6C006320B7 /* XCRemoteSwiftPackageReference "Sparkle" */; 553 | productName = Sparkle; 554 | }; 555 | /* End XCSwiftPackageProductDependency section */ 556 | }; 557 | rootObject = E3FE2CB71E726CE800C6713A /* Project object */; 558 | } 559 | -------------------------------------------------------------------------------- /Touch Bar Simulator.xcodeproj/xcshareddata/xcschemes/Touch Bar Simulator.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Touch Bar Simulator/App.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Sparkle 3 | import Defaults 4 | import LaunchAtLogin 5 | import KeyboardShortcuts 6 | 7 | final class AppDelegate: NSObject, NSApplicationDelegate { 8 | private(set) lazy var window = with(TouchBarWindow()) { 9 | $0.alphaValue = Defaults[.windowTransparency] 10 | $0.setUp() 11 | } 12 | 13 | private(set) lazy var statusItem = with(NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)) { 14 | $0.menu = with(NSMenu()) { 15 | $0.delegate = self 16 | } 17 | $0.button!.image = .menuBarIcon 18 | $0.button!.toolTip = "Right-click or option-click for menu" 19 | $0.button!.preventsHighlight = true 20 | } 21 | 22 | private lazy var updateController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil) 23 | 24 | func applicationWillFinishLaunching(_ notification: Notification) { 25 | UserDefaults.standard.register(defaults: [ 26 | "NSApplicationCrashOnExceptions": true 27 | ]) 28 | } 29 | 30 | func applicationDidFinishLaunching(_ notification: Notification) { 31 | checkAccessibilityPermission() 32 | _ = updateController 33 | _ = window 34 | _ = statusItem 35 | 36 | KeyboardShortcuts.onKeyUp(for: .toggleTouchBar) { [self] in 37 | toggleView() 38 | } 39 | } 40 | 41 | func checkAccessibilityPermission() { 42 | // We intentionally don't use the system prompt as our dialog explains it better. 43 | let options = [kAXTrustedCheckOptionPrompt.takeRetainedValue() as String: false] as CFDictionary 44 | if AXIsProcessTrustedWithOptions(options) { 45 | return 46 | } 47 | 48 | "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility".openUrl() 49 | 50 | let alert = NSAlert() 51 | alert.messageText = "Touch Bar Simulator needs accessibility access." 52 | alert.informativeText = "In the System Preferences window that just opened, find “Touch Bar Simulator” in the list and check its checkbox. Then click the “Continue” button here." 53 | alert.addButton(withTitle: "Continue") 54 | alert.addButton(withTitle: "Quit") 55 | 56 | guard alert.runModal() == .alertFirstButtonReturn else { 57 | SSApp.quit() 58 | return 59 | } 60 | 61 | SSApp.relaunch() 62 | } 63 | 64 | @objc 65 | func captureScreenshot() { 66 | let KEY_6: CGKeyCode = 0x58 67 | pressKey(keyCode: KEY_6, flags: [.maskShift, .maskCommand]) 68 | } 69 | 70 | func toggleView() { 71 | window.setIsVisible(!window.isVisible) 72 | } 73 | } 74 | 75 | extension AppDelegate: NSMenuDelegate { 76 | private func update(menu: NSMenu) { 77 | menu.removeAllItems() 78 | 79 | guard statusItemShouldShowMenu() else { 80 | return 81 | } 82 | 83 | menu.addItem(NSMenuItem(title: "Docking", action: nil, keyEquivalent: "")) 84 | var statusMenuDockingItems: [NSMenuItem] = [] 85 | statusMenuDockingItems.append(NSMenuItem("Floating").bindChecked(to: .windowDocking, value: .floating)) 86 | statusMenuDockingItems.append(NSMenuItem("Docked to Top").bindChecked(to: .windowDocking, value: .dockedToTop)) 87 | statusMenuDockingItems.append(NSMenuItem("Docked to Bottom").bindChecked(to: .windowDocking, value: .dockedToBottom)) 88 | for item in statusMenuDockingItems { 89 | item.indentationLevel = 1 90 | } 91 | menu.items.append(contentsOf: statusMenuDockingItems) 92 | 93 | func sliderMenuItem(_ title: String, boundTo key: Defaults.Key, min: Double, max: Double) -> NSMenuItem { 94 | let menuItem = NSMenuItem(title) 95 | let containerView = NSView(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 20))) 96 | 97 | let slider = MenubarSlider().alwaysRedisplayOnValueChanged() 98 | slider.frame = CGRect(x: 20, y: 4, width: 180, height: 11) 99 | slider.minValue = min 100 | slider.maxValue = max 101 | slider.bindDoubleValue(to: key) 102 | 103 | containerView.addSubview(slider) 104 | slider.translatesAutoresizingMaskIntoConstraints = false 105 | slider.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 24).isActive = true 106 | slider.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -9).isActive = true 107 | slider.centerYAnchor.constraint(equalTo: containerView.centerYAnchor).isActive = true 108 | menuItem.view = containerView 109 | 110 | return menuItem 111 | } 112 | 113 | if Defaults[.windowDocking] != .floating { 114 | menu.addItem(NSMenuItem("Padding")) 115 | menu.addItem(sliderMenuItem("Padding", boundTo: .windowPadding, min: 0.0, max: 120.0)) 116 | } 117 | 118 | menu.addItem(NSMenuItem("Opacity")) 119 | menu.addItem(sliderMenuItem("Opacity", boundTo: .windowTransparency, min: 0.5, max: 1.0)) 120 | 121 | menu.addItem(NSMenuItem.separator()) 122 | 123 | menu.addItem(NSMenuItem("Capture Screenshot", keyEquivalent: "6", keyModifiers: [.shift, .command]) { [self] _ in 124 | captureScreenshot() 125 | }) 126 | 127 | menu.addItem(NSMenuItem.separator()) 128 | 129 | menu.addItem(NSMenuItem("Show on All Desktops").bindState(to: .showOnAllDesktops)) 130 | 131 | menu.addItem(NSMenuItem("Hide and Show Automatically").bindState(to: .dockBehavior)) 132 | 133 | menu.addItem(NSMenuItem("Launch at Login", isChecked: LaunchAtLogin.isEnabled) { item in 134 | item.isChecked.toggle() 135 | LaunchAtLogin.isEnabled = item.isChecked 136 | }) 137 | 138 | menu.addItem(NSMenuItem("Keyboard Shortcuts…") { [self] _ in 139 | guard let button = statusItem.button else { 140 | return 141 | } 142 | let popover = NSPopover() 143 | popover.contentViewController = NSHostingController(rootView: KeyboardShortcutsView()) 144 | popover.behavior = .transient 145 | popover.show(relativeTo: button.frame, of: button, preferredEdge: .maxY) 146 | }) 147 | 148 | menu.addItem(NSMenuItem.separator()) 149 | 150 | menu.addItem(NSMenuItem("Quit Touch Bar Simulator", keyEquivalent: "q") { _ in 151 | NSApp.terminate(nil) 152 | }) 153 | } 154 | 155 | private func statusItemShouldShowMenu() -> Bool { 156 | !NSApp.isLeftMouseDown || NSApp.isOptionKeyDown 157 | } 158 | 159 | func menuNeedsUpdate(_ menu: NSMenu) { 160 | update(menu: menu) 161 | } 162 | 163 | func menuWillOpen(_ menu: NSMenu) { 164 | let shouldShowMenu = statusItemShouldShowMenu() 165 | 166 | statusItem.button!.preventsHighlight = !shouldShowMenu 167 | if !shouldShowMenu { 168 | statusItemButtonClicked() 169 | } 170 | } 171 | 172 | private func statusItemButtonClicked() { 173 | // When the user explicitly wants the Touch Bar to appear then `dockBahavior` should be disabled. 174 | // This is also how the macOS Dock behaves. 175 | Defaults[.dockBehavior] = false 176 | 177 | toggleView() 178 | 179 | if window.isVisible { 180 | window.orderFront(nil) 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon_16x16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "icon_16x16@2x.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "icon_32x32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "icon_32x32@2x.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "icon_128x128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "icon_128x128@2x.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "icon_256x256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "icon_256x256@2x.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "icon_512x512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "icon_512x512@2x.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/Touch Bar Simulator/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /Touch Bar Simulator/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Touch Bar Simulator/Assets.xcassets/MenuBarIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "filename" : "MenuBarIcon.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "filename" : "MenuBarIcon@2x.png", 11 | "scale" : "2x" 12 | } 13 | ], 14 | "info" : { 15 | "version" : 1, 16 | "author" : "xcode" 17 | }, 18 | "properties" : { 19 | "template-rendering-intent" : "template" 20 | } 21 | } -------------------------------------------------------------------------------- /Touch Bar Simulator/Assets.xcassets/MenuBarIcon.imageset/MenuBarIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/Touch Bar Simulator/Assets.xcassets/MenuBarIcon.imageset/MenuBarIcon.png -------------------------------------------------------------------------------- /Touch Bar Simulator/Assets.xcassets/MenuBarIcon.imageset/MenuBarIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/Touch Bar Simulator/Assets.xcassets/MenuBarIcon.imageset/MenuBarIcon@2x.png -------------------------------------------------------------------------------- /Touch Bar Simulator/Constants.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Defaults 3 | import KeyboardShortcuts 4 | 5 | enum Constants { 6 | static let windowAutosaveName = "TouchBarWindow" 7 | } 8 | 9 | extension Defaults.Keys { 10 | static let windowTransparency = Key("windowTransparency", default: 0.75) 11 | static let windowDocking = Key("windowDocking", default: .floating) 12 | static let windowPadding = Key("windowPadding", default: 0.0) 13 | static let showOnAllDesktops = Key("showOnAllDesktops", default: false) 14 | static let lastFloatingPosition = Key("lastFloatingPosition") 15 | static let dockBehavior = Key("dockBehavior", default: false) 16 | static let lastWindowDockingWithDockBehavior = Key("windowDockingWithDockBehavior", default: .dockedToTop) 17 | } 18 | 19 | extension TouchBarWindow.Docking: Defaults.Serializable {} 20 | extension CGPoint: Defaults.Serializable {} 21 | 22 | extension KeyboardShortcuts.Name { 23 | static let toggleTouchBar = Self("toggleTouchBar") 24 | } 25 | -------------------------------------------------------------------------------- /Touch Bar Simulator/Glue.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | import Defaults 3 | 4 | // TODO: Upstream all these to https://github.com/sindresorhus/Defaults 5 | 6 | extension NSMenuItem { 7 | /** 8 | Adds an action to this menu item that toggles the value of `key` in the 9 | defaults system, and initializes this item's state to the current value of 10 | `key`. 11 | 12 | ``` 13 | let menuItem = NSMenuItem(title: "Invert Colors").bindState(to: .invertColors) 14 | ``` 15 | */ 16 | @discardableResult 17 | func bindState(to key: Defaults.Key) -> Self { 18 | addAction { _ in 19 | Defaults[key].toggle() 20 | } 21 | 22 | Defaults.observe(key) { [weak self] change in 23 | self?.isChecked = change.newValue 24 | } 25 | .tieToLifetime(of: self) 26 | 27 | return self 28 | } 29 | 30 | // TODO: The doc comments here are out of date. 31 | /** 32 | Adds an action to this menu item that sets the value of `key` in the 33 | defaults system to `value`, and initializes this item's state based on 34 | whether the current value of `key` matches `value`. 35 | 36 | ``` 37 | enum BillingType { 38 | case paper, electronic, duck 39 | } 40 | 41 | let menuItem = NSMenuItem(title: "Duck").bindChecked(to: .billingType, value: .duck) 42 | ``` 43 | */ 44 | @discardableResult 45 | func bindChecked(to key: Defaults.Key, value: Value) -> Self { 46 | addAction { _ in 47 | Defaults[key] = value 48 | } 49 | 50 | Defaults.observe(key) { [weak self] change in 51 | self?.isChecked = (change.newValue == value) 52 | } 53 | .tieToLifetime(of: self) 54 | 55 | return self 56 | } 57 | } 58 | 59 | // TODO: Generalize this to all `NSControl`s, or maybe even all things with a `.action` and `.doubleValue`? 60 | extension NSSlider { 61 | // TODO: The doc comments here are out of date 62 | /** 63 | Adds an action to this slider that sets the value of `key` in the defaults 64 | system to the slider's `doubleValue`, and initializes its value to the 65 | current value of `key`. 66 | 67 | ``` 68 | let slider = NSSlider().bindDoubleValue(to: .transparency) 69 | ``` 70 | */ 71 | @discardableResult 72 | func bindDoubleValue(to key: Defaults.Key) -> Self { 73 | addAction { sender in 74 | Defaults[key] = sender.doubleValue 75 | } 76 | 77 | Defaults.observe(key) { [weak self] change in 78 | self?.doubleValue = change.newValue 79 | } 80 | .tieToLifetime(of: self) 81 | 82 | return self 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Touch Bar Simulator/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleExecutable 6 | $(EXECUTABLE_NAME) 7 | CFBundleIdentifier 8 | $(PRODUCT_BUNDLE_IDENTIFIER) 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundleName 12 | $(PRODUCT_NAME) 13 | CFBundlePackageType 14 | APPL 15 | CFBundleShortVersionString 16 | $(MARKETING_VERSION) 17 | CFBundleVersion 18 | $(CURRENT_PROJECT_VERSION) 19 | LSApplicationCategoryType 20 | public.app-category.developer-tools 21 | LSMinimumSystemVersion 22 | $(MACOSX_DEPLOYMENT_TARGET) 23 | LSUIElement 24 | 25 | NSHumanReadableCopyright 26 | MIT License © Sindre Sorhus 27 | SUEnableAutomaticChecks 28 | 29 | SUFeedURL 30 | https://sindresorhus.com/touch-bar-simulator/appcast.xml 31 | 32 | 33 | -------------------------------------------------------------------------------- /Touch Bar Simulator/KeyboardShortcutsView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import KeyboardShortcuts 3 | 4 | private struct ShortcutRecorder: View { 5 | var title: String 6 | var shortcut: KeyboardShortcuts.Name 7 | 8 | var body: some View { 9 | HStack(alignment: .firstTextBaseline) { 10 | Text("\(title):") 11 | KeyboardShortcuts.Recorder(for: shortcut) 12 | } 13 | } 14 | } 15 | 16 | struct KeyboardShortcutsView: View { 17 | var body: some View { 18 | VStack { 19 | ShortcutRecorder(title: "Toggle Touch Bar", shortcut: .toggleTouchBar) 20 | } 21 | .padding(20) 22 | .fixedSize() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Touch Bar Simulator/ToolbarSlider.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import Defaults 3 | 4 | private final class ToolbarSliderCell: NSSliderCell { 5 | var fillColor: NSColor 6 | var borderColor: NSColor 7 | var shadow: NSShadow? 8 | 9 | init(fillColor: NSColor, borderColor: NSColor, shadow: NSShadow? = nil) { 10 | self.fillColor = fillColor 11 | self.borderColor = borderColor 12 | self.shadow = shadow 13 | super.init() 14 | } 15 | 16 | @available(*, unavailable) 17 | required init(coder: NSCoder) { 18 | fatalError("init(coder:) has not been implemented") 19 | } 20 | 21 | override func drawKnob(_ knobRect: CGRect) { 22 | var frame = knobRect.insetBy(dx: 0, dy: 10) 23 | if let shadow = shadow { 24 | // Make room on either side of the view for the shadow to spill into, 25 | // rather than clip on the edges. 26 | frame.origin.x *= ((barRect.width - shadow.shadowBlurRadius * 2) / barRect.width) 27 | frame.origin.x += shadow.shadowBlurRadius 28 | } 29 | 30 | // Make the slider grey. 31 | var greySliderFrame = barRect 32 | greySliderFrame.origin.x -= 1 33 | greySliderFrame.origin.y = barRect.origin.y 34 | greySliderFrame.size.width = frame.origin.x 35 | greySliderFrame.size.height = 4 36 | let greySliderPath = NSBezierPath(roundedRect: greySliderFrame, xRadius: 1.5, yRadius: 1.5) 37 | NSColor.lightGray.setFill() 38 | greySliderPath.fill() 39 | 40 | NSGraphicsContext.saveGraphicsState() 41 | 42 | shadow?.set() 43 | 44 | // Circle 45 | var circleFrame = frame 46 | circleFrame.origin.y -= 2 47 | let path = NSBezierPath(roundedRect: circleFrame, xRadius: 4, yRadius: 12) 48 | fillColor.set() 49 | path.fill() 50 | 51 | // Border should not draw a shadow. 52 | NSShadow().set() 53 | 54 | // Border 55 | borderColor.set() 56 | path.lineWidth = 0.8 57 | path.stroke() 58 | 59 | NSGraphicsContext.restoreGraphicsState() 60 | } 61 | 62 | private var barRect = CGRect.zero 63 | 64 | override func drawBar(inside rect: CGRect, flipped: Bool) { 65 | barRect = rect 66 | 67 | // A knob shadow requires a small skew in the origin of the knob (see above), 68 | // which causes the knob to not go all the way to the ends of the bar. 69 | // Fix this by shortening the bar. 70 | if let shadow = shadow { 71 | barRect = barRect.insetBy(dx: shadow.shadowBlurRadius * 2, dy: 0) 72 | } 73 | 74 | super.drawBar(inside: barRect, flipped: flipped) 75 | } 76 | } 77 | 78 | extension NSSlider { 79 | // Redisplaying the slider prevents shadow artifacts that result 80 | // from moving a knob that draws a shadow. 81 | // However, only do so if its value has changed, because if a 82 | // redisplay is attempted without a change, then the slider draws 83 | // itself brighter for some reason. 84 | func alwaysRedisplayOnValueChanged() -> Self { 85 | addAction { sender in 86 | if (Defaults[.windowTransparency] - sender.doubleValue) != 0 { 87 | sender.needsDisplay = true 88 | } 89 | } 90 | 91 | return self 92 | } 93 | } 94 | 95 | final class ToolbarSlider: NSSlider { 96 | override init(frame: CGRect) { 97 | super.init(frame: frame) 98 | 99 | let knobShadow = NSShadow() 100 | knobShadow.shadowColor = .black.withAlphaComponent(0.7) 101 | knobShadow.shadowOffset = CGSize(width: 0.8, height: -0.8) 102 | knobShadow.shadowBlurRadius = 5 103 | 104 | self.cell = ToolbarSliderCell(fillColor: .lightGray, borderColor: .black, shadow: knobShadow) 105 | } 106 | 107 | @available(*, unavailable) 108 | required init?(coder: NSCoder) { 109 | fatalError("init(coder:) has not been implemented") 110 | } 111 | } 112 | 113 | final class MenubarSlider: NSSlider { 114 | override init(frame: CGRect) { 115 | super.init(frame: frame) 116 | 117 | let knobShadow = NSShadow() 118 | knobShadow.shadowColor = .black.withAlphaComponent(0.6) 119 | knobShadow.shadowOffset = CGSize(width: 0.8, height: -0.8) 120 | knobShadow.shadowBlurRadius = 4 121 | 122 | self.cell = ToolbarSliderCell(fillColor: .controlTextColor, borderColor: .clear, shadow: knobShadow) 123 | } 124 | 125 | @available(*, unavailable) 126 | required init?(coder: NSCoder) { 127 | fatalError("init(coder:) has not been implemented") 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Touch Bar Simulator/Touch Bar Simulator-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | CGDisplayStreamRef SLSDFRDisplayStreamCreate(int displayID, dispatch_queue_t queue, CGDisplayStreamFrameAvailableHandler handler); 4 | CGSize DFRGetScreenSize(void); 5 | void DFRSetStatus(int); 6 | int DFRGetStatus(void); 7 | void DFRFoundationPostEventWithMouseActivity(NSEventType type, CGPoint point); 8 | 9 | @interface NSWindow (Private) 10 | - (void)_setPreventsActivation:(bool)preventsActivation; 11 | @end 12 | -------------------------------------------------------------------------------- /Touch Bar Simulator/Touch Bar Simulator.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Touch Bar Simulator/TouchBarView.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | final class TouchBarView: NSView { 4 | private var stream: CGDisplayStream? 5 | private let displayView = NSView() 6 | private let initialDFRStatus: Int32 7 | 8 | override init(frame: CGRect) { 9 | self.initialDFRStatus = DFRGetStatus() 10 | 11 | super.init(frame: .zero) 12 | 13 | wantsLayer = true 14 | start() 15 | setFrameSize(DFRGetScreenSize()) 16 | } 17 | 18 | @available(*, unavailable) 19 | required init?(coder: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | 23 | deinit { 24 | stop() 25 | } 26 | 27 | override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true } 28 | 29 | func start() { 30 | if (initialDFRStatus & 0x01) == 0 { 31 | DFRSetStatus(2) 32 | } 33 | 34 | stream = SLSDFRDisplayStreamCreate(0, .main) { [weak self] status, _, frameSurface, _ in 35 | guard 36 | let self = self, 37 | status == .frameComplete, 38 | let layer = self.layer 39 | else { 40 | return 41 | } 42 | 43 | layer.contents = frameSurface 44 | }.takeUnretainedValue() 45 | 46 | stream?.start() 47 | } 48 | 49 | func stop() { 50 | guard let stream = stream else { 51 | return 52 | } 53 | 54 | stream.stop() 55 | self.stream = nil 56 | DFRSetStatus(initialDFRStatus) 57 | } 58 | 59 | private func mouseEvent(_ event: NSEvent) { 60 | let location = convert(event.locationInWindow, from: nil) 61 | DFRFoundationPostEventWithMouseActivity(event.type, location) 62 | } 63 | 64 | override func mouseDown(with event: NSEvent) { 65 | mouseEvent(event) 66 | } 67 | 68 | override func mouseUp(with event: NSEvent) { 69 | mouseEvent(event) 70 | } 71 | 72 | override func mouseDragged(with event: NSEvent) { 73 | mouseEvent(event) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Touch Bar Simulator/TouchBarWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import Combine 3 | import Defaults 4 | 5 | final class TouchBarWindow: NSPanel { 6 | // TODO: Migrate this to not use `Codable`. 7 | enum Docking: String, Codable { 8 | case floating 9 | case dockedToTop 10 | case dockedToBottom 11 | 12 | func dock(window: TouchBarWindow, padding: Double) { 13 | switch self { 14 | case .floating: 15 | window.addTitlebar() 16 | case .dockedToTop: 17 | window.removeTitlebar() 18 | case .dockedToBottom: 19 | window.removeTitlebar() 20 | } 21 | 22 | reposition(window: window, padding: padding) 23 | } 24 | 25 | func reposition(window: NSWindow, padding: Double) { 26 | switch self { 27 | case .floating: 28 | if let prevPosition = Defaults[.lastFloatingPosition] { 29 | window.setFrameOrigin(prevPosition) 30 | } 31 | case .dockedToTop: 32 | window.moveTo(x: .center, y: .top) 33 | window.setFrameOrigin(CGPoint(x: window.frame.origin.x, y: window.frame.origin.y - padding)) 34 | case .dockedToBottom: 35 | window.moveTo(x: .center, y: .bottom) 36 | window.setFrameOrigin(CGPoint(x: window.frame.origin.x, y: window.frame.origin.y + padding)) 37 | } 38 | } 39 | } 40 | 41 | override var canBecomeMain: Bool { false } 42 | override var canBecomeKey: Bool { false } 43 | 44 | var docking: Docking = .floating { 45 | didSet { 46 | if oldValue == .floating, docking != .floating { 47 | Defaults[.lastFloatingPosition] = frame.origin 48 | } 49 | 50 | if docking == .floating { 51 | dockBehavior = false 52 | } 53 | 54 | // Prevent the Touch Bar from momentarily becoming visible. 55 | if docking == .floating || !dockBehavior { 56 | stopDockBehaviorTimer() 57 | docking.dock(window: self, padding: Defaults[.windowPadding]) 58 | setIsVisible(true) 59 | orderFront(nil) 60 | return 61 | } 62 | 63 | // When docking is set to `dockedToTop` or `dockedToBottom` dockBehavior should start. 64 | if dockBehavior { 65 | setIsVisible(false) 66 | docking.dock(window: self, padding: Defaults[.windowPadding]) 67 | startDockBehaviorTimer() 68 | } 69 | } 70 | } 71 | 72 | var showOnAllDesktops = false { 73 | didSet { 74 | if showOnAllDesktops { 75 | collectionBehavior = .canJoinAllSpaces 76 | } else { 77 | collectionBehavior = .moveToActiveSpace 78 | } 79 | } 80 | } 81 | 82 | var dockBehaviorTimer = Timer() 83 | var showTouchBarTimer = Timer() 84 | 85 | func startDockBehaviorTimer() { 86 | stopDockBehaviorTimer() 87 | 88 | dockBehaviorTimer = Timer.scheduledTimer( 89 | timeInterval: 0.1, 90 | target: self, 91 | selector: #selector(handleDockBehavior), 92 | userInfo: nil, 93 | repeats: true 94 | ) 95 | } 96 | 97 | func stopDockBehaviorTimer() { 98 | dockBehaviorTimer.invalidate() 99 | dockBehaviorTimer = Timer() 100 | } 101 | 102 | var dockBehavior: Bool = Defaults[.dockBehavior] { 103 | didSet { 104 | Defaults[.dockBehavior] = dockBehavior 105 | if docking == .dockedToBottom || docking == .dockedToTop { 106 | Defaults[.lastWindowDockingWithDockBehavior] = docking 107 | } 108 | 109 | if dockBehavior { 110 | if docking == .dockedToBottom || docking == .dockedToTop { 111 | docking = Defaults[.lastWindowDockingWithDockBehavior] 112 | startDockBehaviorTimer() 113 | } else if docking == .floating { 114 | Defaults[.windowDocking] = Defaults[.lastWindowDockingWithDockBehavior] 115 | } 116 | } else { 117 | stopDockBehaviorTimer() 118 | setIsVisible(true) 119 | } 120 | } 121 | } 122 | 123 | @objc 124 | func handleDockBehavior() { 125 | guard 126 | let visibleFrame = NSScreen.main?.visibleFrame, 127 | let screenFrame = NSScreen.main?.frame 128 | else { 129 | return 130 | } 131 | 132 | var detectionRect = CGRect.zero 133 | if docking == .dockedToBottom { 134 | if isVisible { 135 | detectionRect = CGRect( 136 | x: 0, 137 | y: 0, 138 | width: visibleFrame.width, 139 | height: frame.height + (screenFrame.height - visibleFrame.height - NSStatusBar.system.thickness + Defaults[.windowPadding]) 140 | ) 141 | } else { 142 | detectionRect = CGRect(x: 0, y: 0, width: visibleFrame.width, height: 1) 143 | } 144 | } else if docking == .dockedToTop { 145 | if isVisible { 146 | detectionRect = CGRect( 147 | x: 0, 148 | // Without `+ 1`, the Touch Bar would glitch (toggling rapidly). 149 | y: screenFrame.height - frame.height - NSStatusBar.system.thickness - Defaults[.windowPadding] + 1.0, 150 | width: visibleFrame.width, 151 | height: frame.height + NSStatusBar.system.thickness + Defaults[.windowPadding] 152 | ) 153 | } else { 154 | detectionRect = CGRect( 155 | x: 0, 156 | y: screenFrame.height, 157 | width: visibleFrame.width, 158 | height: 1 159 | ) 160 | } 161 | } 162 | 163 | let mouseLocation = NSEvent.mouseLocation 164 | if detectionRect.contains(mouseLocation) { 165 | dismissAnimationDidRun = false 166 | 167 | guard 168 | !showTouchBarTimer.isValid, 169 | !showAnimationDidRun 170 | else { 171 | return 172 | } 173 | 174 | showTouchBarTimer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: false) { [weak self] _ in 175 | guard let self = self else { 176 | return 177 | } 178 | 179 | self.performActionWithAnimation(action: .show) 180 | self.showAnimationDidRun = true 181 | } 182 | } else { 183 | showTouchBarTimer.invalidate() 184 | showTouchBarTimer = Timer() 185 | showAnimationDidRun = false 186 | 187 | if isVisible, !dismissAnimationDidRun { 188 | performActionWithAnimation(action: .dismiss) 189 | dismissAnimationDidRun = true 190 | } 191 | } 192 | } 193 | 194 | var showAnimationDidRun = false 195 | var dismissAnimationDidRun = false 196 | 197 | func performActionWithAnimation(action: TouchBarAction) { 198 | guard 199 | docking == .dockedToTop || 200 | docking == .dockedToBottom 201 | else { 202 | return 203 | } 204 | 205 | var endY: Double! 206 | 207 | if action == .show { 208 | docking.reposition(window: self, padding: -frame.height) 209 | setIsVisible(true) 210 | 211 | if docking == .dockedToTop { 212 | endY = frame.minY - frame.height - Defaults[.windowPadding] 213 | } else if docking == .dockedToBottom { 214 | endY = frame.minY + frame.height + Defaults[.windowPadding] 215 | } 216 | } else if action == .dismiss { 217 | if docking == .dockedToTop { 218 | endY = frame.minY + frame.height + NSStatusBar.system.thickness + Defaults[.windowPadding] 219 | } else if docking == .dockedToBottom { 220 | endY = 0 - frame.height 221 | } 222 | } 223 | 224 | var endFrame = frame 225 | endFrame.origin.y = endY 226 | 227 | NSAnimationContext.runAnimationGroup({ context in 228 | context.duration = TimeInterval(0.3) 229 | animator().setFrame(endFrame, display: false, animate: true) 230 | }, completionHandler: { [self] in 231 | if action == .show { 232 | docking.reposition(window: self, padding: Defaults[.windowPadding]) 233 | } else if action == .dismiss { 234 | setIsVisible(false) 235 | docking.reposition(window: self, padding: 0) 236 | } 237 | }) 238 | } 239 | 240 | enum TouchBarAction { 241 | case show 242 | case dismiss 243 | } 244 | 245 | func addTitlebar() { 246 | styleMask.insert(.titled) 247 | title = "Touch Bar Simulator" 248 | 249 | guard let toolbarView = toolbarView else { 250 | return 251 | } 252 | 253 | toolbarView.addSubviews( 254 | makeScreenshotButton(toolbarView), 255 | makeTransparencySlider(toolbarView) 256 | ) 257 | } 258 | 259 | func removeTitlebar() { 260 | styleMask.remove(.titled) 261 | } 262 | 263 | func makeScreenshotButton(_ toolbarView: NSView) -> NSButton { 264 | let button = NSButton() 265 | button.image = NSImage(systemSymbolName: "camera.fill", accessibilityDescription: "Capture screenshot of the Touch Bar") 266 | button.imageScaling = .scaleProportionallyDown 267 | button.isBordered = false 268 | button.bezelStyle = .shadowlessSquare 269 | button.frame = CGRect(x: toolbarView.frame.width - 19, y: 4, width: 16, height: 11) 270 | button.action = #selector(AppDelegate.captureScreenshot) 271 | return button 272 | } 273 | 274 | private var transparencySlider: ToolbarSlider? 275 | 276 | func makeTransparencySlider(_ parentView: NSView) -> ToolbarSlider { 277 | let slider = ToolbarSlider().alwaysRedisplayOnValueChanged().bindDoubleValue(to: .windowTransparency) 278 | slider.frame = CGRect(x: parentView.frame.width - 160, y: 1, width: 140, height: 11) 279 | slider.minValue = 0.5 280 | return slider 281 | } 282 | 283 | private var cancellable: AnyCancellable? 284 | 285 | func setUp() { 286 | let view = contentView! 287 | view.wantsLayer = true 288 | view.layer?.backgroundColor = NSColor.black.cgColor 289 | 290 | let touchBarView = TouchBarView() 291 | setContentSize(touchBarView.bounds.adding(padding: 5).size) 292 | touchBarView.frame = touchBarView.frame.centered(in: view.bounds) 293 | view.addSubview(touchBarView) 294 | 295 | Defaults.tiedToLifetime(of: self) { 296 | Defaults.observe(.windowTransparency) { [weak self] change in 297 | self?.alphaValue = change.newValue 298 | } 299 | Defaults.observe(.windowDocking) { [weak self] change in 300 | self?.docking = change.newValue 301 | } 302 | Defaults.observe(.windowPadding) { [weak self] change in 303 | guard let self = self else { 304 | return 305 | } 306 | 307 | self.docking.reposition(window: self, padding: change.newValue) 308 | } 309 | // TODO: We could maybe simplify this by creating another `Default` extension to bind a default to a KeyPath: 310 | // `defaults.bind(.showOnAllDesktops, to: \.showOnAllDesktops)` 311 | Defaults.observe(.showOnAllDesktops) { [weak self] change in 312 | self?.showOnAllDesktops = change.newValue 313 | } 314 | Defaults.observe(.dockBehavior) { [weak self] change in 315 | self?.dockBehavior = change.newValue 316 | } 317 | } 318 | 319 | center() 320 | setFrameOrigin(CGPoint(x: frame.origin.x, y: 100)) 321 | 322 | setFrameUsingName(Constants.windowAutosaveName) 323 | setFrameAutosaveName(Constants.windowAutosaveName) 324 | 325 | // Prevent the Touch Bar from momentarily becoming visible. 326 | if !dockBehavior { 327 | orderFront(nil) 328 | } 329 | 330 | cancellable = NSScreen.publisher.sink { [weak self] in 331 | guard let self = self else { 332 | return 333 | } 334 | 335 | self.docking.reposition(window: self, padding: Defaults[.windowPadding]) 336 | } 337 | } 338 | 339 | convenience init() { 340 | self.init( 341 | contentRect: .zero, 342 | styleMask: [ 343 | .titled, 344 | .closable, 345 | .nonactivatingPanel, 346 | .hudWindow, 347 | .utilityWindow 348 | ], 349 | backing: .buffered, 350 | defer: false 351 | ) 352 | 353 | self.level = .assistiveTechHigh 354 | _setPreventsActivation(true) 355 | self.isRestorable = true 356 | self.hidesOnDeactivate = false 357 | self.worksWhenModal = true 358 | self.acceptsMouseMovedEvents = true 359 | self.isMovableByWindowBackground = false 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /Touch Bar Simulator/Utilities.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import Combine 3 | import Defaults 4 | 5 | /** 6 | Convenience function for initializing an object and modifying its properties. 7 | 8 | ``` 9 | let label = with(NSTextField()) { 10 | $0.stringValue = "Foo" 11 | $0.textColor = .systemBlue 12 | view.addSubview($0) 13 | } 14 | ``` 15 | */ 16 | @discardableResult 17 | func with(_ item: T, update: (inout T) throws -> Void) rethrows -> T { 18 | var this = item 19 | try update(&this) 20 | return this 21 | } 22 | 23 | 24 | extension CGRect { 25 | func adding(padding: Double) -> Self { 26 | Self( 27 | x: origin.x - padding, 28 | y: origin.y - padding, 29 | width: width + padding * 2, 30 | height: height + padding * 2 31 | ) 32 | } 33 | 34 | /** 35 | Returns a `CGRect` where `self` is centered in `rect`. 36 | */ 37 | func centered(in rect: Self, xOffset: Double = 0, yOffset: Double = 0) -> Self { 38 | Self( 39 | x: ((rect.width - size.width) / 2) + xOffset, 40 | y: ((rect.height - size.height) / 2) + yOffset, 41 | width: size.width, 42 | height: size.height 43 | ) 44 | } 45 | } 46 | 47 | 48 | extension NSWindow { 49 | var toolbarView: NSView? { standardWindowButton(.closeButton)?.superview } 50 | } 51 | 52 | 53 | extension NSWindow { 54 | enum MoveXPositioning { 55 | case left 56 | case center 57 | case right 58 | } 59 | 60 | enum MoveYPositioning { 61 | case top 62 | case center 63 | case bottom 64 | } 65 | 66 | func moveTo(x xPositioning: MoveXPositioning, y yPositioning: MoveYPositioning) { 67 | guard let screen = NSScreen.main else { 68 | return 69 | } 70 | 71 | let visibleFrame = screen.visibleFrame 72 | 73 | let x: Double 74 | let y: Double 75 | switch xPositioning { 76 | case .left: 77 | x = visibleFrame.minX 78 | case .center: 79 | x = visibleFrame.midX - frame.width / 2 80 | case .right: 81 | x = visibleFrame.maxX - frame.width 82 | } 83 | switch yPositioning { 84 | case .top: 85 | // Defect fix: Keep docked windows below menubar area. 86 | // Previously, the window would obstruct menubar clicks when the menubar was set to auto-hide. 87 | // Now, the window stays below that area. 88 | let menubarThickness = NSStatusBar.system.thickness 89 | y = min(visibleFrame.maxY - frame.height, screen.frame.maxY - menubarThickness - frame.height) 90 | case .center: 91 | y = visibleFrame.midY - frame.height / 2 92 | case .bottom: 93 | y = visibleFrame.minY 94 | } 95 | 96 | setFrameOrigin(CGPoint(x: x, y: y)) 97 | } 98 | } 99 | 100 | 101 | extension NSView { 102 | func addSubviews(_ subviews: NSView...) { 103 | subviews.forEach { addSubview($0) } 104 | } 105 | } 106 | 107 | 108 | extension NSMenuItem { 109 | var isChecked: Bool { 110 | get { state == .on } 111 | set { 112 | state = newValue ? .on : .off 113 | } 114 | } 115 | } 116 | 117 | 118 | extension NSMenuItem { 119 | convenience init( 120 | _ title: String, 121 | keyEquivalent: String = "", 122 | keyModifiers: NSEvent.ModifierFlags? = nil, 123 | isChecked: Bool = false, 124 | action: ((NSMenuItem) -> Void)? = nil 125 | ) { 126 | self.init(title: title, action: nil, keyEquivalent: keyEquivalent) 127 | 128 | if let keyModifiers = keyModifiers { 129 | self.keyEquivalentModifierMask = keyModifiers 130 | } 131 | 132 | self.isChecked = isChecked 133 | 134 | if let action = action { 135 | self.onAction = action 136 | } 137 | } 138 | } 139 | 140 | 141 | final class ObjectAssociation { 142 | subscript(index: AnyObject) -> T? { 143 | get { 144 | objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T? 145 | } set { 146 | objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 147 | } 148 | } 149 | } 150 | 151 | 152 | @objc 153 | protocol TargetActionSender: AnyObject { 154 | var target: AnyObject? { get set } 155 | var action: Selector? { get set } 156 | } 157 | 158 | extension NSControl: TargetActionSender {} 159 | extension NSMenuItem: TargetActionSender {} 160 | extension NSGestureRecognizer: TargetActionSender {} 161 | 162 | private final class ActionTrampoline: NSObject { 163 | typealias ActionClosure = ((Sender) -> Void) 164 | 165 | let action: ActionClosure 166 | 167 | init(action: @escaping ActionClosure) { 168 | self.action = action 169 | } 170 | 171 | @objc 172 | fileprivate func performAction(_ sender: TargetActionSender) { 173 | action(sender as! Sender) 174 | } 175 | } 176 | 177 | private enum TargetActionSenderAssociatedKeys { 178 | fileprivate static let trampoline = ObjectAssociation() 179 | } 180 | 181 | extension TargetActionSender { 182 | /** 183 | Closure version of `.action`. 184 | 185 | ``` 186 | let menuItem = NSMenuItem(title: "Unicorn") 187 | menuItem.onAction = { sender in 188 | print("NSMenuItem action: \(sender)") 189 | } 190 | ``` 191 | */ 192 | var onAction: ((Self) -> Void)? { 193 | get { 194 | (TargetActionSenderAssociatedKeys.trampoline[self] as? ActionTrampoline)?.action 195 | } 196 | set { 197 | guard let newValue = newValue else { 198 | target = nil 199 | action = nil 200 | TargetActionSenderAssociatedKeys.trampoline[self] = nil 201 | return 202 | } 203 | 204 | let trampoline = ActionTrampoline(action: newValue) 205 | TargetActionSenderAssociatedKeys.trampoline[self] = trampoline 206 | target = trampoline 207 | action = #selector(ActionTrampoline.performAction) 208 | } 209 | } 210 | 211 | func addAction(_ action: @escaping ((Self) -> Void)) { 212 | // TODO: The problem with doing it like this is that there's no way to add ability to remove an action. I think a better solution would be to store an array of action handlers using associated object. 213 | let lastAction = onAction 214 | onAction = { sender in 215 | lastAction?(sender) 216 | action(sender) 217 | } 218 | } 219 | } 220 | 221 | 222 | extension NSApplication { 223 | var isLeftMouseDown: Bool { currentEvent?.type == .leftMouseDown } 224 | var isOptionKeyDown: Bool { NSEvent.modifierFlags.contains(.option) } 225 | } 226 | 227 | 228 | // TODO: Find a namespace to put this onto. I don't like free-floating functions. 229 | func pressKey(keyCode: CGKeyCode, flags: CGEventFlags = []) { 230 | let eventSource = CGEventSource(stateID: .hidSystemState) 231 | let keyDown = CGEvent(keyboardEventSource: eventSource, virtualKey: keyCode, keyDown: true) 232 | let keyUp = CGEvent(keyboardEventSource: eventSource, virtualKey: keyCode, keyDown: false) 233 | keyDown?.flags = flags 234 | keyDown?.post(tap: .cghidEventTap) 235 | keyUp?.post(tap: .cghidEventTap) 236 | } 237 | 238 | 239 | extension NSWindow.Level { 240 | private static func level(for cgLevelKey: CGWindowLevelKey) -> Self { 241 | .init(rawValue: Int(CGWindowLevelForKey(cgLevelKey))) 242 | } 243 | 244 | public static let desktop = level(for: .desktopWindow) 245 | public static let desktopIcon = level(for: .desktopIconWindow) 246 | public static let backstopMenu = level(for: .backstopMenu) 247 | public static let dragging = level(for: .draggingWindow) 248 | public static let overlay = level(for: .overlayWindow) 249 | public static let help = level(for: .helpWindow) 250 | public static let utility = level(for: .utilityWindow) 251 | public static let assistiveTechHigh = level(for: .assistiveTechHighWindow) 252 | public static let cursor = level(for: .cursorWindow) 253 | 254 | public static let minimum = level(for: .minimumWindow) 255 | public static let maximum = level(for: .maximumWindow) 256 | } 257 | 258 | 259 | enum SSApp { 260 | static let url = Bundle.main.bundleURL 261 | 262 | static func quit() { 263 | NSApp.terminate(nil) 264 | } 265 | 266 | static func relaunch() { 267 | let configuration = NSWorkspace.OpenConfiguration() 268 | configuration.createsNewApplicationInstance = true 269 | 270 | NSWorkspace.shared.openApplication(at: url, configuration: configuration) { _, error in 271 | DispatchQueue.main.async { 272 | if let error = error { 273 | NSApp.presentError(error) 274 | return 275 | } 276 | 277 | quit() 278 | } 279 | } 280 | } 281 | } 282 | 283 | 284 | extension NSScreen { 285 | // Returns a publisher that sends updates when anything related to screens change. 286 | // This includes screens being added/removed, resolution change, and the screen frame changing (dock and menu bar being toggled). 287 | static var publisher: AnyPublisher { 288 | Publishers.Merge( 289 | NotificationCenter.default.publisher(for: NSApplication.didChangeScreenParametersNotification), 290 | // We use a wake up notification as the screen setup might have changed during sleep. For example, a screen could have been unplugged. 291 | NSWorkspace.shared.notificationCenter.publisher(for: NSWorkspace.didWakeNotification) 292 | ) 293 | .map { _ in } 294 | .eraseToAnyPublisher() 295 | } 296 | } 297 | 298 | 299 | extension Collection where Element == DefaultsObservation { 300 | @discardableResult 301 | func tieAllToLifetime(of weaklyHeldObject: AnyObject) -> Self { 302 | for observation in self { 303 | observation.tieToLifetime(of: weaklyHeldObject) 304 | } 305 | return self 306 | } 307 | } 308 | 309 | extension Defaults { 310 | static func tiedToLifetime(of weaklyHeldObject: AnyObject, @ArrayBuilder _ observations: () -> [DefaultsObservation]) { 311 | observations().tieAllToLifetime(of: weaklyHeldObject) 312 | } 313 | } 314 | 315 | 316 | @resultBuilder 317 | enum ArrayBuilder { 318 | static func buildBlock(_ elements: T...) -> [T] { elements } 319 | } 320 | 321 | 322 | extension NSStatusBarButton { 323 | private var buttonCell: NSButtonCell? { cell as? NSButtonCell } 324 | 325 | /** 326 | Whether the status bar button is prevented from (blue) highlighting on click. 327 | 328 | The default is `false`. 329 | 330 | Can be useful if clicking the status bar button triggers an action instead of opening a menu/popover. 331 | */ 332 | var preventsHighlight: Bool { 333 | get { buttonCell?.highlightsBy.isEmpty ?? false } 334 | set { 335 | buttonCell?.highlightsBy = newValue ? [] : [.changeBackgroundCellMask] 336 | } 337 | } 338 | } 339 | 340 | 341 | /// Convenience for opening URLs. 342 | extension URL { 343 | func open() { 344 | NSWorkspace.shared.open(self) 345 | } 346 | } 347 | 348 | extension String { 349 | /** 350 | ``` 351 | "https://sindresorhus.com".openUrl() 352 | ``` 353 | */ 354 | func openUrl() { 355 | URL(string: self)?.open() 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /Touch Bar Simulator/main.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | 3 | private let app = NSApplication.shared 4 | private let delegate = AppDelegate() 5 | app.delegate = delegate 6 | app.run() 7 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NAME='Touch Bar Simulator' 4 | 5 | 6 | rm -r Release 2>/dev/null 7 | 8 | xcodebuild archive \ 9 | -scheme "$NAME" \ 10 | -archivePath Release/App.xcarchive 11 | 12 | xcodebuild \ 13 | -exportArchive \ 14 | -archivePath Release/App.xcarchive \ 15 | -exportOptionsPlist export-options.plist \ 16 | -exportPath Release 17 | 18 | cd Release 19 | rm -r App.xcarchive 20 | 21 | # Prerequisite: npm i -g create-dmg 22 | create-dmg "${NAME}.app" 23 | -------------------------------------------------------------------------------- /export-options.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | teamID 6 | YG56YK5RN5 7 | method 8 | developer-id 9 | 10 | 11 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Sindre Sorhus (https://sindresorhus.com) 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Touch Bar Simulator [](https://github.com/sindresorhus/touch-bar-simulator/releases/latest) 2 | 3 | > Use the Touch Bar on any Mac 4 | 5 | > [!WARNING] 6 | > This app is discontinued as it no longer works because of changes in macOS. 7 | 8 | Launch the Touch Bar simulator from anywhere without needing to have Xcode installed, whereas Apple requires you to launch it from inside Xcode. It also comes with a handy transparency slider, a screenshot button, and a menu bar icon and system service to toggle the Touch Bar with a click or keyboard shortcut. 9 | 10 | 11 | 12 | Clicking the menu bar icon toggles the Touch Bar window. 13 | 14 | Right-clicking or option-clicking the menu bar icon displays a menu with options to dock the window to the top or bottom of the screen, make it show on all desktops at once, access toolbar features in docked mode, automatically show and hide the Touch Bar, or quit the app. 15 | 16 | 17 | 18 | ## Getting started 19 | 20 | #### [Download the latest release](https://github.com/sindresorhus/touch-bar-simulator/releases/latest) 21 | 22 | Or install it with [Homebrew-Cask](https://caskroom.github.io): 23 | 24 | ```sh 25 | brew install touch-bar-simulator 26 | ``` 27 | 28 | *Requires macOS 12 or later.* 29 | 30 | ## Screenshot 31 | 32 | You can capture a screenshot of the Touch Bar by either: 33 | 34 | 1. Clicking the screenshot button in the Touch Bar window or options menu which saves it to `~/Desktop`. 35 | 2. Pressing ⇧⌘6 which saves it to `~/Desktop`. 36 | 3. Pressing ⌃⇧⌘6 which saves it to the clipboard. 37 | 38 | ## FAQ 39 | 40 | ### Clicking in the simulator window is not working 41 | 42 | Go to “System Settings › Privacy & Security › Accessibility“ and ensure “Touch Bar Simulator.app“ is checked. If it's already checked, try unchecking and checking it again. 43 | 44 | ### Why is this not on the App Store? 45 | 46 | Apple would never allow it as it uses private APIs. 47 | 48 | ### Can I set a keyboard shortcut? 49 | 50 | Right-click or option-click the menu bar icon, select “Keyboard Shortcuts…”, and add your shortcut. 51 | 52 | ### Can I contribute localizations? 53 | 54 | No, we're not interested in localizing the app. 55 | 56 | ### How does this work? 57 | 58 | ~~In short, it exposes the Touch Bar simulator from inside Xcode as a standalone app with added features. I [class-dumped](https://github.com/nygard/class-dump) a private Xcode framework and used that to expose a private class to get a reference to the Touch Bar window controller. I then launch that window and add a screenshot button to it. I've bundled the required private frameworks to make it work without Xcode. That's why the binary is so big.~~ 59 | 60 | Xcode 10 moved the required private symbols needed to trigger the Touch Bar simulator into the main IDEKit framework, which has a lot of dependencies on its own. I managed to get it working by including all those frameworks, but the app ended up being 700 MB... I then went back to the drawing board. I discovered a way to communicate with the Touch Bar simulator directly. The result of this is a faster and more stable app. 61 | 62 | ## Build 63 | 64 | ```sh 65 | ./build 66 | ``` 67 | -------------------------------------------------------------------------------- /screenshot-menu-bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/screenshot-menu-bar.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sindresorhus/touch-bar-simulator/d5d05fec33c0d3bc07e7eb0e8d8970b7c709579c/screenshot.png --------------------------------------------------------------------------------