Squashed commit of the following:

commit 9cf278791848e0a618ebabca97d9fc7405047cde
Author: haritabh-z01 <haritabh.z01+github@gmail.com>
Date:   Fri Sep 12 00:15:52 2025 +0530

    fix: native dep coppying on windows

commit 4cdebf31659753f8f9bf0a01299474dc7a1a99a1
Author: haritabh-z01 <haritabh.z01+github@gmail.com>
Date:   Thu Sep 11 23:21:06 2025 +0530

    chore: set diff default shortcuts for windows

commit 9caf66fa7b5188cf445ace530ccf35033853909d
Author: haritabh-z01 <haritabh.z01+github@gmail.com>
Date:   Thu Sep 11 23:10:16 2025 +0530

    fix: windows shortcuts compatibility
This commit is contained in:
haritabh-z01 2025-09-12 00:16:17 +05:30
parent 93496a2bde
commit f92d47df30
8 changed files with 271 additions and 176 deletions

View file

@ -175,7 +175,10 @@ const config: ForgeConfig = {
// Read where the symlink points to
const symlinkTarget = readlinkSync(localDepPath);
const absoluteTarget = join(localDepPath, "..", symlinkTarget);
let absoluteTarget = symlinkTarget;
if (process.platform !== "win32") {
absoluteTarget = join(localDepPath, "..", symlinkTarget);
}
const sourcePath = normalize(absoluteTarget);
console.log(` Symlink points to: ${sourcePath}`);

View file

@ -1,156 +0,0 @@
#!/usr/bin/env node
const https = require('https');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const { createWriteStream, mkdirSync, chmodSync } = fs;
// Node.js version to download
const NODE_VERSION = '24.4.0';
// Platform configurations
const PLATFORMS = [
{
platform: 'darwin',
arch: 'arm64',
url: `https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-darwin-arm64.tar.gz`,
binary: 'bin/node'
},
{
platform: 'darwin',
arch: 'x64',
url: `https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-darwin-x64.tar.gz`,
binary: 'bin/node'
},
{
platform: 'win32',
arch: 'x64',
url: `https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-win-x64.zip`,
binary: 'node.exe'
},
{
platform: 'linux',
arch: 'x64',
url: `https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz`,
binary: 'bin/node'
}
];
// Base directory for binaries
const RESOURCES_DIR = path.join(__dirname, '..', 'node-binaries');
async function downloadFile(url, dest) {
return new Promise((resolve, reject) => {
const file = createWriteStream(dest);
https.get(url, (response) => {
if (response.statusCode === 302 || response.statusCode === 301) {
// Handle redirect
https.get(response.headers.location, (redirectResponse) => {
redirectResponse.pipe(file);
file.on('finish', () => {
file.close(resolve);
});
}).on('error', reject);
} else {
response.pipe(file);
file.on('finish', () => {
file.close(resolve);
});
}
}).on('error', reject);
});
}
async function extractArchive(archivePath, platform) {
const tempDir = path.join(path.dirname(archivePath), 'temp');
mkdirSync(tempDir, { recursive: true });
if (platform === 'win32') {
// Use unzip command (available on macOS) to extract zip files
execSync(`unzip -q "${archivePath}" -d "${tempDir}"`, { stdio: 'inherit' });
} else {
// Use tar for Unix-like systems
execSync(`tar -xzf "${archivePath}" -C "${tempDir}"`, { stdio: 'inherit' });
}
return tempDir;
}
async function downloadNodeBinary(config) {
const { platform, arch, url, binary } = config;
const platformDir = path.join(RESOURCES_DIR, `${platform}-${arch}`);
const binaryPath = path.join(platformDir, platform === 'win32' ? 'node.exe' : 'node');
// Skip if already exists
if (fs.existsSync(binaryPath)) {
console.log(`${platform}-${arch} binary already exists`);
return;
}
console.log(`Downloading Node.js for ${platform}-${arch}...`);
// Create directory
mkdirSync(platformDir, { recursive: true });
// Download archive
const archiveExt = platform === 'win32' ? '.zip' : '.tar.gz';
const archivePath = path.join(platformDir, `node-v${NODE_VERSION}${archiveExt}`);
try {
await downloadFile(url, archivePath);
console.log(`Downloaded archive for ${platform}-${arch}`);
// Extract archive
const tempDir = await extractArchive(archivePath, platform);
// Find the node binary in extracted files
// Windows uses different directory naming convention (win instead of win32)
const extractedDirName = platform === 'win32'
? `node-v${NODE_VERSION}-win-${arch}`
: `node-v${NODE_VERSION}-${platform}-${arch}`;
const extractedBinaryPath = path.join(tempDir, extractedDirName, binary);
// Copy binary to final location
fs.copyFileSync(extractedBinaryPath, binaryPath);
// Make executable on Unix-like systems
if (platform !== 'win32') {
chmodSync(binaryPath, '755');
}
// Clean up
fs.rmSync(tempDir, { recursive: true, force: true });
fs.unlinkSync(archivePath);
console.log(`✓ Successfully installed ${platform}-${arch} binary`);
} catch (error) {
console.error(`✗ Failed to download ${platform}-${arch}:`, error.message);
// Clean up on failure
if (fs.existsSync(archivePath)) {
fs.unlinkSync(archivePath);
}
}
}
async function main() {
console.log(`Downloading Node.js v${NODE_VERSION} binaries for all platforms...\n`);
// Create base directory
mkdirSync(RESOURCES_DIR, { recursive: true });
// Download binaries for all platforms
for (const platform of PLATFORMS) {
await downloadNodeBinary(platform);
}
console.log('\nDone! Node.js binaries downloaded to:', RESOURCES_DIR);
}
// Run if called directly
if (require.main === module) {
main().catch(console.error);
}
module.exports = { downloadNodeBinary, PLATFORMS, NODE_VERSION };

View file

@ -142,7 +142,10 @@ async function extractArchive(
if (platform === "win32") {
// Use unzip command (available on macOS) to extract zip files
execSync(`unzip -q "${archivePath}" -d "${tempDir}"`, { stdio: "inherit" });
execSync(
`powershell -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${tempDir}' -Force"`,
{ stdio: "inherit" },
);
} else {
// Use tar for Unix-like systems
execSync(`tar -xzf "${archivePath}" -C "${tempDir}"`, { stdio: "inherit" });

View file

@ -12,7 +12,7 @@ interface ShortcutInputProps {
onRecordingShortcutChange: (recording: boolean) => void;
}
const MODIFIER_KEYS = ["Cmd", "Ctrl", "Alt", "Shift", "Fn"];
const MODIFIER_KEYS = ["Cmd", "Win", "Ctrl", "Alt", "Shift", "Fn"];
const MAX_KEY_COMBINATION_LENGTH = 3;
type ValidationResult = {
@ -60,7 +60,7 @@ function validateShortcut(keys: string[]): ValidationResult {
return {
valid: false,
error:
"Multiple keys require at least one modifier (Cmd, Ctrl, Alt, Shift, or Fn)",
"Multiple keys require at least one modifier (Cmd, Win, Ctrl, Alt, Shift, or Fn)",
};
}

View file

@ -19,10 +19,27 @@ import {
type NewAppSettings,
type AppSettingsData,
} from "./schema";
import { isWindows, isMacOS } from "../utils/platform";
// Singleton ID for app settings (we only have one settings record)
const SETTINGS_ID = 1;
// Platform-specific default shortcuts
const getDefaultShortcuts = () => {
if (isMacOS()) {
return {
pushToTalk: "Fn",
toggleRecording: "Fn+Space",
};
} else {
// Windows and Linux
return {
pushToTalk: "Ctrl+Win",
toggleRecording: "Ctrl+Win+Space",
};
}
};
// Default settings
const defaultSettings: AppSettingsData = {
formatterConfig: {
@ -48,10 +65,7 @@ const defaultSettings: AppSettingsData = {
silenceThreshold: 3,
maxRecordingDuration: 60,
},
shortcuts: {
pushToTalk: "Fn",
toggleRecording: "",
},
shortcuts: getDefaultShortcuts(),
modelProvidersConfig: {
defaultSpeechModel: "",
defaultLanguageModel: "",

View file

@ -3,6 +3,7 @@ import { globalShortcut } from "electron";
import { SettingsService } from "@/services/settings-service";
import { NativeBridge } from "@/services/platform/native-bridge-service";
import { getKeyNameFromPayload } from "@/utils/keycode-map";
import { isWindows } from "@/utils/platform";
import { KeyEventPayload, HelperEvent } from "@amical/types";
import { logger } from "@/main/logger";
@ -89,9 +90,9 @@ export class ShortcutManager extends EventEmitter {
}
}
// Track modifier keys
// Track modifier keys with platform-aware names
const modifiers = [
{ flag: payload.metaKey, name: "Cmd" },
{ flag: payload.metaKey, name: isWindows() ? "Win" : "Cmd" },
{ flag: payload.ctrlKey, name: "Ctrl" },
{ flag: payload.altKey, name: "Alt" },
{ flag: payload.shiftKey, name: "Shift" },

View file

@ -7,6 +7,7 @@ import {
updateAppSettings,
} from "../db/app-settings";
import type { AppSettingsData } from "../db/schema";
import { isWindows, isMacOS } from "../utils/platform";
/**
* Database-backed settings service with typed configuration
@ -123,10 +124,14 @@ export class SettingsService {
*/
async getShortcuts(): Promise<ShortcutsConfig> {
const shortcuts = await getSettingsSection("shortcuts");
// Return defaults if not set
// Return platform-specific defaults if not set
const defaults = isMacOS()
? { pushToTalk: "Fn", toggleRecording: "Fn+Space" }
: { pushToTalk: "Ctrl+Win", toggleRecording: "Ctrl+Win+Space" };
return {
pushToTalk: shortcuts?.pushToTalk || "Fn",
toggleRecording: shortcuts?.toggleRecording || "Fn+Space",
pushToTalk: shortcuts?.pushToTalk || defaults.pushToTalk,
toggleRecording: shortcuts?.toggleRecording || defaults.toggleRecording,
};
}

View file

@ -1,5 +1,7 @@
import { isWindows } from "./platform";
// macOS keycode mappings
export const keycodeToKey: Record<number, string> = {
const macOSKeycodeToKey: Record<number, string> = {
// Letters
0: "A",
1: "S",
@ -81,8 +83,202 @@ export const keycodeToKey: Record<number, string> = {
50: "`",
};
// Windows Virtual Key code mappings
const windowsVKToKey: Record<number, string> = {
// Mouse buttons (0x01-0x06)
0x01: "LButton",
0x02: "RButton",
0x04: "MButton",
0x05: "XButton1",
0x06: "XButton2",
// Standard keys
0x08: "Backspace",
0x09: "Tab",
0x0c: "Clear",
0x0d: "Enter",
0x10: "Shift",
0x11: "Ctrl",
0x12: "Alt",
0x13: "Pause",
0x14: "CapsLock",
0x1b: "Escape",
0x20: "Space",
0x21: "PageUp",
0x22: "PageDown",
0x23: "End",
0x24: "Home",
0x25: "Left",
0x26: "Up",
0x27: "Right",
0x28: "Down",
0x29: "Select",
0x2a: "Print",
0x2b: "Execute",
0x2c: "PrintScreen",
0x2d: "Insert",
0x2e: "Delete",
0x2f: "Help",
// Numbers (0-9)
0x30: "0",
0x31: "1",
0x32: "2",
0x33: "3",
0x34: "4",
0x35: "5",
0x36: "6",
0x37: "7",
0x38: "8",
0x39: "9",
// Letters (A-Z)
0x41: "A",
0x42: "B",
0x43: "C",
0x44: "D",
0x45: "E",
0x46: "F",
0x47: "G",
0x48: "H",
0x49: "I",
0x4a: "J",
0x4b: "K",
0x4c: "L",
0x4d: "M",
0x4e: "N",
0x4f: "O",
0x50: "P",
0x51: "Q",
0x52: "R",
0x53: "S",
0x54: "T",
0x55: "U",
0x56: "V",
0x57: "W",
0x58: "X",
0x59: "Y",
0x5a: "Z",
// Windows keys
0x5b: "LWin",
0x5c: "RWin",
0x5d: "Apps",
0x5f: "Sleep",
// Numpad
0x60: "Numpad0",
0x61: "Numpad1",
0x62: "Numpad2",
0x63: "Numpad3",
0x64: "Numpad4",
0x65: "Numpad5",
0x66: "Numpad6",
0x67: "Numpad7",
0x68: "Numpad8",
0x69: "Numpad9",
0x6a: "Multiply",
0x6b: "Add",
0x6c: "Separator",
0x6d: "Subtract",
0x6e: "Decimal",
0x6f: "Divide",
// Function keys (F1-F24)
0x70: "F1",
0x71: "F2",
0x72: "F3",
0x73: "F4",
0x74: "F5",
0x75: "F6",
0x76: "F7",
0x77: "F8",
0x78: "F9",
0x79: "F10",
0x7a: "F11",
0x7b: "F12",
0x7c: "F13",
0x7d: "F14",
0x7e: "F15",
0x7f: "F16",
0x80: "F17",
0x81: "F18",
0x82: "F19",
0x83: "F20",
0x84: "F21",
0x85: "F22",
0x86: "F23",
0x87: "F24",
// Other keys
0x90: "NumLock",
0x91: "ScrollLock",
0xa0: "LShift",
0xa1: "RShift",
0xa2: "LCtrl",
0xa3: "RCtrl",
0xa4: "LAlt",
0xa5: "RAlt",
// Browser control keys
0xa6: "BrowserBack",
0xa7: "BrowserForward",
0xa8: "BrowserRefresh",
0xa9: "BrowserStop",
0xaa: "BrowserSearch",
0xab: "BrowserFavorites",
0xac: "BrowserHome",
// Volume control keys
0xad: "VolumeMute",
0xae: "VolumeDown",
0xaf: "VolumeUp",
// Media control keys
0xb0: "MediaNextTrack",
0xb1: "MediaPrevTrack",
0xb2: "MediaStop",
0xb3: "MediaPlayPause",
// Launch keys
0xb4: "LaunchMail",
0xb5: "LaunchMediaSelect",
0xb6: "LaunchApp1",
0xb7: "LaunchApp2",
// OEM keys (punctuation and symbols)
0xba: ";", // OEM_1
0xbb: "=", // OEM_PLUS
0xbc: ",", // OEM_COMMA
0xbd: "-", // OEM_MINUS
0xbe: ".", // OEM_PERIOD
0xbf: "/", // OEM_2
0xc0: "`", // OEM_3
0xdb: "[", // OEM_4
0xdc: "\\", // OEM_5
0xdd: "]", // OEM_6
0xde: "'", // OEM_7
0xdf: "OEM_8",
// Additional keys
0xe1: "OEM_AX",
0xe2: "OEM_102",
0xe3: "ICOHelp",
0xe4: "ICO00",
0xe5: "ProcessKey",
0xe6: "ICOClear",
0xe7: "Packet",
};
// Export the appropriate mapping based on platform
export const keycodeToKey: Record<number, string> = isWindows()
? windowsVKToKey
: macOSKeycodeToKey;
export function getKeyFromKeycode(keycode: number): string | undefined {
return keycodeToKey[keycode];
// Use the appropriate mapping based on platform
const mapping = isWindows() ? windowsVKToKey : macOSKeycodeToKey;
return mapping[keycode];
}
export function matchesShortcutKey(
@ -91,17 +287,46 @@ export function matchesShortcutKey(
): boolean {
if (keycode === undefined) return false;
const mappedKey = keycodeToKey[keycode];
const mappedKey = getKeyFromKeycode(keycode);
if (!mappedKey) return false;
return mappedKey.toUpperCase() === keyName.toUpperCase();
// Normalize Windows modifier key names for comparison
const normalizedMappedKey = normalizeKeyName(mappedKey);
const normalizedKeyName = normalizeKeyName(keyName);
return normalizedMappedKey.toUpperCase() === normalizedKeyName.toUpperCase();
}
export function getKeyNameFromPayload(payload: any): string | undefined {
export function getKeyNameFromPayload(payload: {
key?: string;
keyCode?: number;
}): string | undefined {
// Try to get key name from various sources
if (payload.key) return payload.key;
if (payload.keyCode !== undefined && keycodeToKey[payload.keyCode]) {
return keycodeToKey[payload.keyCode];
if (payload.keyCode !== undefined) {
const keyName = getKeyFromKeycode(payload.keyCode);
if (keyName) {
// Normalize key names for consistency across platforms
return normalizeKeyName(keyName);
}
}
return undefined;
}
// Helper function to normalize key names across platforms
function normalizeKeyName(keyName: string): string {
// Normalize left/right variants to single names
const normalizations: Record<string, string> = {
LWin: "Win",
RWin: "Win",
LShift: "Shift",
RShift: "Shift",
LCtrl: "Ctrl",
RCtrl: "Ctrl",
LAlt: "Alt",
RAlt: "Alt",
// Keep other keys as-is
};
return normalizations[keyName] || keyName;
}