Add default browser & external-open support
Introduce default-browser integration and external open handling across platforms. Added platform/default_browser.h with a Windows implementation (registry registration + settings UI invocation) and mac/linux stubs. Exposed new commands from the renderer (check-default-browser / set-default-browser) and implemented request/response plumbing in NebulaController (SendDefaultBrowserResult) and BrowserClient. Added UI controls and JS helpers in settings and setup pages to check and prompt the user to make Nebula the default browser. Also added single-instance launch target handling: command-line URL normalization, passing/consolidating the launch target, forwarding it to an existing window on Windows via WM_COPYDATA, and exposing OnExternalOpenRequested on the window/controller. Implemented delayed navigation (CefTask) to safely load pending initial URLs after CEF initialization. Updated CMakeLists and platform startup signatures to include and accept the new files/parameters.
This commit is contained in:
@@ -12,6 +12,109 @@ const HOME_SEARCH_Y_KEY = 'nebula-home-search-y'; // number (vh)
|
||||
const HOME_BOOKMARKS_Y_KEY = 'nebula-home-bookmarks-y'; // number (vh)
|
||||
const HOME_GLANCE_CORNER_KEY = 'nebula-home-glance-corner'; // 'br'|'bl'|'tr'|'tl'
|
||||
const DISPLAY_SCALE_KEY = 'nebula-display-scale'; // number (50-300)
|
||||
const defaultBrowserRequests = new Map();
|
||||
let defaultBrowserRequestId = 0;
|
||||
|
||||
function hasNebulaNativeBridge() {
|
||||
return !!(window.nebulaNative && typeof window.nebulaNative.postMessage === 'function');
|
||||
}
|
||||
|
||||
window.addEventListener('nebula-default-browser-result', (event) => {
|
||||
const detail = event.detail || {};
|
||||
const pending = defaultBrowserRequests.get(detail.requestId);
|
||||
if (!pending) return;
|
||||
|
||||
defaultBrowserRequests.delete(detail.requestId);
|
||||
pending.resolve(detail);
|
||||
});
|
||||
|
||||
function sendDefaultBrowserRequest(command) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!hasNebulaNativeBridge()) {
|
||||
reject(new Error('Native browser integration is unavailable.'));
|
||||
return;
|
||||
}
|
||||
|
||||
const requestId = `settings-default-browser-${Date.now()}-${++defaultBrowserRequestId}`;
|
||||
const timeout = setTimeout(() => {
|
||||
defaultBrowserRequests.delete(requestId);
|
||||
reject(new Error('Timed out waiting for default browser status.'));
|
||||
}, 10000);
|
||||
|
||||
defaultBrowserRequests.set(requestId, {
|
||||
resolve: (value) => {
|
||||
clearTimeout(timeout);
|
||||
resolve(value);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
window.nebulaNative.postMessage(command, requestId);
|
||||
} catch (error) {
|
||||
defaultBrowserRequests.delete(requestId);
|
||||
clearTimeout(timeout);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function refreshDefaultBrowserStatus() {
|
||||
const btn = document.getElementById('set-default-browser-btn');
|
||||
const status = document.getElementById('default-browser-status');
|
||||
if (!btn || !status) return;
|
||||
|
||||
if (!hasNebulaNativeBridge()) {
|
||||
btn.disabled = true;
|
||||
status.textContent = 'Default browser setup is only available in the native app.';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await sendDefaultBrowserRequest('check-default-browser');
|
||||
btn.disabled = !!result.isDefault;
|
||||
btn.textContent = result.isDefault ? 'Already Default' : 'Make Default Browser';
|
||||
status.textContent = result.isDefault
|
||||
? 'Nebula is your default browser.'
|
||||
: 'Nebula is not your default browser.';
|
||||
} catch (error) {
|
||||
console.error('Default browser status error:', error);
|
||||
status.textContent = 'Unable to check default browser status.';
|
||||
}
|
||||
}
|
||||
|
||||
function attachDefaultBrowserHandler() {
|
||||
const btn = document.getElementById('set-default-browser-btn');
|
||||
const status = document.getElementById('default-browser-status');
|
||||
if (!btn || !status) return;
|
||||
|
||||
btn.addEventListener('click', async () => {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Opening Settings...';
|
||||
status.textContent = 'Opening Windows default apps settings...';
|
||||
|
||||
try {
|
||||
const result = await sendDefaultBrowserRequest('set-default-browser');
|
||||
if (result.isDefault) {
|
||||
btn.textContent = 'Already Default';
|
||||
status.textContent = 'Nebula is your default browser.';
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Check Again';
|
||||
status.textContent = result.success
|
||||
? 'Choose Nebula in Windows default apps settings, then check again.'
|
||||
: (result.error || 'Unable to open default browser settings.');
|
||||
} catch (error) {
|
||||
console.error('Default browser setup error:', error);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Try Again';
|
||||
status.textContent = 'Unable to open default browser settings.';
|
||||
}
|
||||
});
|
||||
|
||||
refreshDefaultBrowserStatus();
|
||||
}
|
||||
|
||||
function showStatus(message) {
|
||||
if (statusText && statusDiv) {
|
||||
@@ -74,6 +177,8 @@ function attachClearHandler(btn) {
|
||||
// Try attaching immediately, and again on DOMContentLoaded
|
||||
attachClearHandler(clearBtn);
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
attachDefaultBrowserHandler();
|
||||
|
||||
if (!clearBtn) {
|
||||
clearBtn = document.getElementById('clear-data-btn');
|
||||
attachClearHandler(clearBtn);
|
||||
|
||||
+76
-2
@@ -16,6 +16,78 @@ function hasNebulaNativeBridge() {
|
||||
return !!(window.nebulaNative && typeof window.nebulaNative.postMessage === 'function');
|
||||
}
|
||||
|
||||
const defaultBrowserRequests = new Map();
|
||||
let defaultBrowserRequestId = 0;
|
||||
|
||||
window.addEventListener('nebula-default-browser-result', (event) => {
|
||||
const detail = event.detail || {};
|
||||
const pending = defaultBrowserRequests.get(detail.requestId);
|
||||
if (!pending) return;
|
||||
|
||||
defaultBrowserRequests.delete(detail.requestId);
|
||||
pending.resolve(detail);
|
||||
});
|
||||
|
||||
function sendDefaultBrowserRequest(command) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!hasNebulaNativeBridge()) {
|
||||
reject(new Error('Native browser integration is unavailable.'));
|
||||
return;
|
||||
}
|
||||
|
||||
const requestId = `default-browser-${Date.now()}-${++defaultBrowserRequestId}`;
|
||||
const timeout = setTimeout(() => {
|
||||
defaultBrowserRequests.delete(requestId);
|
||||
reject(new Error('Timed out waiting for default browser status.'));
|
||||
}, 10000);
|
||||
|
||||
defaultBrowserRequests.set(requestId, {
|
||||
resolve: (value) => {
|
||||
clearTimeout(timeout);
|
||||
resolve(value);
|
||||
},
|
||||
reject: (error) => {
|
||||
clearTimeout(timeout);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
window.nebulaNative.postMessage(command, requestId);
|
||||
} catch (error) {
|
||||
defaultBrowserRequests.delete(requestId);
|
||||
clearTimeout(timeout);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createNebulaNativeApi() {
|
||||
return {
|
||||
async getAllThemes() {
|
||||
return { default: getPresetThemes() };
|
||||
},
|
||||
async isDefaultBrowser() {
|
||||
const result = await sendDefaultBrowserRequest('check-default-browser');
|
||||
return !!result.isDefault;
|
||||
},
|
||||
async setAsDefaultBrowser() {
|
||||
return sendDefaultBrowserRequest('set-default-browser');
|
||||
},
|
||||
async applyTheme(themeId) {
|
||||
const theme = getThemeById(themeId);
|
||||
if (theme) {
|
||||
localStorage.setItem('currentTheme', JSON.stringify(normalizeTheme(theme)));
|
||||
}
|
||||
localStorage.setItem('activeThemeName', themeId);
|
||||
},
|
||||
async completeFirstRun(data) {
|
||||
localStorage.setItem('nebula-first-run-complete', JSON.stringify(data));
|
||||
window.nebulaNative.postMessage('complete-first-run', JSON.stringify(data));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getPresetThemes() {
|
||||
if (typeof BrowserCustomizer === 'function') {
|
||||
const customizer = new BrowserCustomizer({ skipInit: true });
|
||||
@@ -61,7 +133,7 @@ function normalizeTheme(theme) {
|
||||
};
|
||||
}
|
||||
|
||||
const nativeApi = window.api || {
|
||||
const fallbackApi = {
|
||||
async getAllThemes() {
|
||||
return { default: getPresetThemes() };
|
||||
},
|
||||
@@ -86,6 +158,8 @@ const nativeApi = window.api || {
|
||||
}
|
||||
};
|
||||
|
||||
const nativeApi = window.api || (hasNebulaNativeBridge() ? createNebulaNativeApi() : fallbackApi);
|
||||
|
||||
// Initialize setup when DOM is ready
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
console.log('[Setup] Initializing first-time setup...');
|
||||
@@ -402,7 +476,7 @@ async function setDefaultBrowser() {
|
||||
const result = await nativeApi.setAsDefaultBrowser();
|
||||
|
||||
if (result.success) {
|
||||
const isDefault = await window.api.isDefaultBrowser();
|
||||
const isDefault = !!result.isDefault || await nativeApi.isDefaultBrowser();
|
||||
if (isDefault) {
|
||||
setupState.defaultBrowserSet = true;
|
||||
|
||||
|
||||
@@ -45,6 +45,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-group">
|
||||
<h3>Default Browser</h3>
|
||||
<p class="note">Use Nebula Browser for web links opened from other apps.</p>
|
||||
<div class="setting-row">
|
||||
<button id="set-default-browser-btn" class="primary-btn">Make Default Browser</button>
|
||||
<span id="default-browser-status" class="note">Checking status...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-group">
|
||||
<h3>Weather Display</h3>
|
||||
<p class="note">Choose how temperature is displayed on the Home page weather card.</p>
|
||||
|
||||
Reference in New Issue
Block a user