Skip to content

Commit 5b11989

Browse files
authored
Merge pull request #254 from aliansoftwareteam/fix/tracker-activity-powermonitor
fix(tracker): AV-safe keyboard/mouse activity detection
2 parents c1b7d7a + 61cbb09 commit 5b11989

6 files changed

Lines changed: 92 additions & 143 deletions

File tree

time-tracker-app/main/background.js

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { app, ipcMain, Menu, Tray, BrowserWindow, shell, dialog, desktopCapturer
22
import serve from 'electron-serve'
33
import { createWindow } from './helpers'
44
const path = require('node:path')
5-
const { GlobalKeyboardListener } = require("node-global-key-listener")
65
const fs = require('fs')
76
const iconPath = path.join(__dirname, 'logo.png')
87
const trayIconPath = path.join(__dirname, 'traylogo.png')
@@ -48,11 +47,13 @@ if (process.defaultApp) {
4847
app.setAsDefaultProtocolClient('myapp')
4948
}
5049

51-
const v = new GlobalKeyboardListener()
5250
let mainWindow
5351
let tray = null
54-
let keyboardListener = null
5552
let isTracking = null
53+
let activityInterval = null
54+
55+
// A second counts as "active" when the OS reports input within this many seconds.
56+
const ACTIVE_IDLE_THRESHOLD_SEC = 2
5657

5758
if (isProd) {
5859
serve({ directory: 'app' })
@@ -302,39 +303,53 @@ if (!gotTheLock) {
302303
})
303304
}
304305

305-
const setupKeyboardListener = () => {
306-
if (!verifyAccessibilityPermission()) {
307-
mainWindow.webContents.send('permission:denied', { type: 'keyboard' })
308-
return false
309-
}
310-
311-
keyboardListener = function (e, down) {
312-
if (e.state == 'UP' && isTracking) {
313-
mainWindow.webContents.send('keyboard:click', { key: 'keyboard', e: e.name })
306+
// Activity sampling WITHOUT a global input hook (a hook is what antivirus flags
307+
// as a keylogger — the bug that quarantined the old node-global-key-listener
308+
// binary). Each second we read two built-in, hook-free signals:
309+
// - powerMonitor.getSystemIdleTime(): seconds since the last input (kbd OR mouse)
310+
// - screen.getCursorScreenPoint(): the current mouse cursor position
311+
// If the cursor moved this second it's MOUSE activity; if there was input but the
312+
// cursor did not move (typing) it's KEYBOARD activity. Limitation: a mouse click
313+
// that doesn't move the cursor is counted as keyboard — acceptable, since exact
314+
// click counts would require the AV-flagged global hook. No native binary, no
315+
// accessibility permission.
316+
let lastCursorPoint = null
317+
const startActivitySampling = () => {
318+
if (activityInterval) return
319+
lastCursorPoint = null
320+
activityInterval = setInterval(() => {
321+
if (!isTracking || !mainWindow || mainWindow.isDestroyed()) return
322+
const idleSeconds = powerMonitor.getSystemIdleTime()
323+
const cursor = screen.getCursorScreenPoint()
324+
const mouseMoved = lastCursorPoint !== null && (cursor.x !== lastCursorPoint.x || cursor.y !== lastCursorPoint.y)
325+
lastCursorPoint = cursor
326+
if (mouseMoved) {
327+
mainWindow.webContents.send('activity:tick', { type: 'mouse' })
328+
} else if (idleSeconds <= ACTIVE_IDLE_THRESHOLD_SEC) {
329+
mainWindow.webContents.send('activity:tick', { type: 'keyboard' })
314330
}
315-
}
316-
v.addListener(keyboardListener)
317-
return true
331+
}, 1000)
318332
}
319333

320-
// Function to remove keyboard listener
321-
const removeKeyboardListener = () => {
322-
if (keyboardListener) {
323-
v.removeListener(keyboardListener)
324-
keyboardListener = null
334+
// Stop the activity sampler.
335+
const stopActivitySampling = () => {
336+
if (activityInterval) {
337+
clearInterval(activityInterval)
338+
activityInterval = null
325339
}
340+
lastCursorPoint = null
326341
}
327342

328343
// Add IPC listeners for start and stop events
329344
ipcMain.on('start-listen-event', () => {
330-
isTracking = setupKeyboardListener()
331-
// Inform renderer process if setup was successful
332-
mainWindow.webContents.send('tracking:status', { active: isTracking })
345+
isTracking = true
346+
startActivitySampling()
347+
mainWindow.webContents.send('tracking:status', { active: true })
333348
})
334349

335350
ipcMain.on('stop-listen-event', () => {
336351
isTracking = false
337-
removeKeyboardListener()
352+
stopActivitySampling()
338353
mainWindow.webContents.send('tracking:status', { active: false })
339354
})
340355

0 commit comments

Comments
 (0)