Skip to content

Commit e53c881

Browse files
committed
major update
- All matching targets are now used instead of only the first match - Bug fixes
1 parent b80eb72 commit e53c881

File tree

2 files changed

+162
-41
lines changed

2 files changed

+162
-41
lines changed

vccli/AudioAPI.hpp

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ namespace vccli {
2727
std::string pname, suid, sguid;
2828

2929
constexpr ProcessInfo(std::string const& PNAME, const DWORD PID, const EDataFlow flow, std::string const& SUID, std::string const& SGUID, std::string const& DGUID, std::string const& DNAME, const bool isDefaultDevice)
30-
: DeviceInfo(DNAME, DGUID, flow, false), pid{ PID }, pname{ PNAME }, suid{ SUID }, sguid{ SGUID } {}
30+
: DeviceInfo(DNAME, DGUID, flow, false), pid{ PID }, pname{ PNAME }, suid{ SUID }, sguid{ SGUID }
31+
{
32+
}
3133

3234
};
3335

@@ -435,6 +437,117 @@ namespace vccli {
435437

436438
return object;
437439
}
440+
static std::vector<std::unique_ptr<Volume>> getObjects(const std::string& target_id, const bool fuzzy, EDataFlow const& deviceFlowFilter, const bool defaultDevIsOutput = true)
441+
{
442+
auto target_id_lower{ str::tolower(target_id) };
443+
if (fuzzy)
444+
target_id_lower = str::trim(target_id_lower);
445+
446+
const auto& compare_target_id_to{ [&target_id_lower, &fuzzy](std::string const& s) -> bool {
447+
return (target_id_lower == (fuzzy ? str::trim(s) : s)) || (fuzzy && s.find(str::trim(target_id_lower)) != std::string::npos);
448+
} };
449+
450+
std::vector<std::unique_ptr<Volume>> objects;
451+
objects.reserve(1);
452+
453+
IMMDeviceEnumerator* deviceEnumerator{ getDeviceEnumerator() };
454+
IMMDevice* dev;
455+
456+
if (target_id.empty()) {
457+
// DEFAULT DEVICE:
458+
EDataFlow defaultDevFlow{ deviceFlowFilter };
459+
if (defaultDevFlow == EDataFlow::eAll) //< we can't request a default 'eAll' device; select input or output
460+
defaultDevFlow = (defaultDevIsOutput ? EDataFlow::eRender : EDataFlow::eCapture);
461+
462+
deviceEnumerator->GetDefaultAudioEndpoint(defaultDevFlow, ERole::eMultimedia, &dev);
463+
$release(deviceEnumerator);
464+
IAudioEndpointVolume* endpoint{};
465+
dev->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (void**)&endpoint);
466+
const auto& devName{ getDeviceFriendlyName(dev) };
467+
const auto& deviceID{ getDeviceID(dev) };
468+
$release(dev);
469+
470+
objects.emplace_back(std::make_unique<EndpointVolume>(endpoint, devName, deviceID, defaultDevFlow, true));
471+
return objects;
472+
} // Else we have an actual target ID to find
473+
474+
// Check if we have a valid PID
475+
std::optional<DWORD> target_pid;
476+
if (std::all_of(target_id.begin(), target_id.end(), str::stdpred::isdigit))
477+
target_pid = str::stoul(target_id);
478+
479+
// Enumerate all devices of the specified I/O type(s):
480+
IMMDeviceCollection* devices;
481+
deviceEnumerator->EnumAudioEndpoints(deviceFlowFilter, ERole::eMultimedia, &devices);
482+
$release(deviceEnumerator)
483+
484+
UINT count;
485+
devices->GetCount(&count);
486+
487+
objects.reserve(count);
488+
489+
for (UINT i{ 0u }; i < count; ++i) {
490+
devices->Item(i, &dev);
491+
492+
LPWSTR sbuf;
493+
dev->GetId(&sbuf);
494+
std::string deviceID{ w_converter.to_bytes(sbuf) };
495+
496+
const auto& deviceName{ getDeviceFriendlyName(dev) };
497+
const auto& deviceFlow{ getDeviceDataFlow(dev) };
498+
499+
// Check if this device is a match
500+
if (!target_pid.has_value() && (compare_target_id_to(str::tolower(deviceID)) || compare_target_id_to(str::tolower(deviceName)))) {
501+
IAudioEndpointVolume* endpointVolume{};
502+
dev->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (void**)&endpointVolume);
503+
objects.emplace_back(std::make_unique<EndpointVolume>(endpointVolume, deviceName, deviceID, deviceFlow, isDefaultDevice(dev)));
504+
}
505+
else { // Check for matching sessions on this device:
506+
IAudioSessionManager2* mgr{};
507+
dev->Activate(__uuidof(IAudioSessionManager2), 0, NULL, (void**)&mgr);
508+
509+
IAudioSessionEnumerator* sessionEnumerator;
510+
mgr->GetSessionEnumerator(&sessionEnumerator);
511+
$release(mgr);
512+
513+
IAudioSessionControl* sessionControl;
514+
IAudioSessionControl2* sessionControl2;
515+
ISimpleAudioVolume* sessionVolumeControl;
516+
517+
// Enumerate all audio sessions on this device:
518+
int sessionCount;
519+
sessionEnumerator->GetCount(&sessionCount);
520+
521+
for (int j{ 0 }; j < sessionCount; ++j) {
522+
sessionEnumerator->GetSession(j, &sessionControl);
523+
524+
sessionControl->QueryInterface<IAudioSessionControl2>(&sessionControl2);
525+
$release(sessionControl);
526+
527+
DWORD pid;
528+
sessionControl2->GetProcessId(&pid);
529+
530+
const auto& pname{ GetProcessNameFrom(pid) };
531+
const auto& suid{ getSessionIdentifier(sessionControl2) }, & sguid{ getSessionInstanceIdentifier(sessionControl2) };
532+
533+
// Check if this session is a match:
534+
if ((pname.has_value() && compare_target_id_to(str::tolower(pname.value()))) || (target_pid.has_value() && target_pid.value() == pid) || compare_target_id_to(suid) || compare_target_id_to(sguid)) {
535+
sessionControl2->QueryInterface<ISimpleAudioVolume>(&sessionVolumeControl);
536+
$release(sessionControl2);
537+
objects.emplace_back(std::make_unique<ApplicationVolume>(sessionVolumeControl, pname.value(), pid, deviceFlow, deviceID, suid, sguid));
538+
break; //< break from session enumeration loop
539+
}
540+
$release(sessionControl2);
541+
} //< end session enumeration loop
542+
$release(sessionEnumerator)
543+
}
544+
$release(dev);
545+
}
546+
$release(devices);
547+
548+
objects.shrink_to_fit();
549+
return objects;
550+
}
438551

