Effortlessly manage your default web browser on macOS directly from the command line or your Python scripts.
macdefaultbrowsy is a powerful yet simple command-line tool and Python library designed for macOS users and developers. It allows you to quickly view all installed web browsers, check the current default, and set a new default browser with a single command. Crucially, it intelligently automates the macOS confirmation dialog, so you don't have to lift a finger.
- macOS Power Users: If you frequently switch browsers for different tasks or prefer keyboard-driven workflows,
macdefaultbrowsygives you precise control. - Developers & Scripters: Integrate default browser management into your Python applications, automation scripts, or development workflows.
- System Administrators: A handy tool for configuring user setups or managing multiple machines.
- Speed and Convenience: List browsers or change your default in seconds without navigating through system settings.
- Seamless Automation: Sets the default browser and automatically handles the macOS confirmation pop-up. No manual clicking is needed!
- Scriptable: Easily incorporate into shell scripts or Python programs.
- Lightweight: Minimal dependencies and a straightforward command-line interface.
- Universal: Works on both Intel and Apple Silicon Macs.
- List Browsers: Display all installed web browsers, clearly marking the current default (e.g., with
*). - Set Default Browser: Change the system's default web browser by specifying its common name (e.g.,
chrome,safari,firefox). - Automatic Dialog Confirmation: When you set a new default browser,
macdefaultbrowsyautomatically interacts with the macOS system dialog to confirm the change. - Python API: A clean and simple API for programmatic control within Python projects.
You can install macdefaultbrowsy using uv (or pip):
uv pip install --system macdefaultbrowsyAlternatively, to install the latest development version directly from GitHub:
uv pip install --system git+https://github.com/twardoch/macdefaultbrowsy(Note: Using --system with uv pip install is typical for command-line tools intended for system-wide use. Adjust according to your Python environment management practices.)
(Homebrew installation might be available in the future.)
The CLI is simple and intuitive.
To see a list of all web browsers macdefaultbrowsy can find on your system, with the current default marked by an asterisk (*):
macdefaultbrowsyExample Output:
* chrome
firefox
safari
edge
arc
To change your default web browser, simply provide its name as an argument. macdefaultbrowsy will attempt to set it and handle the system confirmation.
macdefaultbrowsy firefoxIf successful (and a change was needed), you'll see a confirmation:
INFO: Dialog confirmed with button: Use “Firefox”
INFO: Set firefox as default browser.
If the browser is already the default:
macdefaultbrowsy firefox
INFO: firefox is already the default browser.If the browser name is not recognized:
macdefaultbrowsy nonexistentbrowser
ERROR: Browser 'nonexistentbrowser' not found.You can also use macdefaultbrowsy as a Python library in your own projects.
from macdefaultbrowsy import macdefaultbrowsy
from loguru import logger # Optional: for similar logging output as CLI
# Configure logger if you want to see macdefaultbrowsy's info messages
# import sys
# logger.remove()
# logger.add(sys.stderr, format="{message}")
# Get the current default browser's short name (e.g., "chrome")
current_default = macdefaultbrowsy.get_default_browser()
if current_default:
logger.info(f"Current default browser: {current_default}")
else:
logger.warning("Could not determine the current default browser.")
# List all available browsers (returns a dictionary: {'name': 'bundle_id'})
available_browsers = macdefaultbrowsy.get_browsers()
logger.info("Available browsers:")
for name in sorted(available_browsers.keys()):
bundle_id = available_browsers[name]
logger.info(f" - {name} (Bundle ID: {bundle_id})")
# Set a new default browser (e.g., "safari")
browser_to_set = "safari"
logger.info(f"Attempting to set '{browser_to_set}' as the default browser...")
success = macdefaultbrowsy.set_default_browser(browser_to_set)
if success:
new_default = macdefaultbrowsy.get_default_browser()
logger.info(f"Successfully set '{new_default}' as the default browser.")
else:
logger.error(f"Failed to set '{browser_to_set}' as the default browser.")
# Example of trying to set a browser that's already default
# if current_default:
# success_already_default = macdefaultbrowsy.set_default_browser(current_default)
# logger.info(f"Attempt to set already default browser ('{current_default}') was successful: {success_already_default}")
# Example of trying to set a non-existent browser
# success_non_existent = macdefaultbrowsy.set_default_browser("fakebrowser")
# logger.info(f"Attempt to set non-existent browser was successful: {success_non_existent}")This section provides a more detailed look into how macdefaultbrowsy works, its codebase, and guidelines for contributors.
macdefaultbrowsy interacts with macOS at two main levels:
-
Launch Services API:
- The core functionality of identifying available browsers and managing the default browser setting relies on the macOS Launch Services framework. This is accessed via the
pyobjc-framework-CoreServicesPython bindings. - Discovering Browsers: The tool queries Launch Services for all registered application handlers for
httpandhttpsURL schemes usingLSCopyAllHandlersForURLScheme. An application is considered a web browser if it can handle both schemes. The common name (e.g., "chrome") is derived from its bundle ID (e.g., "com.google.Chrome"). - Getting Default Browser: The current default browser is determined by calling
LSCopyDefaultHandlerForURLSchemefor thehttpscheme. - Setting Default Browser: To set a new default browser,
macdefaultbrowsycallsLSSetDefaultHandlerForURLSchemefor bothhttpandhttpsschemes, providing the bundle ID of the chosen browser.
- The core functionality of identifying available browsers and managing the default browser setting relies on the macOS Launch Services framework. This is accessed via the
-
Automatic Dialog Confirmation:
- When the default browser is changed programmatically via Launch Services, macOS typically presents a confirmation dialog: "Do you want to use as your default browser?". This dialog is managed by the
CoreServicesUIAgentprocess. - To provide a seamless command-line experience,
macdefaultbrowsyautomates clicking the confirmation button (usually labeled "Use " or similar). - This automation is handled by
src/macdefaultbrowsy/dialog_automation.py. Whenset_default_browseris called (and a change is necessary):- A separate thread is spawned before the Launch Services call to change the default.
- This thread runs an AppleScript snippet using
osascript. The script polls for theCoreServicesUIAgentwindow for up to 10 seconds (checking every 0.5 seconds). - If the dialog appears, the script identifies the confirmation button by looking for titles containing the browser's display name (e.g., "Chrome", "Safari") or the word "Use". It then programmatically clicks this button.
- The main thread waits for the dialog automation thread to complete (with a timeout) after attempting to set the browser.
- If the browser is already the default, this dialog automation step is skipped to prevent hanging, as no dialog will appear.
- When the default browser is changed programmatically via Launch Services, macOS typically presents a confirmation dialog: "Do you want to use as your default browser?". This dialog is managed by the
The project follows a standard Python package structure:
src/macdefaultbrowsy/: Main package directory.__init__.py: Package initializer, defines__version__(dynamically set byhatch-vcs).macdefaultbrowsy.py: Contains the core public API and logic:get_browsers(): Discovers available browsers.get_default_browser(): Retrieves the current default browser.set_default_browser(): Sets a new default browser, orchestrating Launch Services calls and dialog automation.print_browsers_list(): CLI helper to print the browser list._browser_name_from_bundle_id(): Utility to simplify bundle IDs to common names.
__main__.py: Provides the command-line interface using thefirelibrary. It defines thecli()function thatfireexposes.launch_services.py: A thin wrapper around the necessaryLaunchServicesAPI functions frompyobjc-framework-CoreServices. This isolates thepyobjcinteractions.dialog_automation.py: Manages the AppleScript execution for automatic dialog confirmation.start_dialog_confirmation(): Starts the monitoring thread._monitor_and_click(): The function running in the thread that polls and clicks the dialog button._dialog_browser_name(): Helper to map internal browser names to display names used in dialogs._run_osascript(): Executes an AppleScript string.
py.typed: Marker file for PEP 561 compatibility, indicating the package supports type checking.
tests/: Contains unit tests for the project, primarily usingunittest.mock.pyproject.toml: Defines project metadata, dependencies, build system (hatchling), and tool configurations (ruff, pytest, mypy, coverage).README.md: This file.LICENSE: MIT License file.AGENT.md,CLAUDE.md: Contain specific instructions and conventions for AI-assisted development.
pyobjc-framework-CoreServices: Essential for interacting with macOS Launch Services to get and set the default browser.fire: Used to quickly create the command-line interface from Python functions/objects in__main__.py.loguru: Provides user-friendly logging for CLI output and internal messages.
Development and testing dependencies are listed in pyproject.toml under [project.optional-dependencies].
We welcome contributions! Please adhere to the following guidelines, many of which are inspired by AGENT.md and CLAUDE.md in this repository.
- Clone the repository.
- It's recommended to use a virtual environment.
- Install the package in editable mode with development and test dependencies:
uv pip install -e ".[dev,test,all]" - Install pre-commit hooks to ensure code quality before committing:
pre-commit install
- General Principles (from
AGENT.md/CLAUDE.md):- Iterate gradually; prefer minimal viable increments.
- Write clear, explanatory docstrings (PEP 257, imperative mood) and comments. Explain the "what" and the "why".
- Maintain the
this_file: path/to/file.pycomment near the top of each Python source file. - Favor flat over nested structures.
- Handle failures gracefully.
- Python Specifics:
- Follow PEP 8 for code style.
ruff formathandles most of this. - Use
uv pipfor package management andpython -mwhen running modules or scripts where appropriate. - Employ type hints (simplest form:
list,dict,|for unions). - Use f-strings for string formatting.
- The CLI (
__main__.py) usesfireandloguru.
- Follow PEP 8 for code style.
- Code Formatting and Linting:
- This project uses
rufffor linting and formatting. Configuration is inpyproject.toml. - The
pre-commithooks will automatically run formatters and linters. - To manually format and lint, you can use the
hatchenvironment scripts (e.g.,hatch run lint:fix) or the command specified inAGENT.MD/CLAUDE.MD:(fd -e py -x autoflake {} \; \ fd -e py -x pyupgrade --py311-plus {} \; \ fd -e py -x ruff check --output-format=github --fix --unsafe-fixes {} \; \ fd -e py -x ruff format --respect-gitignore --target-version py311 {}fd-find(often installed asfd) might be required for thefdcommand. Adapt if necessary.)
- This project uses
- Write tests for new features and bug fixes in the
tests/directory. - This project uses
pytest. - Run tests using:
or via hatch:
python -m pytest
hatch run test - Ensure good test coverage. You can check coverage with:
Coverage reports are configured in
hatch run test:test-cov
pyproject.toml.
- The package is built using
hatchling. - To build wheels and source distributions:
or
python -m build
Artifacts will be placed in thehatch build
dist/directory.
- The version is managed dynamically by
hatch-vcsusing Git tags. - When preparing a release, tag the commit with
vX.Y.Z(e.g.,v0.1.1).hatch-vcswill updatesrc/macdefaultbrowsy/__version__.pyduring the build process.
- Write clear and concise commit messages.
- If contributing, please fork the repository, create a feature branch, and submit a pull request.
- For a similar tool written in Swift, see macdefaultbrowser by Adam Twardoch.
This project is licensed under the MIT License. See the LICENSE file for full details.