emoemu 0.1.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/.claude/settings.local.json +77 -0
- package/.node-version +1 -0
- package/CLAUDE.md +435 -0
- package/README.md +404 -0
- package/TODO.md +655 -0
- package/dist/index.cjs +25108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +25085 -0
- package/docs/building-libretro-cores-arm-mac.md +237 -0
- package/docs/config-file-format.md +488 -0
- package/docs/cores-trd.md +425 -0
- package/docs/headless-hardware-rendering-trd.md +676 -0
- package/docs/libretro-cores-trd.md +997 -0
- package/docs/mupen64-software-rendering-trd.md +751 -0
- package/docs/n64-support-trd.md +306 -0
- package/docs/native-rendering-trd.md +540 -0
- package/docs/native-ui-rendering-trd.md +1195 -0
- package/docs/netplay-trd.md +665 -0
- package/docs/retroarch-netplay-docs.md +277 -0
- package/docs/save-state-format.md +666 -0
- package/eslint.config.js +111 -0
- package/icon/icon.png +0 -0
- package/icon/icon.pxd +0 -0
- package/package.json +63 -0
- package/pnpm-workspace.yaml +10 -0
- package/src/Emulator/consts.ts +14 -0
- package/src/Emulator/index.ts +2496 -0
- package/src/Emulator/saveState/index.ts +155 -0
- package/src/Emulator/screenshot/index.ts +160 -0
- package/src/Emulator/terminalDimensions/index.ts +79 -0
- package/src/Emulator/types.ts +83 -0
- package/src/cli/commands/consts.ts +10 -0
- package/src/cli/commands/index.ts +462 -0
- package/src/cli/parseArgs/consts.ts +17 -0
- package/src/cli/parseArgs/index.ts +457 -0
- package/src/cli/parseArgs/types.ts +61 -0
- package/src/cli/runEmulator/index.ts +406 -0
- package/src/cli/runEmulator/types.ts +7 -0
- package/src/consts.ts +19 -0
- package/src/core/button/consts.ts +35 -0
- package/src/core/button/index.ts +123 -0
- package/src/core/core.ts +300 -0
- package/src/core/index.ts +19 -0
- package/src/cores/libretro/api/index.ts +334 -0
- package/src/cores/libretro/api/types.ts +148 -0
- package/src/cores/libretro/callbacks/consts.ts +41 -0
- package/src/cores/libretro/callbacks/index.ts +456 -0
- package/src/cores/libretro/consts.ts +45 -0
- package/src/cores/libretro/coreOptions/consts.ts +36 -0
- package/src/cores/libretro/coreOptions/index.ts +222 -0
- package/src/cores/libretro/environment/consts.ts +118 -0
- package/src/cores/libretro/environment/index.ts +1095 -0
- package/src/cores/libretro/index.ts +937 -0
- package/src/cores/libretro/loader/index.ts +496 -0
- package/src/cores/libretro/pixelFormat/consts.ts +43 -0
- package/src/cores/libretro/pixelFormat/index.ts +397 -0
- package/src/cores/libretro/types.ts +339 -0
- package/src/frontend/AudioManager/index.ts +420 -0
- package/src/frontend/SettingsManager/index.ts +250 -0
- package/src/frontend/config/index.ts +608 -0
- package/src/frontend/config/tests.ts +354 -0
- package/src/frontend/config/types.ts +36 -0
- package/src/frontend/consts.ts +114 -0
- package/src/frontend/coreBuilder/index.ts +644 -0
- package/src/frontend/coreBuilder/types.ts +15 -0
- package/src/frontend/coreDownloader/index.ts +620 -0
- package/src/frontend/coreDownloader/types.ts +17 -0
- package/src/frontend/corePreferences/index.ts +69 -0
- package/src/frontend/coreRegistry/index.ts +276 -0
- package/src/frontend/directoryCache/index.ts +75 -0
- package/src/frontend/index.ts +79 -0
- package/src/frontend/notifications/consts.ts +14 -0
- package/src/frontend/notifications/index.ts +250 -0
- package/src/frontend/playlist/consts.ts +168 -0
- package/src/frontend/playlist/index.ts +899 -0
- package/src/frontend/playlist/labelFormatter/consts.ts +15 -0
- package/src/frontend/playlist/labelFormatter/index.ts +155 -0
- package/src/frontend/playlist/labelFormatter/tests.ts +153 -0
- package/src/frontend/playlist/reader/index.ts +559 -0
- package/src/frontend/playlist/sync/index.ts +511 -0
- package/src/frontend/playlist/systemLookup/index.ts +233 -0
- package/src/frontend/playlist/utils/index.ts +50 -0
- package/src/frontend/romScanner/consts.ts +348 -0
- package/src/frontend/romScanner/index.ts +1957 -0
- package/src/frontend/saveServices/consts.ts +2 -0
- package/src/frontend/saveServices/index.ts +313 -0
- package/src/frontend/serviceProvider/index.ts +108 -0
- package/src/frontend/serviceProvider/types.ts +13 -0
- package/src/index.ts +428 -0
- package/src/input/Controller/consts.ts +50 -0
- package/src/input/Controller/index.ts +81 -0
- package/src/input/GamepadManager/consts.ts +22 -0
- package/src/input/GamepadManager/index.ts +418 -0
- package/src/input/InputManager/consts.ts +86 -0
- package/src/input/InputManager/index.ts +593 -0
- package/src/input/InputMapper/consts.ts +33 -0
- package/src/input/InputMapper/index.ts +436 -0
- package/src/input/consts.ts +410 -0
- package/src/input/gamepadProfiles/index.ts +1100 -0
- package/src/input/index.ts +38 -0
- package/src/input/inputUtils/index.ts +31 -0
- package/src/netplay/FrameBuffer/consts.ts +2 -0
- package/src/netplay/FrameBuffer/index.ts +364 -0
- package/src/netplay/FrameBuffer/tests.ts +286 -0
- package/src/netplay/InputBuffer/consts.ts +7 -0
- package/src/netplay/InputBuffer/index.ts +347 -0
- package/src/netplay/InputBuffer/tests.ts +166 -0
- package/src/netplay/NetplayClient/index.ts +976 -0
- package/src/netplay/NetplayConnection/index.ts +536 -0
- package/src/netplay/NetplayDiscovery/consts.ts +41 -0
- package/src/netplay/NetplayDiscovery/index.ts +525 -0
- package/src/netplay/NetplayServer/index.ts +1407 -0
- package/src/netplay/SyncManager/index.ts +984 -0
- package/src/netplay/SyncManager/tests.ts +419 -0
- package/src/netplay/consts.ts +371 -0
- package/src/netplay/crc32/consts.ts +14 -0
- package/src/netplay/crc32/index.ts +97 -0
- package/src/netplay/crc32/tests.ts +40 -0
- package/src/netplay/index.ts +41 -0
- package/src/netplay/netplayLogger/consts.ts +30 -0
- package/src/netplay/netplayLogger/index.ts +345 -0
- package/src/netplay/protocol/consts.ts +86 -0
- package/src/netplay/protocol/index.ts +1280 -0
- package/src/netplay/protocol/tests.ts +606 -0
- package/src/netplay/protocol/types.ts +20 -0
- package/src/netplay/types.ts +395 -0
- package/src/rendering/KittyRenderer/index.ts +616 -0
- package/src/rendering/NativeRenderer/index.ts +279 -0
- package/src/rendering/NativeRenderer/tests.ts +133 -0
- package/src/rendering/TerminalRenderer/index.ts +770 -0
- package/src/rendering/consts.ts +401 -0
- package/src/rendering/fonts/CozetteVector.ttf +0 -0
- package/src/rendering/index.ts +26 -0
- package/src/rendering/nativeUi/NativeWindowManager/index.ts +158 -0
- package/src/rendering/nativeUi/NativeWindowManager/tests.ts +81 -0
- package/src/rendering/nativeUi/consts.ts +6 -0
- package/src/rendering/nativeUi/index.ts +20 -0
- package/src/rendering/postProcessing/consts.ts +38 -0
- package/src/rendering/postProcessing/index.ts +923 -0
- package/src/rendering/shared/ansi/consts.ts +12 -0
- package/src/rendering/shared/ansi/index.ts +104 -0
- package/src/rendering/shared/consts.ts +2 -0
- package/src/rendering/shared/fitToTerminal/index.ts +67 -0
- package/src/ui/AddRomsPrompt/consts.ts +13 -0
- package/src/ui/AddRomsPrompt/index.tsx +781 -0
- package/src/ui/App/consts.ts +2 -0
- package/src/ui/App/index.tsx +456 -0
- package/src/ui/AppCapabilities/index.tsx +67 -0
- package/src/ui/ConfigContext/index.tsx +56 -0
- package/src/ui/CoreManager/consts.ts +11 -0
- package/src/ui/CoreManager/index.tsx +779 -0
- package/src/ui/CoreSelector/consts.ts +2 -0
- package/src/ui/CoreSelector/index.tsx +251 -0
- package/src/ui/DialogContainer/index.tsx +42 -0
- package/src/ui/DialogOptionsList/index.tsx +61 -0
- package/src/ui/DuplicateCrcPrompt/consts.ts +5 -0
- package/src/ui/DuplicateCrcPrompt/index.tsx +146 -0
- package/src/ui/GamepadContext/consts.ts +15 -0
- package/src/ui/GamepadContext/index.tsx +295 -0
- package/src/ui/NativeDialog/index.tsx +120 -0
- package/src/ui/NetplayDisconnectedDialog/index.tsx +93 -0
- package/src/ui/NetplayPauseMenu/consts.ts +2 -0
- package/src/ui/NetplayPauseMenu/index.tsx +97 -0
- package/src/ui/RomBrowser/NetplayPanel/consts.ts +24 -0
- package/src/ui/RomBrowser/NetplayPanel/index.tsx +520 -0
- package/src/ui/RomBrowser/SettingsPanel/index.tsx +478 -0
- package/src/ui/RomBrowser/consts.ts +61 -0
- package/src/ui/RomBrowser/index.tsx +1164 -0
- package/src/ui/RomBrowser/settingsConfig/index.ts +320 -0
- package/src/ui/RomBrowser/types.ts +67 -0
- package/src/ui/SaveStateDialog/consts.ts +2 -0
- package/src/ui/SaveStateDialog/index.tsx +225 -0
- package/src/ui/WarningDialog/index.tsx +113 -0
- package/src/ui/consts.ts +27 -0
- package/src/ui/hooks/useClearTerminal/consts.ts +2 -0
- package/src/ui/hooks/useClearTerminal/index.ts +37 -0
- package/src/ui/hooks/useDialogNavigation/index.ts +99 -0
- package/src/ui/hooks/useGamepad/consts.ts +21 -0
- package/src/ui/hooks/useGamepad/index.ts +194 -0
- package/src/ui/index.ts +27 -0
- package/src/utils/buffer/consts.ts +17 -0
- package/src/utils/buffer/index.ts +129 -0
- package/src/utils/color/consts.ts +58 -0
- package/src/utils/color/index.ts +183 -0
- package/src/utils/compression/consts.ts +50 -0
- package/src/utils/compression/index.ts +101 -0
- package/src/utils/consts.ts +2 -0
- package/src/utils/crc32/consts.ts +22 -0
- package/src/utils/crc32/index.ts +83 -0
- package/src/utils/ensureDirectory/index.ts +10 -0
- package/src/utils/format/consts.ts +8 -0
- package/src/utils/format/index.ts +53 -0
- package/src/utils/getErrorMessage/index.ts +10 -0
- package/src/utils/index.ts +113 -0
- package/src/utils/ini/index.ts +200 -0
- package/src/utils/kitty/consts.ts +13 -0
- package/src/utils/kitty/index.ts +181 -0
- package/src/utils/logger/consts.ts +35 -0
- package/src/utils/logger/index.ts +217 -0
- package/src/utils/paths/consts.ts +18 -0
- package/src/utils/paths/index.ts +151 -0
- package/src/utils/png/consts.ts +34 -0
- package/src/utils/png/index.ts +131 -0
- package/src/utils/readJsonFile/index.ts +16 -0
- package/src/utils/rotateLogFile/index.ts +44 -0
- package/src/utils/safeClose/index.ts +10 -0
- package/src/utils/terminal/consts.ts +8 -0
- package/src/utils/terminal/index.ts +102 -0
- package/src/utils/thumbnailRenderer/consts.ts +2 -0
- package/src/utils/thumbnailRenderer/index.ts +147 -0
- package/src/utils/typedError/index.ts +26 -0
- package/tsconfig.json +31 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility Module Exports
|
|
3
|
+
*
|
|
4
|
+
* General-purpose utilities used across the application.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Constants
|
|
8
|
+
export * from './consts';
|
|
9
|
+
|
|
10
|
+
// Data format utilities
|
|
11
|
+
export {
|
|
12
|
+
isGzipped,
|
|
13
|
+
isZstd,
|
|
14
|
+
isZlib,
|
|
15
|
+
detectCompressionFormat,
|
|
16
|
+
decompress,
|
|
17
|
+
} from './compression';
|
|
18
|
+
export type { CompressionFormat } from './compression';
|
|
19
|
+
export { formatPlayTime, formatRuntimeSeconds } from './format';
|
|
20
|
+
|
|
21
|
+
// Platform path utilities
|
|
22
|
+
export {
|
|
23
|
+
getConfigDirectory,
|
|
24
|
+
getDefaultConfigPath,
|
|
25
|
+
getConfigPaths,
|
|
26
|
+
} from './paths';
|
|
27
|
+
|
|
28
|
+
// INI file parsing utilities
|
|
29
|
+
export {
|
|
30
|
+
parseIniLine,
|
|
31
|
+
parseIniContent,
|
|
32
|
+
formatIniValue,
|
|
33
|
+
updateIniLine,
|
|
34
|
+
parseIniBool,
|
|
35
|
+
parseIniNumber,
|
|
36
|
+
parseIniInt,
|
|
37
|
+
parseIniNullableNumber,
|
|
38
|
+
} from './ini';
|
|
39
|
+
export type { IniKeyValue, IniValue } from './ini';
|
|
40
|
+
|
|
41
|
+
// Buffer reading utilities
|
|
42
|
+
export {
|
|
43
|
+
readUint16LE,
|
|
44
|
+
readInt16LE,
|
|
45
|
+
applySignedAnalogToDpad,
|
|
46
|
+
analogToDpad,
|
|
47
|
+
hatToDpad,
|
|
48
|
+
applyDpadToButtons,
|
|
49
|
+
} from './buffer';
|
|
50
|
+
export type { DpadState } from './buffer';
|
|
51
|
+
|
|
52
|
+
// Color utilities
|
|
53
|
+
export {
|
|
54
|
+
extractRgb15Components,
|
|
55
|
+
expand5to8,
|
|
56
|
+
rgb15ToRgb24,
|
|
57
|
+
calculateLuminance,
|
|
58
|
+
calculateLuminance8,
|
|
59
|
+
rgb15ToLuminance,
|
|
60
|
+
rgbToGrayscale,
|
|
61
|
+
findClosestEmoji,
|
|
62
|
+
getGrayscaleEmoji,
|
|
63
|
+
rgb15ToEmoji,
|
|
64
|
+
rgb15ToGrayscaleEmoji,
|
|
65
|
+
rgb24ToEmoji,
|
|
66
|
+
rgb24ToGrayscaleEmoji,
|
|
67
|
+
rgbToAnsi256,
|
|
68
|
+
buildGammaLUT,
|
|
69
|
+
colorDistanceSquared,
|
|
70
|
+
EMOJI_COLORS,
|
|
71
|
+
} from './color';
|
|
72
|
+
export type { EmojiColor } from './color';
|
|
73
|
+
|
|
74
|
+
// CRC32 checksum utilities
|
|
75
|
+
export { crc32, calculateFileCrc32 } from './crc32';
|
|
76
|
+
|
|
77
|
+
// PNG encoding utilities
|
|
78
|
+
export {
|
|
79
|
+
PNG_SIGNATURE,
|
|
80
|
+
createPngChunk,
|
|
81
|
+
rgbToIndexed,
|
|
82
|
+
} from './png';
|
|
83
|
+
export type { IndexedResult } from './png';
|
|
84
|
+
|
|
85
|
+
// Kitty graphics protocol utilities
|
|
86
|
+
export {
|
|
87
|
+
buildKittyImageSequence,
|
|
88
|
+
buildKittyDeleteSequence,
|
|
89
|
+
buildCursorPositionSequence,
|
|
90
|
+
HIDE_CURSOR,
|
|
91
|
+
SHOW_CURSOR,
|
|
92
|
+
} from './kitty';
|
|
93
|
+
|
|
94
|
+
// Filesystem utilities
|
|
95
|
+
export { ensureDirectory } from './ensureDirectory';
|
|
96
|
+
|
|
97
|
+
// Error message extraction
|
|
98
|
+
export { getErrorMessage } from './getErrorMessage';
|
|
99
|
+
|
|
100
|
+
// Typed error factory
|
|
101
|
+
export { createTypedError } from './typedError';
|
|
102
|
+
|
|
103
|
+
// Log rotation
|
|
104
|
+
export { rotateLogFile } from './rotateLogFile';
|
|
105
|
+
|
|
106
|
+
// Terminal utilities
|
|
107
|
+
export {
|
|
108
|
+
getTerminalDimensions,
|
|
109
|
+
cleanupStdin,
|
|
110
|
+
exitAlternateScreen,
|
|
111
|
+
cleanupInkInstance,
|
|
112
|
+
pressAnyKeyToContinue,
|
|
113
|
+
} from './terminal';
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* INI File Parser Utilities
|
|
3
|
+
*
|
|
4
|
+
* Generic utilities for parsing and writing INI-style configuration files.
|
|
5
|
+
* Compatible with RetroArch config format.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { pipe, filter, map, isNonNull, fromEntries } from 'remeda';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parsed key-value pair from an INI line
|
|
12
|
+
*/
|
|
13
|
+
export interface IniKeyValue {
|
|
14
|
+
key: string;
|
|
15
|
+
value: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Supported value types for INI files
|
|
20
|
+
*/
|
|
21
|
+
export type IniValue = string | number | boolean | null;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Parse a config file line and extract key-value pair.
|
|
25
|
+
* Format: key = "value" or key = value
|
|
26
|
+
*
|
|
27
|
+
* @param line A single line from the config file
|
|
28
|
+
* @returns Parsed key-value pair, or null for comments/empty lines
|
|
29
|
+
*/
|
|
30
|
+
export const parseIniLine = (line: string): IniKeyValue | null => {
|
|
31
|
+
const trimmed = line.trim();
|
|
32
|
+
|
|
33
|
+
// Skip comments and empty lines
|
|
34
|
+
if (trimmed === '' || trimmed.startsWith('#')) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Match: key = "value" or key = value
|
|
39
|
+
const match = trimmed.match(/^([a-z_][a-z0-9_]*)\s*=\s*(.*)$/i);
|
|
40
|
+
if (!match) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const key = match[1].toLowerCase();
|
|
45
|
+
let value = match[2].trim();
|
|
46
|
+
|
|
47
|
+
// Remove quotes if present
|
|
48
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
49
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
50
|
+
value = value.slice(1, -1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { key, value };
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Parse INI file content into a key-value record.
|
|
58
|
+
* Only includes lines that match the provided valid keys.
|
|
59
|
+
*
|
|
60
|
+
* @param content File content to parse
|
|
61
|
+
* @param validKeys Set of valid keys to include (others are ignored)
|
|
62
|
+
* @returns Record of key-value pairs
|
|
63
|
+
*/
|
|
64
|
+
export const parseIniContent = (
|
|
65
|
+
content: string,
|
|
66
|
+
validKeys: Set<string>
|
|
67
|
+
): Record<string, string> => {
|
|
68
|
+
const entries = pipe(
|
|
69
|
+
content.split('\n'),
|
|
70
|
+
map(parseIniLine),
|
|
71
|
+
filter(isNonNull),
|
|
72
|
+
filter(({ key }) => validKeys.has(key)),
|
|
73
|
+
map(({ key, value }) => [key, value] as const),
|
|
74
|
+
);
|
|
75
|
+
return fromEntries(entries) as Record<string, string>;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Format a value for writing to an INI file.
|
|
80
|
+
*
|
|
81
|
+
* @param value The value to format
|
|
82
|
+
* @returns Formatted string representation
|
|
83
|
+
*/
|
|
84
|
+
export const formatIniValue = (value: IniValue): string => {
|
|
85
|
+
if (value === null) {
|
|
86
|
+
return 'null';
|
|
87
|
+
}
|
|
88
|
+
if (typeof value === 'string') {
|
|
89
|
+
// Always quote strings for consistency
|
|
90
|
+
return `"${value}"`;
|
|
91
|
+
}
|
|
92
|
+
if (typeof value === 'boolean') {
|
|
93
|
+
return value ? 'true' : 'false';
|
|
94
|
+
}
|
|
95
|
+
return String(value);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Update a single setting in INI file content.
|
|
100
|
+
* If the setting exists (commented or not), updates its value.
|
|
101
|
+
* If the setting is commented, uncomments it.
|
|
102
|
+
* If the setting doesn't exist, appends it at the end.
|
|
103
|
+
*
|
|
104
|
+
* @param content Current file content
|
|
105
|
+
* @param key The config key to update
|
|
106
|
+
* @param value The new value (already formatted)
|
|
107
|
+
* @returns Updated file content
|
|
108
|
+
*/
|
|
109
|
+
export const updateIniLine = (content: string, key: string, value: string): string => {
|
|
110
|
+
const lines = content.split('\n');
|
|
111
|
+
|
|
112
|
+
// Pattern to match the key (commented or not): # key = value OR key = value
|
|
113
|
+
const keyPattern = new RegExp(`^(\\s*#\\s*)?${key}\\s*=.*$`, 'i');
|
|
114
|
+
|
|
115
|
+
let found = false;
|
|
116
|
+
const updatedLines = lines.map((line) => {
|
|
117
|
+
if (keyPattern.test(line)) {
|
|
118
|
+
found = true;
|
|
119
|
+
// Replace with uncommented version
|
|
120
|
+
return `${key} = ${value}`;
|
|
121
|
+
}
|
|
122
|
+
return line;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- mutated inside .map() callback
|
|
126
|
+
if (!found) {
|
|
127
|
+
// Append at the end (before final empty line if present)
|
|
128
|
+
const lastLine = updatedLines[updatedLines.length - 1];
|
|
129
|
+
if (lastLine === '') {
|
|
130
|
+
updatedLines.splice(updatedLines.length - 1, 0, `${key} = ${value}`);
|
|
131
|
+
} else {
|
|
132
|
+
updatedLines.push(`${key} = ${value}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return updatedLines.join('\n');
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Comment out a setting in INI file content.
|
|
141
|
+
* If the setting exists (uncommented), comments it out.
|
|
142
|
+
* If the setting is already commented or doesn't exist, leaves content unchanged.
|
|
143
|
+
*
|
|
144
|
+
* @param content Current file content
|
|
145
|
+
* @param key The config key to comment out
|
|
146
|
+
* @returns Updated file content
|
|
147
|
+
*/
|
|
148
|
+
export const commentOutIniLine = (content: string, key: string): string => {
|
|
149
|
+
const lines = content.split('\n');
|
|
150
|
+
|
|
151
|
+
// Pattern to match the uncommented key: key = value (not already commented)
|
|
152
|
+
const keyPattern = new RegExp(`^\\s*${key}\\s*=.*$`, 'i');
|
|
153
|
+
|
|
154
|
+
const updatedLines = lines.map((line) => {
|
|
155
|
+
if (keyPattern.test(line)) {
|
|
156
|
+
// Comment out the line, preserving the value for reference
|
|
157
|
+
return `# ${line.trim()}`;
|
|
158
|
+
}
|
|
159
|
+
return line;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return updatedLines.join('\n');
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Parse a string value into a boolean.
|
|
167
|
+
* Recognizes: true, 1, yes (case-insensitive)
|
|
168
|
+
*/
|
|
169
|
+
export const parseIniBool = (value: string): boolean =>
|
|
170
|
+
value.toLowerCase() === 'true' || value === '1' || value.toLowerCase() === 'yes';
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Parse a string value into a number.
|
|
174
|
+
* Returns the default value if parsing fails.
|
|
175
|
+
*/
|
|
176
|
+
export const parseIniNumber = (value: string, defaultValue: number): number => {
|
|
177
|
+
const num = parseFloat(value);
|
|
178
|
+
return isNaN(num) ? defaultValue : num;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Parse a string value into an integer.
|
|
183
|
+
* Returns the default value if parsing fails.
|
|
184
|
+
*/
|
|
185
|
+
export const parseIniInt = (value: string, defaultValue: number): number => {
|
|
186
|
+
const num = parseInt(value, 10);
|
|
187
|
+
return isNaN(num) ? defaultValue : num;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Parse a nullable number value.
|
|
192
|
+
* Returns null for empty string, "null", or "auto".
|
|
193
|
+
*/
|
|
194
|
+
export const parseIniNullableNumber = (value: string): number | null => {
|
|
195
|
+
if (value === '' || value === 'null' || value === 'auto') {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
const num = parseInt(value, 10);
|
|
199
|
+
return isNaN(num) ? null : num;
|
|
200
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Kitty graphics protocol chunk size for base64 data
|
|
2
|
+
export const KITTY_CHUNK_SIZE = 4096;
|
|
3
|
+
|
|
4
|
+
// Detection timeout in ms
|
|
5
|
+
export const KITTY_GRAPHICS_DETECT_TIMEOUT_MS = 100;
|
|
6
|
+
export const KITTY_GRAPHICS_RESPONSE_CLEAR_DELAY_MS = 10;
|
|
7
|
+
|
|
8
|
+
// Kitty graphics protocol query - request image ID support
|
|
9
|
+
export const KITTY_GRAPHICS_QUERY = '\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\';
|
|
10
|
+
|
|
11
|
+
// Escape sequences for cursor visibility
|
|
12
|
+
export const HIDE_CURSOR = '\x1b[?25l';
|
|
13
|
+
export const SHOW_CURSOR = '\x1b[?25h';
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kitty Graphics Protocol Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities for building Kitty graphics protocol escape sequences
|
|
5
|
+
* for displaying images in terminal dialogs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export * from './consts';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
KITTY_CHUNK_SIZE,
|
|
12
|
+
KITTY_GRAPHICS_DETECT_TIMEOUT_MS,
|
|
13
|
+
KITTY_GRAPHICS_RESPONSE_CLEAR_DELAY_MS,
|
|
14
|
+
KITTY_GRAPHICS_QUERY,
|
|
15
|
+
} from './consts';
|
|
16
|
+
|
|
17
|
+
// Cached detection result (null = not yet detected)
|
|
18
|
+
let kittyGraphicsSupportedCache: boolean | null = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Detect if the terminal supports Kitty graphics protocol.
|
|
22
|
+
*
|
|
23
|
+
* Uses protocol query to detect support, with fast paths for Kitty terminal.
|
|
24
|
+
* Results are cached for subsequent calls.
|
|
25
|
+
*/
|
|
26
|
+
export const detectKittyGraphicsSupport = async (): Promise<boolean> => {
|
|
27
|
+
// Return cached result if available
|
|
28
|
+
if (kittyGraphicsSupportedCache !== null) {
|
|
29
|
+
return kittyGraphicsSupportedCache;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Fast path: KITTY_WINDOW_ID is set by Kitty terminal (always supports its own protocol)
|
|
33
|
+
if (process.env['KITTY_WINDOW_ID']) {
|
|
34
|
+
kittyGraphicsSupportedCache = true;
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Fast path: TERM=xterm-kitty indicates Kitty terminal
|
|
39
|
+
if (process.env['TERM'] === 'xterm-kitty') {
|
|
40
|
+
kittyGraphicsSupportedCache = true;
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Only query if stdin is a TTY
|
|
45
|
+
if (!process.stdin.isTTY) {
|
|
46
|
+
kittyGraphicsSupportedCache = false;
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Query the terminal using Kitty graphics protocol
|
|
51
|
+
// We need to temporarily configure stdin to receive the response
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
let responded = false;
|
|
54
|
+
let responseData = '';
|
|
55
|
+
|
|
56
|
+
// Save current stdin state
|
|
57
|
+
const wasRaw = process.stdin.isRaw;
|
|
58
|
+
const wasPaused = process.stdin.isPaused();
|
|
59
|
+
|
|
60
|
+
// Configure stdin to receive terminal response
|
|
61
|
+
process.stdin.setRawMode(true);
|
|
62
|
+
if (wasPaused) {
|
|
63
|
+
process.stdin.resume();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Restore stdin state helper
|
|
67
|
+
const restoreStdin = () => {
|
|
68
|
+
process.stdin.setRawMode(wasRaw);
|
|
69
|
+
if (wasPaused) {
|
|
70
|
+
process.stdin.pause();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Temporary handler to check for Kitty graphics response
|
|
75
|
+
const checkResponse = (data: Buffer) => {
|
|
76
|
+
const str = data.toString();
|
|
77
|
+
responseData += str;
|
|
78
|
+
|
|
79
|
+
// Kitty graphics protocol responds with: \x1b_G...;\x1b\\
|
|
80
|
+
// We look for the graphics response pattern
|
|
81
|
+
if (responseData.includes('\x1b_G') && responseData.includes('\x1b\\')) {
|
|
82
|
+
responded = true;
|
|
83
|
+
process.stdin.removeListener('data', checkResponse);
|
|
84
|
+
restoreStdin();
|
|
85
|
+
|
|
86
|
+
// Give time for any additional response data to clear
|
|
87
|
+
setTimeout(() => {
|
|
88
|
+
kittyGraphicsSupportedCache = true;
|
|
89
|
+
resolve(true);
|
|
90
|
+
}, KITTY_GRAPHICS_RESPONSE_CLEAR_DELAY_MS);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
process.stdin.on('data', checkResponse);
|
|
95
|
+
|
|
96
|
+
// Send graphics query
|
|
97
|
+
process.stdout.write(KITTY_GRAPHICS_QUERY);
|
|
98
|
+
|
|
99
|
+
// Timeout - no response means not supported
|
|
100
|
+
setTimeout(() => {
|
|
101
|
+
if (!responded) {
|
|
102
|
+
process.stdin.removeListener('data', checkResponse);
|
|
103
|
+
restoreStdin();
|
|
104
|
+
kittyGraphicsSupportedCache = false;
|
|
105
|
+
resolve(false);
|
|
106
|
+
}
|
|
107
|
+
}, KITTY_GRAPHICS_DETECT_TIMEOUT_MS);
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get the cached Kitty graphics support status.
|
|
113
|
+
* Returns null if detection hasn't been run yet.
|
|
114
|
+
*/
|
|
115
|
+
export const getKittyGraphicsSupported = (): boolean | null => kittyGraphicsSupportedCache;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Reset the cached Kitty graphics detection result.
|
|
119
|
+
* Useful for testing or when terminal capabilities might have changed.
|
|
120
|
+
*/
|
|
121
|
+
export const resetKittyGraphicsDetection = (): void => {
|
|
122
|
+
kittyGraphicsSupportedCache = null;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build Kitty graphics protocol escape sequence for displaying an image.
|
|
127
|
+
*
|
|
128
|
+
* @param base64Data Base64-encoded PNG image data
|
|
129
|
+
* @param cols Display width in terminal columns
|
|
130
|
+
* @param rows Display height in terminal rows
|
|
131
|
+
* @param imageId Unique image ID for cleanup
|
|
132
|
+
* @returns Escape sequence string to write to stdout
|
|
133
|
+
*/
|
|
134
|
+
export const buildKittyImageSequence = (
|
|
135
|
+
base64Data: string,
|
|
136
|
+
cols: number,
|
|
137
|
+
rows: number,
|
|
138
|
+
imageId: number
|
|
139
|
+
): string => {
|
|
140
|
+
const chunks: string[] = [];
|
|
141
|
+
|
|
142
|
+
for (let i = 0; i < base64Data.length; i += KITTY_CHUNK_SIZE) {
|
|
143
|
+
const chunk = base64Data.slice(i, i + KITTY_CHUNK_SIZE);
|
|
144
|
+
const isFirst = i === 0;
|
|
145
|
+
const isLast = i + KITTY_CHUNK_SIZE >= base64Data.length;
|
|
146
|
+
|
|
147
|
+
let control: string;
|
|
148
|
+
if (isFirst) {
|
|
149
|
+
// a=T: transmit and display, f=100: PNG format, q=2: suppress response
|
|
150
|
+
// c=cols, r=rows: display size in cells
|
|
151
|
+
// C=1: do not move cursor
|
|
152
|
+
control = `a=T,f=100,i=${imageId},q=2,c=${cols},r=${rows},C=1,m=${isLast ? 0 : 1}`;
|
|
153
|
+
} else {
|
|
154
|
+
control = `m=${isLast ? 0 : 1}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
chunks.push(`\x1b_G${control};${chunk}\x1b\\`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return chunks.join('');
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Build escape sequence to delete a Kitty image by ID.
|
|
165
|
+
*
|
|
166
|
+
* @param imageId The image ID to delete
|
|
167
|
+
* @returns Escape sequence string to write to stdout
|
|
168
|
+
*/
|
|
169
|
+
export const buildKittyDeleteSequence = (imageId: number): string =>
|
|
170
|
+
`\x1b_Ga=d,d=I,i=${imageId},q=2\x1b\\`;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Build escape sequence to position cursor for image rendering.
|
|
174
|
+
*
|
|
175
|
+
* @param row Terminal row (1-indexed)
|
|
176
|
+
* @param col Terminal column (1-indexed)
|
|
177
|
+
* @returns Escape sequence string to write to stdout
|
|
178
|
+
*/
|
|
179
|
+
export const buildCursorPositionSequence = (row: number, col: number): string =>
|
|
180
|
+
`\x1b[${row};${col}H`;
|
|
181
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { LogLevel } from '.';
|
|
2
|
+
|
|
3
|
+
/** Kilobyte in bytes */
|
|
4
|
+
export const KILOBYTE = 1024;
|
|
5
|
+
|
|
6
|
+
/** Megabyte in bytes */
|
|
7
|
+
export const MEGABYTE = KILOBYTE * KILOBYTE;
|
|
8
|
+
|
|
9
|
+
/** Maximum log file size factor (5 MB) */
|
|
10
|
+
export const MAX_LOG_SIZE_MB = 5;
|
|
11
|
+
|
|
12
|
+
/** Maximum log file size before rotation */
|
|
13
|
+
export const MAX_LOG_SIZE_BYTES = MAX_LOG_SIZE_MB * MEGABYTE;
|
|
14
|
+
|
|
15
|
+
/** Number of backup files to keep */
|
|
16
|
+
export const MAX_BACKUP_FILES = 3;
|
|
17
|
+
|
|
18
|
+
/** Padding width for date components */
|
|
19
|
+
export const DATE_PAD_WIDTH = 2;
|
|
20
|
+
|
|
21
|
+
/** Log level priority for filtering */
|
|
22
|
+
export const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
|
|
23
|
+
debug: 0,
|
|
24
|
+
info: 1,
|
|
25
|
+
warn: 2,
|
|
26
|
+
error: 3,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Log level display names (RetroArch style) */
|
|
30
|
+
export const LOG_LEVEL_TAGS: Record<LogLevel, string> = {
|
|
31
|
+
debug: 'DEBUG',
|
|
32
|
+
info: 'INFO',
|
|
33
|
+
warn: 'WARN',
|
|
34
|
+
error: 'ERROR',
|
|
35
|
+
};
|