-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e195a46
Showing
17 changed files
with
1,193 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/CMakeCache.txt | ||
/cmake_install.cmake | ||
/CMakeFiles | ||
/Makefile | ||
/*autogen | ||
/build | ||
/target | ||
/.cache | ||
/.qt | ||
/compile_commands.json | ||
/vgcore* | ||
/*.log | ||
/*.supp | ||
/PKGBUILD* | ||
/*.tar.gz |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
cmake_minimum_required(VERSION 3.16) | ||
|
||
project(mic-indicator LANGUAGES CXX) | ||
|
||
set(CMAKE_AUTOUIC ON) | ||
set(CMAKE_AUTOMOC ON) | ||
set(CMAKE_AUTORCC ON) | ||
|
||
set(CMAKE_CXX_STANDARD 20) | ||
set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||
|
||
add_compile_options(-fno-exceptions -fno-rtti) | ||
|
||
find_package(Qt6 REQUIRED COMPONENTS Widgets) | ||
find_package(PkgConfig REQUIRED) | ||
pkg_search_module(PIPEWIRE REQUIRED libpipewire-0.3) | ||
|
||
file(GLOB_RECURSE SRCS CONFIGURE_DEPENDS "src/*.cpp") | ||
|
||
add_executable(${PROJECT_NAME} ${SRCS}) | ||
|
||
target_link_libraries(${PROJECT_NAME} Qt6::Widgets) | ||
target_include_directories(${PROJECT_NAME} PRIVATE ${PIPEWIRE_INCLUDE_DIRS}) | ||
target_link_libraries(${PROJECT_NAME} ${PIPEWIRE_LIBRARIES}) | ||
|
||
include(GNUInstallDirs) | ||
install(TARGETS ${PROJECT_NAME} | ||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} | ||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} | ||
) | ||
|
||
set_target_properties(${PROJECT_NAME} PROPERTIES | ||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# PipeWire microphone tray indicator | ||
|
||
A simple microphone indicator for Linux using PipeWire, displayed | ||
in the system tray. It shows if any applications are using the microphone | ||
and if you can be heard. | ||
|
||
 | ||
|
||
## Usage | ||
|
||
``` | ||
Usage: mic-indicator [options] | ||
PipeWire microphone tray indicator | ||
Options: | ||
-h, --help Displays help on commandline options. | ||
--help-all Displays help, including generic Qt | ||
options. | ||
-e, --exclude-nodes <nodes> Comma-separated list of input node names | ||
to ignore (default: | ||
mic-indicator,capture.rnnoise_source) | ||
-v, --volume-threshold <threshold> The volume threshold for speech | ||
detection (default: 0.001) | ||
-s, --sampling-rate <sampling rate> Sampling rate (default: 48000) | ||
-c, --channels <channels> Number of channels (default: 1) | ||
``` | ||
|
||
## Installation | ||
|
||
### Manual installation | ||
|
||
Run these commands: | ||
|
||
```sh | ||
git clone https://github.com/lincot/mic-indicator | ||
cd mic-indicator | ||
cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-march=native" | ||
cmake --build build | ||
sudo cp build/mic-indicator /usr/bin/ | ||
sudo cp res/icons/* /usr/share/icons/hicolor/64x64/apps/ | ||
``` |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
#include "AudioProcessor.h" | ||
#include "PipeWireManager.h" | ||
|
||
AudioProcessor::AudioProcessor(PipeWireManager const *pipeWireManager, | ||
float volumeThreshold) | ||
: m_volumeThreshold(volumeThreshold), m_lowCount(0) { | ||
|
||
connect(&m_timer, &QTimer::timeout, this, &AudioProcessor::processAudio, | ||
Qt::DirectConnection); | ||
connect( | ||
pipeWireManager, &PipeWireManager::onMicUsageChanged, &m_timer, | ||
[this](bool micActive) { | ||
if (micActive) { | ||
m_timer.start(100); | ||
} else { | ||
m_timer.stop(); | ||
} | ||
}, | ||
Qt::QueuedConnection); | ||
} | ||
|
||
void AudioProcessor::start(pw_stream *stream) { m_stream = stream; } | ||
|
||
float calculateRMS(const float *const audioSamples, | ||
const uint32_t sampleCount) { | ||
float sum = 0; | ||
for (uint32_t i = 0; i < sampleCount; ++i) { | ||
sum += audioSamples[i] * audioSamples[i]; | ||
} | ||
return sqrtf(sum / static_cast<float>(sampleCount)); | ||
} | ||
|
||
void AudioProcessor::processAudio() { | ||
pw_buffer *buffer; | ||
spa_buffer *spaBuffer; | ||
|
||
buffer = pw_stream_dequeue_buffer(m_stream); | ||
if (buffer == nullptr) { | ||
return; | ||
} | ||
|
||
spaBuffer = buffer->buffer; | ||
if (spaBuffer->datas[0].data == nullptr) { | ||
pw_stream_queue_buffer(m_stream, buffer); | ||
return; | ||
} | ||
|
||
const auto *audioSamples = static_cast<float *>(spaBuffer->datas[0].data); | ||
const uint32_t sampleCount = spaBuffer->datas[0].chunk->size / sizeof(float); | ||
|
||
const auto volume = calculateRMS(audioSamples, sampleCount); | ||
|
||
pw_stream_queue_buffer(m_stream, buffer); | ||
|
||
if (volume < m_volumeThreshold) { | ||
if (++m_lowCount == 5) { | ||
emit onVolumeChanged(false); | ||
} | ||
} else { | ||
m_lowCount = 0; | ||
emit onVolumeChanged(true); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#pragma once | ||
|
||
#include "PipeWireManager.h" | ||
|
||
// NOLINTNEXTLINE | ||
class AudioProcessor : public QObject { | ||
Q_OBJECT | ||
|
||
public: | ||
AudioProcessor(PipeWireManager const *pipeWireManager, float volumeThreshold); | ||
|
||
signals: | ||
void onVolumeChanged(bool highVolume); | ||
|
||
public slots: | ||
void start(pw_stream *stream); | ||
|
||
private slots: | ||
void processAudio(); | ||
|
||
// NOLINTNEXTLINE | ||
private: | ||
pw_stream *m_stream; | ||
float m_volumeThreshold; | ||
QTimer m_timer; | ||
uint m_lowCount; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
#include "Config.h" | ||
#include <QCommandLineParser> | ||
|
||
Config getConfig(const QApplication &app) { | ||
QCommandLineParser parser; | ||
parser.setApplicationDescription("PipeWire microphone tray indicator"); | ||
parser.addHelpOption(); | ||
|
||
const QCommandLineOption excludedNodesOption( | ||
QStringList() << "e" << "exclude-nodes", | ||
"Comma-separated list of input node names to ignore (default: " | ||
"mic-indicator,capture.rnnoise_source)", | ||
"nodes", "mic-indicator,capture.rnnoise_source"); | ||
parser.addOption(excludedNodesOption); | ||
|
||
const QCommandLineOption volumeThresholdOption( | ||
QStringList() << "v" << "volume-threshold", | ||
"The volume threshold for speech detection (default: 0.001)", "threshold", | ||
"0.001"); | ||
parser.addOption(volumeThresholdOption); | ||
|
||
const QCommandLineOption samplingRateOption( | ||
QStringList() << "s" << "sampling-rate", "Sampling rate (default: 48000)", | ||
"sampling rate", "48000"); | ||
parser.addOption(samplingRateOption); | ||
|
||
const QCommandLineOption channelsOption(QStringList() << "c" << "channels", | ||
"Number of channels (default: 1)", | ||
"channels", "1"); | ||
parser.addOption(channelsOption); | ||
|
||
parser.process(app); | ||
|
||
return Config{ | ||
.excludedNodes = parser.value(excludedNodesOption).split(','), | ||
.volumeThreshold = parser.value(volumeThresholdOption).toFloat(), | ||
.samplingRate = parser.value(samplingRateOption).toUInt(), | ||
.channels = parser.value(channelsOption).toUInt(), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
#pragma once | ||
|
||
#include <QApplication> | ||
|
||
struct Config { | ||
QStringList excludedNodes; | ||
float volumeThreshold; | ||
uint32_t samplingRate; | ||
uint32_t channels; | ||
}; | ||
|
||
Config getConfig(const QApplication &app); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
#include "MicTrayIcon.h" | ||
#include <QApplication> | ||
|
||
MicTrayIcon::MicTrayIcon() | ||
: m_exitAction("Exit"), m_offIcon(QIcon::fromTheme("mic-indicator-off")), | ||
m_lowVolumeIcon(QIcon::fromTheme("mic-indicator-low")), | ||
m_highVolumeIcon(QIcon::fromTheme("mic-indicator-high")), | ||
m_micActive(false), m_volumeHigh(false) { | ||
|
||
setMicStatus(Off); | ||
m_trayIcon.show(); | ||
|
||
QObject::connect(&m_exitAction, &QAction::triggered, qApp, | ||
&QApplication::exit, Qt::DirectConnection); | ||
m_menu.addAction(&m_exitAction); | ||
m_trayIcon.setContextMenu(&m_menu); | ||
} | ||
|
||
void MicTrayIcon::setMicActive(bool micActive) { | ||
m_micActive = micActive; | ||
if (micActive) { | ||
setMicStatus(Low); | ||
} else { | ||
setMicStatus(Off); | ||
m_volumeHigh = false; | ||
} | ||
} | ||
|
||
void MicTrayIcon::setVolumeHigh(bool volumeHigh) { | ||
if (m_volumeHigh == volumeHigh || !m_micActive) { | ||
return; | ||
} | ||
|
||
m_volumeHigh = volumeHigh; | ||
if (volumeHigh) { | ||
setMicStatus(High); | ||
} else { | ||
setMicStatus(Low); | ||
} | ||
} | ||
|
||
void MicTrayIcon::setMicStatus(const MicStatus status) { | ||
switch (status) { | ||
case Off: | ||
m_trayIcon.setIcon(m_offIcon); | ||
m_trayIcon.setToolTip("Microphone is not in use"); | ||
break; | ||
case Low: | ||
m_trayIcon.setIcon(m_lowVolumeIcon); | ||
m_trayIcon.setToolTip("Microphone is in use, no speech detected"); | ||
break; | ||
case High: | ||
m_trayIcon.setIcon(m_highVolumeIcon); | ||
m_trayIcon.setToolTip("Microphone is in use, speech detected"); | ||
break; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
#pragma once | ||
|
||
#include <QIcon> | ||
#include <QMenu> | ||
#include <QSystemTrayIcon> | ||
|
||
enum MicStatus { | ||
Off, | ||
Low, | ||
High, | ||
}; | ||
|
||
class MicTrayIcon : public QObject { | ||
Q_OBJECT | ||
|
||
public: | ||
MicTrayIcon(); | ||
|
||
// NOLINTNEXTLINE | ||
public slots: | ||
void setMicActive(bool micActive); | ||
void setVolumeHigh(bool volumeHigh); | ||
|
||
private: | ||
void setMicStatus(MicStatus micStatus); | ||
|
||
QSystemTrayIcon m_trayIcon; | ||
QMenu m_menu; | ||
QAction m_exitAction; | ||
QIcon m_offIcon; | ||
QIcon m_lowVolumeIcon; | ||
QIcon m_highVolumeIcon; | ||
|
||
bool m_micActive; | ||
bool m_volumeHigh; | ||
}; |
Oops, something went wrong.