!touch test.html
import webbrowser
webbrowser.get('Safari').open('test.html')
True
Call LSCopyDefaultApplicationURLForURL to discover the location of the application associated with a given url.
Use ctypes to call apple APIs.
import ctypes
from ctypes import c_void_p, Structure, POINTER
kCFStringEncodingUTF8 = 0x08000100
CoreFoundation = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation'))
LaunchServices = ctypes.cdll.LoadLibrary(ctypes.util.find_library('LaunchServices'))
# create CFURL from bytes
CFURLCreateWithBytes = CoreFoundation.CFURLCreateWithBytes
CFURLCreateWithBytes.restype = c_void_p
CFURLCreateWithBytes.argtypes = [c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_uint32, c_void_p]
# for debugging CreateWithBytes
CFURLGetBytes = CoreFoundation.CFURLGetBytes
CFURLGetBytes.restype = ctypes.c_int
CFURLGetBytes.argtypes = [c_void_p, ctypes.c_char_p, ctypes.c_int]
# get App associated with CFURL
LSCopyDefaultApplicationURLForURL = LaunchServices.LSCopyDefaultApplicationURLForURL
LSCopyDefaultApplicationURLForURL.restype = c_void_p
LSCopyDefaultApplicationURLForURL.argtypes = [c_void_p, c_void_p, c_void_p]
def app_for_url(url):
"""Returns file:// url for the app associated with a url"""
cf_url = CFURLCreateWithBytes(
None,
url.encode('utf8'),
len(url),
kCFStringEncodingUTF8,
None,
)
cf_app = LSCopyDefaultApplicationURLForURL(cf_url, None, None)
if cf_app is None:
raise ValueError("No app found for %s" % url)
buf = (ctypes.c_char * 1024)()
CFURLGetBytes(cf_app, buf, len(buf))
return buf.value.decode('utf8')
app_for_url("http://google.com")
'file:///Applications/Safari.app/'
Raises if there's no application found
import os
app_for_url("file://" + os.path.abspath("mac-detect-browser.ipynb"))
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-41-bd1a3a99e3f4> in <module> 1 import os ----> 2 app_for_url("file://" + os.path.abspath("mac-detect-browser.ipynb")) <ipython-input-39-d5c013765a13> in app_for_url(url) 10 cf_app = LSCopyDefaultApplicationURLForURL(cf_url, None, None) 11 if cf_app is None: ---> 12 raise ValueError("No app found for %s" % url) 13 buf = (ctypes.c_char * 1024)() 14 CFURLGetBytes(cf_app, buf, len(buf)) ValueError: No app found for file:///Users/benjaminrk/dev/mine/notebooks/empty/mac-detect-browser.ipynb
import os
app_for_url("file://" + os.path.abspath("untitled.txt"))
'file:///Applications/Sublime+Text.app/'
webbrowser doesn't know how to handle absolute paths:
webbrowser.get("/Applications/Safari.app")
--------------------------------------------------------------------------- Error Traceback (most recent call last) <ipython-input-44-1f7ad1cf2a7d> in <module> ----> 1 webbrowser.get("/Applications/Safari.app") ~/conda/lib/python3.6/webbrowser.py in get(using) 49 elif command[0] is not None: 50 return command[0]() ---> 51 raise Error("could not locate runnable browser") 52 53 # Please note: the following definition hides a builtin function. Error: could not locate runnable browser
So we pick out the base name of the .app and hope that works:
from urllib.parse import unquote
def app_name_for_url(url):
"""Return the name of an app associated with a url"""
app = app_for_url(url)
path = app.split('://', 1)[1]
basename = path.rstrip('/')
name, dotapp = os.path.splitext(os.path.basename(path.rstrip('/')))
return unquote(name)
app_name_for_url('http://google.com')
'Safari'
browser = webbrowser.get(app_name_for_url('http://jupyter.org'))
browser.open("file://" + os.path.abspath("test.html"))
True