@phystack/device-phyos 4.4.45 → 4.4.47
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.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/methods/ssh.js +4 -4
- package/dist/methods/ssh.js.map +1 -1
- package/dist/types/twin.types.js +7 -1
- package/dist/types/twin.types.js.map +1 -1
- package/dist/utilities/audio-control.js +919 -0
- package/dist/utilities/audio-control.js.map +1 -0
- package/dist/utilities/docker.js +55 -1
- package/dist/utilities/docker.js.map +1 -1
- package/dist/utilities/instances.js +53 -0
- package/dist/utilities/instances.js.map +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,919 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.applyAudioOutputControlAndReport = applyAudioOutputControlAndReport;
|
|
7
|
+
exports.applyAudioInputControlAndReport = applyAudioInputControlAndReport;
|
|
8
|
+
exports.applyAudioOutputControl = applyAudioOutputControl;
|
|
9
|
+
exports.applyAudioInputControl = applyAudioInputControl;
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const phy_logger_1 = require("@phystack/phy-logger");
|
|
12
|
+
const hub_device_1 = require("@phystack/hub-device");
|
|
13
|
+
const sysinfo_1 = __importDefault(require("@phystack/hub-device/dist/sysinfo"));
|
|
14
|
+
const logger = new phy_logger_1.PhyLogger({
|
|
15
|
+
logToFile: false,
|
|
16
|
+
logToConsole: true,
|
|
17
|
+
includeTrace: true,
|
|
18
|
+
namespace: 'audio-control.ts',
|
|
19
|
+
});
|
|
20
|
+
const PACTL_COMMANDS = {
|
|
21
|
+
SET_DEFAULT_SINK: (deviceId) => `pactl set-default-sink ${deviceId}`,
|
|
22
|
+
SET_DEFAULT_SOURCE: (deviceId) => `pactl set-default-source ${deviceId}`,
|
|
23
|
+
SET_SINK_VOLUME: (deviceId, volumePercent) => `pactl set-sink-volume ${deviceId} ${volumePercent}%`,
|
|
24
|
+
SET_SOURCE_VOLUME: (deviceId, volumePercent) => `pactl set-source-volume ${deviceId} ${volumePercent}%`,
|
|
25
|
+
SET_SINK_MUTE: (deviceId, muteValue) => `pactl set-sink-mute ${deviceId} ${muteValue}`,
|
|
26
|
+
SET_SOURCE_MUTE: (deviceId, muteValue) => `pactl set-source-mute ${deviceId} ${muteValue}`,
|
|
27
|
+
SET_SINK_PORT: (deviceId, portName) => `pactl set-sink-port ${deviceId} ${portName}`,
|
|
28
|
+
SET_SOURCE_PORT: (deviceId, portName) => `pactl set-source-port ${deviceId} ${portName}`,
|
|
29
|
+
LIST_SINKS_SHORT: () => `pactl list short sinks`,
|
|
30
|
+
LIST_SOURCES_SHORT: () => `pactl list short sources`,
|
|
31
|
+
LIST_SINK_INPUTS: () => `pactl list short sink-inputs`,
|
|
32
|
+
MOVE_SINK_INPUT: (inputId, sinkId) => `pactl move-sink-input ${inputId} ${sinkId}`,
|
|
33
|
+
CORK_SINK_INPUT: (inputId, uncork) => `pactl cork-sink-input ${inputId} ${uncork ? '0' : '1'}`,
|
|
34
|
+
SUSPEND_SINK: (deviceId, suspend) => `pactl suspend-sink ${deviceId} ${suspend ? '1' : '0'}`,
|
|
35
|
+
LIST_CARDS: () => `pactl list cards`,
|
|
36
|
+
LIST_CARDS_SHORT: () => `pactl list cards short`,
|
|
37
|
+
GET_SINK_INFO: (sinkId) => `pactl list sinks | grep -A 50 "Name: ${sinkId}"`,
|
|
38
|
+
GET_CARD_INFO: (cardName) => `pactl list cards | grep -A 100 "Name: ${cardName}"`,
|
|
39
|
+
SET_CARD_PROFILE: (cardName, profile) => `pactl set-card-profile ${cardName} ${profile}`,
|
|
40
|
+
CHECK_SINK_EXISTS: (identifier) => `pactl list short sinks | grep "^[0-9]\\+\\s\\+${identifier.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`,
|
|
41
|
+
};
|
|
42
|
+
const PACTL_TIMEOUT = 3000;
|
|
43
|
+
function getPulseAudioEnv() {
|
|
44
|
+
return (0, hub_device_1.getPulseAudioEnv)();
|
|
45
|
+
}
|
|
46
|
+
function getCardNameFromSink(sinkId) {
|
|
47
|
+
try {
|
|
48
|
+
const env = getPulseAudioEnv();
|
|
49
|
+
const match = sinkId.match(/^([^_]+)_(?:output|input|sink|source)\.([^.]+(?:\.[^.]+)*)/);
|
|
50
|
+
if (match) {
|
|
51
|
+
const prefix = match[1];
|
|
52
|
+
const cardIdWithProfile = match[2];
|
|
53
|
+
const cardIdMatch = cardIdWithProfile.match(/^([^.]+\.[^.]+\.[^.]*)/);
|
|
54
|
+
const cardId = cardIdMatch ? cardIdMatch[1] : cardIdWithProfile;
|
|
55
|
+
const pciMatch = cardIdWithProfile.match(/^(pci-[^.]+\.[^.]+\.[0-9]+)/);
|
|
56
|
+
if (pciMatch) {
|
|
57
|
+
return `${prefix}_card.${pciMatch[1]}`;
|
|
58
|
+
}
|
|
59
|
+
const usbMatch = cardIdWithProfile.match(/^(usb-[^.]+\.[^.]+\.[^.]+\-[0-9]+)/);
|
|
60
|
+
if (usbMatch) {
|
|
61
|
+
return `${prefix}_card.${usbMatch[1]}`;
|
|
62
|
+
}
|
|
63
|
+
const bluetoothMatch = cardIdWithProfile.match(/^([0-9A-F]{2}_[0-9A-F]{2}_[0-9A-F]{2}_[0-9A-F]{2}_[0-9A-F]{2}_[0-9A-F]{2})/i);
|
|
64
|
+
if (bluetoothMatch) {
|
|
65
|
+
return `${prefix}_card.${bluetoothMatch[1]}`;
|
|
66
|
+
}
|
|
67
|
+
const cardIdWithoutProfile = cardIdWithProfile.replace(/\.(analog-stereo|hdmi-stereo|hdmi-surround|stereo|mono|surround|a2dp|sco|off).*$/i, '');
|
|
68
|
+
if (cardIdWithoutProfile && cardIdWithoutProfile.length > 0) {
|
|
69
|
+
return `${prefix}_card.${cardIdWithoutProfile}`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const altMatch = sinkId.match(/^([^_]+)_(?:output|input|sink|source)_(\d+)_(\d+)$/);
|
|
73
|
+
if (altMatch) {
|
|
74
|
+
const cardNumber = altMatch[2];
|
|
75
|
+
try {
|
|
76
|
+
const sinkInfo = (0, child_process_1.execSync)(PACTL_COMMANDS.GET_SINK_INFO(sinkId), {
|
|
77
|
+
timeout: PACTL_TIMEOUT,
|
|
78
|
+
stdio: 'pipe',
|
|
79
|
+
env,
|
|
80
|
+
}).toString();
|
|
81
|
+
const cardNumMatch = sinkInfo.match(/alsa\.card\s*=\s*"(\d+)"/);
|
|
82
|
+
if (cardNumMatch) {
|
|
83
|
+
const actualCardNumber = cardNumMatch[1];
|
|
84
|
+
const cardsOutput = (0, child_process_1.execSync)(PACTL_COMMANDS.LIST_CARDS_SHORT(), {
|
|
85
|
+
timeout: PACTL_TIMEOUT,
|
|
86
|
+
stdio: 'pipe',
|
|
87
|
+
env,
|
|
88
|
+
}).toString();
|
|
89
|
+
const cardLine = cardsOutput.split('\n').find(line => {
|
|
90
|
+
const parts = line.split('\t');
|
|
91
|
+
return (parts[0] === actualCardNumber || line.match(new RegExp(`^${actualCardNumber}\\s+`)));
|
|
92
|
+
});
|
|
93
|
+
if (cardLine) {
|
|
94
|
+
const parts = cardLine.split('\t');
|
|
95
|
+
if (parts.length >= 2) {
|
|
96
|
+
const cardName = parts[1].trim();
|
|
97
|
+
if (cardName && cardName.includes('_card.')) {
|
|
98
|
+
return cardName;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const cardNameMatch = cardLine.match(/([a-z0-9_]+_card\.[^\s\t]+)/i);
|
|
102
|
+
if (cardNameMatch) {
|
|
103
|
+
return cardNameMatch[1];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
logger.warn(`getCardNameFromSink(): Sink info lookup failed for sink ${sinkId}, card number ${cardNumber}, continuing with fallback:`, error);
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const cardsOutput = (0, child_process_1.execSync)(PACTL_COMMANDS.LIST_CARDS_SHORT(), {
|
|
113
|
+
timeout: PACTL_TIMEOUT,
|
|
114
|
+
stdio: 'pipe',
|
|
115
|
+
env,
|
|
116
|
+
}).toString();
|
|
117
|
+
const cardLine = cardsOutput.split('\n').find(line => {
|
|
118
|
+
const parts = line.split('\t');
|
|
119
|
+
return parts[0] === cardNumber || line.match(new RegExp(`^${cardNumber}\\s+`));
|
|
120
|
+
});
|
|
121
|
+
if (cardLine) {
|
|
122
|
+
const parts = cardLine.split('\t');
|
|
123
|
+
if (parts.length >= 2) {
|
|
124
|
+
const cardName = parts[1].trim();
|
|
125
|
+
if (cardName && cardName.includes('_card.')) {
|
|
126
|
+
return cardName;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const cardNameMatch = cardLine.match(/([a-z0-9_]+_card\.[^\s\t]+)/i);
|
|
130
|
+
if (cardNameMatch) {
|
|
131
|
+
return cardNameMatch[1];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
logger.warn(`getCardNameFromSink(): Card lookup failed for sink ${sinkId}, card number ${cardNumber}:`, error);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
logger.warn(`getCardNameFromSink(): Failed to get card name for sink ${sinkId}:`, error);
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function isAnalogSink(sinkId) {
|
|
147
|
+
try {
|
|
148
|
+
const env = getPulseAudioEnv();
|
|
149
|
+
const sinkInfo = (0, child_process_1.execSync)(PACTL_COMMANDS.GET_SINK_INFO(sinkId), {
|
|
150
|
+
timeout: PACTL_TIMEOUT,
|
|
151
|
+
stdio: 'pipe',
|
|
152
|
+
env,
|
|
153
|
+
}).toString();
|
|
154
|
+
const descriptionMatch = sinkInfo.match(/Description:\s*([^\n]+)/);
|
|
155
|
+
const description = descriptionMatch ? descriptionMatch[1].toLowerCase() : '';
|
|
156
|
+
const hasAnalogInDescription = description.includes('analog') ||
|
|
157
|
+
description.includes('headphone') ||
|
|
158
|
+
description.includes('speaker') ||
|
|
159
|
+
description.includes('built-in') ||
|
|
160
|
+
description.includes('stereo');
|
|
161
|
+
const hasAnalogInId = sinkId.toLowerCase().includes('analog') ||
|
|
162
|
+
sinkId.toLowerCase().includes('headphone') ||
|
|
163
|
+
sinkId.toLowerCase().includes('speaker');
|
|
164
|
+
const hasAnalogInProperties = sinkInfo.includes('alsa.id = "Analog') ||
|
|
165
|
+
sinkInfo.includes('alsa.name = "Analog') ||
|
|
166
|
+
!!sinkInfo.match(/alsa\.id\s*=\s*"Analog/i);
|
|
167
|
+
const isNotHdmi = !isHdmiSink(sinkId);
|
|
168
|
+
return hasAnalogInDescription || hasAnalogInId || hasAnalogInProperties || isNotHdmi;
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
logger.warn(`isAnalogSink(): Failed to check if sink ${sinkId} is analog:`, error);
|
|
172
|
+
return !sinkId.toLowerCase().includes('hdmi');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function getAvailableOutputProfiles(cardInfo) {
|
|
176
|
+
const profiles = [];
|
|
177
|
+
const profilesSection = cardInfo.match(/Profiles?:([\s\S]*?)(?=\s+\w+:|\s*$)/);
|
|
178
|
+
if (profilesSection) {
|
|
179
|
+
const profileLines = profilesSection[1].split('\n');
|
|
180
|
+
for (const line of profileLines) {
|
|
181
|
+
const match = line.match(/^\s+(output:[^:]+):/);
|
|
182
|
+
if (match && match[1]) {
|
|
183
|
+
const profileName = match[1];
|
|
184
|
+
if (!profiles.includes(profileName)) {
|
|
185
|
+
profiles.push(profileName);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
const simpleMatch = line.match(/^\s+(output:[^\s]+)\s/);
|
|
190
|
+
if (simpleMatch && simpleMatch[1] && !profiles.includes(simpleMatch[1])) {
|
|
191
|
+
profiles.push(simpleMatch[1]);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const allLines = cardInfo.split('\n');
|
|
197
|
+
for (const line of allLines) {
|
|
198
|
+
const match = line.match(/^\s+(output:[^:]+):/);
|
|
199
|
+
if (match && match[1]) {
|
|
200
|
+
const profileName = match[1];
|
|
201
|
+
if (!profiles.includes(profileName)) {
|
|
202
|
+
profiles.push(profileName);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
const simpleMatch = line.match(/^\s+(output:[^\s]+)\s/);
|
|
207
|
+
if (simpleMatch && simpleMatch[1] && !profiles.includes(simpleMatch[1])) {
|
|
208
|
+
profiles.push(simpleMatch[1]);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return profiles;
|
|
213
|
+
}
|
|
214
|
+
function getAnalogProfileForCard(cardName) {
|
|
215
|
+
try {
|
|
216
|
+
const env = getPulseAudioEnv();
|
|
217
|
+
const cardInfo = (0, child_process_1.execSync)(PACTL_COMMANDS.GET_CARD_INFO(cardName), {
|
|
218
|
+
timeout: PACTL_TIMEOUT,
|
|
219
|
+
stdio: 'pipe',
|
|
220
|
+
env,
|
|
221
|
+
}).toString();
|
|
222
|
+
const profilesSection = cardInfo.match(/Profiles?:([\s\S]*?)(?=\s+\w+:|\s*$)/);
|
|
223
|
+
if (profilesSection) {
|
|
224
|
+
const stereoMatch = profilesSection[1].match(/^\s+(output:analog-stereo[^\s]*)\s/);
|
|
225
|
+
if (stereoMatch) {
|
|
226
|
+
return stereoMatch[1].trim();
|
|
227
|
+
}
|
|
228
|
+
const analogMatch = profilesSection[1].match(/^\s+(output:analog[^\s]+)\s/);
|
|
229
|
+
if (analogMatch) {
|
|
230
|
+
return analogMatch[1].trim();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const analogProfileMatch = cardInfo.match(/^\s+(output:analog-stereo[^\s]*)$/m);
|
|
234
|
+
if (analogProfileMatch) {
|
|
235
|
+
return analogProfileMatch[1].trim();
|
|
236
|
+
}
|
|
237
|
+
return 'output:analog-stereo';
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
logger.warn(`getAnalogProfileForCard(): Failed to get analog profile for card ${cardName}:`, error);
|
|
241
|
+
return 'output:analog-stereo';
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function getHdmiProfileForCard(cardName) {
|
|
245
|
+
try {
|
|
246
|
+
const env = getPulseAudioEnv();
|
|
247
|
+
const cardInfo = (0, child_process_1.execSync)(PACTL_COMMANDS.GET_CARD_INFO(cardName), {
|
|
248
|
+
timeout: PACTL_TIMEOUT,
|
|
249
|
+
stdio: 'pipe',
|
|
250
|
+
env,
|
|
251
|
+
}).toString();
|
|
252
|
+
const profilesSection = cardInfo.match(/Profiles?:([\s\S]*?)(?=\s+\w+:|\s*$)/);
|
|
253
|
+
if (profilesSection) {
|
|
254
|
+
const stereoMatch = profilesSection[1].match(/^\s+(output:hdmi-stereo[^\s]*)\s/);
|
|
255
|
+
if (stereoMatch) {
|
|
256
|
+
return stereoMatch[1].trim();
|
|
257
|
+
}
|
|
258
|
+
const hdmiMatch = profilesSection[1].match(/^\s+(output:hdmi[^\s]+)\s/);
|
|
259
|
+
if (hdmiMatch) {
|
|
260
|
+
return hdmiMatch[1].trim();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const hdmiProfileMatch = cardInfo.match(/^\s+(output:hdmi-stereo[^\s]*)$/m);
|
|
264
|
+
if (hdmiProfileMatch) {
|
|
265
|
+
return hdmiProfileMatch[1].trim();
|
|
266
|
+
}
|
|
267
|
+
const anyHdmiMatch = cardInfo.match(/^\s+(output:hdmi[^\s]+)$/m);
|
|
268
|
+
if (anyHdmiMatch) {
|
|
269
|
+
return anyHdmiMatch[1].trim();
|
|
270
|
+
}
|
|
271
|
+
return 'output:hdmi-stereo';
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
logger.warn(`getHdmiProfileForCard(): Failed to get HDMI profile for card ${cardName}:`, error);
|
|
275
|
+
return 'output:hdmi-stereo';
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function isHdmiSink(sinkId) {
|
|
279
|
+
try {
|
|
280
|
+
const env = getPulseAudioEnv();
|
|
281
|
+
const sinkInfo = (0, child_process_1.execSync)(PACTL_COMMANDS.GET_SINK_INFO(sinkId), {
|
|
282
|
+
timeout: PACTL_TIMEOUT,
|
|
283
|
+
stdio: 'pipe',
|
|
284
|
+
env,
|
|
285
|
+
}).toString();
|
|
286
|
+
const hasHdmiIndicator = sinkInfo.includes('device.product.name = "HDMI') ||
|
|
287
|
+
sinkInfo.includes('alsa.id = "HDMI') ||
|
|
288
|
+
!!sinkInfo.match(/device\.product\.name\s*=\s*"[^"]*HDMI[^"]*"/i) ||
|
|
289
|
+
!!sinkInfo.match(/alsa\.id\s*=\s*"HDMI/i);
|
|
290
|
+
const descriptionMatch = sinkInfo.match(/Description:\s*([^\n]+)/);
|
|
291
|
+
const description = descriptionMatch ? descriptionMatch[1].toLowerCase() : '';
|
|
292
|
+
const hasHdmiInDescription = description.includes('hdmi') ||
|
|
293
|
+
description.includes('displayport') ||
|
|
294
|
+
description.includes('display') ||
|
|
295
|
+
description.includes('ultrafine') ||
|
|
296
|
+
description.includes('monitor');
|
|
297
|
+
const hasHdmiInId = sinkId.toLowerCase().includes('hdmi');
|
|
298
|
+
return hasHdmiIndicator || hasHdmiInDescription || hasHdmiInId;
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
logger.warn(`isHdmiSink(): Failed to check if sink ${sinkId} is HDMI:`, error);
|
|
302
|
+
return sinkId.toLowerCase().includes('hdmi');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
async function applyAudioOutputControlAndReport(audioOutputControl, twinId, hubDevice, context = 'applyAudioOutputControlAndReport') {
|
|
306
|
+
try {
|
|
307
|
+
logger.info(`${context}: Applying audio output control settings:`, audioOutputControl);
|
|
308
|
+
const success = await applyAudioOutputControl(audioOutputControl);
|
|
309
|
+
if (success) {
|
|
310
|
+
logger.info(`${context}: Audio output control settings applied successfully`);
|
|
311
|
+
logger.info(`${context}: Re-reporting system information with updated audio state...`);
|
|
312
|
+
try {
|
|
313
|
+
const deviceInfo = await (0, sysinfo_1.default)();
|
|
314
|
+
await hubDevice.reportDeviceTwinProperties(twinId, deviceInfo);
|
|
315
|
+
logger.info(`${context}: System information re-reported successfully`);
|
|
316
|
+
}
|
|
317
|
+
catch (reportError) {
|
|
318
|
+
logger.error(`${context}: Failed to re-report system information:`, reportError);
|
|
319
|
+
}
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
logger.error(`${context}: Failed to apply audio output control settings`);
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
logger.error(`${context}: Failed to apply audio output control:`, error);
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async function applyAudioInputControlAndReport(audioInputControl, twinId, hubDevice, context = 'applyAudioInputControlAndReport') {
|
|
333
|
+
try {
|
|
334
|
+
logger.info(`${context}: Applying audio input control settings:`, audioInputControl);
|
|
335
|
+
const success = await applyAudioInputControl(audioInputControl);
|
|
336
|
+
if (success) {
|
|
337
|
+
logger.info(`${context}: Audio input control settings applied successfully`);
|
|
338
|
+
logger.info(`${context}: Re-reporting system information with updated audio state...`);
|
|
339
|
+
try {
|
|
340
|
+
const deviceInfo = await (0, sysinfo_1.default)();
|
|
341
|
+
await hubDevice.reportDeviceTwinProperties(twinId, deviceInfo);
|
|
342
|
+
logger.info(`${context}: System information re-reported successfully`);
|
|
343
|
+
}
|
|
344
|
+
catch (reportError) {
|
|
345
|
+
logger.error(`${context}: Failed to re-report system information:`, reportError);
|
|
346
|
+
}
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
logger.error(`${context}: Failed to apply audio input control settings`);
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
logger.error(`${context}: Failed to apply audio input control:`, error);
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async function applyAudioOutputControl(audioOutputControl) {
|
|
360
|
+
const { identifier, volume, muted, activePort } = audioOutputControl;
|
|
361
|
+
if (!identifier) {
|
|
362
|
+
logger.error('applyAudioOutputControl(): identifier is required');
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
const env = getPulseAudioEnv();
|
|
367
|
+
let sinkId = identifier;
|
|
368
|
+
let sinkExists = false;
|
|
369
|
+
try {
|
|
370
|
+
const sinkCheck = (0, child_process_1.execSync)(PACTL_COMMANDS.CHECK_SINK_EXISTS(identifier), {
|
|
371
|
+
timeout: PACTL_TIMEOUT,
|
|
372
|
+
stdio: 'pipe',
|
|
373
|
+
env,
|
|
374
|
+
}).toString();
|
|
375
|
+
sinkExists = sinkCheck.trim().length > 0;
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
logger.warn(`applyAudioOutputControl(): Failed to check if sink exists for identifier ${identifier}:`, error);
|
|
379
|
+
}
|
|
380
|
+
if (sinkExists) {
|
|
381
|
+
const isSimpleFormat = identifier.match(/^[^_]+_(output|input|sink|source)_\d+_\d+$/);
|
|
382
|
+
if (isSimpleFormat) {
|
|
383
|
+
let sinkInfo = '';
|
|
384
|
+
try {
|
|
385
|
+
sinkInfo = (0, child_process_1.execSync)(PACTL_COMMANDS.GET_SINK_INFO(identifier), {
|
|
386
|
+
timeout: PACTL_TIMEOUT,
|
|
387
|
+
stdio: 'pipe',
|
|
388
|
+
env,
|
|
389
|
+
}).toString();
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
logger.warn(`applyAudioOutputControl(): Could not get sink info for identifier ${identifier}:`, error);
|
|
393
|
+
}
|
|
394
|
+
const descriptionMatch = sinkInfo.match(/Description:\s*([^\n]+)/);
|
|
395
|
+
const description = descriptionMatch ? descriptionMatch[1].toLowerCase() : '';
|
|
396
|
+
const alsaIdMatch = sinkInfo.match(/alsa\.id\s*=\s*"([^"]+)"/);
|
|
397
|
+
const alsaId = alsaIdMatch ? alsaIdMatch[1].toLowerCase() : '';
|
|
398
|
+
let isHdmiSink = false;
|
|
399
|
+
let isAnalogSink = false;
|
|
400
|
+
if (alsaId.includes('hdmi')) {
|
|
401
|
+
isHdmiSink = true;
|
|
402
|
+
}
|
|
403
|
+
else if (alsaId.includes('analog') || alsaId.includes('alc269vb')) {
|
|
404
|
+
isAnalogSink = true;
|
|
405
|
+
}
|
|
406
|
+
else if (description.includes('ultrafine') ||
|
|
407
|
+
description.includes('hdmi') ||
|
|
408
|
+
description.includes('display')) {
|
|
409
|
+
isHdmiSink = true;
|
|
410
|
+
}
|
|
411
|
+
else if (description.includes('analog') ||
|
|
412
|
+
description.includes('headphone') ||
|
|
413
|
+
description.includes('speaker') ||
|
|
414
|
+
description.includes('alc269vb')) {
|
|
415
|
+
isAnalogSink = true;
|
|
416
|
+
}
|
|
417
|
+
else if (identifier.toLowerCase().includes('hdmi')) {
|
|
418
|
+
isHdmiSink = true;
|
|
419
|
+
}
|
|
420
|
+
else if (identifier.toLowerCase().includes('analog')) {
|
|
421
|
+
isAnalogSink = true;
|
|
422
|
+
}
|
|
423
|
+
const cardName = getCardNameFromSink(identifier);
|
|
424
|
+
if (!cardName) {
|
|
425
|
+
logger.warn(`applyAudioOutputControl(): Could not get card name for ${identifier}, skipping profile check`);
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
logger.info(`applyAudioOutputControl(): Got card name: ${cardName}`);
|
|
429
|
+
try {
|
|
430
|
+
const cardInfo = (0, child_process_1.execSync)(PACTL_COMMANDS.GET_CARD_INFO(cardName), {
|
|
431
|
+
timeout: PACTL_TIMEOUT,
|
|
432
|
+
stdio: 'pipe',
|
|
433
|
+
env,
|
|
434
|
+
}).toString();
|
|
435
|
+
let activeProfile = null;
|
|
436
|
+
const activeProfileLineMatch = cardInfo.match(/Active Profile:\s*(output:[^\s\n]+)/i);
|
|
437
|
+
if (activeProfileLineMatch) {
|
|
438
|
+
activeProfile = activeProfileLineMatch[1];
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
const activeProfileMatch = cardInfo.match(/^\s+(output:[^:]+):[^\n]*\*[^\n]*$/m);
|
|
442
|
+
if (activeProfileMatch) {
|
|
443
|
+
activeProfile = activeProfileMatch[1];
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
const activeProfileMatch2 = cardInfo.match(/\s+(output:[^:]+):[^\n]*\*[^\n]*/);
|
|
447
|
+
if (activeProfileMatch2) {
|
|
448
|
+
activeProfile = activeProfileMatch2[1];
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
const availableProfiles = getAvailableOutputProfiles(cardInfo);
|
|
452
|
+
if (availableProfiles.length === 1) {
|
|
453
|
+
activeProfile = availableProfiles[0];
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
logger.info(`applyAudioOutputControl(): Current active profile: ${activeProfile}`);
|
|
459
|
+
let requiredProfile = null;
|
|
460
|
+
if (isHdmiSink && !isAnalogSink) {
|
|
461
|
+
requiredProfile = getHdmiProfileForCard(cardName);
|
|
462
|
+
logger.info(`applyAudioOutputControl(): HDMI sink detected, required profile: ${requiredProfile}`);
|
|
463
|
+
}
|
|
464
|
+
else if (isAnalogSink && !isHdmiSink) {
|
|
465
|
+
requiredProfile = getAnalogProfileForCard(cardName);
|
|
466
|
+
logger.info(`applyAudioOutputControl(): Analog sink detected, required profile: ${requiredProfile}`);
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
logger.warn(`applyAudioOutputControl(): Ambiguous sink type (HDMI: ${isHdmiSink}, Analog: ${isAnalogSink}), cannot determine required profile`);
|
|
470
|
+
}
|
|
471
|
+
if (requiredProfile && activeProfile !== requiredProfile) {
|
|
472
|
+
logger.info(`applyAudioOutputControl(): Profile mismatch! Active: ${activeProfile}, Required: ${requiredProfile}. Switching profile...`);
|
|
473
|
+
(0, child_process_1.execSync)(PACTL_COMMANDS.SET_CARD_PROFILE(cardName, requiredProfile), {
|
|
474
|
+
timeout: PACTL_TIMEOUT,
|
|
475
|
+
stdio: 'pipe',
|
|
476
|
+
env,
|
|
477
|
+
});
|
|
478
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
479
|
+
const sinksOutput = (0, child_process_1.execSync)(PACTL_COMMANDS.LIST_SINKS_SHORT(), {
|
|
480
|
+
timeout: PACTL_TIMEOUT,
|
|
481
|
+
stdio: 'pipe',
|
|
482
|
+
env,
|
|
483
|
+
}).toString();
|
|
484
|
+
const cardId = cardName.replace(/^[^.]*\./, '');
|
|
485
|
+
let foundSink = false;
|
|
486
|
+
const deviceMatch = identifier.match(/[^_]+_(\d+)_(\d+)$/);
|
|
487
|
+
if (deviceMatch) {
|
|
488
|
+
const deviceNum = deviceMatch[2];
|
|
489
|
+
let sinkLines = sinksOutput.split('\n').filter(line => line.includes(cardId));
|
|
490
|
+
if (isHdmiSink) {
|
|
491
|
+
sinkLines = sinkLines.filter(line => line.toLowerCase().includes('hdmi'));
|
|
492
|
+
}
|
|
493
|
+
else if (isAnalogSink) {
|
|
494
|
+
sinkLines = sinkLines.filter(line => line.toLowerCase().includes('analog') || line.toLowerCase().includes('stereo'));
|
|
495
|
+
}
|
|
496
|
+
let sinkLine = sinkLines.find(line => line.includes(`device=${deviceNum}`) ||
|
|
497
|
+
line.includes(`_${deviceMatch[1]}_${deviceNum}`));
|
|
498
|
+
if (!sinkLine && sinkLines.length > 0) {
|
|
499
|
+
sinkLine = sinkLines[0];
|
|
500
|
+
}
|
|
501
|
+
if (sinkLine) {
|
|
502
|
+
sinkId = sinkLine.split('\t')[1];
|
|
503
|
+
logger.info(`applyAudioOutputControl(): Found sink ${sinkId} after profile switch (by device number)`);
|
|
504
|
+
foundSink = true;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (!foundSink) {
|
|
508
|
+
let sinkLines = sinksOutput.split('\n').filter(line => line.includes(cardId));
|
|
509
|
+
if (isHdmiSink) {
|
|
510
|
+
sinkLines = sinkLines.filter(line => line.toLowerCase().includes('hdmi'));
|
|
511
|
+
}
|
|
512
|
+
else if (isAnalogSink) {
|
|
513
|
+
sinkLines = sinkLines.filter(line => line.toLowerCase().includes('analog') || line.toLowerCase().includes('stereo'));
|
|
514
|
+
}
|
|
515
|
+
if (sinkLines.length > 0) {
|
|
516
|
+
sinkId = sinkLines[0].split('\t')[1];
|
|
517
|
+
logger.info(`applyAudioOutputControl(): Found sink ${sinkId} after profile switch (by card and profile type)`);
|
|
518
|
+
foundSink = true;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (!foundSink) {
|
|
522
|
+
try {
|
|
523
|
+
const checkSink = (0, child_process_1.execSync)(PACTL_COMMANDS.CHECK_SINK_EXISTS(identifier), {
|
|
524
|
+
timeout: PACTL_TIMEOUT,
|
|
525
|
+
stdio: 'pipe',
|
|
526
|
+
env,
|
|
527
|
+
}).toString();
|
|
528
|
+
if (checkSink.trim().length > 0) {
|
|
529
|
+
sinkId = identifier;
|
|
530
|
+
logger.info(`applyAudioOutputControl(): Original sink ${sinkId} still exists after profile switch`);
|
|
531
|
+
foundSink = true;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
logger.warn(`applyAudioOutputControl(): Failed to check if original sink exists for identifier ${identifier}:`, error);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (!foundSink) {
|
|
539
|
+
logger.warn(`applyAudioOutputControl(): Could not find sink after profile switch, using original identifier`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
else if (activeProfile && requiredProfile) {
|
|
543
|
+
logger.info(`applyAudioOutputControl(): Profile is correct (${activeProfile}), using sink directly`);
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
logger.warn(`applyAudioOutputControl(): Could not determine active or required profile, using sink as-is`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
catch (error) {
|
|
550
|
+
logger.warn(`applyAudioOutputControl(): Could not check/switch profile: ${error}, using sink as-is`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (!sinkId || sinkId === identifier) {
|
|
555
|
+
sinkId = identifier;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
const flatFormatMatch = identifier.match(/^([^_]+)_(output|input|sink|source)\.([^.]+(?:\.[^.]+)*)\.(.+)$/);
|
|
560
|
+
if (flatFormatMatch) {
|
|
561
|
+
const prefix = flatFormatMatch[1];
|
|
562
|
+
const cardId = flatFormatMatch[3];
|
|
563
|
+
const profileSuffix = flatFormatMatch[4];
|
|
564
|
+
const cardName = `${prefix}_card.${cardId}`;
|
|
565
|
+
const profile = `output:${profileSuffix}`;
|
|
566
|
+
try {
|
|
567
|
+
(0, child_process_1.execSync)(PACTL_COMMANDS.SET_CARD_PROFILE(cardName, profile), {
|
|
568
|
+
timeout: PACTL_TIMEOUT,
|
|
569
|
+
stdio: 'pipe',
|
|
570
|
+
env,
|
|
571
|
+
});
|
|
572
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
573
|
+
const sinksOutput = (0, child_process_1.execSync)(PACTL_COMMANDS.LIST_SINKS_SHORT(), {
|
|
574
|
+
timeout: PACTL_TIMEOUT,
|
|
575
|
+
stdio: 'pipe',
|
|
576
|
+
env,
|
|
577
|
+
}).toString();
|
|
578
|
+
const sinkLine = sinksOutput
|
|
579
|
+
.split('\n')
|
|
580
|
+
.find(line => line.includes(cardId) && line.includes(profileSuffix));
|
|
581
|
+
if (sinkLine) {
|
|
582
|
+
sinkId = sinkLine.split('\t')[1];
|
|
583
|
+
}
|
|
584
|
+
else {
|
|
585
|
+
logger.error(`applyAudioOutputControl(): Failed to find sink after setting profile`);
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
catch (error) {
|
|
590
|
+
logger.error(`applyAudioOutputControl(): Failed to set profile: ${error}`);
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
const cardName = getCardNameFromSink(identifier);
|
|
596
|
+
if (!cardName) {
|
|
597
|
+
logger.error(`applyAudioOutputControl(): Could not determine card name for ${identifier}`);
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
let sinkInfo = '';
|
|
601
|
+
let sinkExistsWithDifferentProfile = false;
|
|
602
|
+
try {
|
|
603
|
+
sinkInfo = (0, child_process_1.execSync)(PACTL_COMMANDS.GET_SINK_INFO(identifier), {
|
|
604
|
+
timeout: PACTL_TIMEOUT,
|
|
605
|
+
stdio: 'pipe',
|
|
606
|
+
env,
|
|
607
|
+
}).toString();
|
|
608
|
+
sinkExistsWithDifferentProfile = sinkInfo.length > 0;
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
logger.warn(`applyAudioOutputControl(): Failed to check if sink exists with different profile for identifier ${identifier}:`, error);
|
|
612
|
+
}
|
|
613
|
+
let isHdmi = false;
|
|
614
|
+
let isAnalog = false;
|
|
615
|
+
if (identifier.toLowerCase().includes('hdmi')) {
|
|
616
|
+
isHdmi = true;
|
|
617
|
+
}
|
|
618
|
+
else if (identifier.toLowerCase().includes('analog')) {
|
|
619
|
+
isAnalog = true;
|
|
620
|
+
}
|
|
621
|
+
else if (sinkExistsWithDifferentProfile) {
|
|
622
|
+
const hasHdmiInInfo = sinkInfo.includes('HDMI') ||
|
|
623
|
+
sinkInfo.includes('alsa.id = "HDMI') ||
|
|
624
|
+
sinkInfo.toLowerCase().includes('ultrafine') ||
|
|
625
|
+
sinkInfo.toLowerCase().includes('display');
|
|
626
|
+
const hasAnalogInInfo = sinkInfo.includes('analog') ||
|
|
627
|
+
sinkInfo.toLowerCase().includes('headphone') ||
|
|
628
|
+
sinkInfo.toLowerCase().includes('speaker');
|
|
629
|
+
isHdmi = hasHdmiInInfo;
|
|
630
|
+
isAnalog = hasAnalogInInfo;
|
|
631
|
+
}
|
|
632
|
+
if (!isHdmi && !isAnalog) {
|
|
633
|
+
try {
|
|
634
|
+
const cardInfo = (0, child_process_1.execSync)(PACTL_COMMANDS.GET_CARD_INFO(cardName), {
|
|
635
|
+
timeout: PACTL_TIMEOUT,
|
|
636
|
+
stdio: 'pipe',
|
|
637
|
+
env,
|
|
638
|
+
}).toString();
|
|
639
|
+
const sinksOutput = (0, child_process_1.execSync)(PACTL_COMMANDS.LIST_SINKS_SHORT(), {
|
|
640
|
+
timeout: PACTL_TIMEOUT,
|
|
641
|
+
stdio: 'pipe',
|
|
642
|
+
env,
|
|
643
|
+
}).toString();
|
|
644
|
+
const cardId = cardName.replace(/^[^.]*\./, '');
|
|
645
|
+
const deviceMatch = identifier.match(/[^_]+_(\d+)_(\d+)$/);
|
|
646
|
+
if (deviceMatch) {
|
|
647
|
+
const deviceNum = deviceMatch[2];
|
|
648
|
+
const hdmiSinkLine = sinksOutput
|
|
649
|
+
.split('\n')
|
|
650
|
+
.find(line => line.includes(cardId) &&
|
|
651
|
+
(line.includes(`device=${deviceNum}`) ||
|
|
652
|
+
line.includes(`_${deviceMatch[1]}_${deviceNum}`)) &&
|
|
653
|
+
line.toLowerCase().includes('hdmi'));
|
|
654
|
+
const analogSinkLine = sinksOutput
|
|
655
|
+
.split('\n')
|
|
656
|
+
.find(line => line.includes(cardId) &&
|
|
657
|
+
(line.includes(`device=${deviceNum}`) ||
|
|
658
|
+
line.includes(`_${deviceMatch[1]}_${deviceNum}`)) &&
|
|
659
|
+
(line.toLowerCase().includes('analog') || line.toLowerCase().includes('stereo')));
|
|
660
|
+
if (hdmiSinkLine && !analogSinkLine) {
|
|
661
|
+
isHdmi = true;
|
|
662
|
+
}
|
|
663
|
+
else if (analogSinkLine && !hdmiSinkLine) {
|
|
664
|
+
isAnalog = true;
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
const availableProfiles = getAvailableOutputProfiles(cardInfo);
|
|
668
|
+
const hasHdmiProfile = availableProfiles.some(p => p.toLowerCase().includes('hdmi'));
|
|
669
|
+
const hasAnalogProfile = availableProfiles.some(p => p.toLowerCase().includes('analog') || p.toLowerCase().includes('stereo'));
|
|
670
|
+
if (hasHdmiProfile && hasAnalogProfile) {
|
|
671
|
+
const activeProfileMatch = cardInfo.match(/^\s+(output:[^:]+):[^\n]*\*[^\n]*$/m);
|
|
672
|
+
if (activeProfileMatch) {
|
|
673
|
+
const activeProfile = activeProfileMatch[1];
|
|
674
|
+
isHdmi = activeProfile.toLowerCase().includes('hdmi');
|
|
675
|
+
isAnalog = !isHdmi && activeProfile.toLowerCase().includes('analog');
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
isAnalog = true;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
else if (hasHdmiProfile) {
|
|
682
|
+
isHdmi = true;
|
|
683
|
+
}
|
|
684
|
+
else if (hasAnalogProfile) {
|
|
685
|
+
isAnalog = true;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
catch (error) {
|
|
691
|
+
logger.warn(`applyAudioOutputControl(): Failed to detect profile type: ${error}`);
|
|
692
|
+
isAnalog = true;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
let profile = null;
|
|
696
|
+
if (isHdmi && !isAnalog) {
|
|
697
|
+
profile = getHdmiProfileForCard(cardName);
|
|
698
|
+
}
|
|
699
|
+
else if (isAnalog && !isHdmi) {
|
|
700
|
+
profile = getAnalogProfileForCard(cardName);
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
logger.warn(`applyAudioOutputControl(): Could not determine profile type (neither HDMI nor Analog detected)`);
|
|
704
|
+
}
|
|
705
|
+
if (profile) {
|
|
706
|
+
try {
|
|
707
|
+
logger.info(`applyAudioOutputControl(): Setting card profile ${cardName} to ${profile} (${isHdmi ? 'HDMI' : 'Analog'})`);
|
|
708
|
+
(0, child_process_1.execSync)(PACTL_COMMANDS.SET_CARD_PROFILE(cardName, profile), {
|
|
709
|
+
timeout: PACTL_TIMEOUT,
|
|
710
|
+
stdio: 'pipe',
|
|
711
|
+
env,
|
|
712
|
+
});
|
|
713
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
714
|
+
const sinksOutput = (0, child_process_1.execSync)(PACTL_COMMANDS.LIST_SINKS_SHORT(), {
|
|
715
|
+
timeout: PACTL_TIMEOUT,
|
|
716
|
+
stdio: 'pipe',
|
|
717
|
+
env,
|
|
718
|
+
}).toString();
|
|
719
|
+
const deviceMatch = identifier.match(/[^_]+_(\d+)_(\d+)$/);
|
|
720
|
+
if (deviceMatch) {
|
|
721
|
+
const deviceNum = deviceMatch[2];
|
|
722
|
+
const cardId = cardName.replace(/^[^.]*\./, '');
|
|
723
|
+
let sinkLines = sinksOutput.split('\n').filter(line => line.includes(cardId));
|
|
724
|
+
if (isHdmi) {
|
|
725
|
+
sinkLines = sinkLines.filter(line => line.toLowerCase().includes('hdmi'));
|
|
726
|
+
}
|
|
727
|
+
else if (isAnalog) {
|
|
728
|
+
sinkLines = sinkLines.filter(line => line.toLowerCase().includes('analog') || line.toLowerCase().includes('stereo'));
|
|
729
|
+
}
|
|
730
|
+
const sinkLine = sinkLines.find(line => line.includes(`device=${deviceNum}`) ||
|
|
731
|
+
line.includes(`_${deviceMatch[1]}_${deviceNum}`));
|
|
732
|
+
if (sinkLine) {
|
|
733
|
+
sinkId = sinkLine.split('\t')[1];
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
if (sinkLines.length > 0) {
|
|
737
|
+
sinkId = sinkLines[0].split('\t')[1];
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
logger.error(`applyAudioOutputControl(): Failed to find sink after setting profile`);
|
|
741
|
+
return false;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
sinkId = identifier;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
catch (error) {
|
|
750
|
+
logger.error(`applyAudioOutputControl(): Failed to set profile: ${error}`);
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
logger.error(`applyAudioOutputControl(): Could not determine profile for ${identifier}`);
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
try {
|
|
761
|
+
(0, child_process_1.execSync)(PACTL_COMMANDS.SET_DEFAULT_SINK(sinkId), {
|
|
762
|
+
timeout: PACTL_TIMEOUT,
|
|
763
|
+
stdio: 'pipe',
|
|
764
|
+
env,
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
catch (error) {
|
|
768
|
+
logger.error(`applyAudioOutputControl(): Failed to set default sink: ${error}`);
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
try {
|
|
772
|
+
(0, child_process_1.execSync)(PACTL_COMMANDS.SUSPEND_SINK(sinkId, false), {
|
|
773
|
+
timeout: PACTL_TIMEOUT,
|
|
774
|
+
stdio: 'pipe',
|
|
775
|
+
env,
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
catch (error) {
|
|
779
|
+
logger.error(`applyAudioOutputControl(): Failed to unsuspend sink: ${error}`);
|
|
780
|
+
return false;
|
|
781
|
+
}
|
|
782
|
+
try {
|
|
783
|
+
const sinkInputsOutput = (0, child_process_1.execSync)(PACTL_COMMANDS.LIST_SINK_INPUTS(), {
|
|
784
|
+
timeout: PACTL_TIMEOUT,
|
|
785
|
+
stdio: 'pipe',
|
|
786
|
+
env,
|
|
787
|
+
}).toString();
|
|
788
|
+
const sinkInputs = sinkInputsOutput.split('\n').filter(line => line.trim());
|
|
789
|
+
sinkInputs.forEach(line => {
|
|
790
|
+
const inputId = line.split('\t')[0];
|
|
791
|
+
if (inputId && !isNaN(Number(inputId))) {
|
|
792
|
+
try {
|
|
793
|
+
(0, child_process_1.execSync)(PACTL_COMMANDS.MOVE_SINK_INPUT(inputId, sinkId), {
|
|
794
|
+
timeout: PACTL_TIMEOUT,
|
|
795
|
+
stdio: 'pipe',
|
|
796
|
+
env,
|
|
797
|
+
});
|
|
798
|
+
(0, child_process_1.execSync)(PACTL_COMMANDS.CORK_SINK_INPUT(inputId, true), {
|
|
799
|
+
timeout: PACTL_TIMEOUT,
|
|
800
|
+
stdio: 'pipe',
|
|
801
|
+
env,
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
catch (error) {
|
|
805
|
+
logger.warn(`applyAudioOutputControl(): Failed to move/uncork sink-input ${inputId}:`, error);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
catch (error) {
|
|
811
|
+
logger.warn(`applyAudioOutputControl(): Failed to list/move sink inputs:`, error);
|
|
812
|
+
}
|
|
813
|
+
if (volume !== undefined) {
|
|
814
|
+
const volumePercent = Math.max(0, Math.min(100, volume));
|
|
815
|
+
try {
|
|
816
|
+
(0, child_process_1.execSync)(PACTL_COMMANDS.SET_SINK_VOLUME(sinkId, volumePercent), {
|
|
817
|
+
timeout: PACTL_TIMEOUT,
|
|
818
|
+
stdio: 'pipe',
|
|
819
|
+
env,
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
catch (error) {
|
|
823
|
+
logger.error(`applyAudioOutputControl(): Failed to set volume: ${error}`);
|
|
824
|
+
return false;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (muted !== undefined) {
|
|
828
|
+
try {
|
|
829
|
+
(0, child_process_1.execSync)(PACTL_COMMANDS.SET_SINK_MUTE(sinkId, muted ? '1' : '0'), {
|
|
830
|
+
timeout: PACTL_TIMEOUT,
|
|
831
|
+
stdio: 'pipe',
|
|
832
|
+
env,
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
catch (error) {
|
|
836
|
+
logger.error(`applyAudioOutputControl(): Failed to set mute: ${error}`);
|
|
837
|
+
return false;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (activePort) {
|
|
841
|
+
try {
|
|
842
|
+
(0, child_process_1.execSync)(PACTL_COMMANDS.SET_SINK_PORT(sinkId, activePort), {
|
|
843
|
+
timeout: PACTL_TIMEOUT,
|
|
844
|
+
stdio: 'pipe',
|
|
845
|
+
env,
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
catch (error) {
|
|
849
|
+
logger.warn(`applyAudioOutputControl(): Failed to set sink port ${activePort} for sink ${sinkId}:`, error);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return true;
|
|
853
|
+
}
|
|
854
|
+
catch (error) {
|
|
855
|
+
logger.error('applyAudioOutputControl(): Failed:', error);
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
async function applyAudioInputControl(audioInputControl) {
|
|
860
|
+
const { identifier, volume, muted, activePort } = audioInputControl;
|
|
861
|
+
if (!identifier) {
|
|
862
|
+
logger.error('applyAudioInputControl(): identifier is required');
|
|
863
|
+
return false;
|
|
864
|
+
}
|
|
865
|
+
try {
|
|
866
|
+
logger.info(`applyAudioInputControl(): Applying settings to source "${identifier}"`, {
|
|
867
|
+
volume,
|
|
868
|
+
muted,
|
|
869
|
+
activePort,
|
|
870
|
+
});
|
|
871
|
+
if (activePort) {
|
|
872
|
+
logger.info(`applyAudioInputControl(): Setting active port to "${activePort}"`);
|
|
873
|
+
const env = getPulseAudioEnv();
|
|
874
|
+
(0, child_process_1.execSync)(PACTL_COMMANDS.SET_SOURCE_PORT(identifier, activePort), {
|
|
875
|
+
timeout: PACTL_TIMEOUT,
|
|
876
|
+
stdio: 'pipe',
|
|
877
|
+
env,
|
|
878
|
+
});
|
|
879
|
+
logger.info(` Active port set to "${activePort}"`);
|
|
880
|
+
}
|
|
881
|
+
logger.info(`applyAudioInputControl(): Setting as default source`);
|
|
882
|
+
const env = getPulseAudioEnv();
|
|
883
|
+
(0, child_process_1.execSync)(PACTL_COMMANDS.SET_DEFAULT_SOURCE(identifier), {
|
|
884
|
+
timeout: PACTL_TIMEOUT,
|
|
885
|
+
stdio: 'pipe',
|
|
886
|
+
env,
|
|
887
|
+
});
|
|
888
|
+
logger.info(` Source set as default`);
|
|
889
|
+
if (volume !== undefined) {
|
|
890
|
+
const volumePercent = Math.max(0, Math.min(100, volume));
|
|
891
|
+
logger.info(`applyAudioInputControl(): Setting volume to ${volumePercent}%`);
|
|
892
|
+
const env = getPulseAudioEnv();
|
|
893
|
+
(0, child_process_1.execSync)(PACTL_COMMANDS.SET_SOURCE_VOLUME(identifier, volumePercent), {
|
|
894
|
+
timeout: PACTL_TIMEOUT,
|
|
895
|
+
stdio: 'pipe',
|
|
896
|
+
env,
|
|
897
|
+
});
|
|
898
|
+
logger.info(` Volume set to ${volumePercent}%`);
|
|
899
|
+
}
|
|
900
|
+
if (muted !== undefined) {
|
|
901
|
+
const muteValue = muted ? '1' : '0';
|
|
902
|
+
logger.info(`applyAudioInputControl(): Setting mute to ${muted}`);
|
|
903
|
+
const env = getPulseAudioEnv();
|
|
904
|
+
(0, child_process_1.execSync)(PACTL_COMMANDS.SET_SOURCE_MUTE(identifier, muteValue), {
|
|
905
|
+
timeout: PACTL_TIMEOUT,
|
|
906
|
+
stdio: 'pipe',
|
|
907
|
+
env,
|
|
908
|
+
});
|
|
909
|
+
logger.info(` Mute set to ${muted}`);
|
|
910
|
+
}
|
|
911
|
+
logger.info(' Audio input control settings applied successfully');
|
|
912
|
+
return true;
|
|
913
|
+
}
|
|
914
|
+
catch (error) {
|
|
915
|
+
logger.error('applyAudioInputControl(): Failed to apply audio input settings:', error instanceof Error ? error.message : String(error));
|
|
916
|
+
return false;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
//# sourceMappingURL=audio-control.js.map
|