mirror of
https://bitbucket.org/smil3y/kde-workspace.git
synced 2025-02-23 18:32:50 +00:00
1182 lines
42 KiB
C++
1182 lines
42 KiB
C++
/* This file is part of the KDE project
|
|
Copyright (C) 2023 Ivailo Monev <xakepa10@gmail.com>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License version 2, as published by the Free Software Foundation.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "mixer.h"
|
|
|
|
#include <QTimer>
|
|
#include <QGridLayout>
|
|
#include <QGraphicsGridLayout>
|
|
#include <Plasma/TabBar>
|
|
#include <Plasma/Frame>
|
|
#include <Plasma/Label>
|
|
#include <Plasma/Slider>
|
|
#include <Plasma/IconWidget>
|
|
#include <Plasma/Theme>
|
|
#include <Plasma/SignalPlotter>
|
|
#include <Plasma/ToolTipManager>
|
|
#include <KIconLoader>
|
|
#include <KIcon>
|
|
#include <KDebug>
|
|
|
|
#include <alsa/asoundlib.h>
|
|
|
|
static const QSizeF s_minimumsize = QSizeF(290, 140);
|
|
static const QSizeF s_minimumslidersize = QSizeF(10, 70);
|
|
static const QSizeF s_minimumvisualizersize = QSizeF(120, 70);
|
|
static const int s_svgiconsize = 256;
|
|
static const QString s_defaultpopupicon = QString::fromLatin1("audio-card");
|
|
static const int s_defaultsoundcard = -1;
|
|
static const int s_alsapollinterval = 250; // ms
|
|
// for butter-smooth visualization the poll interval is very frequent
|
|
static const int s_alsavisualizerinterval = 50; // ms
|
|
// deciding factor for the visualization samples frequency
|
|
static const int s_alsapcmbuffersize = 256;
|
|
static const bool s_showvisualizer = true;
|
|
static const uint s_visualizerscale = 2;
|
|
static const bool s_visualizericon = false;
|
|
// as little as possible for precision
|
|
static const int s_volumestep = 1;
|
|
|
|
static QList<snd_mixer_selem_channel_id_t> kALSAChannelTypes(snd_mixer_elem_t *alsaelement, const bool capture)
|
|
{
|
|
QList<snd_mixer_selem_channel_id_t> result;
|
|
static const snd_mixer_selem_channel_id_t alsachanneltypes[] = {
|
|
SND_MIXER_SCHN_FRONT_LEFT,
|
|
SND_MIXER_SCHN_FRONT_RIGHT,
|
|
SND_MIXER_SCHN_REAR_LEFT,
|
|
SND_MIXER_SCHN_REAR_RIGHT,
|
|
SND_MIXER_SCHN_FRONT_CENTER,
|
|
SND_MIXER_SCHN_WOOFER,
|
|
SND_MIXER_SCHN_SIDE_LEFT,
|
|
SND_MIXER_SCHN_SIDE_RIGHT,
|
|
SND_MIXER_SCHN_REAR_CENTER,
|
|
SND_MIXER_SCHN_UNKNOWN
|
|
};
|
|
int counter = 0;
|
|
while (alsachanneltypes[counter] != SND_MIXER_SCHN_UNKNOWN) {
|
|
int alsaresult = 0;
|
|
if (!capture) {
|
|
alsaresult = snd_mixer_selem_has_playback_channel(alsaelement, alsachanneltypes[counter]);
|
|
} else {
|
|
alsaresult = snd_mixer_selem_has_capture_channel(alsaelement, alsachanneltypes[counter]);
|
|
}
|
|
if (alsaresult != 0) {
|
|
result.append(alsachanneltypes[counter]);
|
|
}
|
|
counter++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static bool kGetChannelVolumes(snd_mixer_elem_t *alsaelement, snd_mixer_selem_channel_id_t alsaelementchannel, const bool alsahascapture,
|
|
long *alsavolumemin, long *alsavolumemax, long *alsavolume)
|
|
{
|
|
int alsaresult = 0;
|
|
if (alsahascapture) {
|
|
alsaresult = snd_mixer_selem_get_capture_volume_range(alsaelement, alsavolumemin, alsavolumemax);
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not get capture channel volume range" << snd_strerror(alsaresult);
|
|
return false;
|
|
}
|
|
alsaresult = snd_mixer_selem_get_capture_volume(alsaelement, alsaelementchannel, alsavolume);
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not get capture channel volume" << snd_strerror(alsaresult);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
alsaresult = snd_mixer_selem_get_playback_volume_range(alsaelement, alsavolumemin, alsavolumemax);
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not get playback channel volume range" << snd_strerror(alsaresult);
|
|
return false;
|
|
}
|
|
alsaresult = snd_mixer_selem_get_playback_volume(alsaelement, alsaelementchannel, alsavolume);
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not get playback channel volume" << snd_strerror(alsaresult);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool kIsMasterElement(const QString &alsaelementname)
|
|
{
|
|
return alsaelementname.contains(QLatin1String("master"), Qt::CaseInsensitive);
|
|
}
|
|
|
|
// for reference:
|
|
// alsa-lib/src/control/hcontrol.c
|
|
static QString kIconForElement(const QString &alsaelementname)
|
|
{
|
|
if (kIsMasterElement(alsaelementname)) {
|
|
return QString::fromLatin1("mixer-master");
|
|
}
|
|
if (alsaelementname.contains(QLatin1String("capture"), Qt::CaseInsensitive)) {
|
|
return QString::fromLatin1("mixer-capture");
|
|
}
|
|
if (alsaelementname.contains(QLatin1String("pcm"), Qt::CaseInsensitive)) {
|
|
return QString::fromLatin1("mixer-pcm");
|
|
}
|
|
if (alsaelementname.contains(QLatin1String("video"), Qt::CaseInsensitive)) {
|
|
return QString::fromLatin1("mixer-video");
|
|
}
|
|
if (alsaelementname.contains(QLatin1String("pc speaker"), Qt::CaseInsensitive)) {
|
|
return QString::fromLatin1("mixer-pc-speaker");
|
|
}
|
|
if (alsaelementname.contains(QLatin1String("mic"), Qt::CaseInsensitive)) {
|
|
return QString::fromLatin1("mixer-microphone");
|
|
}
|
|
if (alsaelementname.contains(QLatin1String("cd"), Qt::CaseInsensitive)) {
|
|
return QString::fromLatin1("mixer-cd");
|
|
}
|
|
if (alsaelementname.contains(QLatin1String("front"), Qt::CaseInsensitive)) {
|
|
return QString::fromLatin1("mixer-front");
|
|
}
|
|
if (alsaelementname.contains(QLatin1String("center"), Qt::CaseInsensitive)) {
|
|
return QString::fromLatin1("mixer-surround-center");
|
|
}
|
|
if (alsaelementname.contains(QLatin1String("surround"), Qt::CaseInsensitive)) {
|
|
return QString::fromLatin1("mixer-surround");
|
|
}
|
|
if (alsaelementname.contains(QLatin1String("lfe"), Qt::CaseInsensitive)) {
|
|
return QString::fromLatin1("mixer-lfe");
|
|
}
|
|
if (alsaelementname.contains(QLatin1String("headphone"), Qt::CaseInsensitive)) {
|
|
return QString::fromLatin1("mixer-headset");
|
|
}
|
|
return QString::fromLatin1("mixer-line");
|
|
}
|
|
|
|
static int kFixedVolume(const long alsavolume, const long alsavolumemax)
|
|
{
|
|
const qreal valuefactor = (qreal(alsavolumemax) / 100);
|
|
return qRound(qreal(alsavolume) / valuefactor);
|
|
}
|
|
|
|
static QIcon kMixerIcon(QObject *parent, const int value)
|
|
{
|
|
QIcon result;
|
|
Plasma::Svg plasmasvg(parent);
|
|
plasmasvg.setImagePath("icons/audio");
|
|
plasmasvg.setContainsMultipleImages(true);
|
|
if (plasmasvg.isValid()) {
|
|
QPixmap iconpixmap(s_svgiconsize, s_svgiconsize);
|
|
iconpixmap.fill(Qt::transparent);
|
|
QPainter iconpainter(&iconpixmap);
|
|
if (value >= 75) {
|
|
plasmasvg.paint(&iconpainter, iconpixmap.rect(), "audio-volume-high");
|
|
} else if (value >= 50) {
|
|
plasmasvg.paint(&iconpainter, iconpixmap.rect(), "audio-volume-medium");
|
|
} else if (value >= 25) {
|
|
plasmasvg.paint(&iconpainter, iconpixmap.rect(), "audio-volume-low");
|
|
} else {
|
|
plasmasvg.paint(&iconpainter, iconpixmap.rect(), "audio-volume-muted");
|
|
}
|
|
result = QIcon(iconpixmap);
|
|
} else {
|
|
result = KIcon(s_defaultpopupicon);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static QColor kDefaultVisualizerColor()
|
|
{
|
|
return Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor);
|
|
}
|
|
|
|
static QGraphicsWidget* kMakeSpacer(QGraphicsWidget *parent)
|
|
{
|
|
QGraphicsWidget* result = new QGraphicsWidget(parent);
|
|
result->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
result->setMinimumSize(0, 0);
|
|
result->setFlag(QGraphicsItem::ItemHasNoContents);
|
|
return result;
|
|
}
|
|
|
|
int k_alsa_element_callback(snd_mixer_elem_t *alsaelement, unsigned int alsamask);
|
|
|
|
class MixerSlider : public Plasma::Slider
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
explicit MixerSlider(const uint alsaelementindex,
|
|
const snd_mixer_selem_channel_id_t alsaelementchannel,
|
|
const QString &alsaelementname,
|
|
QGraphicsWidget *parent);
|
|
|
|
|
|
void setVolume(const long alsavolumemin, const long alsavolumemax, const long alsavolume);
|
|
long alsaVolume() const;
|
|
|
|
long alsavolumemin;
|
|
long alsavolumemax;
|
|
long alsavolume;
|
|
uint alsaelementindex;
|
|
snd_mixer_selem_channel_id_t alsaelementchannel;
|
|
QString alsaelementname;
|
|
};
|
|
|
|
MixerSlider::MixerSlider(const uint _alsaelementindex,
|
|
const snd_mixer_selem_channel_id_t _alsaelementchannel,
|
|
const QString &_alsaelementname,
|
|
QGraphicsWidget *parent)
|
|
: Plasma::Slider(parent),
|
|
alsavolumemin(0),
|
|
alsavolumemax(0),
|
|
alsavolume(0),
|
|
alsaelementindex(_alsaelementindex),
|
|
alsaelementchannel(_alsaelementchannel),
|
|
alsaelementname(_alsaelementname)
|
|
{
|
|
setOrientation(Qt::Vertical);
|
|
setRange(0, 100);
|
|
nativeWidget()->setSingleStep(s_volumestep);
|
|
setValue(0);
|
|
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
|
|
setMinimumSize(s_minimumslidersize);
|
|
setToolTip(QString::fromLocal8Bit(snd_mixer_selem_channel_name(alsaelementchannel)));
|
|
}
|
|
|
|
void MixerSlider::setVolume(const long _alsavolumemin, const long _alsavolumemax, const long _alsavolume)
|
|
{
|
|
alsavolumemin = _alsavolumemin;
|
|
alsavolumemax = _alsavolumemax;
|
|
alsavolume = _alsavolume;
|
|
setValue(kFixedVolume(alsavolume, alsavolumemax));
|
|
}
|
|
|
|
long MixerSlider::alsaVolume() const
|
|
{
|
|
const qreal valuefactor = (qreal(alsavolumemax) / 100);
|
|
return (valuefactor * value());
|
|
}
|
|
|
|
|
|
class MixerPlotter : public Plasma::SignalPlotter
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
explicit MixerPlotter(const bool icon, QGraphicsWidget *parent);
|
|
|
|
private Q_SLOTS:
|
|
void slotIconChanged();
|
|
|
|
protected:
|
|
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) final;
|
|
|
|
private:
|
|
bool m_icon;
|
|
KIcon m_plainicon;
|
|
KIcon m_uncertainicon;
|
|
KIcon m_sadicon;
|
|
KIcon m_smileicon;
|
|
};
|
|
|
|
MixerPlotter::MixerPlotter(const bool icon, QGraphicsWidget *parent)
|
|
: Plasma::SignalPlotter(parent),
|
|
m_icon(icon)
|
|
{
|
|
slotIconChanged();
|
|
connect(
|
|
KGlobalSettings::self(), SIGNAL(iconChanged(int)),
|
|
this, SLOT(slotIconChanged())
|
|
);
|
|
}
|
|
|
|
void MixerPlotter::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
|
|
{
|
|
Plasma::SignalPlotter::paint(painter, option, widget);
|
|
if (!m_icon) {
|
|
return;
|
|
}
|
|
const QSizeF plottersize = size();
|
|
const QSizeF quarterplottersize = (plottersize / 4);
|
|
const int iconsize = qMin(quarterplottersize.width(), quarterplottersize.height());
|
|
if (iconsize <= 16) {
|
|
return;
|
|
}
|
|
const double lastplotvalue = lastValue(0);
|
|
const QPointF pixmappoint = QPointF((plottersize.width() / 2) - (iconsize / 2), 10);
|
|
if (lastplotvalue <= 0.05 && lastplotvalue >= -0.05) {
|
|
painter->drawPixmap(pixmappoint, m_plainicon.pixmap(iconsize, iconsize));
|
|
} else if (lastplotvalue >= -0.30 && lastplotvalue <= 0.30) {
|
|
painter->drawPixmap(pixmappoint, m_uncertainicon.pixmap(iconsize, iconsize));
|
|
} else if (lastplotvalue > 0.0) {
|
|
painter->drawPixmap(pixmappoint, m_smileicon.pixmap(iconsize, iconsize));
|
|
} else {
|
|
painter->drawPixmap(pixmappoint, m_sadicon.pixmap(iconsize, iconsize));
|
|
}
|
|
}
|
|
|
|
void MixerPlotter::slotIconChanged()
|
|
{
|
|
m_plainicon = KIcon("face-plain");
|
|
m_uncertainicon = KIcon("face-uncertain");
|
|
m_sadicon = KIcon("face-sad");
|
|
m_smileicon = KIcon("face-smile");
|
|
}
|
|
|
|
|
|
class MixerTabWidget : public QGraphicsWidget
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
MixerTabWidget(const bool isdefault, const QString &alsamixername, Plasma::TabBar *tabbar);
|
|
~MixerTabWidget();
|
|
|
|
bool setup(const QByteArray &cardname);
|
|
void showVisualizer(const bool show, const uint scale, const QColor &color, const bool icon);
|
|
void pauseVisualizer(const bool pause);
|
|
// can't be const because Plasma::Svg requires non-const parent
|
|
QIcon mainVolumeIcon();
|
|
Plasma::ToolTipContent toolTipContent() const;
|
|
void decreaseVolume();
|
|
void increaseVolume();
|
|
|
|
// public for the callback
|
|
QList<MixerSlider*> sliders;
|
|
|
|
Q_SIGNALS:
|
|
void mainVolumeChanged();
|
|
|
|
private Q_SLOTS:
|
|
void slotSliderMovedOrChanged(const int value);
|
|
void slotEventsTimeout();
|
|
void slotVisualizerTimeout();
|
|
|
|
private:
|
|
QGraphicsLinearLayout* m_layout;
|
|
snd_mixer_t* m_alsamixer;
|
|
snd_pcm_t* m_alsapcm;
|
|
QTimer* m_eventstimer;
|
|
QTimer* m_visualizertimer;
|
|
QGraphicsWidget* m_spacer;
|
|
Plasma::Frame* m_plotterframe;
|
|
MixerPlotter* m_signalplotter;
|
|
float m_alsapcmbuffer[s_alsapcmbuffersize];
|
|
bool m_isdefault;
|
|
QString m_alsamixername;
|
|
QString m_mainelement;
|
|
QByteArray m_alsacardname;
|
|
};
|
|
|
|
MixerTabWidget::MixerTabWidget(const bool isdefault, const QString &alsamixername, Plasma::TabBar *tabbar)
|
|
: QGraphicsWidget(tabbar),
|
|
m_layout(nullptr),
|
|
m_alsamixer(nullptr),
|
|
m_alsapcm(nullptr),
|
|
m_eventstimer(nullptr),
|
|
m_visualizertimer(nullptr),
|
|
m_spacer(nullptr),
|
|
m_plotterframe(nullptr),
|
|
m_signalplotter(nullptr),
|
|
m_isdefault(isdefault),
|
|
m_alsamixername(alsamixername)
|
|
{
|
|
setMinimumSize(s_minimumsize);
|
|
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
|
|
|
m_layout = new QGraphicsLinearLayout(Qt::Horizontal, this);
|
|
m_layout->setContentsMargins(0, 0, 0, 0);
|
|
setLayout(m_layout);
|
|
|
|
m_eventstimer = new QTimer(this);
|
|
m_eventstimer->setInterval(s_alsapollinterval);
|
|
connect(
|
|
m_eventstimer, SIGNAL(timeout()),
|
|
this, SLOT(slotEventsTimeout())
|
|
);
|
|
|
|
m_visualizertimer = new QTimer(this);
|
|
m_visualizertimer->setInterval(s_alsavisualizerinterval);
|
|
connect(
|
|
m_visualizertimer, SIGNAL(timeout()),
|
|
this, SLOT(slotVisualizerTimeout())
|
|
);
|
|
}
|
|
|
|
MixerTabWidget::~MixerTabWidget()
|
|
{
|
|
m_eventstimer->stop();
|
|
m_visualizertimer->stop();
|
|
if (m_alsapcm) {
|
|
snd_pcm_close(m_alsapcm);
|
|
}
|
|
if (m_alsamixer) {
|
|
snd_mixer_close(m_alsamixer);
|
|
}
|
|
}
|
|
|
|
bool MixerTabWidget::setup(const QByteArray &alsacardname)
|
|
{
|
|
Q_ASSERT(m_alsamixer == nullptr);
|
|
m_alsacardname = alsacardname;
|
|
int alsaresult = snd_mixer_open(&m_alsamixer, 0);
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not open mixer" << snd_strerror(alsaresult);
|
|
return false;
|
|
}
|
|
alsaresult = snd_mixer_attach(m_alsamixer, alsacardname.constData());
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not attach mixer" << snd_strerror(alsaresult);
|
|
snd_mixer_close(m_alsamixer);
|
|
m_alsamixer = nullptr;
|
|
return false;
|
|
}
|
|
alsaresult = snd_mixer_selem_register(m_alsamixer, nullptr, nullptr);
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not register mixer" << snd_strerror(alsaresult);
|
|
snd_mixer_close(m_alsamixer);
|
|
m_alsamixer = nullptr;
|
|
return false;
|
|
}
|
|
alsaresult = snd_mixer_load(m_alsamixer);
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not load mixer" << snd_strerror(alsaresult);
|
|
snd_mixer_close(m_alsamixer);
|
|
m_alsamixer = nullptr;
|
|
return false;
|
|
}
|
|
|
|
const int smalliconsize = KIconLoader::global()->currentSize(KIconLoader::Small);
|
|
const QSizeF smalliconsizef = QSizeF(smalliconsize, smalliconsize);
|
|
bool hasvalidelement = false;
|
|
QStringList alsaelementnames;
|
|
snd_mixer_elem_t *alsaelement = snd_mixer_first_elem(m_alsamixer);
|
|
for (; alsaelement; alsaelement = snd_mixer_elem_next(alsaelement)) {
|
|
if (snd_mixer_elem_empty(alsaelement)) {
|
|
continue;
|
|
}
|
|
const bool alsahasplayback = snd_mixer_selem_has_playback_volume(alsaelement);
|
|
const bool alsahascapture = snd_mixer_selem_has_capture_volume(alsaelement);
|
|
const uint alsaelementindex = snd_mixer_selem_get_index(alsaelement);
|
|
const QString alsaelementname = QString::fromLocal8Bit(snd_mixer_selem_get_name(alsaelement));
|
|
if (!alsahasplayback && !alsahascapture) {
|
|
// no volume to mix
|
|
kDebug() << "Skipping" << alsaelementindex << alsaelementname << "due to lack of volume";
|
|
continue;
|
|
}
|
|
// qDebug() << Q_FUNC_INFO << alsaelementindex << alsaelementname << alsahasplayback << alsahascapture;
|
|
|
|
const QList<snd_mixer_selem_channel_id_t> alsaelementchannels = kALSAChannelTypes(alsaelement, alsahascapture);
|
|
if (alsaelementchannels.size() < 1) {
|
|
kWarning() << "Element has no channels" << alsaelementindex << alsaelementname;
|
|
continue;
|
|
}
|
|
|
|
hasvalidelement = true;
|
|
Plasma::Frame* frame = new Plasma::Frame(this);
|
|
frame->setFrameShadow(Plasma::Frame::Sunken);
|
|
frame->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
|
|
frame->setText(alsaelementname);
|
|
QGraphicsGridLayout* framelayout = new QGraphicsGridLayout(frame);
|
|
int columncounter = 0;
|
|
foreach (const snd_mixer_selem_channel_id_t alsaelementchannel, alsaelementchannels) {
|
|
long alsavolumemin = 0;
|
|
long alsavolumemax = 0;
|
|
long alsavolume = 0;
|
|
const bool gotvolumes = kGetChannelVolumes(
|
|
alsaelement, alsaelementchannel, alsahascapture,
|
|
&alsavolumemin, &alsavolumemax, &alsavolume
|
|
);
|
|
if (!gotvolumes) {
|
|
continue;
|
|
}
|
|
const QString alsaelementchannelname = QString::fromLocal8Bit(snd_mixer_selem_channel_name(alsaelementchannel));
|
|
MixerSlider* slider = new MixerSlider(alsaelementindex, alsaelementchannel, alsaelementname, frame);
|
|
slider->setVolume(alsavolumemin, alsavolumemax, alsavolume);
|
|
connect(
|
|
slider, SIGNAL(sliderMoved(int)),
|
|
this, SLOT(slotSliderMovedOrChanged(int))
|
|
);
|
|
connect(
|
|
slider, SIGNAL(valueChanged(int)),
|
|
this, SLOT(slotSliderMovedOrChanged(int))
|
|
);
|
|
sliders.append(slider);
|
|
framelayout->addItem(slider, 0, columncounter, 1, 1);
|
|
columncounter++;
|
|
}
|
|
Plasma::IconWidget* iconwidget = new Plasma::IconWidget(frame);
|
|
iconwidget->setIcon(kIconForElement(alsaelementname));
|
|
iconwidget->setAcceptHoverEvents(false);
|
|
iconwidget->setMinimumIconSize(smalliconsizef);
|
|
iconwidget->setMaximumIconSize(smalliconsizef);
|
|
framelayout->addItem(iconwidget, 1, 0, 1, columncounter);
|
|
framelayout->setAlignment(iconwidget, Qt::AlignCenter);
|
|
frame->setLayout(framelayout);
|
|
snd_mixer_elem_set_callback(alsaelement, k_alsa_element_callback);
|
|
snd_mixer_elem_set_callback_private(alsaelement, this);
|
|
|
|
m_layout->addItem(frame);
|
|
columncounter++;
|
|
|
|
if (m_mainelement.isEmpty() && kIsMasterElement(alsaelementname)) {
|
|
m_mainelement = alsaelementname;
|
|
} else {
|
|
alsaelementnames.append(alsaelementname);
|
|
}
|
|
}
|
|
if (m_mainelement.isEmpty() && alsaelementnames.size() > 0) {
|
|
// pick the first as main if there is no master
|
|
m_mainelement = alsaelementnames.first();
|
|
}
|
|
kDebug() << "Main element is" << m_mainelement;
|
|
m_spacer = kMakeSpacer(this);
|
|
m_layout->addItem(m_spacer);
|
|
|
|
if (hasvalidelement) {
|
|
m_eventstimer->start();
|
|
}
|
|
|
|
adjustSize();
|
|
|
|
return hasvalidelement;
|
|
}
|
|
|
|
void MixerTabWidget::showVisualizer(const bool show, const uint scale, const QColor &color, const bool icon)
|
|
{
|
|
const bool starttimer = m_visualizertimer->isActive();
|
|
m_visualizertimer->stop();
|
|
if (m_alsapcm) {
|
|
snd_pcm_close(m_alsapcm);
|
|
m_alsapcm = nullptr;
|
|
}
|
|
if (m_spacer) {
|
|
m_layout->removeItem(m_spacer);
|
|
m_spacer->deleteLater();
|
|
m_spacer = nullptr;
|
|
}
|
|
if (m_signalplotter) {
|
|
m_layout->removeItem(m_signalplotter);
|
|
m_signalplotter->deleteLater();
|
|
m_signalplotter = nullptr;
|
|
m_layout->removeItem(m_plotterframe);
|
|
m_plotterframe->deleteLater();
|
|
m_plotterframe = nullptr;
|
|
}
|
|
|
|
if (!show) {
|
|
if (starttimer) {
|
|
m_visualizertimer->start();
|
|
}
|
|
m_spacer = kMakeSpacer(this);
|
|
m_layout->addItem(m_spacer);
|
|
return;
|
|
}
|
|
|
|
int alsaresult = snd_pcm_open(&m_alsapcm, m_alsacardname.constData(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not open PCM" << snd_strerror(alsaresult);
|
|
m_alsapcm = nullptr;
|
|
} else {
|
|
kDebug() << "Opened PCM" << m_alsacardname;
|
|
}
|
|
if (m_alsapcm) {
|
|
alsaresult = snd_spcm_init(
|
|
m_alsapcm,
|
|
44100, // rate
|
|
1, // channels
|
|
SND_PCM_FORMAT_FLOAT,
|
|
SND_PCM_SUBFORMAT_STD,
|
|
SND_SPCM_LATENCY_STANDARD,
|
|
SND_PCM_ACCESS_RW_INTERLEAVED,
|
|
SND_SPCM_XRUN_STOP
|
|
);
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not init PCM" << snd_strerror(alsaresult);
|
|
snd_pcm_close(m_alsapcm);
|
|
m_alsapcm = nullptr;
|
|
}
|
|
}
|
|
if (m_alsapcm) {
|
|
alsaresult = snd_pcm_prepare(m_alsapcm);
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not prepare PCM" << snd_strerror(alsaresult);
|
|
snd_pcm_close(m_alsapcm);
|
|
m_alsapcm = nullptr;
|
|
}
|
|
}
|
|
if (m_alsapcm) {
|
|
// has to be setup not to block
|
|
alsaresult = snd_pcm_nonblock(m_alsapcm, 1);
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not setup PCM for non-blocking" << snd_strerror(alsaresult);
|
|
snd_pcm_close(m_alsapcm);
|
|
m_alsapcm = nullptr;
|
|
}
|
|
}
|
|
if (m_alsapcm) {
|
|
m_plotterframe = new Plasma::Frame(this);
|
|
m_plotterframe->setFrameShadow(Plasma::Frame::Sunken);
|
|
m_plotterframe->setMinimumSize(s_minimumvisualizersize);
|
|
m_plotterframe->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
|
QGraphicsLinearLayout* plotterframelayout = new QGraphicsLinearLayout(m_plotterframe);
|
|
plotterframelayout->setContentsMargins(0, 0, 0, 0);
|
|
m_plotterframe->setLayout(plotterframelayout);
|
|
m_signalplotter = new MixerPlotter(icon, m_plotterframe);
|
|
m_signalplotter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
m_signalplotter->setShowLabels(false);
|
|
m_signalplotter->setShowVerticalLines(false);
|
|
m_signalplotter->setShowHorizontalLines(false);
|
|
m_signalplotter->setThinFrame(false);
|
|
m_signalplotter->setHorizontalScale(scale);
|
|
m_signalplotter->setUseAutoRange(false);
|
|
// the documented range for SND_PCM_FORMAT_FLOAT
|
|
m_signalplotter->setVerticalRange(-1.0, 1.0);
|
|
m_signalplotter->addPlot(color);
|
|
plotterframelayout->addItem(m_signalplotter);
|
|
m_layout->addItem(m_plotterframe);
|
|
|
|
m_visualizertimer->start();
|
|
} else {
|
|
m_spacer = kMakeSpacer(this);
|
|
m_layout->addItem(m_spacer);
|
|
}
|
|
}
|
|
|
|
void MixerTabWidget::pauseVisualizer(const bool pause)
|
|
{
|
|
if (!m_alsapcm) {
|
|
return;
|
|
}
|
|
if (pause) {
|
|
kDebug() << "Pausing visualizer";
|
|
m_visualizertimer->stop();
|
|
const int alsaresult = snd_pcm_drop(m_alsapcm);
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not drop PCM" << snd_strerror(alsaresult);
|
|
}
|
|
} else if (m_signalplotter) {
|
|
kDebug() << "Unpausing visualizer";
|
|
int alsaresult = snd_pcm_prepare(m_alsapcm);
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not prepare PCM" << snd_strerror(alsaresult);
|
|
}
|
|
alsaresult = snd_pcm_start(m_alsapcm);
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not start PCM" << snd_strerror(alsaresult);
|
|
}
|
|
m_visualizertimer->start();
|
|
}
|
|
}
|
|
|
|
QIcon MixerTabWidget::mainVolumeIcon()
|
|
{
|
|
if (m_mainelement.isEmpty()) {
|
|
return KIcon(s_defaultpopupicon);
|
|
}
|
|
snd_mixer_elem_t *alsaelement = snd_mixer_first_elem(m_alsamixer);
|
|
for (; alsaelement; alsaelement = snd_mixer_elem_next(alsaelement)) {
|
|
if (snd_mixer_elem_empty(alsaelement)) {
|
|
continue;
|
|
}
|
|
const QString alsaelementname = QString::fromLocal8Bit(snd_mixer_selem_get_name(alsaelement));
|
|
if (alsaelementname != m_mainelement) {
|
|
continue;
|
|
}
|
|
// the icon represents the highest volume from all element channels
|
|
int alsaelementvolume = 0;
|
|
const bool alsahascapture = snd_mixer_selem_has_capture_volume(alsaelement);
|
|
const QList<snd_mixer_selem_channel_id_t> alsaelementchannels = kALSAChannelTypes(alsaelement, alsahascapture);
|
|
foreach (const snd_mixer_selem_channel_id_t alsaelementchannel, alsaelementchannels) {
|
|
long alsavolumemin = 0;
|
|
long alsavolumemax = 0;
|
|
long alsavolume = 0;
|
|
const bool gotvolumes = kGetChannelVolumes(
|
|
alsaelement, alsaelementchannel, alsahascapture,
|
|
&alsavolumemin, &alsavolumemax, &alsavolume
|
|
);
|
|
if (!gotvolumes) {
|
|
return KIcon(s_defaultpopupicon);
|
|
}
|
|
alsaelementvolume = qMax(alsaelementvolume, kFixedVolume(alsavolume, alsavolumemax));
|
|
}
|
|
return kMixerIcon(this, alsaelementvolume);
|
|
}
|
|
return KIcon(s_defaultpopupicon);
|
|
}
|
|
|
|
Plasma::ToolTipContent MixerTabWidget::toolTipContent() const
|
|
{
|
|
Plasma::ToolTipContent result;
|
|
const char* const tooltipiconname = (m_isdefault ? "mixer-pcm-default" : "mixer-pcm");
|
|
result.setImage(
|
|
KIconLoader::global()->loadIcon(
|
|
QString::fromLatin1(tooltipiconname),
|
|
KIconLoader::Dialog
|
|
)
|
|
);
|
|
result.setMainText(QString::fromLatin1("<center>%1</center>").arg(m_alsamixername));
|
|
result.setSubText(QString::fromLatin1("<center>%1</center>").arg(m_mainelement));
|
|
return result;
|
|
}
|
|
|
|
void MixerTabWidget::decreaseVolume()
|
|
{
|
|
foreach (MixerSlider *slider, sliders) {
|
|
if (slider->alsaelementname == m_mainelement) {
|
|
slider->setValue(qMax(slider->value() - s_volumestep, 0));
|
|
}
|
|
}
|
|
}
|
|
void MixerTabWidget::increaseVolume()
|
|
{
|
|
foreach (MixerSlider *slider, sliders) {
|
|
if (slider->alsaelementname == m_mainelement) {
|
|
slider->setValue(qMin(slider->value() + s_volumestep, 100));
|
|
}
|
|
}
|
|
}
|
|
|
|
void MixerTabWidget::slotSliderMovedOrChanged(const int value)
|
|
{
|
|
Q_ASSERT(m_alsamixer != nullptr);
|
|
MixerSlider* slider = qobject_cast<MixerSlider*>(sender());
|
|
Q_ASSERT(slider != nullptr);
|
|
snd_mixer_elem_t *alsaelement = snd_mixer_first_elem(m_alsamixer);
|
|
for (; alsaelement; alsaelement = snd_mixer_elem_next(alsaelement)) {
|
|
if (snd_mixer_elem_empty(alsaelement)) {
|
|
continue;
|
|
}
|
|
const uint alsaiterelementindex = snd_mixer_selem_get_index(alsaelement);
|
|
const QString alsaiterelementname = QString::fromLocal8Bit(snd_mixer_selem_get_name(alsaelement));
|
|
if (alsaiterelementindex == slider->alsaelementindex && alsaiterelementname == slider->alsaelementname) {
|
|
kDebug() << "Changing" << slider->alsaelementindex << "volume to" << value;
|
|
const bool alsahascapture = snd_mixer_selem_has_capture_volume(alsaelement);
|
|
if (alsahascapture) {
|
|
const int alsaresult = snd_mixer_selem_set_capture_volume(alsaelement, slider->alsaelementchannel, slider->alsaVolume());
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not set capture volume" << snd_strerror(alsaresult);
|
|
return;
|
|
}
|
|
} else {
|
|
const int alsaresult = snd_mixer_selem_set_playback_volume(alsaelement, slider->alsaelementchannel, slider->alsaVolume());
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not set playback volume" << snd_strerror(alsaresult);
|
|
return;
|
|
}
|
|
}
|
|
if (alsaiterelementname == m_mainelement) {
|
|
emit mainVolumeChanged();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
kWarning() << "Could not find the element" << slider->alsaelementindex;
|
|
}
|
|
|
|
void MixerTabWidget::slotEventsTimeout()
|
|
{
|
|
if (Q_LIKELY(m_alsamixer)) {
|
|
snd_mixer_handle_events(m_alsamixer);
|
|
}
|
|
}
|
|
|
|
void MixerTabWidget::slotVisualizerTimeout()
|
|
{
|
|
if (m_alsapcm) {
|
|
switch (snd_pcm_state(m_alsapcm)) {
|
|
case SND_PCM_STATE_PREPARED:
|
|
case SND_PCM_STATE_RUNNING: {
|
|
while (true) {
|
|
::memset(m_alsapcmbuffer, 0, sizeof(m_alsapcmbuffer));
|
|
const int alsaresult = snd_pcm_readi(m_alsapcm, m_alsapcmbuffer, s_alsapcmbuffersize);
|
|
if (alsaresult < 1) {
|
|
if (errno == EAGAIN) {
|
|
// NOTE: this happens when the data is drained which is quite often
|
|
// because the visualization timer is 50ms, there is no data and
|
|
// snd_pcm_recover() does not handle it anyway
|
|
break;
|
|
}
|
|
kWarning() << "Could not read PCM data" << snd_strerror(alsaresult);
|
|
snd_pcm_recover(m_alsapcm, alsaresult, 1);
|
|
break;
|
|
}
|
|
QList<double> alsapcmsamples;
|
|
alsapcmsamples.reserve(alsaresult);
|
|
for (int i = 0; i < alsaresult; i++) {
|
|
alsapcmsamples.append(double(m_alsapcmbuffer[i]));
|
|
}
|
|
// qDebug() << Q_FUNC_INFO << alsasamples;
|
|
m_signalplotter->addSample(alsapcmsamples);
|
|
}
|
|
break;
|
|
}
|
|
case SND_PCM_STATE_XRUN: {
|
|
kWarning() << "PCM xrun" << m_alsamixername;
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: this is called only on external events
|
|
int k_alsa_element_callback(snd_mixer_elem_t *alsaelement, unsigned int alsamask)
|
|
{
|
|
// qDebug() << Q_FUNC_INFO << alsamask;
|
|
if (alsamask & SND_CTL_EVENT_MASK_VALUE) {
|
|
MixerTabWidget* mixertabwidget = static_cast<MixerTabWidget*>(snd_mixer_elem_get_callback_private(alsaelement));
|
|
Q_ASSERT(mixertabwidget != nullptr);
|
|
const uint alsaeventelementindex = snd_mixer_selem_get_index(alsaelement);
|
|
const QString alsaeventelementname = QString::fromLocal8Bit(snd_mixer_selem_get_name(alsaelement));
|
|
foreach (MixerSlider *slider, mixertabwidget->sliders) {
|
|
if (alsaeventelementindex == slider->alsaelementindex && alsaeventelementname == slider->alsaelementname) {
|
|
const bool alsahascapture = snd_mixer_selem_has_capture_volume(alsaelement);
|
|
long alsavolumemin = 0;
|
|
long alsavolumemax = 0;
|
|
long alsavolume = 0;
|
|
const bool gotvolumes = kGetChannelVolumes(
|
|
alsaelement, slider->alsaelementchannel, alsahascapture,
|
|
&alsavolumemin, &alsavolumemax, &alsavolume
|
|
);
|
|
if (!gotvolumes) {
|
|
continue;
|
|
}
|
|
slider->setVolume(alsavolumemin, alsavolumemax, alsavolume);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
class MixerWidget : public Plasma::TabBar
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
MixerWidget(MixerApplet *mixer);
|
|
|
|
void showVisualizer(const bool show, const uint scale, const QColor &color, const bool icon);
|
|
void pauseVisualizer(const bool pause);
|
|
void decreaseVolume();
|
|
void increaseVolume();
|
|
|
|
private Q_SLOTS:
|
|
void slotMainVolumeChanged();
|
|
void slotCurrentChanged(const int index);
|
|
|
|
private:
|
|
MixerApplet* m_mixer;
|
|
QList<MixerTabWidget*> m_tabwidgets;
|
|
};
|
|
|
|
MixerWidget::MixerWidget(MixerApplet* mixer)
|
|
: Plasma::TabBar(mixer),
|
|
m_mixer(mixer)
|
|
{
|
|
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
|
setMinimumSize(s_minimumsize);
|
|
|
|
QStringList uniquemixers;
|
|
int alsacard = s_defaultsoundcard;
|
|
while (true) {
|
|
int alsaresult = snd_card_next(&alsacard);
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not get card" << snd_strerror(alsaresult);
|
|
break;
|
|
}
|
|
|
|
const bool isdefault = (alsacard == s_defaultsoundcard);
|
|
const QByteArray alsacardname = (isdefault ? "default" : "hw:" + QByteArray::number(alsacard));
|
|
snd_ctl_t *alsactl = nullptr;
|
|
alsaresult = snd_ctl_open(&alsactl, alsacardname.constData(), SND_CTL_NONBLOCK);
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not open card" << snd_strerror(alsaresult);
|
|
break;
|
|
}
|
|
|
|
snd_ctl_card_info_t *alsacardinfo = nullptr;
|
|
snd_ctl_card_info_alloca(&alsacardinfo);
|
|
alsaresult = snd_ctl_card_info(alsactl, alsacardinfo);
|
|
if (alsaresult != 0) {
|
|
kWarning() << "Could not open card" << snd_strerror(alsaresult);
|
|
snd_ctl_close(alsactl);
|
|
break;
|
|
}
|
|
|
|
const QString alsamixername = QString::fromLocal8Bit(snd_ctl_card_info_get_mixername(alsacardinfo));
|
|
snd_ctl_close(alsactl);
|
|
// default may be duplicate
|
|
if (uniquemixers.contains(alsamixername)) {
|
|
if (isdefault) {
|
|
break;
|
|
}
|
|
alsacard++;
|
|
continue;
|
|
}
|
|
uniquemixers.append(alsamixername);
|
|
|
|
MixerTabWidget* mixertabwidget = new MixerTabWidget(isdefault, alsamixername, this);
|
|
if (mixertabwidget->setup(alsacardname)) {
|
|
if (isdefault) {
|
|
// default sound card goes to the front
|
|
insertTab(0, KIcon("mixer-pcm-default"), alsamixername, mixertabwidget);
|
|
m_tabwidgets.prepend(mixertabwidget);
|
|
} else {
|
|
addTab(KIcon("mixer-pcm"), alsamixername, mixertabwidget);
|
|
m_tabwidgets.append(mixertabwidget);
|
|
}
|
|
connect(
|
|
mixertabwidget, SIGNAL(mainVolumeChanged()),
|
|
this, SLOT(slotMainVolumeChanged())
|
|
);
|
|
} else {
|
|
delete mixertabwidget;
|
|
}
|
|
|
|
// the loop is until -1 which happens to be the default card
|
|
if (isdefault) {
|
|
break;
|
|
}
|
|
alsacard++;
|
|
}
|
|
|
|
setTabBarShown(m_tabwidgets.size() > 1);
|
|
setCurrentIndex(0);
|
|
if (m_tabwidgets.size() > 0) {
|
|
MixerTabWidget* firstmixertabwidget = m_tabwidgets.first();
|
|
m_mixer->setStatus(Plasma::ItemStatus::ActiveStatus);
|
|
m_mixer->setPopupIcon(firstmixertabwidget->mainVolumeIcon());
|
|
Plasma::ToolTipManager::self()->setContent(m_mixer, firstmixertabwidget->toolTipContent());
|
|
} else {
|
|
Plasma::Label* label = new Plasma::Label(this);
|
|
label->setMinimumSize(s_minimumsize);
|
|
label->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
|
label->setAlignment(Qt::AlignCenter);
|
|
label->setText(i18n("No sound cards found"));
|
|
addTab(QString::fromLatin1("nocards"), label);
|
|
m_mixer->setStatus(Plasma::ItemStatus::PassiveStatus);
|
|
}
|
|
connect(
|
|
this, SIGNAL(currentChanged(int)),
|
|
this, SLOT(slotCurrentChanged(int))
|
|
);
|
|
}
|
|
|
|
void MixerWidget::showVisualizer(const bool show, const uint scale, const QColor &color, const bool icon)
|
|
{
|
|
foreach (MixerTabWidget *mixertabwidget, m_tabwidgets) {
|
|
mixertabwidget->showVisualizer(show, scale, color, icon);
|
|
}
|
|
}
|
|
|
|
void MixerWidget::pauseVisualizer(const bool pause)
|
|
{
|
|
foreach (MixerTabWidget *mixertabwidget, m_tabwidgets) {
|
|
mixertabwidget->pauseVisualizer(pause);
|
|
}
|
|
}
|
|
|
|
void MixerWidget::decreaseVolume()
|
|
{
|
|
if (m_tabwidgets.size() < 1) {
|
|
return;
|
|
}
|
|
MixerTabWidget* mixertabwidget = m_tabwidgets.at(currentIndex());
|
|
mixertabwidget->decreaseVolume();
|
|
}
|
|
|
|
void MixerWidget::increaseVolume()
|
|
{
|
|
if (m_tabwidgets.size() < 1) {
|
|
return;
|
|
}
|
|
MixerTabWidget* mixertabwidget = m_tabwidgets.at(currentIndex());
|
|
mixertabwidget->increaseVolume();
|
|
}
|
|
|
|
void MixerWidget::slotMainVolumeChanged()
|
|
{
|
|
MixerTabWidget* mixertabwidget = qobject_cast<MixerTabWidget*>(sender());
|
|
if (m_tabwidgets.indexOf(mixertabwidget) == currentIndex()) {
|
|
m_mixer->setPopupIcon(mixertabwidget->mainVolumeIcon());
|
|
}
|
|
}
|
|
|
|
void MixerWidget::slotCurrentChanged(const int index)
|
|
{
|
|
Q_ASSERT(index < m_tabwidgets.size());
|
|
MixerTabWidget* mixertabwidget = m_tabwidgets.at(index);
|
|
Q_ASSERT(mixertabwidget != nullptr);
|
|
m_mixer->setPopupIcon(mixertabwidget->mainVolumeIcon());
|
|
Plasma::ToolTipManager::self()->setContent(m_mixer, mixertabwidget->toolTipContent());
|
|
}
|
|
|
|
|
|
MixerApplet::MixerApplet(QObject *parent, const QVariantList &args)
|
|
: Plasma::PopupApplet(parent, args),
|
|
m_mixerwidget(nullptr),
|
|
m_showvisualizer(s_showvisualizer),
|
|
m_visualizerscale(s_visualizerscale),
|
|
m_visualizericon(s_visualizericon),
|
|
m_visualizerbox(nullptr),
|
|
m_visualizerscalelabel(nullptr),
|
|
m_visualizerscalebox(nullptr),
|
|
m_visualizerbutton(nullptr),
|
|
m_spacer(nullptr)
|
|
{
|
|
KGlobal::locale()->insertCatalog("plasma_applet_mixer");
|
|
setAspectRatioMode(Plasma::AspectRatioMode::IgnoreAspectRatio);
|
|
setHasConfigurationInterface(true);
|
|
setPopupIcon(kMixerIcon(this, 0));
|
|
}
|
|
|
|
MixerApplet::~MixerApplet()
|
|
{
|
|
delete m_mixerwidget;
|
|
}
|
|
|
|
void MixerApplet::init()
|
|
{
|
|
Plasma::PopupApplet::init();
|
|
|
|
m_mixerwidget = new MixerWidget(this);
|
|
slotThemeChanged();
|
|
connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(slotThemeChanged()));
|
|
}
|
|
|
|
void MixerApplet::createConfigurationInterface(KConfigDialog *parent)
|
|
{
|
|
QWidget* widget = new QWidget();
|
|
QGridLayout* widgetlayout = new QGridLayout(widget);
|
|
m_visualizerbox = new QCheckBox(widget);
|
|
m_visualizerbox->setChecked(m_showvisualizer);
|
|
m_visualizerbox->setText(i18n("Show visualizer"));
|
|
widgetlayout->addWidget(m_visualizerbox, 0, 0, 1, 2);
|
|
|
|
m_visualizerscalelabel = new QLabel(widget);
|
|
m_visualizerscalelabel->setText(i18n("Smooth-factor"));
|
|
widgetlayout->addWidget(m_visualizerscalelabel, 1, 0, 1, 1, Qt::AlignRight | Qt::AlignVCenter);
|
|
|
|
m_visualizerscalebox = new KIntNumInput(widget);
|
|
m_visualizerscalebox->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
|
|
m_visualizerscalebox->setRange(1, 5);
|
|
m_visualizerscalebox->setValue(m_visualizerscale);
|
|
widgetlayout->addWidget(m_visualizerscalebox, 1, 1, 1, 1);
|
|
|
|
const QColor defaultvisualizercolor = kDefaultVisualizerColor();
|
|
QColor visualizercolor = m_visualizercolor;
|
|
if (!visualizercolor.isValid()) {
|
|
visualizercolor = defaultvisualizercolor;
|
|
}
|
|
m_visualizerbutton = new KColorButton(widget);
|
|
m_visualizerbutton->setDefaultColor(defaultvisualizercolor);
|
|
m_visualizerbutton->setColor(visualizercolor);
|
|
widgetlayout->addWidget(m_visualizerbutton, 2, 0, 1, 2);
|
|
|
|
m_visualizericonbox = new QCheckBox(widget);
|
|
m_visualizericonbox->setChecked(m_visualizericon);
|
|
m_visualizericonbox->setText(i18n("Show icon"));
|
|
widgetlayout->addWidget(m_visualizericonbox, 3, 0, 1, 2);
|
|
|
|
m_spacer = new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
widgetlayout->addItem(m_spacer, 4, 0, 1, 2);
|
|
|
|
widget->setLayout(widgetlayout);
|
|
parent->addPage(widget, i18n("Visualizer"), "player-volume");
|
|
|
|
slotVisualizerToggled(m_showvisualizer);
|
|
connect(parent, SIGNAL(applyClicked()), this, SLOT(slotConfigAccepted()));
|
|
connect(parent, SIGNAL(okClicked()), this, SLOT(slotConfigAccepted()));
|
|
connect(m_visualizerbox, SIGNAL(toggled(bool)), this, SLOT(slotVisualizerToggled(bool)));
|
|
connect(m_visualizerbox, SIGNAL(toggled(bool)), parent, SLOT(settingsModified()));
|
|
connect(m_visualizerscalebox, SIGNAL(valueChanged(int)), parent, SLOT(settingsModified()));
|
|
connect(m_visualizerbutton, SIGNAL(changed(QColor)), parent, SLOT(settingsModified()));
|
|
connect(m_visualizericonbox, SIGNAL(toggled(bool)), parent, SLOT(settingsModified()));
|
|
}
|
|
|
|
QGraphicsWidget* MixerApplet::graphicsWidget()
|
|
{
|
|
return m_mixerwidget;
|
|
}
|
|
|
|
void MixerApplet::popupEvent(bool show)
|
|
{
|
|
if (!m_mixerwidget) {
|
|
return;
|
|
}
|
|
m_mixerwidget->pauseVisualizer(!show);
|
|
}
|
|
|
|
void MixerApplet::wheelEvent(QGraphicsSceneWheelEvent *event)
|
|
{
|
|
if (!m_mixerwidget) {
|
|
return;
|
|
}
|
|
if (event->delta() < 0) {
|
|
m_mixerwidget->decreaseVolume();
|
|
} else {
|
|
m_mixerwidget->increaseVolume();
|
|
}
|
|
}
|
|
|
|
void MixerApplet::slotVisualizerToggled(bool toggled)
|
|
{
|
|
m_visualizerscalebox->setEnabled(toggled);
|
|
m_visualizerbutton->setEnabled(toggled);
|
|
m_visualizericonbox->setEnabled(toggled);
|
|
}
|
|
|
|
void MixerApplet::slotConfigAccepted()
|
|
{
|
|
Q_ASSERT(m_mixerwidget != nullptr);
|
|
Q_ASSERT(m_visualizerbox != nullptr);
|
|
Q_ASSERT(m_visualizerscalebox != nullptr);
|
|
Q_ASSERT(m_visualizerbutton != nullptr);
|
|
Q_ASSERT(m_visualizericonbox != nullptr);
|
|
m_showvisualizer = m_visualizerbox->isChecked();
|
|
m_visualizerscale = m_visualizerscalebox->value();
|
|
m_visualizercolor = m_visualizerbutton->color();
|
|
m_visualizericon = m_visualizericonbox->isChecked();
|
|
KConfigGroup configgroup = config();
|
|
configgroup.writeEntry("showVisualizer", m_showvisualizer);
|
|
configgroup.writeEntry("visualizerScale", m_visualizerscale);
|
|
if (m_visualizercolor == kDefaultVisualizerColor()) {
|
|
configgroup.writeEntry("visualizerColor", QColor());
|
|
} else {
|
|
configgroup.writeEntry("visualizerColor", m_visualizercolor);
|
|
}
|
|
configgroup.writeEntry("visualizerIcon", m_visualizericon);
|
|
emit configNeedsSaving();
|
|
m_mixerwidget->showVisualizer(m_showvisualizer, m_visualizerscale, m_visualizercolor, m_visualizericon);
|
|
m_mixerwidget->pauseVisualizer(isIconified() && !isPopupShowing());
|
|
}
|
|
|
|
void MixerApplet::slotThemeChanged()
|
|
{
|
|
Q_ASSERT(m_mixerwidget != nullptr);
|
|
KConfigGroup configgroup = config();
|
|
m_showvisualizer = configgroup.readEntry("showVisualizer", s_showvisualizer);
|
|
m_visualizerscale = configgroup.readEntry("visualizerScale", s_visualizerscale);
|
|
m_visualizercolor = configgroup.readEntry("visualizerColor", QColor());
|
|
if (!m_visualizercolor.isValid()) {
|
|
m_visualizercolor = kDefaultVisualizerColor();
|
|
}
|
|
m_visualizericon = configgroup.readEntry("visualizerIcon", s_visualizericon);
|
|
|
|
m_mixerwidget->showVisualizer(m_showvisualizer, m_visualizerscale, m_visualizercolor, m_visualizericon);
|
|
m_mixerwidget->pauseVisualizer(isIconified() && !isPopupShowing());
|
|
}
|
|
|
|
K_EXPORT_PLASMA_APPLET(mixer, MixerApplet)
|
|
|
|
#include "moc_mixer.cpp"
|
|
#include "mixer.moc"
|