439552
static bool isDefaultDevice(IMMDevice* dev)
440553
{

vccli/vccli.cpp

Lines changed: 48 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ term::palette<COLOR> colors{
8383
std::make_pair(COLOR::INPUT, color::pink),
8484
std::make_pair(COLOR::OUTPUT, color::setcolor(4, 4, 0)),
8585
};
86-
size_t MARGIN_WIDTH{ 20ull };
86+
size_t MARGIN_WIDTH{ 12ull };
8787

8888
// Forward Declarations:
8989
inline std::string getTargetAndValidateParams(const opt3::ArgManager&);
@@ -92,12 +92,17 @@ inline void handleVolumeArgs(const opt3::ArgManager&, const vccli::Volume*);
9292
inline void handleMuteArgs(const opt3::ArgManager&, const vccli::Volume*);
9393

9494

95-
96-
97-
95+
/**
96+
* @struct VolumeObjectPrinter
97+
* @brief Stream functor that pretty-prints a vccli::Volume object.
98+
*/
9899
struct VolumeObjectPrinter {
99100
vccli::Volume* obj;
100101

102+
/**
103+
* @brief Creates a new VolumeObjectPrinter instance with the given Volume object pointer.
104+
* @param obj A pointer to a valid Volume object to print.
105+
*/
101106
constexpr VolumeObjectPrinter(vccli::Volume* obj) : obj{ obj } {}
102107

103108
friend std::ostream& operator<<(std::ostream& os, const VolumeObjectPrinter& p)
@@ -131,8 +136,11 @@ struct VolumeObjectPrinter {
131136
os
132137
<< " " << colors(typecolor) << p.obj->resolved_name << colors() << '\n'
133138
<< "Typename: " << colors(typecolor) << p.obj->type_name().value_or("null") << colors();
134-
if (is_device && ((vccli::EndpointVolume*)p.obj)->isDefault) os << ' ' << colors(COLOR::LOWLIGHT) << "(Default)" << colors();
135-
os << '\n'
139+
if (is_device && ((vccli::EndpointVolume*)p.obj)->isDefault) os << ' ' << colors(COLOR::LOWLIGHT) << "(Default)" << colors() << '\n';
140+
if (is_session)
141+
os << '\n'
142+
<< "PID: " << colors(COLOR::LOWLIGHT) << p.obj->identifier << colors() << '\n';
143+
os
136144
<< "Direction: " << colors(p.obj->flow_type == EDataFlow::eRender ? COLOR::OUTPUT : COLOR::INPUT) << p.obj->getFlowTypeName() << colors() << '\n'
137145
<< "Volume: " << colors(COLOR::VALUE) << p.obj->getVolumeScaled() << colors() << '\n'
138146
<< "Muted: " << colors(COLOR::VALUE) << std::boolalpha << p.obj->getMuted() << std::noboolalpha << colors() << '\n'
@@ -210,7 +218,7 @@ namespace vccli_operators {
210218
os << static_cast<DeviceInfo>(pi);
211219

212220
if (extended) {
213-
if (quiet) os
221+
if (quiet) os
214222
<< SEP
215223
<< pi.suid << SEP
216224
<< pi.sguid
@@ -293,27 +301,19 @@ int main(const int argc, char** argv)
293301
using namespace vccli;
294302
int rc{ 0 };
295303
try {
296-
using namespace opt3_literals;
297304
opt3::ArgManager args{ argc, argv,
298-
'v'_optcap,
299-
"volume"_optcap,
300-
'm'_optcap,
301-
"is-muted"_optcap,
302-
'I'_reqcap,
303-
"increment"_reqcap,
304-
'D'_reqcap,
305-
"decrement"_reqcap,
306-
'd'_reqcap,
307-
"dev"_reqcap
305+
opt3::make_template(opt3::CaptureStyle::Optional, 'v', "volume"),
306+
opt3::make_template(opt3::CaptureStyle::Optional, 'm', "mute", "muted", "is-muted"),
307+
opt3::make_template(opt3::CaptureStyle::Required, 'I', "increment"),
308+
opt3::make_template(opt3::CaptureStyle::Required, 'D', "decrement"),
309+
opt3::make_template(opt3::CaptureStyle::Required, 'd', "dev"),
308310
};
309311

310-
// handle important general blocking args
312+
// handle important general args
311313
quiet = args.check_any<opt3::Flag, opt3::Option>('q', "quiet");
312314
colors.setActive(!quiet && !args.check_any<opt3::Flag, opt3::Option>('n', "no-color"));
313315
extended = args.check_any<opt3::Flag, opt3::Option>('e', "extended");
314316

315-
if (extended) MARGIN_WIDTH += 10;
316-
317317
if (args.empty() || args.check_any<opt3::Flag, opt3::Option>('h', "help")) {
318318
std::cout << PrintHelp{};
319319
return 0;
@@ -333,9 +333,9 @@ int main(const int argc, char** argv)
333333
throw make_exception("Failed to initialize COM interface with error code ", hr, ": '", GetErrorMessageFrom(hr), "'!");
334334

335335
// Get controller:
336-
const auto& targetController{ AudioAPI::getObject(target, args.check_any<opt3::Flag, opt3::Option>('f', "fuzzy"), flow) };
336+
const auto& targetControllers{ AudioAPI::getObjects(target, args.check_any<opt3::Flag, opt3::Option>('f', "fuzzy"), flow) };
337337

338-
if (targetController.get() == nullptr)
338+
if (targetControllers.empty())
339339
throw make_exception(
340340
"Couldn't locate anything matching the given search term!\n",
341341
indent(10), colors(COLOR::HEADER), "Search Term", colors(), ": ", colors(COLOR::ERR), target, colors(), '\n',
@@ -347,8 +347,14 @@ int main(const int argc, char** argv)
347347
listDevices{ args.check_any<opt3::Flag, opt3::Option>('L', "list-dev") };
348348

349349
// -Q | --query
350-
if (args.check_any<opt3::Flag, opt3::Option>('Q', "query"))
351-
std::cout << VolumeObjectPrinter(targetController.get()) << '\n';
350+
if (args.check_any<opt3::Flag, opt3::Option>('Q', "query")) {
351+
bool fst{ true };
352+
for (const auto& it : targetControllers) {
353+
if (fst) fst = false;
354+
else std::cout << '\n';
355+
std::cout << VolumeObjectPrinter(it.get());
356+
}
357+
}
352358
// list
353359
else if (listSessions || listDevices) {
354360
// -l | --list
@@ -362,11 +368,13 @@ int main(const int argc, char** argv)
362368
}
363369
// Non-blocking options:
364370
else {
365-
// Handle Volume Args:
366-
handleVolumeArgs(args, targetController.get());
371+
for (const auto& it : targetControllers) {
372+
// Handle Volume Args:
373+
handleVolumeArgs(args, it.get());
367374

368-
// Handle Mute Args:
369-
handleMuteArgs(args, targetController.get());
375+
// Handle Mute Args:
376+
handleMuteArgs(args, it.get());
377+
}
370378
}
371379

372380
} catch (const showhelp& ex) {
@@ -451,16 +459,16 @@ inline void handleVolumeArgs(const opt3::ArgManager& args, const vccli::Volume*
451459
else if (tgtVolume < 0.0f)
452460
tgtVolume = 0.0f;
453461
if (controller->getVolumeScaled() == tgtVolume) {
454-
if (!quiet) std::cout << "Volume is" << indent(MARGIN_WIDTH, 10ull) << colors(COLOR::WARN) << static_cast<int>(tgtVolume) << colors() << '\n';
462+
if (!quiet) std::cout << "Volume is" << indent(MARGIN_WIDTH, 9ull) << colors(COLOR::WARN) << static_cast<int>(tgtVolume) << colors() << '\n';
455463
}
456464
else {
457465
controller->setVolumeScaled(tgtVolume);
458-
if (!quiet) std::cout << "Volume =" << indent(MARGIN_WIDTH, 9ull) << colors(COLOR::VALUE) << static_cast<int>(tgtVolume) << colors() << '\n';
466+
if (!quiet) std::cout << "Volume =" << indent(MARGIN_WIDTH, 8ull) << colors(COLOR::VALUE) << static_cast<int>(tgtVolume) << colors() << '\n';
459467
}
460468
}
461469
else {
462470
// Get
463-
if (!quiet) std::cout << "Volume:" << indent(MARGIN_WIDTH, 8ull) << colors(COLOR::VALUE);
471+
if (!quiet) std::cout << "Volume:" << indent(MARGIN_WIDTH, 7ull) << colors(COLOR::VALUE);
464472
std::cout << str::stringify(std::fixed, std::setprecision(0), controller->getVolume() * 100.0f);
465473
if (!quiet) std::cout << colors() << '\n';
466474
}
@@ -476,20 +484,20 @@ inline void handleMuteArgs(const opt3::ArgManager& args, const vccli::Volume* co
476484
throw make_exception("Conflicting Options Specified: ", colors(COLOR::ERR), "-m", colors(), '|', colors(COLOR::ERR), "--mute", colors(), " && ", colors(COLOR::ERR), "-u", colors(), '|', colors(COLOR::ERR), "--unmute", colors());
477485
else if (mute) {
478486
if (controller->getMuted() == true) {
479-
if (!quiet) std::cout << "Muted is" << indent(MARGIN_WIDTH, 9ull) << colors(COLOR::WARN) << "true" << colors() << '\n';
487+
if (!quiet) std::cout << "Muted is" << indent(MARGIN_WIDTH, 8ull) << colors(COLOR::WARN) << "true" << colors() << '\n';
480488
}
481489
else {
482490
controller->mute();
483-
if (!quiet) std::cout << "Muted =" << indent(MARGIN_WIDTH, 8ull) << colors(COLOR::VALUE) << "true" << colors() << '\n';
491+
if (!quiet) std::cout << "Muted =" << indent(MARGIN_WIDTH, 7ull) << colors(COLOR::VALUE) << "true" << colors() << '\n';
484492
}
485493
}
486494
else if (unmute) {
487495
if (controller->getMuted() == false) {
488-
if (!quiet) std::cout << "Muted is" << indent(MARGIN_WIDTH, 9ull) << colors(COLOR::WARN) << "false" << colors() << '\n';
496+
if (!quiet) std::cout << "Muted is" << indent(MARGIN_WIDTH, 8ull) << colors(COLOR::WARN) << "false" << colors() << '\n';
489497
}
490498
else {
491499
controller->unmute();
492-
if (!quiet) std::cout << "Muted =" << indent(MARGIN_WIDTH, 8ull) << colors(COLOR::VALUE) << "false" << colors() << '\n';
500+
if (!quiet) std::cout << "Muted =" << indent(MARGIN_WIDTH, 7ull) << colors(COLOR::VALUE) << "false" << colors() << '\n';
493501
}
494502
}
495503

@@ -500,20 +508,20 @@ inline void handleMuteArgs(const opt3::ArgManager& args, const vccli::Volume* co
500508
const auto& value{ str::trim(captured.value()) };
501509
if (str::equalsAny<true>(value, "true", "1", "on")) {
502510
if (controller->getMuted() == true) {
503-
if (!quiet) std::cout << "Muted is" << indent(MARGIN_WIDTH, 9ull) << colors(COLOR::WARN) << "true" << colors() << '\n';
511+
if (!quiet) std::cout << "Muted is" << indent(MARGIN_WIDTH, 8ull) << colors(COLOR::WARN) << "true" << colors() << '\n';
504512
}
505513
else {
506514
controller->mute();
507-
if (!quiet) std::cout << "Muted =" << indent(MARGIN_WIDTH, 8ull) << colors(COLOR::VALUE) << "true" << colors() << '\n';
515+
if (!quiet) std::cout << "Muted =" << indent(MARGIN_WIDTH, 7ull) << colors(COLOR::VALUE) << "true" << colors() << '\n';
508516
}
509517
}
510518
else if (str::equalsAny<true>(value, "false", "0", "off")) {
511519
if (controller->getMuted() == false) {
512-
if (!quiet) std::cout << "Muted is" << indent(MARGIN_WIDTH, 9ull) << colors(COLOR::WARN) << "false" << colors() << '\n';
520+
if (!quiet) std::cout << "Muted is" << indent(MARGIN_WIDTH, 8ull) << colors(COLOR::WARN) << "false" << colors() << '\n';
513521
}
514522
else {
515523
controller->unmute();
516-
if (!quiet) std::cout << "Muted =" << indent(MARGIN_WIDTH, 8ull) << colors(COLOR::VALUE) << "false" << colors() << '\n';
524+
if (!quiet) std::cout << "Muted =" << indent(MARGIN_WIDTH, 7ull) << colors(COLOR::VALUE) << "false" << colors() << '\n';
517525
}
518526
}
519527
else throw make_exception("Invalid Argument Specified: '", colors(COLOR::ERR), captured.value(), colors(), "'; Expected a boolean value ('", colors(COLOR::ERR), "true", colors(), "'/'", colors(COLOR::ERR), "false", colors(), "')!");

0 commit comments

Comments
 (0)