|
|
@@ -142,7 +142,9 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
|
|
|
private var reloadButton: TitlebarIconButton!
|
|
|
private var pinButton: TitlebarIconButton!
|
|
|
private var homeButton: TitlebarIconButton!
|
|
|
+ private var addToStatusBarButton: TitlebarIconButton!
|
|
|
private var widgetButton: TitlebarIconButton!
|
|
|
+ private var addToDesktopButton: TitlebarIconButton!
|
|
|
private var handButton: TitlebarIconButton!
|
|
|
private var downloadButton: TitlebarIconButton!
|
|
|
private var moreButton: TitlebarIconButton!
|
|
|
@@ -156,10 +158,18 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
|
|
|
private var isPinnedOnTop = false
|
|
|
private var isGestureModeEnabled = true
|
|
|
|
|
|
+ private var statusBarShortcutObserver: NSObjectProtocol?
|
|
|
+
|
|
|
private func setToggledStyle(_ button: TitlebarIconButton, isOn: Bool) {
|
|
|
button.setToggleActive(isOn)
|
|
|
}
|
|
|
|
|
|
+ deinit {
|
|
|
+ if let statusBarShortcutObserver {
|
|
|
+ NotificationCenter.default.removeObserver(statusBarShortcutObserver)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
init(initialURL: URL, windowTitle: String?) {
|
|
|
self.initialURL = initialURL
|
|
|
|
|
|
@@ -240,6 +250,14 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
|
|
|
action: #selector(togglePinOnTop)
|
|
|
)
|
|
|
|
|
|
+ // Add to menu bar (matches launcher tile behavior; requires Premium + known app host).
|
|
|
+ addToStatusBarButton = makeIconButton(
|
|
|
+ symbolName: "arrow.up.square",
|
|
|
+ accessibility: "Add to Status Bar",
|
|
|
+ action: #selector(addCurrentPageToStatusBar),
|
|
|
+ fallbackSymbolName: "square.and.arrow.up"
|
|
|
+ )
|
|
|
+
|
|
|
// Home
|
|
|
homeButton = makeIconButton(
|
|
|
symbolName: "house",
|
|
|
@@ -258,6 +276,14 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
|
|
|
// Bundled PNG: 1x = layout-fluid-3, 2x = layout-fluid-4 @ 48px — slightly larger than 12pt symbols so the full glyph reads clearly.
|
|
|
widgetButton.image = makeTitlebarSizedTemplateImage(named: "widget_custom_icon", pointSize: 14, layoutMarginFraction: 0)
|
|
|
|
|
|
+ // Add desktop shortcut (.webloc); matches launcher tile behavior (Premium).
|
|
|
+ addToDesktopButton = makeIconButton(
|
|
|
+ symbolName: "arrow.up.right.square",
|
|
|
+ accessibility: "Add to Desktop",
|
|
|
+ action: #selector(addCurrentPageToDesktop),
|
|
|
+ fallbackSymbolName: "square.and.arrow.up"
|
|
|
+ )
|
|
|
+
|
|
|
// Center site identity (logo + app/site name).
|
|
|
siteIconView = NSImageView()
|
|
|
siteIconView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
@@ -335,11 +361,21 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
|
|
|
reloadButton.target = self
|
|
|
pinButton.target = self
|
|
|
homeButton.target = self
|
|
|
+ addToStatusBarButton.target = self
|
|
|
widgetButton.target = self
|
|
|
+ addToDesktopButton.target = self
|
|
|
handButton.target = self
|
|
|
downloadButton.target = self
|
|
|
moreButton.target = self
|
|
|
|
|
|
+ statusBarShortcutObserver = NotificationCenter.default.addObserver(
|
|
|
+ forName: .statusBarShortcutIDsChanged,
|
|
|
+ object: nil,
|
|
|
+ queue: .main
|
|
|
+ ) { [weak self] _ in
|
|
|
+ self?.updateAddToStatusBarButtonAppearance()
|
|
|
+ }
|
|
|
+
|
|
|
webView.uiDelegate = self
|
|
|
webView.navigationDelegate = self
|
|
|
webView.allowsBackForwardNavigationGestures = true
|
|
|
@@ -370,7 +406,9 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
|
|
|
|
|
|
let rightStack = NSStackView(views: [
|
|
|
pinButton,
|
|
|
+ addToStatusBarButton,
|
|
|
widgetButton,
|
|
|
+ addToDesktopButton,
|
|
|
handButton,
|
|
|
downloadButton,
|
|
|
moreButton,
|
|
|
@@ -481,6 +519,94 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
|
|
|
)
|
|
|
}
|
|
|
|
|
|
+ @objc private func addCurrentPageToStatusBar() {
|
|
|
+ guard PremiumStore.shared.isPremiumUnlocked else {
|
|
|
+ showAlert(
|
|
|
+ title: "Premium required",
|
|
|
+ message: "Update to Premium to add shortcuts to the menu bar."
|
|
|
+ )
|
|
|
+ return
|
|
|
+ }
|
|
|
+ guard let app = resolvedLauncherAppForCurrentPage() else {
|
|
|
+ showAlert(
|
|
|
+ title: "Can’t add to menu bar",
|
|
|
+ message: "This site isn’t in your launcher list. Open it from the home screen first, then use this button."
|
|
|
+ )
|
|
|
+ return
|
|
|
+ }
|
|
|
+ var ids = LauncherApp.statusBarShortcutIDSet()
|
|
|
+ guard ids.insert(app.id).inserted else {
|
|
|
+ showAlert(title: "Already added", message: "“\(app.name)” is already in the menu bar.")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ LauncherApp.persistStatusBarShortcutIDs(ids)
|
|
|
+ showAlert(
|
|
|
+ title: "Added to Status Bar",
|
|
|
+ message: "“\(app.name)” now has its own icon in the menu bar. Click it to open, or right‑click to remove."
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc private func addCurrentPageToDesktop() {
|
|
|
+ guard PremiumStore.shared.isPremiumUnlocked else {
|
|
|
+ showAlert(
|
|
|
+ title: "Premium required",
|
|
|
+ message: "Update to Premium to save desktop shortcuts."
|
|
|
+ )
|
|
|
+ return
|
|
|
+ }
|
|
|
+ let url = currentPageURL()
|
|
|
+ guard let scheme = url.scheme?.lowercased(), scheme == "http" || scheme == "https" else {
|
|
|
+ showAlert(title: "Unavailable", message: "Desktop shortcuts need a web address (http or https).")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ let name = displayNameForDesktopShortcut()
|
|
|
+ DesktopWeblocShortcutPresenter.beginSavePanel(forAppNamed: name, webURL: url)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func currentPageURL() -> URL {
|
|
|
+ webView.url ?? initialURL
|
|
|
+ }
|
|
|
+
|
|
|
+ private func normalizedHost(_ host: String) -> String {
|
|
|
+ let h = host.lowercased()
|
|
|
+ return h.hasPrefix("www.") ? String(h.dropFirst(4)) : h
|
|
|
+ }
|
|
|
+
|
|
|
+ private func resolvedLauncherAppForCurrentPage() -> LauncherApp? {
|
|
|
+ let url = currentPageURL()
|
|
|
+ guard let host = url.host else { return nil }
|
|
|
+ let nh = normalizedHost(host)
|
|
|
+ let apps = LauncherApp.allLaunchableAppsFromUserDefaults()
|
|
|
+ return apps.first { app in
|
|
|
+ guard let appURL = app.webURL, let appHost = appURL.host else { return false }
|
|
|
+ return normalizedHost(appHost) == nh
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func displayNameForDesktopShortcut() -> String {
|
|
|
+ if let app = resolvedLauncherAppForCurrentPage() {
|
|
|
+ return app.name
|
|
|
+ }
|
|
|
+ let t = siteNameLabel.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
+ if !t.isEmpty, !isLikelyURLString(t) {
|
|
|
+ return t
|
|
|
+ }
|
|
|
+ if let host = currentPageURL().host {
|
|
|
+ return host
|
|
|
+ }
|
|
|
+ return "Shortcut"
|
|
|
+ }
|
|
|
+
|
|
|
+ private func updateAddToStatusBarButtonAppearance() {
|
|
|
+ if resolvedLauncherAppForCurrentPage() == nil {
|
|
|
+ addToStatusBarButton.isEnabled = false
|
|
|
+ addToStatusBarButton.toolTip = "Add to Status Bar — open this site from the launcher first"
|
|
|
+ } else {
|
|
|
+ addToStatusBarButton.isEnabled = true
|
|
|
+ addToStatusBarButton.toolTip = "Add to Status Bar"
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
@objc private func togglePinOnTop() {
|
|
|
isPinnedOnTop.toggle()
|
|
|
window?.level = isPinnedOnTop ? .floating : .normal
|
|
|
@@ -615,6 +741,7 @@ final class InAppBrowserWindowController: NSWindowController, NSWindowDelegate {
|
|
|
|
|
|
// Sync center site name.
|
|
|
updateSiteNameFromWebView()
|
|
|
+ updateAddToStatusBarButtonAppearance()
|
|
|
}
|
|
|
|
|
|
private func showAlert(title: String, message: String) {
|