├── .gitignore ├── BuildTools ├── Empty.swift ├── Package.resolved └── Package.swift ├── DeskPad.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcuserdata │ └── bastianandelefski.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── DeskPad ├── AppDelegate.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-1024.png │ │ ├── Icon-128.png │ │ ├── Icon-16.png │ │ ├── Icon-256.png │ │ ├── Icon-257.png │ │ ├── Icon-32.png │ │ ├── Icon-33.png │ │ ├── Icon-512.png │ │ ├── Icon-513.png │ │ └── Icon-64.png │ ├── Contents.json │ ├── TitleBarActive.colorset │ │ └── Contents.json │ └── TitleBarInactive.colorset │ │ └── Contents.json ├── Backend │ ├── AppState.swift │ ├── MouseLocation │ │ ├── MouseLocationSideEffect.swift │ │ └── MouseLocationState.swift │ ├── ScreenConfiguration │ │ ├── ScreenConfigurationSideEffect.swift │ │ └── ScreenConfigurationState.swift │ ├── SideEffectsMiddleware.swift │ └── Store.swift ├── CGVirtualDisplayPrivate.h ├── DeskPad-Bridging-Header.h ├── DeskPad.entitlements ├── Frontend │ └── Screen │ │ ├── ScreenViewController.swift │ │ └── ScreenViewData.swift ├── Helpers │ └── NSScreen+Extensions.swift ├── SubscriberViewController.swift └── main.swift ├── Icon └── DeskPad.pxd ├── LICENSE.md ├── README.md ├── demonstration.gif └── screenshot.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | BuildTools/.build 2 | BuildTools/.swiftpm 3 | xcuserdata/ 4 | -------------------------------------------------------------------------------- /BuildTools/Empty.swift: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stengo/DeskPad/f9efab4895ca399811543c4f24577a443d400e66/BuildTools/Empty.swift -------------------------------------------------------------------------------- /BuildTools/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "SwiftFormat", 6 | "repositoryURL": "https://github.com/nicklockwood/SwiftFormat", 7 | "state": { 8 | "branch": null, 9 | "revision": "16e7dd37937af0f9adf7d8cfb35e97146ce1875f", 10 | "version": "0.49.7" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /BuildTools/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "BuildTools", 6 | platforms: [.macOS(.v10_11)], 7 | dependencies: [ 8 | .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.47.12"), 9 | ], 10 | targets: [.target(name: "BuildTools", path: "")] 11 | ) 12 | -------------------------------------------------------------------------------- /DeskPad.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6D2F1482280C1F9200A3A2E5 /* ReSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 6D2F1481280C1F9200A3A2E5 /* ReSwift */; }; 11 | 6D2F1486280C20C800A3A2E5 /* SubscriberViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2F1485280C20C800A3A2E5 /* SubscriberViewController.swift */; }; 12 | 6D2F148A280C20D000A3A2E5 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2F1487280C20D000A3A2E5 /* AppState.swift */; }; 13 | 6D2F148B280C20D000A3A2E5 /* SideEffectsMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2F1488280C20D000A3A2E5 /* SideEffectsMiddleware.swift */; }; 14 | 6D2F148C280C20D000A3A2E5 /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2F1489280C20D000A3A2E5 /* Store.swift */; }; 15 | 6D2F148E280C211E00A3A2E5 /* ScreenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2F148D280C211E00A3A2E5 /* ScreenViewController.swift */; }; 16 | 6D41B09F2879FAB6007CEB2F /* MouseLocationSideEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D41B09E2879FAB6007CEB2F /* MouseLocationSideEffect.swift */; }; 17 | 6D41B0A12879FABE007CEB2F /* MouseLocationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D41B0A02879FABE007CEB2F /* MouseLocationState.swift */; }; 18 | 6D41B0A42879FBA8007CEB2F /* ScreenViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D41B0A32879FBA8007CEB2F /* ScreenViewData.swift */; }; 19 | 6D68E1AF287ABB9900CD574A /* ScreenConfigurationSideEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D68E1AE287ABB9900CD574A /* ScreenConfigurationSideEffect.swift */; }; 20 | 6D68E1B2287ABDB900CD574A /* NSScreen+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D68E1B1287ABDB900CD574A /* NSScreen+Extensions.swift */; }; 21 | 6D68E1B4287ABFC800CD574A /* ScreenConfigurationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D68E1B3287ABFC800CD574A /* ScreenConfigurationState.swift */; }; 22 | 6DC044522801877F00281728 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DC044512801877F00281728 /* AppDelegate.swift */; }; 23 | 6DC044562801878100281728 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6DC044552801878100281728 /* Assets.xcassets */; }; 24 | 6DC04461280191EB00281728 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DC04460280191EB00281728 /* main.swift */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 6D2F1485280C20C800A3A2E5 /* SubscriberViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriberViewController.swift; sourceTree = ""; }; 29 | 6D2F1487280C20D000A3A2E5 /* AppState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; 30 | 6D2F1488280C20D000A3A2E5 /* SideEffectsMiddleware.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SideEffectsMiddleware.swift; sourceTree = ""; }; 31 | 6D2F1489280C20D000A3A2E5 /* Store.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = ""; }; 32 | 6D2F148D280C211E00A3A2E5 /* ScreenViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenViewController.swift; sourceTree = ""; }; 33 | 6D36BEB92801A39200EAB869 /* CGVirtualDisplayPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CGVirtualDisplayPrivate.h; sourceTree = ""; }; 34 | 6D36BEBA2801A40600EAB869 /* DeskPad-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "DeskPad-Bridging-Header.h"; sourceTree = ""; }; 35 | 6D41B09E2879FAB6007CEB2F /* MouseLocationSideEffect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MouseLocationSideEffect.swift; sourceTree = ""; }; 36 | 6D41B0A02879FABE007CEB2F /* MouseLocationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MouseLocationState.swift; sourceTree = ""; }; 37 | 6D41B0A32879FBA8007CEB2F /* ScreenViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenViewData.swift; sourceTree = ""; }; 38 | 6D68E1AE287ABB9900CD574A /* ScreenConfigurationSideEffect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenConfigurationSideEffect.swift; sourceTree = ""; }; 39 | 6D68E1B1287ABDB900CD574A /* NSScreen+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSScreen+Extensions.swift"; sourceTree = ""; }; 40 | 6D68E1B3287ABFC800CD574A /* ScreenConfigurationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenConfigurationState.swift; sourceTree = ""; }; 41 | 6DC0444E2801877F00281728 /* DeskPad.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DeskPad.app; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 6DC044512801877F00281728 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 43 | 6DC044552801878100281728 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 44 | 6DC0445A2801878100281728 /* DeskPad.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DeskPad.entitlements; sourceTree = ""; }; 45 | 6DC04460280191EB00281728 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 46 | /* End PBXFileReference section */ 47 | 48 | /* Begin PBXFrameworksBuildPhase section */ 49 | 6DC0444B2801877F00281728 /* Frameworks */ = { 50 | isa = PBXFrameworksBuildPhase; 51 | buildActionMask = 2147483647; 52 | files = ( 53 | 6D2F1482280C1F9200A3A2E5 /* ReSwift in Frameworks */, 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | /* End PBXFrameworksBuildPhase section */ 58 | 59 | /* Begin PBXGroup section */ 60 | 6D2F1483280C201B00A3A2E5 /* Backend */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 6D2F1487280C20D000A3A2E5 /* AppState.swift */, 64 | 6D2F1488280C20D000A3A2E5 /* SideEffectsMiddleware.swift */, 65 | 6D2F1489280C20D000A3A2E5 /* Store.swift */, 66 | 6D68E1AD287ABB6F00CD574A /* ScreenConfiguration */, 67 | 6D41B09D2879FA87007CEB2F /* MouseLocation */, 68 | ); 69 | path = Backend; 70 | sourceTree = ""; 71 | }; 72 | 6D2F1484280C202700A3A2E5 /* Frontend */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 6D41B0A22879FB88007CEB2F /* Screen */, 76 | ); 77 | path = Frontend; 78 | sourceTree = ""; 79 | }; 80 | 6D41B09D2879FA87007CEB2F /* MouseLocation */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 6D41B09E2879FAB6007CEB2F /* MouseLocationSideEffect.swift */, 84 | 6D41B0A02879FABE007CEB2F /* MouseLocationState.swift */, 85 | ); 86 | path = MouseLocation; 87 | sourceTree = ""; 88 | }; 89 | 6D41B0A22879FB88007CEB2F /* Screen */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 6D2F148D280C211E00A3A2E5 /* ScreenViewController.swift */, 93 | 6D41B0A32879FBA8007CEB2F /* ScreenViewData.swift */, 94 | ); 95 | path = Screen; 96 | sourceTree = ""; 97 | }; 98 | 6D68E1AD287ABB6F00CD574A /* ScreenConfiguration */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 6D68E1AE287ABB9900CD574A /* ScreenConfigurationSideEffect.swift */, 102 | 6D68E1B3287ABFC800CD574A /* ScreenConfigurationState.swift */, 103 | ); 104 | path = ScreenConfiguration; 105 | sourceTree = ""; 106 | }; 107 | 6D68E1B0287ABDAB00CD574A /* Helpers */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 6D68E1B1287ABDB900CD574A /* NSScreen+Extensions.swift */, 111 | ); 112 | path = Helpers; 113 | sourceTree = ""; 114 | }; 115 | 6DC044452801877F00281728 = { 116 | isa = PBXGroup; 117 | children = ( 118 | 6DC044502801877F00281728 /* DeskPad */, 119 | 6DC0444F2801877F00281728 /* Products */, 120 | ); 121 | sourceTree = ""; 122 | }; 123 | 6DC0444F2801877F00281728 /* Products */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 6DC0444E2801877F00281728 /* DeskPad.app */, 127 | ); 128 | name = Products; 129 | sourceTree = ""; 130 | }; 131 | 6DC044502801877F00281728 /* DeskPad */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 6DC04460280191EB00281728 /* main.swift */, 135 | 6D2F1485280C20C800A3A2E5 /* SubscriberViewController.swift */, 136 | 6DC044512801877F00281728 /* AppDelegate.swift */, 137 | 6D2F1483280C201B00A3A2E5 /* Backend */, 138 | 6D2F1484280C202700A3A2E5 /* Frontend */, 139 | 6D68E1B0287ABDAB00CD574A /* Helpers */, 140 | 6D36BEBA2801A40600EAB869 /* DeskPad-Bridging-Header.h */, 141 | 6D36BEB92801A39200EAB869 /* CGVirtualDisplayPrivate.h */, 142 | 6DC044552801878100281728 /* Assets.xcassets */, 143 | 6DC0445A2801878100281728 /* DeskPad.entitlements */, 144 | ); 145 | path = DeskPad; 146 | sourceTree = ""; 147 | }; 148 | /* End PBXGroup section */ 149 | 150 | /* Begin PBXNativeTarget section */ 151 | 6DC0444D2801877F00281728 /* DeskPad */ = { 152 | isa = PBXNativeTarget; 153 | buildConfigurationList = 6DC0445D2801878100281728 /* Build configuration list for PBXNativeTarget "DeskPad" */; 154 | buildPhases = ( 155 | 6D2F147F280C1C1400A3A2E5 /* Run SwiftFormat */, 156 | 6DC0444A2801877F00281728 /* Sources */, 157 | 6DC0444B2801877F00281728 /* Frameworks */, 158 | 6DC0444C2801877F00281728 /* Resources */, 159 | ); 160 | buildRules = ( 161 | ); 162 | dependencies = ( 163 | ); 164 | name = DeskPad; 165 | packageProductDependencies = ( 166 | 6D2F1481280C1F9200A3A2E5 /* ReSwift */, 167 | ); 168 | productName = DeskPad; 169 | productReference = 6DC0444E2801877F00281728 /* DeskPad.app */; 170 | productType = "com.apple.product-type.application"; 171 | }; 172 | /* End PBXNativeTarget section */ 173 | 174 | /* Begin PBXProject section */ 175 | 6DC044462801877F00281728 /* Project object */ = { 176 | isa = PBXProject; 177 | attributes = { 178 | BuildIndependentTargetsInParallel = 1; 179 | LastSwiftUpdateCheck = 1320; 180 | LastUpgradeCheck = 1320; 181 | TargetAttributes = { 182 | 6DC0444D2801877F00281728 = { 183 | CreatedOnToolsVersion = 13.2.1; 184 | }; 185 | }; 186 | }; 187 | buildConfigurationList = 6DC044492801877F00281728 /* Build configuration list for PBXProject "DeskPad" */; 188 | compatibilityVersion = "Xcode 13.0"; 189 | developmentRegion = en; 190 | hasScannedForEncodings = 0; 191 | knownRegions = ( 192 | en, 193 | Base, 194 | ); 195 | mainGroup = 6DC044452801877F00281728; 196 | packageReferences = ( 197 | 6D2F1480280C1F9200A3A2E5 /* XCRemoteSwiftPackageReference "ReSwift" */, 198 | ); 199 | productRefGroup = 6DC0444F2801877F00281728 /* Products */; 200 | projectDirPath = ""; 201 | projectRoot = ""; 202 | targets = ( 203 | 6DC0444D2801877F00281728 /* DeskPad */, 204 | ); 205 | }; 206 | /* End PBXProject section */ 207 | 208 | /* Begin PBXResourcesBuildPhase section */ 209 | 6DC0444C2801877F00281728 /* Resources */ = { 210 | isa = PBXResourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | 6DC044562801878100281728 /* Assets.xcassets in Resources */, 214 | ); 215 | runOnlyForDeploymentPostprocessing = 0; 216 | }; 217 | /* End PBXResourcesBuildPhase section */ 218 | 219 | /* Begin PBXShellScriptBuildPhase section */ 220 | 6D2F147F280C1C1400A3A2E5 /* Run SwiftFormat */ = { 221 | isa = PBXShellScriptBuildPhase; 222 | buildActionMask = 2147483647; 223 | files = ( 224 | ); 225 | inputFileListPaths = ( 226 | ); 227 | inputPaths = ( 228 | ); 229 | name = "Run SwiftFormat"; 230 | outputFileListPaths = ( 231 | ); 232 | outputPaths = ( 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | shellPath = /bin/sh; 236 | shellScript = "cd BuildTools\nSDKROOT=(xcrun --sdk macosx --show-sdk-path)\n#swift package update #Uncomment this line temporarily to update the version used to the latest matching your BuildTools/Package.swift file\nswift run -c release swiftformat \"$SRCROOT\" --swiftversion \"5\"\n"; 237 | }; 238 | /* End PBXShellScriptBuildPhase section */ 239 | 240 | /* Begin PBXSourcesBuildPhase section */ 241 | 6DC0444A2801877F00281728 /* Sources */ = { 242 | isa = PBXSourcesBuildPhase; 243 | buildActionMask = 2147483647; 244 | files = ( 245 | 6D68E1B4287ABFC800CD574A /* ScreenConfigurationState.swift in Sources */, 246 | 6D68E1B2287ABDB900CD574A /* NSScreen+Extensions.swift in Sources */, 247 | 6D2F148C280C20D000A3A2E5 /* Store.swift in Sources */, 248 | 6D2F1486280C20C800A3A2E5 /* SubscriberViewController.swift in Sources */, 249 | 6D68E1AF287ABB9900CD574A /* ScreenConfigurationSideEffect.swift in Sources */, 250 | 6D41B0A12879FABE007CEB2F /* MouseLocationState.swift in Sources */, 251 | 6D2F148A280C20D000A3A2E5 /* AppState.swift in Sources */, 252 | 6D2F148B280C20D000A3A2E5 /* SideEffectsMiddleware.swift in Sources */, 253 | 6D41B09F2879FAB6007CEB2F /* MouseLocationSideEffect.swift in Sources */, 254 | 6D41B0A42879FBA8007CEB2F /* ScreenViewData.swift in Sources */, 255 | 6D2F148E280C211E00A3A2E5 /* ScreenViewController.swift in Sources */, 256 | 6DC044522801877F00281728 /* AppDelegate.swift in Sources */, 257 | 6DC04461280191EB00281728 /* main.swift in Sources */, 258 | ); 259 | runOnlyForDeploymentPostprocessing = 0; 260 | }; 261 | /* End PBXSourcesBuildPhase section */ 262 | 263 | /* Begin XCBuildConfiguration section */ 264 | 6DC0445B2801878100281728 /* Debug */ = { 265 | isa = XCBuildConfiguration; 266 | buildSettings = { 267 | ALWAYS_SEARCH_USER_PATHS = NO; 268 | CLANG_ANALYZER_NONNULL = YES; 269 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 270 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 271 | CLANG_CXX_LIBRARY = "libc++"; 272 | CLANG_ENABLE_MODULES = YES; 273 | CLANG_ENABLE_OBJC_ARC = YES; 274 | CLANG_ENABLE_OBJC_WEAK = YES; 275 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 276 | CLANG_WARN_BOOL_CONVERSION = YES; 277 | CLANG_WARN_COMMA = YES; 278 | CLANG_WARN_CONSTANT_CONVERSION = YES; 279 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 280 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 281 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 282 | CLANG_WARN_EMPTY_BODY = YES; 283 | CLANG_WARN_ENUM_CONVERSION = YES; 284 | CLANG_WARN_INFINITE_RECURSION = YES; 285 | CLANG_WARN_INT_CONVERSION = YES; 286 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 287 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 288 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 289 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 290 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 291 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 292 | CLANG_WARN_STRICT_PROTOTYPES = YES; 293 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 294 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 295 | CLANG_WARN_UNREACHABLE_CODE = YES; 296 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 297 | COPY_PHASE_STRIP = NO; 298 | DEBUG_INFORMATION_FORMAT = dwarf; 299 | ENABLE_STRICT_OBJC_MSGSEND = YES; 300 | ENABLE_TESTABILITY = YES; 301 | GCC_C_LANGUAGE_STANDARD = gnu11; 302 | GCC_DYNAMIC_NO_PIC = NO; 303 | GCC_NO_COMMON_BLOCKS = YES; 304 | GCC_OPTIMIZATION_LEVEL = 0; 305 | GCC_PREPROCESSOR_DEFINITIONS = ( 306 | "DEBUG=1", 307 | "$(inherited)", 308 | ); 309 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 310 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 311 | GCC_WARN_UNDECLARED_SELECTOR = YES; 312 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 313 | GCC_WARN_UNUSED_FUNCTION = YES; 314 | GCC_WARN_UNUSED_VARIABLE = YES; 315 | MACOSX_DEPLOYMENT_TARGET = 13.0; 316 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 317 | MTL_FAST_MATH = YES; 318 | ONLY_ACTIVE_ARCH = YES; 319 | SDKROOT = macosx; 320 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 321 | SWIFT_OBJC_BRIDGING_HEADER = "DeskPad/DeskPad-Bridging-Header.h"; 322 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 323 | }; 324 | name = Debug; 325 | }; 326 | 6DC0445C2801878100281728 /* Release */ = { 327 | isa = XCBuildConfiguration; 328 | buildSettings = { 329 | ALWAYS_SEARCH_USER_PATHS = NO; 330 | CLANG_ANALYZER_NONNULL = YES; 331 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 332 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 333 | CLANG_CXX_LIBRARY = "libc++"; 334 | CLANG_ENABLE_MODULES = YES; 335 | CLANG_ENABLE_OBJC_ARC = YES; 336 | CLANG_ENABLE_OBJC_WEAK = YES; 337 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 338 | CLANG_WARN_BOOL_CONVERSION = YES; 339 | CLANG_WARN_COMMA = YES; 340 | CLANG_WARN_CONSTANT_CONVERSION = YES; 341 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 342 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 343 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 344 | CLANG_WARN_EMPTY_BODY = YES; 345 | CLANG_WARN_ENUM_CONVERSION = YES; 346 | CLANG_WARN_INFINITE_RECURSION = YES; 347 | CLANG_WARN_INT_CONVERSION = YES; 348 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 349 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 350 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 351 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 352 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 353 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 354 | CLANG_WARN_STRICT_PROTOTYPES = YES; 355 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 356 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 357 | CLANG_WARN_UNREACHABLE_CODE = YES; 358 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 359 | COPY_PHASE_STRIP = NO; 360 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 361 | ENABLE_NS_ASSERTIONS = NO; 362 | ENABLE_STRICT_OBJC_MSGSEND = YES; 363 | GCC_C_LANGUAGE_STANDARD = gnu11; 364 | GCC_NO_COMMON_BLOCKS = YES; 365 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 366 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 367 | GCC_WARN_UNDECLARED_SELECTOR = YES; 368 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 369 | GCC_WARN_UNUSED_FUNCTION = YES; 370 | GCC_WARN_UNUSED_VARIABLE = YES; 371 | MACOSX_DEPLOYMENT_TARGET = 13.0; 372 | MTL_ENABLE_DEBUG_INFO = NO; 373 | MTL_FAST_MATH = YES; 374 | SDKROOT = macosx; 375 | SWIFT_COMPILATION_MODE = wholemodule; 376 | SWIFT_OBJC_BRIDGING_HEADER = "DeskPad/DeskPad-Bridging-Header.h"; 377 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 378 | }; 379 | name = Release; 380 | }; 381 | 6DC0445E2801878100281728 /* Debug */ = { 382 | isa = XCBuildConfiguration; 383 | buildSettings = { 384 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 385 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 386 | CODE_SIGN_ENTITLEMENTS = DeskPad/DeskPad.entitlements; 387 | CODE_SIGN_STYLE = Automatic; 388 | COMBINE_HIDPI_IMAGES = YES; 389 | CURRENT_PROJECT_VERSION = 6; 390 | DEVELOPMENT_TEAM = TYPC962S4N; 391 | ENABLE_HARDENED_RUNTIME = YES; 392 | GENERATE_INFOPLIST_FILE = YES; 393 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 394 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 395 | LD_RUNPATH_SEARCH_PATHS = ( 396 | "$(inherited)", 397 | "@executable_path/../Frameworks", 398 | ); 399 | MARKETING_VERSION = 1.3.2; 400 | PRODUCT_BUNDLE_IDENTIFIER = com.stengo.DeskPad; 401 | PRODUCT_NAME = "$(TARGET_NAME)"; 402 | SWIFT_EMIT_LOC_STRINGS = YES; 403 | SWIFT_VERSION = 5.0; 404 | }; 405 | name = Debug; 406 | }; 407 | 6DC0445F2801878100281728 /* Release */ = { 408 | isa = XCBuildConfiguration; 409 | buildSettings = { 410 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 411 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 412 | CODE_SIGN_ENTITLEMENTS = DeskPad/DeskPad.entitlements; 413 | CODE_SIGN_STYLE = Automatic; 414 | COMBINE_HIDPI_IMAGES = YES; 415 | CURRENT_PROJECT_VERSION = 6; 416 | DEVELOPMENT_TEAM = TYPC962S4N; 417 | ENABLE_HARDENED_RUNTIME = YES; 418 | GENERATE_INFOPLIST_FILE = YES; 419 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 420 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 421 | LD_RUNPATH_SEARCH_PATHS = ( 422 | "$(inherited)", 423 | "@executable_path/../Frameworks", 424 | ); 425 | MARKETING_VERSION = 1.3.2; 426 | PRODUCT_BUNDLE_IDENTIFIER = com.stengo.DeskPad; 427 | PRODUCT_NAME = "$(TARGET_NAME)"; 428 | SWIFT_EMIT_LOC_STRINGS = YES; 429 | SWIFT_VERSION = 5.0; 430 | }; 431 | name = Release; 432 | }; 433 | /* End XCBuildConfiguration section */ 434 | 435 | /* Begin XCConfigurationList section */ 436 | 6DC044492801877F00281728 /* Build configuration list for PBXProject "DeskPad" */ = { 437 | isa = XCConfigurationList; 438 | buildConfigurations = ( 439 | 6DC0445B2801878100281728 /* Debug */, 440 | 6DC0445C2801878100281728 /* Release */, 441 | ); 442 | defaultConfigurationIsVisible = 0; 443 | defaultConfigurationName = Release; 444 | }; 445 | 6DC0445D2801878100281728 /* Build configuration list for PBXNativeTarget "DeskPad" */ = { 446 | isa = XCConfigurationList; 447 | buildConfigurations = ( 448 | 6DC0445E2801878100281728 /* Debug */, 449 | 6DC0445F2801878100281728 /* Release */, 450 | ); 451 | defaultConfigurationIsVisible = 0; 452 | defaultConfigurationName = Release; 453 | }; 454 | /* End XCConfigurationList section */ 455 | 456 | /* Begin XCRemoteSwiftPackageReference section */ 457 | 6D2F1480280C1F9200A3A2E5 /* XCRemoteSwiftPackageReference "ReSwift" */ = { 458 | isa = XCRemoteSwiftPackageReference; 459 | repositoryURL = "https://github.com/ReSwift/ReSwift.git"; 460 | requirement = { 461 | kind = upToNextMajorVersion; 462 | minimumVersion = 6.0.0; 463 | }; 464 | }; 465 | /* End XCRemoteSwiftPackageReference section */ 466 | 467 | /* Begin XCSwiftPackageProductDependency section */ 468 | 6D2F1481280C1F9200A3A2E5 /* ReSwift */ = { 469 | isa = XCSwiftPackageProductDependency; 470 | package = 6D2F1480280C1F9200A3A2E5 /* XCRemoteSwiftPackageReference "ReSwift" */; 471 | productName = ReSwift; 472 | }; 473 | /* End XCSwiftPackageProductDependency section */ 474 | }; 475 | rootObject = 6DC044462801877F00281728 /* Project object */; 476 | } 477 | -------------------------------------------------------------------------------- /DeskPad.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DeskPad.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DeskPad.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "ReSwift", 6 | "repositoryURL": "https://github.com/ReSwift/ReSwift.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "96146a29f394ae4c79be025fcec194e5b0d9c3b6", 10 | "version": "6.1.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /DeskPad.xcodeproj/xcuserdata/bastianandelefski.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DeskPad.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /DeskPad/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import ReSwift 3 | 4 | enum AppDelegateAction: Action { 5 | case didFinishLaunching 6 | } 7 | 8 | class AppDelegate: NSObject, NSApplicationDelegate { 9 | var window: NSWindow! 10 | 11 | func applicationDidFinishLaunching(_: Notification) { 12 | let viewController = ScreenViewController() 13 | window = NSWindow(contentViewController: viewController) 14 | window.delegate = viewController 15 | window.title = "DeskPad" 16 | window.makeKeyAndOrderFront(nil) 17 | window.titlebarAppearsTransparent = true 18 | window.isMovableByWindowBackground = true 19 | window.titleVisibility = .hidden 20 | window.backgroundColor = .white 21 | window.contentMinSize = CGSize(width: 400, height: 300) 22 | window.contentMaxSize = CGSize(width: 3840, height: 2160) 23 | window.styleMask.insert(.resizable) 24 | window.collectionBehavior.insert(.fullScreenNone) 25 | 26 | let mainMenu = NSMenu() 27 | let mainMenuItem = NSMenuItem() 28 | let subMenu = NSMenu(title: "MainMenu") 29 | let quitMenuItem = NSMenuItem( 30 | title: "Quit", 31 | action: #selector(NSApp.terminate), 32 | keyEquivalent: "q" 33 | ) 34 | subMenu.addItem(quitMenuItem) 35 | mainMenuItem.submenu = subMenu 36 | mainMenu.items = [mainMenuItem] 37 | NSApplication.shared.mainMenu = mainMenu 38 | 39 | store.dispatch(AppDelegateAction.didFinishLaunching) 40 | } 41 | 42 | func applicationShouldTerminateAfterLastWindowClosed(_: NSApplication) -> Bool { 43 | return true 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /DeskPad/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /DeskPad/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon-16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "Icon-32.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "Icon-33.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "Icon-64.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "Icon-128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "Icon-256.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "Icon-257.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "Icon-512.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "Icon-513.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "Icon-1024.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stengo/DeskPad/f9efab4895ca399811543c4f24577a443d400e66/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-1024.png -------------------------------------------------------------------------------- /DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stengo/DeskPad/f9efab4895ca399811543c4f24577a443d400e66/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-128.png -------------------------------------------------------------------------------- /DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stengo/DeskPad/f9efab4895ca399811543c4f24577a443d400e66/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-16.png -------------------------------------------------------------------------------- /DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stengo/DeskPad/f9efab4895ca399811543c4f24577a443d400e66/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-256.png -------------------------------------------------------------------------------- /DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-257.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stengo/DeskPad/f9efab4895ca399811543c4f24577a443d400e66/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-257.png -------------------------------------------------------------------------------- /DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stengo/DeskPad/f9efab4895ca399811543c4f24577a443d400e66/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-32.png -------------------------------------------------------------------------------- /DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stengo/DeskPad/f9efab4895ca399811543c4f24577a443d400e66/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-33.png -------------------------------------------------------------------------------- /DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stengo/DeskPad/f9efab4895ca399811543c4f24577a443d400e66/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-512.png -------------------------------------------------------------------------------- /DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-513.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stengo/DeskPad/f9efab4895ca399811543c4f24577a443d400e66/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-513.png -------------------------------------------------------------------------------- /DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stengo/DeskPad/f9efab4895ca399811543c4f24577a443d400e66/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-64.png -------------------------------------------------------------------------------- /DeskPad/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DeskPad/Assets.xcassets/TitleBarActive.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "platform" : "osx", 6 | "reference" : "selectedContentBackgroundColor" 7 | }, 8 | "idiom" : "universal" 9 | }, 10 | { 11 | "appearances" : [ 12 | { 13 | "appearance" : "luminosity", 14 | "value" : "dark" 15 | } 16 | ], 17 | "color" : { 18 | "platform" : "osx", 19 | "reference" : "selectedContentBackgroundColor" 20 | }, 21 | "idiom" : "universal" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /DeskPad/Assets.xcassets/TitleBarInactive.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "platform" : "osx", 6 | "reference" : "windowBackgroundColor" 7 | }, 8 | "idiom" : "universal" 9 | }, 10 | { 11 | "appearances" : [ 12 | { 13 | "appearance" : "luminosity", 14 | "value" : "dark" 15 | } 16 | ], 17 | "color" : { 18 | "platform" : "osx", 19 | "reference" : "windowBackgroundColor" 20 | }, 21 | "idiom" : "universal" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /DeskPad/Backend/AppState.swift: -------------------------------------------------------------------------------- 1 | import ReSwift 2 | 3 | struct AppState: Equatable { 4 | let mouseLocationState: MouseLocationState 5 | let screenConfigurationState: ScreenConfigurationState 6 | 7 | static var initialState: AppState { 8 | return AppState( 9 | mouseLocationState: .initialState, 10 | screenConfigurationState: .initialState 11 | ) 12 | } 13 | } 14 | 15 | func appReducer(action: Action, state: AppState?) -> AppState { 16 | let state = state ?? .initialState 17 | 18 | return AppState( 19 | mouseLocationState: mouseLocationReducer(action: action, state: state.mouseLocationState), 20 | screenConfigurationState: screenConfigurationReducer(action: action, state: state.screenConfigurationState) 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /DeskPad/Backend/MouseLocation/MouseLocationSideEffect.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import ReSwift 3 | 4 | private var timer: Timer? 5 | 6 | enum MouseLocationAction: Action { 7 | case located(isWithinScreen: Bool) 8 | case requestMove(toPoint: NSPoint) 9 | } 10 | 11 | func mouseLocationSideEffect() -> SideEffect { 12 | return { action, dispatch, getState in 13 | if timer == nil { 14 | timer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { _ in 15 | let mouseLocation = NSEvent.mouseLocation 16 | let screens = NSScreen.screens 17 | let screenContainingMouse = (screens.first { NSMouseInRect(mouseLocation, $0.frame, false) }) 18 | let isWithinScreen = screenContainingMouse?.displayID == getState()?.screenConfigurationState.displayID 19 | dispatch(MouseLocationAction.located(isWithinScreen: isWithinScreen)) 20 | } 21 | } 22 | switch action { 23 | case let MouseLocationAction.requestMove(point): 24 | guard let displayID = getState()?.screenConfigurationState.displayID else { 25 | return 26 | } 27 | CGDisplayMoveCursorToPoint(displayID, point) 28 | default: 29 | return 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /DeskPad/Backend/MouseLocation/MouseLocationState.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import ReSwift 3 | 4 | struct MouseLocationState: Equatable { 5 | let isWithinScreen: Bool 6 | 7 | static var initialState: MouseLocationState { 8 | return MouseLocationState(isWithinScreen: false) 9 | } 10 | } 11 | 12 | func mouseLocationReducer(action: Action, state: MouseLocationState) -> MouseLocationState { 13 | switch action { 14 | case let MouseLocationAction.located(isWithinScreen): 15 | return MouseLocationState(isWithinScreen: isWithinScreen) 16 | 17 | default: 18 | return state 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DeskPad/Backend/ScreenConfiguration/ScreenConfigurationSideEffect.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import ReSwift 3 | 4 | private var isObserving = false 5 | 6 | enum ScreenConfigurationAction: Action { 7 | case set(resolution: CGSize, scaleFactor: CGFloat) 8 | } 9 | 10 | func screenConfigurationSideEffect() -> SideEffect { 11 | return { _, dispatch, getState in 12 | if isObserving == false { 13 | isObserving = true 14 | NotificationCenter.default.addObserver( 15 | forName: NSApplication.didChangeScreenParametersNotification, 16 | object: NSApplication.shared, 17 | queue: .main 18 | ) { _ in 19 | guard let screen = NSScreen.screens.first(where: { 20 | $0.displayID == getState()?.screenConfigurationState.displayID 21 | }) else { 22 | return 23 | } 24 | dispatch(ScreenConfigurationAction.set( 25 | resolution: screen.frame.size, 26 | scaleFactor: screen.backingScaleFactor 27 | )) 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /DeskPad/Backend/ScreenConfiguration/ScreenConfigurationState.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import ReSwift 3 | 4 | struct ScreenConfigurationState: Equatable { 5 | let resolution: CGSize 6 | let scaleFactor: CGFloat 7 | let displayID: CGDirectDisplayID? 8 | 9 | static var initialState: ScreenConfigurationState { 10 | return ScreenConfigurationState( 11 | resolution: .zero, 12 | scaleFactor: 1, 13 | displayID: nil 14 | ) 15 | } 16 | } 17 | 18 | func screenConfigurationReducer(action: Action, state: ScreenConfigurationState) -> ScreenConfigurationState { 19 | switch action { 20 | case let ScreenConfigurationAction.set(resolution, scaleFactor): 21 | return ScreenConfigurationState( 22 | resolution: resolution, 23 | scaleFactor: scaleFactor, 24 | displayID: state.displayID 25 | ) 26 | 27 | case let ScreenViewAction.setDisplayID(displayID): 28 | return ScreenConfigurationState( 29 | resolution: state.resolution, 30 | scaleFactor: state.scaleFactor, 31 | displayID: displayID 32 | ) 33 | 34 | default: 35 | return state 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /DeskPad/Backend/SideEffectsMiddleware.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import ReSwift 3 | 4 | typealias SideEffect = (Action, @escaping DispatchFunction, @escaping () -> AppState?) -> Void 5 | 6 | private let sideEffects: [SideEffect] = [ 7 | mouseLocationSideEffect(), 8 | screenConfigurationSideEffect(), 9 | ] 10 | 11 | let sideEffectsMiddleware: Middleware = { dispatch, getState in 12 | { originalDispatch in 13 | { action in 14 | originalDispatch(action) 15 | for sideEffect in sideEffects { 16 | sideEffect(action, dispatch, getState) 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DeskPad/Backend/Store.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import ReSwift 3 | 4 | let store = Store( 5 | reducer: appReducer, 6 | state: AppState.initialState, 7 | middleware: [ 8 | sideEffectsMiddleware, 9 | ] 10 | ) 11 | -------------------------------------------------------------------------------- /DeskPad/CGVirtualDisplayPrivate.h: -------------------------------------------------------------------------------- 1 | // 2 | // CGVirtualDisplayPrivate.h 3 | // VirtualDisplayExp 4 | // 5 | // Created by Khaos Tian on 2/17/21. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @class CGVirtualDisplayDescriptor; 14 | 15 | @interface CGVirtualDisplayMode : NSObject 16 | 17 | @property(readonly, nonatomic) CGFloat refreshRate; 18 | @property(readonly, nonatomic) NSUInteger width; 19 | @property(readonly, nonatomic) NSUInteger height; 20 | 21 | - (instancetype)initWithWidth:(NSUInteger)arg1 height:(NSUInteger)arg2 refreshRate:(CGFloat)arg3; 22 | 23 | @end 24 | 25 | @interface CGVirtualDisplaySettings : NSObject 26 | 27 | @property(retain, nonatomic) NSArray *modes; 28 | @property(nonatomic) unsigned int hiDPI; 29 | 30 | - (instancetype)init; 31 | 32 | @end 33 | 34 | @interface CGVirtualDisplay : NSObject 35 | 36 | @property(readonly, nonatomic) NSArray *modes; // @synthesize modes=_modes; 37 | @property(readonly, nonatomic) unsigned int hiDPI; // @synthesize hiDPI=_hiDPI; 38 | @property(readonly, nonatomic) CGDirectDisplayID displayID; // @synthesize displayID=_displayID; 39 | @property(readonly, nonatomic) id terminationHandler; // @synthesize terminationHandler=_terminationHandler; 40 | @property(readonly, nonatomic) dispatch_queue_t queue; // @synthesize queue=_queue; 41 | @property(readonly, nonatomic) unsigned int maxPixelsHigh; // @synthesize maxPixelsHigh=_maxPixelsHigh; 42 | @property(readonly, nonatomic) unsigned int maxPixelsWide; // @synthesize maxPixelsWide=_maxPixelsWide; 43 | @property(readonly, nonatomic) CGSize sizeInMillimeters; // @synthesize sizeInMillimeters=_sizeInMillimeters; 44 | @property(readonly, nonatomic) NSString *name; // @synthesize name=_name; 45 | @property(readonly, nonatomic) unsigned int serialNum; // @synthesize serialNum=_serialNum; 46 | @property(readonly, nonatomic) unsigned int productID; // @synthesize productID=_productID; 47 | @property(readonly, nonatomic) unsigned int vendorID; // @synthesize vendorID=_vendorID; 48 | 49 | - (instancetype)initWithDescriptor:(CGVirtualDisplayDescriptor *)arg1; 50 | - (BOOL)applySettings:(CGVirtualDisplaySettings *)arg1; 51 | 52 | @end 53 | 54 | @interface CGVirtualDisplayDescriptor : NSObject 55 | 56 | @property(retain, nonatomic) dispatch_queue_t queue; // @synthesize queue=_queue; 57 | @property(retain, nonatomic) NSString *name; // @synthesize name=_name; 58 | @property(nonatomic) unsigned int maxPixelsHigh; // @synthesize maxPixelsHigh=_maxPixelsHigh; 59 | @property(nonatomic) unsigned int maxPixelsWide; // @synthesize maxPixelsWide=_maxPixelsWide; 60 | @property(nonatomic) CGSize sizeInMillimeters; // @synthesize sizeInMillimeters=_sizeInMillimeters; 61 | @property(nonatomic) unsigned int serialNum; // @synthesize serialNum=_serialNum; 62 | @property(nonatomic) unsigned int productID; // @synthesize productID=_productID; 63 | @property(nonatomic) unsigned int vendorID; // @synthesize vendorID=_vendorID; 64 | @property(copy, nonatomic) void (^terminationHandler)(id, CGVirtualDisplay*); 65 | 66 | - (instancetype)init; 67 | - (nullable dispatch_queue_t)dispatchQueue; 68 | - (void)setDispatchQueue:(dispatch_queue_t)arg1; 69 | 70 | @end 71 | 72 | NS_ASSUME_NONNULL_END 73 | -------------------------------------------------------------------------------- /DeskPad/DeskPad-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "CGVirtualDisplayPrivate.h" 2 | -------------------------------------------------------------------------------- /DeskPad/DeskPad.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.temporary-exception.mach-lookup.global-name 10 | 11 | com.apple.VirtualDisplay 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /DeskPad/Frontend/Screen/ScreenViewController.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import ReSwift 3 | 4 | enum ScreenViewAction: Action { 5 | case setDisplayID(CGDirectDisplayID) 6 | } 7 | 8 | class ScreenViewController: SubscriberViewController, NSWindowDelegate { 9 | override func loadView() { 10 | view = NSView() 11 | view.wantsLayer = true 12 | view.addGestureRecognizer(NSClickGestureRecognizer(target: self, action: #selector(didClickOnScreen))) 13 | } 14 | 15 | private var display: CGVirtualDisplay! 16 | private var stream: CGDisplayStream? 17 | private var isWindowHighlighted = false 18 | private var previousResolution: CGSize? 19 | private var previousScaleFactor: CGFloat? 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | let descriptor = CGVirtualDisplayDescriptor() 25 | descriptor.setDispatchQueue(DispatchQueue.main) 26 | descriptor.name = "DeskPad Display" 27 | descriptor.maxPixelsWide = 3840 28 | descriptor.maxPixelsHigh = 2160 29 | descriptor.sizeInMillimeters = CGSize(width: 1600, height: 1000) 30 | descriptor.productID = 0x1234 31 | descriptor.vendorID = 0x3456 32 | descriptor.serialNum = 0x0001 33 | 34 | let display = CGVirtualDisplay(descriptor: descriptor) 35 | store.dispatch(ScreenViewAction.setDisplayID(display.displayID)) 36 | self.display = display 37 | 38 | let settings = CGVirtualDisplaySettings() 39 | settings.hiDPI = 1 40 | settings.modes = [ 41 | // 16:9 42 | CGVirtualDisplayMode(width: 3840, height: 2160, refreshRate: 60), 43 | CGVirtualDisplayMode(width: 2560, height: 1440, refreshRate: 60), 44 | CGVirtualDisplayMode(width: 1920, height: 1080, refreshRate: 60), 45 | CGVirtualDisplayMode(width: 1600, height: 900, refreshRate: 60), 46 | CGVirtualDisplayMode(width: 1366, height: 768, refreshRate: 60), 47 | CGVirtualDisplayMode(width: 1280, height: 720, refreshRate: 60), 48 | // 16:10 49 | CGVirtualDisplayMode(width: 2560, height: 1600, refreshRate: 60), 50 | CGVirtualDisplayMode(width: 1920, height: 1200, refreshRate: 60), 51 | CGVirtualDisplayMode(width: 1680, height: 1050, refreshRate: 60), 52 | CGVirtualDisplayMode(width: 1440, height: 900, refreshRate: 60), 53 | CGVirtualDisplayMode(width: 1280, height: 800, refreshRate: 60), 54 | ] 55 | display.apply(settings) 56 | } 57 | 58 | override func update(with viewData: ScreenViewData) { 59 | if viewData.isWindowHighlighted != isWindowHighlighted { 60 | isWindowHighlighted = viewData.isWindowHighlighted 61 | view.window?.backgroundColor = isWindowHighlighted 62 | ? NSColor(named: "TitleBarActive") 63 | : NSColor(named: "TitleBarInactive") 64 | if isWindowHighlighted { 65 | view.window?.orderFrontRegardless() 66 | } 67 | } 68 | 69 | if 70 | viewData.resolution != .zero, 71 | viewData.resolution != previousResolution 72 | || viewData.scaleFactor != previousScaleFactor 73 | { 74 | previousResolution = viewData.resolution 75 | previousScaleFactor = viewData.scaleFactor 76 | stream = nil 77 | view.window?.setContentSize(viewData.resolution) 78 | view.window?.contentAspectRatio = viewData.resolution 79 | view.window?.center() 80 | let stream = CGDisplayStream( 81 | dispatchQueueDisplay: display.displayID, 82 | outputWidth: Int(viewData.resolution.width * viewData.scaleFactor), 83 | outputHeight: Int(viewData.resolution.height * viewData.scaleFactor), 84 | pixelFormat: 1_111_970_369, 85 | properties: [ 86 | CGDisplayStream.showCursor: true, 87 | ] as CFDictionary, 88 | queue: .main, 89 | handler: { [weak self] _, _, frameSurface, _ in 90 | if let surface = frameSurface { 91 | self?.view.layer?.contents = surface 92 | } 93 | } 94 | ) 95 | self.stream = stream 96 | stream?.start() 97 | } 98 | } 99 | 100 | func windowWillResize(_ window: NSWindow, to frameSize: NSSize) -> NSSize { 101 | let snappingOffset: CGFloat = 30 102 | let contentSize = window.contentRect(forFrameRect: NSRect(origin: .zero, size: frameSize)).size 103 | guard 104 | let screenResolution = previousResolution, 105 | abs(contentSize.width - screenResolution.width) < snappingOffset 106 | else { 107 | return frameSize 108 | } 109 | return window.frameRect(forContentRect: NSRect(origin: .zero, size: screenResolution)).size 110 | } 111 | 112 | @objc private func didClickOnScreen(_ gestureRecognizer: NSGestureRecognizer) { 113 | guard let screenResolution = previousResolution else { 114 | return 115 | } 116 | let clickedPoint = gestureRecognizer.location(in: view) 117 | let onScreenPoint = NSPoint( 118 | x: clickedPoint.x / view.frame.width * screenResolution.width, 119 | y: (view.frame.height - clickedPoint.y) / view.frame.height * screenResolution.height 120 | ) 121 | store.dispatch(MouseLocationAction.requestMove(toPoint: onScreenPoint)) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /DeskPad/Frontend/Screen/ScreenViewData.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct ScreenViewData: ViewDataType { 4 | struct StateFragment: Equatable { 5 | let mouseLocationState: MouseLocationState 6 | let screenConfiguration: ScreenConfigurationState 7 | } 8 | 9 | static func fragment(of appState: AppState) -> StateFragment { 10 | return StateFragment( 11 | mouseLocationState: appState.mouseLocationState, 12 | screenConfiguration: appState.screenConfigurationState 13 | ) 14 | } 15 | 16 | let isWindowHighlighted: Bool 17 | let resolution: CGSize 18 | let scaleFactor: CGFloat 19 | 20 | init(for fragment: StateFragment) { 21 | isWindowHighlighted = fragment.mouseLocationState.isWithinScreen 22 | resolution = fragment.screenConfiguration.resolution 23 | scaleFactor = fragment.screenConfiguration.scaleFactor 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /DeskPad/Helpers/NSScreen+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension NSScreen { 4 | var displayID: CGDirectDisplayID { 5 | return deviceDescription[NSDeviceDescriptionKey(rawValue: "NSScreenNumber")] as! CGDirectDisplayID 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /DeskPad/SubscriberViewController.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | import ReSwift 3 | 4 | class SubscriberViewController: NSViewController, StoreSubscriber { 5 | typealias StoreSubscriberStateType = ViewData.StateFragment 6 | 7 | override func viewWillAppear() { 8 | super.viewWillAppear() 9 | 10 | store.subscribe(self) { subscription in 11 | subscription 12 | .select(ViewData.fragment(of:)) 13 | .skipRepeats() 14 | } 15 | } 16 | 17 | override func viewWillDisappear() { 18 | super.viewWillDisappear() 19 | 20 | store.unsubscribe(self) 21 | } 22 | 23 | func newState(state: ViewData.StateFragment) { 24 | DispatchQueue.main.async { [weak self] in 25 | self?.update(with: ViewData(for: state)) 26 | } 27 | } 28 | 29 | func update(with _: ViewData) { 30 | fatalError("Please override the SubscriberViewController update method.") 31 | } 32 | } 33 | 34 | protocol ViewDataType { 35 | associatedtype StateFragment: Equatable 36 | 37 | static func fragment(of appState: AppState) -> StateFragment 38 | 39 | init(for fragment: StateFragment) 40 | } 41 | -------------------------------------------------------------------------------- /DeskPad/main.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | 3 | let app = NSApplication.shared 4 | let delegate = AppDelegate() 5 | app.delegate = delegate 6 | 7 | _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) 8 | -------------------------------------------------------------------------------- /Icon/DeskPad.pxd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stengo/DeskPad/f9efab4895ca399811543c4f24577a443d400e66/Icon/DeskPad.pxd -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Bastian Andelefski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | DeskPad Icon 4 | 5 |

6 | 7 | # DeskPad 8 | A virtual monitor for screen sharing 9 | 10 |

11 | 12 | DeskPad Screenshot 13 | 14 |

15 | 16 | Certain workflows require sharing the entire screen (usually due to switching through multiple applications), but if the presenter has a much larger display than the audience it can be hard to see what is happening. 17 | 18 | DeskPad creates a virtual display that is mirrored within its application window so that you can create a dedicated, easily shareable workspace. 19 | 20 | # Installation 21 | 22 | You can either download the [latest release binary](https://github.com/Stengo/DeskPad/releases) or install via [Homebrew](https://brew.sh) by calling `brew install deskpad`. 23 | 24 | # Usage 25 | DeskPad behaves like any other display. Launching the app is equivalent to plugging in a monitor, so macOS will take care of properly arranging your windows to their previous configuration. 26 | 27 | You can change the display resolution through the system preferences and the application window will adjust accordingly. 28 | 29 | Whenever you move your mouse cursor to the virtual display, DeskPad will highlight its title bar in blue and move the application window to the front to let you know where you are. 30 | 31 |

32 | 33 | DeskPad Demonstration 34 | 35 |

36 | -------------------------------------------------------------------------------- /demonstration.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stengo/DeskPad/f9efab4895ca399811543c4f24577a443d400e66/demonstration.gif -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stengo/DeskPad/f9efab4895ca399811543c4f24577a443d400e66/screenshot.jpg --------------------------------------------------------------------------------