Acidham / alfred-airpod-connector

Alfred Workflow to connect/disconnect AirPods (Airpod1/2, Airpod Pro) and Powerbeats Pro
35 stars 7 forks source link

Can not work on M1 mac #4

Closed KeqiZeng closed 3 years ago

KeqiZeng commented 3 years ago

Hey guy, I have tried the WF on my m1 macbook air, but it can not work well. At first, it can't find python3, so I manually changed the path of python3 to /opt/homebrew/bin/python3. And it can not also download blueutil automatically, so I run brew install blueutil in my terminal. In my terminal i can use blueutil just like

blueutil                                                                                      
Power: 1
Discoverable: 0

However, it seems like blueutil can't be found. I got this from alfred

[22:25:09.852] AirPods Connector[Script Filter] Queuing argument '(null)'
[22:25:10.826] AirPods Connector[Script Filter] Script with argv '(null)' finished
[22:25:10.832] STDERR: AirPods Connector[Script Filter] tar: Error opening archive: Unrecognized archive format
Could not download blueutil.
/bin/sh: blueutil: No such file or directory
[22:25:10.833] AirPods Connector[Script Filter] {
    "items": [
        {
            "title": "BLUEUTIL required!",
            "subtitle": "Please install with \"brew install blueutil\" first",
            "valid": false
        }
    ]
}

I think this issue comes from m1 chip or path of blueutil, but I don't know how to tell the script where blueutil is. Any help would be appreciated!!

KeqiZeng commented 3 years ago

There are also some informations maybe can be helpful.

which brew
/opt/homebrew/bin/brew
which blueutil
/opt/homebrew/bin/blueutil
Acidham commented 3 years ago

I cannot reproduce because I have no M1 mac handy. But it seems PATH does not know /opt/homebrew/bin.

The easiest way is to open con_manager.py script and add full path to os.popen: os.popen(f'blueutil --connect {adr}')os.popen(f'/opt/homebrew/bin/blueutil --connect {adr}')

and

os.popen(f'blueutil --disconnect {adr}')os.popen(f'/opt/homebrew/bin/blueutil --disconnect {adr}')

KeqiZeng commented 3 years ago

Hi, I have tried just now and got the same issue. Here is my con_manager.py

#!/opt/homebrew/bin/python3

import os

from Alfred3 import Tools

# automatic blueutil installer
os.environ['PATH'] = os.popen('./_sharedresources "blueutil"').readline()

query = Tools.getArgv(1)
adr, switch = tuple(query.split(";"))

if switch == "disconnected":
    Tools.log(adr)
    os.popen(f'/opt/homebrew/bin/blueutil --connect {adr}')
else:
    os.popen(f'/opt/homebrew/bin/blueutil --disconnect {adr}')

And I think /opt/homebrew/bin is already in my PATH

echo $PATH
/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin:/Applications/Wireshark.app/Contents/MacOS:/Users/ketch/go/bin:/opt/homebrew/opt/llvm/bin
Acidham commented 3 years ago

Ok, sure....you get the same error because it seems to fail before within py3.sh

Can you open first script action and change it to: Language: External Script and point to airp.py

KeqiZeng commented 3 years ago

Sorry bro, I come again... I have changed the first script as you said but it can't also work well. I tried running /usr/bin/python3 airp.py in my terminal and got

{
    "items": [
        {
            "title": "Ketch's AirPods",
            "subtitle": "Ketch's AirPods are connected, Press \u23ce to disconnect...",
            "arg": "b8-81-fa-76-f8-32;connected",
            "uid": "b8-81-fa-76-f8-32",
            "icon": {
                "type": "image",
                "path": "airpod2.png"
            }
        }
    ]
}%

or

{
    "items": [
        {
            "title": "Ketch's AirPods",
            "subtitle": "Ketch's AirPods are NOT connected, \u23ce to connect...",
            "arg": "b8-81-fa-76-f8-32;disconnected",
            "uid": "b8-81-fa-76-f8-32",
            "icon": {
                "type": "image",
                "path": "airpod2_case.png"
            }
        }
    ]
}%

I think it can work so well until now, blueutil can be found. Maybe we have solved some problems .But in alfred it seems like blueutil can't also be found just like at the beginning.

[17:06:20.010] Logging Started...
[17:06:23.062] AirPods Connector[Script Filter] Queuing argument '(null)'
[17:06:23.884] AirPods Connector[Script Filter] Script with argv '(null)' finished
[17:06:23.887] STDERR: AirPods Connector[Script Filter] tar: Error opening archive: Unrecognized archive format
Could not download blueutil.
/bin/sh: blueutil: No such file or directory
[17:06:23.888] AirPods Connector[Script Filter] {
    "items": [
        {
            "title": "BLUEUTIL required!",
            "subtitle": "Please install with \"brew install blueutil\" first",
            "valid": false
        }
    ]
}

