Newer
Older
* Copyright (c) 2010-2022 Belledonne Communications SARL.
Andrea Gianarda
committed
* This file is part of Liblinphone
* (see https://gitlab.linphone.org/BC/public/liblinphone).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <signal.h>
Andrea Gianarda
committed
#include <iomanip>
#include <iostream>
Andrea Gianarda
committed
#include <sstream>
#ifdef HAVE_READLINE
Andrea Gianarda
committed
#include <readline/readline.h>
Andrea Gianarda
committed
#include <bctoolbox/defs.h>
#include "commands/adaptive-jitter-compensation.h"
#include "commands/answer.h"
#include "commands/audio-codec-get.h"
#include "commands/audio-codec-move.h"
#include "commands/audio-codec-toggle.h"
Andrea Gianarda
committed
#include "commands/audio-stream-stop.h"
#include "commands/auth-infos-clear.h"
#include "commands/call-mute.h"
Andrea Gianarda
committed
#include "commands/call-pause.h"
#include "commands/call-resume.h"
Andrea Gianarda
committed
#include "commands/call-stats.h"
#include "commands/call-status.h"
#include "commands/call-transfer.h"
Andrea Gianarda
committed
#include "commands/call.h"
#include "commands/cn.h"
#include "commands/conference.h"
Andrea Gianarda
committed
#include "commands/configcommand.h"
Simon Morlat
committed
#include "commands/contact.h"
Andrea Gianarda
committed
#include "commands/jitterbuffer.h"
Andrea Gianarda
committed
#include "commands/message.h"
Andrea Gianarda
committed
#include "commands/netsim.h"
Andrea Gianarda
committed
#include "commands/play.h"
Andrea Gianarda
committed
#include "commands/quit.h"
#include "commands/register-info.h"
Andrea Gianarda
committed
#include "commands/register.h"
#include "commands/terminate.h"
#include "commands/unregister.h"
Andrea Gianarda
committed
#include "commands/video.h"
#include "daemon.h"
Andrea Gianarda
committed
#define INT_TO_VOIDPTR(i) ((void *)(intptr_t)(i))
#ifndef WIN32
#else
#include <windows.h>
void usleep(int waitTime) {
Andrea Gianarda
committed
Sleep(waitTime / 1000);
}
#endif
#ifdef HAVE_READLINE
#define LICENCE_GPL
#else
#define LICENCE_COMMERCIAL
#endif
Andrea Gianarda
committed
const char *const ice_state_str[] = {
"Not activated", /* LinphoneIceStateNotActivated */
"Failed", /* LinphoneIceStateFailed */
"In progress", /* LinphoneIceStateInProgress */
"Host connection", /* LinphoneIceStateHostConnection */
"Reflexive connection", /* LinphoneIceStateReflexiveConnection */
"Relayed connection" /* LinphoneIceStateRelayConnection */
void *Daemon::iterateThread(void *arg) {
Andrea Gianarda
committed
Daemon *daemon = (Daemon *)arg;
while (daemon->mRunning) {
ms_mutex_lock(&daemon->mMutex);
daemon->iterate();
ms_mutex_unlock(&daemon->mMutex);
usleep(20000);
}
return 0;
}
CallEvent::CallEvent(Daemon *daemon, LinphoneCall *call, LinphoneCallState state) : Event("call-state-changed") {
François Grisez
committed
LinphoneCallLog *callLog = linphone_call_get_call_log(call);
const LinphoneAddress *fromAddr = linphone_call_log_get_from_address(callLog);
François Grisez
committed
char *fromStr = linphone_address_as_string(fromAddr);
François Grisez
committed
ostr << "Event: " << linphone_call_state_to_string(state) << "\n";
ostr << "From: " << fromStr << "\n";
setBody(ostr.str());
François Grisez
committed
bctbx_free(fromStr);
Andrea Gianarda
committed
DtmfEvent::DtmfEvent(Daemon *daemon, LinphoneCall *call, int dtmf) : Event("receiving-tone") {
ostringstream ostr;
char *remote = linphone_call_get_remote_address_as_string(call);
Andrea Gianarda
committed
ostr << "Tone: " << (char)dtmf << "\n";
setBody(ostr.str());
static ostream &printCallStatsHelper(ostream &ostr, const LinphoneCallStats *stats, const string &prefix) {
ostr << prefix << "ICE state: " << ice_state_str[linphone_call_stats_get_ice_state(stats)] << "\n";
ostr << prefix << "RoundTripDelay: " << linphone_call_stats_get_round_trip_delay(stats) << "\n";
Andrea Gianarda
committed
// ostr << prefix << "Jitter: " << stats->jitter_stats.jitter << "\n";
// ostr << prefix << "MaxJitter: " << stats->jitter_stats.max_jitter << "\n";
// ostr << prefix << "SumJitter: " << stats->jitter_stats.sum_jitter << "\n";
// ostr << prefix << "MaxJitterTs: " << stats->jitter_stats.max_jitter_ts << "\n";
ostr << prefix << "JitterBufferSizeMs: " << linphone_call_stats_get_jitter_buffer_size_ms(stats) << "\n";
Andrea Gianarda
committed
ostr << prefix << "Received-InterarrivalJitter: " << linphone_call_stats_get_receiver_interarrival_jitter(stats)
<< "\n";
ostr << prefix << "Received-FractionLost: " << linphone_call_stats_get_receiver_loss_rate(stats) << "\n";
ostr << prefix << "Sent-InterarrivalJitter: " << linphone_call_stats_get_sender_interarrival_jitter(stats) << "\n";
ostr << prefix << "Sent-FractionLost: " << linphone_call_stats_get_sender_loss_rate(stats) << "\n";
Andrea Gianarda
committed
CallStatsEvent::CallStatsEvent(Daemon *daemon, LinphoneCall *call, const LinphoneCallStats *stats)
: Event("call-stats") {
const LinphoneCallParams *callParams = linphone_call_get_current_params(call);
const char *prefix = "";
ostringstream ostr;
ostr << "Id: " << daemon->updateCallId(call) << "\n";
ostr << "Type: ";
if (linphone_call_stats_get_type(stats) == LINPHONE_CALL_STATS_AUDIO) {
ostr << "Audio";
ostr << "Video";
ostr << "\n";
if (linphone_call_stats_get_type(stats) == LINPHONE_CALL_STATS_AUDIO) {
Simon Morlat
committed
const LinphonePayloadType *audioCodec = linphone_call_params_get_used_audio_payload_type(callParams);
ostr << PayloadTypeResponse(linphone_call_get_core(call), audioCodec, -1, prefix, false).getBody() << "\n";
} else {
Simon Morlat
committed
const LinphonePayloadType *videoCodec = linphone_call_params_get_used_video_payload_type(callParams);
ostr << PayloadTypeResponse(linphone_call_get_core(call), videoCodec, -1, prefix, false).getBody() << "\n";
setBody(ostr.str());
Andrea Gianarda
committed
AudioStreamStatsEvent::AudioStreamStatsEvent(Daemon *daemon, AudioStream *stream, const LinphoneCallStats *stats)
: Event("audio-stream-stats") {
ostr << "Id: " << daemon->updateAudioStreamId(stream) << "\n";
ostr << "Type: ";
if (linphone_call_stats_get_type(stats) == LINPHONE_CALL_STATS_AUDIO) {
ostr << "Audio";
ostr << "Video";
ostr << "\n";
setBody(ostr.str());
Andrea Gianarda
committed
CallPlayingStatsEvent::CallPlayingStatsEvent(BCTBX_UNUSED(Daemon *daemon), int id) : Event("call-playing-complete") {
setBody(ostr.str());
Simon Morlat
committed
PayloadTypeResponse::PayloadTypeResponse(BCTBX_UNUSED(LinphoneCore *core),
const LinphonePayloadType *payloadType,
int index,
const string &prefix,
bool enabled_status) {
Andrea Gianarda
committed
if (index >= 0) ostr << prefix << "Index: " << index << "\n";
Simon Morlat
committed
const char *recv_fmtp = linphone_payload_type_get_recv_fmtp(payloadType);
const char *send_fmtp = linphone_payload_type_get_send_fmtp(payloadType);
ostr << prefix << "Payload-type-number: " << linphone_payload_type_get_number(payloadType) << "\n";
ostr << prefix << "Clock-rate: " << linphone_payload_type_get_clock_rate(payloadType) << "\n";
ostr << prefix << "Bitrate: " << linphone_payload_type_get_normal_bitrate(payloadType) << "\n";
ostr << prefix << "Mime: " << linphone_payload_type_get_mime_type(payloadType) << "\n";
ostr << prefix << "Channels: " << linphone_payload_type_get_channels(payloadType) << "\n";
ostr << prefix << "Recv-fmtp: " << (recv_fmtp ? recv_fmtp : "") << "\n";
ostr << prefix << "Send-fmtp: " << (send_fmtp ? send_fmtp : "") << "\n";
Simon Morlat
committed
ostr << prefix << "Enabled: " << (linphone_payload_type_enabled(payloadType) == TRUE ? "true" : "false")
Andrea Gianarda
committed
<< "\n";
Andrea Gianarda
committed
PayloadTypeParser::PayloadTypeParser(LinphoneCore *core, const string &mime_type, bool accept_all)
: mAll(false), mSuccesful(true), mPayloadType(NULL), mPosition(-1) {
int number = -1;
if (accept_all && (mime_type.compare("ALL") == 0)) {
mAll = true;
return;
}
Ghislain MARY
committed
istringstream ist(mime_type);
Simon Morlat
committed
ist >> number;
Ghislain MARY
committed
if (ist.fail()) {
Andrea Gianarda
committed
char type[64] = {0};
Ghislain MARY
committed
int rate, channels;
if (sscanf(mime_type.c_str(), "%63[^/]/%u/%u", type, &rate, &channels) != 3) {
Ghislain MARY
committed
mSuccesful = false;
return;
}
Simon Morlat
committed
mPayloadType = linphone_core_get_payload_type(core, type, rate, channels);
if (mPayloadType) {
bctbx_list_t *codecs = linphone_core_get_audio_payload_types(core);
bctbx_list_t *elem;
int index = 0;
for (elem = codecs; elem != NULL; elem = elem->next, ++index) {
if (linphone_payload_type_weak_equals((LinphonePayloadType *)elem->data, mPayloadType)) {
mPosition = index;
break;
}
}
bctbx_list_free_with_data(codecs, (bctbx_list_free_func)linphone_payload_type_unref);
}
Andrea Gianarda
committed
} else if (number != -1) {
Simon Morlat
committed
bctbx_list_t *codecs = linphone_core_get_audio_payload_types(core);
bctbx_list_t *elem;
for (elem = codecs; elem != NULL; elem = elem->next) {
if (number == linphone_payload_type_get_number((LinphonePayloadType *)elem->data)) {
mPayloadType = linphone_payload_type_ref((LinphonePayloadType *)elem->data);
Simon Morlat
committed
break;
}
Ghislain MARY
committed
}
Simon Morlat
committed
bctbx_list_free_with_data(codecs, (bctbx_list_free_func)linphone_payload_type_unref);
Ghislain MARY
committed
}
}
Andrea Gianarda
committed
DaemonCommandExample::DaemonCommandExample(const string &command, const string &output)
: mCommand(command), mOutput(output) {
}
Andrea Gianarda
committed
DaemonCommand::DaemonCommand(const string &name, const string &proto, const string &description)
: mName(name), mProto(proto), mDescription(description) {
Andrea Gianarda
committed
void DaemonCommand::addExample(std::unique_ptr<const DaemonCommandExample> &&example) {
}
const string DaemonCommand::getHelp() const {
ostringstream ost;
ost << getProto() << endl << endl;
ost << "Description:" << endl << getDescription() << endl << endl;
int c = 1;
Andrea Gianarda
committed
for (const auto &example : getExamples()) {
ost << "Example " << c++ << ":" << endl;
ost << ">" << example->getCommand() << endl;
ost << example->getOutput() << endl;
Andrea Gianarda
committed
bool DaemonCommand::matches(const string &name) const {
Andrea Gianarda
committed
Daemon::Daemon(const char *config_path,
const char *factory_config_path,
const char *log_file,
const char *pipe_path,
bool display_video,
bool capture_video)
: mLSD(0), mLogFile(NULL), mAutoVideo(0), mCallIds(0), mProxyIds(0), mAudioStreamIds(0) {
ms_mutex_init(&mMutex, NULL);
mServerFd = (bctbx_pipe_t)-1;
mChildFd = (bctbx_pipe_t)-1;
if (pipe_path == NULL) {
#ifdef HAVE_READLINE
const char *homedir = getenv("HOME");
Andrea Gianarda
committed
rl_readline_name = (char *)"daemon";
if (homedir == NULL) homedir = ".";
mHistfile = string(homedir) + string("/.linphone_history");
read_history(mHistfile.c_str());
setlinebuf(stdout);
#endif
mServerFd = bctbx_server_pipe_create_by_path(pipe_path);
fprintf(stdout, "Server unix socket created, path=%s fd=%i\n", pipe_path, (int)mServerFd);
fprintf(stdout, "Named pipe created, path=%s fd=%p\n", pipe_path, mServerFd);
if (log_file != NULL) {
mLogFile = fopen(log_file, "a+");
linphone_core_enable_logs(mLogFile);
} else {
linphone_core_disable_logs();
}
LinphoneCoreVTable vtable;
memset(&vtable, 0, sizeof(vtable));
vtable.message_received = messageReceived;
mLc = linphone_core_new(&vtable, config_path, factory_config_path, this);
Andrea Gianarda
committed
linphone_core_enable_video_capture(mLc, capture_video);
linphone_core_enable_video_display(mLc, display_video);
Andrea Gianarda
committed
for (const bctbx_list_t *proxy = linphone_core_get_proxy_config_list(mLc); proxy != NULL;
proxy = bctbx_list_next(proxy)) {
François Grisez
committed
updateProxyId((LinphoneProxyConfig *)bctbx_list_get_data(proxy));
}
Andrea Gianarda
committed
mUseStatsEvents = true;
Andrea Gianarda
committed
const list<DaemonCommand *> &Daemon::getCommandList() const {
LinphoneSoundDaemon *Daemon::getLSD() {
return mLSD;
}
int val = VOIDPTR_TO_INT(linphone_call_get_user_data(call));
linphone_call_set_user_data(call, INT_TO_VOIDPTR(++mCallIds));
return mCallIds;
Ghislain MARY
committed
const bctbx_list_t *elem = linphone_core_get_calls(mLc);
Andrea Gianarda
committed
LinphoneCall *call = (LinphoneCall *)elem->data;
if (VOIDPTR_TO_INT(linphone_call_get_user_data(call)) == id) return call;
int val = VOIDPTR_TO_INT(linphone_proxy_config_get_user_data(cfg));
linphone_proxy_config_set_user_data(cfg, INT_TO_VOIDPTR(++mProxyIds));
return mProxyIds;
LinphoneProxyConfig *Daemon::findProxy(int id) {
Ghislain MARY
committed
const bctbx_list_t *elem = linphone_core_get_proxy_config_list(mLc);
Andrea Gianarda
committed
LinphoneProxyConfig *proxy = (LinphoneProxyConfig *)elem->data;
if (VOIDPTR_TO_INT(linphone_proxy_config_get_user_data(proxy)) == id) return proxy;
Andrea Gianarda
committed
LinphoneAuthInfo *Daemon::findAuthInfo(int id) {
Ghislain MARY
committed
const bctbx_list_t *elem = linphone_core_get_auth_info_list(mLc);
if (elem == NULL || id < 1 || (unsigned int)id > bctbx_list_size(elem)) {
return NULL;
}
while (id > 1) {
elem = elem->next;
--id;
}
Andrea Gianarda
committed
return (LinphoneAuthInfo *)elem->data;
}
int Daemon::updateAudioStreamId(AudioStream *audio_stream) {
Andrea Gianarda
committed
for (map<int, AudioStreamAndOther *>::iterator it = mAudioStreams.begin(); it != mAudioStreams.end(); ++it) {
if (it->second->stream == audio_stream) return it->first;
++mAudioStreamIds;
mAudioStreams.insert(make_pair(mAudioStreamIds, new AudioStreamAndOther(audio_stream)));
return mAudioStreamIds;
AudioStreamAndOther *Daemon::findAudioStreamAndOther(int id) {
Andrea Gianarda
committed
map<int, AudioStreamAndOther *>::iterator it = mAudioStreams.find(id);
if (it != mAudioStreams.end()) return it->second;
Andrea Gianarda
committed
map<int, AudioStreamAndOther *>::iterator it = mAudioStreams.find(id);
if (it != mAudioStreams.end()) return it->second->stream;
return NULL;
}
void Daemon::removeAudioStream(int id) {
Andrea Gianarda
committed
map<int, AudioStreamAndOther *>::iterator it = mAudioStreams.find(id);
Ghislain MARY
committed
mAudioStreams.erase(it);
Andrea Gianarda
committed
delete (it->second);
Ghislain MARY
committed
}
Ghislain MARY
committed
static bool compareCommands(const DaemonCommand *command1, const DaemonCommand *command2) {
return (command1->getProto() < command2->getProto());
}
Simon Morlat
committed
mCommands.push_back(new ContactCommand());
mCommands.push_back(new RegisterStatusCommand());
mCommands.push_back(new RegisterInfoCommand());
mCommands.push_back(new AuthInfosClearCommand());
mCommands.push_back(new CallCommand());
mCommands.push_back(new TerminateCommand());
mCommands.push_back(new DtmfCommand());
mCommands.push_back(new PopEventCommand());
mCommands.push_back(new AnswerCommand());
mCommands.push_back(new CallPauseCommand());
mCommands.push_back(new CallMuteCommand());
mCommands.push_back(new CallResumeCommand());
mCommands.push_back(new CallTransferCommand());
mCommands.push_back(new Video());

Julien Wadel
committed
mCommands.push_back(new Video::Preview());
mCommands.push_back(new VideoSource());
mCommands.push_back(new VideoSourceGet());
mCommands.push_back(new VideoSourceList());
mCommands.push_back(new VideoSourceSet());
mCommands.push_back(new VideoSourceReload());

Julien Wadel
committed
mCommands.push_back(new VideoDisplayGet());
mCommands.push_back(new VideoDisplaySet());
mCommands.push_back(new AutoVideo());
mCommands.push_back(new ConferenceCommand());
mCommands.push_back(new AudioCodecGetCommand());
mCommands.push_back(new AudioCodecEnableCommand());
mCommands.push_back(new AudioCodecDisableCommand());
mCommands.push_back(new AudioCodecMoveCommand());
mCommands.push_back(new AudioStreamStartCommand());
mCommands.push_back(new AudioStreamStopCommand());
mCommands.push_back(new AudioStreamStatsCommand());
mCommands.push_back(new MSFilterAddFmtpCommand());
mCommands.push_back(new FirewallPolicyCommand());
mCommands.push_back(new MediaEncryptionCommand());
mCommands.push_back(new AdaptiveBufferCompensationCommand());
mCommands.push_back(new JitterBufferCommand());
mCommands.push_back(new JitterBufferResetCommand());
mCommands.push_back(new QuitCommand());
mCommands.push_back(new HelpCommand());
mCommands.push_back(new ConfigGetCommand());
mCommands.push_back(new ConfigSetCommand());
mCommands.push_back(new NetsimCommand());
mCommands.push_back(new IncallPlayerStartCommand());
mCommands.push_back(new IncallPlayerStopCommand());
mCommands.push_back(new IncallPlayerPauseCommand());
mCommands.push_back(new IncallPlayerResumeCommand());
mCommands.push_back(new MessageCommand());
mCommands.push_back(new EchoCalibrationCommand());
Ghislain MARY
committed
mCommands.sort(compareCommands);
}
void Daemon::uninitCommands() {
while (!mCommands.empty()) {
delete mCommands.front();
mCommands.pop_front();
}
bool Daemon::pullEvent() {
bool status = false;
size_t size = mEventQueue.size();
if (size != 0) size--;
Andrea Gianarda
committed
ostr << "Size: " << size << "\n"; // size is the number items remaining in the queue after popping the event.
Event *e = mEventQueue.front();
ostr << e->toBuf() << "\n";
delete e;
sendResponse(Response(ostr.str().c_str(), Response::Ok));
return status;
Andrea Gianarda
committed
void Daemon::callStateChanged(LinphoneCall *call, LinphoneCallState state, BCTBX_UNUSED(const char *msg)) {
queueEvent(new CallEvent(this, call, state));
Andrea Gianarda
committed
if (state == LinphoneCallIncomingReceived && mAutoAnswer) {
linphone_call_accept(call);
}
Andrea Gianarda
committed
void Daemon::messageReceived(BCTBX_UNUSED(LinphoneChatRoom *cr), LinphoneChatMessage *msg) {
queueEvent(new IncomingMessageEvent(msg));
}
void Daemon::callStatsUpdated(LinphoneCall *call, const LinphoneCallStats *stats) {
Simon Morlat
committed
if (mUseStatsEvents) {
/* don't queue periodical updates (3 per seconds for just bandwidth updates) */
Andrea Gianarda
committed
if (!(_linphone_call_stats_get_updated(stats) & LINPHONE_CALL_STATS_PERIODICAL_UPDATE)) {
queueEvent(new CallStatsEvent(this, call, stats));
Simon Morlat
committed
}
}
void Daemon::callPlayingComplete(int id) {
queueEvent(new CallPlayingStatsEvent(this, id));
void Daemon::dtmfReceived(LinphoneCall *call, int dtmf) {
queueEvent(new DtmfEvent(this, call, dtmf));
void Daemon::callStateChanged(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState state, const char *msg) {
Andrea Gianarda
committed
Daemon *app = (Daemon *)linphone_core_get_user_data(lc);
void Daemon::callStatsUpdated(LinphoneCore *lc, LinphoneCall *call, const LinphoneCallStats *stats) {
Andrea Gianarda
committed
Daemon *app = (Daemon *)linphone_core_get_user_data(lc);
void Daemon::dtmfReceived(LinphoneCore *lc, LinphoneCall *call, int dtmf) {
Andrea Gianarda
committed
Daemon *app = (Daemon *)linphone_core_get_user_data(lc);
Andrea Gianarda
committed
void Daemon::messageReceived(LinphoneCore *lc, LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
Daemon *app = (Daemon *)linphone_core_get_user_data(lc);
app->messageReceived(cr, msg);
}
Andrea Gianarda
committed
for (map<int, AudioStreamAndOther *>::iterator it = mAudioStreams.begin(); it != mAudioStreams.end(); ++it) {
Andrea Gianarda
committed
while (it->second->queue && (NULL != (ev = ortp_ev_queue_get(it->second->queue)))) {
OrtpEventType evt = ortp_event_get_type(ev);
if (evt == ORTP_EVENT_RTCP_PACKET_RECEIVED || evt == ORTP_EVENT_RTCP_PACKET_EMITTED) {
linphone_call_stats_fill(it->second->stats, &it->second->stream->ms, ev);
Andrea Gianarda
committed
if (mUseStatsEvents)
mEventQueue.push(new AudioStreamStatsEvent(this, it->second->stream, it->second->stats));
}
ortp_event_destroy(ev);
}
}
}
if (mChildFd == (bctbx_pipe_t)-1) {
Event *r = mEventQueue.front();
fprintf(stdout, "\n%s\n", r->toBuf().c_str());
void Daemon::execCommand(const string &command) {
istringstream ist(command);
string name;
ist >> name;
stringbuf argsbuf;
ist.get(argsbuf);
string args = argsbuf.str();
if (!args.empty() && (args[0] == ' ')) args.erase(0, 1);
Andrea Gianarda
committed
list<DaemonCommand *>::iterator it =
find_if(mCommands.begin(), mCommands.end(), [&name](const DaemonCommand *dc) { return dc->matches(name); });
ms_mutex_lock(&mMutex);
ms_mutex_unlock(&mMutex);
void Daemon::sendResponse(const Response &resp) {
if (mChildFd != (bctbx_pipe_t)-1) {
if (bctbx_pipe_write(mChildFd, (uint8_t *)buf.c_str(), (int)buf.size()) == -1) {
ms_error("Fail to write to pipe: %s", strerror(errno));
Andrea Gianarda
committed
void Daemon::queueEvent(Event *ev) {
mEventQueue.push(ev);
}
string Daemon::readPipe() {
char buffer[32768];
memset(buffer, '\0', sizeof(buffer));
if (mChildFd == (bctbx_pipe_t)-1) {
mChildFd = bctbx_server_pipe_accept_client(mServerFd);
if (mChildFd != (bctbx_pipe_t)-1) {
int ret = bctbx_pipe_read(mChildFd, (uint8_t *)buffer, sizeof(buffer));
if (ret == -1) {
ms_error("Fail to read from pipe: %s", strerror(errno));
mChildFd = (bctbx_pipe_t)-1;
} else {
if (ret == 0) {
ms_message("Client disconnected");
mChildFd = (bctbx_pipe_t)-1;
struct pollfd pfd[2];
memset(&pfd[0], 0, sizeof(pfd));
if (mServerFd != (bctbx_pipe_t)-1) {
pfd[0].events = POLLIN;
pfd[0].fd = mServerFd;
if (mChildFd != (bctbx_pipe_t)-1) {
pfd[1].events = POLLIN;
pfd[1].fd = mChildFd;
int err = poll(pfd, (nfds_t)nfds, 50);
if (mServerFd != (bctbx_pipe_t)-1 && (pfd[0].revents & POLLIN)) {
Andrea Gianarda
committed
int childfd = accept(mServerFd, (struct sockaddr *)&addr, &addrlen);
if (mChildFd != (bctbx_pipe_t)-1) {
ms_error("Cannot accept two client at the same time");
close(childfd);
mChildFd = (bctbx_pipe_t)childfd;
if (mChildFd != (bctbx_pipe_t)-1 && (pfd[1].revents & POLLIN)) {
if ((ret = bctbx_pipe_read(mChildFd, (uint8_t *)buffer, sizeof(buffer))) == -1) {
bctbx_server_pipe_close_client(mChildFd);
mChildFd = (bctbx_pipe_t)-1;
void Daemon::dumpCommandsHelp() {
int cols = 80;
#ifdef TIOCGSIZE
struct ttysize ts;
ioctl(STDIN_FILENO, TIOCGSIZE, &ts);
cols = ts.ts_cols;
#elif defined(TIOCGWINSZ)
struct winsize ts;
ioctl(STDIN_FILENO, TIOCGWINSZ, &ts);
cols = ts.ws_col;
#endif
cout << endl;
Andrea Gianarda
committed
for (list<DaemonCommand *>::iterator it = mCommands.begin(); it != mCommands.end(); ++it) {
cout << setfill('-') << setw(cols) << "-" << endl << endl;
cout << (*it)->getHelp();
}
}
Andrea Gianarda
committed
static string htmlEscape(const string &orig) {
string ret = orig;
Andrea Gianarda
committed
while (1) {
pos = ret.find('<');
if (pos != string::npos) {
ret.erase(pos, 1);
ret.insert(pos, "<");
Andrea Gianarda
committed
pos = ret.find('>');
if (pos != string::npos) {
ret.erase(pos, 1);
ret.insert(pos, ">");
Andrea Gianarda
committed
while (1) {
pos = ret.find('\n');
if (pos != string::npos) {
ret.erase(pos, 1);
ret.insert(pos, "<br>");
continue;
}
break;
}
return ret;
}
Andrea Gianarda
committed
void Daemon::dumpCommandsHelpHtml() {
Andrea Gianarda
committed
cout << "<!DOCTYPE html><html><body>" << endl;
cout << "<h1>List of linphone-daemon commands.</h1>" << endl;
for (const auto &cmd : mCommands) {
cout << "<h2>" << htmlEscape(cmd->getProto()) << "</h2>" << endl;
cout << "<h3>"
<< "Description"
<< "</h3>" << endl;
cout << "<p>" << htmlEscape(cmd->getDescription()) << "</p>" << endl;
cout << "<h3>"
<< "Examples"
<< "</h3>" << endl;
const auto &examples = cmd->getExamples();
cout << "<p><i>";
for (const auto &example : examples) {
cout << "<b>" << htmlEscape("Linphone-daemon>") << htmlEscape(example->getCommand()) << "</b><br>" << endl;
cout << htmlEscape(example->getOutput()) << "<br>" << endl;
cout << "<br><br>";
Andrea Gianarda
committed
cout << "</i></p>" << endl;
Andrea Gianarda
committed
cout << "</body></html>" << endl;
Andrea Gianarda
committed
cout << "daemon-linphone [<options>]" << endl
<<
#if defined(LICENCE_GPL) || defined(LICENCE_COMMERCIAL)
Andrea Gianarda
committed
"Licence: "
#ifdef LICENCE_GPL
Andrea Gianarda
committed
"GPL"
#endif
#ifdef LICENCE_COMMERCIAL
Andrea Gianarda
committed
"Commercial"
Andrea Gianarda
committed
<< endl
<<
Andrea Gianarda
committed
"where options are :" << endl
<< "\t--help Print this notice." << endl
<< "\t--dump-commands-help Dump the help of every available commands." << endl
<< "\t--dump-commands-html-help Dump the help of every available commands." << endl
<< "\t--pipe <pipepath> Create an unix server socket in the specified path to receive commands from. "
"For Windows just use a name instead of a path."
<< endl
<< "\t--log <path> Supply a file where the log will be saved." << endl
<< "\t--factory-config <path> Supply a readonly linphonerc style config file to start with." << endl
<< "\t--config <path> Supply a linphonerc style config file to start with." << endl
<< "\t--disable-stats-events Do not automatically raise RTP statistics events." << endl
<< "\t--enable-lsd Use the linphone sound daemon." << endl
<< "\t-C Enable video capture." << endl
<< "\t-D Enable video display." << endl
<< "\t--auto-answer Automatically answer incoming calls." << endl;
void Daemon::startThread() {
ms_thread_create(&this->mThread, NULL, Daemon::iterateThread, this);
}
#ifdef max
#undef max
#endif
Andrea Gianarda
committed
string Daemon::readLine(const string &prompt, bool *eof) {
*eof = false;
#ifdef HAVE_READLINE
Simon Morlat
committed
if (cin.eof()) {
Andrea Gianarda
committed
*eof = true;
Simon Morlat
committed
}
cout << prompt;
stringbuf outbuf;
cin.get(outbuf);
cin.clear();
#endif
}
const string prompt("daemon-linphone>");
startThread();
Andrea Gianarda
committed
bool eof = false;
if (mServerFd == (bctbx_pipe_t)-1) {
line = readLine(prompt, &eof);
if (!line.empty()) {
#ifdef HAVE_READLINE
if (!line.empty()) {
execCommand(line);
Simon Morlat
committed
if (eof && mRunning) {
mRunning = false; // ctrl+d
cout << "Quitting..." << endl;
}
stopThread();
void Daemon::stopThread() {
void *ret;
ms_thread_join(mThread, &ret);
}
Andrea Gianarda
committed
void Daemon::enableStatsEvents(bool enabled) {
mUseStatsEvents = enabled;
Andrea Gianarda
committed
void Daemon::enableAutoAnswer(bool enabled) {
mAutoAnswer = enabled;
}
void Daemon::enableLSD(bool enabled) {
if (mLSD) linphone_sound_daemon_destroy(mLSD);
linphone_core_use_sound_daemon(mLc, NULL);
if (enabled) {
Andrea Gianarda
committed
mLSD = linphone_sound_daemon_new(mLc->factory, NULL, 44100, 1);
linphone_core_use_sound_daemon(mLc, mLSD);
}
}
for (map<int, AudioStreamAndOther *>::iterator it = mAudioStreams.begin(); it != mAudioStreams.end(); ++it) {
if (mChildFd != (bctbx_pipe_t)-1) {
bctbx_server_pipe_close_client(mChildFd);
if (mServerFd != (bctbx_pipe_t)-1) {
bctbx_server_pipe_close(mServerFd);
if (mLogFile != NULL) {
linphone_core_enable_logs(NULL);
fclose(mLogFile);
}
ms_mutex_destroy(&mMutex);
#ifdef HAVE_READLINE
static Daemon *the_app = NULL;
Andrea Gianarda
committed
static void sighandler(BCTBX_UNUSED(int signum)) {
if (the_app) {
the_app->quit();
the_app = NULL;
}
}

Julien Wadel
committed
#if defined(__APPLE__)
Andrea Gianarda
committed
extern "C" int apple_main(int argc, char **argv) {

Julien Wadel
committed
#else

Julien Wadel
committed
#endif
const char *factory_config_path = NULL;
const char *pipe_path = NULL;
const char *log_file = NULL;
bool capture_video = false;
bool display_video = false;
bool auto_answer = false;
for (i = 1; i < argc; ++i) {
if (strcmp(argv[i], "--help") == 0) {
} else if (strcmp(argv[i], "--dump-commands-help") == 0) {
Daemon app(NULL, NULL, NULL, NULL, false, false);
app.dumpCommandsHelp();
return 0;
Andrea Gianarda
committed
} else if (strcmp(argv[i], "--dump-commands-html-help") == 0) {
Daemon app(NULL, NULL, NULL, NULL, false, false);
app.dumpCommandsHelpHtml();