11#include " oss.hpp"
22#include < algorithm>
3+ #include < array>
34#include < cerrno>
45#include < cstdlib>
56#include < cstring>
67#include < utility>
78
89#include < QFile>
910#include < QProcess>
11+ #include < QSocketNotifier>
1012#include < QTextStream>
1113#include < fcntl.h>
1214#include < qalgorithms.h>
1315#include < qlist.h>
1416#include < qlogging.h>
17+ #include < qloggingcategory.h>
1518#include < qobject.h>
1619#include < qtimer.h>
1720#include < qtmetamacros.h>
1821#ifdef __FreeBSD__
1922#include < sys/ioccom.h>
23+ #include < sys/socket.h>
2024#include < sys/sysctl.h>
2125#include < sys/types.h>
26+ #include < sys/un.h>
2227#endif
2328#include < sys/ioctl.h>
2429#include < sys/soundcard.h>
2530#include < unistd.h>
2631
32+ #include " ../../core/logcat.hpp"
33+
34+ namespace {
35+ QS_LOGGING_CATEGORY (logPolkit, " quickshell.service.oss" , QtWarningMsg);
36+ }
37+
2738namespace qs ::service::oss {
2839
2940// OSSMixerControl Implementation
@@ -86,7 +97,7 @@ bool OSSMixerControl::updateValue() {
8697 auto fd = open (mixerPath.toUtf8 ().constData (), O_RDONLY);
8798
8899 if (fd < 0 ) {
89- qWarning ( ) << " Failed to open mixer device:" << mixerPath;
100+ qCWarning (logPolkit ) << " Failed to open mixer device:" << mixerPath;
90101 return false ;
91102 }
92103
@@ -126,7 +137,7 @@ bool OSSMixerControl::writeValue() {
126137 auto fd = open (mixerPath.toUtf8 ().constData (), O_WRONLY);
127138
128139 if (fd < 0 ) {
129- qWarning ( ) << " Failed to open mixer device for writing:" << mixerPath;
140+ qCWarning (logPolkit ) << " Failed to open mixer device for writing:" << mixerPath;
130141 return false ;
131142 }
132143
@@ -136,7 +147,7 @@ bool OSSMixerControl::writeValue() {
136147
137148 if (ioctl (fd, MIXER_WRITE (this ->mMixerId ), &value) < 0 ) { // NOLINT
138149 close (fd);
139- qWarning ( ) << " Failed to write mixer value for control:" << this ->mName ;
150+ qCWarning (logPolkit ) << " Failed to write mixer value for control:" << this ->mName ;
140151 return false ;
141152 }
142153
@@ -164,8 +175,8 @@ bool OSSSoundDevice::initialize() {
164175 auto fd = open (mixerPath.toUtf8 ().constData (), O_RDONLY);
165176
166177 if (fd < 0 ) {
167- qWarning ( ) << " OSS: Failed to open mixer device:" << mixerPath
168- << " error:" << qt_error_string (errno);
178+ qCWarning (logPolkit ) << " Failed to open mixer device:" << mixerPath
179+ << " error:" << qt_error_string (errno);
169180 return false ;
170181 }
171182
@@ -174,7 +185,7 @@ bool OSSSoundDevice::initialize() {
174185 if (ioctl (fd, SOUND_MIXER_READ_DEVMASK, &devmask) >= 0 ) { // NOLINT
175186 this ->loadControls ();
176187 } else {
177- qWarning ( ) << " OSS: Failed to read devmask for" << mixerPath;
188+ qCWarning (logPolkit ) << " Failed to read devmask for" << mixerPath;
178189 close (fd);
179190 return false ;
180191 }
@@ -212,7 +223,7 @@ bool OSSSoundDevice::initialize() {
212223
213224 sndstat.close ();
214225 } else {
215- qWarning ( ) << " OSS: Could not open /dev/sndstat" ;
226+ qCWarning (logPolkit ) << " Could not open /dev/sndstat" ;
216227 }
217228
218229 close (fd);
@@ -287,17 +298,30 @@ QList<QObject*> OSSSoundDevice::controls() const {
287298}
288299
289300// OSS Implementation
290- OSS::OSS (QObject* parent): QObject(parent), mPollTimer (new QTimer(this )) {
301+ OSS::OSS (QObject* parent): QObject(parent), mRescanTimer (new QTimer(this )) {
291302 if (QFile::exists (" /dev/sndstat" )) {
292303 this ->mAvailable = true ;
293304
294305 this ->scanDevices ();
295306 this ->updateDefault ();
296307
297- connect (this ->mPollTimer , &QTimer::timeout, this , &OSS::refresh);
298- this ->mPollTimer ->start (5000 );
308+ this ->mRescanTimer ->setSingleShot (true );
309+ this ->mRescanTimer ->setInterval (100 ); // debounce
310+
311+ connect (this ->mRescanTimer , &QTimer::timeout, this , [this ]() {
312+ this ->scanDevices ();
313+ this ->updateDefault ();
314+ });
315+
316+ this ->connectToDevd ();
299317 } else {
300- qWarning () << " OSS sound system is not available" ;
318+ qCWarning (logPolkit) << " OSS sound system is not available" ;
319+ }
320+ }
321+
322+ OSS::~OSS () {
323+ if (this ->mDevdSocket >= 0 ) {
324+ close (this ->mDevdSocket );
301325 }
302326}
303327
@@ -315,6 +339,113 @@ QList<QObject*> OSS::devices() const {
315339OSSSoundDevice* OSS::defaultDevice () const { return this ->mDefaultDevice ; }
316340bool OSS::isAvailable () const { return this ->mAvailable ; }
317341
342+ void OSS::connectToDevd () {
343+ // clang-format off
344+ #ifdef __FreeBSD__
345+ // Try seqpacket first
346+ const char * pipePaths[] = {
347+ " /var/run/devd.seqpacket.pipe" ,
348+ " /var/run/devd.pipe" ,
349+ };
350+
351+ for (const auto * path: pipePaths) {
352+ if (!QFile::exists (path)) {
353+ qCInfo (logPolkit) << " devd pipe" << path << " does not exist, trying next" ;
354+ continue ;
355+ }
356+
357+ this ->mDevdSocket = socket (PF_UNIX, SOCK_SEQPACKET, 0 );
358+ if (this ->mDevdSocket < 0 ) {
359+ this ->mDevdSocket = socket (PF_UNIX, SOCK_STREAM, 0 );
360+ }
361+
362+ if (this ->mDevdSocket < 0 ) {
363+ qCWarning (logPolkit) << " Failed to create socket for devd:" << qt_error_string (errno);
364+ continue ;
365+ }
366+
367+ struct sockaddr_un addr = {};
368+ addr.sun_family = AF_UNIX;
369+ strncpy (addr.sun_path , path, sizeof (addr.sun_path ) - 1 );
370+
371+ if (::connect (this ->mDevdSocket , (struct sockaddr *) &addr, SUN_LEN (&addr)) == 0 ) {
372+ int flags = fcntl (this ->mDevdSocket , F_GETFL, 0 );
373+ fcntl (this ->mDevdSocket , F_SETFL, flags | O_NONBLOCK);
374+
375+ this ->mDevdNotifier = new QSocketNotifier (this ->mDevdSocket , QSocketNotifier::Read, this );
376+ connect (this ->mDevdNotifier , &QSocketNotifier::activated, this , &OSS::handleDevdEvent);
377+
378+ this ->mDevdNotifier ->setEnabled (true );
379+ qCInfo (logPolkit) << " Successfully connected to devd at" << path;
380+
381+ return ;
382+ } else {
383+ qCWarning (logPolkit) << " Failed to connect to" << path << " :" << qt_error_string (errno);
384+ }
385+
386+ close (this ->mDevdSocket );
387+ this ->mDevdSocket = -1 ;
388+ }
389+
390+ qCWarning (logPolkit) << " Failed to connect to a devd pipe—device monitoring disabled" ;
391+ #else
392+ qCInfo (logPolkit) << " devd monitoring only available on FreeBSD" ;
393+ #endif
394+ // clang-format on
395+ (void ) this ;
396+ }
397+
398+ void OSS::handleDevdEvent () {
399+ std::array<char , 4096 > buffer {};
400+ const ssize_t n = read (this ->mDevdSocket , buffer.data (), buffer.size () - 1 );
401+
402+ if (n <= 0 ) {
403+ if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
404+ qCWarning (logPolkit) << " devd socket read error:" << qt_error_string (errno);
405+ }
406+ return ;
407+ }
408+
409+ buffer[n] = ' \0 ' ;
410+
411+ const std::array<const char *, 4 > audioKeywords = {" dsp" , " mixer" , " pcm" , " snd" };
412+ bool isAudioRelated = false ;
413+
414+ for (const auto * keyword: audioKeywords) {
415+ if (strstr (buffer.data (), keyword) != nullptr ) {
416+ isAudioRelated = true ;
417+ break ;
418+ }
419+ }
420+
421+ if (!isAudioRelated) {
422+ return ; // Skip non-audio events
423+ }
424+
425+ const QString event = QString::fromUtf8 (buffer.data ());
426+
427+ bool needsRescan = false ;
428+
429+ if (event.contains (" system=DEVFS" )
430+ && (event.contains (" cdev=dsp" ) || event.contains (" cdev=mixer" ) || event.contains (" cdev=snd" )
431+ || event.contains (" cdev=pcm" )))
432+ {
433+
434+ qCInfo (logPolkit) << " Audio device event detected, scheduling rescan" ;
435+ needsRescan = true ;
436+ }
437+
438+ if (event.contains (" hw.snd.default_unit" )) {
439+ qCInfo (logPolkit) << " Default device changed, scheduling update" ;
440+ needsRescan = true ;
441+ }
442+
443+ // Prevent multiple rapid rescans when devices are changing quickly
444+ if (needsRescan && !this ->mRescanTimer ->isActive ()) {
445+ this ->mRescanTimer ->start ();
446+ }
447+ }
448+
318449void OSS::scanDevices () {
319450 if (this ->mDefaultDevice != nullptr ) {
320451 this ->mDefaultDevice = nullptr ;
@@ -332,8 +463,9 @@ void OSS::scanDevices() {
332463
333464 if (device->initialize ()) {
334465 this ->mDevices .append (device);
466+ qCInfo (logPolkit) << " Device added:" << device;
335467 } else {
336- qWarning ( ) << " OSS: Failed to initialize device:" << mixerPath ;
468+ qCWarning (logPolkit ) << " Failed to initialize device:" << device ;
337469 delete device;
338470 }
339471 }
@@ -385,7 +517,7 @@ void OSS::updateDefault() {
385517
386518 sndstat.close ();
387519 } else {
388- qWarning ( ) << " OSS: Failed to open /dev/sndstat for reading default device " ;
520+ qCWarning (logPolkit ) << " Failed to read default device via /dev/sndstat" ;
389521 }
390522
391523 if (!newDefault && !this ->mDevices .isEmpty ()) {
@@ -406,20 +538,22 @@ bool OSS::setDefaultDevice(int deviceId) {
406538 #ifdef __FreeBSD__
407539 if (sysctlbyname (" hw.snd.default_unit" , nullptr , nullptr , &deviceId, size) == 0 ) {
408540 this ->updateDefault ();
541+ qCInfo (logPolkit) << " Set default device to" << deviceId;
409542 return true ;
410543 }
411544 #else
412545 (void )this ;
413546 (void )size;
414547
415548 if (!this ->mAvailable ) {
416- qWarning ( ) << " OSS is not available on this system" ;
549+ qCWarning (logPolkit ) << " OSS is not available on this system" ;
417550 return false ;
418551 }
419552 #endif
420553 // clang-format on
421554
422- qWarning () << " Failed to set default device to" << deviceId << " error:" << qt_error_string (errno);
555+ qCWarning (logPolkit) << " Failed to set default device to" << deviceId
556+ << " error:" << qt_error_string (errno);
423557
424558 return false ;
425559}
0 commit comments