Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 1 addition & 10 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,21 +70,13 @@ temp/

# Keep lockfiles tracked (do not ignore package-lock.json)

# Task list
<<<<<<< HEAD
# Task lists
task-list.md
=======
tasklist.md
>>>>>>> development

# Security reports
security_reports/

<<<<<<< HEAD
# Task lists
tasklist.md

=======
# Regression test checklist
REGRESSION_TEST_CHECKLIST.md

Expand All @@ -93,4 +85,3 @@ release.md

# copyparty
copyparty_1.19.22.exe
>>>>>>> development
34 changes: 0 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,39 +87,6 @@ Grok-Desktop/
└── README.md # This file
```

<<<<<<< HEAD
## Build Grok-Desktop
1. Install Node.js from [nodejs.org](https://nodejs.org/).
2. Clone this repository or download the files.
3. Install dependencies if needed: `npm install`
4. Build using npm scripts:
- Directory build (unpacked): `npm run build-dir`
- Portable executable: `npm run build-portable`
- Full installers (NSIS + MSI): `npm run build-installer`

Notes:
- These scripts use `npx electron-builder@latest` (no global install required).
- All build outputs are written to the `build` directory.

## Usage
- After building, install `Grok-Desktop` with `Grok-Desktop_Installer-v1.2.3.exe` from the `build` directory
- Launch `Grok-Desktop` from the Start Menu
- Log in via `grok.com`, using Google, Apple, or xAI authentication as needed.
- Use the + button in the top toolbar (or Ctrl+T) to add new tabs.
- Click the AOT button in the top right to toggle always-on-top functionality.
- Use keyboard shortcuts to work faster:
- Ctrl+T: Open a new tab
- Ctrl+Tab / Ctrl+Shift+Tab: Cycle through open tabs (next/previous)
- Ctrl+R: Reload the currently active tab
- Ctrl+I: Show information/about dialog

## Keyboard Shortcuts
- Ctrl+T: Open a new tab
- Ctrl+Tab: Switch to the next tab
- Ctrl+Shift+Tab: Switch to the previous tab
- Ctrl+R: Reload the active tab (does not reload the entire app window)
- Ctrl+I: Show information/about dialog
=======
## Building from Source

### Prerequisites
Expand All @@ -129,7 +96,6 @@ Notes:
4. Install dependencies: `npm install`

### Build Commands
>>>>>>> development

#### Windows
```bash
Expand Down
9 changes: 8 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,14 @@
};

