OK. Here it is if anyone is interested in taking a look.

> You could attach the code to an email.
>
> On Sun, Dec 7, 2008 at 8:58 AM, David Baron <[EMAIL PROTECTED]> wrote:
> > On Sunday 07 December 2008 15:04:34 Sebastian Kügler wrote:
> >> On Sunday 07 December 2008 13:08:49 David Baron wrote:
> >> > I am building an applet to control a speakerphone modem. I have
> >> > encountered the following problems:
> >>
> >> Knowing your version of KDE would be useful. Also, maybe you can post
> >> code so people can point out things that might be wrong.
> >>
> >> And welcome to Plasma :-)
> >
> > I am using kde4.1.3 from debian experimental and backports.
> > While the code is not voluminous, it is longer than appropriate for such
> > a posting. If the list permits inclusions, I can do so.
> >
> > Basically, the paint method places an image or alternating by a timer,
> > two images based on program state. A "daemon" thread, code lifted from
> > xringd, monitors the modem for dial. Clicking the applet can answer the
> > phone, hang up when talking or put up a widget from which one can dial a
> > call.
> >
> > All of the io which can be time-disrupting is in threads so the UI should
> > remain responsive and even.
> >
> > Nothing very complicated once one can figures out how to read the modem
> > effectively.
> > _______________________________________________
> > Plasma-devel mailing list
> > Plasma-devel@kde.org
> > https://mail.kde.org/mailman/listinfo/plasma-devel
>
> _______________________________________________
> Plasma-devel mailing list
> Plasma-devel@kde.org
> https://mail.kde.org/mailman/listinfo/plasma-devel

/* phoneapplet.cpp */

#include <QPainter>
#include <QFontMetrics>
#include <QSize>
#include <QtCore>
#include <KIcon>

#include <plasma/svg.h>
#include <plasma/theme.h>

// includes for linux std c++, ioctl, etc
#include <errno.h>
#include <stdio.h>
#include <sys/file.h>
#include <stdlib.h>
#include <unistd.h>
#include <paths.h>
#include <fcntl.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <signal.h>
#include <termios.h>
#include <getopt.h>
#include <syslog.h>
#include <pwd.h>
#include <ctype.h>
#include <linux/version.h>
#include <linux/serial.h>
#include <printf.h>

#include "phoneapplet.h"
#include "ui_phonecall.h"

static char* new_string(char*, const char*);
static bool fork_cmd( char* cmd );
static int modem_talk(int, const char*, const char*);
static int modem_volume( int, int );
static int modem_dial(int, const char*);

class RingDaemonThread : public QThread {
public:
    RingDaemonThread( PhoneApplet* parent = 0 ) { m_Parent = parent; m_iRet = 0; }
    ~RingDaemonThread() {}
    void run() { m_iRet = m_Parent->ring_daemon(); }
    void pause( long ms , QThread* qt ) { qt->wait( ms ); }
    void pause( QThread* qt ) { qt->wait(); }
    bool result() { return isRunning(); }
    int error() { return m_iRet; }
private:
    PhoneApplet* m_Parent;
    int m_iRet;
};

class ModemAnswerThread : public QThread {
public:
    ModemAnswerThread( PhoneApplet* parent ) { m_Parent = parent; m_iRet = 0; }
    ~ModemAnswerThread() {}
    void run() { m_iRet = m_Parent->modem_answer_sequence( m_fn ); }
    bool result() { return m_iRet == 0; }
    int error() { return m_iRet; }
    void setFn( int fn ) { m_fn = fn; }
    void sleepu( unsigned long msec ) { usleep( msec ); }
private:
    PhoneApplet* m_Parent;
    int m_fn;
    int m_iRet;
};

class ModemDialThread : public QThread {
public:
    ModemDialThread( PhoneApplet* parent ) { m_Parent = parent; m_iRet = 0; }
    ~ModemDialThread() {}
    void run() { m_iRet = m_Parent->modem_dial_sequence( m_fn, m_Number ); }
    bool result() { return m_iRet == 0; }
    int error() { return m_iRet; }
    void setFn( int fn ) { m_fn = fn; }
    void setNumber( QString n ) { m_Number = n; }
    void sleepu( unsigned long msec ) { usleep( msec ); }
private:
    PhoneApplet* m_Parent;
    QString m_Number;
    int m_fn;
    int m_iRet;
};

class ModemHangupThread : public QThread {
public:
    ModemHangupThread( PhoneApplet* parent ) { m_Parent = parent; m_iRet = 0; }
    ~ModemHangupThread() {}
    void run() { m_iRet = m_Parent->modem_hangup_sequence( m_fn ); }
    bool result() { return m_iRet == 0; }
    int error() { return m_iRet; }
    void setFn( int fn ) { m_fn = fn; }
    void sleepu( unsigned long msec ) { usleep( msec ); }
private:
    PhoneApplet* m_Parent;
    int m_fn;
    int m_iRet;
};

