Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OS Level Shortcut Window for Quick Access to Khoj Desktop #815

Merged
merged 23 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8a1cea0
rough sketch of desktop shortcuts. many bugs to fix still
MythicalCow Jun 10, 2024
438c7df
working MVP of desktop shortcut khoj
MythicalCow Jun 10, 2024
b826b93
UI fixes
MythicalCow Jun 10, 2024
53c461b
UI improvements for editable shortcut message
MythicalCow Jun 12, 2024
1bb7d31
major rendering fix to prevent clipboard text from getting lost
MythicalCow Jun 12, 2024
81ec305
UI improvements and bug fixes
MythicalCow Jun 12, 2024
2c2a13d
UI upgrades: custom top bar, edit sent message and color matching
MythicalCow Jun 12, 2024
33d0c07
Merge branch 'khoj-ai:master' into features/desktop-shortcut
MythicalCow Jun 12, 2024
e496d5a
removed debug javascript file
MythicalCow Jun 12, 2024
64fbac1
Merge branch 'features/desktop-shortcut' of https://github.com/Mythic…
MythicalCow Jun 12, 2024
b29b90f
font reverted to Noto Sans
MythicalCow Jun 17, 2024
d09fcc9
resolving diffs
MythicalCow Jun 17, 2024
b59a8ad
resolving diffs
MythicalCow Jun 17, 2024
d105afc
whitespace diff
MythicalCow Jun 17, 2024
09bd4f1
cleaning up the code and removing diffs
MythicalCow Jun 17, 2024
b348323
UX fixes
MythicalCow Jun 17, 2024
66028d3
cleaning up unused methods from html
MythicalCow Jun 17, 2024
7d3e862
front end for button to send user back to main window to continue con…
MythicalCow Jun 17, 2024
b37aeec
UX fix for window and continue conversation support added
MythicalCow Jun 18, 2024
82bcc73
migrated common js functions into chatutils.js
MythicalCow Jun 18, 2024
81ce0f6
Fix window closing issue in macos by
sabaimran Jun 19, 2024
7182043
removed extra comment and renamed continue convo button
MythicalCow Jun 20, 2024
68034b4
Merge branch 'features/desktop-shortcut' of https://github.com/Mythic…
MythicalCow Jun 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added src/interface/desktop/assets/Inter-Black.ttf
MythicalCow marked this conversation as resolved.
Show resolved Hide resolved
Binary file not shown.
Binary file added src/interface/desktop/assets/Inter-Bold.ttf
Binary file not shown.
Binary file added src/interface/desktop/assets/Inter-ExtraBold.ttf
Binary file not shown.
Binary file not shown.
Binary file added src/interface/desktop/assets/Inter-Light.ttf
Binary file not shown.
Binary file added src/interface/desktop/assets/Inter-Medium.ttf
Binary file not shown.
Binary file added src/interface/desktop/assets/Inter-Regular.ttf
Binary file not shown.
Binary file added src/interface/desktop/assets/Inter-SemiBold.ttf
Binary file not shown.
Binary file added src/interface/desktop/assets/Inter-Thin.ttf
Binary file not shown.
1,480 changes: 1,480 additions & 0 deletions src/interface/desktop/index.html
MythicalCow marked this conversation as resolved.
Show resolved Hide resolved

Large diffs are not rendered by default.

343 changes: 196 additions & 147 deletions src/interface/desktop/main.js
MythicalCow marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -428,162 +428,211 @@ function addCSPHeaderToSession () {
})
}

let firstRun = true;
let win = null;
let titleBarStyle = process.platform === 'win32' ? 'default' : 'hidden';
const createWindow = (tab = 'chat.html') => {
win = new BrowserWindow({
width: 800,
height: 800,
show: false,
titleBarStyle: titleBarStyle,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
}
})

const job = new cron('0 */10 * * * *', function() {
try {
pushDataToKhoj();
const date = new Date();
console.log('Pushing data to Khoj at: ', date);
win.webContents.send('update-state', state);
} catch (err) {
console.error(err);
}
});

win.setResizable(true);
win.setOpacity(0.95);
win.setBackgroundColor('#f5f4f3');
win.setHasShadow(true);

