carvus-lens 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -0
- package/README.md +26 -0
- package/bin/cli.js +12 -0
- package/icon.png +0 -0
- package/index.html +687 -0
- package/main.js +202 -0
- package/package.json +36 -0
- package/render.js +651 -0
package/main.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
const { app, BrowserWindow, ipcMain, desktopCapturer, screen, globalShortcut, Tray, Menu, nativeImage } = require('electron');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
let mainWindow = null;
|
|
5
|
+
let tray = null;
|
|
6
|
+
let isOverlayActive = false;
|
|
7
|
+
|
|
8
|
+
// ─── Create the transparent overlay window ───────────────────────────────────
|
|
9
|
+
function createOverlayWindow() {
|
|
10
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
11
|
+
mainWindow.destroy();
|
|
12
|
+
mainWindow = null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const primaryDisplay = screen.getPrimaryDisplay();
|
|
16
|
+
const { width, height } = primaryDisplay.size;
|
|
17
|
+
const scaleFactor = primaryDisplay.scaleFactor;
|
|
18
|
+
|
|
19
|
+
mainWindow = new BrowserWindow({
|
|
20
|
+
x: primaryDisplay.bounds.x,
|
|
21
|
+
y: primaryDisplay.bounds.y,
|
|
22
|
+
width: width,
|
|
23
|
+
height: height,
|
|
24
|
+
frame: false,
|
|
25
|
+
transparent: true,
|
|
26
|
+
alwaysOnTop: true,
|
|
27
|
+
skipTaskbar: true,
|
|
28
|
+
resizable: false,
|
|
29
|
+
fullscreenable: true,
|
|
30
|
+
hasShadow: false,
|
|
31
|
+
show: false,
|
|
32
|
+
type: 'toolbar', // Helps on some Linux WMs
|
|
33
|
+
webPreferences: {
|
|
34
|
+
nodeIntegration: true,
|
|
35
|
+
contextIsolation: false,
|
|
36
|
+
webSecurity: false,
|
|
37
|
+
webviewTag: true,
|
|
38
|
+
backgroundThrottling: false
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
mainWindow.loadFile('index.html');
|
|
43
|
+
mainWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
|
|
44
|
+
|
|
45
|
+
mainWindow.on('closed', () => {
|
|
46
|
+
mainWindow = null;
|
|
47
|
+
isOverlayActive = false;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return mainWindow;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ─── Trigger the overlay (Super+C action) ────────────────────────────────────
|
|
54
|
+
async function triggerOverlay() {
|
|
55
|
+
if (isOverlayActive) {
|
|
56
|
+
// If already active, dismiss it
|
|
57
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
58
|
+
mainWindow.destroy();
|
|
59
|
+
mainWindow = null;
|
|
60
|
+
}
|
|
61
|
+
isOverlayActive = false;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
isOverlayActive = true;
|
|
66
|
+
createOverlayWindow();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─── Register the global shortcut ────────────────────────────────────────────
|
|
70
|
+
const SHORTCUT_COMBOS = ['Super+C', 'Ctrl+Shift+S', 'Ctrl+Alt+C'];
|
|
71
|
+
let registeredShortcut = null;
|
|
72
|
+
|
|
73
|
+
function registerShortcut() {
|
|
74
|
+
for (const combo of SHORTCUT_COMBOS) {
|
|
75
|
+
try { globalShortcut.unregister(combo); } catch (e) {}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const combo of SHORTCUT_COMBOS) {
|
|
79
|
+
try {
|
|
80
|
+
const registered = globalShortcut.register(combo, () => {
|
|
81
|
+
triggerOverlay();
|
|
82
|
+
});
|
|
83
|
+
if (registered) {
|
|
84
|
+
registeredShortcut = combo;
|
|
85
|
+
console.log(`[Carvus Lens] ${combo} registered successfully.`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.warn('[Carvus Lens] All shortcut registrations failed. Retrying in 3s...');
|
|
92
|
+
setTimeout(registerShortcut, 3000);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─── Create system tray for background service ──────────────────────────────
|
|
96
|
+
function createTray() {
|
|
97
|
+
// Create a small tray icon programmatically
|
|
98
|
+
const iconSize = 16;
|
|
99
|
+
const canvas = nativeImage.createFromBuffer(
|
|
100
|
+
Buffer.alloc(iconSize * iconSize * 4, 0),
|
|
101
|
+
{ width: iconSize, height: iconSize }
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Try to use a proper icon, fallback to empty
|
|
105
|
+
try {
|
|
106
|
+
const iconPath = path.join(__dirname, 'icon.png');
|
|
107
|
+
tray = new Tray(iconPath);
|
|
108
|
+
} catch (e) {
|
|
109
|
+
tray = new Tray(canvas);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const contextMenu = Menu.buildFromTemplate([
|
|
113
|
+
{ label: '🔍 Carvus Lens', enabled: false },
|
|
114
|
+
{ type: 'separator' },
|
|
115
|
+
{ label: 'Trigger (Super+C)', click: () => triggerOverlay() },
|
|
116
|
+
{ type: 'separator' },
|
|
117
|
+
{ label: 'Quit', click: () => { app.quit(); } }
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
tray.setToolTip('Carvus Lens — Press Super+C or Ctrl+Shift+S');
|
|
121
|
+
tray.setContextMenu(contextMenu);
|
|
122
|
+
|
|
123
|
+
tray.on('click', () => {
|
|
124
|
+
triggerOverlay();
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ─── App lifecycle ───────────────────────────────────────────────────────────
|
|
129
|
+
app.whenReady().then(() => {
|
|
130
|
+
createTray();
|
|
131
|
+
registerShortcut();
|
|
132
|
+
|
|
133
|
+
// Launch overlay immediately on first run
|
|
134
|
+
setTimeout(() => triggerOverlay(), 500);
|
|
135
|
+
|
|
136
|
+
// Re-register shortcut periodically in case it gets unregistered by the WM
|
|
137
|
+
setInterval(() => {
|
|
138
|
+
const anyRegistered = SHORTCUT_COMBOS.some(c => globalShortcut.isRegistered(c));
|
|
139
|
+
if (!anyRegistered) {
|
|
140
|
+
registerShortcut();
|
|
141
|
+
}
|
|
142
|
+
}, 8000);
|
|
143
|
+
|
|
144
|
+
const shortcutHint = registeredShortcut || 'Ctrl+Shift+S or Ctrl+Alt+C';
|
|
145
|
+
console.log(`[Carvus Lens] Background service running. Press ${shortcutHint} to activate.`);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
app.on('will-quit', () => {
|
|
149
|
+
globalShortcut.unregisterAll();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Keep the app running in the background — do NOT quit when all windows close
|
|
153
|
+
app.on('window-all-closed', (e) => {
|
|
154
|
+
// Prevent default quit behavior
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// ─── IPC Handlers ────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
// Get desktop stream source ID for screen capture
|
|
160
|
+
ipcMain.handle('get-desktop-stream-id', async () => {
|
|
161
|
+
try {
|
|
162
|
+
const sources = await desktopCapturer.getSources({
|
|
163
|
+
types: ['screen'],
|
|
164
|
+
thumbnailSize: { width: 1, height: 1 }
|
|
165
|
+
});
|
|
166
|
+
if (sources.length > 0) {
|
|
167
|
+
return sources[0].id;
|
|
168
|
+
}
|
|
169
|
+
throw new Error('No screen sources found');
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error('[Carvus Lens] Failed to get desktop stream:', err);
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Show the overlay window after screen capture is ready
|
|
177
|
+
ipcMain.on('show-window', () => {
|
|
178
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
179
|
+
mainWindow.show();
|
|
180
|
+
mainWindow.focus();
|
|
181
|
+
mainWindow.setAlwaysOnTop(true, 'screen-saver');
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Dismiss the overlay
|
|
186
|
+
ipcMain.on('dismiss-overlay', () => {
|
|
187
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
188
|
+
mainWindow.destroy();
|
|
189
|
+
mainWindow = null;
|
|
190
|
+
}
|
|
191
|
+
isOverlayActive = false;
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Quit the app entirely
|
|
195
|
+
ipcMain.on('quit-app', () => {
|
|
196
|
+
app.quit();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Get display scale factor
|
|
200
|
+
ipcMain.handle('get-scale-factor', () => {
|
|
201
|
+
return screen.getPrimaryDisplay().scaleFactor;
|
|
202
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "carvus-lens",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Circle-to-Search for desktop — draw a circle on your screen to instantly search Google Lens, get AI answers, and translate text. Powered by Tesseract OCR and Groq AI.",
|
|
5
|
+
"main": "main.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"carvus-lens": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "electron .",
|
|
11
|
+
"dev": "electron ."
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"circle-to-search",
|
|
15
|
+
"google-lens",
|
|
16
|
+
"screen-search",
|
|
17
|
+
"ocr",
|
|
18
|
+
"tesseract",
|
|
19
|
+
"groq",
|
|
20
|
+
"ai",
|
|
21
|
+
"electron",
|
|
22
|
+
"desktop",
|
|
23
|
+
"visual-search",
|
|
24
|
+
"carvus-lens"
|
|
25
|
+
],
|
|
26
|
+
"author": "Aadil Fazal",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/aadilfazal/carvus-lens"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"electron": "^30.0.0",
|
|
34
|
+
"tesseract.js": "^7.0.0"
|
|
35
|
+
}
|
|
36
|
+
}
|