class MyPhoneNumber : public QDialog {
public:
    MyPhoneNumber(PhoneApplet* parent) {
        m_Parent = parent; // Caller, simplest UI:
        setVisible(FALSE);

        setWindowIcon( KIcon( "internet-telephony" ) );
        //m_Ui.toolButton_Close->setIcon( KIcon( "system-shutdown" ));
        //m_Ui.toolButton_Bell->setIcon( KIcon( "preferences-desktop-notification-bell" ) );

        move( m_Parent->popupPosition( size() ) );

        m_Ui.setupUi( this );

        /* set this all up in the parent because:
         * 1. Cannot find the slot in this context for some reason
         * 2. This might enable tone-control on live call
         *        
        connect( m_Ui.pushButton_Call, SIGNAL( clicked() ), m_Parent, SLOT( CallNumber()) );
        connect( m_Ui.checkBox_Bell, SIGNAL( clicked()), m_Parent, SLOT( toggle_sound()));
        connect( m_Ui.pushButton_Back, SIGNAL( clicked()), this, SLOT(deleteText()));
        connect( m_Ui.pushButton_1, SIGNAL( clicked()), this, SLOT( digitClicked()) );
        connect( m_Ui.pushButton_2, SIGNAL( clicked()), this, SLOT( digitClicked()) );
        connect( m_Ui.pushButton_3, SIGNAL( clicked()), this, SLOT( digitClicked()) );
        connect( m_Ui.pushButton_4, SIGNAL( clicked()), this, SLOT( digitClicked()) );
        connect( m_Ui.pushButton_5, SIGNAL( clicked()), this, SLOT( digitClicked()) );
        connect( m_Ui.pushButton_6, SIGNAL( clicked()), this, SLOT( digitClicked()) );
        connect( m_Ui.pushButton_7, SIGNAL( clicked()), this, SLOT( digitClicked()) );
        connect( m_Ui.pushButton_8, SIGNAL( clicked()), this, SLOT( digitClicked()) );
        connect( m_Ui.pushButton_9, SIGNAL( clicked()), this, SLOT( digitClicked()) );
        connect( m_Ui.pushButton_0, SIGNAL( clicked()), this, SLOT( digitClicked()) );
         */
    }
    QString text() {
        return m_Ui.lineEdit_Number->text();
    }
    QObject* Sender() { return sender(); }
    Ui::PhoneDialog* UI() { return &m_Ui; }
    
private:
    PhoneApplet* m_Parent;
    Ui::PhoneDialog m_Ui;
};

PhoneApplet::PhoneApplet(QObject *parent, const QVariantList &args)
: Plasma::Applet(parent, args),
m_svgMain(this), m_svgRing(this), m_svgTalk(this)
{
    m_eState = eIDLE;
    m_svgMain.setImagePath( ":/icons/internet-telephony.svgz" );
    m_svgRing.setImagePath( ":/icons/preferences-desktop-notification-bell.svgz" );
    m_svgRing2.setImagePath( ":/icons/internet-telephony.svgz" );
    m_svgConnect.setImagePath( ":/icons/media-seek-forward.svgz" );
    m_svgConnect2.setImagePath( ":/icons/internet-telephony.svgz" );
    m_svgDial.setImagePath( ":/icons/preferences-desktop-user.svgz" );
    m_svgDial2.setImagePath( ":/icons/internet-telephony.svgz" );
    m_svgTalk.setImagePath( ":/icons/user-identity.svgz" );
    m_svgTalk2.setImagePath( ":/icons/list-remove-user.svgz" );
    // this will get us the standard applet background, for free!
    setBackgroundHints(DefaultBackground);
    resize(48, 48);
}

PhoneApplet::~PhoneApplet() {
    if (hasFailedToLaunch()) {
        // Do something
    } else {
        // Save settingsome cleanup here
        AbortAllThreads();
        delete m_QTimerBlink;
        delete m_QTimerEndRings;
        delete m_DaemonThread;
        delete m_AnswerThread;
        delete m_HangupThread;
        delete m_DialThread;
        delete m_PhoneNumber;

        delete m_strModemFilename;
        delete m_strPlaySound;
    }
}

void PhoneApplet::closeEvent( QCloseEvent* ev ) {
    // cannot wait until the destructor, do it now!
    // End the thread, just in case :-)
    //AbortAllThreads();
    m_PhoneNumber->setVisible(FALSE);

    ev->ignore();

    Applet::closeEvent( ev );
}

void PhoneApplet::AbortAllThreads() {
    m_DaemonThread->terminate();
    m_QTimerBlink->stop();
    m_QTimerEndRings->stop();
    m_AnswerThread->terminate();
    m_HangupThread->terminate();
    m_DialThread->terminate();
    m_PhoneNumber->setVisible(FALSE);
}

