@phystack/hub-device 4.4.44 → 4.4.46
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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/sysinfo/node.d.ts +2 -0
- package/dist/sysinfo/node.d.ts.map +1 -1
- package/dist/sysinfo/node.js +645 -4
- package/dist/sysinfo/node.js.map +1 -1
- package/dist/types/twin.types.d.ts +43 -0
- package/dist/types/twin.types.d.ts.map +1 -1
- package/dist/types/twin.types.js +7 -1
- package/dist/types/twin.types.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +1 -1
- package/src/sysinfo/node.ts +883 -11
- package/src/types/twin.types.ts +74 -26
package/dist/sysinfo/node.js
CHANGED
|
@@ -3,8 +3,646 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.discoverPulseAudioSocket = discoverPulseAudioSocket;
|
|
7
|
+
exports.getPulseAudioEnv = getPulseAudioEnv;
|
|
6
8
|
exports.getSystemInformation = getSystemInformation;
|
|
7
9
|
const systeminformation_1 = __importDefault(require("systeminformation"));
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const twin_types_1 = require("../types/twin.types");
|
|
12
|
+
const PULSEAUDIO_COMMANDS = {
|
|
13
|
+
INFO: 'pactl info',
|
|
14
|
+
KILL: 'pulseaudio -k 2>/dev/null || true',
|
|
15
|
+
START_DAEMON: 'pulseaudio --daemonize --exit-idle-time=-1 --system=false',
|
|
16
|
+
WAIT: 'sleep 1',
|
|
17
|
+
LIST_MODULES: 'pactl list modules short',
|
|
18
|
+
LOAD_ALSA_MODULE: 'pactl load-module module-alsa-card',
|
|
19
|
+
LIST_SINKS: 'pactl list sinks',
|
|
20
|
+
LIST_SOURCES: 'pactl list sources',
|
|
21
|
+
LIST_CARDS: 'pactl list cards',
|
|
22
|
+
LIST_CARDS_SHORT: 'pactl list cards short',
|
|
23
|
+
LOAD_ALSA_SINK: (hwDevice, sinkName, description) => `pactl load-module module-alsa-sink device=${hwDevice} sink_name=${sinkName} sink_properties="device.description=${description}"`,
|
|
24
|
+
LOAD_ALSA_SOURCE: (hwDevice, sourceName, description) => `pactl load-module module-alsa-source device=${hwDevice} source_name=${sourceName} source_properties="device.description=${description}"`,
|
|
25
|
+
};
|
|
26
|
+
const ALSA_COMMANDS = {
|
|
27
|
+
APLAY_LIST: 'aplay -l 2>/dev/null',
|
|
28
|
+
ARECORD_LIST: 'arecord -l 2>/dev/null',
|
|
29
|
+
};
|
|
30
|
+
const PULSEAUDIO_TIMEOUTS = {
|
|
31
|
+
INFO_CHECK: 2000,
|
|
32
|
+
KILL: 2000,
|
|
33
|
+
START: 5000,
|
|
34
|
+
LIST_MODULES: 2000,
|
|
35
|
+
LOAD_MODULE: 3000,
|
|
36
|
+
LIST_DEVICES: 5000,
|
|
37
|
+
};
|
|
38
|
+
let pulseAudioServerPath;
|
|
39
|
+
function parseAudioDevice(block, isInput) {
|
|
40
|
+
const lines = block.split('\n');
|
|
41
|
+
const device = {};
|
|
42
|
+
const ports = [];
|
|
43
|
+
let inPortsSection = false;
|
|
44
|
+
for (let i = 0; i < lines.length; i++) {
|
|
45
|
+
const line = lines[i];
|
|
46
|
+
const trimmed = line.trim();
|
|
47
|
+
if (trimmed.startsWith('Name:')) {
|
|
48
|
+
device.identifier = trimmed.split('Name:')[1].trim();
|
|
49
|
+
}
|
|
50
|
+
else if (trimmed.startsWith('Description:')) {
|
|
51
|
+
device.displayName = trimmed.split('Description:')[1].trim();
|
|
52
|
+
}
|
|
53
|
+
else if (trimmed.includes('alsa.card =')) {
|
|
54
|
+
const cardMatch = trimmed.match(/alsa\.card\s*=\s*"(\d+)"/);
|
|
55
|
+
if (cardMatch) {
|
|
56
|
+
device.cardNumber = cardMatch[1];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else if (trimmed.includes('alsa.card_name =')) {
|
|
60
|
+
const cardNameMatch = trimmed.match(/alsa\.card_name\s*=\s*"([^"]+)"/);
|
|
61
|
+
if (cardNameMatch) {
|
|
62
|
+
device.cardNameRaw = cardNameMatch[1];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else if (trimmed.startsWith('Volume:')) {
|
|
66
|
+
const match = trimmed.match(/(\d+)%/);
|
|
67
|
+
if (match)
|
|
68
|
+
device.volume = parseInt(match[1], 10);
|
|
69
|
+
}
|
|
70
|
+
else if (trimmed.startsWith('Mute:')) {
|
|
71
|
+
device.muted = trimmed.toLowerCase().includes('yes');
|
|
72
|
+
}
|
|
73
|
+
else if (trimmed.startsWith('Active Port:')) {
|
|
74
|
+
device.activePort = trimmed.split('Active Port:')[1].trim();
|
|
75
|
+
console.log(`[Audio] Found active port: ${device.activePort}`);
|
|
76
|
+
}
|
|
77
|
+
else if (trimmed.startsWith('Ports:')) {
|
|
78
|
+
inPortsSection = true;
|
|
79
|
+
console.log(`[Audio] Entering ports section for device`);
|
|
80
|
+
}
|
|
81
|
+
else if (inPortsSection) {
|
|
82
|
+
if (trimmed.match(/^[A-Z][a-z]+:/) && !line.match(/^\s/)) {
|
|
83
|
+
inPortsSection = false;
|
|
84
|
+
console.log(`[Audio] Exiting ports section, found ${ports.length} ports`);
|
|
85
|
+
}
|
|
86
|
+
else if (trimmed.length > 0 && line.match(/^\s+\S/)) {
|
|
87
|
+
const portMatch = trimmed.match(/^([^:]+):\s*(.+?)(?:\s*\((.+)\))?$/);
|
|
88
|
+
if (portMatch) {
|
|
89
|
+
const portName = portMatch[1].trim();
|
|
90
|
+
const portDescription = portMatch[2].trim();
|
|
91
|
+
const portDetails = portMatch[3];
|
|
92
|
+
const port = {
|
|
93
|
+
name: portName,
|
|
94
|
+
description: portDescription,
|
|
95
|
+
};
|
|
96
|
+
if (portDetails) {
|
|
97
|
+
const typeMatch = portDetails.match(/type:\s*([^,]+)/);
|
|
98
|
+
const priorityMatch = portDetails.match(/priority:\s*(\d+)/);
|
|
99
|
+
const availableMatch = portDetails.match(/available(?::\s*(yes|no))?|not available|availability unknown/i);
|
|
100
|
+
if (typeMatch)
|
|
101
|
+
port.type = typeMatch[1].trim();
|
|
102
|
+
if (priorityMatch)
|
|
103
|
+
port.priority = parseInt(priorityMatch[1], 10);
|
|
104
|
+
if (availableMatch) {
|
|
105
|
+
const availStr = availableMatch[0].toLowerCase();
|
|
106
|
+
if (availStr.includes('availability unknown')) {
|
|
107
|
+
port.availabilityStatus = twin_types_1.AvailabilityStatus.Unknown;
|
|
108
|
+
}
|
|
109
|
+
else if (availStr.includes('not available') || availStr.includes('available: no')) {
|
|
110
|
+
port.availabilityStatus = twin_types_1.AvailabilityStatus.Unavailable;
|
|
111
|
+
}
|
|
112
|
+
else if (availStr.includes('available: yes')) {
|
|
113
|
+
port.availabilityStatus = twin_types_1.AvailabilityStatus.Available;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
console.log(`[Audio] Parsed port: ${portName} - ${portDescription}`);
|
|
118
|
+
ports.push(port);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else if (trimmed.includes('device.product.name =')) {
|
|
123
|
+
const match = trimmed.match(/"([^"]+)"/);
|
|
124
|
+
if (match) {
|
|
125
|
+
device.productName = match[1];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else if (trimmed.includes('device.form_factor =')) {
|
|
129
|
+
const match = trimmed.match(/"([^"]+)"/);
|
|
130
|
+
if (match)
|
|
131
|
+
device.formFactor = match[1];
|
|
132
|
+
}
|
|
133
|
+
else if (trimmed.includes('device.bus =')) {
|
|
134
|
+
const match = trimmed.match(/"([^"]+)"/);
|
|
135
|
+
if (match)
|
|
136
|
+
device.bus = match[1];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (isInput && device.identifier?.includes('.monitor')) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
if (!device.identifier)
|
|
143
|
+
return null;
|
|
144
|
+
if (ports.length > 0) {
|
|
145
|
+
device.availablePorts = ports;
|
|
146
|
+
console.log(`[Audio] Device ${device.identifier} has ${ports.length} ports, active: ${device.activePort}`);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
console.log(`[Audio] Device ${device.identifier} has NO ports detected`);
|
|
150
|
+
}
|
|
151
|
+
const availablePorts = ports.filter(p => p.availabilityStatus === twin_types_1.AvailabilityStatus.Available);
|
|
152
|
+
let deviceAvailable = availablePorts.length > 0;
|
|
153
|
+
if (ports.length === 0 && device.identifier) {
|
|
154
|
+
const stateMatch = block.match(/State:\s*(\w+)/i);
|
|
155
|
+
if (stateMatch) {
|
|
156
|
+
const state = stateMatch[1].toUpperCase();
|
|
157
|
+
if (state === 'RUNNING') {
|
|
158
|
+
deviceAvailable = true;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
try {
|
|
162
|
+
const env = getPulseAudioEnv();
|
|
163
|
+
const cardsOutput = (0, child_process_1.execSync)(PULSEAUDIO_COMMANDS.LIST_CARDS, {
|
|
164
|
+
encoding: 'utf-8',
|
|
165
|
+
timeout: 2000,
|
|
166
|
+
stdio: 'pipe',
|
|
167
|
+
env,
|
|
168
|
+
});
|
|
169
|
+
const deviceMatch = device.identifier.match(/[^_]+_(\d+)_(\d+)$/);
|
|
170
|
+
if (deviceMatch) {
|
|
171
|
+
const deviceNum = deviceMatch[2];
|
|
172
|
+
const portMatch = cardsOutput.match(new RegExp(`hdmi-output-${deviceNum === '9' ? '2' : deviceNum === '8' ? '1' : deviceNum === '7' ? '1' : '0'}[^\\n]*available`, 'i'));
|
|
173
|
+
if (portMatch && !portMatch[0].includes('not available')) {
|
|
174
|
+
deviceAvailable = true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
let cardMatch = device.identifier?.match(/^([^_]+)_(output|input|sink|source)\.([^.]+(?:\.[^.]+)*)\.(.+)$/);
|
|
184
|
+
if (cardMatch) {
|
|
185
|
+
const prefix = cardMatch[1];
|
|
186
|
+
const cardId = cardMatch[3];
|
|
187
|
+
device.cardName = `${prefix}_card.${cardId}`;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
cardMatch = device.identifier?.match(/^([^_]+)_(output|input|sink|source)\.(.+)$/);
|
|
191
|
+
if (cardMatch) {
|
|
192
|
+
const prefix = cardMatch[1];
|
|
193
|
+
const cardId = cardMatch[3];
|
|
194
|
+
device.cardName = `${prefix}_card.${cardId}`;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
const altMatch = device.identifier?.match(/^([^_]+)_(output|input|sink|source)_(\d+)_(\d+)$/);
|
|
198
|
+
if (altMatch) {
|
|
199
|
+
const cardNumber = altMatch[3];
|
|
200
|
+
try {
|
|
201
|
+
const env = getPulseAudioEnv();
|
|
202
|
+
const cardsOutput = (0, child_process_1.execSync)(PULSEAUDIO_COMMANDS.LIST_CARDS_SHORT, {
|
|
203
|
+
encoding: 'utf-8',
|
|
204
|
+
timeout: 2000,
|
|
205
|
+
stdio: 'pipe',
|
|
206
|
+
env,
|
|
207
|
+
});
|
|
208
|
+
const cardLine = cardsOutput.split('\n').find(line => line.includes(`card${cardNumber}`));
|
|
209
|
+
if (cardLine) {
|
|
210
|
+
const cardNameMatch = cardLine.match(/([^_\s]+_card\.[^\s]+)/);
|
|
211
|
+
if (cardNameMatch) {
|
|
212
|
+
device.cardName = cardNameMatch[1];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
const cardNumber = device.cardNumber;
|
|
221
|
+
if (cardNumber !== undefined) {
|
|
222
|
+
try {
|
|
223
|
+
const env = getPulseAudioEnv();
|
|
224
|
+
const cardsOutput = (0, child_process_1.execSync)(PULSEAUDIO_COMMANDS.LIST_CARDS_SHORT, {
|
|
225
|
+
encoding: 'utf-8',
|
|
226
|
+
timeout: 2000,
|
|
227
|
+
stdio: 'pipe',
|
|
228
|
+
env,
|
|
229
|
+
});
|
|
230
|
+
const cardLine = cardsOutput
|
|
231
|
+
.split('\n')
|
|
232
|
+
.find(line => line.includes(`card${cardNumber}`));
|
|
233
|
+
if (cardLine) {
|
|
234
|
+
const cardNameMatch = cardLine.match(/([^_\s]+_card\.[^\s]+)/);
|
|
235
|
+
if (cardNameMatch) {
|
|
236
|
+
device.cardName = cardNameMatch[1];
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
device.label = createDeviceLabel(device, ports, isInput, device.formFactor, device.bus, false, deviceAvailable, device.activePort);
|
|
247
|
+
const cleanDevice = {
|
|
248
|
+
identifier: device.identifier,
|
|
249
|
+
volume: device.volume,
|
|
250
|
+
muted: device.muted,
|
|
251
|
+
availablePorts: device.availablePorts,
|
|
252
|
+
label: device.label,
|
|
253
|
+
};
|
|
254
|
+
return cleanDevice;
|
|
255
|
+
}
|
|
256
|
+
function createDeviceLabel(device, ports, isInput, formFactor, bus, isDefault, available, activePort) {
|
|
257
|
+
const productName = device.productName;
|
|
258
|
+
let baseLabel = '';
|
|
259
|
+
const activePortInfo = activePort ? ports.find(p => p.name === activePort) : undefined;
|
|
260
|
+
if (productName) {
|
|
261
|
+
baseLabel = `${productName} (${isInput ? 'Microphone' : 'Speaker'})`;
|
|
262
|
+
}
|
|
263
|
+
else if (activePortInfo && activePortInfo.description) {
|
|
264
|
+
const portDesc = activePortInfo.description;
|
|
265
|
+
if (portDesc.toLowerCase().includes('headphones')) {
|
|
266
|
+
baseLabel = 'Headphones';
|
|
267
|
+
}
|
|
268
|
+
else if (portDesc.toLowerCase().includes('hdmi') ||
|
|
269
|
+
portDesc.toLowerCase().includes('displayport')) {
|
|
270
|
+
baseLabel = `HDMI Display`;
|
|
271
|
+
}
|
|
272
|
+
else if (portDesc.toLowerCase().includes('microphone') ||
|
|
273
|
+
portDesc.toLowerCase().includes('mic')) {
|
|
274
|
+
baseLabel = 'Microphone';
|
|
275
|
+
}
|
|
276
|
+
else if (portDesc.toLowerCase().includes('line')) {
|
|
277
|
+
baseLabel = `Line ${isInput ? 'In' : 'Out'}`;
|
|
278
|
+
}
|
|
279
|
+
else if (portDesc.toLowerCase().includes('speaker')) {
|
|
280
|
+
baseLabel = 'Speakers';
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
baseLabel = `${portDesc} (${isInput ? 'Input' : 'Output'})`;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
else if (device.displayName) {
|
|
287
|
+
baseLabel = `${device.displayName} (${isInput ? 'Input' : 'Output'})`;
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
baseLabel = device.identifier || 'Unknown Audio Device';
|
|
291
|
+
}
|
|
292
|
+
if (device.displayName &&
|
|
293
|
+
baseLabel !== device.displayName &&
|
|
294
|
+
!baseLabel.includes(device.displayName)) {
|
|
295
|
+
baseLabel = `${device.displayName} - ${baseLabel}`;
|
|
296
|
+
}
|
|
297
|
+
if (formFactor && formFactor !== 'internal' && !baseLabel.includes(formFactor)) {
|
|
298
|
+
baseLabel = `${baseLabel} (${formFactor.charAt(0).toUpperCase() + formFactor.slice(1)})`;
|
|
299
|
+
}
|
|
300
|
+
if (bus && bus.toLowerCase() !== 'pci' && !baseLabel.includes(bus.toUpperCase())) {
|
|
301
|
+
baseLabel = `${baseLabel} (${bus.toUpperCase()})`;
|
|
302
|
+
}
|
|
303
|
+
const statusParts = [];
|
|
304
|
+
if (isDefault) {
|
|
305
|
+
statusParts.push('Default');
|
|
306
|
+
}
|
|
307
|
+
if (available !== undefined) {
|
|
308
|
+
statusParts.push(available ? 'Available' : 'Unavailable');
|
|
309
|
+
}
|
|
310
|
+
return statusParts.length > 0 ? `${baseLabel} (${statusParts.join(', ')})` : baseLabel;
|
|
311
|
+
}
|
|
312
|
+
function discoverPulseAudioSocket() {
|
|
313
|
+
try {
|
|
314
|
+
const findOutput = (0, child_process_1.execSync)('find /tmp -maxdepth 2 -type d -name "pulse-*" 2>/dev/null | head -1', {
|
|
315
|
+
encoding: 'utf-8',
|
|
316
|
+
timeout: 2000,
|
|
317
|
+
stdio: 'pipe',
|
|
318
|
+
}).trim();
|
|
319
|
+
if (findOutput) {
|
|
320
|
+
const socketPath = `${findOutput}/native`;
|
|
321
|
+
try {
|
|
322
|
+
(0, child_process_1.execSync)(`test -S "${socketPath}"`, { stdio: 'pipe' });
|
|
323
|
+
return socketPath;
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
}
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
function getPulseAudioEnv() {
|
|
335
|
+
if (!pulseAudioServerPath) {
|
|
336
|
+
pulseAudioServerPath = discoverPulseAudioSocket();
|
|
337
|
+
if (pulseAudioServerPath) {
|
|
338
|
+
console.log(`Discovered PulseAudio socket: ${pulseAudioServerPath}`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const env = { ...process.env };
|
|
342
|
+
if (pulseAudioServerPath) {
|
|
343
|
+
env.PULSE_SERVER = `unix:${pulseAudioServerPath}`;
|
|
344
|
+
}
|
|
345
|
+
return env;
|
|
346
|
+
}
|
|
347
|
+
function startPulseAudio() {
|
|
348
|
+
if (!pulseAudioServerPath) {
|
|
349
|
+
pulseAudioServerPath = discoverPulseAudioSocket();
|
|
350
|
+
if (pulseAudioServerPath) {
|
|
351
|
+
console.log(`Discovered existing PulseAudio socket: ${pulseAudioServerPath}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
const env = getPulseAudioEnv();
|
|
356
|
+
(0, child_process_1.execSync)(PULSEAUDIO_COMMANDS.INFO, {
|
|
357
|
+
encoding: 'utf-8',
|
|
358
|
+
timeout: PULSEAUDIO_TIMEOUTS.INFO_CHECK,
|
|
359
|
+
stdio: 'pipe',
|
|
360
|
+
env,
|
|
361
|
+
});
|
|
362
|
+
console.log('PulseAudio is running and accessible');
|
|
363
|
+
loadAlsaDevicesIntoPulseAudio();
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
368
|
+
console.log('PulseAudio not accessible:', errorMsg);
|
|
369
|
+
console.log('Attempting to start PulseAudio...');
|
|
370
|
+
}
|
|
371
|
+
try {
|
|
372
|
+
(0, child_process_1.execSync)(PULSEAUDIO_COMMANDS.KILL, {
|
|
373
|
+
timeout: PULSEAUDIO_TIMEOUTS.KILL,
|
|
374
|
+
stdio: 'pipe',
|
|
375
|
+
});
|
|
376
|
+
(0, child_process_1.execSync)(PULSEAUDIO_COMMANDS.WAIT, { stdio: 'pipe' });
|
|
377
|
+
(0, child_process_1.execSync)(PULSEAUDIO_COMMANDS.START_DAEMON, {
|
|
378
|
+
timeout: PULSEAUDIO_TIMEOUTS.START,
|
|
379
|
+
stdio: 'pipe',
|
|
380
|
+
});
|
|
381
|
+
(0, child_process_1.execSync)(PULSEAUDIO_COMMANDS.WAIT, { stdio: 'pipe' });
|
|
382
|
+
pulseAudioServerPath = discoverPulseAudioSocket();
|
|
383
|
+
if (pulseAudioServerPath) {
|
|
384
|
+
console.log(`Discovered PulseAudio socket: ${pulseAudioServerPath}`);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
console.log('Warning: Could not discover PulseAudio socket path');
|
|
388
|
+
}
|
|
389
|
+
const env = getPulseAudioEnv();
|
|
390
|
+
(0, child_process_1.execSync)(PULSEAUDIO_COMMANDS.INFO, {
|
|
391
|
+
timeout: PULSEAUDIO_TIMEOUTS.INFO_CHECK,
|
|
392
|
+
stdio: 'pipe',
|
|
393
|
+
env,
|
|
394
|
+
});
|
|
395
|
+
console.log('✓ PulseAudio started successfully');
|
|
396
|
+
loadAlsaDevicesIntoPulseAudio();
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
console.error('✗ Failed to start PulseAudio:', error instanceof Error ? error.message : String(error));
|
|
401
|
+
console.error(' Hint: Make sure no other PulseAudio instances are running as different users');
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function parseAlsaDevices(aplayOutput) {
|
|
406
|
+
const devices = [];
|
|
407
|
+
const regex = /card (\d+): ([^,]+), device (\d+): ([^\[]+)\[([^\]]+)\]/g;
|
|
408
|
+
let match;
|
|
409
|
+
while ((match = regex.exec(aplayOutput)) !== null) {
|
|
410
|
+
const card = parseInt(match[1], 10);
|
|
411
|
+
const device = parseInt(match[3], 10);
|
|
412
|
+
const name = match[5].trim();
|
|
413
|
+
devices.push({
|
|
414
|
+
card,
|
|
415
|
+
device,
|
|
416
|
+
name,
|
|
417
|
+
hwString: `hw:${card},${device}`,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
return devices;
|
|
421
|
+
}
|
|
422
|
+
function checkExistingSink(hwString, isInput) {
|
|
423
|
+
try {
|
|
424
|
+
const command = isInput ? PULSEAUDIO_COMMANDS.LIST_SOURCES : PULSEAUDIO_COMMANDS.LIST_SINKS;
|
|
425
|
+
const env = getPulseAudioEnv();
|
|
426
|
+
const output = (0, child_process_1.execSync)(command, {
|
|
427
|
+
encoding: 'utf-8',
|
|
428
|
+
timeout: 3000,
|
|
429
|
+
env,
|
|
430
|
+
});
|
|
431
|
+
const [card, device] = hwString.replace('hw:', '').split(',');
|
|
432
|
+
const devicePattern = new RegExp(`alsa\\.device\\s*=\\s*"${device}"`, 'i');
|
|
433
|
+
const cardPattern = new RegExp(`alsa\\.card\\s*=\\s*"${card}"`, 'i');
|
|
434
|
+
const blocks = output.split(/^(Sink|Source) #/m);
|
|
435
|
+
for (const block of blocks) {
|
|
436
|
+
if (cardPattern.test(block) && devicePattern.test(block)) {
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function createPulseAudioSink(alsaDevice, isInput) {
|
|
447
|
+
try {
|
|
448
|
+
const sinkName = isInput
|
|
449
|
+
? `alsa_input_${alsaDevice.card}_${alsaDevice.device}`
|
|
450
|
+
: `alsa_output_${alsaDevice.card}_${alsaDevice.device}`;
|
|
451
|
+
const safeDescription = `${alsaDevice.name}_${alsaDevice.hwString}`.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
452
|
+
const command = isInput
|
|
453
|
+
? PULSEAUDIO_COMMANDS.LOAD_ALSA_SOURCE(alsaDevice.hwString, sinkName, safeDescription)
|
|
454
|
+
: PULSEAUDIO_COMMANDS.LOAD_ALSA_SINK(alsaDevice.hwString, sinkName, safeDescription);
|
|
455
|
+
console.log(` Creating ${isInput ? 'source' : 'sink'} for ${alsaDevice.hwString}: ${alsaDevice.name}`);
|
|
456
|
+
const env = getPulseAudioEnv();
|
|
457
|
+
(0, child_process_1.execSync)(command, {
|
|
458
|
+
timeout: 5000,
|
|
459
|
+
stdio: 'pipe',
|
|
460
|
+
env,
|
|
461
|
+
});
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
console.log(` Failed to create ${isInput ? 'source' : 'sink'} for ${alsaDevice.hwString}:`, error instanceof Error ? error.message : String(error));
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
function loadAlsaDevicesIntoPulseAudio() {
|
|
470
|
+
try {
|
|
471
|
+
console.log('Detecting ALSA hardware and ensuring PulseAudio coverage...');
|
|
472
|
+
try {
|
|
473
|
+
const env = getPulseAudioEnv();
|
|
474
|
+
const modules = (0, child_process_1.execSync)(PULSEAUDIO_COMMANDS.LIST_MODULES, {
|
|
475
|
+
encoding: 'utf-8',
|
|
476
|
+
timeout: PULSEAUDIO_TIMEOUTS.LIST_MODULES,
|
|
477
|
+
env,
|
|
478
|
+
});
|
|
479
|
+
if (!modules.includes('module-alsa-card')) {
|
|
480
|
+
console.log(' Loading ALSA card module for auto-detection...');
|
|
481
|
+
(0, child_process_1.execSync)(PULSEAUDIO_COMMANDS.LOAD_ALSA_MODULE, {
|
|
482
|
+
timeout: PULSEAUDIO_TIMEOUTS.LOAD_MODULE,
|
|
483
|
+
stdio: 'pipe',
|
|
484
|
+
env,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
console.log(' Note: Base ALSA module load failed (will create sinks manually)');
|
|
490
|
+
}
|
|
491
|
+
let outputDevices = [];
|
|
492
|
+
let inputDevices = [];
|
|
493
|
+
try {
|
|
494
|
+
const aplayOutput = (0, child_process_1.execSync)(ALSA_COMMANDS.APLAY_LIST, {
|
|
495
|
+
encoding: 'utf-8',
|
|
496
|
+
timeout: 3000,
|
|
497
|
+
});
|
|
498
|
+
outputDevices = parseAlsaDevices(aplayOutput);
|
|
499
|
+
console.log(` Found ${outputDevices.length} ALSA output device(s)`);
|
|
500
|
+
}
|
|
501
|
+
catch (error) {
|
|
502
|
+
console.log(' No ALSA output devices found or aplay failed');
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
const arecordOutput = (0, child_process_1.execSync)(ALSA_COMMANDS.ARECORD_LIST, {
|
|
506
|
+
encoding: 'utf-8',
|
|
507
|
+
timeout: 3000,
|
|
508
|
+
});
|
|
509
|
+
inputDevices = parseAlsaDevices(arecordOutput);
|
|
510
|
+
console.log(` Found ${inputDevices.length} ALSA input device(s)`);
|
|
511
|
+
}
|
|
512
|
+
catch (error) {
|
|
513
|
+
console.log(' No ALSA input devices found or arecord failed');
|
|
514
|
+
}
|
|
515
|
+
let createdCount = 0;
|
|
516
|
+
for (const device of outputDevices) {
|
|
517
|
+
if (!checkExistingSink(device.hwString, false)) {
|
|
518
|
+
if (createPulseAudioSink(device, false)) {
|
|
519
|
+
createdCount++;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
console.log(` ✓ Sink already exists for ${device.hwString}: ${device.name}`);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
for (const device of inputDevices) {
|
|
527
|
+
if (!checkExistingSink(device.hwString, true)) {
|
|
528
|
+
if (createPulseAudioSink(device, true)) {
|
|
529
|
+
createdCount++;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
console.log(` ✓ Source already exists for ${device.hwString}: ${device.name}`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (createdCount > 0) {
|
|
537
|
+
console.log(`✓ Created ${createdCount} PulseAudio sink(s)/source(s) for missing hardware`);
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
console.log('✓ All ALSA devices already have corresponding PulseAudio sinks/sources');
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
console.log('Note: Could not complete ALSA device loading:', error instanceof Error ? error.message : String(error));
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
function getDefaultDevices() {
|
|
548
|
+
try {
|
|
549
|
+
const env = getPulseAudioEnv();
|
|
550
|
+
const infoOutput = (0, child_process_1.execSync)(PULSEAUDIO_COMMANDS.INFO, {
|
|
551
|
+
encoding: 'utf-8',
|
|
552
|
+
timeout: PULSEAUDIO_TIMEOUTS.INFO_CHECK,
|
|
553
|
+
env,
|
|
554
|
+
});
|
|
555
|
+
const defaultSinkMatch = infoOutput.match(/Default Sink: (.+)/);
|
|
556
|
+
const defaultSourceMatch = infoOutput.match(/Default Source: (.+)/);
|
|
557
|
+
return {
|
|
558
|
+
defaultSink: defaultSinkMatch?.[1]?.trim(),
|
|
559
|
+
defaultSource: defaultSourceMatch?.[1]?.trim(),
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
catch (error) {
|
|
563
|
+
console.error('Failed to get default devices:', error);
|
|
564
|
+
return {};
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function getAudioDevices() {
|
|
568
|
+
if (!startPulseAudio()) {
|
|
569
|
+
console.error('Audio detection failed: PulseAudio unavailable');
|
|
570
|
+
return undefined;
|
|
571
|
+
}
|
|
572
|
+
const { defaultSink, defaultSource } = getDefaultDevices();
|
|
573
|
+
console.log(`Default sink: ${defaultSink}, Default source: ${defaultSource}`);
|
|
574
|
+
const outputs = [];
|
|
575
|
+
const inputs = [];
|
|
576
|
+
try {
|
|
577
|
+
const env = getPulseAudioEnv();
|
|
578
|
+
const sinksOutput = (0, child_process_1.execSync)(PULSEAUDIO_COMMANDS.LIST_SINKS, {
|
|
579
|
+
encoding: 'utf-8',
|
|
580
|
+
timeout: PULSEAUDIO_TIMEOUTS.LIST_DEVICES,
|
|
581
|
+
env,
|
|
582
|
+
});
|
|
583
|
+
sinksOutput
|
|
584
|
+
.split(/^Sink #/m)
|
|
585
|
+
.slice(1)
|
|
586
|
+
.forEach(block => {
|
|
587
|
+
const device = parseAudioDevice(block, false);
|
|
588
|
+
if (device) {
|
|
589
|
+
const isDefault = device.identifier === defaultSink;
|
|
590
|
+
if (isDefault && device.label) {
|
|
591
|
+
if (!device.label.includes('Default')) {
|
|
592
|
+
if (device.label.includes('(')) {
|
|
593
|
+
device.label = device.label.replace(')', ', Default)');
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
device.label = `${device.label} (Default)`;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
outputs.push(device);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
console.log(`Detected ${outputs.length} audio output device(s) via PulseAudio`);
|
|
604
|
+
}
|
|
605
|
+
catch (error) {
|
|
606
|
+
console.error('Failed to detect audio outputs:', error instanceof Error ? error.message : String(error));
|
|
607
|
+
}
|
|
608
|
+
try {
|
|
609
|
+
const env = getPulseAudioEnv();
|
|
610
|
+
const sourcesOutput = (0, child_process_1.execSync)(PULSEAUDIO_COMMANDS.LIST_SOURCES, {
|
|
611
|
+
encoding: 'utf-8',
|
|
612
|
+
timeout: PULSEAUDIO_TIMEOUTS.LIST_DEVICES,
|
|
613
|
+
env,
|
|
614
|
+
});
|
|
615
|
+
sourcesOutput
|
|
616
|
+
.split(/^Source #/m)
|
|
617
|
+
.slice(1)
|
|
618
|
+
.forEach(block => {
|
|
619
|
+
const device = parseAudioDevice(block, true);
|
|
620
|
+
if (device) {
|
|
621
|
+
const isDefault = device.identifier === defaultSource;
|
|
622
|
+
if (isDefault && device.label) {
|
|
623
|
+
if (!device.label.includes('Default')) {
|
|
624
|
+
if (device.label.includes('(')) {
|
|
625
|
+
device.label = device.label.replace(')', ', Default)');
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
device.label = `${device.label} (Default)`;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
inputs.push(device);
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
console.log(`Detected ${inputs.length} audio input device(s) via PulseAudio`);
|
|
636
|
+
}
|
|
637
|
+
catch (error) {
|
|
638
|
+
console.error('Failed to detect audio inputs:', error instanceof Error ? error.message : String(error));
|
|
639
|
+
}
|
|
640
|
+
if (outputs.length === 0 && inputs.length === 0) {
|
|
641
|
+
console.log('No audio devices detected');
|
|
642
|
+
return undefined;
|
|
643
|
+
}
|
|
644
|
+
return { outputs, inputs };
|
|
645
|
+
}
|
|
8
646
|
async function getSystemInformation() {
|
|
9
647
|
const valueObject = {
|
|
10
648
|
baseboard: '*',
|
|
@@ -40,12 +678,15 @@ async function getSystemInformation() {
|
|
|
40
678
|
netIfaces = netIfaces.map((iface) => ({
|
|
41
679
|
...iface,
|
|
42
680
|
type: iface.type ||
|
|
43
|
-
(iface.ifaceName &&
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
: "unknown"),
|
|
681
|
+
(iface.ifaceName && (iface.ifaceName.startsWith('en') || iface.ifaceName.startsWith('eth'))
|
|
682
|
+
? 'wired'
|
|
683
|
+
: 'unknown'),
|
|
47
684
|
}));
|
|
48
685
|
info.networkInterfaces = netIfaces;
|
|
686
|
+
const audioDevices = getAudioDevices();
|
|
687
|
+
if (audioDevices) {
|
|
688
|
+
info.audioDevices = audioDevices;
|
|
689
|
+
}
|
|
49
690
|
const systemInfo = JSON.parse(JSON.stringify(info));
|
|
50
691
|
return systemInfo;
|
|
51
692
|
}
|