├── BuildTools
├── Empty.swift
├── Package.swift
└── Package.resolved
├── .gitignore
├── DeskPad
├── DeskPad-Bridging-Header.h
├── Assets.xcassets
│ ├── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── Icon-16.png
│ │ ├── Icon-32.png
│ │ ├── Icon-33.png
│ │ ├── Icon-64.png
│ │ ├── Icon-1024.png
│ │ ├── Icon-128.png
│ │ ├── Icon-256.png
│ │ ├── Icon-257.png
│ │ ├── Icon-512.png
│ │ ├── Icon-513.png
│ │ └── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── TitleBarInactive.colorset
│ │ └── Contents.json
│ └── TitleBarActive.colorset
│ │ └── Contents.json
├── main.swift
├── Backend
│ ├── Store.swift
│ ├── MouseLocation
│ │ ├── MouseLocationState.swift
│ │ └── MouseLocationSideEffect.swift
│ ├── SideEffectsMiddleware.swift
│ ├── AppState.swift
│ └── ScreenConfiguration
│ │ ├── ScreenConfigurationSideEffect.swift
│ │ └── ScreenConfigurationState.swift
├── Helpers
│ └── NSScreen+Extensions.swift
├── DeskPad.entitlements
├── Frontend
│ └── Screen
│ │ ├── ScreenViewData.swift
│ │ └── ScreenViewController.swift
├── SubscriberViewController.swift
├── AppDelegate.swift
└── CGVirtualDisplayPrivate.h
├── screenshot.jpg
├── Icon
└── DeskPad.pxd
├── demonstration.gif
├── DeskPad.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
├── xcuserdata
│ └── bastianandelefski.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── project.pbxproj
├── LICENSE.md
└── README.md
/BuildTools/Empty.swift:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | BuildTools/.build
2 | BuildTools/.swiftpm
3 | xcuserdata/
4 |
--------------------------------------------------------------------------------
/DeskPad/DeskPad-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "CGVirtualDisplayPrivate.h"
2 |
--------------------------------------------------------------------------------
/screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stengo/DeskPad/HEAD/screenshot.jpg
--------------------------------------------------------------------------------
/Icon/DeskPad.pxd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stengo/DeskPad/HEAD/Icon/DeskPad.pxd
--------------------------------------------------------------------------------
/demonstration.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stengo/DeskPad/HEAD/demonstration.gif
--------------------------------------------------------------------------------
/DeskPad/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stengo/DeskPad/HEAD/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-16.png
--------------------------------------------------------------------------------
/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stengo/DeskPad/HEAD/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-32.png
--------------------------------------------------------------------------------
/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-33.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stengo/DeskPad/HEAD/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-33.png
--------------------------------------------------------------------------------
/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stengo/DeskPad/HEAD/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-64.png
--------------------------------------------------------------------------------
/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stengo/DeskPad/HEAD/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-1024.png
--------------------------------------------------------------------------------
/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stengo/DeskPad/HEAD/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-128.png
--------------------------------------------------------------------------------
/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stengo/DeskPad/HEAD/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-256.png
--------------------------------------------------------------------------------
/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-257.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stengo/DeskPad/HEAD/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-257.png
--------------------------------------------------------------------------------
/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stengo/DeskPad/HEAD/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-512.png
--------------------------------------------------------------------------------
/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-513.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Stengo/DeskPad/HEAD/DeskPad/Assets.xcassets/AppIcon.appiconset/Icon-513.png
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/DeskPad.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/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/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/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.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
6 |
7 | # DeskPad
8 | A virtual monitor for screen sharing
9 |
10 |
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 |
36 |
--------------------------------------------------------------------------------
/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/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.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 |
--------------------------------------------------------------------------------