├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Micro Sniff ├── .DS_Store ├── Micro Sniff Launcher │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Info.plist │ └── MicroSniffLauncher.entitlements ├── Micro Sniff.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcshareddata │ │ └── xcschemes │ │ ├── Micro Sniff Launcher.xcscheme │ │ └── Micro Sniff.xcscheme ├── Micro Sniff │ ├── .DS_Store │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── .DS_Store │ │ ├── AppIcon.appiconset │ │ │ ├── 1024.png │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 256.png │ │ │ ├── 32.png │ │ │ ├── 512.png │ │ │ ├── 64.png │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── ico_app.imageset │ │ │ ├── Contents.json │ │ │ └── MacroSnitch.png │ │ ├── ico_compass.imageset │ │ │ ├── Contents.json │ │ │ └── ico_compass.pdf │ │ ├── ico_email.imageset │ │ │ ├── Contents.json │ │ │ └── ico_email.pdf │ │ ├── ico_github.imageset │ │ │ ├── Contents.json │ │ │ └── ico_github.pdf │ │ ├── ico_overlay_mic.imageset │ │ │ ├── Contents.json │ │ │ └── ico_overlay_mic.pdf │ │ ├── ico_statusbar.imageset │ │ │ ├── Contents.json │ │ │ └── ico_statusbar.png │ │ ├── ico_twitter.imageset │ │ │ ├── Contents.json │ │ │ └── ico_twitter.pdf │ │ └── img_background.imageset │ │ │ ├── Contents.json │ │ │ ├── img_background-1.pdf │ │ │ ├── img_background.pdf │ │ │ └── img_background_dark.pdf │ ├── Base.lproj │ │ └── Main.storyboard │ ├── Extension │ │ ├── Bundle+Extension.swift │ │ ├── Preferences+Extension.swift │ │ └── UserDefault+Extension.swift │ ├── Helper │ │ ├── AMCoreAudio │ │ │ ├── AMCoreAudio.h │ │ │ ├── Info.plist │ │ │ ├── Internal │ │ │ │ ├── AudioObjectPool.swift │ │ │ │ ├── Extensions │ │ │ │ │ ├── AudioObject+Helpers.swift │ │ │ │ │ └── Bool+Extensions.swift │ │ │ │ └── Utils.swift │ │ │ └── Public │ │ │ │ ├── AudioDevice.swift │ │ │ │ ├── AudioHardware.swift │ │ │ │ ├── AudioObject.swift │ │ │ │ ├── AudioStream.swift │ │ │ │ ├── BundleInfo.swift │ │ │ │ ├── Enums │ │ │ │ ├── AudioDeviceEvent.swift │ │ │ │ ├── AudioHardwareEvent.swift │ │ │ │ ├── AudioStreamEvent.swift │ │ │ │ ├── Direction.swift │ │ │ │ ├── TerminalType.swift │ │ │ │ └── TransportType.swift │ │ │ │ ├── Models │ │ │ │ └── VolumeInfo.swift │ │ │ │ ├── NotificationCenter.swift │ │ │ │ └── Protocols │ │ │ │ ├── Event.swift │ │ │ │ └── EventSubscriber.swift │ │ ├── MicroManager.swift │ │ ├── NotificationManager.swift │ │ ├── Preference.swift │ │ └── Util.swift │ ├── MicroSniff.entitlements │ ├── StatusBarController.swift │ ├── Views │ │ ├── HyperlinkTextField.swift │ │ ├── MicroWindow.swift │ │ └── Preferences │ │ │ ├── About │ │ │ ├── AboutPreferenceViewController.swift │ │ │ └── AboutPreferenceViewController.xib │ │ │ └── General │ │ │ ├── GeneralPreferenceViewController.swift │ │ │ └── GeneralPreferenceViewController.xib │ └── info.plist └── Shared │ └── LauncherManager.swift ├── PRIVACY_POLICY.md ├── README.md └── misc ├── appstore.svg ├── guide.gif ├── icon.png └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # Xcode 5 | # 6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 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 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Please fully test whether the existing features are affected by your modifications. 13 | 3. Follow the git-flow workflow. You can see more about git-flow [here](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow) 14 | 15 | ### Branch name convention 16 | 17 | The branch name meaning should be clear and follow these conventions: 18 | - Feature branch should be `feature/add-something` 19 | - Bugfix branch should be `bugfix/fix-something` 20 | 21 | ## Code of Conduct 22 | 23 | ### Our Pledge 24 | 25 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 26 | 27 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 28 | 29 | ### Our Standards 30 | 31 | Examples of behavior that contributes to a positive environment for our community include: 32 | 33 | * Demonstrating empathy and kindness toward other people 34 | * Being respectful of differing opinions, viewpoints, and experiences 35 | * Giving and gracefully accepting constructive feedback 36 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 37 | * Focusing on what is best not just for us as individuals, but for the overall community 38 | 39 | Examples of unacceptable behavior include: 40 | 41 | * The use of sexualized language or imagery, and sexual attention or 42 | advances of any kind 43 | * Trolling, insulting or derogatory comments, and personal or political attacks 44 | * Public or private harassment 45 | * Publishing others' private information, such as a physical or email 46 | address, without their explicit permission 47 | * Other conduct which could reasonably be considered inappropriate in a 48 | professional setting 49 | 50 | ### Enforcement Responsibilities 51 | 52 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 53 | 54 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 55 | 56 | ### Scope 57 | 58 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 59 | 60 | ### Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly. 63 | 64 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 65 | 66 | ### Enforcement Guidelines 67 | 68 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 69 | 70 | #### 1. Correction 71 | 72 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 73 | 74 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 75 | 76 | #### 2. Warning 77 | 78 | **Community Impact**: A violation through a single incident or series of actions. 79 | 80 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 81 | 82 | #### 3. Temporary Ban 83 | 84 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 85 | 86 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 87 | 88 | #### 4. Permanent Ban 89 | 90 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 91 | 92 | **Consequence**: A permanent ban from any sort of public interaction within the project community. 93 | 94 | ### Attribution 95 | 96 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, 97 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 98 | 99 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 100 | 101 | [homepage]: https://www.contributor-covenant.org 102 | 103 | For answers to common questions about this code of conduct, see the FAQ at 104 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dwarves Foundation 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 | -------------------------------------------------------------------------------- /Micro Sniff/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/Micro Sniff/.DS_Store -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff Launcher/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MicMonitorLauncher 4 | // 5 | // Created by phucld on 2/29/20. 6 | // Copyright © 2020 Dwarvesf. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | 15 | 16 | func applicationDidFinishLaunching(_ aNotification: Notification) { 17 | LauncherManager.shared.setupLauncher() 18 | } 19 | 20 | func applicationWillTerminate(_ aNotification: Notification) { 21 | // Insert code here to tear down your application 22 | } 23 | 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff Launcher/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff Launcher/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff Launcher/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSApplicationCategoryType 24 | public.app-category.utilities 25 | LSBackgroundOnly 26 | 27 | LSMinimumSystemVersion 28 | $(MACOSX_DEPLOYMENT_TARGET) 29 | NSHumanReadableCopyright 30 | Copyright © 2020 Dwarvesf. All rights reserved. 31 | NSMainStoryboardFile 32 | Main 33 | NSPrincipalClass 34 | NSApplication 35 | NSSupportsAutomaticTermination 36 | 37 | NSSupportsSuddenTermination 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff Launcher/MicroSniffLauncher.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Preferences", 6 | "repositoryURL": "https://github.com/sindresorhus/Preferences", 7 | "state": { 8 | "branch": null, 9 | "revision": "139484818fe4b6b89823fa462cab686b3a419928", 10 | "version": "1.0.1" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff.xcodeproj/xcshareddata/xcschemes/Micro Sniff Launcher.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff.xcodeproj/xcshareddata/xcschemes/Micro Sniff.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/Micro Sniff/Micro Sniff/.DS_Store -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MicMonitor 4 | // 5 | // Created by Trung Phan on 2/17/20. 6 | // Copyright © 2020 Dwarvesf. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | let statusBarController = StatusBarController() 14 | 15 | func applicationDidFinishLaunching(_ aNotification: Notification) { 16 | 17 | LauncherManager.shared.setupMainApp(isAutoStart: Preference.startAtLogin) 18 | 19 | registerDefaultValues() 20 | 21 | Util.toggleDockIcon(Preference.dockIconState) 22 | 23 | if Preference.showPreferencesOnlaunch { 24 | statusBarController.preferencesWindowController.show() 25 | } 26 | } 27 | 28 | 29 | func applicationWillTerminate(_ aNotification: Notification) { 30 | 31 | } 32 | 33 | func registerDefaultValues() { 34 | UserDefaults.standard.register(defaults: [ 35 | UserDefaults.Key.dockIconState: DockIconState.hide.rawValue, 36 | UserDefaults.Key.startAtLogin: false, 37 | UserDefaults.Key.showPreferencesOnLaunch: true 38 | ]) 39 | } 40 | 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/Micro Sniff/Micro Sniff/Assets.xcassets/.DS_Store -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/Micro Sniff/Micro Sniff/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/AppIcon.appiconset/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/Micro Sniff/Micro Sniff/Assets.xcassets/AppIcon.appiconset/128.png -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/AppIcon.appiconset/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/Micro Sniff/Micro Sniff/Assets.xcassets/AppIcon.appiconset/16.png -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/AppIcon.appiconset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/Micro Sniff/Micro Sniff/Assets.xcassets/AppIcon.appiconset/256.png -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/AppIcon.appiconset/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/Micro Sniff/Micro Sniff/Assets.xcassets/AppIcon.appiconset/32.png -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/AppIcon.appiconset/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/Micro Sniff/Micro Sniff/Assets.xcassets/AppIcon.appiconset/512.png -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/AppIcon.appiconset/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/Micro Sniff/Micro Sniff/Assets.xcassets/AppIcon.appiconset/64.png -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/ico_app.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "MacroSnitch.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/ico_app.imageset/MacroSnitch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/Micro Sniff/Micro Sniff/Assets.xcassets/ico_app.imageset/MacroSnitch.png -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/ico_compass.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ico_compass.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/ico_compass.imageset/ico_compass.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << /ExtGState << /E1 << /ca 0.850000 >> >> >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | /E1 gs 14 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 15 | 0.000000 0.000000 0.000000 scn 16 | 8.473215 8.026785 m 17 | 8.473215 7.872024 8.424108 7.738095 8.325893 7.625000 c 18 | 8.227679 7.511905 8.104167 7.455357 7.955358 7.455357 c 19 | 7.800596 7.455357 7.666667 7.504464 7.553572 7.602678 c 20 | 7.440476 7.700892 7.383929 7.824405 7.383929 7.973214 c 21 | 7.383929 8.127975 7.434524 8.261904 7.535715 8.375000 c 22 | 7.636905 8.488094 7.761905 8.544642 7.910715 8.544642 c 23 | 8.059525 8.544642 8.190476 8.495535 8.303572 8.397322 c 24 | 8.416667 8.299107 8.473215 8.175594 8.473215 8.026785 c 25 | h 26 | 8.607143 7.508928 m 27 | 11.732143 12.696428 l 28 | 11.678572 12.648809 11.477679 12.462797 11.129465 12.138392 c 29 | 10.781251 11.813988 10.407739 11.467262 10.008929 11.098214 c 30 | 9.610120 10.729166 9.203870 10.351191 8.790179 9.964285 c 31 | 8.376489 9.577381 8.028275 9.248512 7.745536 8.977678 c 32 | 7.462798 8.706844 7.312500 8.553572 7.294643 8.517857 c 33 | 4.178572 3.339285 l 34 | 4.220238 3.380952 4.419643 3.565475 4.776786 3.892857 c 35 | 5.133929 4.220238 5.508929 4.566964 5.901786 4.933035 c 36 | 6.294643 5.299107 6.699405 5.677083 7.116072 6.066964 c 37 | 7.532738 6.456845 7.880953 6.787202 8.160715 7.058035 c 38 | 8.440476 7.328869 8.589286 7.479166 8.607143 7.508928 c 39 | h 40 | 14.383929 8.000000 m 41 | 14.383929 6.803571 14.074406 5.699405 13.455358 4.687499 c 42 | 13.437501 4.699405 13.386906 4.732142 13.303572 4.785714 c 43 | 13.220239 4.839285 13.141370 4.888392 13.066965 4.933035 c 44 | 12.992560 4.977678 12.943453 4.999999 12.919643 4.999999 c 45 | 12.842262 4.999999 12.803572 4.961309 12.803572 4.883928 c 46 | 12.803572 4.824405 12.979167 4.693452 13.330358 4.491071 c 47 | 12.889881 3.824405 12.340775 3.257440 11.683036 2.790178 c 48 | 11.025298 2.322916 10.306548 1.994047 9.526786 1.803571 c 49 | 9.383929 2.401785 l 50 | 9.377976 2.461308 9.333334 2.491071 9.250000 2.491071 c 51 | 9.220239 2.491071 9.196429 2.474702 9.178572 2.441964 c 52 | 9.160715 2.409225 9.154762 2.380952 9.160715 2.357142 c 53 | 9.303572 1.749999 l 54 | 8.875000 1.660713 8.440476 1.616071 8.000000 1.616071 c 55 | 6.815476 1.616071 5.708333 1.928571 4.678572 2.553571 c 56 | 4.684524 2.565475 4.723215 2.626488 4.794643 2.736607 c 57 | 4.866072 2.846725 4.930060 2.946428 4.986608 3.035714 c 58 | 5.043155 3.124999 5.071429 3.181547 5.071429 3.205357 c 59 | 5.071429 3.282738 5.032738 3.321428 4.955358 3.321428 c 60 | 4.919643 3.321428 4.869048 3.278274 4.803572 3.191964 c 61 | 4.738095 3.105654 4.671131 3.002975 4.602679 2.883928 c 62 | 4.534226 2.764880 4.494048 2.696428 4.482143 2.678571 c 63 | 3.809524 3.124999 3.238095 3.683035 2.767857 4.352678 c 64 | 2.297619 5.022321 1.970238 5.750000 1.785714 6.535714 c 65 | 2.401786 6.669642 l 66 | 2.461310 6.687500 2.491071 6.732142 2.491071 6.803571 c 67 | 2.491071 6.833333 2.474703 6.857142 2.441964 6.875000 c 68 | 2.409226 6.892857 2.377976 6.898809 2.348214 6.892857 c 69 | 1.741071 6.758928 l 70 | 1.657738 7.187500 1.616071 7.601190 1.616071 8.000000 c 71 | 1.616071 9.226191 1.940476 10.354166 2.589286 11.383928 c 72 | 2.601191 11.377975 2.656250 11.342262 2.754464 11.276785 c 73 | 2.852679 11.211309 2.941964 11.154762 3.022321 11.107142 c 74 | 3.102679 11.059524 3.154762 11.035714 3.178571 11.035714 c 75 | 3.255953 11.035714 3.294643 11.071428 3.294643 11.142857 c 76 | 3.294643 11.178572 3.257441 11.224703 3.183036 11.281250 c 77 | 3.108631 11.337797 3.011905 11.401785 2.892857 11.473214 c 78 | 2.714286 11.580357 l 79 | 3.172619 12.247024 3.735119 12.809524 4.401786 13.267857 c 80 | 5.068452 13.726191 5.794643 14.044642 6.580358 14.223214 c 81 | 6.714286 13.625000 l 82 | 6.726191 13.565476 6.770833 13.535714 6.848215 13.535714 c 83 | 6.877976 13.535714 6.901786 13.552083 6.919643 13.584822 c 84 | 6.937500 13.617559 6.943453 13.648809 6.937500 13.678572 c 85 | 6.803572 14.267857 l 86 | 7.226191 14.345238 7.625000 14.383928 8.000000 14.383928 c 87 | 9.214286 14.383928 10.342262 14.059524 11.383929 13.410714 c 88 | 11.151786 13.077381 11.035715 12.883928 11.035715 12.830357 c 89 | 11.035715 12.752975 11.071429 12.714286 11.142858 12.714286 c 90 | 11.208334 12.714286 11.351191 12.904762 11.571429 13.285714 c 91 | 12.232143 12.839286 12.790179 12.285714 13.245536 11.625000 c 92 | 13.700893 10.964285 14.020834 10.247024 14.205358 9.473214 c 93 | 13.705358 9.366072 l 94 | 13.645834 9.354166 13.616072 9.306547 13.616072 9.223214 c 95 | 13.616072 9.193452 13.632442 9.169642 13.665179 9.151785 c 96 | 13.697917 9.133928 13.726192 9.127975 13.750001 9.133928 c 97 | 14.258929 9.250000 l 98 | 14.342262 8.821428 14.383929 8.404762 14.383929 8.000000 c 99 | h 100 | 15.142858 8.000000 m 101 | 15.142858 8.970238 14.953870 9.895833 14.575893 10.776785 c 102 | 14.197917 11.657738 13.690476 12.416666 13.053572 13.053572 c 103 | 12.416667 13.690476 11.657739 14.197917 10.776786 14.575892 c 104 | 9.895834 14.953869 8.970239 15.142857 8.000000 15.142857 c 105 | 7.029762 15.142857 6.104167 14.953869 5.223215 14.575892 c 106 | 4.342262 14.197917 3.583333 13.690476 2.946429 13.053572 c 107 | 2.309524 12.416666 1.802083 11.657738 1.424107 10.776785 c 108 | 1.046131 9.895833 0.857143 8.970238 0.857143 8.000000 c 109 | 0.857143 7.029761 1.046131 6.104166 1.424107 5.223214 c 110 | 1.802083 4.342261 2.309524 3.583333 2.946429 2.946428 c 111 | 3.583333 2.309524 4.342262 1.802083 5.223215 1.424107 c 112 | 6.104167 1.046130 7.029762 0.857142 8.000000 0.857142 c 113 | 8.970239 0.857142 9.895834 1.046130 10.776786 1.424107 c 114 | 11.657739 1.802083 12.416667 2.309524 13.053572 2.946428 c 115 | 13.690476 3.583333 14.197917 4.342261 14.575893 5.223214 c 116 | 14.953870 6.104166 15.142858 7.029761 15.142858 8.000000 c 117 | h 118 | 16.000000 8.000000 m 119 | 16.000000 6.916666 15.788692 5.880952 15.366072 4.892857 c 120 | 14.943453 3.904761 14.375001 3.053571 13.660715 2.339285 c 121 | 12.946429 1.624999 12.095239 1.056547 11.107143 0.633928 c 122 | 10.119048 0.211308 9.083334 0.000000 8.000000 0.000000 c 123 | 6.916667 0.000000 5.880953 0.211308 4.892858 0.633928 c 124 | 3.904762 1.056547 3.053571 1.624999 2.339286 2.339285 c 125 | 1.625000 3.053571 1.056548 3.904761 0.633929 4.892857 c 126 | 0.211310 5.880952 0.000000 6.916666 0.000000 8.000000 c 127 | 0.000000 9.083333 0.211310 10.119047 0.633929 11.107142 c 128 | 1.056548 12.095238 1.625000 12.946428 2.339286 13.660714 c 129 | 3.053571 14.375000 3.904762 14.943453 4.892858 15.366072 c 130 | 5.880953 15.788691 6.916667 16.000000 8.000000 16.000000 c 131 | 9.083334 16.000000 10.119048 15.788691 11.107143 15.366072 c 132 | 12.095239 14.943453 12.946429 14.375000 13.660715 13.660714 c 133 | 14.375001 12.946428 14.943453 12.095238 15.366072 11.107142 c 134 | 15.788692 10.119047 16.000000 9.083333 16.000000 8.000000 c 135 | h 136 | f 137 | n 138 | Q 139 | 140 | endstream 141 | endobj 142 | 143 | 3 0 obj 144 | 6217 145 | endobj 146 | 147 | 4 0 obj 148 | << /Annots [] 149 | /Type /Page 150 | /MediaBox [ 0.000000 0.000000 16.000000 16.000000 ] 151 | /Resources 1 0 R 152 | /Contents 2 0 R 153 | /Parent 5 0 R 154 | >> 155 | endobj 156 | 157 | 5 0 obj 158 | << /Kids [ 4 0 R ] 159 | /Count 1 160 | /Type /Pages 161 | >> 162 | endobj 163 | 164 | 6 0 obj 165 | << /Type /Catalog 166 | /Pages 5 0 R 167 | >> 168 | endobj 169 | 170 | xref 171 | 0 7 172 | 0000000000 65535 f 173 | 0000000010 00000 n 174 | 0000000074 00000 n 175 | 0000006347 00000 n 176 | 0000006370 00000 n 177 | 0000006543 00000 n 178 | 0000006617 00000 n 179 | trailer 180 | << /ID [ (some) (id) ] 181 | /Root 6 0 R 182 | /Size 7 183 | >> 184 | startxref 185 | 6676 186 | %%EOF -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/ico_email.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ico_email.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/ico_email.imageset/ico_email.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << /ExtGState << /E1 << /ca 0.850000 >> >> >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | /E1 gs 14 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 15 | 0.000000 0.000000 0.000000 scn 16 | 14.400001 12.799988 m 17 | 1.600000 12.799988 l 18 | 0.720000 12.799988 0.008000 12.079988 0.008000 11.199988 c 19 | 0.000000 1.599989 l 20 | 0.000000 0.719990 0.720000 -0.000010 1.600000 -0.000010 c 21 | 14.400001 -0.000010 l 22 | 15.280001 -0.000010 16.000000 0.719990 16.000000 1.599989 c 23 | 16.000000 11.199988 l 24 | 16.000000 12.079988 15.280001 12.799988 14.400001 12.799988 c 25 | h 26 | 14.080001 9.399988 m 27 | 8.424000 5.863989 l 28 | 8.168000 5.703989 7.832000 5.703989 7.576000 5.863989 c 29 | 1.920000 9.399988 l 30 | 1.839782 9.445020 1.769535 9.505859 1.713511 9.578825 c 31 | 1.657487 9.651791 1.616850 9.735366 1.594060 9.824492 c 32 | 1.571271 9.913617 1.566802 10.006440 1.580925 10.097342 c 33 | 1.595048 10.188246 1.627469 10.275337 1.676225 10.353348 c 34 | 1.724982 10.431357 1.789059 10.498663 1.864581 10.551192 c 35 | 1.940102 10.603721 2.025497 10.640379 2.115597 10.658949 c 36 | 2.205696 10.677519 2.298626 10.677614 2.388764 10.659229 c 37 | 2.478901 10.640844 2.564371 10.604362 2.640000 10.551989 c 38 | 8.000000 7.199988 l 39 | 13.360001 10.551989 l 40 | 13.435630 10.604362 13.521099 10.640844 13.611237 10.659229 c 41 | 13.701374 10.677614 13.794305 10.677519 13.884404 10.658949 c 42 | 13.974504 10.640379 14.059897 10.603721 14.135419 10.551192 c 43 | 14.210940 10.498663 14.275019 10.431357 14.323775 10.353348 c 44 | 14.372531 10.275337 14.404952 10.188246 14.419075 10.097342 c 45 | 14.433198 10.006440 14.428730 9.913617 14.405940 9.824492 c 46 | 14.383151 9.735366 14.342514 9.651791 14.286490 9.578825 c 47 | 14.230466 9.505859 14.160218 9.445020 14.080001 9.399988 c 48 | h 49 | f 50 | n 51 | Q 52 | 53 | endstream 54 | endobj 55 | 56 | 3 0 obj 57 | 1594 58 | endobj 59 | 60 | 4 0 obj 61 | << /Annots [] 62 | /Type /Page 63 | /MediaBox [ 0.000000 0.000000 16.000000 12.799988 ] 64 | /Resources 1 0 R 65 | /Contents 2 0 R 66 | /Parent 5 0 R 67 | >> 68 | endobj 69 | 70 | 5 0 obj 71 | << /Kids [ 4 0 R ] 72 | /Count 1 73 | /Type /Pages 74 | >> 75 | endobj 76 | 77 | 6 0 obj 78 | << /Type /Catalog 79 | /Pages 5 0 R 80 | >> 81 | endobj 82 | 83 | xref 84 | 0 7 85 | 0000000000 65535 f 86 | 0000000010 00000 n 87 | 0000000074 00000 n 88 | 0000001724 00000 n 89 | 0000001747 00000 n 90 | 0000001920 00000 n 91 | 0000001994 00000 n 92 | trailer 93 | << /ID [ (some) (id) ] 94 | /Root 6 0 R 95 | /Size 7 96 | >> 97 | startxref 98 | 2053 99 | %%EOF -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/ico_github.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ico_github.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/ico_github.imageset/ico_github.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << /ExtGState << /E1 << /ca 0.850000 >> >> >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | /E1 gs 14 | 1.000000 0.000000 -0.000000 1.000000 0.000000 -0.076874 cm 15 | 0.000000 0.000000 0.000000 scn 16 | 8.000894 15.653258 m 17 | 3.580382 15.655046 0.000000 12.076447 0.000000 7.659503 c 18 | 0.000000 4.166703 2.239749 1.197645 5.358954 0.107263 c 19 | 5.779019 0.001799 5.714669 0.300314 5.714669 0.504090 c 20 | 5.714669 1.889413 l 21 | 3.289018 1.605198 3.190705 3.210383 3.028041 3.478511 c 22 | 2.699140 4.039789 1.921573 4.182791 2.153949 4.450918 c 23 | 2.706290 4.735132 3.269355 4.379416 3.921796 3.415948 c 24 | 4.393699 2.717031 5.314266 2.835006 5.780807 2.951195 c 25 | 5.882695 3.371260 6.100771 3.746638 6.401073 4.038003 c 26 | 3.887834 4.488456 2.840353 6.022141 2.840353 7.845404 c 27 | 2.840353 8.730223 3.131717 9.543541 3.703721 10.199558 c 28 | 3.339068 11.281003 3.737683 12.206935 3.791308 12.344573 c 29 | 4.829852 12.437524 5.909507 11.600967 5.993520 11.534830 c 30 | 6.583398 11.693918 7.257290 11.777931 8.011619 11.777931 c 31 | 8.769524 11.777931 9.445202 11.690344 10.040442 11.529467 c 32 | 10.242432 11.683193 11.243437 12.401773 12.208692 12.314185 c 33 | 12.260530 12.176547 12.650207 11.272065 12.307005 10.204920 c 34 | 12.886158 9.547115 13.181097 8.726646 13.181097 7.840041 c 35 | 13.181097 6.013203 12.126467 4.477731 9.606078 4.034428 c 36 | 9.821956 3.822125 9.993366 3.568936 10.110299 3.289648 c 37 | 10.227232 3.010359 10.287342 2.710570 10.287119 2.407791 c 38 | 10.287119 0.396839 l 39 | 10.301419 0.235964 10.287119 0.076875 10.555245 0.076875 c 40 | 13.720925 1.144019 15.999999 4.134528 15.999999 7.657715 c 41 | 15.999999 12.076447 12.417830 15.653258 8.000894 15.653258 c 42 | h 43 | f 44 | n 45 | Q 46 | 47 | endstream 48 | endobj 49 | 50 | 3 0 obj 51 | 1533 52 | endobj 53 | 54 | 4 0 obj 55 | << /Annots [] 56 | /Type /Page 57 | /MediaBox [ 0.000000 0.000000 16.000000 15.576385 ] 58 | /Resources 1 0 R 59 | /Contents 2 0 R 60 | /Parent 5 0 R 61 | >> 62 | endobj 63 | 64 | 5 0 obj 65 | << /Kids [ 4 0 R ] 66 | /Count 1 67 | /Type /Pages 68 | >> 69 | endobj 70 | 71 | 6 0 obj 72 | << /Type /Catalog 73 | /Pages 5 0 R 74 | >> 75 | endobj 76 | 77 | xref 78 | 0 7 79 | 0000000000 65535 f 80 | 0000000010 00000 n 81 | 0000000074 00000 n 82 | 0000001663 00000 n 83 | 0000001686 00000 n 84 | 0000001859 00000 n 85 | 0000001933 00000 n 86 | trailer 87 | << /ID [ (some) (id) ] 88 | /Root 6 0 R 89 | /Size 7 90 | >> 91 | startxref 92 | 1992 93 | %%EOF -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/ico_overlay_mic.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ico_overlay_mic.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/ico_overlay_mic.imageset/ico_overlay_mic.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/Micro Sniff/Micro Sniff/Assets.xcassets/ico_overlay_mic.imageset/ico_overlay_mic.pdf -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/ico_statusbar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "ico_statusbar.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "template" 23 | } 24 | } -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/ico_statusbar.imageset/ico_statusbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/Micro Sniff/Micro Sniff/Assets.xcassets/ico_statusbar.imageset/ico_statusbar.png -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/ico_twitter.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ico_twitter.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/ico_twitter.imageset/ico_twitter.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << /ExtGState << /E1 << /ca 0.850000 >> >> >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | /E1 gs 14 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 15 | 0.000000 0.000000 0.000000 scn 16 | 8.000000 16.000000 m 17 | 3.582143 16.000000 0.000000 12.417857 0.000000 8.000000 c 18 | 0.000000 3.582143 3.582143 0.000000 8.000000 0.000000 c 19 | 12.417857 0.000000 16.000000 3.582143 16.000000 8.000000 c 20 | 16.000000 12.417857 12.417857 16.000000 8.000000 16.000000 c 21 | h 22 | 11.844644 9.969643 m 23 | 11.850000 9.885714 11.850000 9.798214 11.850000 9.712500 c 24 | 11.850000 7.091071 9.853572 4.071428 6.205358 4.071428 c 25 | 5.080358 4.071428 4.037500 4.398213 3.158929 4.960713 c 26 | 3.319643 4.942857 3.473215 4.935714 3.637500 4.935714 c 27 | 4.566072 4.935714 5.419643 5.249999 6.100000 5.782143 c 28 | 5.228572 5.799999 4.496428 6.371428 4.246428 7.157143 c 29 | 4.551785 7.112499 4.826786 7.112499 5.141071 7.192857 c 30 | 4.692369 7.284017 4.289062 7.527709 3.999672 7.882530 c 31 | 3.710282 8.237350 3.552655 8.681417 3.553572 9.139285 c 32 | 3.553572 9.164286 l 33 | 3.816072 9.016071 4.125000 8.924999 4.448215 8.912500 c 34 | 4.176505 9.093580 3.953675 9.338909 3.799484 9.626732 c 35 | 3.645294 9.914555 3.564506 10.235977 3.564286 10.562500 c 36 | 3.564286 10.932142 3.660715 11.269643 3.833929 11.562500 c 37 | 4.331969 10.949399 4.953448 10.447960 5.657973 10.090775 c 38 | 6.362498 9.733591 7.134300 9.528654 7.923215 9.489285 c 39 | 7.642858 10.837500 8.650001 11.928572 9.860715 11.928572 c 40 | 10.432143 11.928572 10.946429 11.689285 11.308928 11.303572 c 41 | 11.757143 11.387500 12.185715 11.555357 12.567858 11.780357 c 42 | 12.419643 11.321428 12.108929 10.933928 11.696429 10.689285 c 43 | 12.096429 10.732143 12.482143 10.842857 12.839286 10.998214 c 44 | 12.569643 10.601786 12.232143 10.250000 11.844644 9.969643 c 45 | h 46 | f 47 | n 48 | Q 49 | 50 | endstream 51 | endobj 52 | 53 | 3 0 obj 54 | 1652 55 | endobj 56 | 57 | 4 0 obj 58 | << /Annots [] 59 | /Type /Page 60 | /MediaBox [ 0.000000 0.000000 16.000000 16.000000 ] 61 | /Resources 1 0 R 62 | /Contents 2 0 R 63 | /Parent 5 0 R 64 | >> 65 | endobj 66 | 67 | 5 0 obj 68 | << /Kids [ 4 0 R ] 69 | /Count 1 70 | /Type /Pages 71 | >> 72 | endobj 73 | 74 | 6 0 obj 75 | << /Type /Catalog 76 | /Pages 5 0 R 77 | >> 78 | endobj 79 | 80 | xref 81 | 0 7 82 | 0000000000 65535 f 83 | 0000000010 00000 n 84 | 0000000074 00000 n 85 | 0000001782 00000 n 86 | 0000001805 00000 n 87 | 0000001978 00000 n 88 | 0000002052 00000 n 89 | trailer 90 | << /ID [ (some) (id) ] 91 | /Root 6 0 R 92 | /Size 7 93 | >> 94 | startxref 95 | 2111 96 | %%EOF -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/img_background.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "img_background.pdf" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "img_background-1.pdf", 10 | "appearances" : [ 11 | { 12 | "appearance" : "luminosity", 13 | "value" : "light" 14 | } 15 | ] 16 | }, 17 | { 18 | "idiom" : "universal", 19 | "filename" : "img_background_dark.pdf", 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "dark" 24 | } 25 | ] 26 | } 27 | ], 28 | "info" : { 29 | "version" : 1, 30 | "author" : "xcode" 31 | } 32 | } -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/img_background.imageset/img_background-1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/Micro Sniff/Micro Sniff/Assets.xcassets/img_background.imageset/img_background-1.pdf -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/img_background.imageset/img_background.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/Micro Sniff/Micro Sniff/Assets.xcassets/img_background.imageset/img_background.pdf -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Assets.xcassets/img_background.imageset/img_background_dark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/Micro Sniff/Micro Sniff/Assets.xcassets/img_background.imageset/img_background_dark.pdf -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/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 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Extension/Bundle+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bundle+Extension.swift 3 | // MicMonitor 4 | // 5 | // Created by phucld on 2/29/20. 6 | // Copyright © 2020 Dwarvesf. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Bundle { 12 | var releaseVersionNumber: String? { 13 | return infoDictionary?["CFBundleShortVersionString"] as? String 14 | } 15 | var buildVersionNumber: String? { 16 | return infoDictionary?["CFBundleVersion"] as? String 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Extension/Preferences+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Preferences+Extension.swift 3 | // MicMonitor 4 | // 5 | // Created by phucld on 2/28/20. 6 | // Copyright © 2020 Dwarvesf. All rights reserved. 7 | // 8 | 9 | import Preferences 10 | 11 | extension PreferencePane.Identifier { 12 | static let general = Identifier("general") 13 | static let about = Identifier("about") 14 | } 15 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Extension/UserDefault+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefault+Extension.swift 3 | // MicMonitor 4 | // 5 | // Created by phucld on 2/29/20. 6 | // Copyright © 2020 Dwarvesf. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/AMCoreAudio.h: -------------------------------------------------------------------------------- 1 | // 2 | // AMCoreAudio.h 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben Nine on 11/11/2016. 6 | // Copyright © 2016 9Labs. All rights reserved. 7 | // 8 | 9 | @import Foundation; 10 | @import CoreAudio.AudioHardware; 11 | 12 | //! Project version number for AMCoreAudio. 13 | FOUNDATION_EXPORT double AMCoreAudioVersionNumber; 14 | 15 | //! Project version string for AMCoreAudio. 16 | FOUNDATION_EXPORT const unsigned char AMCoreAudioVersionString[]; 17 | 18 | // In this header, you should import all the public headers of your framework using statements like #import 19 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 3.0-rc4 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 0 23 | NSHumanReadableCopyright 24 | Copyright © 2015-2016 9Labs. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Internal/AudioObjectPool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioObjectPool.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben Nine on 20/09/2019. 6 | // Copyright © 2019 9Labs. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class AudioObjectPool { 12 | static var instancePool: NSMapTable = NSMapTable.weakToWeakObjects() 13 | } 14 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Internal/Extensions/AudioObject+Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioObject+Helpers.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben Nine on 20/09/2019. 6 | // Copyright © 2019 9Labs. All rights reserved. 7 | // 8 | 9 | import CoreAudio.AudioHardwareBase 10 | import Foundation 11 | 12 | extension AudioObject { 13 | // MARK: - Class Functions 14 | 15 | class func address(selector: AudioObjectPropertySelector, scope: AudioObjectPropertyScope = kAudioObjectPropertyScopeGlobal, element: AudioObjectPropertyElement = kAudioObjectPropertyElementMaster) -> AudioObjectPropertyAddress { 16 | return AudioObjectPropertyAddress(mSelector: selector, 17 | mScope: scope, 18 | mElement: element) 19 | } 20 | 21 | class func getPropertyDataSize(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout [Q], andSize size: inout UInt32) -> (OSStatus) { 22 | var theAddress = address 23 | 24 | return AudioObjectGetPropertyDataSize(objectID, &theAddress, qualifierDataSize ?? UInt32(0), &qualifierData, &size) 25 | } 26 | 27 | class func getPropertyDataSize(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout Q, andSize size: inout UInt32) -> (OSStatus) { 28 | var theAddress = address 29 | 30 | return AudioObjectGetPropertyDataSize(objectID, &theAddress, qualifierDataSize ?? UInt32(0), &qualifierData, &size) 31 | } 32 | 33 | class func getPropertyDataSize(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, andSize size: inout UInt32) -> (OSStatus) { 34 | var nilValue: ExpressibleByNilLiteral? 35 | 36 | return getPropertyDataSize(objectID, address: address, qualifierDataSize: nil, qualifierData: &nilValue, andSize: &size) 37 | } 38 | 39 | class func getPropertyData(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, andValue value: inout T) -> OSStatus { 40 | var theAddress = address 41 | var size = UInt32(MemoryLayout.size) 42 | let status = AudioObjectGetPropertyData(objectID, &theAddress, UInt32(0), nil, &size, &value) 43 | 44 | return status 45 | } 46 | 47 | class func getPropertyDataArray(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout Q, value: inout [T], andDefaultValue defaultValue: T) -> OSStatus { 48 | var size = UInt32(0) 49 | let sizeStatus = getPropertyDataSize(objectID, address: address, qualifierDataSize: qualifierDataSize, qualifierData: &qualifierData, andSize: &size) 50 | 51 | if noErr == sizeStatus { 52 | value = [T](repeating: defaultValue, count: Int(size) / MemoryLayout.size) 53 | } else { 54 | return sizeStatus 55 | } 56 | 57 | var theAddress = address 58 | let status = AudioObjectGetPropertyData(objectID, &theAddress, qualifierDataSize ?? UInt32(0), &qualifierData, &size, &value) 59 | 60 | return status 61 | } 62 | 63 | class func getPropertyDataArray(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout [Q], value: inout [T], andDefaultValue defaultValue: T) -> OSStatus { 64 | var size = UInt32(0) 65 | let sizeStatus = getPropertyDataSize(objectID, address: address, qualifierDataSize: qualifierDataSize, qualifierData: &qualifierData, andSize: &size) 66 | 67 | if noErr == sizeStatus { 68 | value = [T](repeating: defaultValue, count: Int(size) / MemoryLayout.size) 69 | } else { 70 | return sizeStatus 71 | } 72 | 73 | var theAddress = address 74 | let status = AudioObjectGetPropertyData(objectID, &theAddress, qualifierDataSize ?? UInt32(0), &qualifierData, &size, &value) 75 | 76 | return status 77 | } 78 | 79 | class func getPropertyDataArray(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, value: inout [T], andDefaultValue defaultValue: T) -> OSStatus { 80 | var nilValue: ExpressibleByNilLiteral? 81 | 82 | return getPropertyDataArray(objectID, address: address, qualifierDataSize: nil, qualifierData: &nilValue, value: &value, andDefaultValue: defaultValue) 83 | } 84 | 85 | // MARK: - Instance Functions 86 | 87 | func getPropertyDataSize(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout [Q], andSize size: inout UInt32) -> (OSStatus) { 88 | return type(of: self).getPropertyDataSize(objectID, address: address, qualifierDataSize: qualifierDataSize, qualifierData: &qualifierData, andSize: &size) 89 | } 90 | 91 | func getPropertyDataSize(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout Q, andSize size: inout UInt32) -> (OSStatus) { 92 | return type(of: self).getPropertyDataSize(objectID, address: address, qualifierDataSize: qualifierDataSize, qualifierData: &qualifierData, andSize: &size) 93 | } 94 | 95 | func getPropertyDataSize(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, andSize size: inout UInt32) -> OSStatus { 96 | return type(of: self).getPropertyDataSize(objectID, address: address, andSize: &size) 97 | } 98 | 99 | func getPropertyDataSize(_ address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout [Q], andSize size: inout UInt32) -> (OSStatus) { 100 | return type(of: self).getPropertyDataSize(objectID, address: address, qualifierDataSize: qualifierDataSize, qualifierData: &qualifierData, andSize: &size) 101 | } 102 | 103 | func getPropertyDataSize(_ address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout Q, andSize size: inout UInt32) -> (OSStatus) { 104 | return type(of: self).getPropertyDataSize(objectID, address: address, qualifierDataSize: qualifierDataSize, qualifierData: &qualifierData, andSize: &size) 105 | } 106 | 107 | func getPropertyDataSize(_ address: AudioObjectPropertyAddress, andSize size: inout UInt32) -> OSStatus { 108 | return type(of: self).getPropertyDataSize(objectID, address: address, andSize: &size) 109 | } 110 | 111 | func getPropertyData(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, andValue value: inout T) -> OSStatus { 112 | return type(of: self).getPropertyData(objectID, address: address, andValue: &value) 113 | } 114 | 115 | func getPropertyDataArray(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout Q, value: inout [T], andDefaultValue defaultValue: T) -> OSStatus { 116 | return type(of: self).getPropertyDataArray(objectID, address: address, qualifierDataSize: qualifierDataSize, qualifierData: &qualifierData, value: &value, andDefaultValue: defaultValue) 117 | } 118 | 119 | func getPropertyData(_ address: AudioObjectPropertyAddress, andValue value: inout T) -> OSStatus { 120 | return type(of: self).getPropertyData(objectID, address: address, andValue: &value) 121 | } 122 | 123 | func getPropertyDataArray(_ address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout Q, value: inout [T], andDefaultValue defaultValue: T) -> OSStatus { 124 | return type(of: self).getPropertyDataArray(objectID, address: address, qualifierDataSize: qualifierDataSize, qualifierData: &qualifierData, value: &value, andDefaultValue: defaultValue) 125 | } 126 | 127 | func getPropertyDataArray(_ address: AudioObjectPropertyAddress, qualifierDataSize: UInt32?, qualifierData: inout [Q], value: inout [T], andDefaultValue defaultValue: T) -> OSStatus { 128 | return type(of: self).getPropertyDataArray(objectID, address: address, qualifierDataSize: qualifierDataSize, qualifierData: &qualifierData, value: &value, andDefaultValue: defaultValue) 129 | } 130 | 131 | func getPropertyDataArray(_ address: AudioObjectPropertyAddress, value: inout [T], andDefaultValue defaultValue: T) -> OSStatus { 132 | return type(of: self).getPropertyDataArray(objectID, address: address, value: &value, andDefaultValue: defaultValue) 133 | } 134 | 135 | func setPropertyData(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, andValue value: inout T) -> OSStatus { 136 | var theAddress = address 137 | let size = UInt32(MemoryLayout.size) 138 | let status = AudioObjectSetPropertyData(objectID, &theAddress, UInt32(0), nil, size, &value) 139 | 140 | return status 141 | } 142 | 143 | func setPropertyData(_ objectID: AudioObjectID, address: AudioObjectPropertyAddress, andValue value: inout [T]) -> OSStatus { 144 | var theAddress = address 145 | let size = UInt32(value.count * MemoryLayout.size) 146 | let status = AudioObjectSetPropertyData(objectID, &theAddress, UInt32(0), nil, size, &value) 147 | 148 | return status 149 | } 150 | 151 | func setPropertyData(_ address: AudioObjectPropertyAddress, andValue value: inout T) -> OSStatus { 152 | return setPropertyData(objectID, address: address, andValue: &value) 153 | } 154 | 155 | func setPropertyData(_ address: AudioObjectPropertyAddress, andValue value: inout [T]) -> OSStatus { 156 | return setPropertyData(objectID, address: address, andValue: &value) 157 | } 158 | 159 | func address(selector: AudioObjectPropertySelector, scope: AudioObjectPropertyScope = kAudioObjectPropertyScopeGlobal, element: AudioObjectPropertyElement = kAudioObjectPropertyElementMaster) -> AudioObjectPropertyAddress { 160 | return AudioObject.address(selector: selector, scope: scope, element: element) 161 | } 162 | 163 | func validAddress(selector: AudioObjectPropertySelector, scope: AudioObjectPropertyScope = kAudioObjectPropertyScopeGlobal, element: AudioObjectPropertyElement = kAudioObjectPropertyElementMaster) -> AudioObjectPropertyAddress? { 164 | var address = self.address(selector: selector, scope: scope, element: element) 165 | 166 | if AudioObjectHasProperty(self.objectID, &address) { 167 | return address 168 | } else { 169 | return nil 170 | } 171 | } 172 | 173 | // getProperty with default value 174 | func getProperty(address: AudioObjectPropertyAddress, defaultValue: T) -> T? { 175 | var value = defaultValue 176 | let status = getPropertyData(address, andValue: &value) 177 | 178 | switch status { 179 | case noErr: 180 | 181 | return value 182 | 183 | default: 184 | 185 | log("Unable to get property with address (\(address)). Status: \(status)") 186 | return nil 187 | } 188 | } 189 | 190 | func getProperty(address: AudioObjectPropertyAddress, defaultValue: CFString) -> String? { 191 | var value = defaultValue 192 | let status = getPropertyData(address, andValue: &value) 193 | 194 | switch status { 195 | case noErr: 196 | 197 | return value as String 198 | 199 | default: 200 | 201 | log("Unable to get property with address (\(address)). Status: \(status)") 202 | return nil 203 | } 204 | } 205 | 206 | // getProperty UInt32 207 | func getProperty(address: AudioObjectPropertyAddress) -> UInt32? { 208 | return getProperty(address: address, defaultValue: UInt32(0)) 209 | } 210 | 211 | // getProperty Float32 212 | func getProperty(address: AudioObjectPropertyAddress) -> Float32? { 213 | return getProperty(address: address, defaultValue: Float32(0.0)) 214 | } 215 | 216 | // getProperty Float64 217 | func getProperty(address: AudioObjectPropertyAddress) -> Float64? { 218 | return getProperty(address: address, defaultValue: Float64(0.0)) 219 | } 220 | 221 | // getProperty Bool 222 | func getProperty(address: AudioObjectPropertyAddress) -> Bool? { 223 | if let value = getProperty(address: address, defaultValue: UInt32(0)) { 224 | return value != 0 225 | } else { 226 | return nil 227 | } 228 | } 229 | 230 | // getProperty String 231 | func getProperty(address: AudioObjectPropertyAddress) -> String? { 232 | return getProperty(address: address, defaultValue: "" as CFString) 233 | } 234 | 235 | // setProperty T 236 | func setProperty(address: AudioObjectPropertyAddress, value: T) -> Bool { 237 | let status: OSStatus 238 | 239 | if let unwrappedValue = value as? Bool { 240 | var newValue: UInt32 = unwrappedValue == true ? 1 : 0 241 | status = setPropertyData(address, andValue: &newValue) 242 | } else if let unwrappedValue = value as? String { 243 | var newValue: CFString = unwrappedValue as CFString 244 | status = setPropertyData(address, andValue: &newValue) 245 | } else { 246 | var newValue = value 247 | status = setPropertyData(address, andValue: &newValue) 248 | } 249 | 250 | switch status { 251 | case noErr: 252 | 253 | return true 254 | 255 | default: 256 | 257 | log("Unable to set property with address (\(address)). Status: \(status)") 258 | return false 259 | } 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Internal/Extensions/Bool+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bool+Boolean.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben on 7/9/15. 6 | // Copyright © 2015 9Labs. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Bool { 12 | init(_ integer: T) { 13 | self.init(integer != 0) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Internal/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben Nine on 13/04/16. 6 | // Copyright © 2016 9Labs. All rights reserved. 7 | // 8 | 9 | import CoreAudio.AudioHardwareBase 10 | import Foundation 11 | 12 | private let logDateFormatter: DateFormatter = { 13 | $0.locale = NSLocale.current 14 | $0.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" 15 | 16 | return $0 17 | }(DateFormatter()) 18 | 19 | func log(_ message: String, file: String = #file, line: Int = #line, function: String = #function) { 20 | let filename = file.components(separatedBy: "/").last ?? file 21 | 22 | print("\(logDateFormatter.string(from: Date())) [AMCoreAudio] [\(filename):\(line)] \(function) > \(message)") 23 | } 24 | 25 | func scope(direction: Direction) -> AudioObjectPropertyScope { 26 | return direction == .playback ? kAudioObjectPropertyScopeOutput : kAudioObjectPropertyScopeInput 27 | } 28 | 29 | func direction(to scope: AudioObjectPropertyScope) -> Direction? { 30 | switch scope { 31 | case kAudioObjectPropertyScopeOutput: 32 | return .playback 33 | case kAudioObjectPropertyScopeInput: 34 | return .recording 35 | default: 36 | return nil 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Public/AudioHardware.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioHardware.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben on 7/9/15. 6 | // Copyright © 2015 9Labs. All rights reserved. 7 | // 8 | 9 | import CoreAudio.AudioHardwareBase 10 | import Foundation 11 | 12 | /// This class allows subscribing to hardware-related audio notifications. 13 | /// 14 | /// For a comprehensive list of supported notifications, see `AudioHardwareEvent`. 15 | public final class AudioHardware { 16 | /// Returns a singleton `AudioHardware` instance. 17 | public static let sharedInstance = AudioHardware() 18 | 19 | private var allKnownDevices = [AudioDevice]() 20 | private var isRegisteredForNotifications = false 21 | 22 | private let notificationsQueue = DispatchQueue.global(qos: .userInitiated) 23 | 24 | private lazy var propertyListenerBlock: AudioObjectPropertyListenerBlock = { [weak self] (_, inAddresses) -> Void in 25 | let address = inAddresses.pointee 26 | let notificationCenter = NotificationCenter.defaultCenter 27 | 28 | switch address.mSelector { 29 | case kAudioObjectPropertyOwnedObjects: 30 | // Get the latest device list 31 | let latestDeviceList = AudioDevice.allDevices() 32 | 33 | let addedDevices = latestDeviceList.filter { (audioDevice) -> Bool in 34 | !(self?.allKnownDevices.contains { $0 == audioDevice } ?? false) 35 | } 36 | 37 | let removedDevices = self?.allKnownDevices.filter { (audioDevice) -> Bool in 38 | !(latestDeviceList.contains { $0 == audioDevice }) 39 | } ?? [] 40 | 41 | // Add new devices 42 | for device in addedDevices { 43 | self?.add(device: device) 44 | } 45 | 46 | // Remove old devices 47 | for device in removedDevices { 48 | self?.remove(device: device) 49 | } 50 | 51 | notificationCenter.publish(AudioHardwareEvent.deviceListChanged( 52 | addedDevices: addedDevices, 53 | removedDevices: removedDevices 54 | )) 55 | case kAudioHardwarePropertyDefaultInputDevice: 56 | if let audioDevice = AudioDevice.defaultInputDevice() { 57 | notificationCenter.publish(AudioHardwareEvent.defaultInputDeviceChanged(audioDevice: audioDevice)) 58 | } 59 | case kAudioHardwarePropertyDefaultOutputDevice: 60 | if let audioDevice = AudioDevice.defaultOutputDevice() { 61 | notificationCenter.publish(AudioHardwareEvent.defaultOutputDeviceChanged(audioDevice: audioDevice)) 62 | } 63 | case kAudioHardwarePropertyDefaultSystemOutputDevice: 64 | if let audioDevice = AudioDevice.defaultSystemOutputDevice() { 65 | notificationCenter.publish(AudioHardwareEvent.defaultSystemOutputDeviceChanged(audioDevice: audioDevice)) 66 | } 67 | default: 68 | break 69 | } 70 | } 71 | 72 | // MARK: - Lifecycle Functions 73 | 74 | deinit { 75 | disableDeviceMonitoring() 76 | } 77 | 78 | // MARK: - Public Functions 79 | 80 | /// Enables device monitoring so events like the ones below are generated: 81 | /// 82 | /// - added or removed device 83 | /// - new default input device 84 | /// - new default output device 85 | /// - new default system output device 86 | /// 87 | /// - SeeAlso: `disableDeviceMonitoring()` 88 | public func enableDeviceMonitoring() { 89 | registerForNotifications() 90 | 91 | for device in AudioDevice.allDevices() { 92 | add(device: device) 93 | } 94 | } 95 | 96 | /// Disables device monitoring. 97 | /// 98 | /// - SeeAlso: `enableDeviceMonitoring()` 99 | public func disableDeviceMonitoring() { 100 | for device in allKnownDevices { 101 | remove(device: device) 102 | } 103 | 104 | unregisterForNotifications() 105 | } 106 | 107 | // MARK: - Private Functions 108 | 109 | private func add(device: AudioDevice) { 110 | allKnownDevices.append(device) 111 | } 112 | 113 | private func remove(device: AudioDevice) { 114 | allKnownDevices.removeAll { $0 == device } 115 | } 116 | 117 | // MARK: - Notification Book-keeping 118 | 119 | private func registerForNotifications() { 120 | if isRegisteredForNotifications { 121 | unregisterForNotifications() 122 | } 123 | 124 | var address = AudioObjectPropertyAddress( 125 | mSelector: kAudioObjectPropertySelectorWildcard, 126 | mScope: kAudioObjectPropertyScopeWildcard, 127 | mElement: kAudioObjectPropertyElementWildcard 128 | ) 129 | 130 | let systemObjectID = AudioObjectID(kAudioObjectSystemObject) 131 | let err = AudioObjectAddPropertyListenerBlock(systemObjectID, &address, notificationsQueue, propertyListenerBlock) 132 | 133 | if noErr != err { 134 | log("Error on AudioObjectAddPropertyListenerBlock: \(err)") 135 | } 136 | 137 | isRegisteredForNotifications = noErr == err 138 | } 139 | 140 | private func unregisterForNotifications() { 141 | guard isRegisteredForNotifications else { return } 142 | 143 | var address = AudioObjectPropertyAddress( 144 | mSelector: kAudioObjectPropertySelectorWildcard, 145 | mScope: kAudioObjectPropertyScopeWildcard, 146 | mElement: kAudioObjectPropertyElementWildcard 147 | ) 148 | 149 | let systemObjectID = AudioObjectID(kAudioObjectSystemObject) 150 | let err = AudioObjectRemovePropertyListenerBlock(systemObjectID, &address, notificationsQueue, propertyListenerBlock) 151 | 152 | if noErr != err { 153 | log("Error on AudioObjectRemovePropertyListenerBlock: \(err)") 154 | } 155 | 156 | isRegisteredForNotifications = noErr != err 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Public/AudioObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioObject.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben Nine on 13/04/16. 6 | // Copyright © 2016 9Labs. All rights reserved. 7 | // 8 | 9 | import CoreAudio.AudioHardwareBase 10 | import Foundation 11 | 12 | /// This class represents a Core Audio object currently present in the system. In Core Audio, 13 | /// audio objects are referenced by its `AudioObjectID` and belong to a specific `AudioClassID`. 14 | /// For more information, please refer to Core Audio's documentation or source code. 15 | public class AudioObject { 16 | var objectID: AudioObjectID 17 | 18 | init(objectID: AudioObjectID) { 19 | self.objectID = objectID 20 | } 21 | 22 | deinit { 23 | // NO-OP 24 | } 25 | 26 | /// The `AudioClassID` that identifies the class of this audio object. 27 | /// 28 | /// - Returns: *(optional)* An `AudioClassID`. 29 | public lazy var classID: AudioClassID? = { 30 | var address = AudioObjectPropertyAddress( 31 | mSelector: kAudioObjectPropertyClass, 32 | mScope: kAudioObjectPropertyScopeGlobal, 33 | mElement: kAudioObjectPropertyElementMaster 34 | ) 35 | 36 | guard AudioObjectHasProperty(self.objectID, &address) else { return nil } 37 | 38 | var klassID = AudioClassID() 39 | let status = self.getPropertyData(address, andValue: &klassID) 40 | 41 | guard noErr == status else { return nil } 42 | 43 | return klassID 44 | }() 45 | 46 | /// The audio object that owns this audio object. 47 | /// 48 | /// - Returns: *(optional)* An `AudioObject`. 49 | public lazy var owningObject: AudioObject? = { 50 | var address = AudioObjectPropertyAddress( 51 | mSelector: kAudioObjectPropertyOwner, 52 | mScope: kAudioObjectPropertyScopeGlobal, 53 | mElement: kAudioObjectPropertyElementMaster 54 | ) 55 | 56 | guard AudioObjectHasProperty(self.objectID, &address) else { return nil } 57 | 58 | var objectID = AudioObjectID() 59 | let status = self.getPropertyData(address, andValue: &objectID) 60 | 61 | guard noErr == status else { return nil } 62 | 63 | return AudioObject(objectID: objectID) 64 | }() 65 | 66 | /// The audio device that owns this audio object. 67 | /// 68 | /// - Returns: *(optional)* An `AudioDevice`. 69 | public lazy var owningDevice: AudioDevice? = { 70 | guard let object = self.owningObject, object.classID == kAudioDeviceClassID else { return nil } 71 | 72 | return AudioDevice.lookup(by: object.objectID) 73 | }() 74 | 75 | /// The audio object's name as reported by the system. 76 | /// 77 | /// - Returns: *(optional)* An audio object's name. 78 | var name: String? { 79 | var name: CFString = "" as CFString 80 | 81 | let address = AudioObjectPropertyAddress( 82 | mSelector: kAudioObjectPropertyName, 83 | mScope: kAudioObjectPropertyScopeGlobal, 84 | mElement: kAudioObjectPropertyElementMaster 85 | ) 86 | 87 | let status = getPropertyData(address, andValue: &name) 88 | 89 | return noErr == status ? (name as String) : nil 90 | } 91 | } 92 | 93 | extension AudioObject: Hashable { 94 | /// The hash value. 95 | public func hash(into hasher: inout Hasher) { 96 | hasher.combine(objectID) 97 | } 98 | } 99 | 100 | /// :nodoc: 101 | public func == (lhs: AudioObject, rhs: AudioObject) -> Bool { 102 | return lhs.hashValue == rhs.hashValue 103 | } 104 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Public/AudioStream.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioStream.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben Nine on 13/04/16. 6 | // Copyright © 2016 9Labs. All rights reserved. 7 | // 8 | 9 | import CoreAudio.AudioHardwareBase 10 | import Foundation 11 | 12 | /// `AudioStream` 13 | /// 14 | /// This class represents an audio stream belonging to an audio object. 15 | public final class AudioStream: AudioObject { 16 | // MARK: - Public Properties 17 | 18 | /// This audio stream's identifier. 19 | /// 20 | /// - Returns: An `AudioObjectID`. 21 | public var id: AudioObjectID { 22 | return objectID 23 | } 24 | 25 | /// Returns whether this audio stream is enabled and doing I/O. 26 | /// 27 | /// - Returns: `true` when enabled, `false` otherwise. 28 | public var active: Bool { 29 | var address = AudioObjectPropertyAddress( 30 | mSelector: kAudioStreamPropertyIsActive, 31 | mScope: kAudioObjectPropertyScopeGlobal, 32 | mElement: kAudioObjectPropertyElementMaster 33 | ) 34 | 35 | guard AudioObjectHasProperty(self.id, &address) else { return false } 36 | var active: UInt32 = 0 37 | guard noErr == self.getPropertyData(address, andValue: &active) else { return false } 38 | 39 | return active == 1 40 | } 41 | 42 | /// Specifies the first element in the owning device that corresponds to the element one of this stream. 43 | /// 44 | /// - Returns: *(optional)* A `UInt32`. 45 | public var startingChannel: UInt32? { 46 | var address = AudioObjectPropertyAddress( 47 | mSelector: kAudioStreamPropertyStartingChannel, 48 | mScope: kAudioObjectPropertyScopeGlobal, 49 | mElement: kAudioObjectPropertyElementMaster 50 | ) 51 | 52 | guard AudioObjectHasProperty(self.id, &address) else { return nil } 53 | var startingChannel: UInt32 = 0 54 | guard noErr == self.getPropertyData(address, andValue: &startingChannel) else { return nil } 55 | 56 | return startingChannel 57 | } 58 | 59 | /// Describes the general kind of functionality attached to this stream. 60 | /// 61 | /// - Return: A `TerminalType`. 62 | public var terminalType: TerminalType { 63 | var address = AudioObjectPropertyAddress( 64 | mSelector: kAudioStreamPropertyTerminalType, 65 | mScope: kAudioObjectPropertyScopeGlobal, 66 | mElement: kAudioObjectPropertyElementMaster 67 | ) 68 | 69 | guard AudioObjectHasProperty(id, &address) else { return .unknown } 70 | var terminalType: UInt32 = 0 71 | guard noErr == getPropertyData(address, andValue: &terminalType) else { return .unknown } 72 | 73 | switch terminalType { 74 | case kAudioStreamTerminalTypeLine: 75 | return .line 76 | case kAudioStreamTerminalTypeDigitalAudioInterface: 77 | return .digitalAudioInterface 78 | case kAudioStreamTerminalTypeSpeaker: 79 | return .speaker 80 | case kAudioStreamTerminalTypeHeadphones: 81 | return .headphones 82 | case kAudioStreamTerminalTypeLFESpeaker: 83 | return .lfeSpeaker 84 | case kAudioStreamTerminalTypeReceiverSpeaker: 85 | return .receiverSpeaker 86 | case kAudioStreamTerminalTypeMicrophone: 87 | return .microphone 88 | case kAudioStreamTerminalTypeHeadsetMicrophone: 89 | return .headsetMicrophone 90 | case kAudioStreamTerminalTypeReceiverMicrophone: 91 | return .receiverMicrophone 92 | case kAudioStreamTerminalTypeTTY: 93 | return .tty 94 | case kAudioStreamTerminalTypeHDMI: 95 | return .hdmi 96 | case kAudioStreamTerminalTypeDisplayPort: 97 | return .displayPort 98 | case kAudioStreamTerminalTypeUnknown: 99 | fallthrough 100 | default: 101 | return .unknown 102 | } 103 | } 104 | 105 | /// The latency in frames for this stream. 106 | /// 107 | /// Note that the owning `AudioDevice` may have additional latency so it should be 108 | /// queried as well. If both the device and the stream say they have latency, 109 | /// then the total latency for the stream is the device latency summed with the 110 | /// stream latency. 111 | /// 112 | /// - Returns: *(optional)* A `UInt32` value with the latency in frames. 113 | public var latency: UInt32? { 114 | var address = AudioObjectPropertyAddress( 115 | mSelector: kAudioStreamPropertyLatency, 116 | mScope: kAudioObjectPropertyScopeGlobal, 117 | mElement: kAudioObjectPropertyElementMaster 118 | ) 119 | 120 | guard AudioObjectHasProperty(id, &address) else { return nil } 121 | var latency: UInt32 = 0 122 | guard noErr == getPropertyData(address, andValue: &latency) else { return nil } 123 | 124 | return latency 125 | } 126 | 127 | /// The audio stream's direction. 128 | /// 129 | /// For output streams, and to continue using the same `Direction` concept used by `AudioDevice`, 130 | /// this will be `Direction.Playback`, likewise, for input streams, `Direction.Recording` will be returned. 131 | /// 132 | /// - Returns: *(optional)* A `Direction`. 133 | public var direction: Direction? { 134 | var address = AudioObjectPropertyAddress( 135 | mSelector: kAudioStreamPropertyDirection, 136 | mScope: kAudioObjectPropertyScopeGlobal, 137 | mElement: kAudioObjectPropertyElementMaster 138 | ) 139 | 140 | guard AudioObjectHasProperty(id, &address) else { return nil } 141 | var direction: UInt32 = 0 142 | guard noErr == getPropertyData(address, andValue: &direction) else { return nil } 143 | 144 | switch direction { 145 | case 0: 146 | return .playback 147 | case 1: 148 | return .recording 149 | default: 150 | return nil 151 | } 152 | } 153 | 154 | /// An `AudioStreamBasicDescription` that describes the current data format for this audio stream. 155 | /// 156 | /// - SeeAlso: `virtualFormat` 157 | /// 158 | /// - Returns: *(optional)* An `AudioStreamBasicDescription`. 159 | public var physicalFormat: AudioStreamBasicDescription? { 160 | get { 161 | var asbd = AudioStreamBasicDescription() 162 | guard noErr == getStreamPropertyData(kAudioStreamPropertyPhysicalFormat, andValue: &asbd) else { return nil } 163 | 164 | return asbd 165 | } 166 | 167 | set { 168 | var asbd = newValue 169 | 170 | if noErr == setStreamPropertyData(kAudioStreamPropertyPhysicalFormat, andValue: &asbd) { 171 | log("Error setting physicalFormat to \(String(describing: newValue))") 172 | } 173 | } 174 | } 175 | 176 | /// An `AudioStreamBasicDescription` that describes the current virtual data format for this audio stream. 177 | /// 178 | /// - SeeAlso: `physicalFormat` 179 | /// 180 | /// - Returns: *(optional)* An `AudioStreamBasicDescription`. 181 | public var virtualFormat: AudioStreamBasicDescription? { 182 | get { 183 | var asbd = AudioStreamBasicDescription() 184 | guard noErr == getStreamPropertyData(kAudioStreamPropertyVirtualFormat, andValue: &asbd) else { return nil } 185 | 186 | return asbd 187 | } 188 | 189 | set { 190 | var asbd = newValue 191 | 192 | if noErr == setStreamPropertyData(kAudioStreamPropertyVirtualFormat, andValue: &asbd) { 193 | log("Error setting virtualFormat to \(String(describing: newValue))") 194 | } 195 | } 196 | } 197 | 198 | /// All the available physical formats for this audio stream. 199 | /// 200 | /// - SeeAlso: `availableVirtualFormats` 201 | /// 202 | /// - Returns: *(optional)* An array of `AudioStreamRangedDescription` structs. 203 | public lazy var availablePhysicalFormats: [AudioStreamRangedDescription]? = { 204 | guard let direction = self.direction else { return nil } 205 | 206 | var address = AudioObjectPropertyAddress( 207 | mSelector: kAudioStreamPropertyAvailablePhysicalFormats, 208 | mScope: scope(direction: direction), 209 | mElement: kAudioObjectPropertyElementMaster 210 | ) 211 | 212 | guard AudioObjectHasProperty(id, &address) else { return nil } 213 | var asrd = [AudioStreamRangedDescription]() 214 | guard noErr == getPropertyDataArray(address, value: &asrd, andDefaultValue: AudioStreamRangedDescription()) else { 215 | return nil 216 | } 217 | 218 | return asrd 219 | }() 220 | 221 | /// All the available virtual formats for this audio stream. 222 | /// 223 | /// - SeeAlso: `availablePhysicalFormats` 224 | /// 225 | /// - Returns: *(optional)* An array of `AudioStreamRangedDescription` structs. 226 | public lazy var availableVirtualFormats: [AudioStreamRangedDescription]? = { 227 | guard let direction = self.direction else { return nil } 228 | 229 | var address = AudioObjectPropertyAddress( 230 | mSelector: kAudioStreamPropertyAvailableVirtualFormats, 231 | mScope: scope(direction: direction), 232 | mElement: kAudioObjectPropertyElementMaster 233 | ) 234 | 235 | guard AudioObjectHasProperty(id, &address) else { return nil } 236 | var asrd = [AudioStreamRangedDescription]() 237 | guard noErr == getPropertyDataArray(address, value: &asrd, andDefaultValue: AudioStreamRangedDescription()) else { 238 | return nil 239 | } 240 | 241 | return asrd 242 | }() 243 | 244 | // MARK: - Private Properties 245 | 246 | private var isRegisteredForNotifications = false 247 | 248 | private let notificationsQueue = DispatchQueue.global(qos: .userInitiated) 249 | 250 | private lazy var propertyListenerBlock: AudioObjectPropertyListenerBlock = { (_, inAddresses) -> Void in 251 | let address = inAddresses.pointee 252 | // let direction = direction(to: address.mScope) 253 | let notificationCenter = NotificationCenter.defaultCenter 254 | 255 | switch address.mSelector { 256 | case kAudioStreamPropertyIsActive: 257 | notificationCenter.publish(AudioStreamEvent.isActiveDidChange(audioStream: self)) 258 | case kAudioStreamPropertyPhysicalFormat: 259 | notificationCenter.publish(AudioStreamEvent.physicalFormatDidChange(audioStream: self)) 260 | default: 261 | break 262 | } 263 | } 264 | 265 | // MARK: - Public Functions 266 | 267 | /// Returns an `AudioStream` by providing a valid audio stream identifier. 268 | /// 269 | /// - Note: If identifier is not valid, `nil` will be returned. 270 | public static func lookup(by id: AudioObjectID) -> AudioStream? { 271 | var instance = AudioObjectPool.instancePool.object(forKey: NSNumber(value: UInt(id))) as? AudioStream 272 | 273 | if instance == nil { 274 | instance = AudioStream(id: id) 275 | } 276 | 277 | return instance 278 | } 279 | 280 | /// Initializes an `AudioStream` by providing a valid `AudioObjectID` referencing an existing audio stream. 281 | private init?(id: AudioObjectID) { 282 | super.init(objectID: id) 283 | 284 | guard owningObject != nil else { return nil } 285 | 286 | registerForNotifications() 287 | AudioObjectPool.instancePool.setObject(self, forKey: NSNumber(value: UInt(objectID))) 288 | } 289 | 290 | deinit { 291 | unregisterForNotifications() 292 | AudioObjectPool.instancePool.removeObject(forKey: NSNumber(value: UInt(objectID))) 293 | } 294 | 295 | /// All the available physical formats for this audio stream matching the current physical format's sample rate. 296 | /// 297 | /// - Note: By default, both mixable and non-mixable streams are returned, however, non-mixable 298 | /// streams can be filtered out by setting `includeNonMixable` to `false`. 299 | /// 300 | /// - Parameter includeNonMixable: Whether to include non-mixable streams in the returned array. Defaults to `true`. 301 | /// 302 | /// - SeeAlso: `availableVirtualFormatsMatchingCurrentNominalSampleRate(_:)` 303 | /// 304 | /// - Returns: *(optional)* An array of `AudioStreamBasicDescription` structs. 305 | public final func availablePhysicalFormatsMatchingCurrentNominalSampleRate(_ includeNonMixable: Bool = true) -> [AudioStreamBasicDescription]? { 306 | guard let physicalFormats = availablePhysicalFormats, let physicalFormat = physicalFormat else { return nil } 307 | 308 | var filteredFormats = physicalFormats.filter { (format) -> Bool in 309 | format.mSampleRateRange.mMinimum >= physicalFormat.mSampleRate && 310 | format.mSampleRateRange.mMaximum <= physicalFormat.mSampleRate 311 | }.map { $0.mFormat } 312 | 313 | if !includeNonMixable { 314 | filteredFormats = filteredFormats.filter { $0.mFormatFlags & kAudioFormatFlagIsNonMixable == 0 } 315 | } 316 | 317 | return filteredFormats 318 | } 319 | 320 | /// All the available virtual formats for this audio stream matching the current virtual format's sample rate. 321 | /// 322 | /// - Note: By default, both mixable and non-mixable streams are returned, however, non-mixable 323 | /// streams can be filtered out by setting `includeNonMixable` to `false`. 324 | /// 325 | /// - Parameter includeNonMixable: Whether to include non-mixable streams in the returned array. Defaults to `true`. 326 | /// 327 | /// - SeeAlso: `availablePhysicalFormatsMatchingCurrentNominalSampleRate(_:)` 328 | /// 329 | /// - Returns: *(optional)* An array of `AudioStreamBasicDescription` structs. 330 | public final func availableVirtualFormatsMatchingCurrentNominalSampleRate(_ includeNonMixable: Bool = true) -> [AudioStreamBasicDescription]? { 331 | guard let virtualFormats = availableVirtualFormats, let virtualFormat = virtualFormat else { return nil } 332 | 333 | var filteredFormats = virtualFormats.filter { (format) -> Bool in 334 | format.mSampleRateRange.mMinimum >= virtualFormat.mSampleRate && 335 | format.mSampleRateRange.mMaximum <= virtualFormat.mSampleRate 336 | }.map { $0.mFormat } 337 | 338 | if !includeNonMixable { 339 | filteredFormats = filteredFormats.filter { $0.mFormatFlags & kAudioFormatFlagIsNonMixable == 0 } 340 | } 341 | 342 | return filteredFormats 343 | } 344 | 345 | // MARK: - Private Functions 346 | 347 | /// This is an specialized version of `getPropertyData` that only requires passing an `AudioObjectPropertySelector` 348 | /// instead of an `AudioObjectPropertyAddress`. The scope is computed from the stream's `Direction`, and the element 349 | /// is assumed to be `kAudioObjectPropertyElementMaster`. 350 | /// 351 | /// Additionally, the property address is validated before calling `getPropertyData`. 352 | /// 353 | /// - Parameter selector: The `AudioObjectPropertySelector` that points to the property we want to get. 354 | /// - Parameter value: The value that will be returned. 355 | /// 356 | /// - Returns: An `OSStatus` with `noErr` on success, or an error code other than `noErr` when it fails. 357 | private func getStreamPropertyData(_ selector: AudioObjectPropertySelector, andValue value: inout T) -> OSStatus? { 358 | guard let direction = direction else { return nil } 359 | 360 | var address = AudioObjectPropertyAddress( 361 | mSelector: selector, 362 | mScope: scope(direction: direction), 363 | mElement: kAudioObjectPropertyElementMaster 364 | ) 365 | 366 | guard AudioObjectHasProperty(id, &address) else { return nil } 367 | 368 | return getPropertyData(address, andValue: &value) 369 | } 370 | 371 | /// This is an specialized version of `setPropertyData` that only requires passing an `AudioObjectPropertySelector` 372 | /// instead of an `AudioObjectPropertyAddress`. The scope is computed from the stream's `Direction`, and the element 373 | /// is assumed to be `kAudioObjectPropertyElementMaster`. 374 | /// 375 | /// Additionally, the property address is validated before calling `setPropertyData`. 376 | /// 377 | /// - Parameter selector: The `AudioObjectPropertySelector` that points to the property we want to set. 378 | /// - Parameter value: The new value we want to set. 379 | /// 380 | /// - Returns: An `OSStatus` with `noErr` on success, or an error code other than `noErr` when it fails. 381 | private func setStreamPropertyData(_ selector: AudioObjectPropertySelector, andValue value: inout T) -> OSStatus? { 382 | guard let direction = direction else { return nil } 383 | 384 | var address = AudioObjectPropertyAddress( 385 | mSelector: selector, 386 | mScope: scope(direction: direction), 387 | mElement: kAudioObjectPropertyElementMaster 388 | ) 389 | 390 | guard AudioObjectHasProperty(id, &address) else { return nil } 391 | 392 | return setPropertyData(address, andValue: &value) 393 | } 394 | 395 | // MARK: - Notification Book-keeping 396 | 397 | private func registerForNotifications() { 398 | if isRegisteredForNotifications { 399 | unregisterForNotifications() 400 | } 401 | 402 | var address = AudioObjectPropertyAddress( 403 | mSelector: kAudioObjectPropertySelectorWildcard, 404 | mScope: kAudioObjectPropertyScopeWildcard, 405 | mElement: kAudioObjectPropertyElementWildcard 406 | ) 407 | 408 | let err = AudioObjectAddPropertyListenerBlock(id, &address, notificationsQueue, propertyListenerBlock) 409 | 410 | if noErr != err { 411 | log("Error on AudioObjectAddPropertyListenerBlock: \(err)") 412 | } 413 | 414 | isRegisteredForNotifications = noErr == err 415 | } 416 | 417 | private func unregisterForNotifications() { 418 | guard isRegisteredForNotifications else { return } 419 | 420 | var address = AudioObjectPropertyAddress( 421 | mSelector: kAudioObjectPropertySelectorWildcard, 422 | mScope: kAudioObjectPropertyScopeWildcard, 423 | mElement: kAudioObjectPropertyElementWildcard 424 | ) 425 | 426 | let err = AudioObjectRemovePropertyListenerBlock(id, &address, notificationsQueue, propertyListenerBlock) 427 | 428 | if noErr != err { 429 | log("Error on AudioObjectRemovePropertyListenerBlock: \(err)") 430 | } 431 | 432 | isRegisteredForNotifications = noErr != err 433 | } 434 | } 435 | 436 | extension AudioStream: CustomStringConvertible { 437 | // MARK: - CustomStringConvertible Protocol 438 | 439 | /// Returns a `String` representation of self. 440 | public var description: String { 441 | return "\(name ?? "Stream \(id)") (\(id))" 442 | } 443 | } 444 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Public/BundleInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BundleInfo.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben Nine on 29/04/16. 6 | // Copyright © 2016 9Labs. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// This class provides information about this bundle such as: 12 | /// 13 | /// - build date 14 | /// - name 15 | /// - version 16 | /// - builder number 17 | public final class BundleInfo { 18 | private init() {} 19 | 20 | private static let thisBundle = Bundle(for: BundleInfo.self) 21 | 22 | /// Returns this bundle's build date. 23 | public static let buildDate: String? = thisBundle.infoDictionary?["BuildDate"] as? String 24 | 25 | /// Returns this bundle's name. 26 | public static let name: String? = thisBundle.infoDictionary?["CFBundleName"] as? String 27 | 28 | /// Returns this bundle's version. 29 | public static let version: String? = thisBundle.infoDictionary?["CFBundleShortVersionString"] as? String 30 | 31 | /// Returns this bundle's build number. 32 | public static let buildNumber: String? = thisBundle.infoDictionary?["CFBundleVersion"] as? String 33 | 34 | /// Returns this bundle's build information. 35 | public static func buildInfo() -> String? { 36 | guard let buildDate = buildDate, let name = name, let version = version, let buildNumber = buildNumber else { 37 | return nil 38 | } 39 | 40 | return "\(name) \(version) (build \(buildNumber)) built on \(buildDate)." 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Public/Enums/AudioDeviceEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioDeviceEvent.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben Nine on 20/09/2019. 6 | // Copyright © 2019 9Labs. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Represents an `AudioDevice` event. 12 | public enum AudioDeviceEvent: Event { 13 | /// Called whenever the audio device's sample rate changes. 14 | case nominalSampleRateDidChange(audioDevice: AudioDevice) 15 | 16 | /// Called whenever the audio device's list of nominal sample rates changes. 17 | /// 18 | /// - Note: This will typically happen on *Aggregate* and *Multi-Output* devices when adding or removing other 19 | /// audio devices (either physical or virtual.) 20 | case availableNominalSampleRatesDidChange(audioDevice: AudioDevice) 21 | 22 | /// Called whenever the audio device's clock source changes. 23 | case clockSourceDidChange(audioDevice: AudioDevice) 24 | 25 | /// Called whenever the audio device's name changes. 26 | case nameDidChange(audioDevice: AudioDevice) 27 | 28 | /// Called whenever the list of owned audio devices on this audio device changes. 29 | /// 30 | /// - Note: This will typically happen on *Aggregate* and *Multi-Output* devices when adding or removing other 31 | /// audio devices (either physical or virtual.) 32 | case listDidChange(audioDevice: AudioDevice) 33 | 34 | /// Called whenever the audio device's volume for a given channel and direction changes. 35 | case volumeDidChange(audioDevice: AudioDevice, channel: UInt32, direction: Direction) 36 | 37 | /// Called whenever the audio device's mute state for a given channel and direction changes. 38 | case muteDidChange(audioDevice: AudioDevice, channel: UInt32, direction: Direction) 39 | 40 | /// Called whenever the audio device's *is alive* property changes. 41 | case isAliveDidChange(audioDevice: AudioDevice) 42 | 43 | /// Called whenever the audio device's *is running* property changes. 44 | case isRunningDidChange(audioDevice: AudioDevice) 45 | 46 | /// Called whenever the audio device's *is running somewhere* property changes. 47 | case isRunningSomewhereDidChange(audioDevice: AudioDevice) 48 | 49 | /// Called whenever the audio device's *is jack connected* property changes. 50 | case isJackConnectedDidChange(audioDevice: AudioDevice) 51 | 52 | /// Called whenever the audio device's *preferred channels for stereo* property changes. 53 | case preferredChannelsForStereoDidChange(audioDevice: AudioDevice) 54 | 55 | /// Called whenever the audio device's *hog mode* property changes. 56 | case hogModeDidChange(audioDevice: AudioDevice) 57 | } 58 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Public/Enums/AudioHardwareEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioHardwareEvent.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben Nine on 20/09/2019. 6 | // Copyright © 2019 9Labs. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Represents an `AudioHardware` event. 12 | public enum AudioHardwareEvent: Event { 13 | /// Called whenever the list of hardware devices and device subdevices changes. 14 | /// (i.e., devices that are part of *Aggregate* or *Multi-Output* devices.) 15 | case deviceListChanged(addedDevices: [AudioDevice], removedDevices: [AudioDevice]) 16 | 17 | /// Called whenever the default input device changes. 18 | case defaultInputDeviceChanged(audioDevice: AudioDevice) 19 | 20 | /// Called whenever the default output device changes. 21 | case defaultOutputDeviceChanged(audioDevice: AudioDevice) 22 | 23 | /// Called whenever the default system output device changes. 24 | case defaultSystemOutputDeviceChanged(audioDevice: AudioDevice) 25 | } 26 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Public/Enums/AudioStreamEvent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioStreamEvent.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben Nine on 20/09/2019. 6 | // Copyright © 2019 9Labs. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Represents an `AudioStream` event. 12 | public enum AudioStreamEvent: Event { 13 | /// Called whenever the audio stream `isActive` flag changes state. 14 | case isActiveDidChange(audioStream: AudioStream) 15 | 16 | /// Called whenever the audio stream physical format changes. 17 | case physicalFormatDidChange(audioStream: AudioStream) 18 | } 19 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Public/Enums/Direction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Direction.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben Nine on 20/09/2019. 6 | // Copyright © 2019 9Labs. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Indicates the direction used by an `AudioDevice` or `AudioStream`. 12 | public enum Direction: String { 13 | /// Playback direction 14 | case playback 15 | /// Recording direction 16 | case recording 17 | } 18 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Public/Enums/TerminalType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TerminalType.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben Nine on 20/09/2019. 6 | // Copyright © 2019 9Labs. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Indicates the terminal type used by an `AudioStream`. 12 | public enum TerminalType: String { 13 | /// Unknown 14 | case unknown 15 | 16 | /// The ID for a terminal type of a line level stream. 17 | /// Note that this applies to both input streams and output streams. 18 | case line 19 | 20 | /// A stream from/to a digital audio interface as defined by ISO 60958 (aka SPDIF or AES/EBU). 21 | /// Note that this applies to both input streams and output streams. 22 | case digitalAudioInterface 23 | 24 | /// Speaker 25 | case speaker 26 | 27 | /// Headphones 28 | case headphones 29 | 30 | /// Speaker for low frequency effects 31 | case lfeSpeaker 32 | 33 | /// A speaker on a telephone handset receiver 34 | case receiverSpeaker 35 | 36 | /// A microphone 37 | case microphone 38 | 39 | /// A microphone attached to an headset 40 | case headsetMicrophone 41 | 42 | /// A microphone on a telephone handset receiver 43 | case receiverMicrophone 44 | 45 | /// A device providing a TTY signl 46 | case tty 47 | 48 | /// A stream from/to an HDMI port 49 | case hdmi 50 | 51 | /// A stream from/to an DisplayPort port 52 | case displayPort 53 | } 54 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Public/Enums/TransportType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TransportType.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben Nine on 20/09/2019. 6 | // Copyright © 2019 9Labs. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Indicates the transport type used by an `AudioDevice`. 12 | public enum TransportType: String { 13 | /// Unknown Transport Type 14 | case unknown 15 | 16 | /// Built-In Transport Type 17 | case builtIn 18 | 19 | /// Aggregate Transport Type 20 | case aggregate 21 | 22 | /// Virtual Transport Type 23 | case virtual 24 | 25 | /// PCI Transport Type 26 | case pci 27 | 28 | /// USB Transport Type 29 | case usb 30 | 31 | /// FireWire Transport Type 32 | case fireWire 33 | 34 | /// Bluetooth Transport Type 35 | case bluetooth 36 | 37 | /// Bluetooth LE Transport Type 38 | case bluetoothLE 39 | 40 | /// HDMI Transport Type 41 | case hdmi 42 | 43 | /// DisplayPort Transport Type 44 | case displayPort 45 | 46 | /// AirPlay Transport Type 47 | case airPlay 48 | 49 | /// Audio Video Bridging (AVB) Transport Type 50 | case avb 51 | 52 | /// Thunderbolt Transport Type 53 | case thunderbolt 54 | } 55 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Public/Models/VolumeInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VolumeInfo.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben Nine on 20/09/2019. 6 | // Copyright © 2019 9Labs. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// This struct holds volume, mute, and playthru information about a given channel and direction of an `AudioDevice`. 12 | public struct VolumeInfo { 13 | /// Returns an scalar volume, or `nil` if unavailable. 14 | public var volume: Float32? 15 | 16 | /// Returns whether volume is present. 17 | public var hasVolume: Bool 18 | 19 | /// Returns whether volume can be set. 20 | public var canSetVolume: Bool 21 | 22 | /// Returns whether audio can be muted. 23 | public var canMute: Bool 24 | 25 | /// Returns whether audio is muted. 26 | public var isMuted: Bool 27 | 28 | /// Returns whether play thru is supported. 29 | public var canPlayThru: Bool 30 | 31 | /// Returns whether play thru is enabled. 32 | public var isPlayThruSet: Bool 33 | 34 | init() { 35 | hasVolume = false 36 | canSetVolume = false 37 | canMute = false 38 | isMuted = false 39 | canPlayThru = false 40 | isPlayThruSet = false 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Public/NotificationCenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationCenter.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben Nine on 17/04/16. 6 | // Copyright © 2016 9Labs. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - NotificationCenter 12 | 13 | /// This is `AMCoreAudio`'s de facto pub-sub system. 14 | public final class NotificationCenter { 15 | private struct EventSubscriberDescriptor { 16 | var subscriber: EventSubscriber 17 | var queue: DispatchQueue? 18 | } 19 | 20 | private var subscriberDescriptorsByEvent = [String: [EventSubscriberDescriptor]]() 21 | 22 | private init() {} 23 | 24 | /// Returns a singleton `NotificationCenter` instance. 25 | public static let defaultCenter = NotificationCenter() 26 | 27 | /// Allows a subscriber conforming to the `EventSubscriber` protocol to receive events of 28 | /// the type specified by `eventType`. 29 | /// 30 | /// - Parameter subscriber: Any object conforming to the `EventSubscriber` protocol. 31 | /// - Parameter eventType: A class, struct or enum type conforming to the `Event` protocol. 32 | /// - Parameter dispatchQueue: (optional) A dispatch queue to use for delivering the events. 33 | public func subscribe(_ subscriber: EventSubscriber, eventType: Event.Type, dispatchQueue: DispatchQueue? = nil) { 34 | let type = String(describing: eventType) 35 | 36 | if subscriberDescriptorsByEvent[type] == nil { 37 | subscriberDescriptorsByEvent[type] = [] 38 | 39 | if eventType is AudioHardwareEvent.Type { 40 | AudioHardware.sharedInstance.enableDeviceMonitoring() 41 | } 42 | } 43 | 44 | let descriptor = EventSubscriberDescriptor(subscriber: subscriber, queue: dispatchQueue) 45 | 46 | subscriberDescriptorsByEvent[type]!.append(descriptor) 47 | } 48 | 49 | /// Removes a subscriber from the subscription to events of a specified `eventType`. 50 | /// 51 | /// - Parameter subscriber: Any object conforming to the `EventSubscriber` protocol. 52 | /// - Parameter eventType: A class, struct or enum type conforming to the `Event` protocol. 53 | public func unsubscribe(_ subscriber: EventSubscriber, eventType: Event.Type) { 54 | let type = String(describing: eventType) 55 | 56 | if var subscribers = subscriberDescriptorsByEvent[type] { 57 | if let idx = subscribers.firstIndex(where: { (aSubscriber) -> Bool in aSubscriber.subscriber == subscriber }) { 58 | subscribers.remove(at: idx) 59 | } 60 | 61 | if subscribers.isEmpty { 62 | subscriberDescriptorsByEvent.removeValue(forKey: type) 63 | 64 | if eventType is AudioHardwareEvent.Type { 65 | AudioHardware.sharedInstance.disableDeviceMonitoring() 66 | } 67 | } 68 | } 69 | } 70 | 71 | /// Publishes an event. The event is delivered to all its subscribers. 72 | /// 73 | /// - Parameter event: The event conforming to the `Event` protocol to publish. 74 | func publish(_ event: Event) { 75 | let type = String(describing: Swift.type(of: event)) 76 | 77 | if let subscriberDescriptors = subscriberDescriptorsByEvent[type] { 78 | for descriptor in subscriberDescriptors { 79 | // If queue is present, we will dispatch the event in that queue, 80 | // otherwise, we will just dispatch the event in whatever happens to be the current 81 | // queue. 82 | if let queue = descriptor.queue { 83 | queue.async { 84 | descriptor.subscriber.eventReceiver(event) 85 | } 86 | } else { 87 | descriptor.subscriber.eventReceiver(event) 88 | } 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Public/Protocols/Event.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Event.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben Nine on 20/09/2019. 6 | // Copyright © 2019 9Labs. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// The protocol that any events must implement. 12 | /// 13 | /// An event conforming to this protocol may be a class, a struct or an enum. In AMCoreAudio, 14 | /// we will be relying on enums, since they are very lightweight yet expressive enough (we can 15 | /// pass arguments to them.) 16 | public protocol Event {} 17 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/AMCoreAudio/Public/Protocols/EventSubscriber.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventSubscriber.swift 3 | // AMCoreAudio 4 | // 5 | // Created by Ruben Nine on 20/09/2019. 6 | // Copyright © 2019 9Labs. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// The protocol any event subscriber must implement. 12 | /// 13 | /// Typically, this will be a class that also happens to conform to the `Hashable` protocol. 14 | public protocol EventSubscriber { 15 | /// This is the event handler. 16 | func eventReceiver(_ event: Event) 17 | 18 | /// The hash value. 19 | /// - SeeAlso: The `Hashable` protocol. 20 | var hashValue: Int { get } 21 | } 22 | 23 | func == (lhs: EventSubscriber, rhs: EventSubscriber) -> Bool { 24 | return lhs.hashValue == rhs.hashValue 25 | } 26 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/MicroManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MicroManager.swift 3 | // MicMonitor 4 | // 5 | // Created by phucld on 2/17/20. 6 | // Copyright © 2020 Dwarvesf. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class MicroManager { 12 | 13 | static let sharedInstance = MicroManager() 14 | private init(){} 15 | 16 | var microDidRunningSomeWhere: ((_ isRunning: Bool,_ title: String) -> ())? 17 | var microDidAdd:((_ title: String) ->())? 18 | var microDidRemove:((_ title: String) ->())? 19 | 20 | 21 | func regisAudioNotification() { 22 | NotificationCenter.defaultCenter.subscribe(self, eventType: AudioHardwareEvent.self, dispatchQueue: DispatchQueue.main) 23 | NotificationCenter.defaultCenter.subscribe(self, eventType: AudioDeviceEvent.self, dispatchQueue: DispatchQueue.main) 24 | } 25 | 26 | func removeAllAudioNotification() { 27 | NotificationCenter.defaultCenter.unsubscribe(self, eventType: AudioHardwareEvent.self) 28 | NotificationCenter.defaultCenter.unsubscribe(self, eventType: AudioDeviceEvent.self) 29 | } 30 | 31 | } 32 | extension MicroManager: EventSubscriber { 33 | func eventReceiver(_ event: Event) { 34 | if let event = event as? AudioDeviceEvent { 35 | switch event { 36 | case .isRunningSomewhereDidChange(audioDevice: let audioDevice): 37 | guard 38 | audioDevice.isInputOnlyDevice(), 39 | let microDidRunningSomeWhere = self.microDidRunningSomeWhere 40 | else {return} 41 | microDidRunningSomeWhere(audioDevice.isRunningSomewhere(), audioDevice.name) 42 | default: break 43 | 44 | } 45 | } 46 | 47 | if let event = event as? AudioHardwareEvent { 48 | switch event { 49 | case .deviceListChanged(addedDevices: let addedDevices, removedDevices: let removedDevices): 50 | 51 | if let microDidAdd = self.microDidAdd, let addedMic = addedDevices.first(where: {$0.isMicroDevice()}) { 52 | microDidAdd(addedMic.name) 53 | } 54 | 55 | if let microDidRemove = microDidRemove, let removedMic = removedDevices.first(where: {$0.isMicroDevice()}) { 56 | microDidRemove(removedMic.name) 57 | } 58 | 59 | default: break 60 | } 61 | } 62 | } 63 | 64 | var hashValue: Int { 65 | return 0 66 | } 67 | 68 | } 69 | 70 | 71 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/NotificationManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationManager.swift 3 | // Micro Sniff 4 | // 5 | // Created by Trung Phan on 24/04/2021. 6 | // Copyright © 2021 Dwarvesf. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class NotificationManager { 12 | 13 | static private func showNotification(title: String, desc: String, sound: Bool) { 14 | let notification = NSUserNotification() 15 | notification.title = title 16 | notification.subtitle = desc 17 | if sound { 18 | notification.soundName = NSUserNotificationDefaultSoundName 19 | } 20 | notification.deliveryDate = Date(timeIntervalSinceNow: 1) 21 | 22 | NSUserNotificationCenter.default.deliver(notification) 23 | } 24 | 25 | static func showNotificationIfEnable(title: String, desc: String) { 26 | if !Preference.isShowNotification {return} 27 | self.showNotification(title: title, desc: desc, sound: Preference.isEnableNotificationSound) 28 | } 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/Preference.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Preference.swift 3 | // MicMonitor 4 | // 5 | // Created by phucld on 2/29/20. 6 | // Copyright © 2020 Dwarvesf. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | extension UserDefaults { 12 | enum Key { 13 | static let startAtLogin = "startAtLogin" 14 | static let dockIconState = "dockIconState" 15 | static let showPreferencesOnLaunch = "showPreferencesOnLaunch" 16 | static let isEnableNotificationSound = "isEnableNotificationSound" 17 | static let isShowNotification = "isShowNotification" 18 | } 19 | } 20 | 21 | enum Preference { 22 | static var dockIconState: DockIconState { 23 | get { 24 | guard let state = UserDefaults.standard.string(forKey: UserDefaults.Key.dockIconState) else { 25 | return .show 26 | } 27 | 28 | return DockIconState(rawValue: state) ?? .hide 29 | } 30 | 31 | set { 32 | UserDefaults.standard.set(newValue.rawValue, forKey: UserDefaults.Key.dockIconState) 33 | 34 | Util.toggleDockIcon(newValue) 35 | } 36 | } 37 | 38 | static var startAtLogin: Bool { 39 | get { 40 | return UserDefaults.standard.bool(forKey: UserDefaults.Key.startAtLogin) 41 | } 42 | 43 | set { 44 | UserDefaults.standard.set(newValue, forKey: UserDefaults.Key.startAtLogin) 45 | LauncherManager.shared.setupMainApp(isAutoStart: newValue) 46 | } 47 | } 48 | 49 | static var isShowNotification: Bool { 50 | get { 51 | return UserDefaults.standard.bool(forKey: UserDefaults.Key.isShowNotification) 52 | } 53 | 54 | set { 55 | UserDefaults.standard.set(newValue, forKey: UserDefaults.Key.isShowNotification) 56 | } 57 | } 58 | 59 | static var isEnableNotificationSound: Bool { 60 | get { 61 | return UserDefaults.standard.bool(forKey: UserDefaults.Key.isEnableNotificationSound) 62 | } 63 | 64 | set { 65 | UserDefaults.standard.set(newValue, forKey: UserDefaults.Key.isEnableNotificationSound) 66 | } 67 | } 68 | 69 | static var showPreferencesOnlaunch: Bool { 70 | get { 71 | return UserDefaults.standard.bool(forKey: UserDefaults.Key.showPreferencesOnLaunch) 72 | } 73 | 74 | set { 75 | UserDefaults.standard.set(newValue, forKey: UserDefaults.Key.showPreferencesOnLaunch) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Helper/Util.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.swift 3 | // MicMonitor 4 | // 5 | // Created by phucld on 2/26/20. 6 | // Copyright © 2020 Dwarvesf. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | enum DockIconState: String { 12 | case hide 13 | case show 14 | } 15 | 16 | class Util { 17 | 18 | static var timer: Timer? 19 | 20 | static func toggleDockIcon(_ state: DockIconState) { 21 | // We need to use scheduled timer here for not create multiple dock icon when quickly toggle this option 22 | timer?.invalidate() 23 | 24 | timer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: false) { _ in 25 | switch state { 26 | case .hide: NSApp.setActivationPolicy(NSApplication.ActivationPolicy.accessory) 27 | case .show: NSApp.setActivationPolicy(NSApplication.ActivationPolicy.regular) 28 | } 29 | 30 | NSApp.activate(ignoringOtherApps: true) 31 | } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/MicroSniff.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.device.audio-input 8 | 9 | com.apple.security.files.user-selected.read-only 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/StatusBarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatusBarController.swift 3 | // MicMonitor 4 | // 5 | // Created by phucld on 2/17/20. 6 | // Copyright © 2020 Dwarvesf. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Preferences 11 | import Cocoa 12 | 13 | class StatusBarController { 14 | private let menuStatusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength) 15 | var window: MicroWindow? = nil 16 | 17 | lazy var preferencesWindowController = PreferencesWindowController( 18 | preferencePanes: [ 19 | GeneralPreferenceViewController(), 20 | AboutPreferenceViewController() 21 | ], 22 | style: .segmentedControl 23 | ) 24 | 25 | 26 | init() { 27 | MicroManager.sharedInstance.regisAudioNotification() 28 | setupView() 29 | } 30 | 31 | private func setupView() { 32 | if let button = menuStatusItem.button { 33 | button.image = #imageLiteral(resourceName: "ico_statusbar") 34 | } 35 | menuStatusItem.menu = self.getContextMenu() 36 | 37 | if let mic = AudioDevice.allInputDevices().first(where: {$0.isRunningSomewhere()}) { 38 | createAndShowWindow(micTitle: mic.name) 39 | } 40 | 41 | MicroManager.sharedInstance.microDidRunningSomeWhere = {[weak self] (isRunning, title) in 42 | NotificationManager.showNotificationIfEnable(title: title, desc: "\(title) \(isRunning ? "running" : "stopped")") 43 | if isRunning { 44 | self?.createAndShowWindow(micTitle: title) 45 | } else { 46 | self?.removeWindow() 47 | } 48 | } 49 | 50 | MicroManager.sharedInstance.microDidAdd = {deviceName in 51 | NotificationManager.showNotificationIfEnable(title: deviceName, desc: "\(deviceName) connected") 52 | } 53 | MicroManager.sharedInstance.microDidRemove = {deviceName in 54 | NotificationManager.showNotificationIfEnable(title: deviceName, desc: "\(deviceName) disconnected") 55 | } 56 | } 57 | 58 | private func createAndShowWindow(micTitle: String) { 59 | if window == nil { 60 | window = MicroWindow.initForMainScreen() 61 | } 62 | 63 | window?.micTitle?(micTitle) 64 | window?.openWithAnimation() 65 | } 66 | 67 | private func getContextMenu() -> NSMenu { 68 | let menu = NSMenu() 69 | 70 | menu.addItem(NSMenuItem(title: "Preferences...", action: #selector(openPreferences), keyEquivalent: "P")) 71 | menu.addItem(NSMenuItem.separator()) 72 | menu.addItem(NSMenuItem(title: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")) 73 | 74 | menu.item(withTitle: "Preferences...")?.target = self 75 | 76 | return menu 77 | } 78 | 79 | @objc private func openPreferences() { 80 | self.preferencesWindowController.show() 81 | } 82 | 83 | private func removeWindow() { 84 | window?.close() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Views/HyperlinkTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HyperLinkTextfield.swift 3 | // MicMonitor 4 | // 5 | // Created by phucld on 2/28/20. 6 | // Copyright © 2020 Dwarvesf. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | @IBDesignable 13 | class HyperlinkTextField: NSTextField { 14 | 15 | @IBInspectable var href: String = "" 16 | 17 | override func resetCursorRects() { 18 | discardCursorRects() 19 | addCursorRect(self.bounds, cursor: NSCursor.pointingHand) 20 | } 21 | 22 | override func awakeFromNib() { 23 | super.awakeFromNib() 24 | 25 | // TODO: Fix this and get the hover click to work. 26 | } 27 | 28 | override func mouseDown(with theEvent: NSEvent) { 29 | if let localHref = URL(string: href) { 30 | NSWorkspace.shared.open(localHref) 31 | } 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Views/MicroWindow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // MicMonitor 4 | // 5 | // Created by Trung Phan on 2/17/20. 6 | // Copyright © 2020 Dwarvesf. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class MicroWindow: NSWindow { 12 | 13 | static let microViewWidth: CGFloat = 100 14 | static let hPadding:CGFloat = 10 15 | static let vPadding:CGFloat = 50 16 | static let statusBarHeight: CGFloat = 22 17 | 18 | typealias MicTitle = String 19 | var micTitle: ((MicTitle)->())? = nil 20 | 21 | var lastLocationInScreen: NSPoint = .zero 22 | 23 | class func initForMainScreen() -> MicroWindow? { 24 | guard let screen = NSScreen.main else {return nil} 25 | 26 | let screenRect = screen.frame 27 | 28 | let frame = NSRect(origin: CGPoint(x: screenRect.size.width - hPadding - microViewWidth, y: screenRect.height - vPadding - microViewWidth - statusBarHeight), size: CGSize(width: microViewWidth, height: microViewWidth)) 29 | 30 | let overlayWindow = MicroWindow(contentRect: frame, styleMask: [.borderless], backing: .buffered, defer: false, screen: screen) 31 | overlayWindow.backgroundColor = .clear 32 | overlayWindow.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] 33 | overlayWindow.level = .mainMenu 34 | overlayWindow.order(.above, relativeTo: 0) 35 | overlayWindow.isReleasedWhenClosed = false 36 | overlayWindow.titleVisibility = .hidden 37 | overlayWindow.styleMask.remove(.titled) 38 | overlayWindow.displaysWhenScreenProfileChanges = true 39 | let visualEffect = NSVisualEffectView() 40 | visualEffect.translatesAutoresizingMaskIntoConstraints = false 41 | visualEffect.material = .light 42 | visualEffect.state = .active 43 | visualEffect.wantsLayer = true 44 | visualEffect.layer?.backgroundColor = .clear 45 | visualEffect.layer?.cornerRadius = 10.0 46 | 47 | overlayWindow.contentView?.addSubview(visualEffect) 48 | 49 | guard let constraints = overlayWindow.contentView else { return nil } 50 | 51 | NSLayoutConstraint.activate([ 52 | visualEffect.leadingAnchor.constraint(equalTo: constraints.leadingAnchor), 53 | visualEffect.trailingAnchor.constraint(equalTo: constraints.trailingAnchor), 54 | visualEffect.topAnchor.constraint(equalTo: constraints.topAnchor), 55 | visualEffect.bottomAnchor.constraint(equalTo: constraints.bottomAnchor) 56 | ]) 57 | 58 | let micTitleLabel = NSTextField() 59 | micTitleLabel.translatesAutoresizingMaskIntoConstraints = false 60 | micTitleLabel.isBezeled = false 61 | micTitleLabel.drawsBackground = false 62 | micTitleLabel.isEditable = false 63 | micTitleLabel.alignment = .center 64 | micTitleLabel.alphaValue = 0.3 65 | overlayWindow.micTitle = {[weak micTitleLabel] micTitle in 66 | micTitleLabel?.stringValue = micTitle 67 | } 68 | visualEffect.addSubview(micTitleLabel) 69 | 70 | let imageView = CustomIconAnimationView() 71 | imageView.translatesAutoresizingMaskIntoConstraints = false 72 | 73 | visualEffect.addSubview(imageView) 74 | 75 | NSLayoutConstraint.activate([ 76 | imageView.topAnchor.constraint(equalTo: visualEffect.topAnchor, constant: 10), 77 | imageView.leadingAnchor.constraint(equalTo: visualEffect.leadingAnchor), 78 | imageView.trailingAnchor.constraint(equalTo: visualEffect.trailingAnchor), 79 | imageView.heightAnchor.constraint(equalToConstant: 50), 80 | 81 | micTitleLabel.topAnchor.constraint(greaterThanOrEqualTo: visualEffect.topAnchor, constant: 10), 82 | micTitleLabel.centerXAnchor.constraint(equalTo: visualEffect.centerXAnchor), 83 | micTitleLabel.bottomAnchor.constraint(equalTo: visualEffect.bottomAnchor, constant: -10), 84 | micTitleLabel.widthAnchor.constraint(equalToConstant: microViewWidth) 85 | ]) 86 | overlayWindow.windowController?.shouldCascadeWindows = false 87 | overlayWindow.setFrameAutosaveName("MicroSniff") 88 | return overlayWindow 89 | } 90 | 91 | func openWithAnimation() { 92 | self.makeKeyAndOrderFront(nil) 93 | self.alphaValue = 0 94 | self.relocationToNearestEdge(lastLocationInScreen: self.frame.origin) 95 | NSAnimationContext.runAnimationGroup({ _ in 96 | NSAnimationContext.current.duration = 0.5 97 | self.animator().alphaValue = 1 98 | }) 99 | } 100 | 101 | override func mouseDown(with event: NSEvent) { 102 | super.mouseDown(with: event) 103 | 104 | // Set closehand cursor 105 | self.contentView?.addCursorRect(self.frame, cursor: .closedHand) 106 | let closedHand = NSCursor.closedHand 107 | closedHand.set() 108 | 109 | self.lastLocationInScreen = self.convertPoint(toScreen: event.locationInWindow) 110 | } 111 | 112 | override func mouseDragged(with event: NSEvent) { 113 | super.mouseDown(with: event) 114 | 115 | let newDragLocation = self.convertPoint(toScreen: event.locationInWindow) 116 | var thisOrigin = self.frame.origin 117 | thisOrigin.x += -self.lastLocationInScreen.x + newDragLocation.x 118 | thisOrigin.y += -self.lastLocationInScreen.y + newDragLocation.y 119 | self.setFrameOrigin(thisOrigin) 120 | self.lastLocationInScreen = newDragLocation 121 | } 122 | 123 | override func mouseUp(with event: NSEvent) { 124 | super.mouseUp(with: event) 125 | 126 | // Set arrow cursor 127 | let arrow = NSCursor.arrow 128 | arrow.set() 129 | 130 | let newDragLocation = self.convertPoint(toScreen: event.locationInWindow) 131 | var thisOrigin = self.frame.origin 132 | thisOrigin.x += -self.lastLocationInScreen.x + newDragLocation.x 133 | thisOrigin.y += -self.lastLocationInScreen.y + newDragLocation.y 134 | 135 | relocationToNearestEdge(lastLocationInScreen: thisOrigin) 136 | } 137 | 138 | private func relocationToNearestEdge(lastLocationInScreen: NSPoint) { 139 | //Check current point and auto move to left or right edge 140 | 141 | guard let screen = self.screen else {return} 142 | 143 | 144 | let screenLeading = screen.frame.minX 145 | let screenTrailing = screen.frame.maxX 146 | let screenTop = screen.frame.maxY 147 | let screenBottom = screen.frame.minY 148 | 149 | let screenHCenter = screen.frame.midX 150 | 151 | 152 | let leadingX = Self.hPadding + screenLeading 153 | let trailingX = screenTrailing - Self.hPadding - Self.microViewWidth 154 | 155 | var yPosition = lastLocationInScreen.y 156 | var xPosition = lastLocationInScreen.x 157 | 158 | if xPosition >= screenHCenter { 159 | xPosition = trailingX 160 | } else { 161 | xPosition = leadingX 162 | } 163 | 164 | let paddingTop = screenTop - Self.microViewWidth - Self.statusBarHeight 165 | if yPosition > paddingTop { 166 | yPosition = screenTop - Self.vPadding - Self.microViewWidth - Self.statusBarHeight 167 | } else if yPosition < screenBottom + Self.vPadding { 168 | yPosition = screenBottom + Self.vPadding 169 | } 170 | 171 | let finalPosition = NSPoint(x: xPosition, y: yPosition) 172 | 173 | self.setFrame(NSRect(origin: finalPosition, size: CGSize(width: Self.microViewWidth, height: Self.microViewWidth)), display: true, animate: true) 174 | } 175 | 176 | } 177 | 178 | class CustomIconAnimationView: NSView { 179 | 180 | private var imageView: NSImageView! 181 | 182 | override init(frame frameRect: NSRect) { 183 | super.init(frame: frameRect) 184 | self.setupView() 185 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 186 | self.opacityAnimation() 187 | } 188 | } 189 | 190 | required init?(coder: NSCoder) { 191 | fatalError("init(coder:) has not been implemented") 192 | } 193 | 194 | private func setupView() { 195 | 196 | self.imageView = NSImageView(image: #imageLiteral(resourceName: "ico_overlay_mic")) 197 | self.imageView.imageScaling = .scaleProportionallyUpOrDown 198 | self.imageView.alphaValue = 1 199 | self.imageView.translatesAutoresizingMaskIntoConstraints = false 200 | 201 | self.addSubview(self.imageView) 202 | 203 | NSLayoutConstraint.activate([ 204 | self.imageView.topAnchor.constraint(equalTo: self.topAnchor), 205 | self.imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor), 206 | self.imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor), 207 | self.imageView.trailingAnchor.constraint(equalTo: self.trailingAnchor), 208 | ]) 209 | } 210 | 211 | func opacityAnimation() { 212 | let opacityAnimation = CAKeyframeAnimation(keyPath: "opacity") 213 | opacityAnimation.values = [0, 0.7, 0.3, 0.5, 0.3, 0.8, 1, 0.5, 0.3, 1] 214 | opacityAnimation.keyTimes = [0, 0.3, 0.4, 0.5, 0.6, 0.7, 1, 1.2, 1.4, 2] 215 | opacityAnimation.duration = 2 216 | opacityAnimation.repeatDuration = .infinity 217 | opacityAnimation.autoreverses = true 218 | 219 | self.imageView.layer?.add(opacityAnimation, forKey: "image.opacity") 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Views/Preferences/About/AboutPreferenceViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AboutPreferenceViewController.swift 3 | // MicMonitor 4 | // 5 | // Created by phucld on 2/28/20. 6 | // Copyright © 2020 Dwarvesf. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Preferences 11 | 12 | final class AboutPreferenceViewController: NSViewController, PreferencePane { 13 | let preferencePaneIdentifier = PreferencePane.Identifier.about 14 | let preferencePaneTitle = "About" 15 | let toolbarItemIcon = NSImage(named: NSImage.infoName)! 16 | 17 | override var nibName: NSNib.Name? { "AboutPreferenceViewController" } 18 | 19 | @IBOutlet weak var lblVersion: NSTextField! 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | setupUI() 25 | 26 | preferredContentSize = CGSize(width: 500, height: 360) 27 | } 28 | 29 | private func setupUI() { 30 | if let version = Bundle.main.releaseVersionNumber, 31 | let buildNumber = Bundle.main.buildVersionNumber { 32 | lblVersion.stringValue += " \(version) (\(buildNumber))" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Views/Preferences/About/AboutPreferenceViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Views/Preferences/General/GeneralPreferenceViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GeneralPreferenceViewController.swift 3 | // MicMonitor 4 | // 5 | // Created by phucld on 2/28/20. 6 | // Copyright © 2020 Dwarvesf. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Preferences 11 | 12 | final class GeneralPreferenceViewController: NSViewController, PreferencePane { 13 | let preferencePaneIdentifier = PreferencePane.Identifier.general 14 | let preferencePaneTitle = "General" 15 | let toolbarItemIcon = NSImage(named: NSImage.preferencesGeneralName)! 16 | 17 | override var nibName: NSNib.Name? { "GeneralPreferenceViewController" } 18 | 19 | @IBOutlet weak var checkboxStartAtLogin: NSButton! 20 | @IBOutlet weak var checkboxKeepIconInDock: NSButton! 21 | @IBOutlet weak var checkboxShowPreferencesOnLaunch: NSButton! 22 | 23 | @IBOutlet weak var checkboxEnableNotification: NSButton! 24 | 25 | @IBOutlet weak var checkBoxEnableNotificationSound: NSButton! 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | 30 | fetchPreferenes() 31 | } 32 | 33 | private func fetchPreferenes() { 34 | checkboxStartAtLogin.state = Preference.startAtLogin ? .on : .off 35 | checkboxKeepIconInDock.state = Preference.dockIconState == .show ? .on : .off 36 | checkboxShowPreferencesOnLaunch.state = Preference.showPreferencesOnlaunch ? .on : .off 37 | checkboxEnableNotification.state = Preference.isShowNotification ? .on : .off 38 | } 39 | 40 | @IBAction func toggleStartAtLogin(_ sender: NSButton) { 41 | switch sender.state { 42 | case .on: 43 | Preference.startAtLogin = true 44 | case .off: 45 | Preference.startAtLogin = false 46 | default: 47 | break 48 | } 49 | } 50 | 51 | @IBAction func toggleKeepIconInDock(_ sender: NSButton) { 52 | switch sender.state { 53 | case .on: 54 | Preference.dockIconState = .show 55 | case .off: 56 | Preference.dockIconState = .hide 57 | default: 58 | break 59 | } 60 | } 61 | 62 | @IBAction func toggleShowPreferencesOnLaunch(_ sender: NSButton) { 63 | switch sender.state { 64 | case .on: 65 | Preference.showPreferencesOnlaunch = true 66 | case .off: 67 | Preference.showPreferencesOnlaunch = false 68 | default: 69 | break 70 | } 71 | } 72 | 73 | @IBAction func toggleEnableNotification(_ sender: NSButton) { 74 | Preference.isShowNotification = sender.state == .on 75 | } 76 | @IBAction func toggleEnableNotificationSound(_ sender: NSButton) { 77 | Preference.isEnableNotificationSound = sender.state == .on 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/Views/Preferences/General/GeneralPreferenceViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 37 | 47 | 57 | 67 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Micro Sniff/Micro Sniff/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSApplicationCategoryType 24 | public.app-category.utilities 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | LSUIElement 28 | 29 | NSHumanReadableCopyright 30 | Copyright © 2020 Dwarvesf. All rights reserved. 31 | NSMainStoryboardFile 32 | Main 33 | NSPrincipalClass 34 | NSApplication 35 | NSSupportsAutomaticTermination 36 | 37 | NSSupportsSuddenTermination 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Micro Sniff/Shared/LauncherManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LauncherManager.swift 3 | // MicMonitor 4 | // 5 | // Created by phucld on 2/29/20. 6 | // Copyright © 2020 Dwarvesf. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import ServiceManagement 11 | 12 | /// This class will help launch main app at login 13 | class LauncherManager { 14 | static let shared = LauncherManager() 15 | 16 | private let mainAppID = "foundation.dwarves.microsniff" 17 | private let launcherAppID = "foundation.dwarves.microsnifflauncher" 18 | 19 | private let appName = "Micro Sniff" 20 | private let killLaunchNtfName = Notification.Name("killLauncher") 21 | 22 | private init() {} 23 | 24 | func setupMainApp(isAutoStart: Bool) { 25 | let runningApps = NSWorkspace.shared.runningApplications 26 | let isRunning = !runningApps.filter { $0.bundleIdentifier == launcherAppID }.isEmpty 27 | 28 | SMLoginItemSetEnabled(launcherAppID as CFString, isAutoStart) 29 | 30 | if isRunning { 31 | DistributedNotificationCenter.default().post(name: killLaunchNtfName, 32 | object: Bundle.main.bundleIdentifier!) 33 | } 34 | } 35 | 36 | func setupLauncher() { 37 | let runningApps = NSWorkspace.shared.runningApplications 38 | let isRunning = !runningApps.filter { $0.bundleIdentifier == mainAppID }.isEmpty 39 | 40 | guard !isRunning else { 41 | self.terminate() 42 | return 43 | } 44 | 45 | DistributedNotificationCenter.default().addObserver(self, 46 | selector: #selector(self.terminate), 47 | name: killLaunchNtfName, 48 | object: mainAppID) 49 | 50 | // Getting path to main app 51 | let path = Bundle.main.bundlePath as NSString 52 | var components = path.pathComponents 53 | components.removeLast(3) 54 | components.append("MacOS") 55 | components.append(appName) 56 | let newPath = NSString.path(withComponents: components) 57 | NSWorkspace.shared.launchApplication(newPath) 58 | 59 | 60 | } 61 | 62 | @objc private func terminate() { 63 | NSApp.terminate(nil) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /PRIVACY_POLICY.md: -------------------------------------------------------------------------------- 1 | ## Privacy Policy 2 | 3 | Dwarves Foundation built the Micro Sniff app as an Open Source app. This app is provided by Dwarves Foundation at no cost and is intended for use as is. 4 | 5 | **Information Collection and Use** 6 | 7 | The app does NOT use any third party services that may collect information used to identify you. 8 | 9 | **Contact Us** 10 | 11 | If you have any questions or suggestions about our Privacy Policy, do not hesitate to contact us at macos@d.foundation. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 | 6 | 7 | 8 | 9 | download 10 | 11 | platform 12 | 13 | 14 | systemrequirements 15 | 16 |