// Open links that request a new window
webview.addEventListener('new-window', (e) => { e.preventDefault(); handleOpenUrl(e.url); });
webview.addEventListener('new-window', (e) => {
// Let OAuth popups open as real BrowserWindows (handled by main process)
if (/accounts\.google\.com|appleid\.apple\.com/.test(e.url)) {
return; // Don't prevent — popup opens naturally with shared session
}
e.preventDefault();
handleOpenUrl(e.url);
});
// Allow internal same-tab nav; open external in browser
webview.addEventListener('will-navigate', (e) => {
const url = e.url;
Expand Down
13 changes: 11 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"build-rpm": "npx --yes electron-builder@latest --linux rpm",
"build-deb": "npx --yes electron-builder@latest --linux deb",
"build-rpm-deb": "npx --yes electron-builder@latest --linux rpm deb",
"build-all": "npx --yes electron-builder@latest --win --x64 --linux"
"build-all": "npx --yes electron-builder@latest --win --x64 --linux",
"build-mac": "npx --yes electron-builder@latest --mac"
},
"keywords": [
"electron",
Expand All @@ -23,7 +24,7 @@
"tabs"
],
"author": "AnRkey <anrkey@gmail.com>",
"license": "ISC",
"license": "GPL-2.0",
"type": "commonjs",
"dependencies": {
"electron-tabs": "^1.0.4"
Expand All @@ -49,6 +50,14 @@
"category": "Network",
"icon": "build-resources/icons"
},
"mac": {
"target": [
"dmg"
],
"category": "public.app-category.productivity",
"icon": "build-resources/icons/512x512.png",
"artifactName": "Grok-Desktop-v${version}.dmg"
},
"rpm": {
"artifactName": "Grok-Desktop-v${version}.${ext}"
},
Expand Down
75 changes: 61 additions & 14 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,22 @@ const { exec } = require('child_process');
// GPU acceleration detection and graceful fallback
function configureGpuAcceleration() {
// Check if we're in a headless environment or container
const isHeadless = !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY;
// macOS and Windows don't use DISPLAY/WAYLAND_DISPLAY — only check on Linux
const isHeadless = process.platform === 'linux' && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY;
const isContainer = fs.existsSync('/.dockerenv') || process.env.container === 'docker';

// Check for NVIDIA/AMD GPU availability (basic detection)
// Check for GPU availability (platform-aware detection)
let hasGpu = false;
try {
// Check for NVIDIA GPU
// macOS always has a GPU (Metal/integrated graphics) — never disable acceleration
if (process.platform === 'darwin') {
hasGpu = true;
}
// Check for NVIDIA GPU (Linux/Windows)
if (fs.existsSync('/dev/nvidia0') || process.env.NVIDIA_VISIBLE_DEVICES) {
hasGpu = true;
}
// Check for AMD GPU
// Check for AMD/Intel GPU (Linux)
if (fs.existsSync('/dev/dri/card0')) {
hasGpu = true;
}
Expand Down Expand Up @@ -346,6 +351,15 @@ function createWindow() {
if (typeof grokSession.setSpellCheckerLanguages === 'function') {
grokSession.setSpellCheckerLanguages(languages);
}

// Override User-Agent to match a real Chrome browser so login/OAuth flows work.
// Electron's default UA includes "Electron" which many auth providers reject.
const chromeVersion = process.versions.chrome || '131.0.0.0';
const platform = process.platform === 'darwin' ? 'Macintosh; Intel Mac OS X 10_15_7'
: process.platform === 'win32' ? 'Windows NT 10.0; Win64; x64'
: 'X11; Linux x86_64';
const chromeUA = `Mozilla/5.0 (${platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36`;
grokSession.setUserAgent(chromeUA);
}
} catch (_) {}

Expand Down Expand Up @@ -443,15 +457,48 @@ app.on('window-all-closed', () => {
function setupUrlHandling() {
// Handle navigation events from webContents
app.on('web-contents-created', (event, contents) => {
// Intercept new window requests; always deny BrowserWindow creation
// Internal domains will be handled by the renderer's webview 'new-window' handler
contents.setWindowOpenHandler(({ url }) => {
const isInternal = allowedUrlPatterns.some(pattern => pattern.test(url));
if (!isInternal) {
shell.openExternal(url);
return { action: 'deny' };
}

// Allow OAuth popups to open as real BrowserWindows so login flows work.
// Google/Apple OAuth require a proper popup with window.opener context.
const isOAuth = /^https?:\/\/(accounts\.google\.com|appleid\.apple\.com)/i.test(url);
if (isOAuth) {
return {
action: 'allow',
overrideBrowserWindowOptions: {
width: 500,
height: 700,
webPreferences: {
partition: 'persist:grok',
nodeIntegration: false,
contextIsolation: true
}
}
};
}

// Other internal URLs are handled by the renderer's webview new-window handler
return { action: 'deny' };
});

// Manage OAuth popup lifecycle — close when auth redirects back to grok.com
contents.on('did-create-window', (popup) => {
popup.webContents.on('did-navigate', (_e, navUrl) => {
if (/^https?:\/\/grok\.com/i.test(navUrl)) {
popup.close();
}
});
popup.webContents.on('will-redirect', (_e, navUrl) => {
if (/^https?:\/\/grok\.com/i.test(navUrl)) {
popup.close();
}
});
});
});
}

Expand Down Expand Up @@ -754,38 +801,38 @@ function attachShortcutHandlers(contents) {
try {
contents.on('before-input-event', (event, input) => {
try {
// Only handle keyDown with Control on Windows/Linux
if (input.type !== 'keyDown' || !input.control) return;
// Handle keyDown with Control (Windows/Linux) or Meta/Cmd (macOS)
const modifier = process.platform === 'darwin' ? input.meta : input.control;
if (input.type !== 'keyDown' || !modifier) return;

const key = input.key;
// Deliver to the hosting window (handles webviews as well)
const host = contents.hostWebContents || contents;
const win = BrowserWindow.fromWebContents(host);
if (!win || win.isDestroyed()) return;

// Ctrl+K -> Remap to Ctrl+Shift+K for grok.com search
// Cmd/Ctrl+K -> Remap to Cmd/Ctrl+Shift+K for grok.com search
// grok.com responds to Ctrl+Shift+K, not Ctrl+K (which Chromium intercepts for omnibox)
const modKey = process.platform === 'darwin' ? 'meta' : 'control';
if ((key === 'k' || key === 'K') && !input.shift) {
event.preventDefault();
// Send Ctrl+Shift+K to the webContents using sendInputEvent (creates trusted OS-level events)
contents.sendInputEvent({
type: 'keyDown',
keyCode: 'K',
modifiers: ['control', 'shift']
modifiers: [modKey, 'shift']
});
// Send keyUp after a short delay
setTimeout(() => {
try {
contents.sendInputEvent({
type: 'keyUp',
keyCode: 'K',
modifiers: ['control', 'shift']
modifiers: [modKey, 'shift']
});
} catch (_) {}
}, 10);
return;
}
// Don't intercept Ctrl+Shift+K - let it pass through naturally (it already works)
// Don't intercept Cmd/Ctrl+Shift+K - let it pass through naturally (it already works)

// Ctrl+T -> new tab
if (key === 't' || key === 'T') {
Expand Down