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.
Files changed (213) hide show
  1. package/.claude/settings.local.json +77 -0
  2. package/.node-version +1 -0
  3. package/CLAUDE.md +435 -0
  4. package/README.md +404 -0
  5. package/TODO.md +655 -0
  6. package/dist/index.cjs +25108 -0
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +25085 -0
  9. package/docs/building-libretro-cores-arm-mac.md +237 -0
  10. package/docs/config-file-format.md +488 -0
  11. package/docs/cores-trd.md +425 -0
  12. package/docs/headless-hardware-rendering-trd.md +676 -0
  13. package/docs/libretro-cores-trd.md +997 -0
  14. package/docs/mupen64-software-rendering-trd.md +751 -0
  15. package/docs/n64-support-trd.md +306 -0
  16. package/docs/native-rendering-trd.md +540 -0
  17. package/docs/native-ui-rendering-trd.md +1195 -0
  18. package/docs/netplay-trd.md +665 -0
  19. package/docs/retroarch-netplay-docs.md +277 -0
  20. package/docs/save-state-format.md +666 -0
  21. package/eslint.config.js +111 -0
  22. package/icon/icon.png +0 -0
  23. package/icon/icon.pxd +0 -0
  24. package/package.json +63 -0
  25. package/pnpm-workspace.yaml +10 -0
  26. package/src/Emulator/consts.ts +14 -0
  27. package/src/Emulator/index.ts +2496 -0
  28. package/src/Emulator/saveState/index.ts +155 -0
  29. package/src/Emulator/screenshot/index.ts +160 -0
  30. package/src/Emulator/terminalDimensions/index.ts +79 -0
  31. package/src/Emulator/types.ts +83 -0
  32. package/src/cli/commands/consts.ts +10 -0
  33. package/src/cli/commands/index.ts +462 -0
  34. package/src/cli/parseArgs/consts.ts +17 -0
  35. package/src/cli/parseArgs/index.ts +457 -0
  36. package/src/cli/parseArgs/types.ts +61 -0
  37. package/src/cli/runEmulator/index.ts +406 -0
  38. package/src/cli/runEmulator/types.ts +7 -0
  39. package/src/consts.ts +19 -0
  40. package/src/core/button/consts.ts +35 -0
  41. package/src/core/button/index.ts +123 -0
  42. package/src/core/core.ts +300 -0
  43. package/src/core/index.ts +19 -0
  44. package/src/cores/libretro/api/index.ts +334 -0
  45. package/src/cores/libretro/api/types.ts +148 -0
  46. package/src/cores/libretro/callbacks/consts.ts +41 -0
  47. package/src/cores/libretro/callbacks/index.ts +456 -0
  48. package/src/cores/libretro/consts.ts +45 -0
  49. package/src/cores/libretro/coreOptions/consts.ts +36 -0
  50. package/src/cores/libretro/coreOptions/index.ts +222 -0
  51. package/src/cores/libretro/environment/consts.ts +118 -0
  52. package/src/cores/libretro/environment/index.ts +1095 -0
  53. package/src/cores/libretro/index.ts +937 -0
  54. package/src/cores/libretro/loader/index.ts +496 -0
  55. package/src/cores/libretro/pixelFormat/consts.ts +43 -0
  56. package/src/cores/libretro/pixelFormat/index.ts +397 -0
  57. package/src/cores/libretro/types.ts +339 -0
  58. package/src/frontend/AudioManager/index.ts +420 -0
  59. package/src/frontend/SettingsManager/index.ts +250 -0
  60. package/src/frontend/config/index.ts +608 -0
  61. package/src/frontend/config/tests.ts +354 -0
  62. package/src/frontend/config/types.ts +36 -0
  63. package/src/frontend/consts.ts +114 -0
  64. package/src/frontend/coreBuilder/index.ts +644 -0
  65. package/src/frontend/coreBuilder/types.ts +15 -0
  66. package/src/frontend/coreDownloader/index.ts +620 -0
  67. package/src/frontend/coreDownloader/types.ts +17 -0
  68. package/src/frontend/corePreferences/index.ts +69 -0
  69. package/src/frontend/coreRegistry/index.ts +276 -0
  70. package/src/frontend/directoryCache/index.ts +75 -0
  71. package/src/frontend/index.ts +79 -0
  72. package/src/frontend/notifications/consts.ts +14 -0
  73. package/src/frontend/notifications/index.ts +250 -0
  74. package/src/frontend/playlist/consts.ts +168 -0
  75. package/src/frontend/playlist/index.ts +899 -0
  76. package/src/frontend/playlist/labelFormatter/consts.ts +15 -0
  77. package/src/frontend/playlist/labelFormatter/index.ts +155 -0
  78. package/src/frontend/playlist/labelFormatter/tests.ts +153 -0
  79. package/src/frontend/playlist/reader/index.ts +559 -0
  80. package/src/frontend/playlist/sync/index.ts +511 -0
  81. package/src/frontend/playlist/systemLookup/index.ts +233 -0
  82. package/src/frontend/playlist/utils/index.ts +50 -0
  83. package/src/frontend/romScanner/consts.ts +348 -0
  84. package/src/frontend/romScanner/index.ts +1957 -0
  85. package/src/frontend/saveServices/consts.ts +2 -0
  86. package/src/frontend/saveServices/index.ts +313 -0
  87. package/src/frontend/serviceProvider/index.ts +108 -0
  88. package/src/frontend/serviceProvider/types.ts +13 -0
  89. package/src/index.ts +428 -0
  90. package/src/input/Controller/consts.ts +50 -0
  91. package/src/input/Controller/index.ts +81 -0
  92. package/src/input/GamepadManager/consts.ts +22 -0
  93. package/src/input/GamepadManager/index.ts +418 -0
  94. package/src/input/InputManager/consts.ts +86 -0
  95. package/src/input/InputManager/index.ts +593 -0
  96. package/src/input/InputMapper/consts.ts +33 -0
  97. package/src/input/InputMapper/index.ts +436 -0
  98. package/src/input/consts.ts +410 -0
  99. package/src/input/gamepadProfiles/index.ts +1100 -0
  100. package/src/input/index.ts +38 -0
  101. package/src/input/inputUtils/index.ts +31 -0
  102. package/src/netplay/FrameBuffer/consts.ts +2 -0
  103. package/src/netplay/FrameBuffer/index.ts +364 -0
  104. package/src/netplay/FrameBuffer/tests.ts +286 -0
  105. package/src/netplay/InputBuffer/consts.ts +7 -0
  106. package/src/netplay/InputBuffer/index.ts +347 -0
  107. package/src/netplay/InputBuffer/tests.ts +166 -0
  108. package/src/netplay/NetplayClient/index.ts +976 -0
  109. package/src/netplay/NetplayConnection/index.ts +536 -0
  110. package/src/netplay/NetplayDiscovery/consts.ts +41 -0
  111. package/src/netplay/NetplayDiscovery/index.ts +525 -0
  112. package/src/netplay/NetplayServer/index.ts +1407 -0
  113. package/src/netplay/SyncManager/index.ts +984 -0
  114. package/src/netplay/SyncManager/tests.ts +419 -0
  115. package/src/netplay/consts.ts +371 -0
  116. package/src/netplay/crc32/consts.ts +14 -0
  117. package/src/netplay/crc32/index.ts +97 -0
  118. package/src/netplay/crc32/tests.ts +40 -0
  119. package/src/netplay/index.ts +41 -0
  120. package/src/netplay/netplayLogger/consts.ts +30 -0
  121. package/src/netplay/netplayLogger/index.ts +345 -0
  122. package/src/netplay/protocol/consts.ts +86 -0
  123. package/src/netplay/protocol/index.ts +1280 -0
  124. package/src/netplay/protocol/tests.ts +606 -0
  125. package/src/netplay/protocol/types.ts +20 -0
  126. package/src/netplay/types.ts +395 -0
  127. package/src/rendering/KittyRenderer/index.ts +616 -0
  128. package/src/rendering/NativeRenderer/index.ts +279 -0
  129. package/src/rendering/NativeRenderer/tests.ts +133 -0
  130. package/src/rendering/TerminalRenderer/index.ts +770 -0
  131. package/src/rendering/consts.ts +401 -0
  132. package/src/rendering/fonts/CozetteVector.ttf +0 -0
  133. package/src/rendering/index.ts +26 -0
  134. package/src/rendering/nativeUi/NativeWindowManager/index.ts +158 -0
  135. package/src/rendering/nativeUi/NativeWindowManager/tests.ts +81 -0
  136. package/src/rendering/nativeUi/consts.ts +6 -0
  137. package/src/rendering/nativeUi/index.ts +20 -0
  138. package/src/rendering/postProcessing/consts.ts +38 -0
  139. package/src/rendering/postProcessing/index.ts +923 -0
  140. package/src/rendering/shared/ansi/consts.ts +12 -0
  141. package/src/rendering/shared/ansi/index.ts +104 -0
  142. package/src/rendering/shared/consts.ts +2 -0
  143. package/src/rendering/shared/fitToTerminal/index.ts +67 -0
  144. package/src/ui/AddRomsPrompt/consts.ts +13 -0
  145. package/src/ui/AddRomsPrompt/index.tsx +781 -0
  146. package/src/ui/App/consts.ts +2 -0
  147. package/src/ui/App/index.tsx +456 -0
  148. package/src/ui/AppCapabilities/index.tsx +67 -0
  149. package/src/ui/ConfigContext/index.tsx +56 -0
  150. package/src/ui/CoreManager/consts.ts +11 -0
  151. package/src/ui/CoreManager/index.tsx +779 -0
  152. package/src/ui/CoreSelector/consts.ts +2 -0
  153. package/src/ui/CoreSelector/index.tsx +251 -0
  154. package/src/ui/DialogContainer/index.tsx +42 -0
  155. package/src/ui/DialogOptionsList/index.tsx +61 -0
  156. package/src/ui/DuplicateCrcPrompt/consts.ts +5 -0
  157. package/src/ui/DuplicateCrcPrompt/index.tsx +146 -0
  158. package/src/ui/GamepadContext/consts.ts +15 -0
  159. package/src/ui/GamepadContext/index.tsx +295 -0
  160. package/src/ui/NativeDialog/index.tsx +120 -0
  161. package/src/ui/NetplayDisconnectedDialog/index.tsx +93 -0
  162. package/src/ui/NetplayPauseMenu/consts.ts +2 -0
  163. package/src/ui/NetplayPauseMenu/index.tsx +97 -0
  164. package/src/ui/RomBrowser/NetplayPanel/consts.ts +24 -0
  165. package/src/ui/RomBrowser/NetplayPanel/index.tsx +520 -0
  166. package/src/ui/RomBrowser/SettingsPanel/index.tsx +478 -0
  167. package/src/ui/RomBrowser/consts.ts +61 -0
  168. package/src/ui/RomBrowser/index.tsx +1164 -0
  169. package/src/ui/RomBrowser/settingsConfig/index.ts +320 -0
  170. package/src/ui/RomBrowser/types.ts +67 -0
  171. package/src/ui/SaveStateDialog/consts.ts +2 -0
  172. package/src/ui/SaveStateDialog/index.tsx +225 -0
  173. package/src/ui/WarningDialog/index.tsx +113 -0
  174. package/src/ui/consts.ts +27 -0
  175. package/src/ui/hooks/useClearTerminal/consts.ts +2 -0
  176. package/src/ui/hooks/useClearTerminal/index.ts +37 -0
  177. package/src/ui/hooks/useDialogNavigation/index.ts +99 -0
  178. package/src/ui/hooks/useGamepad/consts.ts +21 -0
  179. package/src/ui/hooks/useGamepad/index.ts +194 -0
  180. package/src/ui/index.ts +27 -0
  181. package/src/utils/buffer/consts.ts +17 -0
  182. package/src/utils/buffer/index.ts +129 -0
  183. package/src/utils/color/consts.ts +58 -0
  184. package/src/utils/color/index.ts +183 -0
  185. package/src/utils/compression/consts.ts +50 -0
  186. package/src/utils/compression/index.ts +101 -0
  187. package/src/utils/consts.ts +2 -0
  188. package/src/utils/crc32/consts.ts +22 -0
  189. package/src/utils/crc32/index.ts +83 -0
  190. package/src/utils/ensureDirectory/index.ts +10 -0
  191. package/src/utils/format/consts.ts +8 -0
  192. package/src/utils/format/index.ts +53 -0
  193. package/src/utils/getErrorMessage/index.ts +10 -0
  194. package/src/utils/index.ts +113 -0
  195. package/src/utils/ini/index.ts +200 -0
  196. package/src/utils/kitty/consts.ts +13 -0
  197. package/src/utils/kitty/index.ts +181 -0
  198. package/src/utils/logger/consts.ts +35 -0
  199. package/src/utils/logger/index.ts +217 -0
  200. package/src/utils/paths/consts.ts +18 -0
  201. package/src/utils/paths/index.ts +151 -0
  202. package/src/utils/png/consts.ts +34 -0
  203. package/src/utils/png/index.ts +131 -0
  204. package/src/utils/readJsonFile/index.ts +16 -0
  205. package/src/utils/rotateLogFile/index.ts +44 -0
  206. package/src/utils/safeClose/index.ts +10 -0
  207. package/src/utils/terminal/consts.ts +8 -0
  208. package/src/utils/terminal/index.ts +102 -0
  209. package/src/utils/thumbnailRenderer/consts.ts +2 -0
  210. package/src/utils/thumbnailRenderer/index.ts +147 -0
  211. package/src/utils/typedError/index.ts +26 -0
  212. package/tsconfig.json +31 -0
  213. 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
+ };