17 | 18 | ## Micro Sniff 19 | 20 | This application using to monitor your Mac microphone activities. 21 | 22 |

23 | 24 |

25 | 26 | ## 🚀 Install 27 | 28 | ###  App Store 29 | 30 | [![AppStore](misc/appstore.svg)](https://apps.apple.com/us/app/micro-sniff/id1504024265?l=vi&ls=1) 31 | 32 | ### Others 33 | 34 | The Micro Sniff is notarized before distributed out side App Store. It's safe to use 👍 35 | 36 | #### Manual download 37 | 38 | - [Download latest version](https://github.com/dwarvesf/Micro-Sniff/releases/latest) 39 | - Unzip and drag the app to the Applications folder 40 | - Right click and press open in context menu to open app 41 | 42 | ## 🕹 Usage 43 | 44 | - Just need to open the app 45 | - When there is any micro device using, this overlay will appear 46 | 47 |

48 | 49 |

50 | 51 | ## ✨Contributors 52 | 53 | This project exists thanks to all the people who contribute. Thank you guys so much 👏 54 | 55 | [![](https://sourcerer.io/fame/phucledien/dwarvesf/Micro-Sniff/images/0)](https://sourcerer.io/fame/phucledien/dwarvesf/Micro-Sniff/links/0)[![](https://sourcerer.io/fame/phucledien/dwarvesf/Micro-Sniff/images/1)](https://sourcerer.io/fame/phucledien/dwarvesf/Micro-Sniff/links/1)[![](https://sourcerer.io/fame/phucledien/dwarvesf/Micro-Sniff/images/2)](https://sourcerer.io/fame/phucledien/dwarvesf/Micro-Sniff/links/2)[![](https://sourcerer.io/fame/phucledien/dwarvesf/Micro-Sniff/images/3)](https://sourcerer.io/fame/phucledien/dwarvesf/Micro-Sniff/links/3)[![](https://sourcerer.io/fame/phucledien/dwarvesf/Micro-Sniff/images/4)](https://sourcerer.io/fame/phucledien/dwarvesf/Micro-Sniff/links/4)[![](https://sourcerer.io/fame/phucledien/dwarvesf/Micro-Sniff/images/5)](https://sourcerer.io/fame/phucledien/dwarvesf/Micro-Sniff/links/5)[![](https://sourcerer.io/fame/phucledien/dwarvesf/Micro-Sniff/images/6)](https://sourcerer.io/fame/phucledien/dwarvesf/Micro-Sniff/links/6)[![](https://sourcerer.io/fame/phucledien/dwarvesf/Micro-Sniff/images/7)](https://sourcerer.io/fame/phucledien/dwarvesf/Micro-Sniff/links/7) 56 | 57 | Please read [this](CONTRIBUTING.md) before you make a contribution. 58 | 59 | ## Requirements 60 | macOS version >= 10.12 61 | 62 | ## You may also like 63 | - [Hidden Bar](https://github.com/dwarvesf/hidden) - An ultra-light MacOS utility that helps hide menu bar icons 64 | - [Blurred](https://github.com/dwarvesf/blurred) - A macOS utility that helps reduce distraction by dimming your inactive noise 65 | 66 | ## License 67 | 68 | MIT © [Dwarves Foundation](https://github.com/dwarvesf) 69 | -------------------------------------------------------------------------------- /misc/appstore.svg: -------------------------------------------------------------------------------- 1 | 2 | Download_on_the_Mac_App_Store_Badge_US-UK_RGB_blk_092917 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /misc/guide.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/misc/guide.gif -------------------------------------------------------------------------------- /misc/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/misc/icon.png -------------------------------------------------------------------------------- /misc/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwarvesf/micro-sniff/40ee5ea00713f77cd49b5b69023232b43c38ec4b/misc/screenshot.png --------------------------------------------------------------------------------