├── .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 |
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()
--------------------------------------------------------------------------------