Skip to content
Merged
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
148 changes: 148 additions & 0 deletions electron/src/main/appDetection/detectCommonWindowsApps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// This file needs to be on main as we are using node APIs to detect if common apps are installed on the user's system. We will likely need to expand this in the future to support more apps and other platforms, but for now we are just focusing on a few common Windows apps.
import fs from 'node:fs'
import path from 'node:path'
import { COMMON_APPS } from "../../shared/appDetection/commonApps.ts"

export type DetectedWindowsApp = {
id: string
displayName: string
category: 'productivity' | 'distraction' | 'browser'
executablePath: string
defaultStatus: 'allowed' | 'blocked'
}

function expandWindowsEnvironmentPath(rawPath: string) {
return rawPath.replace(/%([^%]+)%/g, (_, variableName: string) => {
return process.env[variableName] ?? ''
})
}

function pathHasWildcard(filePath: string) {
return filePath.includes('*')
}

function findWildcardPath(filePath: string) {
const normalizedPath = path.normalize(filePath)
const wildcardIndex = normalizedPath.indexOf('*')

if (wildcardIndex === -1) {
return fs.existsSync(normalizedPath) ? normalizedPath : null
}

const beforeWildcard = normalizedPath.slice(0, wildcardIndex)
const afterWildcard = normalizedPath.slice(wildcardIndex + 1)

const baseDirectory = path.dirname(beforeWildcard)
const prefix = path.basename(beforeWildcard)

try {
if (!fs.existsSync(baseDirectory)) {
return null
}

const entries = fs.readdirSync(baseDirectory, {
withFileTypes: true,
})

const matchedDirectory = entries.find((entry) => {
return entry.isDirectory() && entry.name.startsWith(prefix)
})

if (!matchedDirectory) {
return null
}

const possiblePath = path.join(
baseDirectory,
matchedDirectory.name,
afterWildcard
)

return fs.existsSync(possiblePath) ? possiblePath : null
} catch (error) {
console.warn('[Taskmaster] Could not scan wildcard path:', {
filePath,
baseDirectory,
error,
})

return null
}
}

function findExistingAppPath(commonWindowsPaths: string[]) {
for (const rawPath of commonWindowsPaths) {
const expandedPath = expandWindowsEnvironmentPath(rawPath)

console.log('[Taskmaster] Checking path:', {
rawPath,
expandedPath,
})

if (!expandedPath) {
continue
}

if (pathHasWildcard(expandedPath)) {
const matchedPath = findWildcardPath(expandedPath)

// --- debug log ---
console.log('[Taskmaster] Wildcard path result:', {
expandedPath,
matchedPath,
})
// --- remove later ---

if (matchedPath) {
return matchedPath
}

continue
}

const normalizedPath = path.normalize(expandedPath)
try {
const exists = fs.existsSync(normalizedPath)

console.log('[Taskmaster] Path exists result:', {
normalizedPath,
exists,
})

if (exists) {
return normalizedPath
}
} catch (error) {
console.warn('[Taskmaster] Could not check path:', {
normalizedPath,
error,
})
}
}

return null
}

export function detectCommonWindowsApps(): DetectedWindowsApp[] {
if (process.platform !== 'win32') {
return []
}

return COMMON_APPS.flatMap((app) => {
const executablePath = findExistingAppPath(app.commonWindowsPaths)

if (!executablePath) {
return []
}

return [
{
id: app.id,
displayName: app.displayName,
category: app.category,
executablePath,
defaultStatus: app.defaultStatus,
},
]
})
}
6 changes: 6 additions & 0 deletions electron/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import { app, BrowserWindow, Tray, Menu, nativeImage } from 'electron'
import path from 'path'
import { fileURLToPath } from 'url'
import { registerIpcHandlers } from './ipc-handlers.ts'



const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
Expand Down Expand Up @@ -32,6 +35,7 @@ function createWindow() {
minWidth: 1000,
minHeight: 700,
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
nodeIntegration: false,
contextIsolation: true,
},
Expand All @@ -40,7 +44,9 @@ function createWindow() {
win.loadURL('http://localhost:5173')
}


app.whenReady().then(() => {
registerIpcHandlers()
createWindow()
createTray()
})
17 changes: 16 additions & 1 deletion electron/src/main/ipc-handlers.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,17 @@
// Registers all ipcMain.handle() and ipcMain.on() listeners.
// This is the entry point for every message the renderer sends — start session, save settings, get history, etc.
// This is the entry point for every message the renderer sends — start session, save settings, get history, etc.import { ipcMain } from 'electron'
import { ipcMain } from 'electron'
import { detectCommonWindowsApps } from './appDetection/detectCommonWindowsApps.ts'

export function registerIpcHandlers() {
ipcMain.removeHandler('taskmaster:detect-common-apps')

ipcMain.handle('taskmaster:detect-common-apps', () => {
const detectedApps = detectCommonWindowsApps()

console.log('[Taskmaster] Detected common apps:')
console.log(JSON.stringify(detectedApps, null, 2))

return detectedApps
})
}
11 changes: 11 additions & 0 deletions electron/src/preload/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Uses contextBridge.exposeInMainWorld() to give the renderer a safe, limited API.
// Example: window.taskmaster.startSession().
// The renderer can never call Node directly - everything goes through here.

const { contextBridge, ipcRenderer } = require('electron')

console.log('Taskmaster preload loaded')

contextBridge.exposeInMainWorld('taskmaster', {
detectCommonApps: () => ipcRenderer.invoke('taskmaster:detect-common-apps'),
})
3 changes: 0 additions & 3 deletions electron/src/preload/index.ts

This file was deleted.

Loading
Loading