void PhoneApplet::init() {
    // set up defaults, enable configuration later on
    m_strModemFilename = new_string(m_strModemFilename, MODEM_FILENAME);
    m_strPlaySound = new_string(m_strPlaySound, CMD_PLAY_SOUND);

    m_bWantSound = TRUE;   // for TRUE, need some plasma/threadsafe exec()
    m_iVolume = 2;          // medium

    m_eState = eIDLE; // decent startup state, but must be reset to use
    m_bAlternate = FALSE;

    // set up timer
    m_QTimerBlink = new QTimer( this );
    m_QTimerBlink->setInterval( MSEC_BLINK );
    m_QTimerBlink->setSingleShot(FALSE);
    connect( m_QTimerBlink, SIGNAL(timeout()), this, SLOT(blinkit()) );

    m_QTimerEndRings = new QTimer( this );
    m_QTimerEndRings->setInterval( MSEC_AFTER_RINGS );
    m_QTimerEndRings->setSingleShot(TRUE);
    connect( m_QTimerEndRings, SIGNAL(timeout()), this, SLOT(proc_endringseq()) );

    // Connect up modem UI threads
    m_AnswerThread = new ModemAnswerThread( this );
    connect( m_AnswerThread, SIGNAL( finished()), this, SLOT( CallAnswered() ) );
    m_DialThread = new ModemDialThread( this );
    connect( m_DialThread, SIGNAL( finished()), this, SLOT( CallAnswered() ) );
    m_HangupThread = new ModemHangupThread( this );
    connect( m_HangupThread, SIGNAL( finished()), this, SLOT( CallHungUp() ) );

    m_PhoneNumber = new MyPhoneNumber( this );
    {   /*  set up the UI here as some of it did not work in the class itself
         *
         */
        m_PhoneNumber->UI()->toolButton_Bell->setChecked( m_bWantSound );
        connect( m_PhoneNumber->UI()->pushButton_Call, SIGNAL( clicked() ), this, SLOT( CallNumber()) );
        connect( m_PhoneNumber->UI()->toolButton_Bell, SIGNAL( clicked()), this, SLOT( toggle_sound()));
        connect( m_PhoneNumber->UI()->toolButton_Volume, SIGNAL( clicked()), this, SLOT( setVolume()));
        connect( m_PhoneNumber->UI()->pushButton_Back, SIGNAL( clicked()), this, SLOT(deleteText()));
        connect( m_PhoneNumber->UI()->pushButton_1, SIGNAL( clicked()), this, SLOT( digitClicked1()) );
        connect( m_PhoneNumber->UI()->pushButton_2, SIGNAL( clicked()), this, SLOT( digitClicked2()) );
        connect( m_PhoneNumber->UI()->pushButton_3, SIGNAL( clicked()), this, SLOT( digitClicked3()) );
        connect( m_PhoneNumber->UI()->pushButton_4, SIGNAL( clicked()), this, SLOT( digitClicked4()) );
        connect( m_PhoneNumber->UI()->pushButton_5, SIGNAL( clicked()), this, SLOT( digitClicked5()) );
        connect( m_PhoneNumber->UI()->pushButton_6, SIGNAL( clicked()), this, SLOT( digitClicked6()) );
        connect( m_PhoneNumber->UI()->pushButton_7, SIGNAL( clicked()), this, SLOT( digitClicked7()) );
        connect( m_PhoneNumber->UI()->pushButton_8, SIGNAL( clicked()), this, SLOT( digitClicked8()) );
        connect( m_PhoneNumber->UI()->pushButton_9, SIGNAL( clicked()), this, SLOT( digitClicked9()) );
        connect( m_PhoneNumber->UI()->pushButton_0, SIGNAL( clicked()), this, SLOT( digitClicked0()) );
    }
    //m_QTimerBlink->start();
    m_DaemonThread = new RingDaemonThread( this );
    StartDaemon();
}

void PhoneApplet::digitClicked( char digit) {
    QString  number = m_PhoneNumber->UI()->lineEdit_Number->text();
    number.append( digit );
    m_PhoneNumber->UI()->lineEdit_Number->setText( number);

    if  ( m_eState == eTALK ) {
        // try to send the tone!
        QString tone;
        tone =+ MODEM_TONE;
        tone += '{';
        tone += digit;
        tone += ",2}";  // 2 x 100 ms
        int ret = modem_talk( m_iModemFileno, tone.toAscii(), MODEM_WAS_OK );
        if  ( ret != 0 )
            ErrMessage( ret );  //my not want this but diagnostic for now
    }
}

void PhoneApplet::deleteText() {
    QString number = m_PhoneNumber->UI()->lineEdit_Number->text();
    number.truncate(number.length() - 1);
    m_PhoneNumber->UI()->lineEdit_Number->setText( number);
}

void PhoneApplet::paintInterface(QPainter *p,
        const QStyleOptionGraphicsItem *option, const QRect &contentsRect) {
    p->setRenderHint(QPainter::SmoothPixmapTransform);
    p->setRenderHint(QPainter::Antialiasing );
    // Now we draw the applet, starting with our svg for appropriate program state
    // The icons do not look good filling the whole rectangle so:
    QRect myRect;
    QPoint myOffset;

    myOffset.setX( contentsRect.width() / 10 );
    myOffset.setY( contentsRect.height() / 10 );
    myRect = contentsRect;
    myRect.setX( contentsRect.x() + myOffset.x() );
    myRect.setY( contentsRect.y() + myOffset.y() );
    myRect.setWidth( contentsRect.width() - 2 * myOffset.x() );
    myRect.setHeight( contentsRect.height() - 2 * myOffset.y() );

    switch ( m_eState ) {
        case eTALK:
            paintInterfaceState( p, option, myRect,
                    m_bAlternate ? m_svgTalk2 : m_svgTalk );
            break;
        case eRING:
            paintInterfaceState( p, option, myRect,
                    m_bAlternate ? m_svgRing2 : m_svgRing );
            break;
        case eCONNECT:
            paintInterfaceState( p, option, myRect,
                    m_bAlternate ? m_svgConnect2 : m_svgConnect );
            break;
        case eDIAL:
            paintInterfaceState( p, option, myRect,
                    m_bAlternate ? m_svgDial2 : m_svgDial );
            break;
        default:
            paintInterfaceState( p, option, myRect, m_svgMain);
            break;
    }

    // We place the icon and text
    //p->drawPixmap(7, 0, m_icon.pixmap((int) contentsRect.width(), (int) contentsRect.width() - 14));
    /*p->save();
    p->setPen(Qt::white);
    p->drawText(contentsRect,
            Qt::AlignBottom | Qt::AlignHCenter,
            "Phone Home!");
    p->restore();*/
}

void PhoneApplet::paintInterfaceState(QPainter *p,
        const QStyleOptionGraphicsItem *option, const QRect &contentsRect,
        Plasma::Svg& svg) {
    svg.resize((int) contentsRect.width(), (int) contentsRect.height());
    svg.paint(p, (int) contentsRect.left(), (int) contentsRect.top());
}

void PhoneApplet::StartDaemon() {
    if  ( m_DaemonThread->isRunning() )
        return;

    // do this stuff here to TEST modem access!
    if  ( access(m_strModemFilename, F_OK | R_OK | W_OK ) ) {
        ErrMessage( ERRNO_NOMODEM );
        set_state( eNOGOOD );
        return;
    }

    set_state( eIDLE );
    m_DaemonThread->start();
    if  ( !m_DaemonThread->result() ) {
        set_state( eNOGOOD );
        ErrMessage( m_DaemonThread->error() );
    }
}

