isdn4k-utils/ant-phone/src/session.c

1454 lines
44 KiB
C

/*
* definitions for runtime session specific data handling
*
* This file is part of ANT (Ant is Not a Telephone)
*
* Copyright 2002, 2003 Roland Stigge
*
* ANT is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* ANT 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ANT; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "config.h"
/* regular GNU system includes */
#include <string.h>
#include <stdio.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#include <math.h>
#include <time.h>
/* GTK */
#include <gtk/gtk.h>
/* libsndfile */
#include <sndfile.h>
/* own header files */
#include "globals.h"
#include "session.h"
#include "sound.h"
#include "isdn.h"
#include "mediation.h"
#include "gtk.h"
#include "util.h"
#include "callerid.h"
#include "llcheck.h"
#include "settings.h"
#include "fxgenerator.h"
#include "server.h"
/*
* This is our session. Currently just one globally.
*/
session_t session;
struct state_data_t state_data[STATE_NUMBER] = {
{N_("Ready"), N_("Dial"), 1,N_("Hang up"),0},/* STATE_READY */
{N_("RING"), N_("Answer"), 1,N_("Reject"), 1},/* STATE_RINGING */
{N_("RING"), N_("Answer"), 1,N_("Reject"), 1},/* STATE_RINGING_QUIET */
{N_("Dialing"), N_("Pick up"),0,N_("Cancel"), 1},/* STATE_DIALING */
{N_("B-Channel open"),N_("Pick up"),0,N_("Hang up"),1},/* STATE_CONVERSATION */
{N_("Setup"), N_("Pick up"),0,N_("Hang up"),0},/* STATE_SERVICE */
{N_("Playback"), N_("Pick up"),0,N_("Stop") ,1} /* STATE_PLAYBACK */
};
/*
* Simply opens audio devices for specified session
*
* returns 0 on success, -1 on error
*/
int session_audio_open(session_t *session) {
if (debug)
fprintf(stderr, "session_audio_open: Opening audio device(s).\n");
if (open_audio_devices(session->audio_device_name_in,
session->audio_device_name_out,
1,
session->format_priorities,
&session->audio_fd_in,
&session->audio_fd_out,
&session->fragment_size_in,
&session->fragment_size_out,
&session->audio_format_in,
&session->audio_format_out,
&session->audio_speed_in,
&session->audio_speed_out)) {
return -1;
}
return 0;
}
/*
* Closes audio devices for specified session
*
* returns 0 on success, -1 on error
*/
int session_audio_close(session_t *session) {
if (debug)
printf("session_audio_close: Closing audio device(s).\n");
if (close_audio_devices(session->audio_fd_in, session->audio_fd_out)) {
return -1;
}
return 0;
}
/*
* init audio devices in session
*
* input: session->audio_device_name_in, session->audio_device_name_out
*
* returns 0 on success, -1 otherwise
*
* output: on success, session->audio_LUT* and session->audio_*buf are
* allocated (and set, if appropriate)
*/
int session_audio_init(session_t *session) {
/* message: open audio device(s) */
if (debug) {
if (strcmp(session->audio_device_name_in, session->audio_device_name_out))
{
/* different devices */
fprintf(stderr, "Initializing %s and %s...\n",
session->audio_device_name_in, session->audio_device_name_out);
} else {
fprintf(stderr, "Initializing %s ...\n",
session->audio_device_name_in);
}
}
/* other options */
session->audio_speed_in = ISDN_SPEED; /* default audio speed */
session->audio_speed_out = ISDN_SPEED;
/* audio device buffer fragment sizes */
session->fragment_size_in = DEFAULT_FRAGMENT_SIZE;
session->fragment_size_out = DEFAULT_FRAGMENT_SIZE;
session->format_priorities = default_audio_priorities;
if (session_audio_open(session)) {
fprintf(stderr, "Error initializing audio device(s).\n");
return -1;
}
session->ratio_in = (double)session->audio_speed_out / ISDN_SPEED;
session->ratio_out = (double)ISDN_SPEED / session->audio_speed_in;
if (mediation_makeLUT(session->audio_format_out, &session->audio_LUT_in,
session->audio_format_in, &session->audio_LUT_out,
&session->audio_LUT_generate,
&session->audio_LUT_analyze,
&session->audio_LUT_ulaw2short)) {
fprintf(stderr, "Error building conversion look-up-table.\n");
return -1;
}
session->audio_sample_size_in =
sample_size_from_format(session->audio_format_in);
session->audio_sample_size_out =
sample_size_from_format(session->audio_format_out);
/* allocate buffers */
if (!(session->audio_inbuf =
(unsigned char *)malloc(session->fragment_size_in))) {
return -1;
}
if (!(session->audio_outbuf =
(unsigned char *)malloc(session->fragment_size_out))) {
return -1;
}
return 0;
}
/*
* init isdn in session
*
* input: session->msn, session->msns
*
* returns 0 on success, -1 otherwise
*/
int session_isdn_init(session_t *session) {
/* open and init isdn device */
if (debug)
fprintf(stderr, "Initializing ISDN device...\n");
if ((session->isdn_fd =
open_isdn_device(&session->isdn_device_name,
&session->isdn_lockfile_name)) < 0) {
fprintf(stderr, "Error opening isdn device.\n");
return -1;
}
if (init_isdn_device(session->isdn_fd, &session->isdn_backup)) {
fprintf(stderr, "Error initializing isdn device.\n");
return -1;
}
session->isdn_inbuf_size = session->isdn_outbuf_size = DEFAULT_ISDNBUF_SIZE;
/* allocate buffers */
if (!(session->isdn_inbuf =
(unsigned char *)malloc(session->isdn_inbuf_size))) {
return -1;
}
if (!(session->isdn_outbuf =
(unsigned char *)malloc(session->isdn_outbuf_size))) {
return -1;
}
if (isdn_setMSN(session->isdn_fd, session->msn) ||
isdn_setMSNs(session->isdn_fd, session->msns)) {
fprintf(stderr, "Error setting MSN properties.\n");
return -1;
}
session->isdn_inbuf_len = 0;
session->isdn_inbuf[0] = 1;
return 0;
}
/*
* init recording related things in session
*/
int session_recording_init(session_t *session) {
/* mediation recording stuff */
session->rec_buf_local_size = MEDIATION_RECBUFSIZE;
session->rec_buf_remote_size = MEDIATION_RECBUFSIZE;
session->rec_buf_local_index = 0;
session->rec_buf_remote_index = 0;
if (!(session->rec_buf_local =
(short *) malloc(session->rec_buf_local_size * sizeof(short)))) {
return -1;
}
if (!(session->rec_buf_remote =
(short *) malloc(session->rec_buf_remote_size * sizeof(short)))) {
return -1;
}
if (!(session->recorder =
(struct recorder_t *)malloc(sizeof(struct recorder_t)))) {
return -1;
}
return 0;
}
/*
* deinit recording related things in session
*/
int session_recording_deinit(session_t *session) {
free(session->rec_buf_local);
free(session->rec_buf_remote);
free(session->recorder);
return 0;
}
/*
* de-allocates memory used by audio buffers of session
* -> prepares de-initialization
* -> is part of session_audio_free()
*/
void session_audio_free(session_t *session) {
free(session->audio_inbuf);
free(session->audio_outbuf);
free(session->audio_LUT_in);
free(session->audio_LUT_out);
free(session->audio_LUT_generate);
free(session->audio_LUT_analyze);
free(session->audio_LUT_ulaw2short);
}
/*
* close audio device(s) and clean up (deallocate buffers)
*
* returns 0 on success, -1 otherwise
*/
int session_audio_deinit(session_t *session) {
/* free allocated buffers */
session_audio_free(session);
/* close audio device(s) */
if (debug)
fprintf(stderr, "Closing sound device(s)...\n");
if (session_audio_close(session)) {
fprintf(stderr, "Error closing sound device(s).\n");
return -1;
}
return 0;
}
/*
* close isdn device and clean up (deallocate buffers)
*
* returns 0 on success, -1 otherwise
*/
int session_isdn_deinit(session_t *session) {
/* free allocated buffers */
free(session->isdn_inbuf);
free(session->isdn_outbuf);
if (debug)
fprintf(stderr, "Closing ISDN device...\n");
/* de-init / restore isdn device */
if (deinit_isdn_device(session->isdn_fd, &session->isdn_backup)) {
fprintf(stderr, "Error restoring ttyI state.\n");
}
/* close isdn device */
if (close_isdn_device(session->isdn_fd,
session->isdn_device_name,
session->isdn_lockfile_name)) {
fprintf(stderr, "Error closing isdn device.\n");
}
return 0;
}
/*
* initialize a session (isdn device, audio device) and read options file
*
* input:
* session: empty struct waiting for work
* audio_device_name_in, audio_device_name_out: name(s) of audio device(s)
* msn: msn to use
*
* NOTE: The latter 4 parameters are only the requested ones. When they are
* overridden by the options file, the resulting values will be different
*
* output: session: initialized session struct
*
* returns 0 on success, -1 otherwise
*/
int session_init(session_t *session,
char *audio_device_name_in,
char *audio_device_name_out,
char *msn, char *msns) {
int i;
/*
* first: set all defaults possibly overridden by options file
*/
session->dial_number_history = NULL;
session->dial_number_history = g_list_append(session->dial_number_history,
strdup(""));
session->dial_number_history_maxlen = 10; /* config overrides this */
session->cid_num_max = 100; /* 0 means no limit */
/* options defaults */
session->option_save_options = 1; /* save options automatically (on exit) */
session->option_release_devices = 1;
session->option_show_llcheck = 1;
session->option_show_callerid = 1;
session->option_show_controlpad = 1;
session->option_muted = 0;
session->option_record = 0;
session->option_record_local = 1;
session->option_record_remote = 1;
session->option_recording_format =
RECORDING_FORMAT_WAV | RECORDING_FORMAT_ULAW;
session->option_popup = 1;
session->option_calls_merge = 1;
session->option_calls_merge_max_days = 10;
session->exec_on_incoming = strdup("");
for (i = 0; i < 4; i++) {
asprintf(&session->preset_names[i], _("Preset %d"), i + 1);
session->preset_numbers[i] = strdup("");
}
session->audio_device_name_in = strdup(audio_device_name_in);
session->audio_device_name_out = strdup(audio_device_name_out);
session->msn = strdup(msn);
session->msns = strdup(msns);
session->from = strdup("");
session->to = strdup("");
settings_options_read(session); /* override defaults analyzing options file */
/* command line configurable parameters: set to hard coded defaults
if no setting was made (either at command line or in options file) */
if (!strcmp(session->audio_device_name_in, "")) {
free(session->audio_device_name_in);
session->audio_device_name_in = strdup(DEFAULT_AUDIO_DEVICE_NAME_IN);
}
if (!strcmp(session->audio_device_name_out, "")) {
free(session->audio_device_name_out);
session->audio_device_name_out = strdup(DEFAULT_AUDIO_DEVICE_NAME_OUT);
}
if (!strcmp(session->msn, "")) {
free(session->msn);
session->msn = strdup(DEFAULT_MSN);
}
if (!strcmp(session->msns, "")) {
free(session->msns);
session->msns = strdup(DEFAULT_MSNS);
}
/* other defaults */
session->dial_number_history_pointer = 0;
session->touchtone_countdown_isdn = 0;
session->touchtone_countdown_audio = 0;
session->touchtone_index = 0;
session->ring_time = 0;
session->unanswered = 0;
/* setup audio and isdn */
if (!session->option_release_devices)
if (session_audio_init(session) < 0) return -1;
if (session_isdn_init(session) < 0) return -1;
if (session_recording_init(session) < 0) return -1;
session->state = STATE_READY; /* initial state */
session->effect = EFFECT_NONE;
/* init server functionality */
if (server_init(session) < 0) return -1;
return 0;
}
/*
* helper function to free single element data from g_list
* (used by session_deinit)
*/
void free_g_list_element(gpointer data, gpointer user_data _U_) {
free(data);
}
/*
* de-initialize a session (isdn device, audio device)
*
* input: session: session to de-initialize
*
* returns 0 on success, -1 otherwise
*/
int session_deinit(session_t *session) {
int i;
/* deinit server functionality */
if (server_deinit(session) < 0) return -1;
if (session->option_save_options) settings_options_write(session);
/* free dial_number_history */
g_list_foreach(session->dial_number_history, free_g_list_element, NULL);
g_list_free(session->dial_number_history);
/* close devices and clean up (buffers) */
if (session_isdn_deinit(session) < 0) return -1;
if (!session->option_release_devices)
if (session_audio_deinit(session) < 0) return -1;
if (session_recording_deinit(session) < 0) return -1;
free(session->exec_on_incoming);
/* clean up pre-set options */
free(session->audio_device_name_in);
free(session->audio_device_name_out);
free(session->msn);
free(session->msns);
/* clean up rest */
for (i = 0; i < 4; i++) {
free(session->preset_names[i]);
free(session->preset_numbers[i]);
}
free(session->from);
free(session->to);
return 0;
}
/*
* set up handlers for audio/ISDN input
*/
void session_io_handlers_start(session_t *session) {
if (!(session->option_release_devices &&
(session->state == STATE_READY ||
session->state == STATE_RINGING_QUIET))) {
session->gtk_audio_input_tag = gtk_input_add_full(session->audio_fd_in,
GDK_INPUT_READ,
gtk_handle_audio_input,
NULL,
(gpointer) session,
NULL);
}
session->gtk_isdn_input_tag = gtk_input_add_full(session->isdn_fd,
GDK_INPUT_READ,
gtk_handle_isdn_input,
NULL,
(gpointer) session,
NULL);
/* server functionality */
session->gtk_local_input_tag = gtk_input_add_full(session->local_sock,
GDK_INPUT_READ,
server_handle_local_input,
NULL,
(gpointer) session,
NULL);
}
/*
* remove handlers for audio/ISDN input
*/
void session_io_handlers_stop(session_t *session) {
if (!(session->option_release_devices &&
(session->state == STATE_READY ||
session->state == STATE_RINGING_QUIET))) {
gtk_input_remove(session->gtk_audio_input_tag);
}
gtk_input_remove(session->gtk_isdn_input_tag);
gtk_input_remove(session->gtk_local_input_tag);
}
/*
* Resets audio devices by closing and reopening
*
* assumes audio in open, initialized (possibly used) state
*
* WARNING: * Stop I/O handlers first!
* * Only resets currently used device(s). To use another device,
* use session_audio_deinit() and session_audio_init()
*
* returns 0 on success, -1 on error
*/
int session_reset_audio(session_t *session) {
int result = 0;
if (!(session->option_release_devices &&
(session->state == STATE_READY ||
session->state == STATE_RINGING_QUIET))) {
if (session_audio_close(session)) {
fprintf(stderr, "Error closing audio device(s) while resetting.\n");
result = -1;
}
if (session_audio_open(session)) {
fprintf(stderr, "Error reopening audio device(s) while resetting.\n");
result = -1;
}
}
return result;
}
/*
* Callback for new unhandled modem string
*
* This function will be called, whenever we got a new unhandled modem string.
* That means that it's called unless modem answers were stolen by response
* handlers in functions like isdn_setMSN or isdn_hangup where "OK\r\n" will
* be checked for
*/
void session_new_modem_string_callback(session_t *session) {
/* modem string is in session->isdn_inbuf */
if (debug)
/* new line is in isdn_inbuf */
fprintf(stderr, "|%s", session->isdn_inbuf);
}
/*
* Tries to read isdn device in command mode, returns immediately
* (non blocking). If we have a partially filled input buffer, continue
* with that
*
* input: session struct with open isdn_fd of ttyI in command mode
*
* output: command (or partial command) read in
* session->isdn_inbuf, session->isdn_inbuf_len
*
* returns with 1 if we got a new line (else 0)
*
* NOTE: * completed lines are 0-terminated at session->isdn_inbuf_len,
* non-compleded lines are 1-terminated there
* * completed lines are actually terminated by "\r\n\0"
*/
static int isdn_try_read_line(session_t *session) {
int total = 0; /* number of bytes read in this call */
struct timeval tv;
fd_set fds;
int num; /* number of file descriptors with data (0/1) */
tv.tv_sec = 0; /* return immediately */
tv.tv_usec = 0;
do {
FD_ZERO(&fds);
FD_SET(session->isdn_fd, &fds);
num = select(FD_SETSIZE, &fds, 0, 0, &tv);
if (num > 0) { /* got another char: append to buffer */
if (session->isdn_inbuf[session->isdn_inbuf_len] == 0 ||
session->isdn_inbuf_len == session->isdn_inbuf_size - 1) {
/* we have to start new line or buffer is full -> reset buffer */
session->isdn_inbuf_len = 0;
session->isdn_inbuf[0] = 1;
}
read(session->isdn_fd, &session->isdn_inbuf[session->isdn_inbuf_len], 1);
total ++;
session->isdn_inbuf[++session->isdn_inbuf_len] = 1;
if (session->isdn_inbuf_len >= 2 &&
session->isdn_inbuf[session->isdn_inbuf_len - 2] == '\r' &&
session->isdn_inbuf[session->isdn_inbuf_len - 1] == '\n') {
/* end of line */
session->isdn_inbuf[session->isdn_inbuf_len] = 0;
}
}
} while (num > 0 && session->isdn_inbuf[session->isdn_inbuf_len] != 0);
if (session->isdn_inbuf[session->isdn_inbuf_len] == 0 && total > 0) {
session_new_modem_string_callback(session);
return 1;
} else
return 0;
}
/* do some initialization of full duplex conversation mode */
void session_init_conversation(session_t *session) {
session->samples_in = 0;
session->samples_out = 0;
session->audio_outbuf_index = 0;
session->isdn_outbuf_index = 0;
session->escape = 0;
session->hangup = 0;
session->aborted = 0;
session->no_input = 0;
}
/*
* do some deinitialization of full duplex conversation mode
*
* return: self_hangup: 1 == we hangup, 0 == other side hung up
*/
void session_deinit_conversation(session_t *session, int self_hangup) {
if (session->option_record) {
recording_write(session->recorder, session->rec_buf_local,
session->rec_buf_local_index, RECORDING_LOCAL);
recording_write(session->recorder, session->rec_buf_remote,
session->rec_buf_remote_index, RECORDING_REMOTE);
recording_close(session->recorder);
}
session->rec_buf_local_index = 0;
session->rec_buf_remote_index = 0;
session_io_handlers_stop(session);
session_reset_audio(session);
session_io_handlers_start(session);
if (isdn_blockmode(session->isdn_fd, 0))
fprintf(stderr, "Warning: "
"Switching back to normal isdn tty mode not successful.\n");
/* go back to command mode */
if (isdn_stop_audio(session->isdn_fd, self_hangup)) {
fprintf(stderr, "Error switching back to command mode.\n");
}
/* isdn hangup */
if (isdn_hangup(session->isdn_fd)) {
fprintf(stderr, "Error hanging up.\n");
}
session->isdn_inbuf_len = 0;
session->isdn_inbuf[0] = 1;
}
/*
* will be called directly after getting VCON from ttyI
* includes state transition
*/
void session_start_conversation(session_t *session) {
char *digits; /* time in digits form */
if (isdn_set_full_duplex(session->isdn_fd)) {
/* ISDN full duplex (REC start command) error */
fprintf(stderr, "Error setting full duplex audio mode.\n");
isdn_hangup(session->isdn_fd);
session_set_state(session, STATE_READY);
cid_set_duration(session, _("(HW ERROR)"));
} else { /* full duplex ok: audio mode */
session->vcon_time = time(NULL); /* for caller id monitor */
cid_set_date(session, session->vcon_time);
session_set_state(session, STATE_CONVERSATION);
if (session->option_record) {
if ((digits = util_digitstime(&session->vcon_time))) {
if (recording_open(session->recorder, digits,
session->option_recording_format)) {
fprintf(stderr, "Error opening audio file.\n");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(
session->record_checkbutton), FALSE);
}
free(digits);
cid_row_mark_record(session, session->cid_num - 1);
} else {
fprintf(stderr, "Error generating audio filename.\n");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(
session->record_checkbutton), FALSE);
}
}
if (isdn_blockmode(session->isdn_fd, 1)) /* blockmode error */
fprintf(stderr,
"Warning: Switching to ISDN blockmode not successful.\n");
session_init_conversation(session); /* init some conversation variables */
session_io_handlers_stop(session);
session_reset_audio(session);
session_io_handlers_start(session);
/* start with first block to start recording */
process_audio_source(session);
}
}
/*
* to be called by a timeout and everyone who wants to stop a running effect
* to go back to STATE_READY
*/
gint session_effect_stop_cb(gpointer data) {
session_t *session = (session_t *) data;
session_set_state(session, STATE_READY);
return FALSE; /* don't call me regularly */
}
/*
* To be called when we can write another block to sound device
*/
void session_effect_callback(gpointer data,
gint source _U_, GdkInputCondition condition _U_)
{
session_t *session = (session_t *) data;
int pos = 0; /* position in raw byte output buffer */
int i;
int index_out = 0; /* logical position (sample number) in output buffer */
unsigned char x; /* generated ulaw sample */
int just_read = 0;
int sndfile_buffer_index = 0;
double speed_factor = (double)session->effect_sndfile_buffer_frames /
(session->fragment_size_out / session->audio_sample_size_out);
/* factor from audio device to sndfile */
if (session->effect == EFFECT_SOUNDFILE) {
just_read = sf_readf_short(session->effect_sndfile,
session->effect_sndfile_buffer, session->effect_sndfile_buffer_frames);
}
for (i = 0; i < session->effect_sfinfo.channels; i++)
session->effect_sndfile_buffer[session->effect_sndfile_buffer_frames *
session->effect_sfinfo.channels + i] =
session->effect_sndfile_buffer[(session->effect_sndfile_buffer_frames-1) *
session->effect_sfinfo.channels + i];
/* fill buffer */
while (pos < session->fragment_size_out) {
if (session->effect == EFFECT_SOUNDFILE) {
double sum = 0.0;
double leftbound = speed_factor * index_out; /* sndfile bounds */
double rightbound = speed_factor * (index_out + 1);
double dummy;
double frac = modf(leftbound, &dummy);
while (leftbound < rightbound) {
double frame_sum = 0;
double weight;
if (rightbound - leftbound < 1.0)
weight = rightbound - leftbound;
else
weight = 1.0;
sndfile_buffer_index = (int)leftbound;
if (sndfile_buffer_index < just_read) {
int t_count;
for (t_count=0; t_count < session->effect_sfinfo.channels; t_count++){
frame_sum += (1.0 - frac) * session->effect_sndfile_buffer[
sndfile_buffer_index * session->effect_sfinfo.channels + t_count]
+ frac * session->effect_sndfile_buffer[
(sndfile_buffer_index+1)*session->effect_sfinfo.channels+t_count];
}
frame_sum /= session->effect_sfinfo.channels;
} else {
frame_sum = 0;
}
sum += weight * frame_sum;
leftbound += 1.0;
}
sum /= speed_factor;
x = session->audio_LUT_generate[(int)sum / 256 + 128];
} else {
double seconds = (double)session->effect_pos / session->audio_speed_out;
x = fxgenerate(session, session->effect, 0, seconds);
}
if (session->audio_sample_size_out == 1) {
session->audio_outbuf[pos++] = session->audio_LUT_in[(int)x];
} else { /* audio_sample_size == 2 */
session->audio_outbuf[pos++] = session->audio_LUT_in[(int)x * 2];
session->audio_outbuf[pos++] = session->audio_LUT_in[(int)x * 2 + 1];
}
index_out ++;
session->effect_pos++;
}
/* play it! */
write_buf(session->audio_fd_out,
session->audio_outbuf,
session->fragment_size_out);
if (session->effect == EFFECT_SOUNDFILE &&
just_read < session->effect_sndfile_buffer_frames &&
session->effect_filename)
{
gtk_timeout_add(
1000 * session->fragment_size_out / session->audio_sample_size_out *
(audio_get_write_fragments_total(session->audio_fd_out) -
audio_get_write_fragments_number(session->audio_fd_out)) /
session->audio_speed_out,
session_effect_stop_cb, session);
/* session->effect_filename provides a flag if we already want to stop */
free(session->effect_filename);
session->effect_filename = NULL;
}
}
/*
* Start an effect (effect_t) playing on the sound device
*
* on kind == EFFECT_SOUNDFILE, session->effect_filename should be
* initialized so that session_effect_stop can free() it afterwards
*/
void session_effect_start(session_t *session, enum effect_t kind) {
if (kind == EFFECT_SOUNDFILE) {
session->effect_sfinfo.format = 0;
if (!(session->effect_sndfile =
sf_open(session->effect_filename, SFM_READ, &session->effect_sfinfo)))
{
fprintf(stderr, "Error on opening sound file.\n");
}
session->effect_sndfile_buffer_frames =
session->fragment_size_out / session->audio_sample_size_out *
session->effect_sfinfo.samplerate / session->audio_speed_out;
session->effect_sndfile_buffer = (short*) malloc (sizeof(short) *
(session->effect_sndfile_buffer_frames + 1) * /* doubled dummy at end */
session->effect_sfinfo.channels);
}
session->effect_tag = gtk_input_add_full(session->audio_fd_out,
GDK_INPUT_WRITE,
session_effect_callback,
NULL,
(gpointer) session,
NULL);
session->effect = kind;
session->effect_pos = 0;
}
/*
* Reset sound device and unset callback
*/
void session_effect_stop(session_t *session) {
if (session->effect != EFFECT_NONE) { /* stop only if already playing */
if (session->effect == EFFECT_SOUNDFILE) {
sf_close(session->effect_sndfile);
free(session->effect_sndfile_buffer);
}
gtk_input_remove(session->effect_tag);
session->effect = EFFECT_NONE;
}
session_io_handlers_stop(session);
session_reset_audio(session);
session_io_handlers_start(session);
}
/*
* Sets status bar audio state (e.g. "AUDIO OFF")
* hide if note is ""
*/
void session_audio_notify(session_t *session, char *note) {
GtkWidget *dummy_label; /* needed to adjust size of sub-statusbar */
GtkRequisition requisition;
gtk_widget_hide(session->audio_warning);
if (*note) {
gtk_statusbar_pop(GTK_STATUSBAR(session->audio_warning),
session->audio_context_id);
gtk_statusbar_push(GTK_STATUSBAR(session->audio_warning),
session->audio_context_id, note);
dummy_label = gtk_label_new(note);
gtk_widget_show(dummy_label);
gtk_widget_size_request(dummy_label, &requisition);
gtk_widget_set_size_request(session->audio_warning,
requisition.width + 4, -1);
gtk_widget_show(session->audio_warning);
}
}
/*
* Sets new state in session and GUI (also handles audio state)
*
* returns 0 on success, -1 otherwise (can't open audio device)
*/
int session_set_state(session_t *session, enum state_t state) {
int result = 0;
/* open / close audio when needed, set state */
session_io_handlers_stop(session);
if (session->option_release_devices && state != session->state) {
if (state == STATE_READY && session->state != STATE_RINGING_QUIET) {
/* release */
session_audio_deinit(session);
} else if ((session->state == STATE_READY ||
session->state == STATE_RINGING_QUIET) &&
state != STATE_READY && state != STATE_RINGING_QUIET) {
/* (try to) resume */
if (session_audio_init(session)) {
state = session->state;
result = -1;
}
}
}
session->state = state;
session_io_handlers_start(session);
/* some menu items are selected only in STATE_READY */
gtk_widget_set_sensitive(session->menuitem_settings, state == STATE_READY);
gtk_widget_set_sensitive(session->menuitem_line_check, state == STATE_READY);
/* start / stop effects when needed */
switch (state) {
case STATE_DIALING:
if (session->effect == EFFECT_NONE)
session_effect_start(session, EFFECT_RINGING);
if (debug) fprintf(stderr, "New state: STATE_DIALING\n");
break;
case STATE_RINGING:
if (session->option_popup) {
gtk_window_present(GTK_WINDOW(session->main_window));
}
if (session->effect == EFFECT_NONE)
session_effect_start(session, EFFECT_RING);
if (debug) fprintf(stderr, "New state: STATE_RINGING\n");
break;
case STATE_RINGING_QUIET:
if (session->option_popup) {
gtk_window_present(GTK_WINDOW(session->main_window));
}
if (debug) fprintf(stderr, "New state: STATE_RINGING_QUIET\n");
break;
case STATE_READY:
gtk_widget_grab_focus(GTK_WIDGET(GTK_COMBO(session->dial_number_box)
->entry));
if (session->effect != EFFECT_NONE)
session_effect_stop(session);
if (debug) fprintf(stderr, "New state: STATE_READY\n");
break;
case STATE_CONVERSATION:
if (session->effect != EFFECT_NONE)
session_effect_stop(session);
session->touchtone_countdown_isdn = 0;
session->touchtone_countdown_audio = 0;
if (debug) fprintf(stderr, "New state: STATE_CONVERSATION\n");
break;
case STATE_SERVICE:
if (debug) fprintf(stderr, "New state: STATE_SERVICE\n");
break;
case STATE_PLAYBACK:
if (session->effect == EFFECT_NONE)
session_effect_start(session, EFFECT_SOUNDFILE);
if (debug) fprintf(stderr, "New state: STATE_PLAYBACK\n");
break;
default:
fprintf(stderr, "Warning: session_set_state: Unhandled state.\n");
}
/* audio on / off notify */
if (session->option_release_devices) {
session_audio_notify(session,
state == STATE_READY || state == STATE_RINGING_QUIET ?
_("Audio OFF") : _("Audio ON"));
} else {
session_audio_notify(session, "");
}
/* status line */
gtk_statusbar_pop(GTK_STATUSBAR(session->status_bar),
session->phone_context_id);
gtk_statusbar_push(GTK_STATUSBAR(session->status_bar),
session->phone_context_id,
_(state_data[state].status_bar));
gtk_label_set_text(GTK_LABEL(session->pick_up_label),
_(state_data[state].pick_up_label));
gtk_widget_set_sensitive(session->pick_up_button,
state_data[state].pick_up_state);
gtk_label_set_text(GTK_LABEL(session->hang_up_label),
_(state_data[state].hang_up_label));
gtk_widget_set_sensitive(session->hang_up_button,
state_data[state].hang_up_state);
if (state == STATE_READY) {
llcheck_bar_reset(session->llcheck_in);
llcheck_bar_reset(session->llcheck_out);
}
return result;
}
/*
* Callback: reinitialize isdn input watchdog
*/
static gboolean gtk_isdn_input_defer_timeout(session_t* session) {
session->gtk_isdn_input_tag = gtk_input_add_full(session->isdn_fd,
GDK_INPUT_READ,
gtk_handle_isdn_input,
NULL,
(gpointer) session,
NULL);
return FALSE; /* don't call me regularly */
}
/* defer 300 milliseconds if appropriate */
#define DEFER_INTERVAL 300
/*
* put a graceful delay into GTK main loop to prevent permanent isdn input
* callback
*/
static void gtk_isdn_input_defer(session_t* session) {
gtk_input_remove(session->gtk_isdn_input_tag);
session->gtk_isdn_input_tag =
gtk_timeout_add(DEFER_INTERVAL,
(GtkFunction) gtk_isdn_input_defer_timeout,
session);
}
/*
* callback for gtk on isdn input
*
* input: data: session (session_t *)
* fd: file descriptor where we got the input from
* condition: will be GDK_INPUT_READ in this case
*/
void gtk_handle_isdn_input(gpointer data, gint fd _U_,
GdkInputCondition condition _U_) {
session_t *session = (session_t *) data;
char *temp;
switch (session->state) {
case STATE_READY: /* we are in command mode */
if (isdn_try_read_line(session)){ /* got new line: something happened */
if (!strncmp(session->isdn_inbuf, "RING/", 5)) { /* -> RINGING state */
session->isdn_inbuf[session->isdn_inbuf_len - 2] = 0;
/* caller id update */
session->ring_time = time(NULL);
/* save callee's number */
free(session->to);
session->to = strdup(&session->isdn_inbuf[5]);
cid_add_line(session, CALL_IN, NULL, session->to);
if (session_set_state(session, STATE_RINGING))
session_set_state(session, STATE_RINGING_QUIET);
} else { /* something else */
if (debug)
fprintf(stderr, "Unknown message from ISDN device.\n");
}
}
break;
case STATE_DIALING:
if (isdn_try_read_line(session)){ /* a response to our dial request */
if (strstr(session->isdn_inbuf, "BUSY\r\n")) { /* get back to READY */
session_set_state(session, STATE_READY);
cid_set_duration(session, _("(BUSY)"));
} else if (strstr(session->isdn_inbuf, "VCON\r\n")) { /* let's go! */
session_start_conversation(session); /* including state transition */
} else if (strstr(session->isdn_inbuf, "NO CARRIER\r\n")) {
/* timeout? */
session_set_state(session, STATE_READY);
cid_set_duration(session, _("(TIMEOUT)"));
} else { /* got some other modem answer string while dialing out */
if (debug)
fprintf(stderr, "Unknown message from ISDN device.\n");
}
}
break;
case STATE_RINGING:
case STATE_RINGING_QUIET:
if (isdn_try_read_line(session)){ /* got new line: something happened */
if (strstr(session->isdn_inbuf, "VCON\r\n")) { /* let's go! */
/* will only come in STATE_RINGING */
session_start_conversation(session); /* including state transition */
} else if (strstr(session->isdn_inbuf, "CALLER NUMBER: ")) {
/* got Caller ID */
session->isdn_inbuf[session->isdn_inbuf_len - 2] = 0;
/* save caller's number */
free(session->from);
session->from = strdup(&session->isdn_inbuf[15]);
/* complete from field */
cid_set_from(session, session->from);
/* execute command if given */
if (session->exec_on_incoming) {
temp = strdup(session->exec_on_incoming);
substitute(&temp, "%n", session->to);
substitute(&temp, "%s", session->from);
execute(temp);
free(temp);
}
} else if (strstr(session->isdn_inbuf, "RUNG\r\n")) {
/* caller giving up */
session_set_state(session, STATE_READY);
cid_set_duration(session, _("(RUNG)"));
cid_mark_row(session, session->cid_num - 1, TRUE);
} else { /* got some other modem answer string while it rings */
if (debug)
fprintf(stderr, "Unknown message from ISDN device.\n");
}
}
break;
case STATE_CONVERSATION:
process_isdn_source(session);
if (session->samples_in >= ISDN_SPEED)
session->samples_in %= ISDN_SPEED;
session->no_input = 0;
if (session->aborted || session->hangup) { /* That's it! */
if (session->hangup)
session_deinit_conversation(session, 0); /* 0 == other side hung up */
else
session_deinit_conversation(session, 1); /* 1 == let's hang up (I/O error) */
session_set_state(session, STATE_READY);
cid_set_duration(session, NULL);
}
break;
case STATE_SERVICE:
if (debug)
fprintf(stderr, "Note: Got ISDN input in service mode.\n");
gtk_isdn_input_defer(session);
break;
case STATE_PLAYBACK:
if (debug)
fprintf(stderr, "Note: Got ISDN input in playback mode.\n");
gtk_isdn_input_defer(session);
break;
default:
fprintf(stderr,
"Warning: gtk_handle_isdn_input: Unknown session state.\n");
gtk_isdn_input_defer(session);
}
}
/*
* callback for gtk on audio isdn input
*
* input: data: session (session_t *)
* fd: file descriptor where we got the input from
* condition: will be GDK_INPUT_READ in this case
*/
void gtk_handle_audio_input(gpointer data, gint fd _U_,
GdkInputCondition condition _U_) {
session_t *session = (session_t *) data;
switch (session->state) {
case STATE_READY: /* we are in command mode */
if (debug > 1)
fprintf(stderr, "Warning: Got audio input in ready mode (ALSA?).\n");
/* flush audio input */
read(session->audio_fd_in,
session->audio_inbuf, session->fragment_size_in);
break;
case STATE_DIALING:
if (debug > 1)
fprintf(stderr, "Warning: Got audio input in dialing mode (ALSA?).\n");
/* flush audio input */
read(session->audio_fd_in,
session->audio_inbuf, session->fragment_size_in);
break;
case STATE_RINGING:
if (debug > 1)
fprintf(stderr, "Warning: Got audio input in ringing mode (ALSA?).\n");
/* flush audio input */
read(session->audio_fd_in,
session->audio_inbuf, session->fragment_size_in);
break;
case STATE_RINGING_QUIET:
if (debug > 1)
fprintf(stderr,
"Warning: Got audio input in QUIET ringing mode.\n");
/* flush audio input */
read(session->audio_fd_in,
session->audio_inbuf, session->fragment_size_in);
break;
case STATE_CONVERSATION:
process_audio_source(session);
if (session->samples_out >= session->audio_speed_in)
session->samples_out %= session->audio_speed_in;
session->no_input++;
/* if no more input from isdn came, assume abort and switch back */
if (session->no_input >= 10) {
/* XXX: reasonable number? */
if (isdn_blockmode(session->isdn_fd, 0))
fprintf(stderr, "Error: Could not switching off isdn blockmode.\n");
session->no_input = 0;
}
if (session->aborted) { /* That's it! */
session_deinit_conversation(session, 1);
/* 1 == let's hang up (I/O error) */
session_set_state(session, STATE_READY);
cid_set_duration(session, NULL);
}
break;
case STATE_SERVICE:
if (debug > 1)
fprintf(stderr, "Warning: Got audio input in service mode (ALSA?).\n");
/* flush audio input */
read(session->audio_fd_in,
session->audio_inbuf, session->fragment_size_in);
break;
case STATE_PLAYBACK:
if (debug > 1)
fprintf(stderr, "Warning: Got audio input in playback mode (ALSA?).\n");
/* flush audio input */
read(session->audio_fd_in,
session->audio_inbuf, session->fragment_size_in);
break;
default:
fprintf(stderr,
"Warning: gtk_handle_audio_input: Unknown session state.\n");
/* flush audio input */
read(session->audio_fd_in,
session->audio_inbuf, session->fragment_size_in);
}
}
/*
* initiates dialing to specified number
* -> changes contents of dial entry and simulates pick up button
*/
void session_make_call(session_t *session, char *number) {
gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(session->dial_number_box)->entry),
number);
gtk_button_clicked(GTK_BUTTON(session->pick_up_button));
}
/*
* callback for GTK on pick up button clicked
*
* input: widget: the button
* data: will be a (session_t *)
*/
void gtk_handle_pick_up_button(GtkWidget *widget _U_, gpointer data) {
session_t *session = (session_t *) data;
char *s; /* the modem dial string */
const char *number; /* the number to dial "inside" gtk (entry) */
char *clear_number; /* number after un_vanity() */
int result;
switch (session->state) {
case STATE_READY: /* we are in command mode and want to dial */
number = gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(session->dial_number_box)
->entry));
/* replace letters with numbers ("Vanity" Numbers) */
clear_number = un_vanity(strdup(number));
if ((s = (char*) malloc(strlen(clear_number) + 5)) &&
strcmp(clear_number, "")) {
if (!session_set_state(session, STATE_DIALING)) {
/* dial only if audio is on etc. */
snprintf(s, strlen(clear_number) + 5, "ATD%s\n", clear_number);
tty_clear(session->isdn_fd);
result = tty_write(session->isdn_fd, s);
free(s);
if (result) {
fprintf(stderr, "Error dialing.\n");
} else {
/* update dial combo box */
session_history_add(session, number);
/* caller id update */
session->ring_time = time(NULL);
cid_add_line(session, CALL_OUT, session->msn, clear_number);
/* save caller's and callee's number */
free(session->from);
session->from = strdup(session->msn);
free(session->to);
session->to = strdup(clear_number);
}
} else {
show_audio_error_dialog();
}
}
free(clear_number);
break;
case STATE_DIALING: /* already dialing! */
break;
case STATE_RINGING: /* we want to pick up the phone while it rings */
tty_clear(session->isdn_fd);
if (tty_write(session->isdn_fd, "ATA\n"))
fprintf(stderr, "Error answering call.\n");
break;
case STATE_RINGING_QUIET:
if (!session_set_state(session, STATE_RINGING)) {
tty_clear(session->isdn_fd);
if (tty_write(session->isdn_fd, "ATA\n"))
fprintf(stderr, "Error answering call.\n");
} else {
show_audio_error_dialog();
}
break;
case STATE_CONVERSATION: /* channel already working */
fprintf(stderr,
"Non-sense warning: Pick up button pressed in conversation mode\n");
break;
case STATE_SERVICE:
fprintf(stderr,
"Non-sense warning: Pick up button pressed in service mode\n");
break;
case STATE_PLAYBACK:
fprintf(stderr,
"Non-sense warning: Pick up button pressed in playback mode\n");
break;
default:
fprintf(stderr,
"Warning: gtk_handle_pick_up_button: Unknown session state.\n");
}
}
/*
* callback for GTK on hang up button clicked, !!! also called on exit !!!
*
* input: widget: the button, NULL when called directly (on exit)
* data: will be a (session_t *)
*/
void gtk_handle_hang_up_button(GtkWidget *widget _U_, gpointer data) {
session_t *session = (session_t *) data;
switch (session->state) {
case STATE_READY: /* we are already in command mode */
break;
case STATE_DIALING:/* abort dialing */
tty_clear(session->isdn_fd);
if (tty_write(session->isdn_fd, "ATH\n"))
fprintf(stderr, "Error answering call.\n");
session_set_state(session, STATE_READY);
cid_set_duration(session, _("(ABORTED)"));
break;
case STATE_RINGING: /* reject call */
case STATE_RINGING_QUIET: /* reject call */
tty_clear(session->isdn_fd);
if (tty_write(session->isdn_fd, "ATH\n"))
fprintf(stderr, "Error answering call.\n");
session_set_state(session, STATE_READY);
cid_set_duration(session, _("(REJECTED)"));
break;
case STATE_CONVERSATION: /* hang up (while b-channel is open) */
session_deinit_conversation(session, 1); /* 1 == we hang up ourselves ;) */
session_set_state(session, STATE_READY);
cid_set_duration(session, NULL);
break;
case STATE_SERVICE:
fprintf(stderr,
"Non-sense warning: Hang up button pressed in service mode\n");
break;
case STATE_PLAYBACK:
session_set_state(session, STATE_READY);
break;
default:
fprintf(stderr,
"Warning: gtk_handle_hang_up_button: Unknown session state.\n");
}
}
/*
* cut session->dial_number_history to specified size
* (session->dial_number_history_maxlen)
* -> and redisplay in session->dial_number_box
*/
static void session_history_normalize(session_t *session) {
/* cut size if needed */
while (g_list_length(session->dial_number_history) >
session->dial_number_history_maxlen + 1) {
free(g_list_nth_data(session->dial_number_history,
g_list_length(session->dial_number_history) - 1));
session->dial_number_history = g_list_remove_link(
session->dial_number_history,
g_list_last(session->dial_number_history));
}
gtk_combo_set_popdown_strings(GTK_COMBO(session->dial_number_box),
session->dial_number_history);
}
/*
* Add line to history of dial number combo box at start (first row)
* and care about maximum size of history
*
* number will be copied, so caller has to care about it's associated memory
*/
void session_history_add(session_t *session, const char *number) {
char *temp = strdup(number);
session->dial_number_history = g_list_insert(
session->dial_number_history, temp, 1);
session_history_normalize(session);
}
/*
* like session_history_add but _appending_ number to list
*/
void session_history_append(session_t *session, char *number) {
char *temp = strdup(number);
session->dial_number_history = g_list_append(
session->dial_number_history, temp);
session_history_normalize(session);
}