kdelibs/kutils/kspeech/kspeech.cpp
Ivailo Monev 116cd8f60e kutils: set KSpeech voice from config only if the voice value is not empty
Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2022-10-16 14:04:38 +03:00

410 lines
10 KiB
C++

/* This file is part of the KDE libraries
Copyright (C) 2022 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 "kspeech.h"
#include <QMutex>
#include <QCoreApplication>
#include <kdebug.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#if defined(HAVE_SPEECHD)
# include <speech-dispatcher/libspeechd.h>
#endif
typedef QMap<int, KSpeechPrivate*> KSpeechMap;
Q_GLOBAL_STATIC(QMutex, globalKSpeechInstancesMutex)
Q_GLOBAL_STATIC(KSpeechMap, globalKSpeechInstances)
// NOTE: the callback function is not called in the same thread as the object that opens the
// connection
class KSpeechPrivate : public QObject
{
Q_OBJECT
public:
KSpeechPrivate(QObject *parent);
~KSpeechPrivate();
#if defined(HAVE_SPEECHD)
void openSpeechD();
void closeSpeechD();
static void speechCallback(size_t msg_id, size_t client_id, SPDNotificationType state);
#endif
Q_SIGNALS:
void signalJobStateChanged(int jobNum, int state);
private:
friend KSpeech;
#if defined(HAVE_SPEECHD)
SPDConnection* m_speechd;
int m_speechdid;
QByteArray m_voice;
#endif
QByteArray m_speechid;
};
KSpeechPrivate::KSpeechPrivate(QObject *parent)
: QObject(parent)
#if defined(HAVE_SPEECHD)
, m_speechd(nullptr),
m_speechdid(0)
#endif
{
m_speechid = QCoreApplication::applicationName().toLocal8Bit();
}
KSpeechPrivate::~KSpeechPrivate()
{
#if defined(HAVE_SPEECHD)
Q_ASSERT(!m_speechd);
Q_ASSERT(m_speechdid == 0);
#endif
}
#if defined(HAVE_SPEECHD)
void KSpeechPrivate::openSpeechD()
{
Q_ASSERT(!m_speechd);
m_speechd = spd_open(m_speechid.constData(), "main", NULL, SPD_MODE_THREADED);
if (Q_UNLIKELY(!m_speechd)) {
kWarning() << "Could not open speech-dispatcher connection";
return;
}
m_speechdid = spd_get_client_id(m_speechd);
{
QMutexLocker locker(globalKSpeechInstancesMutex());
globalKSpeechInstances()->insert(m_speechdid, this);
}
m_speechd->callback_begin = KSpeechPrivate::speechCallback;
m_speechd->callback_end = KSpeechPrivate::speechCallback;
m_speechd->callback_cancel = KSpeechPrivate::speechCallback;
spd_set_notification_on(m_speechd, SPD_BEGIN);
spd_set_notification_on(m_speechd, SPD_END);
spd_set_notification_on(m_speechd, SPD_CANCEL);
KConfig kconfig("kspeechrc", KConfig::SimpleConfig);
KConfigGroup kconfiggroup = kconfig.group(m_speechid);
const int volume = kconfiggroup.readEntry("volume", 100);
const int pitch = kconfiggroup.readEntry("pitch", 0);
const QByteArray voice = kconfiggroup.readEntry("voice", QByteArray());
KSpeech* kspeech = qobject_cast<KSpeech*>(parent());
Q_ASSERT(kspeech);
kspeech->setVolume(volume);
kspeech->setPitch(pitch);
if (!voice.isEmpty()) {
kspeech->setVoice(voice);
}
}
void KSpeechPrivate::closeSpeechD()
{
if (m_speechd) {
spd_close(m_speechd);
m_speechd = nullptr;
QMutexLocker locker(globalKSpeechInstancesMutex());
globalKSpeechInstances()->remove(m_speechdid);
m_speechdid = 0;
}
}
void KSpeechPrivate::speechCallback(size_t msg_id, size_t client_id, SPDNotificationType state)
{
QMutexLocker locker(globalKSpeechInstancesMutex());
KSpeechPrivate* kspeechprivate = globalKSpeechInstances()->value(static_cast<int>(client_id), 0);
if (Q_UNLIKELY(!kspeechprivate)) {
kWarning() << "Null kspeech private pointer";
return;
}
const int jobNum = msg_id;
switch (state) {
case SPD_EVENT_BEGIN: {
emit kspeechprivate->signalJobStateChanged(jobNum, KSpeech::JobStarted);
break;
}
case SPD_EVENT_END: {
emit kspeechprivate->signalJobStateChanged(jobNum, KSpeech::JobFinished);
break;
}
case SPD_EVENT_CANCEL: {
emit kspeechprivate->signalJobStateChanged(jobNum, KSpeech::JobCanceled);
break;
}
default: {
break;
}
}
}
#endif // HAVE_SPEECHD
KSpeech::KSpeech(QObject* parent)
: QObject(parent),
d(new KSpeechPrivate(this))
{
connect(
d, SIGNAL(signalJobStateChanged(int,int)),
this, SLOT(_jobStateChanged(int,int))
);
#if defined(HAVE_SPEECHD)
d->openSpeechD();
#endif
}
KSpeech::~KSpeech()
{
#if defined(HAVE_SPEECHD)
d->closeSpeechD();
#endif
delete d;
}
bool KSpeech::isSupported()
{
#if defined(HAVE_SPEECHD)
return true;
#else
return false;
#endif
}
void KSpeech::setSpeechID(const QString &id)
{
#if defined(HAVE_SPEECHD)
d->closeSpeechD();
#endif
d->m_speechid = id.toUtf8();
#if defined(HAVE_SPEECHD)
d->openSpeechD();
#endif
}
int KSpeech::volume() const
{
#if defined(HAVE_SPEECHD)
if (Q_UNLIKELY(!d->m_speechd)) {
kDebug() << "Null speech-dispatcher pointer";
return 0;
}
return spd_get_volume(d->m_speechd);
#else
return 0;
#endif
}
bool KSpeech::setVolume(const int volume)
{
#if defined(HAVE_SPEECHD)
if (Q_UNLIKELY(!d->m_speechd)) {
kDebug() << "Null speech-dispatcher pointer";
return false;
}
if (Q_UNLIKELY(volume < -100 || volume > 100)) {
kWarning() << "Invalid volume value" << volume;
return false;
}
const int speechdresult = spd_set_volume(d->m_speechd, volume);
if (Q_UNLIKELY(speechdresult < 0)) {
kWarning() << "Speech-dispatcher set volume failed";
return false;
}
return true;
#else
return false;
#endif
}
int KSpeech::pitch() const
{
#if defined(HAVE_SPEECHD)
if (Q_UNLIKELY(!d->m_speechd)) {
kDebug() << "Null speech-dispatcher pointer";
return 0;
}
return spd_get_voice_pitch(d->m_speechd);
#else
return 0;
#endif
}
bool KSpeech::setPitch(const int pitch)
{
#if defined(HAVE_SPEECHD)
if (Q_UNLIKELY(!d->m_speechd)) {
kDebug() << "Null speech-dispatcher pointer";
return false;
}
if (Q_UNLIKELY(pitch < -100 || pitch > 100)) {
kWarning() << "Invalid pitch value" << pitch;
return false;
}
const int speechdresult = spd_set_voice_pitch(d->m_speechd, pitch);
if (Q_UNLIKELY(speechdresult < 0)) {
kWarning() << "Speech-dispatcher set pitch failed";
return false;
}
return true;
#else
return false;
#endif
}
QByteArray KSpeech::voice() const
{
#if defined(HAVE_SPEECHD)
return d->m_voice;
#else
return QByteArray();
#endif
}
QList<QByteArray> KSpeech::voices() const
{
QList<QByteArray> result;
#if defined(HAVE_SPEECHD)
if (Q_UNLIKELY(!d->m_speechd)) {
kDebug() << "Null speech-dispatcher pointer";
return result;
}
SPDVoice** speechdvoices = spd_list_synthesis_voices(d->m_speechd);
if (Q_UNLIKELY(!speechdvoices)) {
kWarning() << "Null speech-dispatcher voices pointer";
return result;
}
int i = 0;
while (speechdvoices[i]) {
result.append(speechdvoices[i]->name);
i++;
}
free_spd_voices(speechdvoices);
#endif
return result;
}
bool KSpeech::setVoice(const QByteArray &voice)
{
#if defined(HAVE_SPEECHD)
if (Q_UNLIKELY(!d->m_speechd)) {
kDebug() << "Null speech-dispatcher pointer";
return false;
}
// could be empty if not set in kspeechrc
if (voice.isEmpty()) {
kWarning() << "Empty voice" << voice;
return false;
} else if (Q_UNLIKELY(!KSpeech::voices().contains(voice))) {
// NOTE: if voice is invalid speech-dispatcher will not dispatch anything
kWarning() << "Invalid voice value" << voice;
return false;
}
const int speechdresult = spd_set_synthesis_voice(d->m_speechd, voice.constData());
if (Q_UNLIKELY(speechdresult < 0)) {
kWarning() << "Speech-dispatcher set voice failed";
return false;
}
d->m_voice = voice;
return true;
#else
return false;
#endif
}
int KSpeech::say(const QString &text)
{
#if defined(HAVE_SPEECHD)
if (Q_UNLIKELY(!d->m_speechd)) {
kDebug() << "Null speech-dispatcher pointer";
return 0;
}
const QByteArray textbytes = text.toUtf8();
const int jobNum = spd_say(d->m_speechd, SPD_TEXT, textbytes.constData());
if (Q_UNLIKELY(jobNum < 0)) {
kWarning() << "Speech-dispatcher say failed";
return 0;
}
return jobNum;
#else
kWarning() << "KSpeech is a stub";
return 0;
#endif // HAVE_SPEECHD
}
bool KSpeech::removeAllJobs()
{
#if defined(HAVE_SPEECHD)
if (Q_UNLIKELY(!d->m_speechd)) {
kDebug() << "Null speech-dispatcher pointer";
return false;
}
const int speechdresult = spd_stop(d->m_speechd);
if (Q_UNLIKELY(speechdresult < 0)) {
kWarning() << "Speech-dispatcher stop failed";
return false;
}
return true;
#else
kWarning() << "KSpeech is a stub";
return false;
#endif
}
bool KSpeech::removeJob(int jobNum)
{
#if defined(HAVE_SPEECHD)
if (Q_UNLIKELY(!d->m_speechd)) {
kWarning() << "Null speech-dispatcher pointer";
return false;
}
const int speechdresult = spd_stop_uid(d->m_speechd, jobNum);
if (Q_UNLIKELY(speechdresult < 0)) {
kWarning() << "Speech-dispatcher stop uid failed";
return false;
}
return true;
#else
kWarning() << "KSpeech is a stub";
return false;
#endif
}
void KSpeech::_jobStateChanged(int jobNum, int state)
{
emit jobStateChanged(jobNum, state);
}
#include "kspeech.moc"