void PhoneApplet::set_state( eMODEM_STATE newstate ) {
    m_bAlternate = FALSE;
    m_eState = newstate;
    if  ( m_eState == eIDLE )
        m_QTimerBlink->stop();
    else
        m_QTimerBlink->start();
    update();
}

void PhoneApplet::blinkit() {
    m_bAlternate = !m_bAlternate;
    update();
}

void PhoneApplet::AbortDaemon() {
    if  ( !m_DaemonThread->isRunning() )
        return;

    m_DaemonThread->quit();
}

int PhoneApplet::ring_daemon() {
    int ModemFileno = modem_open(m_strModemFilename, TRUE ); /* first open */

    // this should be tested beforehand!
    if (ModemFileno < 0) {
        return ERRNO_NOMODEM;
    }

    // code from xringd.c

    int c;
    if (ioctl(ModemFileno, TIOCGICOUNT, &c) != 0) {
        return ERRNO_BADLINUX;
    }
    // code from xringg.d

    long msec;
    struct timeval tmBef, tm;

    gettimeofday( &tmBef, NULL );

    /* main daemon loop */
    while (TRUE) {
        /* wait on RI line */
        if (ioctl(ModemFileno, TIOCMIWAIT, TIOCM_RNG) != 0) {
            if (EINTR == errno) {
                continue;
            }
            /*
             * XXX EIO may occur: port hung up by other process!
             * Reopen it - danger: reopening failing continously
             */
            ::close(ModemFileno);
            usleep(3 * 1000000); // apparently necessary on repopen
            ModemFileno = modem_open(m_strModemFilename, TRUE);
            continue;
        }

        gettimeofday(&tm, NULL);
        msec = (tm.tv_sec - tmBef.tv_sec) * 1000L +
                (tm.tv_usec - tmBef.tv_usec) / 1000L;
        tmBef = tm; // update the before time!
        if (msec > MSEC_IGNORE_RING)
            proc_ring();
    }

    if  ( ModemFileno >= 0 )
        ::close(ModemFileno);

    return 0;
}

void PhoneApplet::serial_setup( int gPortFd )
{
    int i;

    struct termios attr;

    if (tcgetattr(gPortFd, &attr) < 0) {
        MessageBox( "Call to tcgetattr failed" );
        return;
    }

    attr.c_iflag = 0;
    attr.c_oflag = 0;
    attr.c_cflag = CLOCAL | CREAD | CS8;
    attr.c_lflag = 0;
    attr.c_cc[VTIME] = 0;       /* timeout in tenths of a second */
    attr.c_cc[VMIN] = 1;        /* Only wait for a single char */

    cfsetispeed(&attr, MODEM_BAUD);
    cfsetospeed(&attr, MODEM_BAUD);

    if (tcsetattr(gPortFd, TCSAFLUSH, &attr) < 0) {
        MessageBox( "Call to tcsetattr failed" );
    }
}

int PhoneApplet::modem_open(char *file, bool readonly ) {
    int fd = -1;
    int options;

    if (readonly ) {
        options = O_NOCTTY | O_RDONLY | O_NDELAY ;
    }
    else {
        options = O_NOCTTY | O_RDWR | O_NDELAY ;
    }

    if  ((fd = ::open(file, options)) < 0) {
        return -1;
    }
    
    // DISABLE ECHO ALWAYS
    struct termios tios;

    ioctl(fd, TCGETS, &tios);
    tios.c_lflag &= ~(ECHO); /*|ECHOE|ECHOK|ECHOKE|ECHOCTL*/
    ioctl(fd, TCSETS, &tios);

    if  (!readonly )
        serial_setup( fd );

    return fd;
}

// FOR m_iModemFileno ONLY
void PhoneApplet::modem_close() {
    ::close( m_iModemFileno );
    m_iModemFileno = -1;
}

void PhoneApplet::proc_ring() {
    if  ( m_eState == eDIAL ) {
        m_DialThread->quit();
        modem_close();
        ErrMessage( ERRNO_TIMEOUT );
        set_state( eRING );
    }
    else if  ( m_eState == eIDLE ) {
        set_state( eRING);
    }
    else {}

    m_QTimerEndRings->start();

    if (m_eState == eRING && m_bWantSound) {
        // play the sound, do not care about restult
        QtConcurrent::run( fork_cmd, m_strPlaySound );
        //QtConcurrent::run( QSound::play, PLAY_SOUND_FILE );
    }
}

void PhoneApplet::proc_endringseq() {
    if  ( m_eState == eCONNECT && !m_AnswerThread->isFinished() ) {
        toggle_daemon();
        set_state( eIDLE );
        m_AnswerThread->quit();
        modem_close();
        ErrMessage( ERRNO_TIMEOUT );
        return;
    }
    if  ( m_eState == eRING )
        set_state( eIDLE );
}

void PhoneApplet::toggle_sound() {
    //m_bWantSound = !m_bWantSound;
    m_bWantSound = m_PhoneNumber->UI()->toolButton_Bell->isChecked();
}

void PhoneApplet::setVolume() {
    ++m_iVolume;
    if  ( m_iVolume > 3 )
        m_iVolume = 0;
}

void PhoneApplet::toggle_daemon() {
    if  ( m_DaemonThread->isRunning() )
        AbortDaemon();
    else
        StartDaemon();
}

// I do not know why this works but ... it does :-)
// TODO, do this using phonon
static bool fork_cmd(char *cmd)
{
	int pid;
	int fdnull;

	if ((pid = fork()) < 0) {
		return FALSE;
	}
        else if (0 == pid) {
		/* child */
		/* unblock signals we use - may be blocked here */
		//sigprocmask(SIG_UNBLOCK, &sig_mask, NULL);
		setsid();
		close(STDIN_FILENO);
		close(STDOUT_FILENO);
		close(STDERR_FILENO);
		/* connect /dev/null to std{in/out/err} */
		fdnull = open(_PATH_DEVNULL, O_RDWR);
		if (fdnull >= 0) {
			if (fdnull != 0) {
				dup2(fdnull, 0);
				close(fdnull);
			}
			dup2(0, 1);
			dup2(0, 2);
		}
		setuid(getuid());
		setgid(getgid());
		execl("/bin/sh", "sh", "-c", cmd, NULL);
                return TRUE;
		//exit(1);
	}
        else {
            return TRUE;   // ever get here?
        }
	/* parent */
}

// This is the UI:
void PhoneApplet::mousePressEvent ( QGraphicsSceneMouseEvent *ev ) {
    if  ( signalsBlocked() ) {
        Plasma::Applet::mousePressEvent( ev );
        return;
    }

    if  ( ev->button() == Qt::LeftButton )
        proc_click();
    else
        Plasma::Applet::mousePressEvent( ev );
}

void PhoneApplet::proc_click() {
    switch ( m_eState ) {
        case eTALK:     HangUp();       break;
        case eRING:     AnswerCall();   break;
        case eIDLE:     ToggleGUI();    break;
        case eDIAL:     m_DialThread->quit();
                        HangUp();
                        break;
        default:                        break;
    }
}

#define FMT "%s%s "

void PhoneApplet::HangUp() {
    // should not get here:
    if  ( m_iModemFileno < 0 )
        return;

    m_HangupThread->setFn( m_iModemFileno );
    m_HangupThread->start();
}

void PhoneApplet::CallHungUp() {
    modem_close();
    //m_PhoneNumber->hide();
    set_state( eIDLE );
}

void PhoneApplet::AnswerCall() {
    m_iModemFileno = modem_open( m_strModemFilename, FALSE );
    if  ( m_iModemFileno < 1 ) {
        ErrMessage( ERRNO_NOMODEM );
        return;
    }

    //toggle_daemon();
    set_state( eCONNECT );

    m_AnswerThread->setFn( m_iModemFileno );
    m_AnswerThread->start();
    m_DaemonThread->pause( MSEC_WAIT_THREAD, m_AnswerThread );
    m_QTimerEndRings->stop();
}

void PhoneApplet::CallAnswered() {
    bool stat;
    int err;

    //toggle_daemon();
    switch ( m_eState ) {
        case eCONNECT:
            stat = m_AnswerThread->result();
            err = m_AnswerThread->error();
            break;
        case eDIAL:
            stat = m_DialThread->result();
            err = m_DialThread->error();
            break;
        default:
            set_state( eIDLE );
            return;
    }

    if  ( !stat ) {
        //m_PhoneNumber->setVisible( FALSE );
        set_state( eIDLE );
        
        ErrMessage( err );
        modem_close();
        return;
    }

    set_state( eTALK );
}

void PhoneApplet::ToggleGUI() {
    //set_status( eDIAL);
    m_PhoneNumber->setVisible( !m_PhoneNumber->isVisible() );
}

void PhoneApplet::CallNumber() {
    // actions like main click
    if (m_eState == eTALK) {
        HangUp();
        return;
    } else if (m_eState == eDIAL) {
        m_DialThread->quit();
        HangUp();
        return;
    } else if (m_eState == eIDLE) {

        // prevent recursion
        if (m_DialThread->isRunning())
            return;

        m_iModemFileno = modem_open(m_strModemFilename, FALSE);
        if (m_iModemFileno < 1) {
            ErrMessage(ERRNO_NOMODEM);
            return;
        }
        //toggle_daemon();
        set_state(eDIAL);
        //MessageBox ( m_PhoneNumber->UI()->lineEdit_Number->text().toAscii() );

        m_DialThread->setFn(m_iModemFileno);
        m_DialThread->setNumber(m_PhoneNumber->UI()->lineEdit_Number->text());
        m_DialThread->start();
        m_DaemonThread->pause(MSEC_WAIT_THREAD, m_DialThread);
        //m_QTimerEndRings->stop();
    }
    else ;
}

void PhoneApplet::NotImplemented() {
    MessageBox("Not Implemented");
}

void PhoneApplet::MessageBox( const char* cmd ) {
    QMessageBox(QMessageBox::Information, name(), QString(cmd) ).exec();
}


int PhoneApplet::modem_answer_sequence( int fn ) {
    int ret;

    if  ( ( ret = modem_talk( fn, MODEM_INIT, MODEM_WAS_OK ) ) != 0 )
        return ERRNO_INITMODEM;
    //if  ( ( ret = modem_talk( fn, MODEM_RESET, MODEM_WAS_OK ) ) != 0 )
    //    return ERRNO_RESETMODEM;
    if  ( ( ret = modem_volume( fn, m_iVolume )) != 0 )
        return ERRNO_MODEMVOLUME;
    if  ( ( ret = modem_talk( fn, MODEM_SET_SPEAKERPHONE, MODEM_WAS_OK ) ) != 0 )
        return ERRNO_SETSPEAKERPHONE;
    if  ( ( ret = modem_talk( fn, MODEM_ANSWER, MODEM_VOICE_COM ) ) != 0 )
        return ERRNO_ANSWERMODEM;

    return 0;
}

int PhoneApplet::modem_dial_sequence( int fn, QString& number ) {
    int ret;

    if  ( number == "" )
        return ERRNO_NONUMBER;

    QString numbercmd;
    numbercmd = MODEM_TONE_DIAL_PRE;
    numbercmd += number;
    numbercmd += MODEM_TONE_DIAL_SUF;
    numbercmd.remove( '-'); //carefull!

    if  ( ( ret = modem_talk( fn, MODEM_INIT, MODEM_WAS_OK ) ) != 0 )
        return ERRNO_INITMODEM;
    //if  ( ( ret = modem_talk( fn, MODEM_RESET, MODEM_WAS_OK ) ) != 0 )
    //    return ERRNO_RESETMODEM;
    if  ( ( ret = modem_volume( fn, m_iVolume )) != 0 )
        return ERRNO_MODEMVOLUME;
    if  ( ( ret = modem_talk( fn, MODEM_SET_SPEAKERPHONE, MODEM_WAS_OK ) ) != 0 )
        return ERRNO_SETSPEAKERPHONE;
    if  ( ( ret = modem_dial( fn, numbercmd.toAscii() ) ) != 0 )
        return ret;     // may contain other status flags!

    return 0;
}

int PhoneApplet::modem_hangup_sequence(int fn ) {
    int ret1 = 0, ret2 = 0;

    ret1 = modem_talk( fn, MODEM_HANGUP, MODEM_WAS_OK );
    ret2 = modem_talk( fn, MODEM_RESET, MODEM_WAS_OK );

    return ret1 != 0 ? ret1 : ret2;
}

bool testresp( char* test, const char* resp ) {
    return strcasestr( test, resp ) != NULL;
}
/*
 * Tne next two function were written by Nelson Castillo.
 * You should not blame Dave for what he wrote here :-)
 */

int serial_read(int fd, char *buf, int len, struct timeval* tv) {
    fd_set rfds;
    int retval;
    int nread = 0;

    FD_ZERO(&rfds);
    FD_SET(fd, &rfds);

select_next:

    /* We rely on the value of tv after the select call.
     * This is a Linux-only thing.
     * TODO: Fix. */

    errno = 0;
    retval = select(fd + 1, &rfds, NULL, NULL, tv);

    if (retval == -1) {
        if (errno == EINTR)
            goto select_next;

        return -1;
    } else if (retval) {
        if (len - nread > 0) {
            int r;
            r = read(fd, buf + nread, len - nread);
            if (r > 0) {
                nread += r;

                if (len - nread > 0)
                    goto select_next;
            }
            if ( r < 0 )
                return nread;
        }
    }

    return nread;
}

void setup_timeout(long ms_timeout, struct timeval* tv) {
    tv->tv_sec = ms_timeout / 1000;
    tv->tv_usec = (ms_timeout % 1000) * 1000;
};

int modem_talk(int fn, const char* cmd, const char* resp) {
    char buffer[64];
    int ret;

    for (int t = 0; t < RETRIES; ++t) {
        while ( read( fn, buffer, 1 ) == 1 ) ;       // FLUSH before command!

        ret = write(fn, cmd, strlen(cmd));

        if ( ret < 0 )
            break;
        write( fn, "\r", 1 );       // general terminator ^M
        
        if (ret != strlen(cmd))
            continue;

        /*switch ( m_eState ) {
            case eCONNECT:  m_AnswerThread->sleepu(150000l);    break;
            case eDIAL:     m_DialThread->sleepu( 150000l);     break;
            case eTALK:     m_HangupThread->sleepu( 150000l );  break;
            default:                                            break;
        */

        /*char* bufPtr = buffer;
        char ch;
        do {
            if (read(fn, &ch, 1) < 0)
                break;
            *bufPtr = ch;
            ++bufPtr;
            *bufPtr = '\0';

            // if I interecepted a RING, start over :-)
            if  ( testresp( buffer, MODEM_RING ) )
                ch = 0, bufPtr = buffer;
        } while (ch != '\n' && ch != '\r' && bufPtr - buffer < 63);
        //ret = read( fn, buffer, 64 ); // flush is apparently needed!
        //ret = ioctl( fn, _IOR( 4, 1, char*), buffer );*/
        struct timeval tv;
        setup_timeout( MSEC_READ_TIMEOUT, &tv );
        serial_read( fn, buffer, 64, &tv );

        if (testresp( buffer, resp) )
            return 0;
        // need valid test
    }
    
    return ERRNO_GENERAL;
}

int modem_dial(int fn, const char *cmd) {
    char buffer[64];
    int ret;

    while ( read( fn, buffer, 1 ) == 1 ) ;       // FLUSH before command!
    
    ret = write(fn, cmd, strlen(cmd));

    if (ret < 0)
        return ERRNO_DIAL;

    write(fn, "\r", 1); // general terminator ^M
    
    if (ret != strlen(cmd))
        return ERRNO_DIAL;
    
    //m_DialThread->sleepu( 350000l );
    //long lret;
    //while ( ioctl(fn, FIONREAD, &lret), lret == 0l) ;

    /*char* bufPtr = buffer;
    char ch;
    do {
        ret = read(fn, &ch, 1);
        if  ( ret < 0)
            break;   //!!
        if  ( ret == 0 )
            continue;           // ??
     *bufPtr = ch;
        ++bufPtr;
     *bufPtr = '\0';

        // if I interecepted a RING, start over :-)
        if (testresp(buffer, MODEM_RING))
            ch = 0, bufPtr = buffer;
    } while (ch != '\n' && ch != '\r' && bufPtr - buffer < 63);*/
    struct timeval tv;
    setup_timeout(MSEC_DIAL_TIMEOUT, &tv);
    ret = serial_read(fn, buffer, 64, &tv);
    
    if  ( ret < 0 )
        return ERRNO_DIAL;

    // return any specific responses
    if (testresp(buffer, MODEM_BUSY))
        return ERRNO_BUSY;
    if (testresp(buffer, MODEM_CARRIER))
        return ERRNO_NOCARRIER;
    if (testresp(buffer, MODEM_DIALTONE))
        return ERRNO_NODIALTONE;
    if (testresp(buffer, MODEM_NOANSWER))
        return ERRNO_NOANSWER;
    if (testresp(buffer, MODEM_WAS_ERROR))
        return ERRNO_GENERAL;

    return 0;
}

int modem_volume( int fn, int vol ) {
    switch (vol) {
        case 0: return modem_talk( fn, MODEM_ZERO_VOLUME, MODEM_WAS_OK );
        case 1: return modem_talk( fn, MODEM_LOW_VOLUME, MODEM_WAS_OK );
        case 2: return modem_talk( fn, MODEM_MED_VOLUME, MODEM_WAS_OK );
        default:return modem_talk( fn, MODEM_HI_VOLUME, MODEM_WAS_OK );
    }
}

void PhoneApplet::ErrMessage( int err_no ) {
    switch ( err_no ) {
        case ERRNO_NOMODEM:         MessageBox( ERRMSG_NOMODEM );           break;
        case ERRNO_BADLINUX:        MessageBox( ERRMSG_BADLINUX );          break;
        case ERRNO_INITMODEM:       MessageBox( ERRMSG_INITMODEM );         break;
        case ERRNO_RESETMODEM:      MessageBox( ERRMSG_RESETMODEM );        break;
        case ERRNO_SETSPEAKERPHONE: MessageBox( ERRMSG_SETSPEAKERPHONE );   break;
        case ERRNO_HANGUPMODEM:     MessageBox( ERRMSG_HANGUPMODEM );       break;
        case ERRNO_ANSWERMODEM:     MessageBox( ERRMSG_ANSWERMODEM );       break;
        case ERRNO_BUSY:            MessageBox( ERRMSG_BUSY );              break;
        case ERRNO_NOCARRIER:       MessageBox( ERRMSG_NOCARRIER );         break;
        case ERRNO_NONUMBER:        MessageBox( ERRMSG_NONUMBER );          break;
        case ERRNO_DIAL:            MessageBox( ERRMSG_DIAL );              break;
        case ERRNO_TIMEOUT:         MessageBox( ERRMSG_TIMEOUT );           break;
        case ERRNO_NODIALTONE:      MessageBox( ERRMSG_NODIALTONE );        break;
        case ERRNO_NOANSWER:        MessageBox( ERRMSG_NOANSWER );          break;
        case ERRNO_MODEMVOLUME:     MessageBox( ERRMSG_MODEMVOLUME );       break;
        default:                    MessageBox( ERRMSG_GENERAL );           break;
    }
}

static char* new_string(char *s, const char *ns) {
    int newsize = strlen(ns) + 1;
    char *p = new char[ newsize ];
    strcpy(p, ns);
    return p;
}

#include "phoneapplet.moc"

// phoneapplet.h definitions
//
// David Baron, November 2008
//
// Here we avoid loading the header multiple times
#ifndef PhoneApplet_HEADER
#define PhoneApplet_HEADER

// We need the Plasma Applet headers
#include <Plasma/Applet>
#include <Plasma/Svg>
#include <QIcon>
#include <QtGui>

#include <QtConcurrentRun>
#include <QFuture>
#include <QTimer>

// These are the program states:

typedef enum {
    eIDLE, eRING, eCONNECT, eDIAL, eTALK, eBUSY, eNOGOOD
} eMODEM_STATE;

// Edit this for your modem device!
const char* MODEM_FILENAME = "/dev/ttyS2";
const speed_t MODEM_BAUD = B57600;

// These are for Rockwell chip (336, etc) speakerphone modems
// Edit these for your modem if necessary (\n or ^M ??)
// Standar Hayes compatable
const char* MODEM_INIT = "~AT S7=45 S0=0 M2 V1 X4 &c1 E0 Q0";    // pref ~\r~
                            // S7 remote carrier wait secs
                            // S0 rings before auto-answer
                            // M2 speaker always on (0-off, 1-till answer 3-during answer)
                            // V1 textual responses
                            // X4 all of the above
                            // &c1 DCD follows carrier
                            // E0 echo off
                            // Q0 DTR set
const char* MODEM_RESET = "ATZ";
const char* MODEM_ANSWER = "ATA";
const char* MODEM_HANGUP = "ATH";
const char* MODEM_TONE_DIAL_PRE = "ATDT";
const char* MODEM_TONE_DIAL_SUF = "";
const char* MODEM_ZERO_VOLUME = "ATL0";
const char* MODEM_LOW_VOLUME = "ATL1";
const char* MODEM_MED_VOLUME = "ATL2";
const char* MODEM_HI_VOLUME = "ATL3";
const char* MODEM_TONE = "AT#VTS=";

// should precede dialing or answer:
// may change for other models
const char* MODEM_SET_SPEAKERPHONE = "AT#CLS=8 #VRN=0 #VLS=6";

// standard? modem status returns
const char* MODEM_WAS_OK = "OK";
const char* MODEM_WAS_ERROR = "ERROR";
const char* MODEM_VOICE_COM = "VCON";
const char* MODEM_RING = "RING";
const char* MODEM_BUSY = "BUSY";
const char* MODEM_CARRIER = "CARRIER";
const char* MODEM_DIALTONE = "DIALTONE";
const char* MODEM_NOANSWER = "ANSWER";

const char* CMD_PLAY_SOUND = "/usr/bin/ogg123 -d alsa /usr/share/sounds/KDE-Im-Nudge.ogg";
const char* PLAY_SOUND_FILE = "/usr/share/sounds/KDE-Im-Nudge.ogg";

const long MSEC_IGNORE_RING = 500l; // because rings can be irregular
const long MSEC_BLINK = 667l;    // blink time
const long MSEC_AFTER_RINGS = 3500l; // if I do not answer the phone
const long MSEC_WAIT_THREAD = 5000l;      // pasue the thread while procesing commands from me
const long MSEC_READ_TIMEOUT = 250l;
const long MSEC_DIAL_TIMEOUT = 15000l;

const int RETRIES = 3;

// ERROR STATUSES AND MESSAGES
const int   ERRNO_NOMODEM =         -1;
const char* ERRMSG_NOMODEM =        "Could not open modem";
const int   ERRNO_BADLINUX =        -2;
const char* ERRMSG_BADLINUX =       "Incompatable Linux version";
const int   ERRNO_INITMODEM =       -3;
const char* ERRMSG_INITMODEM =      "Could not initialize modem";
const int   ERRNO_RESETMODEM =      -4;
const char* ERRMSG_RESETMODEM =     "Could not reset modem";
const int   ERRNO_SETSPEAKERPHONE = -5;
const char* ERRMSG_SETSPEAKERPHONE = "Could not set modem to speakerphone";
const int   ERRNO_HANGUPMODEM =     -6;
const char* ERRMSG_HANGUPMODEM =    "Could not hang up modem";
const int   ERRNO_BUSY =            -7;
const char* ERRMSG_BUSY =           "Line busy";
const int   ERRNO_NOCARRIER =       -8;
const char* ERRMSG_NOCARRIER =      "No carrier";
const int   ERRNO_TIMEOUT =         -9;
const char* ERRMSG_TIMEOUT =        "Could not succeed in time";
const int   ERRNO_ANSWERMODEM =     -10;
const char* ERRMSG_ANSWERMODEM =    "Could not answer";
const int   ERRNO_NONUMBER =        -11;
const char* ERRMSG_NONUMBER =       "No phone number entered";
const int   ERRNO_DIAL  =           -12;
const char* ERRMSG_DIAL =           "Cannot dial";
const int   ERRNO_NODIALTONE =      -13;
const char* ERRMSG_NODIALTONE =     "No dial tone";
const int   ERRNO_NOANSWER =        -14;
const char* ERRMSG_NOANSWER =       "No answer";
const int   ERRNO_MODEMVOLUME =     -15;
const char* ERRMSG_MODEMVOLUME =    "Cannot set volume";
const int   ERRNO_GENERAL =         -100;
const char* ERRMSG_GENERAL =        "Could not do it";

class QSizeF;

class RingDaemonThread;
class ModemAnswerThread;
class ModemHangupThread;
class ModemDialThread;
class MyPhoneNumber;

// Define our plasma Applet
namespace Plasma {
    class Svg;
}

class PhoneApplet : public Plasma::Applet {
    Q_OBJECT
            
public:
    // public functions
    // Basic Create/Destroy
    PhoneApplet(QObject *parent, const QVariantList &args);
    ~PhoneApplet();

    // The paintInterface procedure paints the applet to screen
    void paintInterface(QPainter *painter,
            const QStyleOptionGraphicsItem *option,
            const QRect& contentsRect);
    void init();
    int ring_daemon();
    void proc_click();
    int modem_answer_sequence( int fn );
    int modem_hangup_sequence( int fn );
    int modem_dial_sequence( int fn, QString& numb );

private:
    void closeEvent( QCloseEvent* ev );
    void paintInterfaceState(QPainter *painter,
            const QStyleOptionGraphicsItem *option,
            const QRect& contentsRect,
            Plasma::Svg&);
    // functions from xringd.c
    int modem_open(char *file, bool readonly );
    //bool modem_talk( char* cmd, char* resp );   // for member file number
    void modem_close(); // for member file number
    bool is_ring_near();
    //bool is_ring_far();
    void proc_ring();
    void set_state( eMODEM_STATE newstate );

    void MessageBox(const char*);
    void ErrMessage(int);
    void NotImplemented();
    void mousePressEvent(QGraphicsSceneMouseEvent *);
    void AbortAllThreads();
    void digitClicked(char digit);

public slots:
    void digitClicked1() { digitClicked( '1' ); }
    void digitClicked2() { digitClicked( '2' ); }
    void digitClicked3() { digitClicked( '3' ); }
    void digitClicked4() { digitClicked( '4' ); }
    void digitClicked5() { digitClicked( '5' ); }
    void digitClicked6() { digitClicked( '6' ); }
    void digitClicked7() { digitClicked( '7' ); }
    void digitClicked8() { digitClicked( '8' ); }
    void digitClicked9() { digitClicked( '9' ); }
    void digitClicked0() { digitClicked( '0' ); }
    void deleteText();
    void CallNumber();
    void toggle_sound();
    void setVolume();

    void serial_setup(int);

private slots:
    void blinkit();
    void proc_endringseq();
    void toggle_daemon();
    void StartDaemon();
    void AbortDaemon();
    void HangUp();
    void CallHungUp();
    void AnswerCall();
    void CallAnswered();
    void ToggleGUI();


private:
    Plasma::Svg m_svgMain;
    Plasma::Svg m_svgRing;
    Plasma::Svg m_svgRing2;
    Plasma::Svg m_svgConnect;
    Plasma::Svg m_svgConnect2;
    Plasma::Svg m_svgDial;
    Plasma::Svg m_svgDial2;
    Plasma::Svg m_svgTalk;
    Plasma::Svg m_svgTalk2;

    eMODEM_STATE m_eState;
    int m_iModemFileno;
    bool m_bWantSound;
    bool m_bAlternate;
    int m_iVolume;

    //struct timeval m_tmBef;

    QTimer* m_QTimerBlink;
    QTimer* m_QTimerEndRings;
    RingDaemonThread* m_DaemonThread;
    ModemAnswerThread* m_AnswerThread;
    ModemHangupThread* m_HangupThread;
    ModemDialThread* m_DialThread;

    MyPhoneNumber* m_PhoneNumber;

    // program configuration copied to these
    char* m_strModemFilename;
    char* m_strPlaySound;
};


// This is the command that links your applet to the .desktop file
K_EXPORT_PLASMA_APPLET(phoneapplet, PhoneApplet)
#endif
_______________________________________________
Plasma-devel mailing list
Plasma-devel@kde.org
https://mail.kde.org/mailman/listinfo/plasma-devel

Reply via email to