// Open external links in link handler registered on OS (e.g. browser)
win.webContents.setWindowOpenHandler(async ({ url }) => {
let shouldOpen = { response: 0 };

if (!url.startsWith(store.get('hostURL'))) {
// Confirm before opening external links
const confirmNotice = `Do you want to open this link? It will be handled by an external application.\n\n${url}`;
shouldOpen = await dialog.showMessageBox({
type: 'question',
buttons: ['Yes', 'No'],
defaultId: 1,
title: 'Confirm',
message: confirmNotice,
});
}

// If user confirms, let OS link handler open the link in appropriate app
if (shouldOpen.response === 0) shell.openExternal(url);
const {globalShortcut, clipboard} = require('electron');
let firstRun = true;
let win = null;
let titleBarStyle = process.platform === 'win32' ? 'default' : 'hidden';

function addCSPHeaderToSession() {
const hostURL = store.get('hostURL') || KHOJ_URL;
const defaultDomains = `'self' ${hostURL} https://app.khoj.dev https://assets.khoj.dev`;
const default_src = `default-src ${defaultDomains};`;
const script_src = `script-src ${defaultDomains} 'unsafe-inline';`;
const connect_src = `connect-src ${hostURL} https://ipapi.co/json;`;
const style_src = `style-src ${defaultDomains} 'unsafe-inline' https://fonts.googleapis.com;`;
const img_src = `img-src ${defaultDomains} data: https://*.khoj.dev https://*.googleusercontent.com;`;
const font_src = `font-src https://fonts.gstatic.com;`;
const child_src = `child-src 'none';`;
const objectSrc = `object-src 'none';`;
const csp = `${default_src} ${script_src} ${connect_src} ${style_src} ${img_src} ${font_src} ${child_src} ${objectSrc}`;

session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [csp]
}
});
});
}

// Do not open external links within the app
return { action: 'deny' };
const createWindow = (tab = 'chat.html') => {
win = new BrowserWindow({
width: 800,
height: 800,
show: false,
titleBarStyle: titleBarStyle,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
}
});

const job = new cron('0 */10 * * * *', function() {
try {
pushDataToKhoj();
const date = new Date();
console.log('Pushing data to Khoj at: ', date);
win.webContents.send('update-state', state);
} catch (err) {
console.error(err);
}
});

win.setResizable(true);
win.setOpacity(0.95);
win.setBackgroundColor('#f5f4f3');
win.setHasShadow(true);

win.webContents.setWindowOpenHandler(async ({ url }) => {
let shouldOpen = { response: 0 };

if (!url.startsWith(store.get('hostURL'))) {
const confirmNotice = `Do you want to open this link? It will be handled by an external application.\n\n${url}`;
shouldOpen = await dialog.showMessageBox({
type: 'question',
buttons: ['Yes', 'No'],
defaultId: 1,
title: 'Confirm',
message: confirmNotice,
});
}

if (shouldOpen.response === 0) shell.openExternal(url);
return { action: 'deny' };
});

job.start();
win.loadFile(tab);

if (firstRun === true) {
firstRun = false;

let splash = new BrowserWindow({ width: 400, height: 400, transparent: true, frame: false, alwaysOnTop: true });
splash.setOpacity(1.0);
splash.setBackgroundColor('#d16b4e');
splash.loadFile('splash.html');

win.once('ready-to-show', () => {
setTimeout(() => { splash.close(); win.show(); }, 4500);
});
} else {
win.once('ready-to-show', () => { win.show(); });
}
};

const createShortcutWindow = (tab = 'index.html') => {
var shortcutWin = new BrowserWindow({
width: 400,
height: 600,
show: false,
titleBarStyle: titleBarStyle,
autoHideMenuBar: true,
frame: false,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
}
});
shortcutWin.setMenuBarVisibility(false);
shortcutWin.setResizable(false);
shortcutWin.setOpacity(0.95);
shortcutWin.setBackgroundColor('#f5f4f3');
shortcutWin.setHasShadow(true);
shortcutWin.setVibrancy('popover');

shortcutWin.loadFile(tab);
shortcutWin.once('ready-to-show', () => {
shortcutWin.show();
});

shortcutWin.on('closed', () => {
shortcutWin = null;
});

job.start();

win.loadFile(tab)

