/* * daemon.cpp - interface with alarm daemon * Program: kalarm * Copyright © 2001-2007 by David Jarvie * * This program 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kalarm.h" #include #include #include #include #include #include #include #include #include "kalarmd/kalarmd.h" #include "kalarmd/alarmdaemoniface.h" #include "kalarmd/alarmdaemoniface_stub.h" #include "kalarmd/alarmguiiface.h" #include "alarmcalendar.h" #include "kalarmapp.h" #include "preferences.h" #include "daemon.moc" static const int REGISTER_TIMEOUT = 20; // seconds to wait before assuming registration with daemon has failed static const char* NOTIFY_DCOP_OBJECT = "notify"; // DCOP name of KAlarm's interface for notification by alarm daemon static TQString expandURL(const TQString& urlString); /*============================================================================= = Class: NotificationHandler = Handles the the alarm daemon's client notification DCOP interface. =============================================================================*/ class NotificationHandler : public TQObject, virtual public AlarmGuiIface { public: NotificationHandler(); private: // DCOP interface void alarmDaemonUpdate(int calendarStatus, const TQString& calendarURL); void handleEvent(const TQString& calendarURL, const TQString& eventID); void registered(bool reregister, int result, int version); }; Daemon* Daemon::mInstance = 0; NotificationHandler* Daemon::mDcopHandler = 0; TQValueList Daemon::mQueuedEvents; TQValueList Daemon::mSavingEvents; TQTimer* Daemon::mStartTimer = 0; TQTimer* Daemon::mRegisterTimer = 0; TQTimer* Daemon::mStatusTimer = 0; int Daemon::mStatusTimerCount = 0; int Daemon::mStatusTimerInterval; int Daemon::mStartTimeout = 0; Daemon::Status Daemon::mStatus = Daemon::STOPPED; bool Daemon::mRunning = false; bool Daemon::mCalendarDisabled = false; bool Daemon::mEnableCalPending = false; bool Daemon::mRegisterFailMsg = false; // How frequently to check the daemon's status after starting it. // This is equal to the length of time we wait after the daemon is registered with DCOP // before we assume that it is ready to accept DCOP calls. static const int startCheckInterval = 500; // 500 milliseconds /****************************************************************************** * Initialise. * A Daemon instance needs to be constructed only in order for slots to work. * All external access is via static methods. */ void Daemon::initialise() { if (!mInstance) mInstance = new Daemon(); connect(AlarmCalendar::activeCalendar(), TQ_SIGNAL(calendarSaved(AlarmCalendar*)), mInstance, TQ_SLOT(slotCalendarSaved(AlarmCalendar*))); } /****************************************************************************** * Initialise the daemon status timer. */ void Daemon::createDcopHandler() { if (mDcopHandler) return; mDcopHandler = new NotificationHandler(); // Check if the alarm daemon is running, but don't start it yet, since // the program is still initialising. mRunning = isRunning(false); mStatusTimerInterval = Preferences::daemonTrayCheckInterval(); Preferences::connect(TQ_SIGNAL(preferencesChanged()), mInstance, TQ_SLOT(slotPreferencesChanged())); mStatusTimer = new TQTimer(mInstance); connect(mStatusTimer, TQ_SIGNAL(timeout()), mInstance, TQ_SLOT(timerCheckIfRunning())); mStatusTimer->start(mStatusTimerInterval * 1000); // check regularly if daemon is running } /****************************************************************************** * Start the alarm daemon if necessary, and register this application with it. * Reply = false if the daemon definitely couldn't be started or registered with. */ bool Daemon::start() { kdDebug(5950) << "Daemon::start()\n"; updateRegisteredStatus(); switch (mStatus) { case STOPPED: { if (mStartTimer) return true; // we're currently waiting for the daemon to start // Start the alarm daemon. It is a TDEUniqueApplication, which means that // there is automatically only one instance of the alarm daemon running. TQString execStr = locate("exe", TQString::fromLatin1(DAEMON_APP_NAME)); if (execStr.isEmpty()) { KMessageBox::error(0, i18n("Alarm daemon not found.")); kdError() << "Daemon::startApp(): " DAEMON_APP_NAME " not found" << endl; return false; } TDEApplication::tdeinitExec(execStr); kdDebug(5950) << "Daemon::start(): Alarm daemon started" << endl; mStartTimeout = 5000/startCheckInterval + 1; // check daemon status for 5 seconds before giving up mStartTimer = new TQTimer(mInstance); connect(mStartTimer, TQ_SIGNAL(timeout()), mInstance, TQ_SLOT(checkIfStarted())); mStartTimer->start(startCheckInterval); mInstance->checkIfStarted(); return true; } case RUNNING: return true; // we're waiting for the daemon to be completely ready case READY: // Daemon is ready. Register this application with it. if (!registerWith(false)) return false; break; case REGISTERED: break; } return true; } /****************************************************************************** * Register this application with the alarm daemon, and tell it to load the * calendar. * Set 'reregister' true in order to notify the daemon of a change in the * 'disable alarms if stopped' setting. */ bool Daemon::registerWith(bool reregister) { if (mRegisterTimer) return true; switch (mStatus) { case STOPPED: case RUNNING: return false; case REGISTERED: if (!reregister) return true; break; case READY: break; } bool disabledIfStopped = theApp()->alarmsDisabledIfStopped(); kdDebug(5950) << (reregister ? "Daemon::reregisterWith(): " : "Daemon::registerWith(): ") << (disabledIfStopped ? "NO_START" : "COMMAND_LINE") << endl; TQCString appname = kapp->aboutData()->appName(); AlarmDaemonIface_stub s(DAEMON_APP_NAME, DAEMON_DCOP_OBJECT); if (reregister) s.registerChange(appname, !disabledIfStopped); else s.registerApp(appname, kapp->aboutData()->programName(), TQCString(NOTIFY_DCOP_OBJECT), AlarmCalendar::activeCalendar()->urlString(), !disabledIfStopped); if (!s.ok()) { kdError(5950) << "Daemon::registerWith(" << reregister << "): DCOP error" << endl; registrationResult(reregister, KAlarmd::FAILURE); return false; } mRegisterTimer = new TQTimer(mInstance); connect(mRegisterTimer, TQ_SIGNAL(timeout()), mInstance, TQ_SLOT(registerTimerExpired())); mRegisterTimer->start(REGISTER_TIMEOUT * 1000); // wait for the reply return true; } /****************************************************************************** * Called when the daemon has notified us of the result of the register() DCOP call. */ void Daemon::registrationResult(bool reregister, int result, int version) { kdDebug(5950) << "Daemon::registrationResult(" << reregister << ") version: " << version << endl; delete mRegisterTimer; mRegisterTimer = 0; bool failed = false; TQString errmsg; if (version && version != DAEMON_VERSION_NUM) { failed = true; kdError(5950) << "Daemon::registrationResult(" << reregister << "): kalarmd reports incompatible version " << version << endl; errmsg = i18n("Cannot enable alarms.\nInstallation or configuration error: Alarm Daemon (%1) version is incompatible.") .arg(TQString::fromLatin1(DAEMON_APP_NAME)); } else { switch (result) { case KAlarmd::SUCCESS: break; case KAlarmd::NOT_FOUND: // We've successfully registered with the daemon, but the daemon can't // find the KAlarm executable so won't be able to restart KAlarm if // KAlarm exits. kdError(5950) << "Daemon::registrationResult(" << reregister << "): registerApp dcop call: " << kapp->aboutData()->appName() << " not found\n"; KMessageBox::error(0, i18n("Alarms will be disabled if you stop KAlarm.\n" "(Installation or configuration error: %1 cannot locate %2 executable.)") .arg(TQString::fromLatin1(DAEMON_APP_NAME)) .arg(kapp->aboutData()->appName())); break; case KAlarmd::FAILURE: default: // Either the daemon reported an error in the registration DCOP call, // there was a DCOP error calling the daemon, or a timeout on the reply. kdError(5950) << "Daemon::registrationResult(" << reregister << "): registerApp dcop call failed -> " << result << endl; failed = true; if (!reregister) { errmsg = i18n("Cannot enable alarms:\nFailed to register with Alarm Daemon (%1)") .arg(TQString::fromLatin1(DAEMON_APP_NAME)); } break; } } if (failed) { if (!errmsg.isEmpty()) { if (mStatus == REGISTERED) mStatus = READY; if (!mRegisterFailMsg) { mRegisterFailMsg = true; KMessageBox::error(0, errmsg); } } return; } if (!reregister) { // The alarm daemon has loaded the calendar mStatus = REGISTERED; mRegisterFailMsg = false; kdDebug(5950) << "Daemon::start(): daemon startup complete" << endl; } } /****************************************************************************** * Check whether the alarm daemon has started yet, and if so, register with it. */ void Daemon::checkIfStarted() { updateRegisteredStatus(); bool err = false; switch (mStatus) { case STOPPED: if (--mStartTimeout > 0) return; // wait a bit more to check again // Output error message, but delete timer first to prevent // multiple messages. err = true; break; case RUNNING: case READY: case REGISTERED: break; } delete mStartTimer; mStartTimer = 0; if (err) { kdError(5950) << "Daemon::checkIfStarted(): failed to start daemon" << endl; KMessageBox::error(0, i18n("Cannot enable alarms:\nFailed to start Alarm Daemon (%1)").arg(TQString::fromLatin1(DAEMON_APP_NAME))); } } /****************************************************************************** * Check whether the alarm daemon has started yet, and if so, whether it is * ready to accept DCOP calls. */ void Daemon::updateRegisteredStatus(bool timeout) { if (!kapp->dcopClient()->isApplicationRegistered(DAEMON_APP_NAME)) { mStatus = STOPPED; mRegisterFailMsg = false; } else { switch (mStatus) { case STOPPED: // The daemon has newly been detected as registered with DCOP. // Wait for a short time to ensure that it is ready for DCOP calls. mStatus = RUNNING; TQTimer::singleShot(startCheckInterval, mInstance, TQ_SLOT(slotStarted())); break; case RUNNING: if (timeout) { mStatus = READY; start(); } break; case READY: case REGISTERED: break; } } kdDebug(5950) << "Daemon::updateRegisteredStatus() -> " << mStatus << endl; } /****************************************************************************** * Stop the alarm daemon if it is running. */ bool Daemon::stop() { kdDebug(5950) << "Daemon::stop()" << endl; if (kapp->dcopClient()->isApplicationRegistered(DAEMON_APP_NAME)) { AlarmDaemonIface_stub s(DAEMON_APP_NAME, DAEMON_DCOP_OBJECT); s.quit(); if (!s.ok()) { kdError(5950) << "Daemon::stop(): dcop call failed" << endl; return false; } } return true; } /****************************************************************************** * Reset the alarm daemon. * Reply = true if daemon was told to reset * = false if daemon is not running. */ bool Daemon::reset() { kdDebug(5950) << "Daemon::reset()" << endl; if (!kapp->dcopClient()->isApplicationRegistered(DAEMON_APP_NAME)) return false; AlarmDaemonIface_stub s(DAEMON_APP_NAME, DAEMON_DCOP_OBJECT); s.resetCalendar(TQCString(kapp->aboutData()->appName()), AlarmCalendar::activeCalendar()->urlString()); if (!s.ok()) kdError(5950) << "Daemon::reset(): resetCalendar dcop send failed" << endl; return true; } /****************************************************************************** * Tell the alarm daemon to reread the calendar file. */ void Daemon::reload() { kdDebug(5950) << "Daemon::reload()\n"; AlarmDaemonIface_stub s(DAEMON_APP_NAME, DAEMON_DCOP_OBJECT); s.reloadCalendar(TQCString(kapp->aboutData()->appName()), AlarmCalendar::activeCalendar()->urlString()); if (!s.ok()) kdError(5950) << "Daemon::reload(): reloadCalendar dcop send failed" << endl; } /****************************************************************************** * Tell the alarm daemon to enable/disable monitoring of the calendar file. */ void Daemon::enableCalendar(bool enable) { AlarmDaemonIface_stub s(DAEMON_APP_NAME, DAEMON_DCOP_OBJECT); s.enableCalendar(AlarmCalendar::activeCalendar()->urlString(), enable); mEnableCalPending = false; } /****************************************************************************** * Tell the alarm daemon to enable/disable autostart at login. */ void Daemon::enableAutoStart(bool enable) { // Tell the alarm daemon in case it is running. AlarmDaemonIface_stub s(DAEMON_APP_NAME, DAEMON_DCOP_OBJECT); s.enableAutoStart(enable); // The return status doesn't report failure even if the daemon isn't running, // so in case of failure, rewrite the config file in any case. TDEConfig adconfig(locate("config", DAEMON_APP_NAME"rc")); adconfig.setGroup(TQString::fromLatin1(DAEMON_AUTOSTART_SECTION)); adconfig.writeEntry(TQString::fromLatin1(DAEMON_AUTOSTART_KEY), enable); adconfig.sync(); } /****************************************************************************** * Notify the alarm daemon that the start-of-day time for date-only alarms has * changed. */ void Daemon::notifyTimeChanged() { AlarmDaemonIface_stub s(DAEMON_APP_NAME, DAEMON_DCOP_OBJECT); s.timeConfigChanged(); if (!s.ok()) kdError(5950) << "Daemon::timeConfigChanged(): dcop send failed" << endl; } /****************************************************************************** * Read the alarm daemon's autostart-at-login setting. */ bool Daemon::autoStart() { TDEConfig adconfig(locate("config", DAEMON_APP_NAME"rc")); adconfig.setGroup(TQString::fromLatin1(DAEMON_AUTOSTART_SECTION)); return adconfig.readBoolEntry(TQString::fromLatin1(DAEMON_AUTOSTART_KEY), true); } /****************************************************************************** * Notification that the alarm daemon has enabled/disabled monitoring of the * calendar file. */ void Daemon::calendarIsEnabled(bool enabled) { mCalendarDisabled = !enabled; emit mInstance->daemonRunning(enabled); } /****************************************************************************** * Tell the alarm daemon to stop or start monitoring the calendar file as * appropriate. */ void Daemon::setAlarmsEnabled(bool enable) { kdDebug(5950) << "Daemon::setAlarmsEnabled(" << enable << ")\n"; if (enable && !checkIfRunning()) { // The daemon is not running, so start it if (!start()) { emit daemonRunning(false); return; } mEnableCalPending = true; setFastCheck(); } // If the daemon is now running, tell it to enable/disable the calendar if (checkIfRunning()) enableCalendar(enable); } /****************************************************************************** * Return whether the alarm daemon is monitoring alarms. */ bool Daemon::monitoringAlarms() { bool ok = !mCalendarDisabled && isRunning(); emit mInstance->daemonRunning(ok); return ok; } /****************************************************************************** * Check whether the alarm daemon is currently running and available. */ bool Daemon::isRunning(bool startdaemon) { static bool runState = false; updateRegisteredStatus(); bool newRunState = (mStatus == READY || mStatus == REGISTERED); if (newRunState != runState) { // Daemon's status has changed runState = newRunState; if (runState && startdaemon) start(); // re-register with the daemon } return runState && (mStatus == REGISTERED); } /****************************************************************************** * Called by the timer to check whether the daemon is running. */ void Daemon::timerCheckIfRunning() { checkIfRunning(); // Limit how long we check at the fast rate if (mStatusTimerCount > 0 && --mStatusTimerCount <= 0) mStatusTimer->changeInterval(mStatusTimerInterval * 1000); } /****************************************************************************** * Check whether the alarm daemon is currently running. * If its status has changed, trigger GUI updates. */ bool Daemon::checkIfRunning() { bool newstatus = isRunning(); if (newstatus != mRunning) { mRunning = newstatus; int status = mRunning && !mCalendarDisabled; emit mInstance->daemonRunning(status); mStatusTimer->changeInterval(mStatusTimerInterval * 1000); // exit from fast checking mStatusTimerCount = 0; if (mRunning) { // The alarm daemon has started up if (mEnableCalPending) enableCalendar(true); // tell it to monitor the calendar, if appropriate } } return mRunning; } /****************************************************************************** * Starts checking at a faster rate whether the daemon is running. */ void Daemon::setFastCheck() { mStatusTimer->start(500); // check new status every half second mStatusTimerCount = 20; // don't check at this rate for more than 10 seconds } /****************************************************************************** * Called when a program setting has changed. * If the system tray icon update interval has changed, reset the timer. */ void Daemon::slotPreferencesChanged() { int newInterval = Preferences::daemonTrayCheckInterval(); if (newInterval != mStatusTimerInterval) { // Daemon check interval has changed mStatusTimerInterval = newInterval; if (mStatusTimerCount <= 0) // don't change if on fast rate mStatusTimer->changeInterval(mStatusTimerInterval * 1000); } } /****************************************************************************** * Create an "Alarms Enabled/Enable Alarms" action. */ AlarmEnableAction* Daemon::createAlarmEnableAction(TDEActionCollection* actions, const char* name) { AlarmEnableAction* a = new AlarmEnableAction(0, actions, name); connect(a, TQ_SIGNAL(userClicked(bool)), mInstance, TQ_SLOT(setAlarmsEnabled(bool))); connect(mInstance, TQ_SIGNAL(daemonRunning(bool)), a, TQ_SLOT(setCheckedActual(bool))); return a; } /****************************************************************************** * Called when a calendar has been saved. * If it's the active alarm calendar, notify the alarm daemon. */ void Daemon::slotCalendarSaved(AlarmCalendar* cal) { if (cal == AlarmCalendar::activeCalendar()) { int n = mSavingEvents.count(); if (n) { // We have just saved a modified event originally triggered by the daemon. // Notify the daemon of the event, and tell it to reload the calendar. for (int i = 0; i < n - 1; ++i) notifyEventHandled(mSavingEvents[i], false); notifyEventHandled(mSavingEvents[n - 1], true); mSavingEvents.clear(); } else reload(); } } /****************************************************************************** * Note an event ID which has been triggered by the alarm daemon. */ void Daemon::queueEvent(const TQString& eventId) { mQueuedEvents += eventId; } /****************************************************************************** * Note an event ID which is currently being saved in the calendar file, if the * event was originally triggered by the alarm daemon. */ void Daemon::savingEvent(const TQString& eventId) { if (mQueuedEvents.remove(eventId) > 0) mSavingEvents += eventId; } /****************************************************************************** * If the event ID has been triggered by the alarm daemon, tell the daemon that * it has been processed, and whether to reload its calendar. */ void Daemon::eventHandled(const TQString& eventId, bool reloadCal) { if (mQueuedEvents.remove(eventId) > 0) notifyEventHandled(eventId, reloadCal); // it's a daemon event, so tell daemon that it's been handled else if (reloadCal) reload(); // not a daemon event, so simply tell the daemon to reload the calendar } /****************************************************************************** * Tell the daemon that an event has been processed, and whether to reload its * calendar. */ void Daemon::notifyEventHandled(const TQString& eventId, bool reloadCal) { kdDebug(5950) << "Daemon::notifyEventHandled(" << eventId << (reloadCal ? "): reload" : ")") << endl; AlarmDaemonIface_stub s(DAEMON_APP_NAME, DAEMON_DCOP_OBJECT); s.eventHandled(TQCString(kapp->aboutData()->appName()), AlarmCalendar::activeCalendar()->urlString(), eventId, reloadCal); if (!s.ok()) kdError(5950) << "Daemon::notifyEventHandled(): eventHandled dcop send failed" << endl; } /****************************************************************************** * Return the maximum time (in seconds) elapsed since the last time the alarm * daemon must have checked alarms. */ int Daemon::maxTimeSinceCheck() { return DAEMON_CHECK_INTERVAL; } /*============================================================================= = Class: NotificationHandler =============================================================================*/ NotificationHandler::NotificationHandler() : DCOPObject(NOTIFY_DCOP_OBJECT), TQObject() { kdDebug(5950) << "NotificationHandler::NotificationHandler()\n"; } /****************************************************************************** * DCOP call from the alarm daemon to notify a change. * The daemon notifies calendar statuses when we first register as a GUI, and whenever * a calendar status changes. So we don't need to read its config files. */ void NotificationHandler::alarmDaemonUpdate(int calendarStatus, const TQString& calendarURL) { kdDebug(5950) << "NotificationHandler::alarmDaemonUpdate(" << calendarStatus << ")\n"; KAlarmd::CalendarStatus status = KAlarmd::CalendarStatus(calendarStatus); if (expandURL(calendarURL) != AlarmCalendar::activeCalendar()->urlString()) return; // it's not a notification about KAlarm's calendar bool enabled = false; switch (status) { case KAlarmd::CALENDAR_UNAVAILABLE: // Calendar is not available for monitoring kdDebug(5950) << "NotificationHandler::alarmDaemonUpdate(CALENDAR_UNAVAILABLE)\n"; break; case KAlarmd::CALENDAR_DISABLED: // Calendar is available for monitoring but is not currently being monitored kdDebug(5950) << "NotificationHandler::alarmDaemonUpdate(DISABLE_CALENDAR)\n"; break; case KAlarmd::CALENDAR_ENABLED: // Calendar is currently being monitored kdDebug(5950) << "NotificationHandler::alarmDaemonUpdate(ENABLE_CALENDAR)\n"; enabled = true; break; default: return; } Daemon::calendarIsEnabled(enabled); } /****************************************************************************** * DCOP call from the alarm daemon to notify that an alarm is due. */ void NotificationHandler::handleEvent(const TQString& url, const TQString& eventId) { TQString id = eventId; if (id.startsWith(TQString::fromLatin1("ad:"))) { // It's a notification from the alarm deamon id = id.mid(3); Daemon::queueEvent(id); } theApp()->handleEvent(url, id); } /****************************************************************************** * DCOP call from the alarm daemon to notify the success or failure of a * registration request from KAlarm. */ void NotificationHandler::registered(bool reregister, int result, int version) { Daemon::registrationResult(reregister, result, version); } /*============================================================================= = Class: AlarmEnableAction =============================================================================*/ AlarmEnableAction::AlarmEnableAction(int accel, TQObject* parent, const char* name) : TDEToggleAction(i18n("Enable &Alarms"), accel, parent, name), mInitialised(false) { setCheckedState(i18n("Disable &Alarms")); setCheckedActual(false); // set the correct text mInitialised = true; } /****************************************************************************** * Set the checked status and the correct text for the Alarms Enabled action. */ void AlarmEnableAction::setCheckedActual(bool running) { kdDebug(5950) << "AlarmEnableAction::setCheckedActual(" << running << ")\n"; if (running != isChecked() || !mInitialised) { TDEToggleAction::setChecked(running); emit switched(running); } } /****************************************************************************** * Request a change in the checked status. * The status is only actually changed when the alarm daemon run state changes. */ void AlarmEnableAction::setChecked(bool check) { kdDebug(5950) << "AlarmEnableAction::setChecked(" << check << ")\n"; if (check != isChecked()) { if (check) Daemon::allowRegisterFailMsg(); emit userClicked(check); } } /****************************************************************************** * Expand a DCOP call parameter URL to a full URL. * (We must store full URLs in the calendar data since otherwise later calls to * reload or remove calendars won't necessarily find a match.) */ TQString expandURL(const TQString& urlString) { if (urlString.isEmpty()) return TQString(); return KURL(urlString).url(); }