/* * kalarmapp.cpp - the KAlarm application object * Program: kalarm * Copyright © 2001-2009 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alarmcalendar.h" #include "alarmlistview.h" #include "birthdaydlg.h" #include "editdlg.h" #include "daemon.h" #include "dcophandler.h" #include "functions.h" #include "kamail.h" #include "karecurrence.h" #include "mainwindow.h" #include "messagebox.h" #include "messagewin.h" #include "preferences.h" #include "prefdlg.h" #include "shellprocess.h" #include "traywindow.h" #include "kalarmapp.moc" #include static bool convWakeTime(const TQCString& timeParam, TQDateTime&, bool& noTime); static bool convInterval(const TQCString& timeParam, KARecurrence::Type&, int& timeInterval, bool allowMonthYear = false); /****************************************************************************** * Find the maximum number of seconds late which a late-cancel alarm is allowed * to be. This is calculated as the alarm daemon's check interval, plus a few * seconds leeway to cater for any timing irregularities. */ static inline int maxLateness(int lateCancel) { static const int LATENESS_LEEWAY = 5; int lc = (lateCancel >= 1) ? (lateCancel - 1)*60 : 0; return Daemon::maxTimeSinceCheck() + LATENESS_LEEWAY + lc; } KAlarmApp* KAlarmApp::theInstance = 0; int KAlarmApp::mActiveCount = 0; int KAlarmApp::mFatalError = 0; TQString KAlarmApp::mFatalMessage; /****************************************************************************** * Construct the application. */ KAlarmApp::KAlarmApp() : KUniqueApplication(), mInitialised(false), mDcopHandler(new DcopHandler()), #ifdef OLD_DCOP mDcopHandlerOld(new DcopHandlerOld()), #endif mTrayWindow(0), mPendingQuit(false), mProcessingQueue(false), mCheckingSystemTray(false), mSessionClosingDown(false), mRefreshExpiredAlarms(false), mSpeechEnabled(false) { Preferences::initialise(); Preferences::connect(TQT_SIGNAL(preferencesChanged()), TQT_TQOBJECT(this), TQT_SLOT(slotPreferencesChanged())); KCal::CalFormat::setApplication(aboutData()->programName(), AlarmCalendar::icalProductId()); KARecurrence::setDefaultFeb29Type(Preferences::defaultFeb29Type()); // Check if the system tray is supported by this window manager mHaveSystemTray = true; // assume yes in lieu of a test which works if (AlarmCalendar::initialiseCalendars()) { connect(AlarmCalendar::expiredCalendar(), TQT_SIGNAL(purged()), TQT_SLOT(slotExpiredPurged())); TDEConfig* config = kapp->config(); config->setGroup(TQString::fromLatin1("General")); mNoSystemTray = config->readBoolEntry(TQString::fromLatin1("NoSystemTray"), false); mSavedNoSystemTray = mNoSystemTray; mOldRunInSystemTray = wantRunInSystemTray(); mDisableAlarmsIfStopped = mOldRunInSystemTray && !mNoSystemTray && Preferences::disableAlarmsIfStopped(); mStartOfDay = Preferences::startOfDay(); if (Preferences::hasStartOfDayChanged()) mStartOfDay.setHMS(100,0,0); // start of day time has changed: flag it as invalid DateTime::setStartOfDay(mStartOfDay); mPrefsExpiredColour = Preferences::expiredColour(); mPrefsExpiredKeepDays = Preferences::expiredKeepDays(); } // Check if the speech synthesis daemon is installed mSpeechEnabled = (TDETrader::self()->query("DCOP/Text-to-Speech", "Name == 'KTTSD'").count() > 0); if (!mSpeechEnabled) kdDebug(5950) << "KAlarmApp::KAlarmApp(): speech synthesis disabled (KTTSD not found)" << endl; // Check if KOrganizer is installed TQString korg = TQString::fromLatin1("korganizer"); mKOrganizerEnabled = !locate("exe", korg).isNull() || !TDEStandardDirs::findExe(korg).isNull(); if (!mKOrganizerEnabled) kdDebug(5950) << "KAlarmApp::KAlarmApp(): KOrganizer options disabled (KOrganizer not found)" << endl; } /****************************************************************************** */ KAlarmApp::~KAlarmApp() { while (!mCommandProcesses.isEmpty()) { ProcData* pd = mCommandProcesses.first(); mCommandProcesses.pop_front(); delete pd; } AlarmCalendar::terminateCalendars(); } /****************************************************************************** * Return the one and only KAlarmApp instance. * If it doesn't already exist, it is created first. */ KAlarmApp* KAlarmApp::getInstance() { if (!theInstance) { theInstance = new KAlarmApp; if (mFatalError) theInstance->quitFatal(); else { // This is here instead of in the constructor to avoid recursion Daemon::initialise(); // calendars must be initialised before calling this } } return theInstance; } /****************************************************************************** * Restore the saved session if required. */ bool KAlarmApp::restoreSession() { if (!isRestored()) return false; if (mFatalError) { quitFatal(); return false; } // Process is being restored by session management. kdDebug(5950) << "KAlarmApp::restoreSession(): Restoring\n"; ++mActiveCount; if (!initCheck(true)) // open the calendar file (needed for main windows) { --mActiveCount; quitIf(1, true); // error opening the main calendar - quit return true; } MainWindow* trayParent = 0; for (int i = 1; TDEMainWindow::canBeRestored(i); ++i) { TQString type = TDEMainWindow::classNameOfToplevel(i); if (type == TQString::fromLatin1("MainWindow")) { MainWindow* win = MainWindow::create(true); win->restore(i, false); if (win->isHiddenTrayParent()) trayParent = win; else win->show(); } else if (type == TQString::fromLatin1("MessageWin")) { MessageWin* win = new MessageWin; win->restore(i, false); if (win->isValid()) win->show(); else delete win; } } initCheck(); // register with the alarm daemon // Try to display the system tray icon if it is configured to be autostarted, // or if we're in run-in-system-tray mode. if (Preferences::autostartTrayIcon() || (MainWindow::count() && wantRunInSystemTray())) { displayTrayIcon(true, trayParent); // Occasionally for no obvious reason, the main main window is // shown when it should be hidden, so hide it just to be sure. if (trayParent) trayParent->hide(); } --mActiveCount; quitIf(0); // quit if no windows are open return true; } /****************************************************************************** * Called for a KUniqueApplication when a new instance of the application is * started. */ int KAlarmApp::newInstance() { kdDebug(5950)<<"KAlarmApp::newInstance()\n"; if (mFatalError) { quitFatal(); return 1; } ++mActiveCount; int exitCode = 0; // default = success static bool firstInstance = true; bool dontRedisplay = false; if (!firstInstance || !isRestored()) { TQString usage; TDECmdLineArgs* args = TDECmdLineArgs::parsedArgs(); // Use a 'do' loop which is executed only once to allow easy error exits. // Errors use 'break' to skip to the end of the function. // Note that DCOP handling is only set up once the command line parameters // have been checked, since we mustn't register with the alarm daemon only // to quit immediately afterwards. do { #define USAGE(message) { usage = message; break; } if (args->isSet("stop")) { // Stop the alarm daemon kdDebug(5950)<<"KAlarmApp::newInstance(): stop\n"; args->clear(); // free up memory if (!Daemon::stop()) { exitCode = 1; break; } dontRedisplay = true; // exit program if no other instances running } else if (args->isSet("reset")) { // Reset the alarm daemon, if it's running. // (If it's not running, it will reset automatically when it eventually starts.) kdDebug(5950)<<"KAlarmApp::newInstance(): reset\n"; args->clear(); // free up memory Daemon::reset(); dontRedisplay = true; // exit program if no other instances running } else if (args->isSet("tray")) { // Display only the system tray icon kdDebug(5950)<<"KAlarmApp::newInstance(): tray\n"; args->clear(); // free up memory if (!mHaveSystemTray) { exitCode = 1; break; } if (!initCheck()) // open the calendar, register with daemon { exitCode = 1; break; } if (!displayTrayIcon(true)) { exitCode = 1; break; } } else if (args->isSet("handleEvent") || args->isSet("triggerEvent") || args->isSet("cancelEvent") || args->isSet("calendarURL")) { // Display or delete the event with the specified event ID kdDebug(5950)<<"KAlarmApp::newInstance(): handle event\n"; EventFunc function = EVENT_HANDLE; int count = 0; const char* option = 0; if (args->isSet("handleEvent")) { function = EVENT_HANDLE; option = "handleEvent"; ++count; } if (args->isSet("triggerEvent")) { function = EVENT_TRIGGER; option = "triggerEvent"; ++count; } if (args->isSet("cancelEvent")) { function = EVENT_CANCEL; option = "cancelEvent"; ++count; } if (!count) USAGE(i18n("%1 requires %2, %3 or %4").arg(TQString::fromLatin1("--calendarURL")).arg(TQString::fromLatin1("--handleEvent")).arg(TQString::fromLatin1("--triggerEvent")).arg(TQString::fromLatin1("--cancelEvent"))) if (count > 1) USAGE(i18n("%1, %2, %3 mutually exclusive").arg(TQString::fromLatin1("--handleEvent")).arg(TQString::fromLatin1("--triggerEvent")).arg(TQString::fromLatin1("--cancelEvent"))); if (!initCheck(true)) // open the calendar, don't register with daemon yet { exitCode = 1; break; } if (args->isSet("calendarURL")) { TQString calendarUrl = args->getOption("calendarURL"); if (KURL(calendarUrl).url() != AlarmCalendar::activeCalendar()->urlString()) USAGE(i18n("%1: wrong calendar file").arg(TQString::fromLatin1("--calendarURL"))) } TQString eventID = args->getOption(option); args->clear(); // free up memory if (eventID.startsWith(TQString::fromLatin1("ad:"))) { // It's a notification from the alarm deamon eventID = eventID.mid(3); Daemon::queueEvent(eventID); } setUpDcop(); // start processing DCOP calls if (!handleEvent(eventID, function)) { exitCode = 1; break; } } else if (args->isSet("edit")) { TQString eventID = args->getOption("edit"); if (!initCheck()) { exitCode = 1; break; } if (!KAlarm::edit(eventID)) { USAGE(i18n("%1: Event %2 not found, or not editable").arg(TQString::fromLatin1("--edit")).arg(eventID)) exitCode = 1; break; } } else if (args->isSet("edit-new") || args->isSet("edit-new-preset")) { TQString templ; if (args->isSet("edit-new-preset")) templ = args->getOption("edit-new-preset"); if (!initCheck()) { exitCode = 1; break; } KAlarm::editNew(templ); } else if (args->isSet("file") || args->isSet("exec") || args->isSet("mail") || args->count()) { // Display a message or file, execute a command, or send an email KAEvent::Action action = KAEvent::MESSAGE; TQCString alMessage; uint alFromID = 0; EmailAddressList alAddresses; TQStringList alAttachments; TQCString alSubject; if (args->isSet("file")) { kdDebug(5950)<<"KAlarmApp::newInstance(): file\n"; if (args->isSet("exec")) USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--exec")).arg(TQString::fromLatin1("--file"))) if (args->isSet("mail")) USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--mail")).arg(TQString::fromLatin1("--file"))) if (args->count()) USAGE(i18n("message incompatible with %1").arg(TQString::fromLatin1("--file"))) alMessage = args->getOption("file"); action = KAEvent::FILE; } else if (args->isSet("exec")) { kdDebug(5950)<<"KAlarmApp::newInstance(): exec\n"; if (args->isSet("mail")) USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--mail")).arg(TQString::fromLatin1("--exec"))) alMessage = args->getOption("exec"); int n = args->count(); for (int i = 0; i < n; ++i) { alMessage += ' '; alMessage += args->arg(i); } action = KAEvent::COMMAND; } else if (args->isSet("mail")) { kdDebug(5950)<<"KAlarmApp::newInstance(): mail\n"; if (args->isSet("subject")) alSubject = args->getOption("subject"); if (args->isSet("from-id")) alFromID = KAMail::identityUoid(args->getOption("from-id")); QCStringList params = args->getOptionList("mail"); for (QCStringList::Iterator i = params.begin(); i != params.end(); ++i) { TQString addr = TQString::fromLocal8Bit(*i); if (!KAMail::checkAddress(addr)) USAGE(i18n("%1: invalid email address").arg(TQString::fromLatin1("--mail"))) alAddresses += KCal::Person(TQString(), addr); } params = args->getOptionList("attach"); for (QCStringList::Iterator i = params.begin(); i != params.end(); ++i) alAttachments += TQString::fromLocal8Bit(*i); alMessage = args->arg(0); action = KAEvent::EMAIL; } else { kdDebug(5950)<<"KAlarmApp::newInstance(): message\n"; alMessage = args->arg(0); } if (action != KAEvent::EMAIL) { if (args->isSet("subject")) USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--subject")).arg(TQString::fromLatin1("--mail"))) if (args->isSet("from-id")) USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--from-id")).arg(TQString::fromLatin1("--mail"))) if (args->isSet("attach")) USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--attach")).arg(TQString::fromLatin1("--mail"))) if (args->isSet("bcc")) USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--bcc")).arg(TQString::fromLatin1("--mail"))) } bool alarmNoTime = false; TQDateTime alarmTime, endTime; TQColor bgColour = Preferences::defaultBgColour(); TQColor fgColour = Preferences::defaultFgColour(); KARecurrence recurrence; int repeatCount = 0; int repeatInterval = 0; if (args->isSet("color")) { // Background colour is specified TQCString colourText = args->getOption("color"); if (static_cast(colourText)[0] == '0' && tolower(static_cast(colourText)[1]) == 'x') colourText.replace(0, 2, "#"); bgColour.setNamedColor(colourText); if (!bgColour.isValid()) USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--color"))) } if (args->isSet("colorfg")) { // Foreground colour is specified TQCString colourText = args->getOption("colorfg"); if (static_cast(colourText)[0] == '0' && tolower(static_cast(colourText)[1]) == 'x') colourText.replace(0, 2, "#"); fgColour.setNamedColor(colourText); if (!fgColour.isValid()) USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--colorfg"))) } if (args->isSet("time")) { TQCString dateTime = args->getOption("time"); if (!convWakeTime(dateTime, alarmTime, alarmNoTime)) USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--time"))) } else alarmTime = TQDateTime::currentDateTime(); bool haveRecurrence = args->isSet("recurrence"); if (haveRecurrence) { if (args->isSet("login")) USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--login")).arg(TQString::fromLatin1("--recurrence"))) if (args->isSet("until")) USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--until")).arg(TQString::fromLatin1("--recurrence"))) TQCString rule = args->getOption("recurrence"); recurrence.set(TQString::fromLocal8Bit(static_cast(rule))); } if (args->isSet("interval")) { // Repeat count is specified int count; if (args->isSet("login")) USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--login")).arg(TQString::fromLatin1("--interval"))) bool ok; if (args->isSet("repeat")) { count = args->getOption("repeat").toInt(&ok); if (!ok || !count || count < -1 || (count < 0 && haveRecurrence)) USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--repeat"))) } else if (haveRecurrence) USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--interval")).arg(TQString::fromLatin1("--repeat"))) else if (args->isSet("until")) { count = 0; TQCString dateTime = args->getOption("until"); if (!convWakeTime(dateTime, endTime, alarmNoTime)) USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--until"))) if (endTime < alarmTime) USAGE(i18n("%1 earlier than %2").arg(TQString::fromLatin1("--until")).arg(TQString::fromLatin1("--time"))) } else count = -1; // Get the recurrence interval int interval; KARecurrence::Type recurType; if (!convInterval(args->getOption("interval"), recurType, interval, !haveRecurrence) || interval < 0) USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--interval"))) if (alarmNoTime && recurType == KARecurrence::MINUTELY) USAGE(i18n("Invalid %1 parameter for date-only alarm").arg(TQString::fromLatin1("--interval"))) if (haveRecurrence) { // There is a also a recurrence specified, so set up a sub-repetition int longestInterval = recurrence.longestInterval(); if (count * interval > longestInterval) USAGE(i18n("Invalid %1 and %2 parameters: repetition is longer than %3 interval").arg(TQString::fromLatin1("--interval")).arg(TQString::fromLatin1("--repeat")).arg(TQString::fromLatin1("--recurrence"))); repeatCount = count; repeatInterval = interval; } else { // There is no other recurrence specified, so convert the repetition // parameters into a KCal::Recurrence recurrence.set(recurType, interval, count, DateTime(alarmTime, alarmNoTime), endTime); } } else { if (args->isSet("repeat")) USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--repeat")).arg(TQString::fromLatin1("--interval"))) if (args->isSet("until")) USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--until")).arg(TQString::fromLatin1("--interval"))) } TQCString audioFile; float audioVolume = -1; #ifdef WITHOUT_ARTS bool audioRepeat = false; #else bool audioRepeat = args->isSet("play-repeat"); #endif if (audioRepeat || args->isSet("play")) { // Play a sound with the alarm if (audioRepeat && args->isSet("play")) USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--play")).arg(TQString::fromLatin1("--play-repeat"))) if (args->isSet("beep")) USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--beep")).arg(TQString::fromLatin1(audioRepeat ? "--play-repeat" : "--play"))) if (args->isSet("speak")) USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--speak")).arg(TQString::fromLatin1(audioRepeat ? "--play-repeat" : "--play"))) audioFile = args->getOption(audioRepeat ? "play-repeat" : "play"); #ifndef WITHOUT_ARTS if (args->isSet("volume")) { bool ok; int volumepc = args->getOption("volume").toInt(&ok); if (!ok || volumepc < 0 || volumepc > 100) USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--volume"))) audioVolume = static_cast(volumepc) / 100; } #endif } #ifndef WITHOUT_ARTS else if (args->isSet("volume")) USAGE(i18n("%1 requires %2 or %3").arg(TQString::fromLatin1("--volume")).arg(TQString::fromLatin1("--play")).arg(TQString::fromLatin1("--play-repeat"))) #endif if (args->isSet("speak")) { if (args->isSet("beep")) USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--beep")).arg(TQString::fromLatin1("--speak"))) if (!mSpeechEnabled) USAGE(i18n("%1 requires speech synthesis to be configured using KTTSD").arg(TQString::fromLatin1("--speak"))) } int reminderMinutes = 0; bool onceOnly = args->isSet("reminder-once"); if (args->isSet("reminder") || onceOnly) { // Issue a reminder alarm in advance of the main alarm if (onceOnly && args->isSet("reminder")) USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--reminder")).arg(TQString::fromLatin1("--reminder-once"))) TQString opt = onceOnly ? TQString::fromLatin1("--reminder-once") : TQString::fromLatin1("--reminder"); if (args->isSet("exec")) USAGE(i18n("%1 incompatible with %2").arg(opt).arg(TQString::fromLatin1("--exec"))) if (args->isSet("mail")) USAGE(i18n("%1 incompatible with %2").arg(opt).arg(TQString::fromLatin1("--mail"))) KARecurrence::Type recurType; TQString optval = args->getOption(onceOnly ? "reminder-once" : "reminder"); if (!convInterval(args->getOption(onceOnly ? "reminder-once" : "reminder"), recurType, reminderMinutes)) USAGE(i18n("Invalid %1 parameter").arg(opt)) if (recurType == KARecurrence::MINUTELY && alarmNoTime) USAGE(i18n("Invalid %1 parameter for date-only alarm").arg(opt)) } int lateCancel = 0; if (args->isSet("late-cancel")) { KARecurrence::Type recurType; bool ok = convInterval(args->getOption("late-cancel"), recurType, lateCancel); if (!ok || lateCancel <= 0) USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("late-cancel"))) } else if (args->isSet("auto-close")) USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--auto-close")).arg(TQString::fromLatin1("--late-cancel"))) int flags = KAEvent::DEFAULT_FONT; if (args->isSet("ack-confirm")) flags |= KAEvent::CONFIRM_ACK; if (args->isSet("auto-close")) flags |= KAEvent::AUTO_CLOSE; if (args->isSet("beep")) flags |= KAEvent::BEEP; if (args->isSet("speak")) flags |= KAEvent::SPEAK; if (args->isSet("korganizer")) flags |= KAEvent::COPY_KORGANIZER; if (args->isSet("disable")) flags |= KAEvent::DISABLED; if (audioRepeat) flags |= KAEvent::REPEAT_SOUND; if (args->isSet("login")) flags |= KAEvent::REPEAT_AT_LOGIN; if (args->isSet("bcc")) flags |= KAEvent::EMAIL_BCC; if (alarmNoTime) flags |= KAEvent::ANY_TIME; args->clear(); // free up memory // Display or schedule the event if (!initCheck()) { exitCode = 1; break; } if (!scheduleEvent(action, alMessage, alarmTime, lateCancel, flags, bgColour, fgColour, TQFont(), audioFile, audioVolume, reminderMinutes, recurrence, repeatInterval, repeatCount, alFromID, alAddresses, alSubject, alAttachments)) { exitCode = 1; break; } } else { // No arguments - run interactively & display the main window kdDebug(5950)<<"KAlarmApp::newInstance(): interactive\n"; if (args->isSet("ack-confirm")) usage += TQString::fromLatin1("--ack-confirm "); if (args->isSet("attach")) usage += TQString::fromLatin1("--attach "); if (args->isSet("auto-close")) usage += TQString::fromLatin1("--auto-close "); if (args->isSet("bcc")) usage += TQString::fromLatin1("--bcc "); if (args->isSet("beep")) usage += TQString::fromLatin1("--beep "); if (args->isSet("color")) usage += TQString::fromLatin1("--color "); if (args->isSet("colorfg")) usage += TQString::fromLatin1("--colorfg "); if (args->isSet("disable")) usage += TQString::fromLatin1("--disable "); if (args->isSet("from-id")) usage += TQString::fromLatin1("--from-id "); if (args->isSet("korganizer")) usage += TQString::fromLatin1("--korganizer "); if (args->isSet("late-cancel")) usage += TQString::fromLatin1("--late-cancel "); if (args->isSet("login")) usage += TQString::fromLatin1("--login "); if (args->isSet("play")) usage += TQString::fromLatin1("--play "); #ifndef WITHOUT_ARTS if (args->isSet("play-repeat")) usage += TQString::fromLatin1("--play-repeat "); #endif if (args->isSet("reminder")) usage += TQString::fromLatin1("--reminder "); if (args->isSet("reminder-once")) usage += TQString::fromLatin1("--reminder-once "); if (args->isSet("speak")) usage += TQString::fromLatin1("--speak "); if (args->isSet("subject")) usage += TQString::fromLatin1("--subject "); if (args->isSet("time")) usage += TQString::fromLatin1("--time "); #ifndef WITHOUT_ARTS if (args->isSet("volume")) usage += TQString::fromLatin1("--volume "); #endif if (!usage.isEmpty()) { usage += i18n(": option(s) only valid with a message/%1/%2").arg(TQString::fromLatin1("--file")).arg(TQString::fromLatin1("--exec")); break; } args->clear(); // free up memory if (!initCheck()) { exitCode = 1; break; } (MainWindow::create())->show(); } } while (0); // only execute once if (!usage.isEmpty()) { // Note: we can't use args->usage() since that also quits any other // running 'instances' of the program. std::cerr << usage.local8Bit().data() << i18n("\nUse --help to get a list of available command line options.\n").local8Bit().data(); exitCode = 1; } } if (firstInstance && !dontRedisplay && !exitCode) redisplayAlarms(); --mActiveCount; firstInstance = false; // Quit the application if this was the last/only running "instance" of the program. // Executing 'return' doesn't work very well since the program continues to // run if no windows were created. quitIf(exitCode); return exitCode; } /****************************************************************************** * Quit the program, optionally only if there are no more "instances" running. */ void KAlarmApp::quitIf(int exitCode, bool force) { if (force) { // Quit regardless, except for message windows MainWindow::closeAll(); displayTrayIcon(false); if (MessageWin::instanceCount()) return; } else { // Quit only if there are no more "instances" running mPendingQuit = false; if (mActiveCount > 0 || MessageWin::instanceCount()) return; int mwcount = MainWindow::count(); MainWindow* mw = mwcount ? MainWindow::firstWindow() : 0; if (mwcount > 1 || (mwcount && (!mw->isHidden() || !mw->isTrayParent()))) return; // There are no windows left except perhaps a main window which is a hidden tray icon parent if (mTrayWindow) { // There is a system tray icon. // Don't exit unless the system tray doesn't seem to exist. if (checkSystemTray()) return; } if (!mDcopQueue.isEmpty() || !mCommandProcesses.isEmpty()) { // Don't quit yet if there are outstanding actions on the DCOP queue mPendingQuit = true; mPendingQuitCode = exitCode; return; } } // This was the last/only running "instance" of the program, so exit completely. kdDebug(5950) << "KAlarmApp::quitIf(" << exitCode << "): quitting" << endl; BirthdayDlg::close(); exit(exitCode); } /****************************************************************************** * Called when the Quit menu item is selected. * Closes the system tray window and all main windows, but does not exit the * program if other windows are still open. */ void KAlarmApp::doQuit(TQWidget* parent) { kdDebug(5950) << "KAlarmApp::doQuit()\n"; if (mDisableAlarmsIfStopped && MessageBox::warningContinueCancel(parent, KMessageBox::Cancel, i18n("Quitting will disable alarms\n(once any alarm message windows are closed)."), TQString(), KStdGuiItem::quit(), Preferences::QUIT_WARN ) != KMessageBox::Yes) return; quitIf(0, true); } /****************************************************************************** * Called when the session manager is about to close down the application. */ void KAlarmApp::commitData(TQSessionManager& sm) { mSessionClosingDown = true; KUniqueApplication::commitData(sm); mSessionClosingDown = false; // reset in case shutdown is cancelled } /****************************************************************************** * Display an error message for a fatal error. Prevent further actions since * the program state is unsafe. */ void KAlarmApp::displayFatalError(const TQString& message) { if (!mFatalError) { mFatalError = 1; mFatalMessage = message; if (theInstance) TQTimer::singleShot(0, theInstance, TQT_SLOT(quitFatal())); } } /****************************************************************************** * Quit the program, once the fatal error message has been acknowledged. */ void KAlarmApp::quitFatal() { switch (mFatalError) { case 0: case 2: return; case 1: mFatalError = 2; KMessageBox::error(0, mFatalMessage); mFatalError = 3; // fall through to '3' case 3: if (theInstance) theInstance->quitIf(1, true); break; } TQTimer::singleShot(1000, this, TQT_SLOT(quitFatal())); } /****************************************************************************** * The main processing loop for KAlarm. * All KAlarm operations involving opening or updating calendar files are called * from this loop to ensure that only one operation is active at any one time. * This precaution is necessary because KAlarm's activities are mostly * asynchronous, being in response to DCOP calls from the alarm daemon (or other * programs) or timer events, any of which can be received in the middle of * performing another operation. If a calendar file is opened or updated while * another calendar operation is in progress, the program has been observed to * hang, or the first calendar call has failed with data loss - clearly * unacceptable!! */ void KAlarmApp::processQueue() { if (mInitialised && !mProcessingQueue) { kdDebug(5950) << "KAlarmApp::processQueue()\n"; mProcessingQueue = true; // Reset the alarm daemon if it's been queued KAlarm::resetDaemonIfQueued(); // Process DCOP calls while (!mDcopQueue.isEmpty()) { DcopTQEntry& entry = mDcopQueue.first(); if (entry.eventId.isEmpty()) { // It's a new alarm switch (entry.function) { case EVENT_TRIGGER: execAlarm(entry.event, entry.event.firstAlarm(), false); break; case EVENT_HANDLE: KAlarm::addEvent(entry.event, 0); break; case EVENT_CANCEL: break; } } else handleEvent(entry.eventId, entry.function); mDcopQueue.pop_front(); } // Purge the expired alarms calendar if it's time to do so AlarmCalendar::expiredCalendar()->purgeIfQueued(); // Now that the queue has been processed, quit if a quit was queued if (mPendingQuit) quitIf(mPendingQuitCode); mProcessingQueue = false; } } /****************************************************************************** * Redisplay alarms which were being shown when the program last exited. * Normally, these alarms will have been displayed by session restoration, but * if the program crashed or was killed, we can redisplay them here so that * they won't be lost. */ void KAlarmApp::redisplayAlarms() { AlarmCalendar* cal = AlarmCalendar::displayCalendar(); if (cal->isOpen()) { KCal::Event::List events = cal->events(); for (KCal::Event::List::ConstIterator it = events.begin(); it != events.end(); ++it) { KCal::Event* kcalEvent = *it; KAEvent event(*kcalEvent); event.setUid(KAEvent::ACTIVE); if (!MessageWin::findEvent(event.id())) { // This event should be displayed, but currently isn't being kdDebug(5950) << "KAlarmApp::redisplayAlarms(): " << event.id() << endl; KAAlarm alarm = event.convertDisplayingAlarm(); (new MessageWin(event, alarm, false, !alarm.repeatAtLogin()))->show(); } } } } /****************************************************************************** * Called when the system tray main window is closed. */ void KAlarmApp::removeWindow(TrayWindow*) { mTrayWindow = 0; quitIf(); } /****************************************************************************** * Display or close the system tray icon. */ bool KAlarmApp::displayTrayIcon(bool show, MainWindow* parent) { static bool creating = false; if (show) { if (!mTrayWindow && !creating) { if (!mHaveSystemTray) return false; if (!MainWindow::count() && wantRunInSystemTray()) { creating = true; // prevent main window constructor from creating an additional tray icon parent = MainWindow::create(); creating = false; } mTrayWindow = new TrayWindow(parent ? parent : MainWindow::firstWindow()); connect(mTrayWindow, TQT_SIGNAL(deleted()), TQT_SIGNAL(trayIconToggled())); mTrayWindow->show(); emit trayIconToggled(); // Set up a timer so that we can check after all events in the window system's // event queue have been processed, whether the system tray actually exists mCheckingSystemTray = true; mSavedNoSystemTray = mNoSystemTray; mNoSystemTray = false; TQTimer::singleShot(0, this, TQT_SLOT(slotSystemTrayTimer())); } } else if (mTrayWindow) { delete mTrayWindow; mTrayWindow = 0; } return true; } /****************************************************************************** * Called by a timer to check whether the system tray icon has been housed in * the system tray. Because there is a delay between the system tray icon show * event and the icon being reparented by the system tray, we have to use a * timer to check whether the system tray has actually grabbed it, or whether * the system tray probably doesn't exist. */ void KAlarmApp::slotSystemTrayTimer() { mCheckingSystemTray = false; if (!checkSystemTray()) quitIf(0); // exit the application if there are no open windows } /****************************************************************************** * Check whether the system tray icon has been housed in the system tray. * If the system tray doesn't seem to exist, tell the alarm daemon to notify us * of alarms regardless of whether we're running. */ bool KAlarmApp::checkSystemTray() { if (mCheckingSystemTray || !mTrayWindow) return true; if (mTrayWindow->inSystemTray() != !mSavedNoSystemTray) { kdDebug(5950) << "KAlarmApp::checkSystemTray(): changed -> " << mSavedNoSystemTray << endl; mNoSystemTray = mSavedNoSystemTray = !mSavedNoSystemTray; // Store the new setting in the config file, so that if KAlarm exits and is then // next activated by the daemon to display a message, it will register with the // daemon with the correct NOTIFY type. If that happened when there was no system // tray and alarms are disabled when KAlarm is not running, registering with // NO_START_NOTIFY could result in alarms never being seen. TDEConfig* config = kapp->config(); config->setGroup(TQString::fromLatin1("General")); config->writeEntry(TQString::fromLatin1("NoSystemTray"), mNoSystemTray); config->sync(); // Update other settings and reregister with the alarm daemon slotPreferencesChanged(); } else { kdDebug(5950) << "KAlarmApp::checkSystemTray(): no change = " << !mSavedNoSystemTray << endl; mNoSystemTray = mSavedNoSystemTray; } return !mNoSystemTray; } /****************************************************************************** * Return the main window associated with the system tray icon. */ MainWindow* KAlarmApp::trayMainWindow() const { return mTrayWindow ? mTrayWindow->assocMainWindow() : 0; } /****************************************************************************** * Called when KAlarm preferences have changed. */ void KAlarmApp::slotPreferencesChanged() { bool newRunInSysTray = wantRunInSystemTray(); if (newRunInSysTray != mOldRunInSystemTray) { // The system tray run mode has changed ++mActiveCount; // prevent the application from quitting MainWindow* win = mTrayWindow ? mTrayWindow->assocMainWindow() : 0; delete mTrayWindow; // remove the system tray icon if it is currently shown mTrayWindow = 0; mOldRunInSystemTray = newRunInSysTray; if (!newRunInSysTray) { if (win && win->isHidden()) delete win; } displayTrayIcon(true); --mActiveCount; } bool newDisableIfStopped = wantRunInSystemTray() && !mNoSystemTray && Preferences::disableAlarmsIfStopped(); if (newDisableIfStopped != mDisableAlarmsIfStopped) { mDisableAlarmsIfStopped = newDisableIfStopped; // N.B. this setting is used by Daemon::reregister() Preferences::setQuitWarn(true); // since mode has changed, re-allow warning messages on Quit Daemon::reregister(); // re-register with the alarm daemon } // Change alarm times for date-only alarms if the start of day time has changed if (Preferences::startOfDay() != mStartOfDay) changeStartOfDay(); // In case the date for February 29th recurrences has changed KARecurrence::setDefaultFeb29Type(Preferences::defaultFeb29Type()); if (Preferences::expiredColour() != mPrefsExpiredColour) { // The expired alarms text colour has changed mRefreshExpiredAlarms = true; mPrefsExpiredColour = Preferences::expiredColour(); } if (Preferences::expiredKeepDays() != mPrefsExpiredKeepDays) { // How long expired alarms are being kept has changed. // N.B. This also adjusts for any change in start-of-day time. mPrefsExpiredKeepDays = Preferences::expiredKeepDays(); AlarmCalendar::expiredCalendar()->setPurgeDays(mPrefsExpiredKeepDays); } if (mRefreshExpiredAlarms) { mRefreshExpiredAlarms = false; MainWindow::updateExpired(); } } /****************************************************************************** * Change alarm times for date-only alarms after the start of day time has changed. */ void KAlarmApp::changeStartOfDay() { Daemon::notifyTimeChanged(); // tell the alarm daemon the new time TQTime sod = Preferences::startOfDay(); DateTime::setStartOfDay(sod); AlarmCalendar* cal = AlarmCalendar::activeCalendar(); if (KAEvent::adjustStartOfDay(cal->events())) cal->save(); Preferences::updateStartOfDayCheck(); // now that calendar is updated, set OK flag in config file mStartOfDay = sod; } /****************************************************************************** * Called when the expired alarms calendar has been purged. * Updates the alarm list in all main windows. */ void KAlarmApp::slotExpiredPurged() { mRefreshExpiredAlarms = false; MainWindow::updateExpired(); } /****************************************************************************** * Return whether the program is configured to be running in the system tray. */ bool KAlarmApp::wantRunInSystemTray() const { return Preferences::runInSystemTray() && mHaveSystemTray; } /****************************************************************************** * Called to schedule a new alarm, either in response to a DCOP notification or * to command line options. * Reply = true unless there was a parameter error or an error opening calendar file. */ bool KAlarmApp::scheduleEvent(KAEvent::Action action, const TQString& text, const TQDateTime& dateTime, int lateCancel, int flags, const TQColor& bg, const TQColor& fg, const TQFont& font, const TQString& audioFile, float audioVolume, int reminderMinutes, const KARecurrence& recurrence, int repeatInterval, int repeatCount, uint mailFromID, const EmailAddressList& mailAddresses, const TQString& mailSubject, const TQStringList& mailAttachments) { kdDebug(5950) << "KAlarmApp::scheduleEvent(): " << text << endl; if (!dateTime.isValid()) return false; TQDateTime now = TQDateTime::currentDateTime(); if (lateCancel && dateTime < now.addSecs(-maxLateness(lateCancel))) return true; // alarm time was already expired too long ago TQDateTime alarmTime = dateTime; // Round down to the nearest minute to avoid scheduling being messed up alarmTime.setTime(TQTime(alarmTime.time().hour(), alarmTime.time().minute(), 0)); KAEvent event(alarmTime, text, bg, fg, font, action, lateCancel, flags); if (reminderMinutes) { bool onceOnly = (reminderMinutes < 0); event.setReminder((onceOnly ? -reminderMinutes : reminderMinutes), onceOnly); } if (!audioFile.isEmpty()) event.setAudioFile(audioFile, audioVolume, -1, 0); if (!mailAddresses.isEmpty()) event.setEmail(mailFromID, mailAddresses, mailSubject, mailAttachments); event.setRecurrence(recurrence); event.setFirstRecurrence(); event.setRepetition(repeatInterval, repeatCount - 1); if (alarmTime <= now) { // Alarm is due for display already. // First execute it once without adding it to the calendar file. if (!mInitialised) mDcopQueue.append(DcopTQEntry(event, EVENT_TRIGGER)); else execAlarm(event, event.firstAlarm(), false); // If it's a recurring alarm, reschedule it for its next occurrence if (!event.recurs() || event.setNextOccurrence(now) == KAEvent::NO_OCCURRENCE) return true; // It has recurrences in the future } // Queue the alarm for insertion into the calendar file mDcopQueue.append(DcopTQEntry(event)); if (mInitialised) TQTimer::singleShot(0, this, TQT_SLOT(processQueue())); return true; } /****************************************************************************** * Called in response to a DCOP notification by the alarm daemon that an event * should be handled, i.e. displayed or cancelled. * Optionally display the event. Delete the event from the calendar file and * from every main window instance. */ bool KAlarmApp::handleEvent(const TQString& urlString, const TQString& eventID, EventFunc function) { kdDebug(5950) << "KAlarmApp::handleEvent(DCOP): " << eventID << endl; AlarmCalendar* cal = AlarmCalendar::activeCalendar(); // this can be called before calendars have been initialised if (cal && KURL(urlString).url() != cal->urlString()) { kdError(5950) << "KAlarmApp::handleEvent(DCOP): wrong calendar file " << urlString << endl; Daemon::eventHandled(eventID, false); return false; } mDcopQueue.append(DcopTQEntry(function, eventID)); if (mInitialised) TQTimer::singleShot(0, this, TQT_SLOT(processQueue())); return true; } /****************************************************************************** * Either: * a) Display the event and then delete it if it has no outstanding repetitions. * b) Delete the event. * c) Reschedule the event for its next repetition. If none remain, delete it. * If the event is deleted, it is removed from the calendar file and from every * main window instance. */ bool KAlarmApp::handleEvent(const TQString& eventID, EventFunc function) { kdDebug(5950) << "KAlarmApp::handleEvent(): " << eventID << ", " << (function==EVENT_TRIGGER?"TRIGGER":function==EVENT_CANCEL?"CANCEL":function==EVENT_HANDLE?"HANDLE":"?") << endl; KCal::Event* kcalEvent = AlarmCalendar::activeCalendar()->event(eventID); if (!kcalEvent) { kdError(5950) << "KAlarmApp::handleEvent(): event ID not found: " << eventID << endl; Daemon::eventHandled(eventID, false); return false; } KAEvent event(*kcalEvent); switch (function) { case EVENT_CANCEL: KAlarm::deleteEvent(event, true); break; case EVENT_TRIGGER: // handle it if it's due, else execute it regardless case EVENT_HANDLE: // handle it if it's due { TQDateTime now = TQDateTime::currentDateTime(); bool updateCalAndDisplay = false; bool alarmToExecuteValid = false; KAAlarm alarmToExecute; // Check all the alarms in turn. // Note that the main alarm is fetched before any other alarms. for (KAAlarm alarm = event.firstAlarm(); alarm.valid(); alarm = event.nextAlarm(alarm)) { // Check if the alarm is due yet. int secs = alarm.dateTime(true).dateTime().secsTo(now); if (secs < 0) { // This alarm is definitely not due yet kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": not due\n"; continue; } if (alarm.repeatAtLogin()) { // Alarm is to be displayed at every login. // Check if the alarm has only just been set up. // (The alarm daemon will immediately notify that it is due // since it is set up with a time in the past.) kdDebug(5950) << "KAlarmApp::handleEvent(): REPEAT_AT_LOGIN\n"; if (secs < maxLateness(1)) continue; // Check if the main alarm is already being displayed. // (We don't want to display both at the same time.) if (alarmToExecute.valid()) continue; // Set the time to display if it's a display alarm alarm.setTime(now); } if (alarm.lateCancel()) { // Alarm is due, and it is to be cancelled if too late. kdDebug(5950) << "KAlarmApp::handleEvent(): LATE_CANCEL\n"; bool late = false; bool cancel = false; if (alarm.dateTime().isDateOnly()) { // The alarm has no time, so cancel it if its date is too far past int maxlate = alarm.lateCancel() / 1440; // maximum lateness in days TQDateTime limit(alarm.date().addDays(maxlate + 1), Preferences::startOfDay()); if (now >= limit) { // It's too late to display the scheduled occurrence. // Find the last previous occurrence of the alarm. DateTime next; KAEvent::OccurType type = event.previousOccurrence(now, next, true); switch (type & ~KAEvent::OCCURRENCE_REPEAT) { case KAEvent::FIRST_OR_ONLY_OCCURRENCE: case KAEvent::RECURRENCE_DATE: case KAEvent::RECURRENCE_DATE_TIME: case KAEvent::LAST_RECURRENCE: limit.setDate(next.date().addDays(maxlate + 1)); limit.setTime(Preferences::startOfDay()); if (now >= limit) { if (type == KAEvent::LAST_RECURRENCE || (type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event.recurs())) cancel = true; // last occurrence (and there are no repetitions) else late = true; } break; case KAEvent::NO_OCCURRENCE: default: late = true; break; } } } else { // The alarm is timed. Allow it to be the permitted amount late before cancelling it. int maxlate = maxLateness(alarm.lateCancel()); if (secs > maxlate) { // It's over the maximum interval late. // Find the most recent occurrence of the alarm. DateTime next; KAEvent::OccurType type = event.previousOccurrence(now, next, true); switch (type & ~KAEvent::OCCURRENCE_REPEAT) { case KAEvent::FIRST_OR_ONLY_OCCURRENCE: case KAEvent::RECURRENCE_DATE: case KAEvent::RECURRENCE_DATE_TIME: case KAEvent::LAST_RECURRENCE: if (next.dateTime().secsTo(now) > maxlate) { if (type == KAEvent::LAST_RECURRENCE || (type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event.recurs())) cancel = true; // last occurrence (and there are no repetitions) else late = true; } break; case KAEvent::NO_OCCURRENCE: default: late = true; break; } } } if (cancel) { // All recurrences are finished, so cancel the event event.setArchive(); cancelAlarm(event, alarm.type(), false); updateCalAndDisplay = true; continue; } if (late) { // The latest repetition was too long ago, so schedule the next one rescheduleAlarm(event, alarm, false); updateCalAndDisplay = true; continue; } } if (!alarmToExecuteValid) { kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": execute\n"; alarmToExecute = alarm; // note the alarm to be executed alarmToExecuteValid = true; // only trigger one alarm for the event } else kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": skip\n"; } // If there is an alarm to execute, do this last after rescheduling/cancelling // any others. This ensures that the updated event is only saved once to the calendar. if (alarmToExecute.valid()) execAlarm(event, alarmToExecute, true, !alarmToExecute.repeatAtLogin()); else { if (function == EVENT_TRIGGER) { // The alarm is to be executed regardless of whether it's due. // Only trigger one alarm from the event - we don't want multiple // identical messages, for example. KAAlarm alarm = event.firstAlarm(); if (alarm.valid()) execAlarm(event, alarm, false); } if (updateCalAndDisplay) KAlarm::updateEvent(event, 0); // update the window lists and calendar file else if (function != EVENT_TRIGGER) { kdDebug(5950) << "KAlarmApp::handleEvent(): no action\n"; Daemon::eventHandled(eventID, false); } } break; } } return true; } /****************************************************************************** * Called when an alarm is currently being displayed, to store a copy of the * alarm in the displaying calendar, and to reschedule it for its next repetition. * If no repetitions remain, cancel it. */ void KAlarmApp::alarmShowing(KAEvent& event, KAAlarm::Type alarmType, const DateTime& alarmTime) { kdDebug(5950) << "KAlarmApp::alarmShowing(" << event.id() << ", " << KAAlarm::debugType(alarmType) << ")\n"; KCal::Event* kcalEvent = AlarmCalendar::activeCalendar()->event(event.id()); if (!kcalEvent) kdError(5950) << "KAlarmApp::alarmShowing(): event ID not found: " << event.id() << endl; else { KAAlarm alarm = event.alarm(alarmType); if (!alarm.valid()) kdError(5950) << "KAlarmApp::alarmShowing(): alarm type not found: " << event.id() << ":" << alarmType << endl; else { // Copy the alarm to the displaying calendar in case of a crash, etc. KAEvent dispEvent; dispEvent.setDisplaying(event, alarmType, alarmTime.dateTime()); AlarmCalendar* cal = AlarmCalendar::displayCalendarOpen(); if (cal) { cal->deleteEvent(dispEvent.id()); // in case it already exists cal->addEvent(dispEvent); cal->save(); } rescheduleAlarm(event, alarm, true); return; } } Daemon::eventHandled(event.id(), false); } /****************************************************************************** * Called when an alarm action has completed, to perform any post-alarm actions. */ void KAlarmApp::alarmCompleted(const KAEvent& event) { if (!event.postAction().isEmpty() && ShellProcess::authorised()) { TQString command = event.postAction(); kdDebug(5950) << "KAlarmApp::alarmCompleted(" << event.id() << "): " << command << endl; doShellCommand(command, event, 0, ProcData::POST_ACTION); } } /****************************************************************************** * Reschedule the alarm for its next recurrence. If none remain, delete it. * If the alarm is deleted and it is the last alarm for its event, the event is * removed from the calendar file and from every main window instance. */ void KAlarmApp::rescheduleAlarm(KAEvent& event, const KAAlarm& alarm, bool updateCalAndDisplay) { kdDebug(5950) << "KAlarmApp::rescheduleAlarm()" << endl; bool update = false; if (alarm.reminder() || alarm.deferred()) { // It's an advance warning alarm or an extra deferred alarm, so delete it event.removeExpiredAlarm(alarm.type()); update = true; } else if (alarm.repeatAtLogin()) { // Leave an alarm which repeats at every login until its main alarm is deleted if (updateCalAndDisplay && event.updated()) update = true; } else { // Reschedule the alarm for its next recurrence. KAEvent::OccurType type = event.setNextOccurrence(TQDateTime::currentDateTime()); switch (type) { case KAEvent::NO_OCCURRENCE: // All repetitions are finished, so cancel the event cancelAlarm(event, alarm.type(), updateCalAndDisplay); break; default: if (!(type & KAEvent::OCCURRENCE_REPEAT)) break; // Next occurrence is a repeat, so fall through to recurrence handling case KAEvent::RECURRENCE_DATE: case KAEvent::RECURRENCE_DATE_TIME: case KAEvent::LAST_RECURRENCE: // The event is due by now and repetitions still remain, so rewrite the event if (updateCalAndDisplay) update = true; else { event.cancelCancelledDeferral(); event.setUpdated(); // note that the calendar file needs to be updated } break; case KAEvent::FIRST_OR_ONLY_OCCURRENCE: // The first occurrence is still due?!?, so don't do anything break; } if (event.deferred()) { // Just in case there's also a deferred alarm, ensure it's removed event.removeExpiredAlarm(KAAlarm::DEFERRED_ALARM); update = true; } } if (update) { event.cancelCancelledDeferral(); KAlarm::updateEvent(event, 0); // update the window lists and calendar file } } /****************************************************************************** * Delete the alarm. If it is the last alarm for its event, the event is removed * from the calendar file and from every main window instance. */ void KAlarmApp::cancelAlarm(KAEvent& event, KAAlarm::Type alarmType, bool updateCalAndDisplay) { kdDebug(5950) << "KAlarmApp::cancelAlarm()" << endl; event.cancelCancelledDeferral(); if (alarmType == KAAlarm::MAIN_ALARM && !event.displaying() && event.toBeArchived()) { // The event is being deleted. Save it in the expired calendar file first. TQString id = event.id(); // save event ID since KAlarm::addExpiredEvent() changes it KAlarm::addExpiredEvent(event); event.setEventID(id); // restore event ID } event.removeExpiredAlarm(alarmType); if (!event.alarmCount()) KAlarm::deleteEvent(event, false); else if (updateCalAndDisplay) KAlarm::updateEvent(event, 0); // update the window lists and calendar file } /****************************************************************************** * Execute an alarm by displaying its message or file, or executing its command. * Reply = ShellProcess instance if a command alarm * != 0 if successful * = 0 if the alarm is disabled, or if an error message was output. */ void* KAlarmApp::execAlarm(KAEvent& event, const KAAlarm& alarm, bool reschedule, bool allowDefer, bool noPreAction) { if (!event.enabled()) { // The event is disabled. if (reschedule) rescheduleAlarm(event, alarm, true); return 0; } void* result = (void*)1; event.setArchive(); switch (alarm.action()) { case KAAlarm::MESSAGE: case KAAlarm::FILE: { // Display a message or file, provided that the same event isn't already being displayed MessageWin* win = MessageWin::findEvent(event.id()); // Find if we're changing a reminder message to the real message bool reminder = (alarm.type() & KAAlarm::REMINDER_ALARM); bool replaceReminder = !reminder && win && (win->alarmType() & KAAlarm::REMINDER_ALARM); if (!reminder && !event.deferred() && (replaceReminder || !win) && !noPreAction && !event.preAction().isEmpty() && ShellProcess::authorised()) { // It's not a reminder or a deferred alarm, and there is no message window // (other than a reminder window) currently displayed for this alarm, // and we need to execute a command before displaying the new window. // Check whether the command is already being executed for this alarm. for (TQValueList::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it) { ProcData* pd = *it; if (pd->event->id() == event.id() && (pd->flags & ProcData::PRE_ACTION)) { kdDebug(5950) << "KAlarmApp::execAlarm(): already executing pre-DISPLAY command" << endl; return pd->process; // already executing - don't duplicate the action } } TQString command = event.preAction(); kdDebug(5950) << "KAlarmApp::execAlarm(): pre-DISPLAY command: " << command << endl; int flags = (reschedule ? ProcData::RESCHEDULE : 0) | (allowDefer ? ProcData::ALLOW_DEFER : 0); if (doShellCommand(command, event, &alarm, (flags | ProcData::PRE_ACTION))) return result; // display the message after the command completes // Error executing command - display the message even though it failed } if (!event.enabled()) delete win; // event is disabled - close its window else if (!win || (!win->hasDefer() && !alarm.repeatAtLogin()) || replaceReminder) { // Either there isn't already a message for this event, // or there is a repeat-at-login message with no Defer // button, which needs to be replaced with a new message, // or the caption needs to be changed from "Reminder" to "Message". if (win) win->setRecreating(); // prevent post-alarm actions delete win; (new MessageWin(event, alarm, reschedule, allowDefer))->show(); } else { // Raise the existing message window and replay any sound win->repeat(alarm); // N.B. this reschedules the alarm } break; } case KAAlarm::COMMAND: { int flags = event.commandXterm() ? ProcData::EXEC_IN_XTERM : 0; TQString command = event.cleanText(); if (event.commandScript()) { // Store the command script in a temporary file for execution kdDebug(5950) << "KAlarmApp::execAlarm(): COMMAND: (script)" << endl; TQString tmpfile = createTempScriptFile(command, false, event, alarm); if (tmpfile.isEmpty()) result = 0; else result = doShellCommand(tmpfile, event, &alarm, (flags | ProcData::TEMP_FILE)); } else { kdDebug(5950) << "KAlarmApp::execAlarm(): COMMAND: " << command << endl; result = doShellCommand(command, event, &alarm, flags); } if (reschedule) rescheduleAlarm(event, alarm, true); break; } case KAAlarm::EMAIL: { kdDebug(5950) << "KAlarmApp::execAlarm(): EMAIL to: " << event.emailAddresses(", ") << endl; TQStringList errmsgs; if (!KAMail::send(event, errmsgs, (reschedule || allowDefer))) result = 0; if (!errmsgs.isEmpty()) { // Some error occurred, although the email may have been sent successfully if (result) kdDebug(5950) << "KAlarmApp::execAlarm(): copy error: " << errmsgs[1] << endl; else kdDebug(5950) << "KAlarmApp::execAlarm(): failed: " << errmsgs[1] << endl; (new MessageWin(event, alarm.dateTime(), errmsgs))->show(); } if (reschedule) rescheduleAlarm(event, alarm, true); break; } default: return 0; } return result; } /****************************************************************************** * Execute a shell command line specified by an alarm. * If the PRE_ACTION bit of 'flags' is set, the alarm will be executed via * execAlarm() once the command completes, the execAlarm() parameters being * derived from the remaining bits in 'flags'. */ ShellProcess* KAlarmApp::doShellCommand(const TQString& command, const KAEvent& event, const KAAlarm* alarm, int flags) { kdDebug(5950) << "KAlarmApp::doShellCommand(" << command << ", " << event.id() << ")" << endl; TDEProcess::Communication comms = TDEProcess::NoCommunication; TQString cmd; TQString tmpXtermFile; if (flags & ProcData::EXEC_IN_XTERM) { // Execute the command in a terminal window. cmd = Preferences::cmdXTermCommand(); cmd.replace("%t", aboutData()->programName()); // set the terminal window title if (cmd.find("%C") >= 0) { // Execute the command from a temporary script file if (flags & ProcData::TEMP_FILE) cmd.replace("%C", command); // the command is already calling a temporary file else { tmpXtermFile = createTempScriptFile(command, true, event, *alarm); if (tmpXtermFile.isEmpty()) return 0; cmd.replace("%C", tmpXtermFile); // %C indicates where to insert the command } } else if (cmd.find("%W") >= 0) { // Execute the command from a temporary script file, // with a sleep after the command is executed tmpXtermFile = createTempScriptFile(command + TQString::fromLatin1("\nsleep 86400\n"), true, event, *alarm); if (tmpXtermFile.isEmpty()) return 0; cmd.replace("%W", tmpXtermFile); // %w indicates where to insert the command } else if (cmd.find("%w") >= 0) { // Append a sleep to the command. // Quote the command in case it contains characters such as [>|;]. TQString exec = KShellProcess::quote(command + TQString::fromLatin1("; sleep 86400")); cmd.replace("%w", exec); // %w indicates where to insert the command string } else { // Set the command to execute. // Put it in quotes in case it contains characters such as [>|;]. TQString exec = KShellProcess::quote(command); if (cmd.find("%c") >= 0) cmd.replace("%c", exec); // %c indicates where to insert the command string else cmd.append(exec); // otherwise, simply append the command string } } else { cmd = command; comms = TDEProcess::AllOutput; } ShellProcess* proc = new ShellProcess(cmd); connect(proc, TQT_SIGNAL(shellExited(ShellProcess*)), TQT_SLOT(slotCommandExited(ShellProcess*))); TQGuardedPtr logproc = 0; if (comms == TDEProcess::AllOutput && !event.logFile().isEmpty()) { // Output is to be appended to a log file. // Set up a logging process to write the command's output to. connect(proc, TQT_SIGNAL(receivedStdout(TDEProcess*,char*,int)), TQT_SLOT(slotCommandOutput(TDEProcess*,char*,int))); connect(proc, TQT_SIGNAL(receivedStderr(TDEProcess*,char*,int)), TQT_SLOT(slotCommandOutput(TDEProcess*,char*,int))); logproc = new ShellProcess(TQString::fromLatin1("cat >>%1").arg(event.logFile())); connect(logproc, TQT_SIGNAL(shellExited(ShellProcess*)), TQT_SLOT(slotLogProcExited(ShellProcess*))); logproc->start(TDEProcess::Stdin); TQCString heading; if (alarm && alarm->dateTime().isValid()) { TQString dateTime = alarm->dateTime().isDateOnly() ? TDEGlobal::locale()->formatDate(alarm->dateTime().date(), true) : TDEGlobal::locale()->formatDateTime(alarm->dateTime().dateTime()); heading.sprintf("\n******* KAlarm %s *******\n", dateTime.latin1()); } else heading = "\n******* KAlarm *******\n"; logproc->writeStdin(heading, heading.length()+1); } ProcData* pd = new ProcData(proc, logproc, new KAEvent(event), (alarm ? new KAAlarm(*alarm) : 0), flags); if (flags & ProcData::TEMP_FILE) pd->tempFiles += command; if (!tmpXtermFile.isEmpty()) pd->tempFiles += tmpXtermFile; mCommandProcesses.append(pd); if (proc->start(comms)) return proc; // Error executing command - report it kdError(5950) << "KAlarmApp::doShellCommand(): command failed to start\n"; commandErrorMsg(proc, event, alarm, flags); mCommandProcesses.remove(pd); delete pd; return 0; } /****************************************************************************** * Create a temporary script file containing the specified command string. * Reply = path of temporary file, or null string if error. */ TQString KAlarmApp::createTempScriptFile(const TQString& command, bool insertShell, const KAEvent& event, const KAAlarm& alarm) { KTempFile tmpFile(TQString(), TQString(), 0700); tmpFile.setAutoDelete(false); // don't delete file when it is destructed TQTextStream* stream = tmpFile.textStream(); if (!stream) kdError(5950) << "KAlarmApp::createTempScript(): Unable to create a temporary script file" << endl; else { if (insertShell) *stream << "#!" << ShellProcess::shellPath() << "\n"; *stream << command; tmpFile.close(); if (tmpFile.status()) kdError(5950) << "KAlarmApp::createTempScript(): Error " << tmpFile.status() << " writing to temporary script file" << endl; else return tmpFile.name(); } TQStringList errmsgs(i18n("Error creating temporary script file")); (new MessageWin(event, alarm.dateTime(), errmsgs))->show(); return TQString(); } /****************************************************************************** * Called when an executing command alarm sends output to stdout or stderr. */ void KAlarmApp::slotCommandOutput(TDEProcess* proc, char* buffer, int bufflen) { //kdDebug(5950) << "KAlarmApp::slotCommandOutput(): '" << TQCString(buffer, bufflen+1) << "'\n"; // Find this command in the command list for (TQValueList::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it) { ProcData* pd = *it; if (pd->process == proc && pd->logProcess) { pd->logProcess->writeStdin(buffer, bufflen); break; } } } /****************************************************************************** * Called when a logging process completes. */ void KAlarmApp::slotLogProcExited(ShellProcess* proc) { // Because it's held as a guarded pointer in the ProcData structure, // we don't need to set any pointers to zero. delete proc; } /****************************************************************************** * Called when a command alarm's execution completes. */ void KAlarmApp::slotCommandExited(ShellProcess* proc) { kdDebug(5950) << "KAlarmApp::slotCommandExited()\n"; // Find this command in the command list for (TQValueList::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it) { ProcData* pd = *it; if (pd->process == proc) { // Found the command if (pd->logProcess) pd->logProcess->stdinExit(); // terminate the logging process // Check its exit status if (!proc->normalExit()) { TQString errmsg = proc->errorMessage(); kdWarning(5950) << "KAlarmApp::slotCommandExited(" << pd->event->cleanText() << "): " << errmsg << endl; if (pd->messageBoxParent) { // Close the existing informational KMessageBox for this process TQObjectList* dialogs = pd->messageBoxParent->queryList("KDialogBase", 0, false, true); KDialogBase* dialog = (KDialogBase*)dialogs->getFirst(); delete dialog; delete dialogs; if (!pd->tempFile()) { errmsg += '\n'; errmsg += proc->command(); } KMessageBox::error(pd->messageBoxParent, errmsg); } else commandErrorMsg(proc, *pd->event, pd->alarm, pd->flags); } if (pd->preAction()) execAlarm(*pd->event, *pd->alarm, pd->reschedule(), pd->allowDefer(), true); mCommandProcesses.remove(it); delete pd; break; } } // If there are now no executing shell commands, quit if a quit was queued if (mPendingQuit && mCommandProcesses.isEmpty()) quitIf(mPendingQuitCode); } /****************************************************************************** * Output an error message for a shell command. */ void KAlarmApp::commandErrorMsg(const ShellProcess* proc, const KAEvent& event, const KAAlarm* alarm, int flags) { TQStringList errmsgs; if (flags & ProcData::PRE_ACTION) errmsgs += i18n("Pre-alarm action:"); else if (flags & ProcData::POST_ACTION) errmsgs += i18n("Post-alarm action:"); errmsgs += proc->errorMessage(); if (!(flags & ProcData::TEMP_FILE)) errmsgs += proc->command(); (new MessageWin(event, (alarm ? alarm->dateTime() : DateTime()), errmsgs))->show(); } /****************************************************************************** * Notes that an informational KMessageBox is displayed for this process. */ void KAlarmApp::commandMessage(ShellProcess* proc, TQWidget* parent) { // Find this command in the command list for (TQValueList::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it) { ProcData* pd = *it; if (pd->process == proc) { pd->messageBoxParent = parent; break; } } } /****************************************************************************** * Set up remaining DCOP handlers and start processing DCOP calls. */ void KAlarmApp::setUpDcop() { if (!mInitialised) { mInitialised = true; // we're now ready to handle DCOP calls Daemon::createDcopHandler(); TQTimer::singleShot(0, this, TQT_SLOT(processQueue())); // process anything already queued } } /****************************************************************************** * If this is the first time through, open the calendar file, optionally start * the alarm daemon and register with it, and set up the DCOP handler. */ bool KAlarmApp::initCheck(bool calendarOnly) { bool startdaemon; AlarmCalendar* cal = AlarmCalendar::activeCalendar(); if (!cal->isOpen()) { kdDebug(5950) << "KAlarmApp::initCheck(): opening active calendar\n"; // First time through. Open the calendar file. if (!cal->open()) return false; if (!mStartOfDay.isValid()) changeStartOfDay(); // start of day time has changed, so adjust date-only alarms /* Need to open the display calendar now, since otherwise if the daemon * immediately notifies display alarms, they will often be processed while * redisplayAlarms() is executing open() (but before open() completes), * which causes problems!! */ AlarmCalendar::displayCalendar()->open(); /* Need to open the expired alarm calendar now, since otherwise if the daemon * immediately notifies multiple alarms, the second alarm is likely to be * processed while the calendar is executing open() (but before open() completes), * which causes a hang!! */ AlarmCalendar::expiredCalendar()->open(); AlarmCalendar::expiredCalendar()->setPurgeDays(theInstance->mPrefsExpiredKeepDays); startdaemon = true; } else startdaemon = !Daemon::isRegistered(); if (!calendarOnly) { setUpDcop(); // start processing DCOP calls if (startdaemon) Daemon::start(); // make sure the alarm daemon is running } return true; } /****************************************************************************** * Convert the --time parameter string into a date/time or date value. * The parameter is in the form [[[yyyy-]mm-]dd-]hh:mm or yyyy-mm-dd. * Reply = true if successful. */ static bool convWakeTime(const TQCString& timeParam, TQDateTime& dateTime, bool& noTime) { if (timeParam.length() > 19) return false; char timeStr[20]; strcpy(timeStr, timeParam); int dt[5] = { -1, -1, -1, -1, -1 }; char* s; char* end; // Get the minute value if ((s = strchr(timeStr, ':')) == 0) noTime = true; else { noTime = false; *s++ = 0; dt[4] = strtoul(s, &end, 10); if (end == s || *end || dt[4] >= 60) return false; // Get the hour value if ((s = strrchr(timeStr, '-')) == 0) s = timeStr; else *s++ = 0; dt[3] = strtoul(s, &end, 10); if (end == s || *end || dt[3] >= 24) return false; } bool dateSet = false; if (s != timeStr) { dateSet = true; // Get the day value if ((s = strrchr(timeStr, '-')) == 0) s = timeStr; else *s++ = 0; dt[2] = strtoul(s, &end, 10); if (end == s || *end || dt[2] == 0 || dt[2] > 31) return false; if (s != timeStr) { // Get the month value if ((s = strrchr(timeStr, '-')) == 0) s = timeStr; else *s++ = 0; dt[1] = strtoul(s, &end, 10); if (end == s || *end || dt[1] == 0 || dt[1] > 12) return false; if (s != timeStr) { // Get the year value dt[0] = strtoul(timeStr, &end, 10); if (end == timeStr || *end) return false; } } } TQDate date(dt[0], dt[1], dt[2]); TQTime time(0, 0, 0); if (noTime) { // No time was specified, so the full date must have been specified if (dt[0] < 0) return false; } else { // Compile the values into a date/time structure TQDateTime now = TQDateTime::currentDateTime(); if (dt[0] < 0) date.setYMD(now.date().year(), (dt[1] < 0 ? now.date().month() : dt[1]), (dt[2] < 0 ? now.date().day() : dt[2])); time.setHMS(dt[3], dt[4], 0); if (!dateSet && time < now.time()) date = date.addDays(1); } if (!date.isValid()) return false; dateTime.setDate(date); dateTime.setTime(time); return true; } /****************************************************************************** * Convert a time interval command line parameter. * 'timeInterval' receives the count for the recurType. If 'allowMonthYear' is * false, 'timeInterval' is converted to minutes. * Reply = true if successful. */ static bool convInterval(const TQCString& timeParam, KARecurrence::Type& recurType, int& timeInterval, bool allowMonthYear) { TQCString timeString = timeParam; // Get the recurrence interval bool ok = true; uint interval = 0; bool negative = (timeString[0] == '-'); if (negative) timeString = timeString.right(1); uint length = timeString.length(); switch (timeString[length - 1]) { case 'Y': if (!allowMonthYear) ok = false; recurType = KARecurrence::ANNUAL_DATE; timeString = timeString.left(length - 1); break; case 'W': recurType = KARecurrence::WEEKLY; timeString = timeString.left(length - 1); break; case 'D': recurType = KARecurrence::DAILY; timeString = timeString.left(length - 1); break; case 'M': { int i = timeString.find('H'); if (i < 0) { if (!allowMonthYear) ok = false; recurType = KARecurrence::MONTHLY_DAY; timeString = timeString.left(length - 1); } else { recurType = KARecurrence::MINUTELY; interval = timeString.left(i).toUInt(&ok) * 60; timeString = timeString.mid(i + 1, length - i - 2); } break; } default: // should be a digit recurType = KARecurrence::MINUTELY; break; } if (ok) interval += timeString.toUInt(&ok); if (!allowMonthYear) { // Convert time interval to minutes switch (recurType) { case KARecurrence::WEEKLY: interval *= 7; // fall through to DAILY case KARecurrence::DAILY: interval *= 24*60; break; default: break; } } timeInterval = static_cast(interval); if (negative) timeInterval = -timeInterval; return ok; } KAlarmApp::ProcData::ProcData(ShellProcess* p, ShellProcess* logp, KAEvent* e, KAAlarm* a, int f) : process(p), logProcess(logp), event(e), alarm(a), messageBoxParent(0), flags(f) { } KAlarmApp::ProcData::~ProcData() { while (!tempFiles.isEmpty()) { // Delete the temporary file called by the XTerm command TQFile f(tempFiles.first()); f.remove(); tempFiles.remove(tempFiles.begin()); } delete process; delete event; delete alarm; }