├── .gitignore ├── HomeConMenu.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ ├── Hide os_log.xcscheme │ └── Show os_log.xcscheme ├── HomeConMenu ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png │ ├── Contents.json │ ├── appbigicon.imageset │ │ ├── Contents.json │ │ └── appbigicon.png │ ├── homeappshot.imageset │ │ ├── Contents.json │ │ └── homeappshot.png │ ├── house.imageset │ │ ├── Contents.json │ │ └── house.svg │ ├── privacy.imageset │ │ ├── Contents.json │ │ └── privacy.jpg │ ├── shots01.imageset │ │ ├── Contents.json │ │ └── shots01.png │ ├── shots02.imageset │ │ ├── Contents.json │ │ └── shots02.png │ └── transparentIcon.imageset │ │ ├── Contents.json │ │ └── temp.png ├── Dummy.storekit ├── HomeConMenu.entitlements ├── HomeConMenuRelease.entitlements ├── Info.plist ├── Japan.storekit ├── Products.plist ├── Resources │ ├── Base.lproj │ │ ├── Acknowledgments.css │ │ ├── Acknowledgments.html │ │ └── Main.storyboard │ ├── LaunchScreen.storyboard │ ├── Localizable.xcstrings │ ├── de.lproj │ │ ├── Acknowledgments.css │ │ ├── Acknowledgments.html │ │ ├── DonationPane.strings │ │ ├── GeneralPane.strings │ │ ├── InformationPane.strings │ │ ├── LaunchView.strings │ │ ├── Localizable.strings │ │ ├── Main.strings │ │ └── ShortcutsPane.strings │ ├── ja.lproj │ │ ├── Acknowledgments.html │ │ └── Main.strings │ └── mul.lproj │ │ └── Main.xcstrings ├── Shared │ ├── AccessoryInfo.swift │ ├── AccessoryTypeBridge.swift │ ├── ActionSetInfo.swift │ ├── CharacteristicInfo.swift │ ├── CharacteristicTypeBridge.swift │ ├── Error.swift │ ├── HomeInfo.swift │ ├── InterfaceProtocols.swift │ ├── Log.swift │ ├── RoomInfo.swift │ ├── ServiceGroupInfo.swift │ ├── ServiceInfo.swift │ └── ServiceTypeBridge.swift ├── US.storekit ├── iOS │ ├── AppDelegate.swift │ ├── BaseManager+HomeKit.swift │ ├── BaseManager+mac2iOS.swift │ ├── BaseManager.swift │ ├── CameraViewController.swift │ ├── DummyViewController.swift │ ├── HomeKitExtension.swift │ ├── MonitoringNetworkState.swift │ ├── SceneDelegate.swift │ └── WebViewController.swift ├── macOS │ ├── AppleScriptManager.swift │ ├── Base.lproj │ │ ├── LaunchView.xib │ │ └── ShortcutsPane 2.xib │ ├── LaunchViewController.swift │ ├── LaunchWindowController.swift │ ├── MacOSController+iOS2Mac.swift │ ├── MacOSController.swift │ ├── MenuItem │ │ ├── ActionSetMenuItem.swift │ │ ├── CameraMenuItem.swift │ │ ├── HomeSelectMenuItem.swift │ │ ├── LightBrightnessColorMenuItem.swift │ │ ├── LightColorMenuItem.swift │ │ ├── LightRGBColorMenuItem.swift │ │ ├── LightbulbMenuItem.swift │ │ ├── MenuItemProtocol.swift │ │ ├── NSMenuItem+HomeMenu.swift │ │ ├── OnOffMenuItem.swift │ │ ├── OutletMenuItem.swift │ │ ├── SensorMenuItem.swift │ │ ├── SwitchMenuItem.swift │ │ └── ToggleMenuItem.swift │ ├── Music │ │ ├── AirPlayDeviceView.swift │ │ ├── AirPlayDeviceView.xib │ │ ├── Music+extension.swift │ │ ├── Music.h │ │ ├── Music.swift │ │ ├── MusicPlayerView.swift │ │ ├── MusicPlayerView.xib │ │ ├── MusicTrackView.swift │ │ └── MusicTrackView.xib │ ├── PreferencePanes │ │ ├── Base.lproj │ │ │ ├── DonationPane.xib │ │ │ ├── GeneralPane.xib │ │ │ ├── InformationPane.xib │ │ │ └── ShortcutsPane.xib │ │ ├── DonationItemView.swift │ │ ├── DonationPaneController.swift │ │ ├── GeneralPaneController.swift │ │ ├── InformationPaneController.swift │ │ ├── ShortcutInfo.swift │ │ ├── ShortcutsPaneController.swift │ │ └── mul.lproj │ │ │ ├── DonationPane.xcstrings │ │ │ ├── GeneralPane.xcstrings │ │ │ ├── InformationPane.xcstrings │ │ │ └── ShortcutsPane.xcstrings │ ├── Preferences │ │ ├── SettingsPane.swift │ │ ├── SettingsTabViewController.swift │ │ ├── SettingsWindow.swift │ │ └── SettingsWindowController.swift │ └── mul.lproj │ │ └── LaunchView.xcstrings ├── macOSBridge-Bridging-Header.h └── macOSBridge-Info.plist ├── LICENSE ├── README.md └── python ├── accessory_category_types.txt ├── accessory_service_types.txt ├── characteristic_types.txt └── create_class_from_txt.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## macOS 6 | .DS_Store 7 | 8 | ## User settings 9 | xcuserdata/ 10 | 11 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 12 | *.xcscmblueprint 13 | *.xccheckout 14 | 15 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 16 | build/ 17 | DerivedData/ 18 | *.moved-aside 19 | *.pbxuser 20 | !default.pbxuser 21 | *.mode1v3 22 | !default.mode1v3 23 | *.mode2v3 24 | !default.mode2v3 25 | *.perspectivev3 26 | !default.perspectivev3 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | 31 | ## App packaging 32 | *.ipa 33 | *.dSYM.zip 34 | *.dSYM 35 | 36 | ## Playgrounds 37 | timeline.xctimeline 38 | playground.xcworkspace 39 | 40 | # Swift Package Manager 41 | # 42 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 43 | # Packages/ 44 | # Package.pins 45 | # Package.resolved 46 | # *.xcodeproj 47 | # 48 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 49 | # hence it is not needed unless you have added a package configuration file to your project 50 | # .swiftpm 51 | 52 | .build/ 53 | 54 | # CocoaPods 55 | # 56 | # We recommend against adding the Pods directory to your .gitignore. However 57 | # you should judge for yourself, the pros and cons are mentioned at: 58 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 59 | # 60 | # Pods/ 61 | # 62 | # Add this line if you want to avoid checking in source code from the Xcode workspace 63 | # *.xcworkspace 64 | 65 | # Carthage 66 | # 67 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 68 | # Carthage/Checkouts 69 | 70 | Carthage/Build/ 71 | 72 | # Accio dependency management 73 | Dependencies/ 74 | .accio/ 75 | 76 | # fastlane 77 | # 78 | # It is recommended to not store the screenshots in the git repo. 79 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 80 | # For more information about the recommended setup visit: 81 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 82 | 83 | fastlane/report.xml 84 | fastlane/Preview.html 85 | fastlane/screenshots/**/*.png 86 | fastlane/test_output 87 | 88 | # Code Injection 89 | # 90 | # After new code Injection tools there's a generated folder /iOSInjectionProject 91 | # https://github.com/johnno1962/injectionforxcode 92 | 93 | iOSInjectionProject/ 94 | -------------------------------------------------------------------------------- /HomeConMenu.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /HomeConMenu.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /HomeConMenu.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "colorwheelpanelview", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/sonsongithub/ColorWheelPanelView", 7 | "state" : { 8 | "branch" : "main", 9 | "revision" : "9df7cbc040c17141da6261f377a31cc3b400d174" 10 | } 11 | }, 12 | { 13 | "identity" : "keyboardshortcuts", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/sonsongithub/KeyboardShortcuts.git", 16 | "state" : { 17 | "branch" : "without-swiftui", 18 | "revision" : "cfdd47815a8550ce29c09a12aa51c9b29292a627" 19 | } 20 | }, 21 | { 22 | "identity" : "launchatlogin", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/sindresorhus/LaunchAtLogin", 25 | "state" : { 26 | "branch" : "main", 27 | "revision" : "d811817ce35d74872a1170c851cab243a2b8b559" 28 | } 29 | } 30 | ], 31 | "version" : 2 32 | } 33 | -------------------------------------------------------------------------------- /HomeConMenu.xcodeproj/xcshareddata/xcschemes/Hide os_log.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 58 | 59 | 60 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /HomeConMenu.xcodeproj/xcshareddata/xcschemes/Show os_log.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 58 | 59 | 60 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /HomeConMenu/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 | -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon_16x16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "icon_16x16@2x.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "icon_32x32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "icon_32x32@2x.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "icon_128x128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "icon_128x128@2x.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "icon_256x256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "icon_256x256@2x.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "icon_512x512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "icon_512x512@2x.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/appbigicon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "appbigicon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/appbigicon.imageset/appbigicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Assets.xcassets/appbigicon.imageset/appbigicon.png -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/homeappshot.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "homeappshot.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/homeappshot.imageset/homeappshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Assets.xcassets/homeappshot.imageset/homeappshot.png -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/house.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "house.svg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/house.imageset/house.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/privacy.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "privacy.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/privacy.imageset/privacy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Assets.xcassets/privacy.imageset/privacy.jpg -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/shots01.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "shots01.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/shots01.imageset/shots01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Assets.xcassets/shots01.imageset/shots01.png -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/shots02.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "shots02.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/shots02.imageset/shots02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Assets.xcassets/shots02.imageset/shots02.png -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/transparentIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "temp.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /HomeConMenu/Assets.xcassets/transparentIcon.imageset/temp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Assets.xcassets/transparentIcon.imageset/temp.png -------------------------------------------------------------------------------- /HomeConMenu/Dummy.storekit: -------------------------------------------------------------------------------- 1 | { 2 | "identifier" : "87DB397C", 3 | "nonRenewingSubscriptions" : [ 4 | 5 | ], 6 | "products" : [ 7 | { 8 | "displayPrice" : "300", 9 | "familyShareable" : false, 10 | "internalID" : "0B6A1013", 11 | "localizations" : [ 12 | { 13 | "description" : "☕️", 14 | "displayName" : "Coffee", 15 | "locale" : "en_US" 16 | }, 17 | { 18 | "description" : "☕️", 19 | "displayName" : "コーヒー", 20 | "locale" : "ja" 21 | } 22 | ], 23 | "productID" : "com.sonson.HomeConMenu.coffee", 24 | "referenceName" : "coffee", 25 | "type" : "Consumable" 26 | }, 27 | { 28 | "displayPrice" : "765", 29 | "familyShareable" : false, 30 | "internalID" : "20A1C328", 31 | "localizations" : [ 32 | { 33 | "description" : "🍺", 34 | "displayName" : "Beer", 35 | "locale" : "en_US" 36 | }, 37 | { 38 | "description" : "🍺", 39 | "displayName" : "ビール", 40 | "locale" : "ja" 41 | } 42 | ], 43 | "productID" : "com.sonson.HomeConMenu.beer", 44 | "referenceName" : "beer", 45 | "type" : "Consumable" 46 | }, 47 | { 48 | "displayPrice" : "1700", 49 | "familyShareable" : false, 50 | "internalID" : "2C8E1A30", 51 | "localizations" : [ 52 | { 53 | "description" : "🎁", 54 | "displayName" : "New Device", 55 | "locale" : "en_US" 56 | }, 57 | { 58 | "description" : "🎁", 59 | "displayName" : "新しいデバイス", 60 | "locale" : "ja" 61 | } 62 | ], 63 | "productID" : "com.sonson.HomeConMenu.newDevice", 64 | "referenceName" : "New device", 65 | "type" : "Consumable" 66 | } 67 | ], 68 | "settings" : { 69 | "_askToBuyEnabled" : false, 70 | "_billingIssuesEnabled" : false, 71 | "_failTransactionsEnabled" : false, 72 | "_locale" : "ja", 73 | "_renewalBillingIssuesEnabled" : false, 74 | "_storefront" : "JPN", 75 | "_storeKitErrors" : [ 76 | { 77 | "current" : { 78 | "index" : 5, 79 | "type" : "generic" 80 | }, 81 | "enabled" : false, 82 | "name" : "Load Products" 83 | }, 84 | { 85 | "current" : { 86 | "index" : 3, 87 | "type" : "generic" 88 | }, 89 | "enabled" : false, 90 | "name" : "Purchase" 91 | }, 92 | { 93 | "current" : { 94 | "index" : 5, 95 | "type" : "verification" 96 | }, 97 | "enabled" : false, 98 | "name" : "Verification" 99 | }, 100 | { 101 | "current" : null, 102 | "enabled" : false, 103 | "name" : "App Store Sync" 104 | }, 105 | { 106 | "current" : null, 107 | "enabled" : false, 108 | "name" : "Subscription Status" 109 | }, 110 | { 111 | "current" : { 112 | "index" : 3, 113 | "type" : "generic" 114 | }, 115 | "enabled" : false, 116 | "name" : "App Transaction" 117 | }, 118 | { 119 | "current" : { 120 | "index" : 3, 121 | "type" : "generic" 122 | }, 123 | "enabled" : false, 124 | "name" : "Manage Subscriptions Sheet" 125 | }, 126 | { 127 | "current" : { 128 | "index" : 0, 129 | "type" : "refund request" 130 | }, 131 | "enabled" : false, 132 | "name" : "Refund Request Sheet" 133 | }, 134 | { 135 | "current" : { 136 | "index" : 4, 137 | "type" : "generic" 138 | }, 139 | "enabled" : false, 140 | "name" : "Offer Code Redeem Sheet" 141 | } 142 | ] 143 | }, 144 | "subscriptionGroups" : [ 145 | 146 | ], 147 | "version" : { 148 | "major" : 3, 149 | "minor" : 0 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /HomeConMenu/HomeConMenu.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.homekit 6 | 7 | com.apple.security.app-sandbox 8 | 9 | com.apple.security.automation.apple-events 10 | 11 | com.apple.security.network.client 12 | 13 | com.apple.security.temporary-exception.apple-events 14 | 15 | com.apple.Music 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /HomeConMenu/HomeConMenuRelease.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.homekit 6 | 7 | com.apple.security.app-sandbox 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /HomeConMenu/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ITSAppUsesNonExemptEncryption 6 | 7 | UIApplicationSceneManifest 8 | 9 | UIApplicationSupportsMultipleScenes 10 | 11 | UISceneConfigurations 12 | 13 | UIWindowSceneSessionRoleApplication 14 | 15 | 16 | UISceneConfigurationName 17 | Default Configuration 18 | UISceneDelegateClassName 19 | $(PRODUCT_MODULE_NAME).SceneDelegate 20 | UISceneStoryboardFile 21 | Main 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /HomeConMenu/Japan.storekit: -------------------------------------------------------------------------------- 1 | { 2 | "identifier" : "BBABBC4A", 3 | "nonRenewingSubscriptions" : [ 4 | 5 | ], 6 | "products" : [ 7 | { 8 | "displayPrice" : "900", 9 | "familyShareable" : false, 10 | "internalID" : "6470705293", 11 | "localizations" : [ 12 | { 13 | "description" : "開発者にビールを寄付する", 14 | "displayName" : "ビールをおごる", 15 | "locale" : "ja" 16 | }, 17 | { 18 | "description" : "Donate beer to the developer", 19 | "displayName" : "Offer Beer", 20 | "locale" : "en_US" 21 | } 22 | ], 23 | "productID" : "com.sonson.HomeConMenu.beer", 24 | "referenceName" : "beer", 25 | "type" : "Consumable" 26 | }, 27 | { 28 | "displayPrice" : "400", 29 | "familyShareable" : false, 30 | "internalID" : "6470705176", 31 | "localizations" : [ 32 | { 33 | "description" : "Donate coffee to the developer.", 34 | "displayName" : "Offer Coffree", 35 | "locale" : "en_US" 36 | }, 37 | { 38 | "description" : "開発者にコーヒーを寄付する", 39 | "displayName" : "コーヒーをおごる", 40 | "locale" : "ja" 41 | } 42 | ], 43 | "productID" : "com.sonson.HomeConMenu.coffee", 44 | "referenceName" : "coffee", 45 | "type" : "Consumable" 46 | }, 47 | { 48 | "displayPrice" : "2000", 49 | "familyShareable" : false, 50 | "internalID" : "6470705320", 51 | "localizations" : [ 52 | { 53 | "description" : "Donate new device to the developer", 54 | "displayName" : "Offer new device", 55 | "locale" : "en_US" 56 | }, 57 | { 58 | "description" : "開発者に新しいデバイスを寄付する", 59 | "displayName" : "新しいデバイスをおごる", 60 | "locale" : "ja" 61 | } 62 | ], 63 | "productID" : "com.sonson.HomeConMenu.newDevice", 64 | "referenceName" : "New device", 65 | "type" : "Consumable" 66 | } 67 | ], 68 | "settings" : { 69 | "_applicationInternalID" : "1615397537", 70 | "_developerTeamID" : "JP866VWWSD", 71 | "_failTransactionsEnabled" : false, 72 | "_lastSynchronizedDate" : 722652858.64629698, 73 | "_locale" : "ja", 74 | "_storefront" : "JPN", 75 | "_storeKitErrors" : [ 76 | { 77 | "current" : null, 78 | "enabled" : false, 79 | "name" : "Load Products" 80 | }, 81 | { 82 | "current" : null, 83 | "enabled" : false, 84 | "name" : "Purchase" 85 | }, 86 | { 87 | "current" : null, 88 | "enabled" : false, 89 | "name" : "Verification" 90 | }, 91 | { 92 | "current" : null, 93 | "enabled" : false, 94 | "name" : "App Store Sync" 95 | }, 96 | { 97 | "current" : null, 98 | "enabled" : false, 99 | "name" : "Subscription Status" 100 | }, 101 | { 102 | "current" : null, 103 | "enabled" : false, 104 | "name" : "App Transaction" 105 | }, 106 | { 107 | "current" : null, 108 | "enabled" : false, 109 | "name" : "Manage Subscriptions Sheet" 110 | }, 111 | { 112 | "current" : null, 113 | "enabled" : false, 114 | "name" : "Refund Request Sheet" 115 | }, 116 | { 117 | "current" : null, 118 | "enabled" : false, 119 | "name" : "Offer Code Redeem Sheet" 120 | } 121 | ] 122 | }, 123 | "subscriptionGroups" : [ 124 | 125 | ], 126 | "version" : { 127 | "major" : 3, 128 | "minor" : 0 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /HomeConMenu/Products.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.sonson.HomeConMenu.coffee 6 | ☕️ 7 | com.sonson.HomeConMenu.beer 8 | 🍺 9 | com.sonson.HomeConMenu.newDevice 10 | 🎁 11 | 12 | 13 | -------------------------------------------------------------------------------- /HomeConMenu/Resources/Base.lproj/Acknowledgments.css: -------------------------------------------------------------------------------- 1 | 2 | * { margin: 0; padding: 0 } 3 | body { font: 12px/1.4 -apple-system, sans-serif; padding: 8px 16px } 4 | h1 { font-size: 18px; font-weight: 600; margin: 0.6em 0 } 5 | h2 { font-size: 14px; font-weight: 600; margin: 0.2em 0 } 6 | h3 { font-size: 13px; font-weight: 600; margin: 0.1em 0 } 7 | em { font-weight: 600; font-style: normal } 8 | 9 | ul { padding-left: 1.5em } 10 | ul li { margin: 0 0 0.4em; list-style-type: circle } 11 | 12 | a:hover { text-decoration: none } 13 | 14 | section { margin: 2em 0 1em } 15 | section+section { margin-top: 1.5em } 16 | blockquote pre { font: 90%/1.3 -apple-system, sans-serif; 17 | margin: 1em 0.5em; white-space: pre-wrap; 18 | border-left: 3px solid; padding-left: 1em } 19 | h3 span { font-size: 90%; font-weight: normal } 20 | 21 | details { margin-top: 0.5em } 22 | details summary { font-weight: 600; cursor: pointer; outline: none } 23 | details hr { border: 0.25px solid -apple-system-grid } 24 | 25 | h3::before, h2::before { margin-right: 0.4em } 26 | #Libraries h3::before { content: '📦' } 27 | #InfoSource h2::before { content: '📚' } 28 | #Contributors h2::before { content: '👥' } 29 | #Project h2::before { content: '🔨' } 30 | 31 | a:link, 32 | a:visited { color: hsl(90,45%,40%) } 33 | a:not([href]) { color: hsla( 0, 0%, 0%,0.60) } 34 | blockquote pre { color: hsla( 0, 0%, 0%,0.60) ; border-color: hsla( 0, 0%, 0%,0.1) } 35 | h3 span { color: hsla( 0, 0%, 0%,0.60) } 36 | details summary { color: hsla( 0, 0%, 0%,0.67) } 37 | 38 | @media (prefers-color-scheme: dark) { 39 | body { color: hsla(0,0%,100%,0.95); background: hsl( 0, 0%,12%) } 40 | a:link, 41 | a:visited { color: hsl(90,55%,55%) } 42 | a:not([href]) { color: hsla( 0, 0%,100%,0.60) } 43 | blockquote pre { color: hsla( 0, 0%,100%,0.60) ; border-color: hsla( 0, 0%,100%,0.1) } 44 | h3 span { color: hsla( 0, 0%,100%,0.50) } 45 | details summary { color: hsla( 0, 0%,100%,0.67) } 46 | } 47 | -------------------------------------------------------------------------------- /HomeConMenu/Resources/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /HomeConMenu/Resources/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /HomeConMenu/Resources/de.lproj/Acknowledgments.css: -------------------------------------------------------------------------------- 1 | 2 | * { margin: 0; padding: 0 } 3 | body { font: 12px/1.4 -apple-system, sans-serif; padding: 8px 16px } 4 | h1 { font-size: 18px; font-weight: 600; margin: 0.6em 0 } 5 | h2 { font-size: 14px; font-weight: 600; margin: 0.2em 0 } 6 | h3 { font-size: 13px; font-weight: 600; margin: 0.1em 0 } 7 | em { font-weight: 600; font-style: normal } 8 | 9 | ul { padding-left: 1.5em } 10 | ul li { margin: 0 0 0.4em; list-style-type: circle } 11 | 12 | a:hover { text-decoration: none } 13 | 14 | section { margin: 2em 0 1em } 15 | section+section { margin-top: 1.5em } 16 | blockquote pre { font: 90%/1.3 -apple-system, sans-serif; 17 | margin: 1em 0.5em; white-space: pre-wrap; 18 | border-left: 3px solid; padding-left: 1em } 19 | h3 span { font-size: 90%; font-weight: normal } 20 | 21 | details { margin-top: 0.5em } 22 | details summary { font-weight: 600; cursor: pointer; outline: none } 23 | details hr { border: 0.25px solid -apple-system-grid } 24 | 25 | h3::before, h2::before { margin-right: 0.4em } 26 | #Libraries h3::before { content: '📦' } 27 | #InfoSource h2::before { content: '📚' } 28 | #Contributors h2::before { content: '👥' } 29 | #Project h2::before { content: '🔨' } 30 | 31 | a:link, 32 | a:visited { color: hsl(90,45%,40%) } 33 | a:not([href]) { color: hsla( 0, 0%, 0%,0.60) } 34 | blockquote pre { color: hsla( 0, 0%, 0%,0.60) ; border-color: hsla( 0, 0%, 0%,0.1) } 35 | h3 span { color: hsla( 0, 0%, 0%,0.60) } 36 | details summary { color: hsla( 0, 0%, 0%,0.67) } 37 | 38 | @media (prefers-color-scheme: dark) { 39 | body { color: hsla(0,0%,100%,0.95); background: hsl( 0, 0%,12%) } 40 | a:link, 41 | a:visited { color: hsl(90,55%,55%) } 42 | a:not([href]) { color: hsla( 0, 0%,100%,0.60) } 43 | blockquote pre { color: hsla( 0, 0%,100%,0.60) ; border-color: hsla( 0, 0%,100%,0.1) } 44 | h3 span { color: hsla( 0, 0%,100%,0.50) } 45 | details summary { color: hsla( 0, 0%,100%,0.67) } 46 | } 47 | -------------------------------------------------------------------------------- /HomeConMenu/Resources/de.lproj/DonationPane.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Resources/de.lproj/DonationPane.strings -------------------------------------------------------------------------------- /HomeConMenu/Resources/de.lproj/GeneralPane.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Resources/de.lproj/GeneralPane.strings -------------------------------------------------------------------------------- /HomeConMenu/Resources/de.lproj/InformationPane.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Resources/de.lproj/InformationPane.strings -------------------------------------------------------------------------------- /HomeConMenu/Resources/de.lproj/LaunchView.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Resources/de.lproj/LaunchView.strings -------------------------------------------------------------------------------- /HomeConMenu/Resources/de.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Resources/de.lproj/Localizable.strings -------------------------------------------------------------------------------- /HomeConMenu/Resources/de.lproj/Main.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Resources/de.lproj/Main.strings -------------------------------------------------------------------------------- /HomeConMenu/Resources/de.lproj/ShortcutsPane.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonsongithub/HomeConMenu/778cff11cae46a7b75034d7305c82fa54347418c/HomeConMenu/Resources/de.lproj/ShortcutsPane.strings -------------------------------------------------------------------------------- /HomeConMenu/Resources/ja.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "I'd like to hide this window, when this app has just launched. How do I do it?"; ObjectID = "KIJ-iC-27o"; */ 3 | "KIJ-iC-27o.text" = "I'd like to hide this window, when this app has just launched. How do I do it?"; 4 | 5 | /* Class = "UILabel"; text = "HomeConMenuをメニューバーから開き、ホームデバイスを制御できます。"; ObjectID = "OLx-Mq-dEp"; */ 6 | "OLx-Mq-dEp.text" = "HomeConMenuをメニューバーから開き、ホームデバイスを制御できます。"; 7 | 8 | /* Class = "UILabel"; text = "起動時にこのウィンドウを表示する"; ObjectID = "Rh0-R4-YHf"; */ 9 | "Rh0-R4-YHf.text" = "起動時にこのウィンドウを表示する"; 10 | 11 | /* Class = "UILabel"; text = "HomeConMenuへようこそ"; ObjectID = "YOj-FH-EiA"; */ 12 | "YOj-FH-EiA.text" = "HomeConMenuへようこそ"; 13 | 14 | /* Class = "UILabel"; text = "Do not show this window when launching"; ObjectID = "bFT-wE-8Oj"; */ 15 | "bFT-wE-8Oj.text" = "Do not show this window when launching"; 16 | 17 | /* Class = "UIViewController"; title = "HomeConMenu"; ObjectID = "lcc-8Q-9QY"; */ 18 | "lcc-8Q-9QY.title" = "HomeConMenu"; 19 | 20 | /* Class = "UILabel"; text = "あなたのHomeKit環境にHomeがなかったり、Homeにホームデバイスが追加されていない場合、HomeConMenuを開いても何も表示されません。iOSのホームアプリを使って、あなたのホームデバイスをHomeKitに追加してください。"; ObjectID = "n6y-T4-DFR"; */ 21 | "n6y-T4-DFR.text" = "あなたのHomeKit環境にHomeがなかったり、Homeにホームデバイスが追加されていない場合、HomeConMenuを開いても何も表示されません。iOSのホームアプリを使って、あなたのホームデバイスをHomeKitに追加してください。"; 22 | 23 | /* Class = "UILabel"; text = "HomeConMenuでホームデバイスを制御するにはHomeKitへのアクセス権が必要です。システム環境設定の「セキュリティとプライバシー」からHomeConMenuにアクセス権を設定してください。"; ObjectID = "ugb-4b-hrP"; */ 24 | "ugb-4b-hrP.text" = "HomeConMenuでホームデバイスを制御するにはHomeKitへのアクセス権が必要です。システム環境設定の「セキュリティとプライバシー」からHomeConMenuにアクセス権を設定してください。"; 25 | 26 | /* Class = "UILabel"; text = "アプリケーションを起動するとメニューバーに家のアイコンが表示されます。このアイコンをクリックするとホームデバイスを制御できます。HomeConMenuは、電源、スイッチ、電球、室温、湿度デバイスの制御、情報表示に対応しています。"; ObjectID = "wRu-HS-yrR"; */ 27 | "wRu-HS-yrR.text" = "アプリケーションを起動するとメニューバーに家のアイコンが表示されます。このアイコンをクリックするとホームデバイスを制御できます。HomeConMenuは、電源、スイッチ、電球、室温、湿度デバイスの制御、情報表示に対応しています。"; 28 | -------------------------------------------------------------------------------- /HomeConMenu/Resources/mul.lproj/Main.xcstrings: -------------------------------------------------------------------------------- 1 | { 2 | "sourceLanguage" : "en", 3 | "strings" : { 4 | "KIJ-iC-27o.text" : { 5 | "comment" : "Class = \"UILabel\"; text = \"I'd like to hide this window, when this app has just launched. How do I do it?\"; ObjectID = \"KIJ-iC-27o\";", 6 | "extractionState" : "extracted_with_value", 7 | "localizations" : { 8 | "de" : { 9 | "stringUnit" : { 10 | "state" : "translated", 11 | "value" : "Dieses Fenster nach dem Programmstart verbergen? So geht's…" 12 | } 13 | }, 14 | "en" : { 15 | "stringUnit" : { 16 | "state" : "new", 17 | "value" : "I'd like to hide this window, when this app has just launched. How do I do it?" 18 | } 19 | }, 20 | "ja" : { 21 | "stringUnit" : { 22 | "state" : "translated", 23 | "value" : "I'd like to hide this window, when this app has just launched. How do I do it?" 24 | } 25 | } 26 | } 27 | } 28 | }, 29 | "version" : "1.0" 30 | } -------------------------------------------------------------------------------- /HomeConMenu/Shared/AccessoryInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessoryInfo.swift 3 | // HomeMenu 4 | // 5 | // Created by Yuichi Yoshida on 2022/03/08. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | 30 | #if !os(macOS) 31 | import HomeKit 32 | #endif 33 | 34 | @objc(AccessoryInfoProtocol) 35 | public protocol AccessoryInfoProtocol: NSObjectProtocol { 36 | init() 37 | var home: HomeInfoProtocol? { get set } 38 | var room: RoomInfoProtocol? { get set } 39 | 40 | var name: String { get set } 41 | var uniqueIdentifier: UUID { get set } 42 | 43 | var hasCamera: Bool { get set } 44 | 45 | var services: [ServiceInfoProtocol] { get set } 46 | 47 | var type: AccessoryType { get set } 48 | } 49 | 50 | public class AccessoryInfo: NSObject, AccessoryInfoProtocol { 51 | public var home: HomeInfoProtocol? 52 | public var room: RoomInfoProtocol? 53 | 54 | public var name: String 55 | public var uniqueIdentifier: UUID = UUID() 56 | 57 | public var services: [ServiceInfoProtocol] = [] 58 | 59 | public var type: AccessoryType = .unknown 60 | 61 | public var hasCamera: Bool = false 62 | 63 | #if !os(macOS) 64 | init(accessory: HMAccessory) { 65 | uniqueIdentifier = accessory.uniqueIdentifier 66 | name = accessory.name 67 | 68 | if let tmp = accessory.room { 69 | room = RoomInfo(name: tmp.name, uniqueIdentifier: tmp.uniqueIdentifier) 70 | } 71 | 72 | if let cameraProfiles = accessory.cameraProfiles { 73 | hasCamera = (cameraProfiles.count > 0) 74 | } 75 | 76 | services = accessory 77 | .services 78 | .filter({ $0.isSupported }) 79 | .map({ ServiceInfo(service: $0) }) 80 | .compactMap({$0}) 81 | 82 | super.init() 83 | } 84 | #endif 85 | 86 | required public override init() { 87 | fatalError() 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /HomeConMenu/Shared/ActionSetInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActionSetInfo.swift 3 | // HomeConMenu 4 | // 5 | // Created by Yuichi Yoshida on 2022/05/06. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | 30 | #if !os(macOS) 31 | import HomeKit 32 | #endif 33 | 34 | 35 | @objc(ActionSetInfoProtocol) 36 | public protocol ActionSetInfoProtocol: NSObjectProtocol { 37 | init() 38 | var name: String { get set } 39 | var uniqueIdentifier: UUID { get set } 40 | var actionUniqueIdentifiers: [UUID] { get set } 41 | } 42 | 43 | public class ActionSetInfo: NSObject, ActionSetInfoProtocol { 44 | public var name: String 45 | public var uniqueIdentifier: UUID 46 | public var actionUniqueIdentifiers: [UUID] 47 | 48 | #if !os(macOS) 49 | public init(actionSet: HMActionSet) { 50 | self.name = actionSet.name 51 | self.uniqueIdentifier = actionSet.uniqueIdentifier 52 | self.actionUniqueIdentifiers = actionSet.actions.compactMap({ $0 as? HMCharacteristicWriteAction }).map({ $0.characteristic.uniqueIdentifier }) 53 | super.init() 54 | } 55 | #endif 56 | 57 | required public override init() { 58 | fatalError() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /HomeConMenu/Shared/CharacteristicInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Characteristics.swift 3 | // HomeMenu 4 | // 5 | // Created by Yuichi Yoshida on 2022/03/08. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | 30 | #if !os(macOS) 31 | import HomeKit 32 | #endif 33 | 34 | @objc(CharacteristicInfoProtocol) 35 | public protocol CharacteristicInfoProtocol: NSObjectProtocol { 36 | init() 37 | var uniqueIdentifier: UUID { get set } 38 | var value: Any? { get set } 39 | var type: CharacteristicType { get set } 40 | } 41 | 42 | public class CharacteristicInfo: NSObject, CharacteristicInfoProtocol { 43 | public var uniqueIdentifier: UUID = UUID() 44 | public var value: Any? 45 | public var type: CharacteristicType = .unknown 46 | 47 | required public override init() { 48 | fatalError() 49 | } 50 | 51 | public var isSupported: Bool { 52 | return type.isSupported 53 | } 54 | 55 | #if !os(macOS) 56 | init(characteristic: HMCharacteristic) { 57 | super.init() 58 | uniqueIdentifier = characteristic.uniqueIdentifier 59 | value = characteristic.value 60 | type = CharacteristicType(key: characteristic.characteristicType) 61 | } 62 | #endif 63 | } 64 | 65 | -------------------------------------------------------------------------------- /HomeConMenu/Shared/Error.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Error.swift 3 | // HomeConMenu 4 | // 5 | // Created by Yuichi Yoshida on 2022/05/08. 6 | // 7 | 8 | import Foundation 9 | 10 | enum HomeConMenuError: Error { 11 | case characteristicTypeError(String, UUID, String?, UUID) 12 | case actionSetCharacteristicsCountError(String, UUID, Int, Int) 13 | case primaryHomeNotFound 14 | case characteristicNotFound(UUID) 15 | case characteristicValueNil(UUID) 16 | case actionSetNotFound(UUID) 17 | 18 | var localizedDescription: String { 19 | switch self { 20 | case .characteristicTypeError: 21 | return "Can not get expected type value from the characteristic." 22 | case .actionSetCharacteristicsCountError(let name, let uuid, let targetCount, let currentCount): 23 | return "The count of target values and current ones do not match. - \(name) - \(uuid.uuidString) - target=\(targetCount) - current=\(currentCount)" 24 | case .primaryHomeNotFound: 25 | return "Primary home is not found." 26 | case .actionSetNotFound(let uuid): 27 | return "No action sets which have the specified unique identifier are found. - \(uuid.uuidString)" 28 | case .characteristicNotFound(let uuid): 29 | return "No characteristics which have the specified unique identifier are found. - \(uuid.uuidString)" 30 | case .characteristicValueNil(let uuid): 31 | return "Value of the specified characteristic is nil. - \(uuid.uuidString)" 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /HomeConMenu/Shared/HomeInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeInfo.swift 3 | // HomeMenu 4 | // 5 | // Created by Yuichi Yoshida on 2022/03/08. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | 30 | @objc(HomeInfoProtocol) 31 | public protocol HomeInfoProtocol: NSObjectProtocol { 32 | init() 33 | var name: String? { get set } 34 | var uniqueIdentifier: UUID? { get set } 35 | } 36 | 37 | public class HomeInfo: NSObject, HomeInfoProtocol { 38 | public var name: String? 39 | public var uniqueIdentifier: UUID? 40 | 41 | public init(name: String, uniqueIdentifier: UUID) { 42 | self.name = name 43 | self.uniqueIdentifier = uniqueIdentifier 44 | super.init() 45 | } 46 | 47 | required public override init() { 48 | fatalError() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /HomeConMenu/Shared/InterfaceProtocols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MacOSBridge.swift 3 | // HomeMenu 4 | // 5 | // Created by Yuichi Yoshida on 2022/03/02. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | @objc(iOS2Mac) 30 | public protocol iOS2Mac: NSObjectProtocol { 31 | init() 32 | func updateMenuItemsRelated(to uniqueIdentifier: UUID, using value: Any) 33 | func setReachablityOfMenuItemRelated(to uniqueIdentifier: UUID, using isReachable: Bool) 34 | func bringToFront() 35 | func reloadMenuExtra() 36 | func centeringWindows() 37 | func openHomeKitAuthenticationError() -> Bool 38 | func openNoHomeError() 39 | func showLaunchView() 40 | var iosListener: mac2iOS? { get set } 41 | } 42 | 43 | @objc(mac2iOS) 44 | public protocol mac2iOS: NSObjectProtocol { 45 | func readCharacteristic(of uniqueIdentifier: UUID) 46 | func getActionSet(with uniqueIdentifier: UUID) throws -> ActionSetInfo 47 | func getTargetValuesInActionSet(with uniqueIdentifier: UUID) throws -> [Any] 48 | func getCharacteristic(of uniqueIdentifier: UUID) throws -> Any 49 | func setCharacteristic(of uniqueIdentifier: UUID, object: Any) 50 | func openAcknowledgement() 51 | func rebootHomeManager() 52 | var homes: [HomeInfoProtocol] { get set } 53 | var homeUniqueIdentifier: UUID? { get set } 54 | var accessories: [AccessoryInfoProtocol] { get set } 55 | var serviceGroups: [ServiceGroupInfoProtocol] { get set } 56 | var rooms: [RoomInfoProtocol] { get set } 57 | var actionSets: [ActionSetInfoProtocol] { get set } 58 | func openCamera(uniqueIdentifier: UUID) 59 | func executeActionSet(uniqueIdentifier: UUID) 60 | func close(windows: [Any]) 61 | } 62 | -------------------------------------------------------------------------------- /HomeConMenu/Shared/Log.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Log.swift 3 | // HomeConMenu 4 | // 5 | // Created by Yuichi Yoshida on 2022/05/03. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | import os 30 | 31 | extension OSLog { 32 | static let homeKitLog = OSLog(subsystem: "com.sonson.HomeConMenu.macOS", category: "HomeKit") 33 | static let appLog = OSLog(subsystem: "com.sonson.HomeConMenu.macOS", category: "Application") 34 | } 35 | 36 | extension Logger { 37 | static let homeKit = Logger(OSLog.homeKitLog) 38 | static let app = Logger(OSLog.appLog) 39 | } 40 | -------------------------------------------------------------------------------- /HomeConMenu/Shared/RoomInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneInfo.swift 3 | // HomeMenu 4 | // 5 | // Created by Yuichi Yoshida on 2022/03/08. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | 30 | @objc(RoomInfoProtocol) 31 | public protocol RoomInfoProtocol: NSObjectProtocol { 32 | init() 33 | var name: String { get set } 34 | var uniqueIdentifier: UUID { get set } 35 | } 36 | 37 | public class RoomInfo: NSObject, RoomInfoProtocol { 38 | public var name: String 39 | public var uniqueIdentifier: UUID 40 | 41 | public init(name: String, uniqueIdentifier: UUID) { 42 | self.name = name 43 | self.uniqueIdentifier = uniqueIdentifier 44 | super.init() 45 | } 46 | 47 | required public override init() { 48 | fatalError() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /HomeConMenu/Shared/ServiceGroupInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceGroupInfo.swift 3 | // HomeConMenu 4 | // 5 | // Created by Yuichi Yoshida on 2022/04/12. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | 30 | #if !os(macOS) 31 | import HomeKit 32 | #endif 33 | 34 | /// Protocol to describe service group information. 35 | @objc(ServiceGroupInfoProtocol) 36 | public protocol ServiceGroupInfoProtocol: NSObjectProtocol { 37 | init() 38 | /// Name of service group. 39 | var name: String { get set } 40 | /// Unique identifier of service group. 41 | var uniqueIdentifier: UUID { get set } 42 | /// Services in service group. 43 | var services: [ServiceInfoProtocol] { get set } 44 | /// Common characteristic types in service group. 45 | var commonCharacteristicTypes: [CharacteristicInfo] { get set } 46 | } 47 | 48 | public class ServiceGroupInfo: NSObject, ServiceGroupInfoProtocol { 49 | public var name: String 50 | public var uniqueIdentifier: UUID = UUID() 51 | public var services: [ServiceInfoProtocol] = [] 52 | public var commonCharacteristicTypes: [CharacteristicInfo] = [] 53 | 54 | required public override init() { 55 | fatalError() 56 | } 57 | #if !os(macOS) 58 | init(serviceGroup: HMServiceGroup) { 59 | name = serviceGroup.name 60 | uniqueIdentifier = serviceGroup.uniqueIdentifier 61 | services = serviceGroup.services.map({ ServiceInfo(service: $0) }) 62 | super.init() 63 | } 64 | #endif 65 | } 66 | -------------------------------------------------------------------------------- /HomeConMenu/Shared/ServiceInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceInfo.swift 3 | // HomeMenu 4 | // 5 | // Created by Yuichi Yoshida on 2022/03/08. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | import Foundation 28 | 29 | #if !os(macOS) 30 | import HomeKit 31 | #endif 32 | 33 | @objc(ServiceInfoProtocol) 34 | public protocol ServiceInfoProtocol: NSObjectProtocol { 35 | init() 36 | var name: String { get set } 37 | var uniqueIdentifier: UUID { get set } 38 | var isUserInteractive: Bool { get set } 39 | var characteristics: [CharacteristicInfoProtocol] { get set } 40 | var type: ServiceType { get set } 41 | var associatedServiceType: ServiceType { get set } 42 | } 43 | 44 | public class ServiceInfo: NSObject, ServiceInfoProtocol { 45 | public var name: String 46 | public var uniqueIdentifier: UUID = UUID() 47 | public var isUserInteractive: Bool = false 48 | public var characteristics: [CharacteristicInfoProtocol] = [] 49 | public var type: ServiceType = .unknown 50 | public var associatedServiceType: ServiceType = .unknown 51 | 52 | required public override init() { 53 | fatalError() 54 | } 55 | 56 | public var isSupported: Bool { 57 | return self.type.isSupported 58 | } 59 | 60 | #if !os(macOS) 61 | init(service: HMService) { 62 | name = service.name 63 | uniqueIdentifier = service.uniqueIdentifier 64 | isUserInteractive = service.isUserInteractive 65 | type = ServiceType(key: service.serviceType) 66 | characteristics = service.characteristics.map({ CharacteristicInfo(characteristic: $0) }).filter({ $0.isSupported }) 67 | if let tmp = service.associatedServiceType { 68 | associatedServiceType = ServiceType(key: tmp) 69 | } 70 | super.init() 71 | } 72 | #endif 73 | } 74 | -------------------------------------------------------------------------------- /HomeConMenu/US.storekit: -------------------------------------------------------------------------------- 1 | { 2 | "identifier" : "6BA849FE", 3 | "nonRenewingSubscriptions" : [ 4 | 5 | ], 6 | "products" : [ 7 | { 8 | "displayPrice" : "5.99", 9 | "familyShareable" : false, 10 | "internalID" : "6470705293", 11 | "localizations" : [ 12 | { 13 | "description" : "開発者にビールを寄付する", 14 | "displayName" : "ビールをおごる", 15 | "locale" : "ja" 16 | }, 17 | { 18 | "description" : "Donate beer to the developer", 19 | "displayName" : "Offer Beer", 20 | "locale" : "en_US" 21 | } 22 | ], 23 | "productID" : "com.sonson.HomeConMenu.beer", 24 | "referenceName" : "beer", 25 | "type" : "Consumable" 26 | }, 27 | { 28 | "displayPrice" : "2.99", 29 | "familyShareable" : false, 30 | "internalID" : "6470705176", 31 | "localizations" : [ 32 | { 33 | "description" : "Donate coffee to the developer.", 34 | "displayName" : "Offer Coffree", 35 | "locale" : "en_US" 36 | }, 37 | { 38 | "description" : "開発者にコーヒーを寄付する", 39 | "displayName" : "コーヒーをおごる", 40 | "locale" : "ja" 41 | } 42 | ], 43 | "productID" : "com.sonson.HomeConMenu.coffee", 44 | "referenceName" : "coffee", 45 | "type" : "Consumable" 46 | }, 47 | { 48 | "displayPrice" : "12.99", 49 | "familyShareable" : false, 50 | "internalID" : "6470705320", 51 | "localizations" : [ 52 | { 53 | "description" : "Donate new device to the developer", 54 | "displayName" : "Offer new device", 55 | "locale" : "en_US" 56 | }, 57 | { 58 | "description" : "開発者に新しいデバイスを寄付する", 59 | "displayName" : "新しいデバイスをおごる", 60 | "locale" : "ja" 61 | } 62 | ], 63 | "productID" : "com.sonson.HomeConMenu.newDevice", 64 | "referenceName" : "New device", 65 | "type" : "Consumable" 66 | } 67 | ], 68 | "settings" : { 69 | "_applicationInternalID" : "1615397537", 70 | "_developerTeamID" : "JP866VWWSD", 71 | "_failTransactionsEnabled" : false, 72 | "_lastSynchronizedDate" : 722652840.30804503, 73 | "_locale" : "en_US", 74 | "_storefront" : "USA", 75 | "_storeKitErrors" : [ 76 | { 77 | "current" : null, 78 | "enabled" : false, 79 | "name" : "Load Products" 80 | }, 81 | { 82 | "current" : null, 83 | "enabled" : false, 84 | "name" : "Purchase" 85 | }, 86 | { 87 | "current" : null, 88 | "enabled" : false, 89 | "name" : "Verification" 90 | }, 91 | { 92 | "current" : null, 93 | "enabled" : false, 94 | "name" : "App Store Sync" 95 | }, 96 | { 97 | "current" : null, 98 | "enabled" : false, 99 | "name" : "Subscription Status" 100 | }, 101 | { 102 | "current" : null, 103 | "enabled" : false, 104 | "name" : "App Transaction" 105 | }, 106 | { 107 | "current" : null, 108 | "enabled" : false, 109 | "name" : "Manage Subscriptions Sheet" 110 | }, 111 | { 112 | "current" : null, 113 | "enabled" : false, 114 | "name" : "Refund Request Sheet" 115 | }, 116 | { 117 | "current" : null, 118 | "enabled" : false, 119 | "name" : "Offer Code Redeem Sheet" 120 | } 121 | ] 122 | }, 123 | "subscriptionGroups" : [ 124 | 125 | ], 126 | "version" : { 127 | "major" : 3, 128 | "minor" : 0 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /HomeConMenu/iOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // HomeMenu 4 | // 5 | // Created by Yuichi Yoshida on 2022/03/02. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import UIKit 29 | import HomeKit 30 | 31 | @main 32 | class AppDelegate: UIResponder, UIApplicationDelegate { 33 | 34 | let monitor = MonitoringNetworkState() 35 | 36 | var baseManager: BaseManager? 37 | 38 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 39 | baseManager = BaseManager() 40 | return true 41 | } 42 | 43 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 44 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /HomeConMenu/iOS/CameraViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CameraViewController.swift 3 | // HomeMenu 4 | // 5 | // Created by Yuichi Yoshida on 2022/03/11. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | import UIKit 30 | import HomeKit 31 | import os 32 | 33 | class CameraViewController: UIViewController, HMCameraStreamControlDelegate { 34 | 35 | var cameraView: HMCameraView? 36 | var cameraProfile: HMCameraProfile? 37 | 38 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 39 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 40 | Logger.app.info("\(self) init") 41 | } 42 | 43 | required init?(coder: NSCoder) { 44 | fatalError("init(coder:) has not been implemented") 45 | } 46 | 47 | deinit { 48 | Logger.app.debug("\(self) deinit") 49 | } 50 | 51 | override func viewWillDisappear(_ animated: Bool) { 52 | if let streamControl = cameraProfile?.streamControl { 53 | streamControl.delegate = nil 54 | streamControl.stopStream() 55 | } 56 | } 57 | 58 | override func viewDidAppear(_ animated: Bool) { 59 | super.viewDidAppear(animated) 60 | guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } 61 | appDelegate.baseManager?.macOSController?.bringToFront() 62 | } 63 | 64 | override func viewDidLoad() { 65 | super.viewDidLoad() 66 | 67 | let indicator = UIActivityIndicatorView(style: .large) 68 | self.view.addSubview(indicator) 69 | indicator.translatesAutoresizingMaskIntoConstraints = false 70 | 71 | self.view.addConstraints([ 72 | self.view.safeAreaLayoutGuide.centerXAnchor.constraint(equalTo: indicator.centerXAnchor, constant: 0), 73 | self.view.safeAreaLayoutGuide.centerYAnchor.constraint(equalTo: indicator.centerYAnchor, constant: 0) 74 | ]) 75 | indicator.startAnimating() 76 | guard let cameraProfile = cameraProfile else { return } 77 | 78 | cameraProfile.streamControl?.delegate = self 79 | let cameraView = HMCameraView() 80 | cameraView.frame = self.view.bounds 81 | cameraView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 82 | self.view.addSubview(cameraView) 83 | self.cameraView = cameraView 84 | 85 | cameraProfile.streamControl?.startStream() 86 | 87 | 88 | self.view.setNeedsLayout() 89 | } 90 | 91 | // MARK: - HMCameraStreamControlDelegate 92 | 93 | func cameraStreamControlDidStartStream(_ cameraStreamControl: HMCameraStreamControl) { 94 | guard let cameraView = cameraView else { return } 95 | cameraView.cameraSource = cameraStreamControl.cameraStream 96 | } 97 | } 98 | 99 | -------------------------------------------------------------------------------- /HomeConMenu/iOS/DummyViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // HomeMenu 4 | // 5 | // Created by Yuichi Yoshida on 2022/03/02. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import UIKit 29 | import os 30 | 31 | class DummyViewController: UIViewController { 32 | 33 | required init?(coder: NSCoder) { 34 | super.init(coder: coder) 35 | Logger.app.info("\(self) init") 36 | } 37 | 38 | deinit { 39 | Logger.app.debug("\(self) deinit") 40 | } 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /HomeConMenu/iOS/MonitoringNetworkState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MonitoringNetworkState.swift 3 | // HomeConMenu 4 | // 5 | // Created by Yuichi Yoshida on 2023/10/15. 6 | // 7 | 8 | import Foundation 9 | import Network 10 | 11 | class MonitoringNetworkState: ObservableObject { 12 | 13 | static let didPathUpdateNotification = Notification.Name("didPathUpdateNotification") 14 | private let monitor = NWPathMonitor() 15 | private let queue = DispatchQueue.global(qos: .background) 16 | 17 | init() { 18 | monitor.start(queue: queue) 19 | monitor.pathUpdateHandler = { path in 20 | NotificationCenter.default.post(name: MonitoringNetworkState.didPathUpdateNotification, object: path) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /HomeConMenu/iOS/WebViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewController.swift 3 | // HomeConMenu 4 | // 5 | // Created by Yuichi Yoshida on 2023/09/09. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | import WebKit 30 | import os 31 | 32 | class WebViewController: UIViewController { 33 | 34 | private let fileURL: URL 35 | let webView = WKWebView() 36 | 37 | deinit { 38 | Logger.app.debug("\(self) deinit") 39 | } 40 | 41 | init(fileURL: URL) { 42 | self.fileURL = fileURL 43 | super.init(nibName: nil, bundle: nil) 44 | Logger.app.info("\(self) init") 45 | } 46 | 47 | required init?(coder: NSCoder) { 48 | fatalError("init(coder:) has not been implemented") 49 | } 50 | 51 | override func viewDidLoad() { 52 | super.viewDidLoad() 53 | self.view.addSubview(webView) 54 | 55 | self.view.translatesAutoresizingMaskIntoConstraints = false 56 | self.webView.translatesAutoresizingMaskIntoConstraints = false 57 | 58 | self.webView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true 59 | self.webView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true 60 | self.webView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true 61 | self.webView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true 62 | webView.loadFileURL(fileURL, allowingReadAccessTo: fileURL) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/AppleScriptManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppleScriptManager.swift 3 | // MusicConMenu 4 | // 5 | // Created by Yuichi Yoshida on 2024/11/05. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Cocoa 29 | import Foundation 30 | 31 | protocol CreatableFromNSAppleEventDescriptor { 32 | static func get(a:NSAppleEventDescriptor) throws -> Self 33 | } 34 | 35 | extension Int : CreatableFromNSAppleEventDescriptor { 36 | static func get(a:NSAppleEventDescriptor) throws -> Self { 37 | return Int(a.int32Value) 38 | } 39 | } 40 | 41 | extension String : CreatableFromNSAppleEventDescriptor { 42 | static func get(a:NSAppleEventDescriptor) throws -> Self { 43 | guard let string = a.stringValue else { 44 | throw AppleScriptManagerError.canNotGetString 45 | } 46 | return string 47 | } 48 | } 49 | 50 | enum AppleScriptManagerError: Error { 51 | case appleScriptError 52 | case appleScriptExecutionError(Dictionary) 53 | case canNotGetString 54 | } 55 | 56 | class AppleScriptManager { 57 | 58 | static func call(script: String) throws -> NSAppleEventDescriptor { 59 | var error: NSDictionary? 60 | guard let scriptObject = NSAppleScript(source: script) else { 61 | throw AppleScriptManagerError.appleScriptError 62 | } 63 | let output = scriptObject.executeAndReturnError(&error) 64 | if let error { 65 | var dicitonary: [String: Any] = [:] 66 | for key in error.allKeys { 67 | if let key = key as? String { 68 | dicitonary[key] = error[key] 69 | } 70 | } 71 | throw AppleScriptManagerError.appleScriptExecutionError(dicitonary) 72 | } 73 | return output 74 | } 75 | 76 | static func execute(script: String) throws -> A { 77 | let output = try call(script: script) 78 | return try A.get(a: output) 79 | } 80 | 81 | static func execute(script: String) throws -> [A] { 82 | let output = try call(script: script) 83 | var array: [A] = [] 84 | for i in 0.. Bool { 127 | Logger.app.info("openHomeKitAuthenticationError") 128 | let alert = NSAlert() 129 | alert.messageText = NSLocalizedString("Failed to access HomeKit because of your privacy settings.", comment: "") 130 | alert.informativeText = NSLocalizedString("Allow HomeConMenu to access HomeKit in System Settings.", comment:"") 131 | alert.alertStyle = .informational 132 | 133 | alert.addButton(withTitle: "OK") 134 | alert.addButton(withTitle: NSLocalizedString("Open System Settings", comment: "")) 135 | 136 | let ret = alert.runModal() 137 | switch ret { 138 | case .alertSecondButtonReturn: 139 | Logger.app.info("Open System Preferences.app") 140 | if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_HomeKit") { 141 | NSWorkspace.shared.open(url) 142 | } 143 | return true 144 | default: 145 | Logger.app.info("Does not open System Preferences.app") 146 | return false 147 | } 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/MenuItem/CameraMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CameraMenu.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2022/03/20. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Cocoa 29 | 30 | class CameraMenuItem: NSMenuItem, MenuItemFromUUID, MenuItemOrder { 31 | 32 | var orderPriority: Int { 33 | -1 34 | } 35 | 36 | func UUIDs() -> [UUID] { 37 | [accessoryCharacteristicIdentifier] 38 | } 39 | 40 | func bind(with uniqueIdentifier: UUID) -> Bool { 41 | return accessoryCharacteristicIdentifier == uniqueIdentifier 42 | } 43 | 44 | func characteristicInfos() -> [CharacteristicInfo] { 45 | return [] 46 | } 47 | 48 | let accessoryCharacteristicIdentifier: UUID 49 | var mac2ios: mac2iOS? 50 | 51 | @IBAction func open(sender: NSMenuItem) { 52 | self.mac2ios?.openCamera(uniqueIdentifier: accessoryCharacteristicIdentifier) 53 | } 54 | 55 | init?(accessoryInfo: AccessoryInfoProtocol, mac2ios: mac2iOS?) { 56 | guard accessoryInfo.hasCamera else { return nil } 57 | 58 | self.mac2ios = mac2ios 59 | self.accessoryCharacteristicIdentifier = accessoryInfo.uniqueIdentifier 60 | super.init(title: accessoryInfo.name, action: nil, keyEquivalent: "") 61 | 62 | self.image = NSImage(systemSymbolName: "camera", accessibilityDescription: nil) 63 | self.action = #selector(CameraMenuItem.open(sender:)) 64 | self.target = self 65 | } 66 | 67 | override init(title string: String, action selector: Selector?, keyEquivalent charCode: String) { 68 | self.accessoryCharacteristicIdentifier = UUID() 69 | super.init(title: string, action: selector, keyEquivalent: charCode) 70 | } 71 | 72 | required init(coder: NSCoder) { 73 | fatalError("init(coder:) has not been implemented") 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/MenuItem/HomeSelectMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeSelectMenuItem.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2023/12/10. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | import AppKit 30 | 31 | class HomeSelectMenuItem: NSMenuItem { 32 | let uniqueIdentifier: UUID 33 | 34 | init(uniqueIdentifier: UUID) { 35 | self.uniqueIdentifier = uniqueIdentifier 36 | super.init(title: "", action: nil, keyEquivalent: "") 37 | } 38 | 39 | required init(coder: NSCoder) { 40 | fatalError("init(coder:) has not been implemented") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/MenuItem/LightBrightnessColorMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LightBrightnessColorMenuItem.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2024/02/15. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Cocoa 29 | import ColorWheelPanelView 30 | import os 31 | 32 | class LightBrightnessColorMenuItem: LightColorMenuItem, GraySliderPanelViewDelegate { 33 | 34 | let brightnessCharcteristicIdentifier: UUID 35 | 36 | override func UUIDs() -> [UUID] { 37 | return [brightnessCharcteristicIdentifier] 38 | } 39 | 40 | override func bind(with uniqueIdentifier: UUID) -> Bool { 41 | return uniqueIdentifier == brightnessCharcteristicIdentifier 42 | } 43 | 44 | override init?(serviceInfo: ServiceInfoProtocol, mac2ios: mac2iOS?) { 45 | guard let brightnessChara = serviceInfo.characteristics.first(where: { obj in 46 | obj.type == .brightness 47 | }) else { return nil } 48 | 49 | let brightness = brightnessChara.value as? CGFloat ?? CGFloat(100) 50 | 51 | self.brightnessCharcteristicIdentifier = brightnessChara.uniqueIdentifier 52 | 53 | super.init(serviceInfo: serviceInfo, mac2ios: mac2ios) 54 | 55 | let view = GraySliderPanelView() 56 | 57 | if let mac2ios = mac2ios { 58 | do { 59 | guard let brightness = try mac2ios.getCharacteristic(of: brightnessCharcteristicIdentifier) as? Double 60 | else { throw HomeConMenuError.characteristicTypeError(serviceInfo.name, serviceInfo.uniqueIdentifier, brightness.description, brightnessChara.uniqueIdentifier) } 61 | self.color = NSColor(hue: 1.0, saturation: 0.0, brightness: brightness/100.0, alpha: 1.0) 62 | } catch let error as HomeConMenuError { 63 | Logger.app.error("\(error.localizedDescription)") 64 | } catch { 65 | Logger.app.error("Can not get brightness from characteristic. - \(error.localizedDescription)") 66 | } 67 | } 68 | 69 | view.frame = NSRect(x: 0, y: 0, width: 250, height: 50) 70 | view.brightness = brightness / 100 71 | self.view = view 72 | view.isContinuous = false 73 | view.delegate = self 74 | } 75 | 76 | override func update(of uniqueIdentifier: UUID, value: Double) { 77 | switch uniqueIdentifier { 78 | case brightnessCharcteristicIdentifier: 79 | updateColor(hue: nil, saturation: nil, brightness: value / 100.0) 80 | default: 81 | do {} 82 | } 83 | } 84 | 85 | func didChangeColor(brightness: Double) { 86 | let brightness100 = brightness * 100 87 | mac2ios?.setCharacteristic(of: brightnessCharcteristicIdentifier, object: brightness100) 88 | } 89 | 90 | required init(coder: NSCoder) { 91 | fatalError("init(coder:) has not been implemented") 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/MenuItem/LightColorMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LightColorMenuItem.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2022/03/20. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Cocoa 29 | import ColorWheelPanelView 30 | import os 31 | 32 | class LightColorMenuItem: NSMenuItem, NSWindowDelegate, MenuItemFromUUID, MenuItemOrder, ErrorMenuItem { 33 | 34 | var color = NSColor.white 35 | var mac2ios: mac2iOS? 36 | 37 | // MARK: - MenuItemProtocol 38 | 39 | var reachable: Bool = true 40 | 41 | var orderPriority: Int { 42 | 100 43 | } 44 | 45 | func UUIDs() -> [UUID] { 46 | return [] 47 | } 48 | 49 | func bind(with uniqueIdentifier: UUID) -> Bool { 50 | return false 51 | } 52 | 53 | func updateColor(hue: Double?, saturation: Double?, brightness: Double?) { 54 | let hue = hue ?? self.color.hueComponent 55 | let saturation = saturation ?? self.color.saturationComponent 56 | let brightness = brightness ?? self.color.brightnessComponent 57 | 58 | self.color = NSColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1.0) 59 | if let parent = self.parent { 60 | parent.image = createImage() 61 | } 62 | } 63 | 64 | func createImage() -> NSImage? { 65 | let size = CGFloat(16) 66 | let view = NSView(frame: NSRect(x: 0, y: 0, width: size, height: size)) 67 | view.wantsLayer = true 68 | view.translatesAutoresizingMaskIntoConstraints = false 69 | view.layer?.backgroundColor = NSColor.clear.cgColor 70 | 71 | let destinationSize = Double(size) 72 | 73 | let image = NSImage(size: NSSize(width: destinationSize, height: destinationSize)) 74 | 75 | guard let icon = NSImage(systemSymbolName: "lightbulb.fill", accessibilityDescription: nil) else { return nil } 76 | guard let frameIcon = NSImage(systemSymbolName: "lightbulb", accessibilityDescription: nil) else { return nil } 77 | 78 | var width = Double(1) 79 | var height = Double(1) 80 | 81 | if icon.size.width < icon.size.height { 82 | width = destinationSize * (icon.size.width / icon.size.height) 83 | height = destinationSize 84 | } else { 85 | width = destinationSize 86 | height = destinationSize * (icon.size.height / icon.size.width) 87 | } 88 | 89 | let x = (destinationSize - width) / 2 90 | let y = (destinationSize - height) / 2 91 | 92 | image.lockFocus() 93 | guard let ctx = NSGraphicsContext.current?.cgContext else { return nil } 94 | 95 | view.layer?.render(in: ctx) 96 | let source = NSRect(origin: CGPoint.zero, size: icon.size) 97 | 98 | let destination = NSRect(x: x, y: y, width: width, height: height) 99 | 100 | 101 | ctx.setFillColor(NSColor.blue.cgColor) 102 | let rect = CGRect(x: x+1, y: y, width: width-2, height: height) 103 | rect.fill() 104 | 105 | icon.draw(in: destination, from: source, operation: .destinationIn, fraction: 1) 106 | frameIcon.draw(in: destination, from: source, operation: .darken, fraction: 1) 107 | image.unlockFocus() 108 | 109 | return image 110 | } 111 | 112 | init?(serviceInfo: ServiceInfoProtocol, mac2ios: mac2iOS?) { 113 | self.mac2ios = mac2ios 114 | super.init(title: "", action: nil, keyEquivalent: "") 115 | } 116 | 117 | func update(of uniqueIdentifier: UUID, value: Double) { 118 | // dummy 119 | } 120 | 121 | required init(coder: NSCoder) { 122 | fatalError("init(coder:) has not been implemented") 123 | } 124 | 125 | /// Create a sub menu for lightbulb. If `serviceInfo` has characteristics to adjust hue, brightness and staturation, this function returns LightRGBColorMenuItem instance. 126 | /// If `serviceInfo` has only brightness, returns LightBrightnessColorMenuItem instance. In the other cases, returns nil. 127 | /// - Parameters: 128 | /// - serviceInfo: Service information of the lightbulb. 129 | /// - mac2ios: Delegate object to send messages to MacCatalyst from macOS bundle. 130 | static func createSubMenu(serviceInfo: ServiceInfoProtocol, mac2ios: mac2iOS?) -> NSMenuItem? { 131 | 132 | let brightnessChara = serviceInfo.characteristics.first(where: { obj in 133 | obj.type == .brightness 134 | }) 135 | let hueChara = serviceInfo.characteristics.first(where: { obj in 136 | obj.type == .hue 137 | }) 138 | let saturationChara = serviceInfo.characteristics.first(where: { obj in 139 | obj.type == .saturation 140 | }) 141 | 142 | if brightnessChara != nil && hueChara != nil && saturationChara != nil { 143 | return LightRGBColorMenuItem(serviceInfo: serviceInfo, mac2ios: mac2ios) 144 | } else if brightnessChara != nil { 145 | return LightBrightnessColorMenuItem(serviceInfo: serviceInfo, mac2ios: mac2ios) 146 | } 147 | 148 | return nil 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/MenuItem/LightRGBColorMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LightRGBColorMenuItem.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2024/02/15. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | import Cocoa 30 | import os 31 | import ColorWheelPanelView 32 | 33 | class LightRGBColorMenuItem: LightColorMenuItem, ColorWheelPanelViewDelegate { 34 | 35 | let hueCharcteristicIdentifier: UUID 36 | let saturationCharcteristicIdentifier: UUID 37 | let brightnessCharcteristicIdentifier: UUID 38 | 39 | override func UUIDs() -> [UUID] { 40 | return [hueCharcteristicIdentifier, saturationCharcteristicIdentifier, brightnessCharcteristicIdentifier] 41 | } 42 | 43 | override func bind(with uniqueIdentifier: UUID) -> Bool { 44 | return uniqueIdentifier == hueCharcteristicIdentifier || uniqueIdentifier == saturationCharcteristicIdentifier || uniqueIdentifier == brightnessCharcteristicIdentifier 45 | } 46 | 47 | override func update(of uniqueIdentifier: UUID, value: Double) { 48 | switch uniqueIdentifier { 49 | case hueCharcteristicIdentifier: 50 | updateColor(hue: value / 360.0, saturation: nil, brightness: nil) 51 | case saturationCharcteristicIdentifier: 52 | updateColor(hue: nil, saturation: value / 100.0, brightness: nil) 53 | case brightnessCharcteristicIdentifier: 54 | updateColor(hue: nil, saturation: nil, brightness: value / 100.0) 55 | default: 56 | do {} 57 | } 58 | } 59 | 60 | override init?(serviceInfo: ServiceInfoProtocol, mac2ios: mac2iOS?) { 61 | guard let brightnessChara = serviceInfo.characteristics.first(where: { obj in 62 | obj.type == .brightness 63 | }) else { return nil } 64 | guard let hueChara = serviceInfo.characteristics.first(where: { obj in 65 | obj.type == .hue 66 | }) else { return nil } 67 | guard let saturationChara = serviceInfo.characteristics.first(where: { obj in 68 | obj.type == .saturation 69 | }) else { return nil } 70 | 71 | self.hueCharcteristicIdentifier = hueChara.uniqueIdentifier 72 | self.saturationCharcteristicIdentifier = saturationChara.uniqueIdentifier 73 | self.brightnessCharcteristicIdentifier = brightnessChara.uniqueIdentifier 74 | 75 | super.init(serviceInfo: serviceInfo, mac2ios: mac2ios) 76 | 77 | let view = ColorWheelPanelView() 78 | 79 | if let mac2ios = mac2ios { 80 | do { 81 | guard let hue = try mac2ios.getCharacteristic(of: hueCharcteristicIdentifier) as? Double 82 | else { throw HomeConMenuError.characteristicTypeError(serviceInfo.name, serviceInfo.uniqueIdentifier, hueChara.type.description, hueChara.uniqueIdentifier) } 83 | guard let saturation = try mac2ios.getCharacteristic(of: saturationCharcteristicIdentifier) as? Double 84 | else { throw HomeConMenuError.characteristicTypeError(serviceInfo.name, serviceInfo.uniqueIdentifier, saturationChara.description, saturationChara.uniqueIdentifier) } 85 | guard let brightness = try mac2ios.getCharacteristic(of: brightnessCharcteristicIdentifier) as? Double 86 | else { throw HomeConMenuError.characteristicTypeError(serviceInfo.name, serviceInfo.uniqueIdentifier, brightnessChara.description, brightnessChara.uniqueIdentifier) } 87 | self.color = NSColor(hue: hue/360.0, saturation: saturation/100.0, brightness: brightness/100.0, alpha: 1.0) 88 | 89 | view.hue = hue / 360.0 90 | view.saturation = saturation / 100.0 91 | view.brightness = brightness / 100.0 92 | } catch let error as HomeConMenuError { 93 | Logger.app.error("\(error.localizedDescription)") 94 | } catch { 95 | Logger.app.error("Can not get brightness, hue, staturation from characteristic. - \(error.localizedDescription)") 96 | } 97 | } 98 | 99 | view.isContinuous = false 100 | view.delegate = self 101 | view.frame = NSRect(x: 0, y: 0, width: 250, height: 220) 102 | self.view = view 103 | } 104 | 105 | func didChangeColor(hue: Double, saturation: Double, brightness: Double) { 106 | let hue360 = hue * 360.0 107 | let saturation100 = saturation * 100 108 | let brightness100 = brightness * 100 109 | 110 | mac2ios?.setCharacteristic(of: hueCharcteristicIdentifier, object: hue360) 111 | mac2ios?.setCharacteristic(of: saturationCharcteristicIdentifier, object: saturation100) 112 | mac2ios?.setCharacteristic(of: brightnessCharcteristicIdentifier, object: brightness100) 113 | } 114 | 115 | required init(coder: NSCoder) { 116 | fatalError("init(coder:) has not been implemented") 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/MenuItem/LightbulbMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LightbulbMenuItem.swift 3 | // HomeConMenu 4 | // 5 | // Created by Yuichi Yoshida on 2022/04/16. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Cocoa 29 | import KeyboardShortcuts 30 | 31 | class LightbulbMenuItem: ToggleMenuItem { 32 | 33 | let subColorMenu = NSMenu() 34 | 35 | override var reachable: Bool { 36 | didSet { 37 | if reachable { 38 | if subColorMenu.items.count > 0 { 39 | self.submenu = subColorMenu 40 | if let item = subColorMenu.items.first as? LightColorMenuItem { 41 | self.image = item.createImage() 42 | } 43 | } else { 44 | self.image = icon 45 | } 46 | } else { 47 | self.image = NSImage(systemSymbolName: "exclamationmark.triangle", accessibilityDescription: nil) 48 | self.submenu = nil 49 | } 50 | } 51 | } 52 | 53 | override var icon: NSImage? { 54 | return NSImage(systemSymbolName: "lightbulb", accessibilityDescription: nil) 55 | } 56 | 57 | override init?(serviceInfo: ServiceInfoProtocol, mac2ios: mac2iOS?) { 58 | 59 | super.init(serviceInfo: serviceInfo, mac2ios: mac2ios) 60 | 61 | self.image = NSImage(systemSymbolName: "lightbulb", accessibilityDescription: nil) 62 | self.action = #selector(self.toggle(sender:)) 63 | self.target = self 64 | 65 | if let subMenuForLightbulbMenu = LightColorMenuItem.createSubMenu(serviceInfo: serviceInfo, mac2ios: mac2ios) as? LightColorMenuItem { 66 | subColorMenu.addItem(subMenuForLightbulbMenu) 67 | self.submenu = subColorMenu 68 | self.image = subMenuForLightbulbMenu.createImage() 69 | } 70 | } 71 | 72 | override init?(serviceGroupInfo: ServiceGroupInfoProtocol, mac2ios: mac2iOS?) { 73 | super.init(serviceGroupInfo: serviceGroupInfo, mac2ios: mac2ios) 74 | reachable = true 75 | } 76 | 77 | required init(coder: NSCoder) { 78 | fatalError("init(coder:) has not been implemented") 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/MenuItem/MenuItemProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuFromUUID.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2022/03/20. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | import Cocoa 30 | 31 | protocol MenuItemFromUUID { 32 | func bind(with uniqueIdentifier: UUID) -> Bool 33 | func UUIDs() -> [UUID] 34 | } 35 | 36 | protocol ErrorMenuItem { 37 | var reachable: Bool { get set } 38 | } 39 | 40 | protocol MenuItemOrder { 41 | var orderPriority: Int { get } 42 | } 43 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/MenuItem/NSMenuItem+HomeMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSMenuItem+HomeMenu.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2022/03/21. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | import AppKit 30 | 31 | extension NSMenuItem { 32 | @MainActor 33 | class func HomeMenus(serviceInfo: ServiceInfoProtocol, mac2ios: mac2iOS?) -> [NSMenuItem?] { 34 | switch serviceInfo.type { 35 | case .humiditySensor, .temperatureSensor: 36 | return [SensorMenuItem(serviceInfo: serviceInfo, mac2ios: mac2ios)] 37 | case .lightbulb: 38 | return [LightbulbMenuItem(serviceInfo: serviceInfo, mac2ios: mac2ios)] 39 | case .switch: 40 | return [SwitchMenuItem(serviceInfo: serviceInfo, mac2ios: mac2ios)] 41 | case .outlet: 42 | return [OutletMenuItem(serviceInfo: serviceInfo, mac2ios: mac2ios)] 43 | default: 44 | return [] 45 | } 46 | } 47 | 48 | class func HomeMenus(serviceGroup: ServiceGroupInfoProtocol, mac2ios: mac2iOS?) -> [NSMenuItem?] { 49 | 50 | let serviceTypes = Set(serviceGroup.services.map({ $0.type})) 51 | 52 | guard let firstService = serviceGroup.services.first else { return [] } 53 | 54 | var buffer = Set(firstService.characteristics.map({ $0.type })) 55 | for service in serviceGroup.services { 56 | buffer = Set(service.characteristics.map({$0.type})).intersection(buffer) 57 | } 58 | if buffer.contains(.powerState) && serviceTypes.contains(.outlet) { 59 | return [OutletMenuItem(serviceGroupInfo: serviceGroup, mac2ios: mac2ios)] 60 | } 61 | if buffer.contains(.powerState) && serviceTypes.contains(.switch) { 62 | return [SwitchMenuItem(serviceGroupInfo: serviceGroup, mac2ios: mac2ios)] 63 | } 64 | if buffer.contains(.powerState) && serviceTypes.contains(.lightbulb) { 65 | return [LightbulbMenuItem(serviceGroupInfo: serviceGroup, mac2ios: mac2ios)] 66 | } 67 | return [] 68 | } 69 | 70 | @MainActor func cancelKeyboardShortcut() { 71 | self.setShortcut(nil) 72 | self.setShortcut(for: nil) 73 | } 74 | } 75 | 76 | extension NSMenu { 77 | static func getSubItems(menu: NSMenu) -> [NSMenuItem] { 78 | 79 | var buffer: [NSMenuItem] = [] 80 | 81 | for item in menu.items { 82 | buffer.append(item) 83 | if let submenu = item.submenu { 84 | buffer.append(contentsOf: getSubItems(menu: submenu)) 85 | } 86 | } 87 | 88 | return buffer 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/MenuItem/OnOffMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnOffMenuItem.swift 3 | // HomeConMenu 4 | // 5 | // Created by Yuichi Yoshida on 2024/02/15. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | 30 | enum DisplayItemType { 31 | case light 32 | case `switch` 33 | case outlet 34 | case fan 35 | case none 36 | } 37 | 38 | class OnOffMenuItem: ToggleMenuItem { 39 | 40 | var displayItem: DisplayItemType = .switch 41 | 42 | override init?(serviceInfo: ServiceInfoProtocol, mac2ios: mac2iOS?) { 43 | 44 | // decide icon type 45 | switch serviceInfo.associatedServiceType { 46 | case .lightbulb: 47 | self.displayItem = .light 48 | case .outlet: 49 | self.displayItem = .outlet 50 | case .switch: 51 | self.displayItem = .switch 52 | case .fan: 53 | self.displayItem = .fan 54 | default: 55 | self.displayItem = .none 56 | } 57 | super.init(serviceInfo: serviceInfo, mac2ios: mac2ios) 58 | } 59 | 60 | override init?(serviceGroupInfo: ServiceGroupInfoProtocol, mac2ios: mac2iOS?) { 61 | super.init(serviceGroupInfo: serviceGroupInfo, mac2ios: mac2ios) 62 | } 63 | 64 | required init(coder: NSCoder) { 65 | fatalError("init(coder:) has not been implemented") 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/MenuItem/OutletMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutletMenuItem.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2024/02/15. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Cocoa 29 | 30 | class OutletMenuItem: OnOffMenuItem { 31 | 32 | override var icon: NSImage? { 33 | switch displayItem { 34 | case .light: 35 | return NSImage(systemSymbolName: "lightbulb", accessibilityDescription: nil) 36 | case .fan: 37 | return NSImage(systemSymbolName: "fan", accessibilityDescription: nil) 38 | case .outlet: 39 | return NSImage(systemSymbolName: "poweroutlet.type.b", accessibilityDescription: nil) 40 | case .switch: 41 | return NSImage(systemSymbolName: "lightswitch.on", accessibilityDescription: nil) 42 | case .none: 43 | return NSImage(systemSymbolName: "poweroutlet.type.b", accessibilityDescription: nil) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/MenuItem/SensorMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SensorMenu.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2022/03/20. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Cocoa 29 | import os 30 | 31 | class SensorMenuItem: NSMenuItem, MenuItemFromUUID, ErrorMenuItem, MenuItemOrder { 32 | var orderPriority: Int { 33 | switch self.type { 34 | case .temperature: 35 | return 1000 36 | case .humidity: 37 | return 999 38 | default: 39 | return 0 40 | } 41 | } 42 | 43 | var mac2ios: mac2iOS? 44 | let uniqueIdentifier: UUID 45 | let type: SensorType 46 | 47 | var reachable: Bool { 48 | didSet { 49 | if reachable { 50 | switch self.type { 51 | case .temperature: 52 | self.image = NSImage(systemSymbolName: "thermometer", accessibilityDescription: nil) 53 | case .humidity: 54 | self.image = NSImage(systemSymbolName: "humidity", accessibilityDescription: nil) 55 | default: 56 | do{} 57 | } 58 | } else { 59 | self.image = NSImage(systemSymbolName: "exclamationmark.triangle", accessibilityDescription: nil) 60 | } 61 | } 62 | } 63 | 64 | func UUIDs() -> [UUID] { 65 | return [uniqueIdentifier] 66 | } 67 | 68 | public enum SensorType { 69 | case temperature 70 | case humidity 71 | case unknown 72 | } 73 | 74 | func bind(with uniqueIdentifier: UUID) -> Bool { 75 | return self.uniqueIdentifier == uniqueIdentifier 76 | } 77 | 78 | func update(value: Double) { 79 | reachable = true 80 | switch (self.type, value) { 81 | case (.temperature, let value): 82 | let temperatureMeasurement = Measurement(value: value, unit: UnitTemperature.celsius) 83 | self.title = "\(MeasurementFormatter().string(from: temperatureMeasurement))" 84 | case (.humidity, let value): 85 | self.title = "\(value.formatted(.number.precision(.fractionLength(0...1))))%" 86 | default: 87 | self.title = "unsupported" 88 | } 89 | } 90 | 91 | override init(title string: String, action selector: Selector?, keyEquivalent charCode: String) { 92 | self.uniqueIdentifier = UUID() 93 | self.type = .unknown 94 | self.reachable = true 95 | super.init(title: string, action: selector, keyEquivalent: charCode) 96 | } 97 | 98 | required init(coder: NSCoder) { 99 | fatalError("init(coder:) has not been implemented") 100 | } 101 | 102 | init?(serviceInfo: ServiceInfoProtocol, mac2ios: mac2iOS?) { 103 | 104 | func decideType(serviceInfo: ServiceInfoProtocol) -> (CharacteristicInfoProtocol, SensorType)? { 105 | if serviceInfo.type == .humiditySensor { 106 | guard let humidityChara = serviceInfo.characteristics.first(where: { obj in 107 | obj.type == .currentRelativeHumidity 108 | }) else { return nil } 109 | return (humidityChara, .humidity) 110 | } else if serviceInfo.type == .temperatureSensor { 111 | guard let temperatureChara = serviceInfo.characteristics.first(where: { obj in 112 | obj.type == .currentTemperature 113 | }) else { return nil } 114 | return (temperatureChara, .temperature) 115 | } 116 | return nil 117 | } 118 | 119 | guard let (characteristicInfo, type) = decideType(serviceInfo: serviceInfo) else { return nil } 120 | 121 | self.reachable = true 122 | self.uniqueIdentifier = characteristicInfo.uniqueIdentifier 123 | self.type = type 124 | super.init(title: "", action: nil, keyEquivalent: "") 125 | 126 | switch self.type { 127 | case .temperature: 128 | self.image = NSImage(systemSymbolName: "thermometer", accessibilityDescription: nil) 129 | case .humidity: 130 | self.image = NSImage(systemSymbolName: "humidity", accessibilityDescription: nil) 131 | default: 132 | do{} 133 | } 134 | 135 | if let mac2ios = mac2ios { 136 | do { 137 | guard let value = try mac2ios.getCharacteristic(of: uniqueIdentifier) as? Double 138 | else { throw HomeConMenuError.characteristicTypeError(serviceInfo.name, serviceInfo.uniqueIdentifier, characteristicInfo.description, characteristicInfo.uniqueIdentifier) } 139 | update(value: value) 140 | } catch let error as HomeConMenuError { 141 | Logger.app.error("\(error.localizedDescription)") 142 | } catch { 143 | Logger.app.error("Can not get temperature, humidity from characteristic. - \(error.localizedDescription)") 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/MenuItem/SwitchMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwitchMenuItem.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2024/02/15. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Cocoa 29 | 30 | class SwitchMenuItem: OnOffMenuItem { 31 | 32 | override var icon: NSImage? { 33 | switch displayItem { 34 | case .light: 35 | return NSImage(systemSymbolName: "lightbulb", accessibilityDescription: nil) 36 | case .fan: 37 | return NSImage(systemSymbolName: "fan", accessibilityDescription: nil) 38 | case .outlet: 39 | return NSImage(systemSymbolName: "poweroutlet.type.b", accessibilityDescription: nil) 40 | case .switch: 41 | return NSImage(systemSymbolName: "lightswitch.on", accessibilityDescription: nil) 42 | case .none: 43 | return NSImage(systemSymbolName: "lightswitch.on", accessibilityDescription: nil) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/MenuItem/ToggleMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PowerMenu.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2022/03/20. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Cocoa 29 | import os 30 | import KeyboardShortcuts 31 | 32 | class ToggleMenuItem: NSMenuItem, MenuItemFromUUID, ErrorMenuItem, MenuItemOrder { 33 | 34 | let characteristicIdentifiers: [UUID] 35 | var mac2ios: mac2iOS? 36 | var icon: NSImage? { 37 | return NSImage(systemSymbolName: "powerplug", accessibilityDescription: nil) 38 | } 39 | 40 | init?(serviceGroupInfo: ServiceGroupInfoProtocol, mac2ios: mac2iOS?) { 41 | 42 | let characteristicInfos = serviceGroupInfo.services.map({ $0.characteristics }).flatMap({ $0 }) 43 | 44 | let infos = characteristicInfos.filter({ $0.type == .powerState }) 45 | 46 | guard infos.count > 0 else { return nil } 47 | 48 | guard let sample = infos.first else { return nil} 49 | 50 | 51 | let uuids = infos.map({$0.uniqueIdentifier}) 52 | 53 | self.reachable = true 54 | self.mac2ios = mac2ios 55 | self.characteristicIdentifiers = uuids 56 | super.init(title: serviceGroupInfo.name, action: nil, keyEquivalent: "") 57 | 58 | if let number = sample.value as? Int { 59 | self.state = (number == 0) ? .off : .on 60 | } 61 | self.image = self.icon 62 | self.action = #selector(self.toggle(sender:)) 63 | self.target = self 64 | } 65 | 66 | @MainActor 67 | init?(serviceInfo: ServiceInfoProtocol, mac2ios: mac2iOS?) { 68 | guard let powerStateChara = serviceInfo.characteristics.first(where: { obj in 69 | obj.type == .powerState 70 | }) else { return nil } 71 | self.reachable = true 72 | self.mac2ios = mac2ios 73 | self.characteristicIdentifiers = [powerStateChara.uniqueIdentifier] 74 | super.init(title: serviceInfo.name, action: nil, keyEquivalent: "") 75 | 76 | if let number = powerStateChara.value as? Int { 77 | self.state = (number == 0) ? .off : .on 78 | } 79 | self.image = self.icon 80 | self.action = #selector(self.toggle(sender:)) 81 | self.target = self 82 | if let r = KeyboardShortcuts.Name(rawValue: serviceInfo.uniqueIdentifier.uuidString) { 83 | setShortcut(for: r) 84 | KeyboardShortcuts.onKeyDown(for: r, action: { 85 | self.toggle(sender: self) 86 | }) 87 | } 88 | } 89 | 90 | override init(title string: String, action selector: Selector?, keyEquivalent charCode: String) { 91 | self.characteristicIdentifiers = [] 92 | self.reachable = true 93 | super.init(title: string, action: selector, keyEquivalent: charCode) 94 | } 95 | 96 | required init(coder: NSCoder) { 97 | fatalError("init(coder:) has not been implemented") 98 | } 99 | 100 | func update(value: Bool) { 101 | reachable = true 102 | self.state = value ? .on : .off 103 | } 104 | 105 | // MARK: - MenuItemProtocol 106 | 107 | func bind(with uniqueIdentifier: UUID) -> Bool { 108 | return characteristicIdentifiers.contains(where: { $0 == uniqueIdentifier }) 109 | } 110 | 111 | var orderPriority: Int { 112 | 100 113 | } 114 | 115 | var reachable: Bool { 116 | didSet { 117 | if reachable { 118 | self.image = icon 119 | } else { 120 | self.image = NSImage(systemSymbolName: "exclamationmark.triangle", accessibilityDescription: nil) 121 | } 122 | } 123 | } 124 | 125 | func UUIDs() -> [UUID] { 126 | return characteristicIdentifiers 127 | } 128 | 129 | // MARK: - Aciton 130 | 131 | @IBAction func toggle(sender: NSMenuItem) { 132 | guard let uuid = characteristicIdentifiers.first else { return } 133 | do { 134 | let value = try self.mac2ios?.getCharacteristic(of: uuid) 135 | if let boolValue = value as? Bool { 136 | for uuid in characteristicIdentifiers { 137 | self.mac2ios?.setCharacteristic(of: uuid, object: !boolValue) 138 | } 139 | } 140 | } catch let error as HomeConMenuError { 141 | Logger.app.error("\(error.localizedDescription)") 142 | } catch { 143 | Logger.app.error("Can not get toggle status, unexpected error - \(error.localizedDescription)") 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/Music/AirPlayDeviceView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AirPlayDeviceView.swift 3 | // MusicMenu 4 | // 5 | // Created by Yuichi Yoshida on 2024/11/04. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Cocoa 29 | import os 30 | 31 | class AirPlayDeviceView : NSView { 32 | var name: String = "" 33 | 34 | @IBOutlet var deviceNameLabel: NSTextField? 35 | @IBOutlet var deviceVolumeSlider: NSSlider? 36 | @IBOutlet var enableButton: NSButton? 37 | @IBOutlet var icon: NSImageView? 38 | @IBOutlet var volumeImage: NSImageView? 39 | 40 | required init?(coder: NSCoder) { 41 | super.init(coder: coder) 42 | let center = DistributedNotificationCenter.default() 43 | center.addObserver(self, selector: #selector(receiveNotification(_:)), name: NSNotification.Name("com.apple.Music.playerInfo"), object: nil) 44 | } 45 | 46 | override init(frame frameRect: NSRect) { 47 | super.init(frame: frameRect) 48 | let center = DistributedNotificationCenter.default() 49 | center.addObserver(self, selector: #selector(receiveNotification(_:)), name: NSNotification.Name("com.apple.Music.playerInfo"), object: nil) 50 | } 51 | 52 | static func create(frame frameRect: NSRect, name: String) -> AirPlayDeviceView? { 53 | var topLevelObjects: NSArray? = nil 54 | Bundle.main.loadNibNamed("AirPlayDeviceView", owner: self, topLevelObjects: &topLevelObjects) 55 | guard let view = topLevelObjects?.first(where: { $0 is NSView }) as? AirPlayDeviceView else { 56 | return nil 57 | } 58 | view.name = name 59 | view.frame = frameRect 60 | view.autoresizingMask = [.width, .height] 61 | 62 | view.update() 63 | return view 64 | } 65 | 66 | func update() { 67 | Task { 68 | guard let musicApp = SBApplication.musicApp else { return } 69 | guard let device = musicApp.AirPlayDevices?().first(where: { String($0.name ?? "") == self.name }) else { return } 70 | 71 | DispatchQueue.main.async { 72 | guard let device_select = device.selected else { return } 73 | 74 | if let stateButton = self.enableButton { 75 | stateButton.state = device_select ? .on : .off 76 | } 77 | if let stateSlider = self.deviceVolumeSlider { 78 | stateSlider.isHidden = !device_select 79 | self.volumeImage?.isHidden = !device_select 80 | stateSlider.integerValue = device.soundVolume ?? 0 81 | self.volumeImage?.updateSoundVolume(with: stateSlider) 82 | } 83 | } 84 | } 85 | } 86 | 87 | @objc func receiveNotification(_ notification: NSNotification) { 88 | if let userInfo = notification.userInfo { 89 | if let message = userInfo["Player State"] as? String { 90 | if message == "Playing" { 91 | self.update() 92 | } 93 | if message == "Paused" { 94 | self.update() 95 | } 96 | } 97 | } 98 | } 99 | 100 | @IBAction func enableButtonClicked(_ sender: NSButton) { 101 | guard let musicApp = SBApplication.musicApp else { return } 102 | guard let device = musicApp.AirPlayDevices?().first(where: { String($0.name ?? "") == self.name }) else { return } 103 | device.setSelected?((sender.state == .on)) 104 | self.update() 105 | } 106 | 107 | @IBAction func volumeSliderChanged(_ sender: NSSlider) { 108 | guard let musicApp = SBApplication.musicApp else { return } 109 | guard let device = musicApp.AirPlayDevices?().first(where: { String($0.name ?? "") == self.name }) else { return } 110 | device.setSoundVolume?(sender.integerValue) 111 | self.volumeImage?.updateSoundVolume(with: sender) 112 | } 113 | 114 | deinit { 115 | Logger.app.debug("\(self) deinit") 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/Music/Music+extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Music+extension.swift 3 | // MusicConMenu 4 | // 5 | // Created by Yuichi Yoshida on 2024/11/05. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | 27 | 28 | import Foundation 29 | import os 30 | 31 | struct AirPlayDeviceInfo : Hashable { 32 | let name: String 33 | let kind: MusicEAPD 34 | 35 | func hash(into hasher: inout Hasher) { 36 | (self.name + self.kind.description).hash(into: &hasher) 37 | } 38 | } 39 | 40 | extension SBApplication { 41 | static var musicApp: MusicApplication? { 42 | guard NSWorkspace.shared.runningApplications.contains(where: { $0.bundleIdentifier == "com.apple.Music" }) else { return nil } 43 | return SBApplication(bundleIdentifier: "com.apple.Music") 44 | } 45 | 46 | static func getCurrentPlaylistNames() -> [String] { 47 | var playlists: [String] = [] 48 | if let musicApp = SBApplication.musicApp { 49 | musicApp.playlists?().forEach { playlist in 50 | if let name = playlist.name { 51 | playlists.append(String(name)) 52 | } 53 | } 54 | } 55 | return playlists 56 | } 57 | 58 | static func getCurrentAirPlayDevices() -> [AirPlayDeviceInfo] { 59 | var devices: [AirPlayDeviceInfo] = [] 60 | if let musicApp = SBApplication.musicApp { 61 | musicApp.AirPlayDevices?().forEach { device in 62 | if let name = device.name, let kind = device.kind { 63 | devices.append(AirPlayDeviceInfo(name: String(name), kind: kind)) 64 | } 65 | } 66 | } 67 | return devices 68 | } 69 | 70 | static func isRunning () -> Bool { 71 | do { 72 | let script = """ 73 | set appName to "Music" 74 | tell application "System Events" 75 | set appRunning to (name of processes) contains appName 76 | end tell 77 | return appRunning 78 | """ 79 | let result: Int = try AppleScriptManager.execute(script: script) 80 | return result == 1 81 | } catch { 82 | Logger.app.error("\(error.localizedDescription)") 83 | return false 84 | } 85 | } 86 | } 87 | 88 | extension MusicPlaylist { 89 | func play() { 90 | do { 91 | guard let name = self.name else { return } 92 | let script = """ 93 | tell application "Music" 94 | play the playlist named "\(name)" 95 | end tell 96 | """ 97 | _ = try AppleScriptManager.call(script: script) 98 | } catch { 99 | Logger.app.error("\(error.localizedDescription)") 100 | } 101 | } 102 | } 103 | 104 | extension MusicEAPD { 105 | 106 | var description: String { 107 | switch self { 108 | case .computer: 109 | return "Computer" 110 | case .airPortExpress: 111 | return "AirPort Express" 112 | case .appleTV: 113 | return "Apple TV" 114 | case .airPlayDevice: 115 | return "AirPlay Device" 116 | case .bluetoothDevice: 117 | return "Bluetooth Device" 118 | case .homePod: 119 | return "HomePod" 120 | case .tV: 121 | return "TV" 122 | case .unknown: 123 | return "Unknown" 124 | } 125 | } 126 | 127 | var icon: NSImage { 128 | switch self { 129 | case .computer: 130 | return NSImage.init(systemSymbolName: "desktopcomputer", accessibilityDescription: nil)! 131 | case .airPortExpress: 132 | return NSImage.init(systemSymbolName: "airport.extreme", accessibilityDescription: nil)! 133 | case .appleTV: 134 | return NSImage.init(systemSymbolName: "appletv", accessibilityDescription: nil)! 135 | case .airPlayDevice: 136 | return NSImage.init(systemSymbolName: "airplayaudio", accessibilityDescription: nil)! 137 | case .bluetoothDevice: 138 | return NSImage.init(systemSymbolName: "airplayaudio", accessibilityDescription: nil)! 139 | case .homePod: 140 | return NSImage.init(systemSymbolName: "homepod.fill", accessibilityDescription: nil)! 141 | case .tV: 142 | return NSImage.init(systemSymbolName: "appletv.fill", accessibilityDescription: nil)! 143 | case .unknown: 144 | return NSImage.init(systemSymbolName: "hifispeaker.fill", accessibilityDescription: nil)! 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/Music/MusicPlayerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MusicPlayerView.swift 3 | // HomeConMenu 4 | // 5 | // Created by sonson on 2024/11/16. 6 | // 7 | 8 | import AppKit 9 | import KeyboardShortcuts 10 | import os 11 | 12 | extension NSImageView { 13 | func updateSoundVolume(with slider: NSSlider) { 14 | if slider.integerValue == 0 { 15 | self.image = NSImage.init(systemSymbolName: "speaker", accessibilityDescription: nil) 16 | } else if slider.integerValue < 33 { 17 | self.image = NSImage.init(systemSymbolName: "speaker.wave.1", accessibilityDescription: nil) 18 | } else if slider.integerValue < 66 { 19 | self.image = NSImage.init(systemSymbolName: "speaker.wave.2", accessibilityDescription: nil) 20 | } else { 21 | self.image = NSImage.init(systemSymbolName: "speaker.wave.3", accessibilityDescription: nil) 22 | } 23 | } 24 | } 25 | 26 | class MusicPlayerView : NSView { 27 | 28 | @IBOutlet var playButton: NSButton? 29 | @IBOutlet var nextButton: NSButton? 30 | @IBOutlet var prevButton: NSButton? 31 | @IBOutlet var volumeSlider: NSSlider? 32 | @IBOutlet var speakerIcon: NSImageView? 33 | @IBOutlet var messageLabel: NSTextField? 34 | @IBOutlet var groupView: NSView? 35 | @IBOutlet var disclosureImage: NSImageView? 36 | 37 | static func create(frame frameRect: NSRect) -> MusicPlayerView? { 38 | var topLevelObjects: NSArray? = nil 39 | Bundle.main.loadNibNamed("MusicPlayerView", owner: self, topLevelObjects: &topLevelObjects) 40 | guard let view = topLevelObjects?.first(where: { $0 is NSView }) as? MusicPlayerView else { 41 | return nil 42 | } 43 | view.frame = frameRect 44 | view.autoresizingMask = [.width, .height] 45 | view.setup() 46 | return view 47 | } 48 | 49 | @objc func receiveNotification(_ notification: NSNotification) { 50 | if let userInfo = notification.userInfo { 51 | if let message = userInfo["Player State"] as? String { 52 | if message == "Playing" { 53 | self.update() 54 | self.playButton?.image = NSImage.init(systemSymbolName: "pause.circle", accessibilityDescription: nil) 55 | } 56 | if message == "Paused" { 57 | self.update() 58 | self.playButton?.image = NSImage.init(systemSymbolName: "play.circle", accessibilityDescription: nil) 59 | } 60 | } 61 | } 62 | } 63 | 64 | func showUI(isMusicAppRunning: Bool) { 65 | self.groupView?.isHidden = !isMusicAppRunning 66 | self.disclosureImage?.isHidden = !isMusicAppRunning 67 | self.messageLabel?.isHidden = isMusicAppRunning 68 | } 69 | 70 | func update() { 71 | Task { 72 | if let musicApp = SBApplication.musicApp { 73 | guard let currentVolume = musicApp.soundVolume else { return } 74 | DispatchQueue.main.async { 75 | if let slider = self.volumeSlider { 76 | slider.doubleValue = Double(currentVolume) 77 | self.speakerIcon?.updateSoundVolume(with: slider) 78 | } 79 | } 80 | } 81 | } 82 | } 83 | 84 | var dummyItem: [NSMenuItem] = [] 85 | 86 | func setup() { 87 | let center = DistributedNotificationCenter.default() 88 | center.addObserver(self, selector: #selector(receiveNotification(_:)), name: NSNotification.Name("com.apple.Music.playerInfo"), object: nil) 89 | 90 | [ShortcutInfo.musicPlay, ShortcutInfo.musicForward, ShortcutInfo.musicBackward].forEach { info in 91 | if let r = KeyboardShortcuts.Name(rawValue: info.uniqueIdentifier.uuidString) { 92 | let dummy = NSMenuItem() 93 | dummy.setShortcut(for: r) 94 | self.dummyItem.append(dummy) 95 | KeyboardShortcuts.onKeyDown(for: r, action: { 96 | self.shortcut(info: info) 97 | }) 98 | } 99 | } 100 | } 101 | 102 | func shortcut(info: ShortcutInfo) { 103 | switch info { 104 | case .musicPlay: 105 | if let musicApp = SBApplication.musicApp { 106 | musicApp.playpause?() 107 | } 108 | case .musicForward: 109 | if let musicApp = SBApplication.musicApp { 110 | musicApp.nextTrack?() 111 | } 112 | case .musicBackward: 113 | if let musicApp = SBApplication.musicApp { 114 | musicApp.previousTrack?() 115 | } 116 | default: 117 | break 118 | } 119 | } 120 | 121 | @IBAction func didPushPlayButton(sender: NSButton) { 122 | if let musicApp = SBApplication.musicApp { 123 | musicApp.playpause?() 124 | } 125 | } 126 | 127 | @IBAction func didPushForwardButton(sender: NSButton) { 128 | if let musicApp = SBApplication.musicApp { 129 | musicApp.nextTrack?() 130 | } 131 | } 132 | 133 | @IBAction func didPushPrevButton(sender: NSButton) { 134 | if let musicApp = SBApplication.musicApp { 135 | musicApp.previousTrack?() 136 | } 137 | } 138 | 139 | @IBAction func didChangeVolume(sender: NSSlider) { 140 | if let musicApp = SBApplication.musicApp { 141 | musicApp.setSoundVolume?(sender.integerValue) 142 | } 143 | self.speakerIcon?.updateSoundVolume(with: sender) 144 | } 145 | 146 | deinit { 147 | Logger.app.debug("\(self) deinit") 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/Music/MusicTrackView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MusicTrackView.swift 3 | // HomeConMenu 4 | // 5 | // Created by sonson on 2024/11/17. 6 | // 7 | 8 | import AppKit 9 | import os 10 | 11 | class MusicTrackView : NSView { 12 | 13 | @IBOutlet var atworkImageView: NSImageView? 14 | @IBOutlet var titleLabel: NSTextField? 15 | @IBOutlet var artistLabel: NSTextField? 16 | @IBOutlet var albumLabel: NSTextField? 17 | 18 | required init?(coder: NSCoder) { 19 | super.init(coder: coder) 20 | let center = DistributedNotificationCenter.default() 21 | center.addObserver(self, selector: #selector(receiveNotification(_:)), name: NSNotification.Name("com.apple.Music.playerInfo"), object: nil) 22 | } 23 | 24 | override init(frame frameRect: NSRect) { 25 | super.init(frame: frameRect) 26 | let center = DistributedNotificationCenter.default() 27 | center.addObserver(self, selector: #selector(receiveNotification(_:)), name: NSNotification.Name("com.apple.Music.playerInfo"), object: nil) 28 | } 29 | 30 | override func awakeFromNib() { 31 | super.awakeFromNib() 32 | self.titleLabel?.stringValue = "" 33 | self.artistLabel?.stringValue = "" 34 | self.albumLabel?.stringValue = "" 35 | } 36 | 37 | static func create(frame frameRect: NSRect) -> MusicTrackView? { 38 | var topLevelObjects: NSArray? = nil 39 | Bundle.main.loadNibNamed("MusicTrackView", owner: self, topLevelObjects: &topLevelObjects) 40 | guard let view = topLevelObjects?.first(where: { $0 is NSView }) as? MusicTrackView else { 41 | return nil 42 | } 43 | view.frame = frameRect 44 | view.autoresizingMask = [.width, .height] 45 | return view 46 | } 47 | 48 | func update() { 49 | Task { 50 | guard let musicApp = SBApplication.musicApp else { return } 51 | guard let currentTrack = musicApp.currentTrack else { return } 52 | guard let artwork = currentTrack.artworks?().first else { return } 53 | guard let title = currentTrack.name else { return } 54 | guard let artist = currentTrack.artist else { return } 55 | guard let album = currentTrack.album else { return } 56 | DispatchQueue.main.async { 57 | self.atworkImageView?.image = artwork.data 58 | self.titleLabel?.stringValue = String(title) 59 | self.artistLabel?.stringValue = String(artist) 60 | self.albumLabel?.stringValue = String(album) 61 | } 62 | } 63 | } 64 | 65 | @objc func receiveNotification(_ notification: NSNotification) { 66 | if let userInfo = notification.userInfo { 67 | if let message = userInfo["Player State"] as? String { 68 | if message == "Playing" { 69 | self.update() 70 | } 71 | if message == "Paused" { 72 | self.update() 73 | } 74 | } 75 | } 76 | } 77 | 78 | deinit { 79 | Logger.app.debug("\(self) deinit") 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/PreferencePanes/DonationItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DonationItemView.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2023/10/08. 6 | // 7 | 8 | import AppKit 9 | 10 | class DonationItemView: NSView { 11 | @IBOutlet var icon: NSTextField? 12 | @IBOutlet var label: NSTextField? 13 | @IBOutlet var donate: NSButton? 14 | } 15 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/PreferencePanes/GeneralPaneController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GeneralPaneController.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2023/09/08. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | import AppKit 30 | import LaunchAtLogin 31 | 32 | class GeneralPaneController: NSViewController { 33 | @objc dynamic var launchAtLogin = LaunchAtLogin.kvo 34 | 35 | @IBOutlet var showLaunchViewController: NSButton? 36 | @IBOutlet var showReloadMenuItemButton: NSButton? 37 | @IBOutlet var showHomeAppMenuItemButton: NSButton? 38 | @IBOutlet var allowDuplicatingServices: NSButton? 39 | @IBOutlet var useScenes: NSButton? 40 | @IBOutlet var alwaysShowHomeItemOnMenu: NSButton? 41 | @IBOutlet var musicControllerShowsButton: NSButton? 42 | 43 | override func viewDidLoad() { 44 | super.viewDidLoad() 45 | NotificationCenter.default.addObserver(self, selector: #selector(self.didChangeUserDefaults), name: UserDefaults.didChangeNotification, object: nil) 46 | } 47 | 48 | @IBAction func didChangeUserDefaults(notification: Notification) { 49 | if let value = UserDefaults.standard.object(forKey: "showLaunchViewController") as? Bool { 50 | showLaunchViewController?.state = value ? .on : .off 51 | } else { 52 | UserDefaults.standard.setValue(true, forKey: "showLaunchViewController") 53 | showLaunchViewController?.state = .on 54 | } 55 | } 56 | 57 | override func viewWillAppear() { 58 | super.viewWillAppear() 59 | 60 | if let value = UserDefaults.standard.object(forKey: "showLaunchViewController") as? Bool { 61 | showLaunchViewController?.state = value ? .on : .off 62 | } else { 63 | UserDefaults.standard.setValue(true, forKey: "showLaunchViewController") 64 | showLaunchViewController?.state = .on 65 | } 66 | 67 | if let value = UserDefaults.standard.object(forKey: "showReloadMenuItem") as? Bool { 68 | showReloadMenuItemButton?.state = value ? .on : .off 69 | } else { 70 | showReloadMenuItemButton?.state = .off 71 | } 72 | 73 | if let value = UserDefaults.standard.object(forKey: "allowDuplicatingServices") as? Bool { 74 | allowDuplicatingServices?.state = value ? .on : .off 75 | } else { 76 | allowDuplicatingServices?.state = .off 77 | } 78 | 79 | if let value = UserDefaults.standard.object(forKey: "useScenes") as? Bool { 80 | useScenes?.state = value ? .on : .off 81 | } else { 82 | useScenes?.state = .off 83 | } 84 | 85 | if let value = UserDefaults.standard.object(forKey: "alwaysShowHomeItemOnMenu") as? Bool { 86 | alwaysShowHomeItemOnMenu?.state = value ? .on : .off 87 | } else { 88 | alwaysShowHomeItemOnMenu?.state = .off 89 | } 90 | 91 | if let value = UserDefaults.standard.object(forKey: "musicControllerShows") as? Bool { 92 | musicControllerShowsButton?.state = value ? .on : .off 93 | } else { 94 | musicControllerShowsButton?.state = .off 95 | } 96 | 97 | } 98 | 99 | @IBAction func didChangeShowHomeAppMenuItemButton(sender: NSButton) { 100 | UserDefaults.standard.set(sender.state == .on, forKey: "showHomeAppMenuItem") 101 | } 102 | 103 | @IBAction func didChangeShowLaunchViewController(sender: NSButton) { 104 | UserDefaults.standard.set(sender.state == .on, forKey: "showLaunchViewController") 105 | } 106 | 107 | @IBAction func didChangeShowReloadMenuItemButton(sender: NSButton) { 108 | UserDefaults.standard.set(sender.state == .on, forKey: "showReloadMenuItem") 109 | } 110 | 111 | @IBAction func didChangeAllowDuplicatingServices(sender: NSButton) { 112 | UserDefaults.standard.set(sender.state == .on, forKey: "allowDuplicatingServices") 113 | } 114 | 115 | @IBAction func didChangeUseScenes(sender: NSButton) { 116 | UserDefaults.standard.set(sender.state == .on, forKey: "useScenes") 117 | } 118 | 119 | @IBAction func didChangeAlwaysShowHomeItemOnMenu(sender: NSButton) { 120 | UserDefaults.standard.set(sender.state == .on, forKey: "alwaysShowHomeItemOnMenu") 121 | } 122 | 123 | @IBAction func didChangeMusicControllerShows(sender: NSButton) { 124 | UserDefaults.standard.set(sender.state == .on, forKey: "musicControllerShows") 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/PreferencePanes/InformationPaneController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InformationPaneController.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2023/09/09. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import AppKit 29 | import WebKit 30 | 31 | class InformationPaneController: NSViewController { 32 | var mac2ios: mac2iOS? 33 | @IBOutlet var versionField: NSTextField? 34 | 35 | let version: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "?.?.?" 36 | let build: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "?" 37 | 38 | @IBAction func openGithub(sender: Any?) { 39 | NSWorkspace.shared.open(URL(string: "https://github.com/sonsongithub/HomeConMenu")!) 40 | } 41 | 42 | @IBAction func openIssueTracker(sender: Any?) { 43 | NSWorkspace.shared.open(URL(string: "https://github.com/sonsongithub/HomeConMenu/issues")!) 44 | } 45 | 46 | @IBAction func openAcknowledgement(sender: Any?) { 47 | if let mac2ios = mac2ios { 48 | mac2ios.openAcknowledgement() 49 | } 50 | NSApp.activate(ignoringOtherApps: true) 51 | } 52 | 53 | override func viewDidLoad() { 54 | super.viewDidLoad() 55 | #if DEBUG 56 | versionField?.stringValue = "\(version) (\(build)) Debug" 57 | #else 58 | versionField?.stringValue = "\(version) (\(build))" 59 | #endif 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/PreferencePanes/ShortcutInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShortcutInfo.swift 3 | // HomeConMenu 4 | // 5 | // Created by Yuichi Yoshida on 2023/09/13. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | import AppKit 30 | 31 | enum ShortcutInfo { 32 | case lightbulb(name: String, uniqueIdentifier: UUID) 33 | case outlet(name: String, uniqueIdentifier: UUID) 34 | case `switch`(name: String, uniqueIdentifier: UUID) 35 | case home(name: String, uniqueIdentifier: UUID) 36 | case musicPlay 37 | case musicForward 38 | case musicBackward 39 | 40 | var name: String { 41 | switch self { 42 | case .lightbulb(let name, _): 43 | return name 44 | case .outlet(let name, _): 45 | return name 46 | case .switch(let name, _): 47 | return name 48 | case .home(let name, _): 49 | return name 50 | case .musicPlay: 51 | return NSLocalizedString("Play/Pause", comment: "") 52 | case .musicForward: 53 | return NSLocalizedString("Next", comment: "") 54 | case .musicBackward: 55 | return NSLocalizedString("Previous", comment: "") 56 | } 57 | } 58 | 59 | var uniqueIdentifier: UUID { 60 | switch self { 61 | case .lightbulb(_, let uniqueIdentifier): 62 | return uniqueIdentifier 63 | case .outlet(_, let uniqueIdentifier): 64 | return uniqueIdentifier 65 | case .switch(_, let uniqueIdentifier): 66 | return uniqueIdentifier 67 | case .home(_, let uniqueIdentifier): 68 | return uniqueIdentifier 69 | case .musicPlay: 70 | return UUID(uuidString: "98119D00-5FB7-46B8-AEF8-BA95D6E98860")! 71 | case .musicForward: 72 | return UUID(uuidString: "DA6E43E7-C9F3-4C32-B339-4AA01B4955FB")! 73 | case .musicBackward: 74 | return UUID(uuidString: "7AB9D57D-0E22-496A-A9F1-C2BC20747213")! 75 | } 76 | } 77 | 78 | var image: NSImage { 79 | switch self { 80 | case .lightbulb(_, _): 81 | return NSImage(systemSymbolName: "lightbulb", accessibilityDescription: nil)! 82 | case .outlet(_, _): 83 | return NSImage(systemSymbolName: "powerplug", accessibilityDescription: nil)! 84 | case .switch(_, _): 85 | return NSImage(systemSymbolName: "switch.2", accessibilityDescription: nil)! 86 | case .home(_, _): 87 | return NSImage(systemSymbolName: "house", accessibilityDescription: nil)! 88 | case .musicPlay: 89 | return NSImage(systemSymbolName: "play.fill", accessibilityDescription: nil)! 90 | case .musicForward: 91 | return NSImage(systemSymbolName: "forward.fill", accessibilityDescription: nil)! 92 | case .musicBackward: 93 | return NSImage(systemSymbolName: "backward.fill", accessibilityDescription: nil)! 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/PreferencePanes/ShortcutsPaneController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShortcutsPaneController.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2023/09/08. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import AppKit 29 | import KeyboardShortcuts 30 | 31 | class ShortcutsPaneController: NSViewController, NSTableViewDataSource, NSTableViewDelegate { 32 | 33 | var shortcutLabels: [ShortcutInfo] = [] 34 | 35 | @IBOutlet var tableView: NSTableView? 36 | 37 | @IBAction func didPushResetButton(sender: NSButton) { 38 | 39 | let alert = NSAlert() 40 | alert.messageText = NSLocalizedString("Key Bindings", comment: "") 41 | alert.informativeText = NSLocalizedString("You will remove all key bindings settings. Are you sure to remove them?", comment:"") 42 | alert.alertStyle = .critical 43 | 44 | alert.addButton(withTitle: NSLocalizedString("Remove All", comment: "")) 45 | alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "")) 46 | 47 | let ret = alert.runModal() 48 | if ret == .alertFirstButtonReturn { 49 | KeyboardShortcuts.reset(shortcutLabels.compactMap({ KeyboardShortcuts.Name($0.uniqueIdentifier.uuidString) })) 50 | self.tableView?.reloadData() 51 | } 52 | } 53 | 54 | func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { 55 | return 30 56 | } 57 | 58 | override func viewWillAppear() { 59 | super.viewWillAppear() 60 | self.tableView?.reloadData() 61 | } 62 | 63 | func numberOfRows(in tableView: NSTableView) -> Int { 64 | return shortcutLabels.count 65 | } 66 | 67 | func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool { 68 | return false 69 | } 70 | 71 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { 72 | guard let tableColumn = tableColumn else { return nil } 73 | 74 | if tableColumn.identifier == NSUserInterfaceItemIdentifier(rawValue: "Device") { 75 | let view = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("DeviceCell"), owner: self) 76 | if let view = view as? NSTableCellView { 77 | view.imageView?.image = shortcutLabels[row].image 78 | view.textField?.stringValue = shortcutLabels[row].name 79 | } 80 | return view 81 | } else if tableColumn.identifier == NSUserInterfaceItemIdentifier(rawValue: "Key") { 82 | let view = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("KeyCell"), owner: self) 83 | if let view = view as? NSTableCellView { 84 | view.subviews.forEach { view in 85 | view.removeFromSuperview() 86 | } 87 | if let r = KeyboardShortcuts.Name(rawValue: shortcutLabels[row].uniqueIdentifier.uuidString) { 88 | let recorder = KeyboardShortcuts.RecorderCocoa(for: r) 89 | view.addSubview(recorder) 90 | view.translatesAutoresizingMaskIntoConstraints = false 91 | recorder.translatesAutoresizingMaskIntoConstraints = false 92 | view.trailingAnchor.constraint(equalTo: recorder.trailingAnchor, constant: 0).isActive = true 93 | recorder.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true 94 | recorder.heightAnchor.constraint(equalToConstant: 20).isActive = true 95 | view.centerYAnchor.constraint(equalTo: recorder.centerYAnchor).isActive = true 96 | recorder.bezelStyle = .squareBezel 97 | } 98 | } 99 | return view 100 | } 101 | return nil 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/PreferencePanes/mul.lproj/DonationPane.xcstrings: -------------------------------------------------------------------------------- 1 | { 2 | "sourceLanguage" : "en", 3 | "strings" : { 4 | "7PB-ST-t86.title" : { 5 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Donate something\"; ObjectID = \"7PB-ST-t86\";", 6 | "extractionState" : "extracted_with_value", 7 | "localizations" : { 8 | "en" : { 9 | "stringUnit" : { 10 | "state" : "new", 11 | "value" : "Donate something" 12 | } 13 | } 14 | }, 15 | "shouldTranslate" : false 16 | }, 17 | "8U7-c7-khC.title" : { 18 | "comment" : "Class = \"NSButtonCell\"; title = \"Button\"; ObjectID = \"8U7-c7-khC\";", 19 | "extractionState" : "extracted_with_value", 20 | "localizations" : { 21 | "en" : { 22 | "stringUnit" : { 23 | "state" : "new", 24 | "value" : "Button" 25 | } 26 | } 27 | }, 28 | "shouldTranslate" : false 29 | }, 30 | "l1C-Ps-PNQ.title" : { 31 | "comment" : "Class = \"NSTextFieldCell\"; title = \"🍺\"; ObjectID = \"l1C-Ps-PNQ\";", 32 | "extractionState" : "extracted_with_value", 33 | "localizations" : { 34 | "en" : { 35 | "stringUnit" : { 36 | "state" : "new", 37 | "value" : "🍺" 38 | } 39 | } 40 | }, 41 | "shouldTranslate" : false 42 | }, 43 | "yxH-8z-2kf.title" : { 44 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Label\"; ObjectID = \"yxH-8z-2kf\";", 45 | "extractionState" : "extracted_with_value", 46 | "localizations" : { 47 | "en" : { 48 | "stringUnit" : { 49 | "state" : "new", 50 | "value" : "Label" 51 | } 52 | } 53 | }, 54 | "shouldTranslate" : false 55 | } 56 | }, 57 | "version" : "1.0" 58 | } -------------------------------------------------------------------------------- /HomeConMenu/macOS/PreferencePanes/mul.lproj/InformationPane.xcstrings: -------------------------------------------------------------------------------- 1 | { 2 | "sourceLanguage" : "en", 3 | "strings" : { 4 | "0xY-yl-ENL.title" : { 5 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Version:\"; ObjectID = \"0xY-yl-ENL\";", 6 | "extractionState" : "extracted_with_value", 7 | "localizations" : { 8 | "de" : { 9 | "stringUnit" : { 10 | "state" : "translated", 11 | "value" : "Version:" 12 | } 13 | }, 14 | "en" : { 15 | "stringUnit" : { 16 | "state" : "new", 17 | "value" : "Version:" 18 | } 19 | }, 20 | "ja" : { 21 | "stringUnit" : { 22 | "state" : "translated", 23 | "value" : "バージョン:" 24 | } 25 | } 26 | } 27 | }, 28 | "1OJ-qY-yUk.title" : { 29 | "comment" : "Class = \"NSTextFieldCell\"; title = \"3.0 (100)\"; ObjectID = \"1OJ-qY-yUk\";", 30 | "extractionState" : "extracted_with_value", 31 | "localizations" : { 32 | "en" : { 33 | "stringUnit" : { 34 | "state" : "new", 35 | "value" : "3.0 (100)" 36 | } 37 | } 38 | }, 39 | "shouldTranslate" : false 40 | }, 41 | "Asg-ui-Ub7.title" : { 42 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Report:\"; ObjectID = \"Asg-ui-Ub7\";", 43 | "extractionState" : "extracted_with_value", 44 | "localizations" : { 45 | "de" : { 46 | "stringUnit" : { 47 | "state" : "translated", 48 | "value" : "Fehler-Bericht:" 49 | } 50 | }, 51 | "en" : { 52 | "stringUnit" : { 53 | "state" : "new", 54 | "value" : "Report:" 55 | } 56 | }, 57 | "ja" : { 58 | "stringUnit" : { 59 | "state" : "translated", 60 | "value" : "レポート:" 61 | } 62 | } 63 | } 64 | }, 65 | "e0K-az-LXZ.title" : { 66 | "comment" : "Class = \"NSButtonCell\"; title = \"Learn More\"; ObjectID = \"e0K-az-LXZ\";", 67 | "extractionState" : "extracted_with_value", 68 | "localizations" : { 69 | "de" : { 70 | "stringUnit" : { 71 | "state" : "translated", 72 | "value" : "Weitere Infos …" 73 | } 74 | }, 75 | "en" : { 76 | "stringUnit" : { 77 | "state" : "new", 78 | "value" : "Learn More" 79 | } 80 | }, 81 | "ja" : { 82 | "stringUnit" : { 83 | "state" : "translated", 84 | "value" : "詳細情報..." 85 | } 86 | } 87 | } 88 | }, 89 | "Fnv-Vf-ZIe.title" : { 90 | "comment" : "Class = \"NSButtonCell\"; title = \"Send via github.com\"; ObjectID = \"Fnv-Vf-ZIe\";", 91 | "extractionState" : "extracted_with_value", 92 | "localizations" : { 93 | "de" : { 94 | "stringUnit" : { 95 | "state" : "translated", 96 | "value" : "Auf GitHub melden" 97 | } 98 | }, 99 | "en" : { 100 | "stringUnit" : { 101 | "state" : "new", 102 | "value" : "Send via github.com" 103 | } 104 | }, 105 | "ja" : { 106 | "stringUnit" : { 107 | "state" : "translated", 108 | "value" : "github.comで送る" 109 | } 110 | } 111 | } 112 | }, 113 | "GRs-qi-tgr.title" : { 114 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Source code:\"; ObjectID = \"GRs-qi-tgr\";", 115 | "extractionState" : "extracted_with_value", 116 | "localizations" : { 117 | "de" : { 118 | "stringUnit" : { 119 | "state" : "translated", 120 | "value" : "Quellcode:" 121 | } 122 | }, 123 | "en" : { 124 | "stringUnit" : { 125 | "state" : "new", 126 | "value" : "Source code:" 127 | } 128 | }, 129 | "ja" : { 130 | "stringUnit" : { 131 | "state" : "translated", 132 | "value" : "ソースコード:" 133 | } 134 | } 135 | } 136 | }, 137 | "Pvk-zR-jJh.title" : { 138 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Acknowledgments:\"; ObjectID = \"Pvk-zR-jJh\";", 139 | "extractionState" : "extracted_with_value", 140 | "localizations" : { 141 | "de" : { 142 | "stringUnit" : { 143 | "state" : "translated", 144 | "value" : "Anmerkungen:" 145 | } 146 | }, 147 | "en" : { 148 | "stringUnit" : { 149 | "state" : "new", 150 | "value" : "Acknowledgments:" 151 | } 152 | }, 153 | "ja" : { 154 | "stringUnit" : { 155 | "state" : "translated", 156 | "value" : "謝辞:" 157 | } 158 | } 159 | } 160 | }, 161 | "sbA-Wg-xaJ.title" : { 162 | "comment" : "Class = \"NSTextFieldCell\"; title = \"Become a sponsor:\"; ObjectID = \"sbA-Wg-xaJ\";", 163 | "extractionState" : "manual", 164 | "localizations" : { 165 | "de" : { 166 | "stringUnit" : { 167 | "state" : "translated", 168 | "value" : "Sponsoren:" 169 | } 170 | }, 171 | "ja" : { 172 | "stringUnit" : { 173 | "state" : "translated", 174 | "value" : "スポンサーになる:" 175 | } 176 | } 177 | } 178 | }, 179 | "XdS-KB-BdG.title" : { 180 | "comment" : "Class = \"NSButtonCell\"; title = \"Open github.com\"; ObjectID = \"XdS-KB-BdG\";", 181 | "extractionState" : "extracted_with_value", 182 | "localizations" : { 183 | "de" : { 184 | "stringUnit" : { 185 | "state" : "translated", 186 | "value" : "Auf GitHub anzeigen" 187 | } 188 | }, 189 | "en" : { 190 | "stringUnit" : { 191 | "state" : "new", 192 | "value" : "Open github.com" 193 | } 194 | }, 195 | "ja" : { 196 | "stringUnit" : { 197 | "state" : "translated", 198 | "value" : "github.comを開く" 199 | } 200 | } 201 | } 202 | } 203 | }, 204 | "version" : "1.0" 205 | } -------------------------------------------------------------------------------- /HomeConMenu/macOS/PreferencePanes/mul.lproj/ShortcutsPane.xcstrings: -------------------------------------------------------------------------------- 1 | { 2 | "sourceLanguage" : "en", 3 | "strings" : { 4 | "Jg4-tM-fSd.headerCell.title" : { 5 | "comment" : "Class = \"NSTableColumn\"; headerCell.title = \"Device\"; ObjectID = \"Jg4-tM-fSd\";", 6 | "extractionState" : "extracted_with_value", 7 | "localizations" : { 8 | "de" : { 9 | "stringUnit" : { 10 | "state" : "translated", 11 | "value" : "Gerät" 12 | } 13 | }, 14 | "en" : { 15 | "stringUnit" : { 16 | "state" : "new", 17 | "value" : "Device" 18 | } 19 | }, 20 | "ja" : { 21 | "stringUnit" : { 22 | "state" : "translated", 23 | "value" : "デバイス" 24 | } 25 | } 26 | } 27 | }, 28 | "qQU-rG-fch.headerCell.title" : { 29 | "comment" : "Class = \"NSTableColumn\"; headerCell.title = \"Key\"; ObjectID = \"qQU-rG-fch\";", 30 | "extractionState" : "extracted_with_value", 31 | "localizations" : { 32 | "de" : { 33 | "stringUnit" : { 34 | "state" : "translated", 35 | "value" : "Taste" 36 | } 37 | }, 38 | "en" : { 39 | "stringUnit" : { 40 | "state" : "new", 41 | "value" : "Key" 42 | } 43 | }, 44 | "ja" : { 45 | "stringUnit" : { 46 | "state" : "translated", 47 | "value" : "キー" 48 | } 49 | } 50 | } 51 | } 52 | }, 53 | "version" : "1.0" 54 | } -------------------------------------------------------------------------------- /HomeConMenu/macOS/Preferences/SettingsPane.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsPane.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2023/09/08. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | 30 | enum SettingsPane: String, CaseIterable { 31 | 32 | case general 33 | case shortcuts 34 | case information 35 | // case donation 36 | 37 | /// Localized label. 38 | var label: String { 39 | 40 | switch self { 41 | case .general: 42 | return String(localized: "General") 43 | case .shortcuts: 44 | return String(localized: "Key Bindings") 45 | case .information: 46 | return String(localized: "Information") 47 | // case .donation: 48 | // return String(localized: "Donation") 49 | } 50 | } 51 | 52 | /// Symbol image name. 53 | var symbolName: String { 54 | 55 | switch self { 56 | case .general: 57 | return "gearshape" 58 | case .shortcuts: 59 | return "keyboard" 60 | case .information: 61 | return "info.circle.fill" 62 | // case .donation: 63 | // return "heart" 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/Preferences/SettingsTabViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsTabViewController.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2023/09/08. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import AppKit 29 | 30 | final class SettingsTabViewController: NSTabViewController { 31 | 32 | // MARK: Tab View Controller Methods 33 | 34 | override var selectedTabViewItemIndex: Int { 35 | 36 | didSet { 37 | if self.isViewLoaded { 38 | UserDefaults.standard.set(self.tabViewItems[selectedTabViewItemIndex].identifier, forKey: "lastSettingsPaneIdentifier") 39 | } 40 | } 41 | } 42 | 43 | override func viewDidLoad() { 44 | 45 | super.viewDidLoad() 46 | 47 | // select last used pane 48 | if let identifier = UserDefaults.standard.string(forKey: "lastSettingsPaneIdentifier"), 49 | let index = self.tabViewItems.firstIndex(where: { $0.identifier as? String == identifier }) { 50 | self.selectedTabViewItemIndex = index 51 | } 52 | } 53 | 54 | override func viewWillAppear() { 55 | super.viewWillAppear() 56 | self.view.window!.title = self.tabViewItems[self.selectedTabViewItemIndex].label 57 | } 58 | 59 | override func tabView(_ tabView: NSTabView, willSelect tabViewItem: NSTabViewItem?) { 60 | 61 | super.tabView(tabView, willSelect: tabViewItem) 62 | 63 | guard let tabViewItem else { return assertionFailure() } 64 | 65 | if let viewController = tabViewItem.viewController as? DonationPaneController { 66 | viewController.didShowOnTabView() 67 | } 68 | 69 | self.switchPane(to: tabViewItem) 70 | } 71 | 72 | // MARK: Private Methods 73 | 74 | /// Resize window to fit to the new view. 75 | /// 76 | /// - Parameter tabViewItem: The tab view item to switch. 77 | private func switchPane(to tabViewItem: NSTabViewItem) { 78 | 79 | guard let contentSize = tabViewItem.view?.frame.size else { return assertionFailure() } 80 | 81 | // initialize tabView's frame size 82 | guard let window = self.view.window else { 83 | self.view.frame.size = contentSize 84 | return 85 | } 86 | 87 | NSAnimationContext.runAnimationGroup { _ in 88 | self.view.isHidden = true 89 | window.animator().setFrame(for: contentSize) 90 | 91 | } completionHandler: { [weak self] in 92 | self?.view.isHidden = false 93 | window.title = tabViewItem.label 94 | } 95 | } 96 | } 97 | 98 | private extension NSWindow { 99 | 100 | /// Update window frame for the given contentSize. 101 | /// 102 | /// - Parameters: 103 | /// - contentSize: The frame rectangle for the window content view. 104 | /// - flag: Specifies whether the window redraws the views that need to be displayed. 105 | func setFrame(for contentSize: NSSize, display flag: Bool = false) { 106 | 107 | let frameSize = self.frameRect(forContentRect: NSRect(origin: .zero, size: contentSize)).size 108 | let frame = NSRect(origin: self.frame.origin, size: frameSize) 109 | .offsetBy(dx: 0, dy: self.frame.height - frameSize.height) 110 | 111 | self.setFrame(frame, display: flag) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/Preferences/SettingsWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsWindow.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2023/09/08. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import AppKit 29 | 30 | final class SettingsWindow: NSPanel { 31 | 32 | // MARK: Panel Methods 33 | 34 | /// disable "Hide Toolbar" menu item 35 | override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool { 36 | switch menuItem.action { 37 | case #selector(toggleToolbarShown): 38 | return false 39 | default: 40 | return super.validateMenuItem(menuItem) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /HomeConMenu/macOS/Preferences/SettingsWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsWindowController.swift 3 | // macOSBridge 4 | // 5 | // Created by Yuichi Yoshida on 2023/09/08. 6 | // 7 | // MIT License 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining a copy 10 | // of this software and associated documentation files (the "Software"), to deal 11 | // in the Software without restriction, including without limitation the rights 12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the Software is 14 | // furnished to do so, subject to the following conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be included in all 17 | // copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | // SOFTWARE. 26 | // 27 | 28 | import Foundation 29 | import AppKit 30 | 31 | final class SettingsWindowController: NSWindowController { 32 | 33 | var settingsTabViewController: SettingsTabViewController? 34 | 35 | convenience init() { 36 | let viewController = SettingsTabViewController() 37 | viewController.tabStyle = .toolbar 38 | viewController.canPropagateSelectedChildViewControllerTitle = false 39 | viewController.tabViewItems = SettingsPane.allCases.map(\.tabViewItem) 40 | 41 | let window = SettingsWindow(contentViewController: viewController) 42 | window.styleMask = [.closable, .titled] 43 | window.hidesOnDeactivate = false 44 | window.isReleasedWhenClosed = true 45 | 46 | self.init(window: window) 47 | self.settingsTabViewController = viewController 48 | } 49 | 50 | 51 | // MARK: Public Methods 52 | 53 | /// Open specific setting pane. 54 | /// 55 | /// - Parameter pane: The pane to display. 56 | func openPane(_ pane: SettingsPane) { 57 | 58 | let index = SettingsPane.allCases.firstIndex(of: pane)! 59 | (self.contentViewController as? NSTabViewController)?.selectedTabViewItemIndex = index 60 | 61 | self.showWindow(nil) 62 | NSApp.activate(ignoringOtherApps: true) 63 | } 64 | } 65 | 66 | // MARK: - 67 | 68 | private extension SettingsPane { 69 | 70 | var tabViewItem: NSTabViewItem { 71 | 72 | let viewController: NSViewController = self.resource 73 | 74 | let tabViewItem = NSTabViewItem(viewController: viewController) 75 | tabViewItem.label = self.label 76 | tabViewItem.image = NSImage(systemSymbolName: self.symbolName, accessibilityDescription: self.label) 77 | tabViewItem.identifier = self.rawValue 78 | 79 | return tabViewItem 80 | } 81 | 82 | private var resource: NSViewController { 83 | switch self { 84 | case .general: 85 | return GeneralPaneController(nibName: NSNib.Name("GeneralPane"), bundle: nil) 86 | case .shortcuts: 87 | return ShortcutsPaneController(nibName: NSNib.Name("ShortcutsPane"), bundle: nil) 88 | case .information: 89 | return InformationPaneController(nibName: NSNib.Name("InformationPane"), bundle: nil) 90 | // case .donation: 91 | // return DonationPaneController(nibName: NSNib.Name("DonationPane"), bundle: nil) 92 | } 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /HomeConMenu/macOSBridge-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "Music.h" 6 | -------------------------------------------------------------------------------- /HomeConMenu/macOSBridge-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 sonson 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 | ## HomeConMenu 2 | 3 | 4 | Menu Bar Extra to control HomeKit Devices 5 | 6 | ## Movie 7 | 8 | https://user-images.githubusercontent.com/33768/197396558-0a3675d9-df77-44e5-9112-0a616991e915.mov 9 | 10 | ## Supported devices 11 | 12 | 1. Light bulb(including brightness, color) 13 | 2. Switch 14 | 3. Display climate information 15 | 16 | Do you have any requests...? - [Issue](https://github.com/sonsongithub/HomeConMenu/issues) 17 | 18 | ## Download 19 | 20 | HomeConMenu is available for free only on the [Mac App Store](https://apps.apple.com/us/app/homeconmenu/id1615397537) only, because Apple does not allow us to sign and notarize binaries that use HomeKit framework. 21 | To tell the truth, I don't want to publish on the [Mac App Store](https://apps.apple.com/us/app/homeconmenu/id1615397537) because it would be too much trouble. 22 | 23 | 24 | 25 | ## Privacy control 26 | 27 | For testing, you can reset the status of privacy control to access HomeKit. 28 | 29 | 30 | ``` 31 | tccutil reset Willow com.sonson.HomeConMenu.macOS 32 | ``` 33 | -------------------------------------------------------------------------------- /python/accessory_category_types.txt: -------------------------------------------------------------------------------- 1 | Light 2 | let HMAccessoryCategoryTypeLightbulb: String 3 | A lightbulb accessory. 4 | Power and Switches 5 | let HMAccessoryCategoryTypeOutlet: String 6 | An outlet accessory. 7 | let HMAccessoryCategoryTypeProgrammableSwitch: String 8 | A programmable switch accessory. 9 | let HMAccessoryCategoryTypeSwitch: String 10 | A switch accessory. 11 | Air Quality and Smoke Detection 12 | let HMAccessoryCategoryTypeFan: String 13 | A fan accessory. 14 | let HMAccessoryCategoryTypeAirPurifier: String 15 | An air purifier accessory. 16 | Temperature and Humidity 17 | let HMAccessoryCategoryTypeThermostat: String 18 | A thermostat accessory. 19 | let HMAccessoryCategoryTypeAirConditioner: String 20 | An air conditioner accessory. 21 | let HMAccessoryCategoryTypeAirDehumidifier: String 22 | A dehumidifier accessory. 23 | let HMAccessoryCategoryTypeAirHeater: String 24 | An air heater accessory. 25 | let HMAccessoryCategoryTypeAirHumidifier: String 26 | A humidifier accessory. 27 | Windows 28 | let HMAccessoryCategoryTypeWindow: String 29 | A window accessory. 30 | let HMAccessoryCategoryTypeWindowCovering: String 31 | A window covering accessory. 32 | Locks and Openers 33 | let HMAccessoryCategoryTypeDoor: String 34 | A door accessory. 35 | let HMAccessoryCategoryTypeDoorLock: String 36 | A door lock accessory. 37 | let HMAccessoryCategoryTypeGarageDoorOpener: String 38 | A garage door opener accessory. 39 | let HMAccessoryCategoryTypeVideoDoorbell: String 40 | A video doorbell accessory. 41 | Safety and Security 42 | let HMAccessoryCategoryTypeSensor: String 43 | A sensor accessory. 44 | let HMAccessoryCategoryTypeSecuritySystem: String 45 | A security system accessory. 46 | Cameras 47 | let HMAccessoryCategoryTypeIPCamera: String 48 | A networked camera accessory. 49 | Water 50 | let HMAccessoryCategoryTypeSprinkler: String 51 | A sprinkler system accessory. 52 | let HMAccessoryCategoryTypeFaucet: String 53 | A faucet accessory. 54 | let HMAccessoryCategoryTypeShowerHead: String 55 | A shower head accessory. 56 | Network 57 | let HMAccessoryCategoryTypeBridge: String 58 | A bridge accessory. 59 | let HMAccessoryCategoryTypeRangeExtender: String 60 | A range extender accessory. 61 | Uncategorized 62 | let HMAccessoryCategoryTypeOther: String 63 | An uncategorized accessory. 64 | -------------------------------------------------------------------------------- /python/accessory_service_types.txt: -------------------------------------------------------------------------------- 1 | Light 2 | let HMServiceTypeLightbulb: String 3 | A light bulb service. 4 | let HMServiceTypeLightSensor: String 5 | A light sensor service. 6 | Power and Switches 7 | let HMServiceTypeSwitch: String 8 | A switch service. 9 | let HMServiceTypeBattery: String 10 | A battery service. 11 | let HMServiceTypeOutlet: String 12 | An outlet service. 13 | let HMServiceTypeStatefulProgrammableSwitch: String 14 | A stateful programmable switch service. 15 | let HMServiceTypeStatelessProgrammableSwitch: String 16 | A stateless programmable switch service. 17 | Air Quality and Smoke Detection 18 | let HMServiceTypeAirPurifier: String 19 | An air purifier service. 20 | let HMServiceTypeAirQualitySensor: String 21 | An air quality sensor service. 22 | let HMServiceTypeCarbonDioxideSensor: String 23 | A carbon dioxide sensor service. 24 | let HMServiceTypeCarbonMonoxideSensor: String 25 | A carbon monoxide sensor service. 26 | let HMServiceTypeSmokeSensor: String 27 | A smoke sensor service. 28 | Temperature and Humidity 29 | let HMServiceTypeHeaterCooler: String 30 | A heater or cooler service. 31 | let HMServiceTypeTemperatureSensor: String 32 | A temperature sensor service. 33 | let HMServiceTypeThermostat: String 34 | A thermostat service. 35 | let HMServiceTypeFan: String 36 | A fan service. 37 | let HMServiceTypeFilterMaintenance: String 38 | A filter maintenance service. 39 | let HMServiceTypeHumidifierDehumidifier: String 40 | A humidifier or dehumidifier service. 41 | let HMServiceTypeHumiditySensor: String 42 | A humidity sensor service. 43 | let HMServiceTypeVentilationFan: String 44 | A ventilation fan service. 45 | Windows 46 | let HMServiceTypeWindow: String 47 | A window service. 48 | let HMServiceTypeWindowCovering: String 49 | A window covering service. 50 | let HMServiceTypeSlats: String 51 | A slats service. 52 | Water 53 | let HMServiceTypeFaucet: String 54 | A faucet service. 55 | let HMServiceTypeValve: String 56 | A valve service. 57 | let HMServiceTypeIrrigationSystem: String 58 | An irrigation system service. 59 | let HMServiceTypeLeakSensor: String 60 | A leak sensor service. 61 | Locks and Openers 62 | let HMServiceTypeDoor: String 63 | A door service. 64 | let HMServiceTypeDoorbell: String 65 | A doorbell service. 66 | let HMServiceTypeGarageDoorOpener: String 67 | A garage door opener service. 68 | let HMServiceTypeLockManagement: String 69 | A lock management service. 70 | let HMServiceTypeLockMechanism: String 71 | A lock mechanism service. 72 | Safety and Security 73 | let HMServiceTypeMotionSensor: String 74 | A motion sensor service. 75 | let HMServiceTypeOccupancySensor: String 76 | An occupancy sensor service. 77 | let HMServiceTypeSecuritySystem: String 78 | A security system service. 79 | let HMServiceTypeContactSensor: String 80 | A contact sensor service. 81 | Video and Audio 82 | let HMServiceTypeCameraControl: String 83 | A camera control service. 84 | let HMServiceTypeCameraRTPStreamManagement: String 85 | A stream management service. 86 | let HMServiceTypeMicrophone: String 87 | A microphone service. 88 | let HMServiceTypeSpeaker: String 89 | An audio speaker service. 90 | Information 91 | let HMServiceTypeLabel: String 92 | A label namespace service used when an accessory supports multiple services of the same type. 93 | let HMServiceTypeAccessoryInformation: String 94 | An accessory information service. 95 | -------------------------------------------------------------------------------- /python/create_class_from_txt.py: -------------------------------------------------------------------------------- 1 | 2 | import json 3 | import re 4 | 5 | def write_source(enum_name, definitions, descriptions): 6 | str = """ 7 | #if !os(macOS) 8 | import HomeKit 9 | #endif 10 | 11 | """ 12 | 13 | str = str + f'@objc public enum {enum_name}: Int {{\n' 14 | for define, desc in zip(definitions, descriptions): 15 | str = str + f' case {define["element"]} // {desc}\n' 16 | 17 | str = str + f' case unknown\n' 18 | 19 | str = str + f'\n\n' 20 | str = str + f' func detail() -> String {{\n' 21 | str = str + f' switch self {{\n' 22 | for define, desc in zip(definitions, descriptions): 23 | str = str + f' case .{define["element"]}:\n' 24 | str = str + f' return "{desc}"\n' 25 | 26 | str = str + f' case .unknown:\n' 27 | str = str + f' return "Unknown"\n' 28 | str = str + f' }}\n' 29 | str = str + f' }}\n' 30 | str = str + f'\n' 31 | str = str + f'\n' 32 | str = str + f'#if !os(macOS)\n' 33 | str = str + f' init(key: String) {{\n' 34 | 35 | str = str + f' switch key {{\n' 36 | for define, desc in zip(definitions, descriptions): 37 | str = str + f' case {define["original"]}:\n' 38 | str = str + f' self = .{define["element"]}\n' 39 | str = str + f' default:\n' 40 | str = str + f' self = .unknown\n' 41 | str = str + f' }}\n' 42 | str = str + f' }}\n' 43 | str = str + f'#endif\n' 44 | 45 | 46 | str = str + f'}}\n' 47 | return str 48 | 49 | def main(): 50 | 51 | entries = [ 52 | { 53 | 'filename': '../HomeMenu/bridge/AccessoryTypeBridge.swift', 54 | 'enum_name': 'AccessoryType', 55 | 'file': 'accessory_category_types.txt', 56 | 'template': r'(let (HMAccessoryCategoryType(.+?)): String$)' 57 | }, 58 | { 59 | 'filename': '../HomeMenu/bridge/ServiceTypeBridge.swift', 60 | 'enum_name': 'ServiceType', 61 | 'file': 'accessory_service_types.txt', 62 | 'template': r'(let (HMServiceType(.+?)): String$)' 63 | }, 64 | { 65 | 'filename': '../HomeMenu/bridge/CharacteristicTypeBridge.swift', 66 | 'enum_name': 'CharacteristicType', 67 | 'file': 'characteristic_types.txt', 68 | 'template': r'(let (HMCharacteristicType(.+?)): String$)' 69 | } 70 | ] 71 | 72 | for entry in entries: 73 | with open(entry['file']) as fr: 74 | lines = fr.readlines() 75 | 76 | definitions = [] 77 | descriptions = [] 78 | 79 | for line in lines: 80 | # case 1 description 81 | template = r'(.+\.)\n' 82 | a = re.findall(template, line) 83 | if len(a) > 0: 84 | descriptions.append(a[0]) 85 | continue 86 | 87 | # case 2 definition 88 | template = entry['template'] 89 | a = re.findall(template, line) 90 | if len(a) > 0: 91 | element = a[0][2] 92 | element = element[0].lower() + element[1:] 93 | info = { 94 | "original": a[0][1], 95 | "element": element 96 | } 97 | definitions.append(info) 98 | continue 99 | 100 | message = f'{len(descriptions)} vs {len(definitions)}' 101 | assert len(descriptions) == len(definitions), message 102 | 103 | str = write_source(entry['enum_name'], definitions, descriptions) 104 | with open(entry["filename"], 'w') as fw: 105 | fw.write(str) 106 | main() --------------------------------------------------------------------------------