if (firstRun === true) {
firstRun = false;

// Create splash screen
let splash = new BrowserWindow({width: 400, height: 400, transparent: true, frame: false, alwaysOnTop: true});
splash.setOpacity(1.0);
splash.setBackgroundColor('#d16b4e');
splash.loadFile('splash.html');

// Show splash screen on app load
win.once('ready-to-show', () => {
setTimeout(function(){ splash.close(); win.show(); }, 4500);
return shortcutWin;
};
app.whenReady().then(() => {
addCSPHeaderToSession();
MythicalCow marked this conversation as resolved.
Show resolved Hide resolved

ipcMain.on('set-title', handleSetTitle);
ipcMain.handle('handleFileOpen', (event, type) => handleFileOpen(type));
ipcMain.on('update-state', (event, arg) => { console.log(arg); event.reply('update-state', arg); });
ipcMain.on('needsSubscription', (event, arg) => { console.log(arg); event.reply('needsSubscription', arg); });
ipcMain.on('navigate', (event, page) => { win.loadFile(page); });
ipcMain.on('navigateToWebApp', (event, page) => { shell.openExternal(`${store.get('hostURL')}/${page}`); });

ipcMain.handle('getFiles', getFiles);
ipcMain.handle('getFolders', getFolders);
ipcMain.handle('removeFile', removeFile);
ipcMain.handle('removeFolder', removeFolder);
ipcMain.handle('setURL', setURL);
ipcMain.handle('getURL', getURL);
ipcMain.handle('setToken', setToken);
ipcMain.handle('getToken', getToken);
ipcMain.handle('getUserInfo', getUserInfo);
ipcMain.handle('syncData', (event, regenerate) => syncData(regenerate));
ipcMain.handle('deleteAllFiles', deleteAllFiles);

createWindow();

app.setAboutPanelOptions({
applicationName: "Khoj",
applicationVersion: khojPackage.version,
version: khojPackage.version,
authors: "Saba Imran, Debanjum Singh Solanky and contributors",
MythicalCow marked this conversation as resolved.
Show resolved Hide resolved
website: "https://khoj.dev",
copyright: "GPL v3",
iconPath: path.join(__dirname, 'assets', 'icons', 'favicon-128x128.png')
});

app.on('ready', async() => {
try {
const result = await todesktop.autoUpdater.checkForUpdates();
if (result.updateInfo) {
console.log("Desktop app update found:", result.updateInfo.version);
todesktop.autoUpdater.restartAndInstall();
}
} catch (e) {
console.warn("Desktop app update check failed:", e);
}
});
openShortcut = false;
globalShortcut.register('CommandOrControl+Shift+K', () => {
sabaimran marked this conversation as resolved.
Show resolved Hide resolved
if(openShortcut) return;
const shortcutWin = createShortcutWindow(); // Create a new shortcut window each time the shortcut is triggered
const clipboardText = clipboard.readText();
console.log('Sending clipboard text:', clipboardText); // Debug log
shortcutWin.webContents.once('dom-ready', () => {
shortcutWin.webContents.send('clip', clipboardText);
console.log('Message sent to window'); // Debug log
});
openShortcut = true;
// Register a global shortcut for the Escape key for the shortcutWin
globalShortcut.register('Escape', () => {
if (shortcutWin) {
shortcutWin.close();
}
// Unregister the Escape key shortcut
globalShortcut.unregister('Escape');
openShortcut = false;
});
} else {
// Show main window directly if not first run
win.once('ready-to-show', () => { win.show(); });
}
}

app.whenReady().then(() => {
addCSPHeaderToSession();

ipcMain.on('set-title', handleSetTitle);

ipcMain.handle('handleFileOpen', (event, type) => {
return handleFileOpen(type);
});

ipcMain.on('update-state', (event, arg) => {
console.log(arg);
event.reply('update-state', arg);
});

ipcMain.on('needsSubscription', (event, arg) => {
console.log(arg);
event.reply('needsSubscription', arg);
});

ipcMain.on('navigate', (event, page) => {
win.loadFile(page);
});

ipcMain.on('navigateToWebApp', (event, page) => {
shell.openExternal(`${store.get('hostURL')}/${page}`);
});

ipcMain.handle('getFiles', getFiles);
ipcMain.handle('getFolders', getFolders);

ipcMain.handle('removeFile', removeFile);
ipcMain.handle('removeFolder', removeFolder);

ipcMain.handle('setURL', setURL);
ipcMain.handle('getURL', getURL);

ipcMain.handle('setToken', setToken);
ipcMain.handle('getToken', getToken);
ipcMain.handle('getUserInfo', getUserInfo);

ipcMain.handle('syncData', (event, regenerate) => {
syncData(regenerate);
});
ipcMain.handle('deleteAllFiles', deleteAllFiles);

createWindow();
});

app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});

app.setAboutPanelOptions({
applicationName: "Khoj",
applicationVersion: khojPackage.version,
version: khojPackage.version,
authors: "Saba Imran, Debanjum Singh Solanky and contributors",
website: "https://khoj.dev",
copyright: "GPL v3",
iconPath: path.join(__dirname, 'assets', 'icons', 'favicon-128x128.png')
});

app.on('ready', async() => {
try {
const result = await todesktop.autoUpdater.checkForUpdates();
if (result.updateInfo) {
console.log("Desktop app update found:", result.updateInfo.version);
todesktop.autoUpdater.restartAndInstall();
}
} catch (e) {
console.warn("Desktop app update check failed:", e);
}
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});

app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('will-quit', () => {
globalShortcut.unregisterAll();
});

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})

/*
** About Page
Expand Down
8 changes: 8 additions & 0 deletions src/interface/desktop/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ contextBridge.exposeInMainWorld('electronAPI', {
setTitle: (title) => ipcRenderer.send('set-title', title)
})

contextBridge.exposeInMainWorld('clipboardAPI', {
sendClipboardText: (callback) => {
ipcRenderer.on('clip', (event, message) => {
callback(message);
});
}
});

contextBridge.exposeInMainWorld('storeValueAPI', {
handleFileOpen: (key) => ipcRenderer.invoke('handleFileOpen', key)
})
Expand Down
Loading