Here is my airp.py, I think I did not change it.

#!/usr/bin/python3

import json
import os
import plistlib
import sys

from Alfred3 import Items, Tools

AIRPD_PRODUCT_INDX = {
    8202: "airpodmax",
    8206: "airpodpro",
    8194: "airpod1",
    8207: "airpod2",
    8203: "powerbeatspro"

}

# automatic blueutil installer
os.environ['PATH'] = os.popen('./_sharedresources "blueutil"').readline()

def airpod_info(device_id: str) -> tuple:
    """
    Get airpod info with a given address (MAC)

    Args:
        device_id (str): MAC address of the AirPod to search

    Returns:
        tuble: productlabel, productid, vendorid
    """
    with open("/Library/Preferences/com.apple.Bluetooth.plist", "rb") as f:
        pl = plistlib.load(f)
    devices: dict = pl.get("DeviceCache")
    ret: tuple = tuple()
    for d, v in devices.items():
        if device_id in d:
            product_id: str = v.get("ProductID")
            product_label: str = AIRPD_PRODUCT_INDX.get(product_id)
            vendor_id: str = v.get("VendorID")
            ret: tuple = (product_label, product_id, vendor_id)
            break
    return ret

def paired_airpods() -> dict:
    """
    Get paired AirPods including info

    Returns:
        dict: dict with paired AirPod name including dict with info
    """
    jsn: dict = json.loads(os.popen('blueutil --paired --format json').read())
    out_dict = {}
    for i in jsn:
        address: str = i.get('address')
        prod_label, prod_id, vendor_id = airpod_info(address)
        if vendor_id == 76 and prod_id in AIRPD_PRODUCT_INDX.keys():
            out_dict.update(
                {i.get('name'):
                    {"address": i.get('address'),
                     "connected": i.get('connected'),
                     "prod_label": prod_label
                     }
                 }
            )
    return out_dict

def is_blueutil() -> bool:
    """
    Checks if Blueutil was properly installed

    Returns:
        bool: True=available, False=not found
    """
    blueutil: str = os.popen("blueutil -v").readline()
    if blueutil == "":
        return False
    else:
        return True

def main():
    query = Tools.getArgv(1)
    wf = Items()
    if is_blueutil():
        for ap_name, status in paired_airpods().items():
            adr: str = status.get('address')
            ap_type: str = status.get('prod_label')
            is_connected: bool = status.get('connected')
            con_str: str = "connected, Press \u23CE to disconnect..." if is_connected else "NOT connected, \u23CE to connect..."
            ico: str = f"{ap_type}.png" if is_connected else f"{ap_type}_case.png"
            con_switch: str = "connected" if is_connected else "disconnected"
            if query == "" or query.lower() in ap_name.lower():
                wf.setItem(
                    title=ap_name,
                    subtitle=f"{ap_name} are {con_str}",
                    arg=f"{adr};{con_switch}",
                    uid=adr
                )
                wf.setIcon(ico, "image")
                wf.addItem()
    else:
        wf.setItem(
            title="BLUEUTIL required!",
            subtitle='Please install with "brew install blueutil" first',
            valid=False
        )
        wf.addItem()
    wf.write()

if __name__ == "__main__":
    main()

and con_manager.py (I find /usr/bin/python3 can also work)

#!/usr/bin/python3

import os

from Alfred3 import Tools

# automatic blueutil installer
os.environ['PATH'] = os.popen('./_sharedresources "blueutil"').readline()

query = Tools.getArgv(1)
adr, switch = tuple(query.split(";"))

if switch == "disconnected":
    Tools.log(adr)
    os.popen(f'/opt/homebrew/bin/blueutil --connect {adr}')
else:
    os.popen(f'/opt/homebrew/bin/blueutil --disconnect {adr}')

I'm so sorry for wasting your time.

Acidham commented 3 years ago

oh forgot to mention to disable automatic blueutil installer. Comment out following line in both scripts:

os.environ['PATH'] = os.popen('./_sharedresources "blueutil"').readline()

KeqiZeng commented 3 years ago

Hey, I think I find the problem. I have tested as you said, but it seems nothing changed. Then I change all blueutil in airp.py to /opt/homebrew/bin/blueutil and it can work well for me. Before this, airp.py can actually work well in my terminal but not in alfred. It's so strange.

So, I think for m1 mac, maybe we need to

  1. open first script action and change it to: Language: External Script, and point to airp.py

  2. then change all blueutil to /opt/homebrew/bin/blueutil in both scripts.

I hope this is helpful for those who use m1 mac. Finally, thank you again for the nice WF. It's great!