gopro / OpenGoPro

An open source interface specification to communicate with a GoPro camera with accompanying demos and tutorials.
https://gopro.github.io/OpenGoPro/
MIT License
651 stars 148 forks source link

MacOS WIFI Driver broken on >= Sonoma 14.x because `airport` command is retired #506

Open epheo opened 3 months ago

epheo commented 3 months ago

Apple has officially retired the airport command-line utility on macOS Sonoma 14.4

Component Python SDK

Description SSID Scan does not work anymore on recent Mac OS X version.

To Reproduce Try connecting to a GoPro using Mac OS Sonoma 14.4

Expected behavior Python SDK should migrate to wdutil as per Apple recomandation.

Screenshots

~ % airport
WARNING: The airport command line tool is deprecated and will be removed in a future release.
For diagnosing Wi-Fi related issues, use the Wireless Diagnostics app or wdutil command line tool.
epheo commented 3 months ago

So wdutil only returns the known SSIDs... I'm struggling to find a suitable alternative to airport -scan.

One (not so straightforward) alternative could be to conditionally add pyobjc dependency if system is darwin (can rely on sys_platform marker https://python-poetry.org/docs/dependency-specification/#using-environment-markers) and make use of the native ObjC /System/Library/Frameworks/CoreWLAN.framework.

[tool.poetry.dependencies]
pyobjc = {version = "*", markers = "sys_platform == 'darwin'"}

Then something like:

import objc
from CoreWLAN import *

def scan_wifi_networks(interface_name=None):
    # Load CoreWLAN framework
    objc.loadBundle('CoreWLAN',
                    bundle_path='/System/Library/Frameworks/CoreWLAN.framework',
                    module_globals=globals())

    # Create a CWInterface object
    if interface_name:
        interface = CWInterface.interfaceWithName_(interface_name)
    else:
        interface = CWInterface.interface()

    # Scan for networks
    error = None
    networks, error = interface.scanForNetworksWithSSID_error_(None, None)

    if error:
        print(f"Error scanning for Wi-Fi networks: {error}")
    else:
        for network in networks:
            print(f"SSID: {network.ssid()}")
sfoley-gpqa commented 3 months ago

So wdutil only returns the known SSIDs... I'm struggling to find a suitable alternative to airport -scan.

One (not so straightforward) alternative could be to conditionally add pyobjc dependency if system is darwin (can rely on sys_platform marker https://python-poetry.org/docs/dependency-specification/#using-environment-markers) and make use of the native ObjC /System/Library/Frameworks/CoreWLAN.framework.

[tool.poetry.dependencies]
pyobjc = {version = "*", markers = "sys_platform == 'darwin'"}

Then something like:

import objc
from CoreWLAN import *

def scan_wifi_networks(interface_name=None):
    # Load CoreWLAN framework
    objc.loadBundle('CoreWLAN',
                    bundle_path='/System/Library/Frameworks/CoreWLAN.framework',
                    module_globals=globals())

    # Create a CWInterface object
    if interface_name:
        interface = CWInterface.interfaceWithName_(interface_name)
    else:
        interface = CWInterface.interface()

    # Scan for networks
    error = None
    networks, error = interface.scanForNetworksWithSSID_error_(None, None)

    if error:
        print(f"Error scanning for Wi-Fi networks: {error}")
    else:
        for network in networks:
            print(f"SSID: {network.ssid()}")

This seems like a good solution; however, on my system, the SSID is None for every network object. I've searched for a solution, gave Full Disk Access to both iTerm and terminal but no luck so far. I also tried to grant Location Services access to iTerm/terminal but I don't see an option to manually add them in macOS 14.3.1.

If you have further input, please feel free to offer it! 😬

epheo commented 3 months ago

I do not own a Macintosh, and MacOS Ventura is the latest I can virtualize atm. After borrowing a system with 14.4.1 for a moment I could get a correct SSID as follow:

[...]
>>> import CoreLocation
>>> location_manager = CoreLocation.CLLocationManager.alloc().init()
>>> location_manager.startUpdatingLocation()
>>> networks, error = iface.scanForNetworksWithName_error_( None, None )
>>> print(networks)

Dependencies:

I tested on MacOS 14.4.1 with Python 3.11 installed via brew and pyobjc 10.2. The following references seems to indicate that behavior may differ between systems and Python Versions.

sfoley-gpqa commented 3 months ago

I do not own a Macintosh, and MacOS Ventura is the latest I can virtualize atm. After borrowing a system with 14.4.1 for a moment I could get a correct SSID as follow:

[...]
>>> import CoreLocation
>>> location_manager = CoreLocation.CLLocationManager.alloc().init()
>>> location_manager.startUpdatingLocation()
>>> networks, error = iface.scanForNetworksWithName_error_( None, None )
>>> print(networks)

Dependencies:

  • pyobjc-framework-CoreWLAN
  • pyobjc-framework-CoreLocation

I tested on MacOS 14.4.1 with Python 3.11 installed via brew and pyobjc 10.2. The following references seems to indicate that behavior may differ between systems and Python Versions.

Running macOS Sonoma 14.3.1 (23D60) and using the code below, I'm getting null for all SSIDs:

python3 -m venv /tmp/scantest
/tmp/scantest/bin/pip3 install pyobjc
...
/tmp/scantest/bin/python3
Python 3.10.13 (main, Sep 13 2023, 13:03:08) [Clang 14.0.3 (clang-1403.0.22.14.1)] on darwin
>>> import CoreLocation
>>> from CoreWLAN import CWInterface, CWWiFiClient
>>> wifi_client: CWWiFiClient = CWWiFiClient.sharedWiFiClient()
>>> interface = wifi_client.interface()
>>> location_manager = CoreLocation.CLLocationManager.alloc().init()
>>> location_manager.startUpdatingLocation()
>>> networks, error = interface.scanForNetworksWithName_error_( None, None )
>>> print(networks)
{(
    <CWNetwork: 0x600003afc4a0> [ssid=(null), bssid=(null), security=WPA2 Personal, rssi=-27, channel=<CWChannel: 0x600003ad4040> [channelNumber=1(2GHz), channelWidth={20MHz}], ibss=0],
    <CWNetwork: 0x600003afc500> [ssid=(null), bssid=(null), security=WPA2 Personal, rssi=-84, channel=<CWChannel: 0x600003ad8800> [channelNumber=136(5GHz), channelWidth={80MHz}], ibss=0],
    <CWNetwork: 0x600003afc580> [ssid=(null), bssid=(null), security=WPA2 Personal, rssi=-77, channel=<CWChannel: 0x600003ad87d0> [channelNumber=11(2GHz), channelWidth={20MHz}], ibss=0],
    <CWNetwork: 0x600003afc4f0> [ssid=(null), bssid=(null), security=WPA2 Personal, rssi=-38, channel=<CWChannel: 0x600003ad8830> [channelNumber=149(5GHz), channelWidth={80MHz}], ibss=0],
    <CWNetwork: 0x600003afc670> [ssid=(null), bssid=(null), security=WPA/WPA2 Personal, rssi=-74, channel=<CWChannel: 0x600003ad8860> [channelNumber=10(2GHz), channelWidth={20MHz}], ibss=0]
)}
epheo commented 3 months ago

weird... So I tried combining both Python 3.11 (from brew), PyObjc 9.2 (to satisfy Bleak dependencies), Python 3.9 (from system) and PyObjc 10.2 (latest) and all seems to be working fine on 14.4.1 If 14.3 and 14.4 are handling this differently, wow... such mess.

% poetry show |egrep "(corewlan|corelocation)" 
pyobjc-framework-corelocation  9.2            Wrappers for the framework Co...
pyobjc-framework-corewlan      9.2            Wrappers for the framework Co...

% poetry run python3 --version
Python 3.11.8

% sw_vers
ProductName:        macOS
ProductVersion:     14.4.1
BuildVersion:       23E224

% poetry run python3
Python 3.11.8 (main, Feb  6 2024, 21:21:21) [Clang 15.0.0 (clang-1500.1.0.2.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import CoreLocation
>>> from CoreWLAN import CWInterface, CWWiFiClient
>>> wifi_client: CWWiFiClient = CWWiFiClient.sharedWiFiClient()
>>> interface = wifi_client.interface()
>>> location_manager = CoreLocation.CLLocationManager.alloc().init()
>>> location_manager.startUpdatingLocation()
>>> networks, error = interface.scanForNetworksWithName_error_( None, None )
>>> print(networks)
{(
    <CWNetwork: 0x60000289c620> [ssid=Almacen Yuniko, bssid=<redacted>, security=WPA2 Personal, rssi=-88, channel=<CWChannel: 0x60000289cb20> [channelNumber=10(2GHz), channelWidth={40MHz(-1)}], ibss=0],
    <CWNetwork: 0x60000289c6a0> [ssid=lar 2, bssid=<redacted>, security=WPA/WPA2 Personal, rssi=-87, channel=<CWChannel: 0x60000289cb50> [channelNumber=7(2GHz), channelWidth={40MHz(+1)}], ibss=0],
    <CWNetwork: 0x60000289c780> [ssid=DIR-615-0914, bssid=<redacted>, security=WPA2 Personal, rssi=-87, channel=<CWChannel: 0x60000289cb80> [channelNumber=13(2GHz), channelWidth={40MHz(-1)}], ibss=0],
    <CWNetwork: 0x60000289c690> [ssid=LAR1, bssid=<redacted>, security=WPA2 Personal, rssi=-50, channel=<CWChannel: 0x60000289cbb0> [channelNumber=2(2GHz), channelWidth={40MHz(+1)}], ibss=0],
    <CWNetwork: 0x60000289c860> [ssid=Lar13, bssid=<redacted>, security=WPA/WPA2 Personal, rssi=-74, channel=<CWChannel: 0x60000289cbe0> [channelNumber=1(2GHz), channelWidth={40MHz(+1)}], ibss=0],
    <CWNetwork: 0x60000289c920> [ssid=NETGEAR71, bssid=<redacted>, security=WPA2 Personal, rssi=-91, channel=<CWChannel: 0x60000289cc10> [channelNumber=9(2GHz), channelWidth={20MHz}], ibss=0],
    <CWNetwork: 0x60000289c9b0> [ssid=Tara, bssid=<redacted>, security=WPA/WPA2 Personal, rssi=-94, channel=<CWChannel: 0x60000289cc40> [channelNumber=6(2GHz), channelWidth={40MHz(+1)}], ibss=0],
    <CWNetwork: 0x60000289ca90> [ssid=Administración, bssid=<redacted>, security=WPA2 Personal, rssi=-84, channel=<CWChannel: 0x60000289cc70> [channelNumber=8(2GHz), channelWidth={20MHz}], ibss=0]
)}
% sw_vers
ProductName:        macOS
ProductVersion:     14.4.1
BuildVersion:       23E224

% python3 -m venv /tmp/scantest
% /tmp/scantest/bin/pip3 install pyobjc

% /tmp/scantest/bin/pip3 list |egrep "(CoreWLAN|CoreLocation)" 
pyobjc-framework-CoreLocation                     10.2
pyobjc-framework-CoreWLAN                         10.2

% /tmp/scantest/bin/python3
Python 3.9.6 (default, Feb  3 2024, 15:58:28) 
[Clang 15.0.0 (clang-1500.3.9.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import CoreLocation
>>> from CoreWLAN import CWInterface, CWWiFiClient
>>> wifi_client: CWWiFiClient = CWWiFiClient.sharedWiFiClient()
>>> interface = wifi_client.interface()
>>> location_manager = CoreLocation.CLLocationManager.alloc().init()
>>> location_manager.startUpdatingLocation()
>>> networks, error = interface.scanForNetworksWithName_error_( None, None )
>>> print(networks)
{(
    <CWNetwork: 0x600002080200> [ssid=lar 2, bssid=(null), security=WPA/WPA2 Personal, rssi=-84, channel=<CWChannel: 0x600002088000> [channelNumber=7(2GHz), channelWidth={40MHz(+1)}], ibss=0],
    <CWNetwork: 0x6000020802d0> [ssid=LAR14, bssid=(null), security=WPA/WPA2 Personal, rssi=-91, channel=<CWChannel: 0x600002095300> [channelNumber=5(2GHz), channelWidth={40MHz(-1)}], ibss=0],
    <CWNetwork: 0x600002080380> [ssid=ETAZLA, bssid=(null), security=WPA2 Personal, rssi=-85, channel=<CWChannel: 0x600002095330> [channelNumber=4(2GHz), channelWidth={40MHz(+1)}], ibss=0],
    <CWNetwork: 0x6000020802c0> [ssid=LAR1, bssid=(null), security=WPA2 Personal, rssi=-55, channel=<CWChannel: 0x600002095360> [channelNumber=2(2GHz), channelWidth={40MHz(+1)}], ibss=0],
    <CWNetwork: 0x600002080460> [ssid=DIR-615-0914, bssid=(null), security=WPA2 Personal, rssi=-84, channel=<CWChannel: 0x600002095390> [channelNumber=13(2GHz), channelWidth={40MHz(-1)}], ibss=0]
)}

I do not have any 14.3 systems at hand but working on virtualizing Sonoma. Would you by chance have a MacOS CI/CD test pipeline or similar to compare versions ?

tcamise-gpsw commented 2 months ago

I also am getting null SSID's on Sonoma 14.4.1. Is my understanding correct that this is because location services are not enabled? At least on my machine, I do not appear to have anyway of enabling them.

epheo commented 2 months ago

At least on the macbook someone landed me location_manager.startUpdatingLocation() is opening a graphical pop-up where you could approve the request for location services permission. I also tested this an another 14.4.1 macintosh and got similar results with correct SSIDs.

However this inconsistency seems to corroborate others people findings as mentioned in:

I am not familiar with Apple's operating system and hardware but if, indeed, their low level tooling is unable to provide consistent results another option could be to adopt the same approach as for Windows and just try to connect until it either succeed or the maximum amount of attempts is reached.

(This may even be a simpler option as it wouldn't require Location Service permission and dependency)

tcamise-gpsw commented 2 months ago

I do think the finite retries combined with your check for location authorization is the only path forward right now. Regardless I still need to find a way to enable location services on my Mac.

keithrbennett commented 2 months ago

I am very interested in a solution to this too. It seems weird to me that special permissions are now required to access information that the logged in user can already access using the GUI. Also weird that Apple would choose to remove functionality from one utility without providing it in another one. I'm the author of a Ruby command line utility that reports and manages wifi state (https://github.com/keithrbennett/wifiwand) and this is a huge problem for my users and me too.

In addition to removing the ability to display all available networks, the removal of the airport utility also eliminates the abilities to do the following, which were, until now, supported in wifiwand:

keithrbennett commented 2 months ago

FYI, I have been able to restore wifi functionality by using Swift scripts, shelling out to them and then ingesting and parsing their output. However, these Swift scripts use CoreWLAN and, I believe, will not work unless XCode is installed. Here is the script that lists the names of available networks:

import Foundation
import CoreWLAN

class NetworkScanner {
    var currentInterface: CWInterface

    init?() {
        // Initialize with the default Wi-Fi interface
        guard let defaultInterface = CWWiFiClient.shared().interface(),
              defaultInterface.interfaceName != nil else {
            return nil
        }
        self.currentInterface = defaultInterface
        self.scanForNetworks()
    }

    func scanForNetworks() {
        do {
            let networks = try currentInterface.scanForNetworks(withName: nil)
            for network in networks {
                print("\(network.ssid ?? "Unknown")")
            }
        } catch let error as NSError {
            print("Error: \(error.localizedDescription)")
        }
    }
}

NetworkScanner()

You can put this code in a file with '.swift' extension, e.g. AvailableWifiNetworkLister.swift and then run the file with the swift interpreter, i.e. swift AvailableWifiNetworkLister.swift.

sfoley-gpqa commented 2 months ago

@keithrbennett ,

Thank you for the update above. I installed XCode to get the Swift interpreter but am having several issues:

Issue 1

When I run swift AvailableWifiNetworkListener.swift, I get the following error:

JIT session error: Symbols not found: [ _OBJC_CLASS_$_CWNetwork, _OBJC_CLASS_$_CWWiFiClient ]
...

Issue 2

If I copy-paste the code into the swift interpreter (i.e. run swift by itself, paste code), everything works including printing the SSIDs. However, this is obviously not a good automated solution

Issue 3

I tried compiling the code into a binary/app and that worked but the output showed "Unknown" for all the SSIDs (i.e. missing Location Services permission and running the application did not cause any popups)

swiftc -framework CoreWLAN -o AvailableWifiNetworkListener AvailableWifiNetworkListener.swift

Issue 4

I tried making a minimal standalone app in the hopes of causing a Location Services popup, updating the swift code to write the SSIDs to a temporary file; however, the values are still "Unknown":

tree AvailableWifiNetworkListener.app/
AvailableWifiNetworkListener.app/
└── Contents
    ├── Info.plist
    └── MacOS
        └── AvailableWifiNetworkListener

3 directories, 2 files
cat AvailableWifiNetworkListener.app/Contents/Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleExecutable</key>
    <string>AvailableWifiNetworkListener</string>
    <key>CFBundleIdentifier</key>
    <string>com.example.mywifiapp</string>
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>This app needs access to location for scanning Wi-Fi networks.</string>
</dict>
</plist>
import Foundation
import CoreWLAN

class NetworkScanner {
    var currentInterface: CWInterface

    init?() {
        // Initialize with the default Wi-Fi interface
        guard let defaultInterface = CWWiFiClient.shared().interface(),
              defaultInterface.interfaceName != nil else {
            return nil
        }
        self.currentInterface = defaultInterface
        self.scanForNetworks()
    }

    func scanForNetworks() {
        let fileManager = FileManager.default
        let fileURL = fileManager.temporaryDirectory.appendingPathComponent("wifiScanResults.txt")
        do {
            let networks = try currentInterface.scanForNetworks(withName: nil)
            var results = [String]()
            for network in networks {
                results.append(network.ssid ?? "Unknown")
            }
            let content = results.joined(separator: "\n")
            try content.write(to: fileURL, atomically: true, encoding: .utf8)
            print("File written to \(fileURL.path)")
        } catch {
            print("Failed to scan networks or write to file: \(error)")
        }
    }
}

_ = NetworkScanner()
cat /var/folders/tp/nb0kgz6935g59xrhs_rwsps40000gn/T/wifiScanResults.txt
Unknown
Unknown
Unknown
Unknown

Note: I am not a macOS/Swift developer. All of the above stuff was kludged together with fingers crossed 🫠

keithrbennett commented 2 months ago

@sfoley-gpqa You may just need to install XCode's command line tools: xcode-select --install. See https://www.freecodecamp.org/news/install-xcode-command-line-tools/ for an article about it.

sfoley-gpqa commented 2 months ago

@keithrbennett

My team has had that installed for years 😬

xcode-select --install
xcode-select: note: Command line tools are already installed. Use "Software Update" in System Settings or the softwareupdate command line interface to install updates
keithrbennett commented 2 months ago

@sfoley-gpqa:

Issue 1

What version do you get when you run pkgutil --pkg-info=com.apple.pkg.CLTools_Executables? I get 15.3.0.0.1.1708646388 and my system says I am up to date.

When you run swift --version, what do you get? I get: Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)

I did some brief research and this error occurred in prior versions of Swift. I'm hoping that your version is not up to date, and an update will fix it?

Issue 2

When running swift by itself, I get some help output. What is it you pasted the code into? swift repl starts a text mode REPL, is that what it was? Or did you mean XCode?

Issues 3 & 4

I had the same result. Github Copilot had the information and advice pasted below. This is more trouble than it's worth for me at the moment, since for my purposes it's ok not to compile the script:


The issue might be related to the permissions required to access WiFi information on macOS. When you run a Swift script using the Swift interpreter (i.e., swift script.swift), it runs in a sandboxed environment which has limited access to system resources. This is a security feature of macOS.

However, when you compile a Swift program into a binary and run it, it's not sandboxed in the same way. Therefore, it might not have the necessary permissions to access the WiFi information.

To resolve this issue, you can try to add the necessary entitlements to your Swift program and then codesign it. This process involves creating an entitlements file, compiling your Swift program into a binary, and then signing that binary with the entitlements file.

Here's a step-by-step guide:

  1. Create an entitlements file named entitlements.plist with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.wifi.scan</key>
    <true/>
</dict>
</plist>
  1. Compile your Swift program into a binary:
swiftc -o networkscanner AvailableWifiNetworkLister.swift
  1. Sign the binary with the entitlements file:
codesign --entitlements entitlements.plist -s - networkscanner

Please note that this process requires you to have a valid Developer ID Application certificate in your keychain. If you don't have one, you can create one in the Apple Developer portal. Also, keep in mind that this process might not work if your app is distributed outside of the Mac App Store, as the necessary entitlements might not be granted.


sfoley-gpqa commented 2 months ago

@keithrbennett

Re: Issue 1

pkgutil --pkg-info=com.apple.pkg.CLTools_Executables
package-id: com.apple.pkg.CLTools_Executables
version: 15.1.0.0.1.1700200546
volume: /
location: /
install-time: 1707601182

swift --version
Apple Swift version 5.9.2 (swiftlang-5.9.2.2.56 clang-1500.1.0.2.5)
Target: arm64-apple-darwin23.3.0

This is very odd. I just installed Xcode yesterday in order to get Swift. Xcode says the version is Version 15.3 (15E204a) and (quick Internet search) says the best way to update Swift is to update Xcode--yet I don't have as high a version as you. Hmmm...

I verified that I only have one version of Xcode installed several different ways so there doesn't seem to be a versioning conflict.

Re: Issue 2

Sorry for being unclear. I am more familiar with Python and its interpreter (i.e. Run python from the terminal). When I run swift by itself in my terminal, I get something like the Python interpreter, which allows me to write (or copy-paste) Swift code and have it run immediately:

swift
Welcome to Apple Swift version 5.9.2 (swiftlang-5.9.2.2.56 clang-1500.1.0.2.5).
Type :help for assistance.
  1> import Foundation
  2> print("Hello, world!")
Hello, world!

Re: Issues 3 & 4

Thanks for taking the time to compare results. In order to scan for the camera's SSID after enable AP Mode, our teams need to have an automated way (SDK, API, CLI tool) to scan for SSIDs without having to do any manual steps like pasting Swift code into the interpreter (or, ideally, without having to install Swift at all. Hmmm....)

I totally understand that you may not have time to dig much deeper into this. I appreciate the time you've put into it already! 😀

Re: Copilot

I tried the steps without having the Developer ID Application certificate in my keychain as a litmus test but when I ran the compiled application, something Killed: 9 it. I may have to look into how to get the developer ID stuff setup but I am still really hoping that there is a simpler solution than this particular rabbit hole.

Thanks again for your time and feedback!

tcamise-gpsw commented 2 months ago

@keithrbennett Here's a strange half-baked idea. Since you're already managing this in the WifiWand package, I wonder if it would be feasible for me to consume WifiWand in my Python SDK here. I guess this would be something like:

  1. Compile WifiWand to binary
  2. Ship binary as part of Python SDK when installing on MacOS

Are you aware of any obvious pitfalls to this idea? Specifically:

  1. Will I need to compile per MacOS version or do any other special handling for MacOS versions?
  2. Will the compiled WifiWand handle gathering the necessary permissions?

For the sake of simplicity, let's assume that XCode is always available on the host machine.

keithrbennett commented 2 months ago

@sfoley-gpqa

Re: Issue 1

It seems that those tools were installed in Februrary, does that sound right? This is your installation timestamp converted to date in Ruby's irb (REPL):

:001 > Time.at(1707601182).utc
 => 2024-02-10 21:39:42 UTC

Is it possible that you brew installed swift and that is what is being run? Does brew install swift show that it is (brew) installed?

Re: Issue 2

I'm surprised that you get that prompt when you run swift by itself. I get:

$ swift

Welcome to Swift!

Subcommands:

  swift build      Build Swift packages
  swift package    Create and work on packages
  swift run        Run a program from a package
  swift test       Run package tests
  swift repl       Experiment with Swift code interactively

  Use `swift --version` for Swift version information.

  Use `swift --help` for descriptions of available options and flags.

  Use `swift help <subcommand>` for more information about a subcommand.

Re: Issues 3 & 4

I used Swift because it was simpler to run the Swift interpreter on a (text) source code file than to build a Mac executable with all the ceremony that entails. Your use case might justify using Objective C instead; it is lower level than Swift and may require fewer setup steps for your users. Also, for either language, if you built an application (rather than interpreting a script), then you would only need all the setup on a single dev machine to build the app. I realize that Swift is not working for built applications -- it's possible that Objective C would work ok, I don't know.

Re: Copilot

I tried building and running the application, and for me too, when I ran it, it immediately exited. I suspect that the OS knew that it was not a legit app and killed it. Fixing it may be as simple as getting a developer registration. I'm not thrilled about having to do that either. ;)

Re: Calling WifiWand from Python

@tcamise-gpsw, although you could certainly call a Ruby (or any other) executable from Python, there is no way that I know of to compile a Ruby application to binary. Also, you would still need to install all the Ruby infrastucture and gems (wifiwand plus its dependencies).

It wouldn't help with building Ruby into a binary, but in other ways, it's possible that JRuby would work better for you, I don't know. JRuby is Ruby that is run by a Ruby interpreter written in Java and running on the JVM. From the point of view of the system, the Ruby app is really a Java app, so the configuration issues might be easier to resolve, since Java use is widespread. If your users' machines already have Java installed, and/or your organization uses Java applications, this might be worth looking into. However, it takes a couple of seconds for a JVM to start up, and that may be a dealbreaker for you.

My gut feeling is that your best bet is to write something in Python yourselves. The wifi reporting and management part of wifiwand (as opposed to its UI) is quite simple and consists mostly of running Mac OS external applications. If you extract that functionality and tailor it for your specific use case, then I believe it would not be complex or overly time consuming. Regarding the Swift part, it's possible that Objective-C might turn out to be a better option if, for example, the CoreWLAN functionality can be statically linked into the distributed application, eliminating the need for it to be found on the user's system at runtime.

I am available to help on a consulting basis if that would be helpful.

tcamise-gpsw commented 2 months ago

Ok thank you for the information. I agree this doesn't seem worth following up on since there is no easy way to "compile" Ruby gems.

To summarize this entire thread specifically in regards to the open-gopro implementation:

  1. MacOS WiFi Driver is broken on >= Sonoma 14.x because airport will be deprecated. We therefore have no way to scan for SSIDs
  2. It is possible to use CoreWLAN via PyObjC to scan for SSIDs as a replacement
  3. This requires that the relevant Python executable has been authorized for location services.

So the simplest solution here is to (for MacOS >= 14):

I propose this as the immediate fix here and will start implementing / testing it soon.

There can be further investigation to figure out:

FYI I have been contemplating splitting this WiFi "driver" functionality out of the Open GoPro Python SDK into its own package and will probably do this at some point. Then just consume it from the Python SDK.