├── demo.gif ├── icon.png ├── blank.png ├── disconnect.png ├── unconfigured.png ├── 9A8B35BB-F0A4-4634-9CA2-338AE0D4F306.png ├── A76D5B19-978D-4C5F-AE57-63EBE5A3B974.png ├── LICENSE ├── get_devices.py ├── get_connection_status.py └── README.md /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariuskiessling/alfred-airpods-connector/HEAD/demo.gif -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariuskiessling/alfred-airpods-connector/HEAD/icon.png -------------------------------------------------------------------------------- /blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariuskiessling/alfred-airpods-connector/HEAD/blank.png -------------------------------------------------------------------------------- /disconnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariuskiessling/alfred-airpods-connector/HEAD/disconnect.png -------------------------------------------------------------------------------- /unconfigured.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariuskiessling/alfred-airpods-connector/HEAD/unconfigured.png -------------------------------------------------------------------------------- /9A8B35BB-F0A4-4634-9CA2-338AE0D4F306.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariuskiessling/alfred-airpods-connector/HEAD/9A8B35BB-F0A4-4634-9CA2-338AE0D4F306.png -------------------------------------------------------------------------------- /A76D5B19-978D-4C5F-AE57-63EBE5A3B974.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariuskiessling/alfred-airpods-connector/HEAD/A76D5B19-978D-4C5F-AE57-63EBE5A3B974.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Marius Kießling 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 | -------------------------------------------------------------------------------- /get_devices.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import re 5 | import json 6 | 7 | TRUE, pout = os.popen4('/bin/bash -c /usr/local/bin/BluetoothConnector') 8 | devicesRaw = pout.read() 9 | 10 | deviceMacs = [] 11 | deviceNames = [] 12 | airPodsPosition = -1 13 | 14 | macsRegex = r"^([0-9A-Fa-f]{2}[-]){5}([0-9A-Fa-f]{2})" 15 | macsMatches = re.finditer(macsRegex, devicesRaw, re.MULTILINE) 16 | 17 | for matchNum, match in enumerate(macsMatches, start=1): 18 | if match.group() != '': 19 | deviceMacs.append(match.group()) 20 | 21 | namesRegex = r"(?<=\s-\s).*" 22 | namesMatches = re.finditer(namesRegex, devicesRaw, re.MULTILINE) 23 | 24 | for matchNum, match in enumerate(namesMatches, start=1): 25 | if match.group() != '': 26 | deviceNames.append(match.group()) 27 | # Save AirPods device position 28 | if "AirPods" in match.group(): 29 | airPodsPosition = matchNum 30 | 31 | # Remove AirPods and prepend them 32 | if airPodsPosition != -1: 33 | deviceMacs.insert(0, deviceMacs.pop(airPodsPosition - 1)) 34 | deviceNames.insert(0, deviceNames.pop(airPodsPosition - 1)) 35 | 36 | menu = { 37 | "items": [ 38 | ] 39 | } 40 | 41 | for i, mac in enumerate(deviceMacs): 42 | item = { 43 | "type": "default", 44 | "title": deviceNames[i], 45 | "subtitle": "MAC: " + mac + (" (These probably are your AirPods!)" if "AirPods" in deviceNames[i] else ""), 46 | "arg": mac, 47 | "icon": { 48 | "path": "icon.png" 49 | } 50 | } 51 | menu["items"].append(item) 52 | 53 | print(json.dumps(menu)) 54 | -------------------------------------------------------------------------------- /get_connection_status.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import re 5 | import json 6 | import sys 7 | 8 | mac = os.getenv('AIRPODS_MAC') 9 | 10 | if mac == None or mac == "": 11 | menu = { 12 | "items": [ 13 | { 14 | "uid": 0, 15 | "type": "default", 16 | "title": "Your setup isn't completet yet.", 17 | "subtitle": "Configure your AirPods device to get started.", 18 | "arg": "unconfigured", 19 | "icon": { 20 | "path": "unconfigured.png" 21 | } 22 | } 23 | ] 24 | } 25 | print(json.dumps(menu)) 26 | sys.exit() 27 | 28 | devices = os.popen('/bin/bash -c "system_profiler SPBluetoothDataType"').read() 29 | 30 | connectedRegex = r"(Address: " + mac.upper() + "(.*\n)*?.*Connected: )(Yes|No)" 31 | statusMatch = re.finditer(connectedRegex, devices, re.MULTILINE) 32 | 33 | status = "" 34 | 35 | for matchNum, match in enumerate(statusMatch): 36 | if match.group() != '': 37 | if match.group(3) == "Yes": 38 | status = "connected" 39 | else: 40 | status = "disconnected" 41 | 42 | menu = { 43 | "items": [ 44 | { 45 | "uid": 0, 46 | "type": "default", 47 | "title": "Disconnect AirPods" if status == "connected" else "Connect AirPods", 48 | "subtitle": "Status: " + status, 49 | "arg": status, 50 | "icon": { 51 | "path": "icon.png" if status == "disconnected" else "disconnect.png" 52 | } 53 | } 54 | ] 55 | } 56 | 57 | print(json.dumps(menu)) 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Alfred AirPods Connector](https://www.packal.org/workflow/airpods-connector) 2 | 3 | > This [Alfred](https://www.alfredapp.com/) workflow helps you to quickly connect to your AirPods using the Alfred search or a hotkey. 4 | 5 | ###### See it in action 6 | ![Workflow demo](https://github.com/mariuskiessling/alfred-airpods-connector/raw/master/demo.gif "Workflow demo") 7 | 8 | ## Installation 9 | 1) Install the `BluetoothConnector` utility using 10 | ``` 11 | brew install bluetoothconnector 12 | ``` 13 | Make sure to have [`brew`](https://brew.sh/) installed. This will install the latest version of BluetoothConnector. Unfortunately BluetoothConnector is not @-versioned. Thus, make sure that brew installs version `2.0.0+` of BluetoothConnector or upgrade using 14 | ``` 15 | brew upgrade bluetoothconnector 16 | ``` 17 | in case you update from an older version of this workflow. 18 | 19 | 2) Download the workflow file [↗ here](https://github.com/packal/repository/raw/master/de.mariuskiessling.alfred-airpods-connector/airpods_connector.alfredworkflow) and import it. 20 | 21 | ###### In case of errors 22 | If you encounter any errors running the workflow double-check the steps above and test whether you have Python installed on your system (version 2.7 and version 3.* are compatible with this workflow). 23 | 24 | ## Usage 25 | On your first run, you have to select the bluetooth device that are your AirPods. Once you have done this you can use the **`airp`** keyword to connect / disconnect your AirPods. Whenever you want to change the device you can use the **`airp config`** keyword to select a new bluetooth device. 26 | 27 | By default, this workflow also offers the ability to trigger a connect / disconnect action using a hotkey. The default hotkey is **`⌃ Ctrl + ⇧ Shift + ⌘ Cmd + A`**. 28 | 29 | ## Acknowledgments 30 | * Big thanks to Daniel Schäfer ([@JohnAZoidberg](https://github.com/JohnAZoidberg)) for helping design the RegEx used in this workflow. 31 | --------------------------------------------------------------------------------