Fix browser devtools persistence and Safari shortcut wiring
This commit is contained in:
parent
031b0fcb30
commit
743cfcdc6d
11 changed files with 538 additions and 45 deletions
|
|
@ -136,6 +136,8 @@ Everything is scriptable through the CLI and socket API — create workspaces/ta
|
|||
|
||||
### Browser
|
||||
|
||||
Browser developer-tool shortcuts follow Safari defaults and are customizable in `Settings → Keyboard Shortcuts`.
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| ⌘ ⇧ L | Open browser in split |
|
||||
|
|
@ -143,7 +145,8 @@ Everything is scriptable through the CLI and socket API — create workspaces/ta
|
|||
| ⌘ [ | Back |
|
||||
| ⌘ ] | Forward |
|
||||
| ⌘ R | Reload page |
|
||||
| ⌥ ⌘ I | Open Developer Tools |
|
||||
| ⌥ ⌘ I | Toggle Developer Tools (Safari default) |
|
||||
| ⌥ ⌘ C | Show JavaScript Console (Safari default) |
|
||||
|
||||
### Notifications
|
||||
|
||||
|
|
|
|||
|
|
@ -1878,6 +1878,21 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
|
|||
return true
|
||||
}
|
||||
|
||||
// Safari defaults:
|
||||
// - Option+Command+I => Show/Toggle Web Inspector
|
||||
// - Option+Command+C => Show JavaScript Console
|
||||
if matchShortcut(event: event, shortcut: KeyboardShortcutSettings.shortcut(for: .toggleBrowserDeveloperTools)) {
|
||||
let didHandle = tabManager?.toggleDeveloperToolsFocusedBrowser() ?? false
|
||||
if !didHandle { NSSound.beep() }
|
||||
return true
|
||||
}
|
||||
|
||||
if matchShortcut(event: event, shortcut: KeyboardShortcutSettings.shortcut(for: .showBrowserJavaScriptConsole)) {
|
||||
let didHandle = tabManager?.showJavaScriptConsoleFocusedBrowser() ?? false
|
||||
if !didHandle { NSSound.beep() }
|
||||
return true
|
||||
}
|
||||
|
||||
// Focus browser address bar: Cmd+L
|
||||
if flags == [.command] && chars == "l" {
|
||||
if let focusedPanel = tabManager?.focusedBrowserPanel {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ enum KeyboardShortcutSettings {
|
|||
|
||||
// Panels
|
||||
case openBrowser
|
||||
case toggleBrowserDeveloperTools
|
||||
case showBrowserJavaScriptConsole
|
||||
|
||||
var id: String { rawValue }
|
||||
|
||||
|
|
@ -52,6 +54,8 @@ enum KeyboardShortcutSettings {
|
|||
case .splitRight: return "Split Right"
|
||||
case .splitDown: return "Split Down"
|
||||
case .openBrowser: return "Open Browser"
|
||||
case .toggleBrowserDeveloperTools: return "Toggle Browser Developer Tools"
|
||||
case .showBrowserJavaScriptConsole: return "Show Browser JavaScript Console"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,6 +79,8 @@ enum KeyboardShortcutSettings {
|
|||
case .prevSurface: return "shortcut.prevSurface"
|
||||
case .newSurface: return "shortcut.newSurface"
|
||||
case .openBrowser: return "shortcut.openBrowser"
|
||||
case .toggleBrowserDeveloperTools: return "shortcut.toggleBrowserDeveloperTools"
|
||||
case .showBrowserJavaScriptConsole: return "shortcut.showBrowserJavaScriptConsole"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +122,12 @@ enum KeyboardShortcutSettings {
|
|||
return StoredShortcut(key: "t", command: true, shift: false, option: false, control: false)
|
||||
case .openBrowser:
|
||||
return StoredShortcut(key: "l", command: true, shift: true, option: false, control: false)
|
||||
case .toggleBrowserDeveloperTools:
|
||||
// Safari default: Show Web Inspector.
|
||||
return StoredShortcut(key: "i", command: true, shift: false, option: true, control: false)
|
||||
case .showBrowserJavaScriptConsole:
|
||||
// Safari default: Show JavaScript Console.
|
||||
return StoredShortcut(key: "c", command: true, shift: false, option: true, control: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -182,6 +194,8 @@ enum KeyboardShortcutSettings {
|
|||
static func newSurfaceShortcut() -> StoredShortcut { shortcut(for: .newSurface) }
|
||||
|
||||
static func openBrowserShortcut() -> StoredShortcut { shortcut(for: .openBrowser) }
|
||||
static func toggleBrowserDeveloperToolsShortcut() -> StoredShortcut { shortcut(for: .toggleBrowserDeveloperTools) }
|
||||
static func showBrowserJavaScriptConsoleShortcut() -> StoredShortcut { shortcut(for: .showBrowserJavaScriptConsole) }
|
||||
}
|
||||
|
||||
/// A keyboard shortcut that can be stored in UserDefaults
|
||||
|
|
|
|||
|
|
@ -825,6 +825,8 @@ final class BrowserPanel: Panel, ObservableObject {
|
|||
private let minPageZoom: CGFloat = 0.25
|
||||
private let maxPageZoom: CGFloat = 5.0
|
||||
private let pageZoomStep: CGFloat = 0.1
|
||||
// Persist user intent across WebKit detach/reattach churn (split/layout updates).
|
||||
private var preferredDeveloperToolsVisible: Bool = false
|
||||
|
||||
var displayTitle: String {
|
||||
if !pageTitle.isEmpty {
|
||||
|
|
@ -865,6 +867,11 @@ final class BrowserPanel: Panel, ObservableObject {
|
|||
let webView = CmuxWebView(frame: .zero, configuration: config)
|
||||
webView.allowsBackForwardNavigationGestures = true
|
||||
|
||||
// Required for Web Inspector support on recent WebKit SDKs.
|
||||
if #available(macOS 13.3, *) {
|
||||
webView.isInspectable = true
|
||||
}
|
||||
|
||||
// Match the empty-page background to the window so newly-created browsers
|
||||
// don't flash white before content loads.
|
||||
webView.underPageBackgroundColor = .windowBackgroundColor
|
||||
|
|
@ -1296,6 +1303,90 @@ extension BrowserPanel {
|
|||
webView.stopLoading()
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func toggleDeveloperTools() -> Bool {
|
||||
guard let inspector = webView.cmuxInspectorObject() else { return false }
|
||||
let visible = inspector.cmuxCallBool(selector: NSSelectorFromString("isVisible")) ?? false
|
||||
let targetVisible = !visible
|
||||
let selector = NSSelectorFromString(targetVisible ? "show" : "close")
|
||||
guard inspector.responds(to: selector) else { return false }
|
||||
inspector.cmuxCallVoid(selector: selector)
|
||||
preferredDeveloperToolsVisible = targetVisible
|
||||
return true
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showDeveloperTools() -> Bool {
|
||||
guard let inspector = webView.cmuxInspectorObject() else { return false }
|
||||
let visible = inspector.cmuxCallBool(selector: NSSelectorFromString("isVisible")) ?? false
|
||||
if !visible {
|
||||
let showSelector = NSSelectorFromString("show")
|
||||
guard inspector.responds(to: showSelector) else { return false }
|
||||
inspector.cmuxCallVoid(selector: showSelector)
|
||||
}
|
||||
preferredDeveloperToolsVisible = true
|
||||
return true
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showDeveloperToolsConsole() -> Bool {
|
||||
guard showDeveloperTools() else { return false }
|
||||
guard let inspector = webView.cmuxInspectorObject() else { return true }
|
||||
// WebKit private inspector API differs by OS; try known console selectors.
|
||||
let consoleSelectors = [
|
||||
"showConsole",
|
||||
"showConsoleTab",
|
||||
"showConsoleView",
|
||||
]
|
||||
for raw in consoleSelectors {
|
||||
let selector = NSSelectorFromString(raw)
|
||||
if inspector.responds(to: selector) {
|
||||
inspector.cmuxCallVoid(selector: selector)
|
||||
break
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/// Called before WKWebView detaches so manual inspector closes are respected.
|
||||
func syncDeveloperToolsPreferenceFromInspector() {
|
||||
guard let inspector = webView.cmuxInspectorObject() else { return }
|
||||
if let visible = inspector.cmuxCallBool(selector: NSSelectorFromString("isVisible")) {
|
||||
preferredDeveloperToolsVisible = visible
|
||||
}
|
||||
}
|
||||
|
||||
/// Called after WKWebView reattaches to keep inspector stable across split/layout churn.
|
||||
func restoreDeveloperToolsAfterAttachIfNeeded() {
|
||||
guard preferredDeveloperToolsVisible else { return }
|
||||
guard let inspector = webView.cmuxInspectorObject() else { return }
|
||||
let visible = inspector.cmuxCallBool(selector: NSSelectorFromString("isVisible")) ?? false
|
||||
guard !visible else { return }
|
||||
let selector = NSSelectorFromString("show")
|
||||
guard inspector.responds(to: selector) else { return }
|
||||
inspector.cmuxCallVoid(selector: selector)
|
||||
preferredDeveloperToolsVisible = true
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func isDeveloperToolsVisible() -> Bool {
|
||||
guard let inspector = webView.cmuxInspectorObject() else { return false }
|
||||
return inspector.cmuxCallBool(selector: NSSelectorFromString("isVisible")) ?? false
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func hideDeveloperTools() -> Bool {
|
||||
guard let inspector = webView.cmuxInspectorObject() else { return false }
|
||||
let visible = inspector.cmuxCallBool(selector: NSSelectorFromString("isVisible")) ?? false
|
||||
if visible {
|
||||
let selector = NSSelectorFromString("close")
|
||||
guard inspector.responds(to: selector) else { return false }
|
||||
inspector.cmuxCallVoid(selector: selector)
|
||||
}
|
||||
preferredDeveloperToolsVisible = false
|
||||
return true
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func zoomIn() -> Bool {
|
||||
applyPageZoom(webView.pageZoom + pageZoomStep)
|
||||
|
|
@ -1427,6 +1518,33 @@ private extension BrowserPanel {
|
|||
}
|
||||
}
|
||||
|
||||
private extension WKWebView {
|
||||
func cmuxInspectorObject() -> NSObject? {
|
||||
let selector = NSSelectorFromString("_inspector")
|
||||
guard responds(to: selector),
|
||||
let inspector = perform(selector)?.takeUnretainedValue() as? NSObject else {
|
||||
return nil
|
||||
}
|
||||
return inspector
|
||||
}
|
||||
}
|
||||
|
||||
private extension NSObject {
|
||||
func cmuxCallBool(selector: Selector) -> Bool? {
|
||||
guard responds(to: selector) else { return nil }
|
||||
typealias Fn = @convention(c) (AnyObject, Selector) -> Bool
|
||||
let fn = unsafeBitCast(method(for: selector), to: Fn.self)
|
||||
return fn(self, selector)
|
||||
}
|
||||
|
||||
func cmuxCallVoid(selector: Selector) {
|
||||
guard responds(to: selector) else { return }
|
||||
typealias Fn = @convention(c) (AnyObject, Selector) -> Void
|
||||
let fn = unsafeBitCast(method(for: selector), to: Fn.self)
|
||||
fn(self, selector)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Navigation Delegate
|
||||
|
||||
private class BrowserNavigationDelegate: NSObject, WKNavigationDelegate {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,113 @@ import SwiftUI
|
|||
import WebKit
|
||||
import AppKit
|
||||
|
||||
enum BrowserDevToolsIconOption: String, CaseIterable, Identifiable {
|
||||
case wrenchAndScrewdriver = "wrench.and.screwdriver"
|
||||
case wrenchAndScrewdriverFill = "wrench.and.screwdriver.fill"
|
||||
case curlyBracesSquare = "curlybraces.square"
|
||||
case curlyBraces = "curlybraces"
|
||||
case terminalFill = "terminal.fill"
|
||||
case terminal = "terminal"
|
||||
case hammer = "hammer"
|
||||
case hammerCircle = "hammer.circle"
|
||||
case ladybug = "ladybug"
|
||||
case ladybugFill = "ladybug.fill"
|
||||
case scope = "scope"
|
||||
case codeChevrons = "chevron.left.slash.chevron.right"
|
||||
case gearshape = "gearshape"
|
||||
case gearshapeFill = "gearshape.fill"
|
||||
case globe = "globe"
|
||||
case globeAmericas = "globe.americas.fill"
|
||||
|
||||
var id: String { rawValue }
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .wrenchAndScrewdriver: return "Wrench + Screwdriver"
|
||||
case .wrenchAndScrewdriverFill: return "Wrench + Screwdriver (Fill)"
|
||||
case .curlyBracesSquare: return "Curly Braces"
|
||||
case .curlyBraces: return "Curly Braces (Plain)"
|
||||
case .terminalFill: return "Terminal (Fill)"
|
||||
case .terminal: return "Terminal"
|
||||
case .hammer: return "Hammer"
|
||||
case .hammerCircle: return "Hammer Circle"
|
||||
case .ladybug: return "Bug"
|
||||
case .ladybugFill: return "Bug (Fill)"
|
||||
case .scope: return "Scope"
|
||||
case .codeChevrons: return "Code Chevrons"
|
||||
case .gearshape: return "Gear"
|
||||
case .gearshapeFill: return "Gear (Fill)"
|
||||
case .globe: return "Globe"
|
||||
case .globeAmericas: return "Globe Americas (Fill)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum BrowserDevToolsIconColorOption: String, CaseIterable, Identifiable {
|
||||
case bonsplitInactive
|
||||
case bonsplitActive
|
||||
case accent
|
||||
case tertiary
|
||||
|
||||
var id: String { rawValue }
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .bonsplitInactive: return "Bonsplit Inactive (Terminal/Globe)"
|
||||
case .bonsplitActive: return "Bonsplit Active (Terminal/Globe)"
|
||||
case .accent: return "Accent"
|
||||
case .tertiary: return "Tertiary"
|
||||
}
|
||||
}
|
||||
|
||||
var color: Color {
|
||||
switch self {
|
||||
case .bonsplitInactive:
|
||||
// Matches Bonsplit tab icon tint for inactive tabs.
|
||||
return Color(nsColor: .secondaryLabelColor)
|
||||
case .bonsplitActive:
|
||||
// Matches Bonsplit tab icon tint for active tabs.
|
||||
return Color(nsColor: .labelColor)
|
||||
case .accent:
|
||||
return .accentColor
|
||||
case .tertiary:
|
||||
return Color(nsColor: .tertiaryLabelColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum BrowserDevToolsButtonDebugSettings {
|
||||
static let iconNameKey = "browserDevToolsIconName"
|
||||
static let iconColorKey = "browserDevToolsIconColor"
|
||||
static let defaultIcon = BrowserDevToolsIconOption.wrenchAndScrewdriver
|
||||
static let defaultColor = BrowserDevToolsIconColorOption.bonsplitInactive
|
||||
|
||||
static func iconOption(defaults: UserDefaults = .standard) -> BrowserDevToolsIconOption {
|
||||
guard let raw = defaults.string(forKey: iconNameKey),
|
||||
let option = BrowserDevToolsIconOption(rawValue: raw) else {
|
||||
return defaultIcon
|
||||
}
|
||||
return option
|
||||
}
|
||||
|
||||
static func colorOption(defaults: UserDefaults = .standard) -> BrowserDevToolsIconColorOption {
|
||||
guard let raw = defaults.string(forKey: iconColorKey),
|
||||
let option = BrowserDevToolsIconColorOption(rawValue: raw) else {
|
||||
return defaultColor
|
||||
}
|
||||
return option
|
||||
}
|
||||
|
||||
static func copyPayload(defaults: UserDefaults = .standard) -> String {
|
||||
let icon = iconOption(defaults: defaults)
|
||||
let color = colorOption(defaults: defaults)
|
||||
return """
|
||||
browserDevToolsIconName=\(icon.rawValue)
|
||||
browserDevToolsIconColor=\(color.rawValue)
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
struct OmnibarInlineCompletion: Equatable {
|
||||
let typedText: String
|
||||
let displayText: String
|
||||
|
|
@ -25,6 +132,8 @@ struct BrowserPanelView: View {
|
|||
@State private var addressBarFocused: Bool = false
|
||||
@AppStorage(BrowserSearchSettings.searchEngineKey) private var searchEngineRaw = BrowserSearchSettings.defaultSearchEngine.rawValue
|
||||
@AppStorage(BrowserSearchSettings.searchSuggestionsEnabledKey) private var searchSuggestionsEnabledStorage = BrowserSearchSettings.defaultSearchSuggestionsEnabled
|
||||
@AppStorage(BrowserDevToolsButtonDebugSettings.iconNameKey) private var devToolsIconNameRaw = BrowserDevToolsButtonDebugSettings.defaultIcon.rawValue
|
||||
@AppStorage(BrowserDevToolsButtonDebugSettings.iconColorKey) private var devToolsIconColorRaw = BrowserDevToolsButtonDebugSettings.defaultColor.rawValue
|
||||
@State private var suggestionTask: Task<Void, Never>?
|
||||
@State private var isLoadingRemoteSuggestions: Bool = false
|
||||
@State private var latestRemoteSuggestionQuery: String = ""
|
||||
|
|
@ -38,6 +147,8 @@ struct BrowserPanelView: View {
|
|||
@State private var omnibarPillFrame: CGRect = .zero
|
||||
@State private var lastHandledAddressBarFocusRequestId: UUID?
|
||||
private let omnibarPillCornerRadius: CGFloat = 12
|
||||
private let addressBarButtonSize: CGFloat = 22
|
||||
private let devToolsButtonIconSize: CGFloat = 11
|
||||
|
||||
private var searchEngine: BrowserSearchEngine {
|
||||
BrowserSearchEngine(rawValue: searchEngineRaw) ?? BrowserSearchSettings.defaultSearchEngine
|
||||
|
|
@ -63,6 +174,14 @@ struct BrowserPanelView: View {
|
|||
return searchSuggestionsEnabled
|
||||
}
|
||||
|
||||
private var devToolsIconOption: BrowserDevToolsIconOption {
|
||||
BrowserDevToolsIconOption(rawValue: devToolsIconNameRaw) ?? BrowserDevToolsButtonDebugSettings.defaultIcon
|
||||
}
|
||||
|
||||
private var devToolsColorOption: BrowserDevToolsIconColorOption {
|
||||
BrowserDevToolsIconColorOption(rawValue: devToolsIconColorRaw) ?? BrowserDevToolsButtonDebugSettings.defaultColor
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
addressBar
|
||||
|
|
@ -202,6 +321,8 @@ struct BrowserPanelView: View {
|
|||
omnibarField
|
||||
.accessibilityIdentifier("BrowserOmnibarPill")
|
||||
.accessibilityLabel("Browser omnibar")
|
||||
|
||||
developerToolsButton
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 6)
|
||||
|
|
@ -211,8 +332,6 @@ struct BrowserPanelView: View {
|
|||
}
|
||||
|
||||
private var addressBarButtonBar: some View {
|
||||
let navButtonSize: CGFloat = 22
|
||||
|
||||
return HStack(spacing: 0) {
|
||||
Button(action: {
|
||||
#if DEBUG
|
||||
|
|
@ -222,10 +341,10 @@ struct BrowserPanelView: View {
|
|||
}) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.frame(width: navButtonSize, height: navButtonSize, alignment: .center)
|
||||
.frame(width: addressBarButtonSize, height: addressBarButtonSize, alignment: .center)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.frame(width: navButtonSize, height: navButtonSize, alignment: .center)
|
||||
.frame(width: addressBarButtonSize, height: addressBarButtonSize, alignment: .center)
|
||||
.disabled(!panel.canGoBack)
|
||||
.opacity(panel.canGoBack ? 1.0 : 0.4)
|
||||
.help("Go Back")
|
||||
|
|
@ -238,10 +357,10 @@ struct BrowserPanelView: View {
|
|||
}) {
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.frame(width: navButtonSize, height: navButtonSize, alignment: .center)
|
||||
.frame(width: addressBarButtonSize, height: addressBarButtonSize, alignment: .center)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.frame(width: navButtonSize, height: navButtonSize, alignment: .center)
|
||||
.frame(width: addressBarButtonSize, height: addressBarButtonSize, alignment: .center)
|
||||
.disabled(!panel.canGoForward)
|
||||
.opacity(panel.canGoForward ? 1.0 : 0.4)
|
||||
.help("Go Forward")
|
||||
|
|
@ -261,14 +380,29 @@ struct BrowserPanelView: View {
|
|||
}) {
|
||||
Image(systemName: panel.isLoading ? "xmark" : "arrow.clockwise")
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.frame(width: navButtonSize, height: navButtonSize, alignment: .center)
|
||||
.frame(width: addressBarButtonSize, height: addressBarButtonSize, alignment: .center)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.frame(width: navButtonSize, height: navButtonSize, alignment: .center)
|
||||
.frame(width: addressBarButtonSize, height: addressBarButtonSize, alignment: .center)
|
||||
.help(panel.isLoading ? "Stop" : "Reload")
|
||||
}
|
||||
}
|
||||
|
||||
private var developerToolsButton: some View {
|
||||
Button(action: {
|
||||
openDevTools()
|
||||
}) {
|
||||
Image(systemName: devToolsIconOption.rawValue)
|
||||
.font(.system(size: devToolsButtonIconSize, weight: .medium))
|
||||
.foregroundStyle(devToolsColorOption.color)
|
||||
.frame(width: addressBarButtonSize, height: addressBarButtonSize, alignment: .center)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.frame(width: addressBarButtonSize, height: addressBarButtonSize, alignment: .center)
|
||||
.help("Toggle Developer Tools")
|
||||
.accessibilityIdentifier("BrowserToggleDevToolsButton")
|
||||
}
|
||||
|
||||
private var omnibarField: some View {
|
||||
let showSecureBadge = panel.currentURL?.scheme == "https"
|
||||
|
||||
|
|
@ -376,12 +510,6 @@ struct BrowserPanelView: View {
|
|||
}
|
||||
})
|
||||
.zIndex(0)
|
||||
.contextMenu {
|
||||
Button("Open Developer Tools") {
|
||||
openDevTools()
|
||||
}
|
||||
.keyboardShortcut("i", modifiers: [.command, .option])
|
||||
}
|
||||
}
|
||||
|
||||
private func triggerFocusFlashAnimation() {
|
||||
|
|
@ -445,10 +573,11 @@ struct BrowserPanelView: View {
|
|||
}
|
||||
|
||||
private func openDevTools() {
|
||||
// WKWebView with developerExtrasEnabled allows right-click > Inspect Element
|
||||
// We can also trigger via JavaScript
|
||||
Task {
|
||||
try? await panel.evaluateJavaScript("window.webkit?.messageHandlers?.devTools?.postMessage('open')")
|
||||
#if DEBUG
|
||||
dlog("browser.toggleDevTools panel=\(panel.id.uuidString.prefix(5))")
|
||||
#endif
|
||||
if !panel.toggleDeveloperTools() {
|
||||
NSSound.beep()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2426,7 +2555,6 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
|
||||
final class Coordinator {
|
||||
weak var webView: WKWebView?
|
||||
var constraints: [NSLayoutConstraint] = []
|
||||
var attachRetryWorkItem: DispatchWorkItem?
|
||||
var attachRetryCount: Int = 0
|
||||
var attachGeneration: Int = 0
|
||||
|
|
@ -2453,7 +2581,7 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
return container
|
||||
}
|
||||
|
||||
private static func attachWebView(_ webView: WKWebView, to host: NSView, coordinator: Coordinator) {
|
||||
private static func attachWebView(_ webView: WKWebView, to host: NSView) {
|
||||
// WebKit can crash if a WKWebView (or an internal first-responder object) stays first responder
|
||||
// while being detached/reparented during bonsplit/SwiftUI structural updates.
|
||||
if let window = webView.window,
|
||||
|
|
@ -2466,15 +2594,11 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
host.subviews.forEach { $0.removeFromSuperview() }
|
||||
host.addSubview(webView)
|
||||
|
||||
webView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.deactivate(coordinator.constraints)
|
||||
coordinator.constraints = [
|
||||
webView.leadingAnchor.constraint(equalTo: host.leadingAnchor),
|
||||
webView.trailingAnchor.constraint(equalTo: host.trailingAnchor),
|
||||
webView.topAnchor.constraint(equalTo: host.topAnchor),
|
||||
webView.bottomAnchor.constraint(equalTo: host.bottomAnchor),
|
||||
]
|
||||
NSLayoutConstraint.activate(coordinator.constraints)
|
||||
// Work around WebKit bug 272474 where Inspect Element can render blank/flicker
|
||||
// when WKWebView is edge-pinned using Auto Layout constraints.
|
||||
webView.translatesAutoresizingMaskIntoConstraints = true
|
||||
webView.autoresizingMask = [.width, .height]
|
||||
webView.frame = host.bounds
|
||||
|
||||
// Make reparenting resilient: WebKit can occasionally stay visually blank until forced to lay out.
|
||||
webView.needsLayout = true
|
||||
|
|
@ -2483,7 +2607,13 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
webView.displayIfNeeded()
|
||||
}
|
||||
|
||||
private static func scheduleAttachRetry(_ webView: WKWebView, to host: NSView, coordinator: Coordinator, generation: Int) {
|
||||
private static func scheduleAttachRetry(
|
||||
_ webView: WKWebView,
|
||||
panel: BrowserPanel,
|
||||
to host: NSView,
|
||||
coordinator: Coordinator,
|
||||
generation: Int
|
||||
) {
|
||||
// Don't schedule multiple overlapping retries.
|
||||
guard coordinator.attachRetryWorkItem == nil else { return }
|
||||
|
||||
|
|
@ -2506,14 +2636,21 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
// container off-window longer than a few seconds under load.
|
||||
if coordinator.attachRetryCount < 400 {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
|
||||
scheduleAttachRetry(webView, to: host, coordinator: coordinator, generation: generation)
|
||||
scheduleAttachRetry(
|
||||
webView,
|
||||
panel: panel,
|
||||
to: host,
|
||||
coordinator: coordinator,
|
||||
generation: generation
|
||||
)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
coordinator.attachRetryCount = 0
|
||||
attachWebView(webView, to: host, coordinator: coordinator)
|
||||
attachWebView(webView, to: host)
|
||||
panel.restoreDeveloperToolsAfterAttachIfNeeded()
|
||||
}
|
||||
|
||||
coordinator.attachRetryWorkItem = work
|
||||
|
|
@ -2528,6 +2665,7 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
// in the window hierarchy while hidden and rapidly switching focus between tabs. To reduce
|
||||
// WebKit crashes, detach the WKWebView when this surface is not the selected tab in its pane.
|
||||
if !shouldAttachWebView {
|
||||
panel.syncDeveloperToolsPreferenceFromInspector()
|
||||
context.coordinator.attachRetryWorkItem?.cancel()
|
||||
context.coordinator.attachRetryWorkItem = nil
|
||||
context.coordinator.attachRetryCount = 0
|
||||
|
|
@ -2539,9 +2677,6 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
window.makeFirstResponder(nil)
|
||||
}
|
||||
|
||||
NSLayoutConstraint.deactivate(context.coordinator.constraints)
|
||||
context.coordinator.constraints.removeAll()
|
||||
|
||||
if webView.superview != nil {
|
||||
webView.removeFromSuperview()
|
||||
}
|
||||
|
|
@ -2560,12 +2695,14 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
// can create containers that are never inserted into the window.
|
||||
Self.scheduleAttachRetry(
|
||||
webView,
|
||||
panel: panel,
|
||||
to: nsView,
|
||||
coordinator: context.coordinator,
|
||||
generation: context.coordinator.attachGeneration
|
||||
)
|
||||
} else {
|
||||
Self.attachWebView(webView, to: nsView, coordinator: context.coordinator)
|
||||
Self.attachWebView(webView, to: nsView)
|
||||
panel.restoreDeveloperToolsAfterAttachIfNeeded()
|
||||
}
|
||||
} else {
|
||||
// Already attached; no need for any pending retry.
|
||||
|
|
@ -2573,6 +2710,7 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
context.coordinator.attachRetryWorkItem = nil
|
||||
context.coordinator.attachRetryCount = 0
|
||||
context.coordinator.attachGeneration += 1
|
||||
panel.restoreDeveloperToolsAfterAttachIfNeeded()
|
||||
}
|
||||
|
||||
// Focus handling. Avoid fighting the address bar when it is focused.
|
||||
|
|
@ -2601,9 +2739,6 @@ struct WebViewRepresentable: NSViewRepresentable {
|
|||
coordinator.attachRetryCount = 0
|
||||
coordinator.attachGeneration += 1
|
||||
|
||||
NSLayoutConstraint.deactivate(coordinator.constraints)
|
||||
coordinator.constraints.removeAll()
|
||||
|
||||
guard let webView = coordinator.webView else { return }
|
||||
|
||||
// If we're being torn down while the WKWebView (or one of its subviews) is first responder,
|
||||
|
|
|
|||
|
|
@ -823,6 +823,16 @@ class TabManager: ObservableObject {
|
|||
focusedBrowserPanel?.resetZoom() ?? false
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func toggleDeveloperToolsFocusedBrowser() -> Bool {
|
||||
focusedBrowserPanel?.toggleDeveloperTools() ?? false
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func showJavaScriptConsoleFocusedBrowser() -> Bool {
|
||||
focusedBrowserPanel?.showDeveloperToolsConsole() ?? false
|
||||
}
|
||||
|
||||
/// Backwards compatibility: returns the focused surface ID
|
||||
func focusedSurfaceId(for tabId: UUID) -> UUID? {
|
||||
focusedPanelId(for: tabId)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ struct cmuxApp: App {
|
|||
@AppStorage(SocketControlSettings.appStorageKey) private var socketControlMode = SocketControlSettings.defaultMode.rawValue
|
||||
@AppStorage(KeyboardShortcutSettings.Action.splitRight.defaultsKey) private var splitRightShortcutData = Data()
|
||||
@AppStorage(KeyboardShortcutSettings.Action.splitDown.defaultsKey) private var splitDownShortcutData = Data()
|
||||
@AppStorage(KeyboardShortcutSettings.Action.toggleBrowserDeveloperTools.defaultsKey)
|
||||
private var toggleBrowserDeveloperToolsShortcutData = Data()
|
||||
@AppStorage(KeyboardShortcutSettings.Action.showBrowserJavaScriptConsole.defaultsKey)
|
||||
private var showBrowserJavaScriptConsoleShortcutData = Data()
|
||||
@NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
|
||||
|
||||
init() {
|
||||
|
|
@ -422,6 +426,20 @@ struct cmuxApp: App {
|
|||
}
|
||||
.keyboardShortcut("r", modifiers: .command)
|
||||
|
||||
splitCommandButton(title: "Toggle Developer Tools", shortcut: toggleBrowserDeveloperToolsMenuShortcut) {
|
||||
let manager = (AppDelegate.shared?.tabManager ?? tabManager)
|
||||
if !manager.toggleDeveloperToolsFocusedBrowser() {
|
||||
NSSound.beep()
|
||||
}
|
||||
}
|
||||
|
||||
splitCommandButton(title: "Show JavaScript Console", shortcut: showBrowserJavaScriptConsoleMenuShortcut) {
|
||||
let manager = (AppDelegate.shared?.tabManager ?? tabManager)
|
||||
if !manager.showJavaScriptConsoleFocusedBrowser() {
|
||||
NSSound.beep()
|
||||
}
|
||||
}
|
||||
|
||||
Button("Zoom In") {
|
||||
_ = (AppDelegate.shared?.tabManager ?? tabManager).zoomInFocusedBrowser()
|
||||
}
|
||||
|
|
@ -536,6 +554,20 @@ struct cmuxApp: App {
|
|||
decodeShortcut(from: splitDownShortcutData, fallback: KeyboardShortcutSettings.Action.splitDown.defaultShortcut)
|
||||
}
|
||||
|
||||
private var toggleBrowserDeveloperToolsMenuShortcut: StoredShortcut {
|
||||
decodeShortcut(
|
||||
from: toggleBrowserDeveloperToolsShortcutData,
|
||||
fallback: KeyboardShortcutSettings.Action.toggleBrowserDeveloperTools.defaultShortcut
|
||||
)
|
||||
}
|
||||
|
||||
private var showBrowserJavaScriptConsoleMenuShortcut: StoredShortcut {
|
||||
decodeShortcut(
|
||||
from: showBrowserJavaScriptConsoleShortcutData,
|
||||
fallback: KeyboardShortcutSettings.Action.showBrowserJavaScriptConsole.defaultShortcut
|
||||
)
|
||||
}
|
||||
|
||||
private var notificationMenuSnapshot: NotificationMenuSnapshot {
|
||||
NotificationMenuSnapshotBuilder.make(notifications: notificationStore.notifications)
|
||||
}
|
||||
|
|
@ -1123,6 +1155,7 @@ private enum DebugWindowConfigSnapshot {
|
|||
"""
|
||||
|
||||
let menuBarPayload = MenuBarIconDebugSettings.copyPayload(defaults: defaults)
|
||||
let browserDevToolsPayload = BrowserDevToolsButtonDebugSettings.copyPayload(defaults: defaults)
|
||||
|
||||
return """
|
||||
# Sidebar Debug
|
||||
|
|
@ -1133,6 +1166,9 @@ private enum DebugWindowConfigSnapshot {
|
|||
|
||||
# Menu Bar Extra Debug
|
||||
\(menuBarPayload)
|
||||
|
||||
# Browser DevTools Button
|
||||
\(browserDevToolsPayload)
|
||||
"""
|
||||
}
|
||||
|
||||
|
|
@ -1199,6 +1235,16 @@ private struct DebugWindowControlsView: View {
|
|||
@AppStorage(ShortcutHintDebugSettings.paneHintYKey) private var paneShortcutHintYOffset = ShortcutHintDebugSettings.defaultPaneHintY
|
||||
@AppStorage(ShortcutHintDebugSettings.alwaysShowHintsKey) private var alwaysShowShortcutHints = ShortcutHintDebugSettings.defaultAlwaysShowHints
|
||||
@AppStorage("debugTitlebarLeadingExtra") private var titlebarLeadingExtra: Double = 0
|
||||
@AppStorage(BrowserDevToolsButtonDebugSettings.iconNameKey) private var browserDevToolsIconNameRaw = BrowserDevToolsButtonDebugSettings.defaultIcon.rawValue
|
||||
@AppStorage(BrowserDevToolsButtonDebugSettings.iconColorKey) private var browserDevToolsIconColorRaw = BrowserDevToolsButtonDebugSettings.defaultColor.rawValue
|
||||
|
||||
private var selectedDevToolsIconOption: BrowserDevToolsIconOption {
|
||||
BrowserDevToolsIconOption(rawValue: browserDevToolsIconNameRaw) ?? BrowserDevToolsButtonDebugSettings.defaultIcon
|
||||
}
|
||||
|
||||
private var selectedDevToolsColorOption: BrowserDevToolsIconColorOption {
|
||||
BrowserDevToolsIconColorOption(rawValue: browserDevToolsIconColorRaw) ?? BrowserDevToolsButtonDebugSettings.defaultColor
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
|
|
@ -1282,12 +1328,58 @@ private struct DebugWindowControlsView: View {
|
|||
.padding(.top, 2)
|
||||
}
|
||||
|
||||
GroupBox("Browser DevTools Button") {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
HStack(spacing: 8) {
|
||||
Text("Icon")
|
||||
Picker("Icon", selection: $browserDevToolsIconNameRaw) {
|
||||
ForEach(BrowserDevToolsIconOption.allCases) { option in
|
||||
Text(option.title).tag(option.rawValue)
|
||||
}
|
||||
}
|
||||
.labelsHidden()
|
||||
.pickerStyle(.menu)
|
||||
Spacer()
|
||||
}
|
||||
|
||||
HStack(spacing: 8) {
|
||||
Text("Color")
|
||||
Picker("Color", selection: $browserDevToolsIconColorRaw) {
|
||||
ForEach(BrowserDevToolsIconColorOption.allCases) { option in
|
||||
Text(option.title).tag(option.rawValue)
|
||||
}
|
||||
}
|
||||
.labelsHidden()
|
||||
.pickerStyle(.menu)
|
||||
Spacer()
|
||||
}
|
||||
|
||||
HStack(spacing: 8) {
|
||||
Text("Preview")
|
||||
Spacer()
|
||||
Image(systemName: selectedDevToolsIconOption.rawValue)
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
.foregroundStyle(selectedDevToolsColorOption.color)
|
||||
}
|
||||
|
||||
HStack(spacing: 12) {
|
||||
Button("Reset Button") {
|
||||
resetBrowserDevToolsButton()
|
||||
}
|
||||
Button("Copy Button Config") {
|
||||
copyBrowserDevToolsButtonConfig()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.top, 2)
|
||||
}
|
||||
|
||||
GroupBox("Copy") {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Button("Copy All Debug Config") {
|
||||
DebugWindowConfigSnapshot.copyCombinedToPasteboard()
|
||||
}
|
||||
Text("Copies sidebar, background, and menu bar debug settings as one payload.")
|
||||
Text("Copies sidebar, background, menu bar, and browser devtools settings as one payload.")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
|
@ -1348,6 +1440,18 @@ private struct DebugWindowControlsView: View {
|
|||
pasteboard.clearContents()
|
||||
pasteboard.setString(payload, forType: .string)
|
||||
}
|
||||
|
||||
private func resetBrowserDevToolsButton() {
|
||||
browserDevToolsIconNameRaw = BrowserDevToolsButtonDebugSettings.defaultIcon.rawValue
|
||||
browserDevToolsIconColorRaw = BrowserDevToolsButtonDebugSettings.defaultColor.rawValue
|
||||
}
|
||||
|
||||
private func copyBrowserDevToolsButtonConfig() {
|
||||
let payload = BrowserDevToolsButtonDebugSettings.copyPayload(defaults: .standard)
|
||||
let pasteboard = NSPasteboard.general
|
||||
pasteboard.clearContents()
|
||||
pasteboard.setString(payload, forType: .string)
|
||||
}
|
||||
}
|
||||
|
||||
private final class AboutWindowController: NSWindowController, NSWindowDelegate {
|
||||
|
|
|
|||
|
|
@ -88,6 +88,90 @@ final class CmuxWebViewKeyEquivalentTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
final class BrowserDevToolsButtonDebugSettingsTests: XCTestCase {
|
||||
private func makeIsolatedDefaults() -> UserDefaults {
|
||||
let suiteName = "BrowserDevToolsButtonDebugSettingsTests.\(UUID().uuidString)"
|
||||
guard let defaults = UserDefaults(suiteName: suiteName) else {
|
||||
fatalError("Failed to create defaults suite")
|
||||
}
|
||||
defaults.removePersistentDomain(forName: suiteName)
|
||||
addTeardownBlock {
|
||||
defaults.removePersistentDomain(forName: suiteName)
|
||||
}
|
||||
return defaults
|
||||
}
|
||||
|
||||
func testIconCatalogIncludesExpandedChoices() {
|
||||
XCTAssertGreaterThanOrEqual(BrowserDevToolsIconOption.allCases.count, 10)
|
||||
XCTAssertTrue(BrowserDevToolsIconOption.allCases.contains(.terminal))
|
||||
XCTAssertTrue(BrowserDevToolsIconOption.allCases.contains(.globe))
|
||||
XCTAssertTrue(BrowserDevToolsIconOption.allCases.contains(.curlyBracesSquare))
|
||||
}
|
||||
|
||||
func testIconOptionFallsBackToDefaultForUnknownRawValue() {
|
||||
let defaults = makeIsolatedDefaults()
|
||||
defaults.set("this.symbol.does.not.exist", forKey: BrowserDevToolsButtonDebugSettings.iconNameKey)
|
||||
|
||||
XCTAssertEqual(
|
||||
BrowserDevToolsButtonDebugSettings.iconOption(defaults: defaults),
|
||||
BrowserDevToolsButtonDebugSettings.defaultIcon
|
||||
)
|
||||
}
|
||||
|
||||
func testColorOptionFallsBackToDefaultForUnknownRawValue() {
|
||||
let defaults = makeIsolatedDefaults()
|
||||
defaults.set("notAValidColor", forKey: BrowserDevToolsButtonDebugSettings.iconColorKey)
|
||||
|
||||
XCTAssertEqual(
|
||||
BrowserDevToolsButtonDebugSettings.colorOption(defaults: defaults),
|
||||
BrowserDevToolsButtonDebugSettings.defaultColor
|
||||
)
|
||||
}
|
||||
|
||||
func testCopyPayloadUsesPersistedValues() {
|
||||
let defaults = makeIsolatedDefaults()
|
||||
defaults.set(BrowserDevToolsIconOption.scope.rawValue, forKey: BrowserDevToolsButtonDebugSettings.iconNameKey)
|
||||
defaults.set(BrowserDevToolsIconColorOption.bonsplitActive.rawValue, forKey: BrowserDevToolsButtonDebugSettings.iconColorKey)
|
||||
|
||||
let payload = BrowserDevToolsButtonDebugSettings.copyPayload(defaults: defaults)
|
||||
XCTAssertTrue(payload.contains("browserDevToolsIconName=scope"))
|
||||
XCTAssertTrue(payload.contains("browserDevToolsIconColor=bonsplitActive"))
|
||||
}
|
||||
}
|
||||
|
||||
final class BrowserDeveloperToolsShortcutDefaultsTests: XCTestCase {
|
||||
func testSafariDefaultShortcutForToggleDeveloperTools() {
|
||||
let shortcut = KeyboardShortcutSettings.Action.toggleBrowserDeveloperTools.defaultShortcut
|
||||
XCTAssertEqual(shortcut.key, "i")
|
||||
XCTAssertTrue(shortcut.command)
|
||||
XCTAssertTrue(shortcut.option)
|
||||
XCTAssertFalse(shortcut.shift)
|
||||
XCTAssertFalse(shortcut.control)
|
||||
}
|
||||
|
||||
func testSafariDefaultShortcutForShowJavaScriptConsole() {
|
||||
let shortcut = KeyboardShortcutSettings.Action.showBrowserJavaScriptConsole.defaultShortcut
|
||||
XCTAssertEqual(shortcut.key, "c")
|
||||
XCTAssertTrue(shortcut.command)
|
||||
XCTAssertTrue(shortcut.option)
|
||||
XCTAssertFalse(shortcut.shift)
|
||||
XCTAssertFalse(shortcut.control)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class BrowserDeveloperToolsConfigurationTests: XCTestCase {
|
||||
func testBrowserPanelEnablesInspectableWebViewAndDeveloperExtras() {
|
||||
let panel = BrowserPanel(workspaceId: UUID())
|
||||
let developerExtras = panel.webView.configuration.preferences.value(forKey: "developerExtrasEnabled") as? Bool
|
||||
XCTAssertEqual(developerExtras, true)
|
||||
|
||||
if #available(macOS 13.3, *) {
|
||||
XCTAssertTrue(panel.webView.isInspectable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class WorkspaceShortcutMapperTests: XCTestCase {
|
||||
func testCommandNineMapsToLastWorkspaceIndex() {
|
||||
XCTAssertEqual(WorkspaceShortcutMapper.workspaceIndex(forCommandDigit: 9, workspaceCount: 1), 0)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ Keep this workflow focused on existing debug windows and menu entries. Do not ad
|
|||
## Workflow
|
||||
|
||||
1. Verify debug menu wiring in `Sources/cmuxApp.swift` under `CommandMenu("Debug")`.
|
||||
- Menu path in app: `Debug` → `Debug Windows` → window entry.
|
||||
- The `Debug` menu only exists in DEBUG builds (`./scripts/reload.sh --tag ...`).
|
||||
- Release builds (`reloadp.sh`, `reloads.sh`) do not show this menu.
|
||||
2. Keep these actions available in `Menu("Debug Windows")`:
|
||||
- `Sidebar Debug…`
|
||||
- `Background Debug…`
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ usage() {
|
|||
cat <<'USAGE'
|
||||
Usage: debug_windows_snapshot.sh [--domain <defaults-domain>] [--copy]
|
||||
|
||||
Collect Sidebar Debug, Background Debug, and Menu Bar Extra debug values from macOS defaults
|
||||
and print a combined payload. Use --copy to also copy the payload to clipboard.
|
||||
Collect Sidebar Debug, Background Debug, Menu Bar Extra, and Browser DevTools debug values
|
||||
from macOS defaults and print a combined payload. Use --copy to also copy the payload.
|
||||
|
||||
Examples:
|
||||
debug_windows_snapshot.sh
|
||||
|
|
@ -118,13 +118,16 @@ menubarDebugSingleDigitYOffset="$(format_number "$(read_value menubarDebugSingle
|
|||
menubarDebugMultiDigitYOffset="$(format_number "$(read_value menubarDebugMultiDigitYOffset 0.60)" 2)"
|
||||
legacySingleDigitX="$(read_value menubarDebugTextRectXAdjust '')"
|
||||
if [[ -n "$legacySingleDigitX" ]]; then
|
||||
menubarDebugSingleDigitXAdjust="$(format_number "$legacySingleDigitX" 2)"
|
||||
menubarDebugSingleDigitXAdjust="$(format_number "$legacySingleDigitX" 2)"
|
||||
else
|
||||
menubarDebugSingleDigitXAdjust="$(format_number "$(read_value menubarDebugSingleDigitXAdjust -1.10)" 2)"
|
||||
fi
|
||||
menubarDebugMultiDigitXAdjust="$(format_number "$(read_value menubarDebugMultiDigitXAdjust 2.42)" 2)"
|
||||
menubarDebugTextRectWidthAdjust="$(format_number "$(read_value menubarDebugTextRectWidthAdjust 1.80)" 2)"
|
||||
|
||||
browserDevToolsIconName="$(read_value browserDevToolsIconName 'wrench.and.screwdriver')"
|
||||
browserDevToolsIconColor="$(read_value browserDevToolsIconColor bonsplitInactive)"
|
||||
|
||||
payload="$(cat <<PAYLOAD
|
||||
# Defaults domain
|
||||
$domain
|
||||
|
|
@ -166,6 +169,10 @@ menubarDebugMultiDigitYOffset=$menubarDebugMultiDigitYOffset
|
|||
menubarDebugSingleDigitXAdjust=$menubarDebugSingleDigitXAdjust
|
||||
menubarDebugMultiDigitXAdjust=$menubarDebugMultiDigitXAdjust
|
||||
menubarDebugTextRectWidthAdjust=$menubarDebugTextRectWidthAdjust
|
||||
|
||||
# Browser DevTools Button
|
||||
browserDevToolsIconName=$browserDevToolsIconName
|
||||
browserDevToolsIconColor=$browserDevToolsIconColor
|
||||
PAYLOAD
|
||||
)"
|
||||
|
||||
|
|
|
|||
2
vendor/bonsplit
vendored
2
vendor/bonsplit
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 748d9c0fe12edebd5448b946ce2c23d7549cd073
|
||||
Subproject commit 2bd60ba40dc3350bd3c774b5f2de9f9b9c1b39fb
|
||||
Loading…
Add table
Add a link
Reference in a new issue