mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-24 10:52:49 +00:00
410 lines
10 KiB
C++
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"
|