Last active
March 10, 2024 06:21
-
-
Save knu/80c703b06bf048251da7200eb26c89e6 to your computer and use it in GitHub Desktop.
JXA script to open a URL in a specified Chromium-based browser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/osascript -l JavaScript | |
// -*- javascript -*- | |
// | |
// open-chromium: Open a URL in a Chromium-based browser in a specified profile | |
// | |
// usage: open-chromium [(-a | --application) <app>] [(-p | --profile) <profile>] <url> | |
// | |
// <app>: "Chromium" (default), "Google Chrome", "Microsoft Edge", etc. | |
// <profile>: "Default", "Profile 1", etc. | |
// | |
// If the URL is already opened in a tab, it is activated. | |
function first(collection) { | |
return collection.length > 0 ? collection[0] : null; | |
} | |
function activateWindowById(app, id) { | |
const window = app.windows.byId(id); | |
if (!window) { | |
console.log("window is gone!"); | |
return false; | |
} | |
if (window.index() !== 1) window.index = 1; | |
return true; | |
} | |
function activateTabById(window, id) { | |
Array.from(window.tabs()).forEach((tab, i) => { | |
if (tab.id() === id) { | |
window.activeTabIndex = i + 1; | |
return true; | |
} | |
}); | |
} | |
function selectProfile(app, profile) { | |
const system = Application("System Events"); | |
const browserAppProc = system.applicationProcesses.byName(app.name()); | |
const menuBar = browserAppProc.menuBars[0]; | |
const menu = first( | |
menuBar.menus.whose( | |
{ | |
_or: [ | |
{ name: "Profiles" }, | |
// other languages | |
{ name: "プロファイル" }, | |
], | |
}, | |
{ considering: "case" }, | |
), | |
); | |
const menuName = menu.name(); | |
const menuItem = first( | |
menu.menuItems.whose( | |
{ | |
_or: [ | |
// Chrome, Chromium | |
{ name: { _endsWith: ` (${profile})` } }, | |
// Microsoft Edge | |
{ name: profile }, | |
], | |
}, | |
{ considering: "case" }, | |
), | |
); | |
if (!menuItem) return false; | |
const menuItemName = menuItem.name(); | |
menuItem.click(); | |
const isSelected = () => | |
menuBar.menus | |
.byName(menuName) | |
.menuItems.byName(menuItemName) | |
.attributes.byName("AXMenuItemMarkChar") | |
.value() === "✓"; | |
if (isSelected()) return true; | |
for (const id of Array.from(app.windows()) | |
.slice(1) | |
.map((window) => window.id())) { | |
activateWindowById(app, id); | |
if (isSelected()) return true; | |
} | |
return false; | |
} | |
function openUrlInWindow(app, window, url) { | |
const tab = first(window.tabs.whose({ url }, { considering: "case" })); | |
if (tab) { | |
activateTabById(window, tab.id()); | |
} else { | |
const tab = first( | |
window.tabs.whose( | |
{ | |
_or: [ | |
// Chrome, Chromium | |
{ url: "chrome://newtab/" }, | |
// Microsoft Edge | |
{ url: "edge://newtab/" }, | |
], | |
}, | |
{ considering: "case" }, | |
), | |
); | |
if (tab) { | |
tab.url = url; | |
} else { | |
window.tabs.push(app.Tab({ url })); | |
} | |
} | |
} | |
function openUrl(app, url, { profile }) { | |
if (profile) { | |
if (!selectProfile(app, profile)) console.log("profile not found"); | |
if (!url) return; | |
openUrlInWindow( | |
app, | |
Array.from(app.windows()).find( | |
(window) => window.mode() !== "incognito", | |
) || app.Window().make(), | |
url, | |
); | |
} else { | |
if (!url) return; | |
Array.from(app.windows()).find((window, i) => { | |
if (window.mode() === "incognito") return false; | |
const tab = first(window.tabs.whose({ url }, { considering: "case" })); | |
if (tab) { | |
activateWindowById(app, window.id()); | |
activateTabById(window, tab.id()); | |
return true; | |
} | |
return false; | |
}) || | |
openUrlInWindow( | |
app, | |
Array.from(app.windows()).find( | |
(window) => window.mode() !== "incognito", | |
) || app.Window().make(), | |
url, | |
); | |
} | |
} | |
function run(argv) { | |
let appName = "Chromium", | |
profile, | |
urls = []; | |
for (let i = 0; i < argv.length; i++) { | |
const arg = argv[i]; | |
let m; | |
if ((m = /^(?:-p(.+)?|--profile(?:=(.*))?)$/.exec(arg))) { | |
profile = m[1] ?? m[2] ?? argv[++i]; | |
} else if ((m = /^(?:-a(.+)?|--application(?:=(.*))?)$/.exec(arg))) { | |
appName = m[1] ?? m[2] ?? argv[++i]; | |
} else { | |
urls.push(arg); | |
} | |
} | |
const app = Application(appName); | |
app.activate(); | |
for (const url of urls) { | |
openUrl(app, url, { profile }); | |
} | |
app.activate(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment