|
|
|
/*
|
|
|
|
kopeteaway.cpp - Kopete Away
|
|
|
|
|
|
|
|
Copyright (c) 2002 by Hendrik vom Lehn <hvl@linux-4-ever.de>
|
|
|
|
Copyright (c) 2003 Olivier Goffart <ogoffart @ kde.org>
|
|
|
|
|
|
|
|
Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org>
|
|
|
|
|
|
|
|
*************************************************************************
|
|
|
|
* *
|
|
|
|
* This library is free software; you can redistribute it and/or *
|
|
|
|
* modify it under the terms of the GNU Lesser General Public *
|
|
|
|
* License as published by the Free Software Foundation; either *
|
|
|
|
* version 2 of the License, or (at your option) any later version. *
|
|
|
|
* *
|
|
|
|
*************************************************************************
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include <config.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "kopeteaway.h"
|
|
|
|
|
|
|
|
#include "kopeteaccountmanager.h"
|
|
|
|
#include "kopeteaccount.h"
|
|
|
|
#include "kopeteonlinestatus.h"
|
|
|
|
#include "kopeteonlinestatusmanager.h"
|
|
|
|
#include "kopetecontact.h"
|
|
|
|
#include "kopeteprefs.h"
|
|
|
|
|
|
|
|
#include <tdeconfig.h>
|
|
|
|
#include <tqtimer.h>
|
|
|
|
#include <tdeapplication.h>
|
|
|
|
#include <dcopref.h>
|
|
|
|
|
|
|
|
#include <tdelocale.h>
|
|
|
|
#include <tdeglobal.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <ksettings/dispatcher.h>
|
|
|
|
|
|
|
|
#ifdef TQ_WS_X11
|
|
|
|
#include <X11/Xlib.h>
|
|
|
|
#include <X11/Xatom.h>
|
|
|
|
#include <X11/Xresource.h>
|
|
|
|
// The following include is to make --enable-final work
|
|
|
|
#include <X11/Xutil.h>
|
|
|
|
|
|
|
|
#ifdef HAVE_XSCREENSAVER
|
|
|
|
#define HasScreenSaver
|
|
|
|
#include <X11/extensions/scrnsaver.h>
|
|
|
|
#endif
|
|
|
|
#endif // TQ_WS_X11
|
|
|
|
|
|
|
|
// As this is an untested X extension we better leave it off
|
|
|
|
#undef HAVE_XIDLE
|
|
|
|
#undef HasXidle
|
|
|
|
|
|
|
|
|
|
|
|
struct KopeteAwayPrivate
|
|
|
|
{
|
|
|
|
TQString awayMessage;
|
|
|
|
TQString autoAwayMessage;
|
|
|
|
bool useAutoAwayMessage;
|
|
|
|
bool globalAway;
|
|
|
|
TQStringList awayMessageList;
|
|
|
|
TQTime idleTime;
|
|
|
|
TQTimer *timer;
|
|
|
|
bool autoaway;
|
|
|
|
bool goAvailable;
|
|
|
|
int awayTimeout;
|
|
|
|
bool useAutoAway;
|
|
|
|
TQPtrList<Kopete::Account> autoAwayAccounts;
|
|
|
|
|
|
|
|
int mouse_x;
|
|
|
|
int mouse_y;
|
|
|
|
unsigned int mouse_mask;
|
|
|
|
#ifdef TQ_WS_X11
|
|
|
|
Window root; /* root window the pointer is on */
|
|
|
|
Screen* screen; /* screen the pointer is on */
|
|
|
|
|
|
|
|
Time xIdleTime;
|
|
|
|
#endif
|
|
|
|
bool useXidle;
|
|
|
|
bool useMit;
|
|
|
|
};
|
|
|
|
|
|
|
|
Kopete::Away *Kopete::Away::instance = 0L;
|
|
|
|
|
|
|
|
Kopete::Away::Away() : TQObject( kapp , "Kopete::Away")
|
|
|
|
{
|
|
|
|
int dummy = 0;
|
|
|
|
dummy = dummy; // shut up
|
|
|
|
|
|
|
|
d = new KopeteAwayPrivate;
|
|
|
|
|
|
|
|
// Set up the away messages
|
|
|
|
d->awayMessage = TQString();
|
|
|
|
d->autoAwayMessage = TQString();
|
|
|
|
d->useAutoAwayMessage = false;
|
|
|
|
d->globalAway = false;
|
|
|
|
d->autoaway = false;
|
|
|
|
d->useAutoAway = true;
|
|
|
|
|
|
|
|
// Empty the list
|
|
|
|
d->awayMessageList.clear();
|
|
|
|
|
|
|
|
// set the XAutoLock info
|
|
|
|
#ifdef TQ_WS_X11
|
|
|
|
Display *dsp = tqt_xdisplay();
|
|
|
|
#endif
|
|
|
|
d->mouse_x = d->mouse_y=0;
|
|
|
|
d->mouse_mask = 0;
|
|
|
|
#ifdef TQ_WS_X11
|
|
|
|
d->root = DefaultRootWindow (dsp);
|
|
|
|
d->screen = ScreenOfDisplay (dsp, DefaultScreen (dsp));
|
|
|
|
#endif
|
|
|
|
d->useXidle = false;
|
|
|
|
d->useMit = false;
|
|
|
|
#ifdef HasXidle
|
|
|
|
d->useXidle = XidleQueryExtension(tqt_xdisplay(), &dummy, &dummy);
|
|
|
|
#endif
|
|
|
|
#ifdef HasScreenSaver
|
|
|
|
if(!d->useXidle)
|
|
|
|
d->useMit = XScreenSaverQueryExtension(tqt_xdisplay(), &dummy, &dummy);
|
|
|
|
#endif
|
|
|
|
#ifdef TQ_WS_X11
|
|
|
|
d->xIdleTime = 0;
|
|
|
|
#endif
|
|
|
|
kdDebug(14010) << k_funcinfo << "Idle detection methods:" << endl;
|
|
|
|
kdDebug(14010) << k_funcinfo << "\tKScreensaverIface::isBlanked()" << endl;
|
|
|
|
#ifdef TQ_WS_X11
|
|
|
|
kdDebug(14010) << k_funcinfo << "\tX11 XQueryPointer()" << endl;
|
|
|
|
#endif
|
|
|
|
if (d->useXidle)
|
|
|
|
{
|
|
|
|
kdDebug(14010) << k_funcinfo << "\tX11 Xidle extension" << endl;
|
|
|
|
}
|
|
|
|
if (d->useMit)
|
|
|
|
{
|
|
|
|
kdDebug(14010) << k_funcinfo << "\tX11 MIT Screensaver extension" << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
load();
|
|
|
|
KSettings::Dispatcher::self()->registerInstance( TDEGlobal::instance(), this, TQ_SLOT( load() ) );
|
|
|
|
// Set up the config object
|
|
|
|
TDEConfig *config = TDEGlobal::config();
|
|
|
|
/* Load the saved away messages */
|
|
|
|
config->setGroup("Away Messages");
|
|
|
|
|
|
|
|
// Away Messages
|
|
|
|
if(config->hasKey("Messages"))
|
|
|
|
{
|
|
|
|
d->awayMessageList = config->readListEntry("Messages");
|
|
|
|
}
|
|
|
|
else if(config->hasKey("Titles")) // Old config format
|
|
|
|
{
|
|
|
|
TQStringList titles = config->readListEntry("Titles"); // Get the titles
|
|
|
|
for(TQStringList::iterator i = titles.begin(); i != titles.end(); ++i)
|
|
|
|
{
|
|
|
|
d->awayMessageList.append( config->readEntry(*i) ); // And add it to the list
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Save this list to disk */
|
|
|
|
save();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
d->awayMessageList.append( i18n( "Sorry, I am busy right now" ) );
|
|
|
|
d->awayMessageList.append( i18n( "I am gone right now, but I will be back later" ) );
|
|
|
|
|
|
|
|
/* Save this list to disk */
|
|
|
|
save();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Auto away message
|
|
|
|
if(config->hasKey("AutoAwayMessage"))
|
|
|
|
{
|
|
|
|
d->autoAwayMessage = config->readEntry("AutoAwayMessage");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
d->autoAwayMessage = i18n( "I am gone right now, but I will be back later" );
|
|
|
|
|
|
|
|
// Save the default auto away message to disk
|
|
|
|
save();
|
|
|
|
}
|
|
|
|
|
|
|
|
// init the timer
|
|
|
|
d->timer = new TQTimer(this, "AwayTimer");
|
|
|
|
connect(d->timer, TQ_SIGNAL(timeout()), this, TQ_SLOT(slotTimerTimeout()));
|
|
|
|
d->timer->start(4000);
|
|
|
|
|
|
|
|
//init the time and other
|
|
|
|
setActive();
|
|
|
|
}
|
|
|
|
|
|
|
|
Kopete::Away::~Away()
|
|
|
|
{
|
|
|
|
if(this == instance)
|
|
|
|
instance = 0L;
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString Kopete::Away::message()
|
|
|
|
{
|
|
|
|
return getInstance()->d->awayMessage;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString Kopete::Away::autoAwayMessage()
|
|
|
|
{
|
|
|
|
return getInstance()->d->autoAwayMessage;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kopete::Away::setGlobalAwayMessage(const TQString &message)
|
|
|
|
{
|
|
|
|
if( !message.isEmpty() )
|
|
|
|
{
|
|
|
|
kdDebug(14010) << k_funcinfo <<
|
|
|
|
"Setting global away message: " << message << endl;
|
|
|
|
d->awayMessage = message;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kopete::Away::setAutoAwayMessage(const TQString &message)
|
|
|
|
{
|
|
|
|
if( !message.isEmpty() )
|
|
|
|
{
|
|
|
|
kdDebug(14010) << k_funcinfo <<
|
|
|
|
"Setting auto away message: " << message << endl;
|
|
|
|
d->autoAwayMessage = message;
|
|
|
|
|
|
|
|
// Save the new auto away message to disk
|
|
|
|
save();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Kopete::Away *Kopete::Away::getInstance()
|
|
|
|
{
|
|
|
|
if (!instance)
|
|
|
|
instance = new Kopete::Away;
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Kopete::Away::globalAway()
|
|
|
|
{
|
|
|
|
return getInstance()->d->globalAway;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kopete::Away::setGlobalAway(bool status)
|
|
|
|
{
|
|
|
|
getInstance()->d->globalAway = status;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kopete::Away::save()
|
|
|
|
{
|
|
|
|
TDEConfig *config = TDEGlobal::config();
|
|
|
|
/* Set the away message settings in the Away Messages config group */
|
|
|
|
config->setGroup("Away Messages");
|
|
|
|
config->writeEntry("Messages", d->awayMessageList);
|
|
|
|
config->writeEntry("AutoAwayMessage", d->autoAwayMessage);
|
|
|
|
config->sync();
|
|
|
|
|
|
|
|
emit( messagesChanged() );
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kopete::Away::load()
|
|
|
|
{
|
|
|
|
TDEConfig *config = TDEGlobal::config();
|
|
|
|
config->setGroup("AutoAway");
|
|
|
|
d->awayTimeout=config->readNumEntry("Timeout", 600);
|
|
|
|
d->goAvailable=config->readBoolEntry("GoAvailable", true);
|
|
|
|
d->useAutoAway=config->readBoolEntry("UseAutoAway", true);
|
|
|
|
d->useAutoAwayMessage=config->readBoolEntry("UseAutoAwayMessage", false);
|
|
|
|
}
|
|
|
|
|
|
|
|
TQStringList Kopete::Away::getMessages()
|
|
|
|
{
|
|
|
|
return d->awayMessageList;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString Kopete::Away::getMessage( uint messageNumber )
|
|
|
|
{
|
|
|
|
TQStringList::iterator it = d->awayMessageList.at( messageNumber );
|
|
|
|
if( it != d->awayMessageList.end() )
|
|
|
|
{
|
|
|
|
TQString str = *it;
|
|
|
|
d->awayMessageList.prepend( str );
|
|
|
|
d->awayMessageList.remove( it );
|
|
|
|
save();
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return TQString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kopete::Away::addMessage(const TQString &message)
|
|
|
|
{
|
|
|
|
d->awayMessageList.prepend( message );
|
|
|
|
if( (int)d->awayMessageList.count() > KopetePrefs::prefs()->rememberedMessages() )
|
|
|
|
d->awayMessageList.pop_back();
|
|
|
|
save();
|
|
|
|
}
|
|
|
|
|
|
|
|
long int Kopete::Away::idleTime()
|
|
|
|
{
|
|
|
|
//FIXME: the time is reset to zero if more than 24 hours are elapsed
|
|
|
|
// we can imagine someone who leave his PC for several weeks
|
|
|
|
return (d->idleTime.elapsed() / 1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kopete::Away::slotTimerTimeout()
|
|
|
|
{
|
|
|
|
// Time to check whether we're active or autoaway. We basically have two
|
|
|
|
// bits of info to go on - KDE's screensaver status
|
|
|
|
// (KScreenSaverIface::isBlanked()) and the X11 activity detection.
|
|
|
|
//
|
|
|
|
// Note that isBlanked() is a slight of a misnomer. It returns true if we're:
|
|
|
|
// - using a non-locking screensaver, which is running, or
|
|
|
|
// - using a locking screensaver which is still locked, regardless of
|
|
|
|
// whether the user is trying to unlock it right now
|
|
|
|
// Either way, it's only worth checking for activity if the screensaver
|
|
|
|
// isn't blanked/locked, because activity while blanked is impossible and
|
|
|
|
// activity while locked never matters (if there is any, it's probably just
|
|
|
|
// the cleaner wiping the keyboard :).
|
|
|
|
|
|
|
|
|
|
|
|
/* we should be able to respond to KDesktop queries to avoid a deadlock, so we allow the event loop to be called */
|
|
|
|
static bool rentrency_protection=false;
|
|
|
|
if(rentrency_protection)
|
|
|
|
return;
|
|
|
|
rentrency_protection=true;
|
|
|
|
DCOPRef screenSaver("kdesktop", "KScreensaverIface");
|
|
|
|
DCOPReply isBlanked = screenSaver.callExt("isBlanked" , DCOPRef::UseEventLoop, 10);
|
|
|
|
rentrency_protection=false;
|
|
|
|
if(!instance) //this may have been deleted in the event loop
|
|
|
|
return;
|
|
|
|
if (!(isBlanked.isValid() && isBlanked.type == "bool" && ((bool)isBlanked)))
|
|
|
|
{
|
|
|
|
// DCOP failed, or returned something odd, or the screensaver is
|
|
|
|
// inactive, so check for activity the X11 way. It's only worth
|
|
|
|
// checking for autoaway if there's no activity, and because
|
|
|
|
// Screensaver blanking/locking implies autoAway activation (see
|
|
|
|
// KopeteIface::KopeteIface()), only worth checking autoAway when the
|
|
|
|
// screensaver isn't running.
|
|
|
|
if (isActivity())
|
|
|
|
{
|
|
|
|
setActive();
|
|
|
|
}
|
|
|
|
else if (!d->autoaway && d->useAutoAway && idleTime() > d->awayTimeout)
|
|
|
|
{
|
|
|
|
setAutoAway();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Kopete::Away::isActivity()
|
|
|
|
{
|
|
|
|
// Copyright (c) 1999 Martin R. Jones <mjones@kde.org>
|
|
|
|
//
|
|
|
|
// KDE screensaver engine
|
|
|
|
//
|
|
|
|
// This module is a heavily modified xautolock.
|
|
|
|
// In fact as of KDE 2.0 this code is practically unrecognisable as xautolock.
|
|
|
|
|
|
|
|
bool activity = false;
|
|
|
|
|
|
|
|
#ifdef TQ_WS_X11
|
|
|
|
Display *dsp = tqt_xdisplay();
|
|
|
|
Window dummy_w;
|
|
|
|
int dummy_c;
|
|
|
|
unsigned int mask; /* modifier mask */
|
|
|
|
int root_x;
|
|
|
|
int root_y;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find out whether the pointer has moved. Using XQueryPointer for this
|
|
|
|
* is gross, but it also is the only way never to mess up propagation
|
|
|
|
* of pointer events.
|
|
|
|
*
|
|
|
|
* Remark : Unlike XNextEvent(), XPending () doesn't notice if the
|
|
|
|
* connection to the server is lost. For this reason, earlier
|
|
|
|
* versions of xautolock periodically called XNoOp (). But
|
|
|
|
* why not let XQueryPointer () do the job for us, since
|
|
|
|
* we now call that periodically anyway?
|
|
|
|
*/
|
|
|
|
if (!XQueryPointer (dsp, d->root, &(d->root), &dummy_w, &root_x, &root_y,
|
|
|
|
&dummy_c, &dummy_c, &mask))
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Pointer has moved to another screen, so let's find out which one.
|
|
|
|
*/
|
|
|
|
for (int i = 0; i < ScreenCount(dsp); i++)
|
|
|
|
{
|
|
|
|
if (d->root == RootWindow(dsp, i))
|
|
|
|
{
|
|
|
|
d->screen = ScreenOfDisplay (dsp, i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// =================================================================================
|
|
|
|
|
|
|
|
Time xIdleTime = 0; // millisecs since last input event
|
|
|
|
|
|
|
|
#ifdef HasXidle
|
|
|
|
if (d->useXidle)
|
|
|
|
{
|
|
|
|
XGetIdleTime(dsp, &xIdleTime);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#endif /* HasXIdle */
|
|
|
|
|
|
|
|
{
|
|
|
|
#ifdef HasScreenSaver
|
|
|
|
if(d->useMit)
|
|
|
|
{
|
|
|
|
static XScreenSaverInfo* mitInfo = 0;
|
|
|
|
if (!mitInfo) mitInfo = XScreenSaverAllocInfo();
|
|
|
|
XScreenSaverQueryInfo (dsp, d->root, mitInfo);
|
|
|
|
xIdleTime = mitInfo->idle;
|
|
|
|
}
|
|
|
|
#endif /* HasScreenSaver */
|
|
|
|
}
|
|
|
|
|
|
|
|
// =================================================================================
|
|
|
|
|
|
|
|
// Only check idle time if we have some way of measuring it, otherwise if
|
|
|
|
// we've neither Mit nor Xidle it'll still be zero and we'll always appear active.
|
|
|
|
// FIXME: what problem does the 2000ms fudge solve?
|
|
|
|
if (root_x != d->mouse_x || root_y != d->mouse_y || mask != d->mouse_mask
|
|
|
|
|| ((d->useXidle || d->useMit) && xIdleTime < d->xIdleTime + 2000))
|
|
|
|
{
|
|
|
|
// -1 => just gone autoaway, ignore apparent activity this time round
|
|
|
|
// anything else => genuine activity
|
|
|
|
// See setAutoAway().
|
|
|
|
if (d->mouse_x != -1)
|
|
|
|
{
|
|
|
|
activity = true;
|
|
|
|
}
|
|
|
|
d->mouse_x = root_x;
|
|
|
|
d->mouse_y = root_y;
|
|
|
|
d->mouse_mask = mask;
|
|
|
|
d->xIdleTime = xIdleTime;
|
|
|
|
}
|
|
|
|
#endif // TQ_WS_X11
|
|
|
|
// =================================================================================
|
|
|
|
|
|
|
|
return activity;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kopete::Away::setActive()
|
|
|
|
{
|
|
|
|
// kdDebug(14010) << k_funcinfo << "Found activity on desktop, resetting away timer" << endl;
|
|
|
|
d->idleTime.start();
|
|
|
|
|
|
|
|
if(d->autoaway)
|
|
|
|
{
|
|
|
|
d->autoaway = false;
|
|
|
|
emit activity();
|
|
|
|
if (d->goAvailable)
|
|
|
|
{
|
|
|
|
d->autoAwayAccounts.setAutoDelete(false);
|
|
|
|
for(Kopete::Account *i=d->autoAwayAccounts.first() ; i; i=d->autoAwayAccounts.current() )
|
|
|
|
{
|
|
|
|
if(i->isConnected() && i->isAway())
|
|
|
|
{
|
|
|
|
i->setOnlineStatus( Kopete::OnlineStatusManager::self()->onlineStatus( i->protocol() ,
|
|
|
|
Kopete::OnlineStatusManager::Online ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove() makes the next entry in the list the current one,
|
|
|
|
// that's why we use current() above
|
|
|
|
d->autoAwayAccounts.remove();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Kopete::Away::setAutoAway()
|
|
|
|
{
|
|
|
|
// A value of -1 in mouse_x indicates to checkActivity() that next time it
|
|
|
|
// fires it should ignore any apparent idle/mouse/keyboard changes.
|
|
|
|
// I think the point of this is that if you manually start the screensaver
|
|
|
|
// then there'll unavoidably be some residual mouse/keyboard activity
|
|
|
|
// that should be ignored.
|
|
|
|
d->mouse_x = -1;
|
|
|
|
|
|
|
|
// kdDebug(14010) << k_funcinfo << "Going AutoAway!" << endl;
|
|
|
|
d->autoaway = true;
|
|
|
|
|
|
|
|
// Set all accounts that are not away already to away.
|
|
|
|
// We remember them so later we only set the accounts to
|
|
|
|
// available that we set to away (and not the user).
|
|
|
|
TQPtrList<Kopete::Account> accounts = Kopete::AccountManager::self()->accounts();
|
|
|
|
for(Kopete::Account *i=accounts.first() ; i; i=accounts.next() )
|
|
|
|
{
|
|
|
|
if(i->myself()->onlineStatus().status() == Kopete::OnlineStatus::Online)
|
|
|
|
{
|
|
|
|
d->autoAwayAccounts.append(i);
|
|
|
|
|
|
|
|
if(d->useAutoAwayMessage)
|
|
|
|
{
|
|
|
|
// Display a specific away message
|
|
|
|
i->setOnlineStatus( Kopete::OnlineStatusManager::self()->onlineStatus( i->protocol() ,
|
|
|
|
Kopete::OnlineStatusManager::Idle ) ,
|
|
|
|
getInstance()->d->autoAwayMessage);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Display the last away message used
|
|
|
|
i->setOnlineStatus( Kopete::OnlineStatusManager::self()->onlineStatus( i->protocol() ,
|
|
|
|
Kopete::OnlineStatusManager::Idle ) ,
|
|
|
|
getInstance()->d->awayMessage);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "kopeteaway.moc"
|