kalarmapp.cpp
00001 /* 00002 * kalarmapp.cpp - the KAlarm application object 00003 * Program: kalarm 00004 * Copyright © 2001-2009 by David Jarvie <djarvie@kde.org> 00005 * 00006 * This program is free software; you can redistribute it and/or modify 00007 * it under the terms of the GNU General Public License as published by 00008 * the Free Software Foundation; either version 2 of the License, or 00009 * (at your option) any later version. 00010 * 00011 * This program is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU General Public License along 00017 * with this program; if not, write to the Free Software Foundation, Inc., 00018 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "kalarm.h" 00022 00023 #include <stdlib.h> 00024 #include <ctype.h> 00025 #include <iostream> 00026 00027 #include <tqobjectlist.h> 00028 #include <tqtimer.h> 00029 #include <tqregexp.h> 00030 #include <tqfile.h> 00031 00032 #include <kcmdlineargs.h> 00033 #include <klocale.h> 00034 #include <kstandarddirs.h> 00035 #include <kconfig.h> 00036 #include <kaboutdata.h> 00037 #include <dcopclient.h> 00038 #include <kprocess.h> 00039 #include <ktempfile.h> 00040 #include <kfileitem.h> 00041 #include <kstdguiitem.h> 00042 #include <ktrader.h> 00043 #include <kstaticdeleter.h> 00044 #include <kdebug.h> 00045 00046 #include <libkcal/calformat.h> 00047 00048 #include <kalarmd/clientinfo.h> 00049 00050 #include "alarmcalendar.h" 00051 #include "alarmlistview.h" 00052 #include "birthdaydlg.h" 00053 #include "editdlg.h" 00054 #include "daemon.h" 00055 #include "dcophandler.h" 00056 #include "functions.h" 00057 #include "kamail.h" 00058 #include "karecurrence.h" 00059 #include "mainwindow.h" 00060 #include "messagebox.h" 00061 #include "messagewin.h" 00062 #include "preferences.h" 00063 #include "prefdlg.h" 00064 #include "shellprocess.h" 00065 #include "traywindow.h" 00066 #include "kalarmapp.moc" 00067 00068 #include <netwm.h> 00069 00070 00071 static bool convWakeTime(const TQCString& timeParam, TQDateTime&, bool& noTime); 00072 static bool convInterval(const TQCString& timeParam, KARecurrence::Type&, int& timeInterval, bool allowMonthYear = false); 00073 00074 /****************************************************************************** 00075 * Find the maximum number of seconds late which a late-cancel alarm is allowed 00076 * to be. This is calculated as the alarm daemon's check interval, plus a few 00077 * seconds leeway to cater for any timing irregularities. 00078 */ 00079 static inline int maxLateness(int lateCancel) 00080 { 00081 static const int LATENESS_LEEWAY = 5; 00082 int lc = (lateCancel >= 1) ? (lateCancel - 1)*60 : 0; 00083 return Daemon::maxTimeSinceCheck() + LATENESS_LEEWAY + lc; 00084 } 00085 00086 00087 KAlarmApp* KAlarmApp::theInstance = 0; 00088 int KAlarmApp::mActiveCount = 0; 00089 int KAlarmApp::mFatalError = 0; 00090 TQString KAlarmApp::mFatalMessage; 00091 00092 00093 /****************************************************************************** 00094 * Construct the application. 00095 */ 00096 KAlarmApp::KAlarmApp() 00097 : KUniqueApplication(), 00098 mInitialised(false), 00099 mDcopHandler(new DcopHandler()), 00100 #ifdef OLD_DCOP 00101 mDcopHandlerOld(new DcopHandlerOld()), 00102 #endif 00103 mTrayWindow(0), 00104 mPendingQuit(false), 00105 mProcessingQueue(false), 00106 mCheckingSystemTray(false), 00107 mSessionClosingDown(false), 00108 mRefreshExpiredAlarms(false), 00109 mSpeechEnabled(false) 00110 { 00111 Preferences::initialise(); 00112 Preferences::connect(TQT_SIGNAL(preferencesChanged()), TQT_TQOBJECT(this), TQT_SLOT(slotPreferencesChanged())); 00113 KCal::CalFormat::setApplication(aboutData()->programName(), AlarmCalendar::icalProductId()); 00114 KARecurrence::setDefaultFeb29Type(Preferences::defaultFeb29Type()); 00115 00116 // Check if the system tray is supported by this window manager 00117 mHaveSystemTray = true; // assume yes in lieu of a test which works 00118 00119 if (AlarmCalendar::initialiseCalendars()) 00120 { 00121 connect(AlarmCalendar::expiredCalendar(), TQT_SIGNAL(purged()), TQT_SLOT(slotExpiredPurged())); 00122 00123 KConfig* config = kapp->config(); 00124 config->setGroup(TQString::fromLatin1("General")); 00125 mNoSystemTray = config->readBoolEntry(TQString::fromLatin1("NoSystemTray"), false); 00126 mSavedNoSystemTray = mNoSystemTray; 00127 mOldRunInSystemTray = wantRunInSystemTray(); 00128 mDisableAlarmsIfStopped = mOldRunInSystemTray && !mNoSystemTray && Preferences::disableAlarmsIfStopped(); 00129 mStartOfDay = Preferences::startOfDay(); 00130 if (Preferences::hasStartOfDayChanged()) 00131 mStartOfDay.setHMS(100,0,0); // start of day time has changed: flag it as invalid 00132 DateTime::setStartOfDay(mStartOfDay); 00133 mPrefsExpiredColour = Preferences::expiredColour(); 00134 mPrefsExpiredKeepDays = Preferences::expiredKeepDays(); 00135 } 00136 00137 // Check if the speech synthesis daemon is installed 00138 mSpeechEnabled = (KTrader::self()->query("DCOP/Text-to-Speech", "Name == 'KTTSD'").count() > 0); 00139 if (!mSpeechEnabled) 00140 kdDebug(5950) << "KAlarmApp::KAlarmApp(): speech synthesis disabled (KTTSD not found)" << endl; 00141 // Check if KOrganizer is installed 00142 TQString korg = TQString::fromLatin1("korganizer"); 00143 mKOrganizerEnabled = !locate("exe", korg).isNull() || !KStandardDirs::findExe(korg).isNull(); 00144 if (!mKOrganizerEnabled) 00145 kdDebug(5950) << "KAlarmApp::KAlarmApp(): KOrganizer options disabled (KOrganizer not found)" << endl; 00146 } 00147 00148 /****************************************************************************** 00149 */ 00150 KAlarmApp::~KAlarmApp() 00151 { 00152 while (!mCommandProcesses.isEmpty()) 00153 { 00154 ProcData* pd = mCommandProcesses.first(); 00155 mCommandProcesses.pop_front(); 00156 delete pd; 00157 } 00158 AlarmCalendar::terminateCalendars(); 00159 } 00160 00161 /****************************************************************************** 00162 * Return the one and only KAlarmApp instance. 00163 * If it doesn't already exist, it is created first. 00164 */ 00165 KAlarmApp* KAlarmApp::getInstance() 00166 { 00167 if (!theInstance) 00168 { 00169 theInstance = new KAlarmApp; 00170 00171 if (mFatalError) 00172 theInstance->quitFatal(); 00173 else 00174 { 00175 // This is here instead of in the constructor to avoid recursion 00176 Daemon::initialise(); // calendars must be initialised before calling this 00177 } 00178 } 00179 return theInstance; 00180 } 00181 00182 /****************************************************************************** 00183 * Restore the saved session if required. 00184 */ 00185 bool KAlarmApp::restoreSession() 00186 { 00187 if (!isRestored()) 00188 return false; 00189 if (mFatalError) 00190 { 00191 quitFatal(); 00192 return false; 00193 } 00194 00195 // Process is being restored by session management. 00196 kdDebug(5950) << "KAlarmApp::restoreSession(): Restoring\n"; 00197 ++mActiveCount; 00198 if (!initCheck(true)) // open the calendar file (needed for main windows) 00199 { 00200 --mActiveCount; 00201 quitIf(1, true); // error opening the main calendar - quit 00202 return true; 00203 } 00204 MainWindow* trayParent = 0; 00205 for (int i = 1; KMainWindow::canBeRestored(i); ++i) 00206 { 00207 TQString type = KMainWindow::classNameOfToplevel(i); 00208 if (type == TQString::fromLatin1("MainWindow")) 00209 { 00210 MainWindow* win = MainWindow::create(true); 00211 win->restore(i, false); 00212 if (win->isHiddenTrayParent()) 00213 trayParent = win; 00214 else 00215 win->show(); 00216 } 00217 else if (type == TQString::fromLatin1("MessageWin")) 00218 { 00219 MessageWin* win = new MessageWin; 00220 win->restore(i, false); 00221 if (win->isValid()) 00222 win->show(); 00223 else 00224 delete win; 00225 } 00226 } 00227 initCheck(); // register with the alarm daemon 00228 00229 // Try to display the system tray icon if it is configured to be autostarted, 00230 // or if we're in run-in-system-tray mode. 00231 if (Preferences::autostartTrayIcon() 00232 || MainWindow::count() && wantRunInSystemTray()) 00233 { 00234 displayTrayIcon(true, trayParent); 00235 // Occasionally for no obvious reason, the main main window is 00236 // shown when it should be hidden, so hide it just to be sure. 00237 if (trayParent) 00238 trayParent->hide(); 00239 } 00240 00241 --mActiveCount; 00242 quitIf(0); // quit if no windows are open 00243 return true; 00244 } 00245 00246 /****************************************************************************** 00247 * Called for a KUniqueApplication when a new instance of the application is 00248 * started. 00249 */ 00250 int KAlarmApp::newInstance() 00251 { 00252 kdDebug(5950)<<"KAlarmApp::newInstance()\n"; 00253 if (mFatalError) 00254 { 00255 quitFatal(); 00256 return 1; 00257 } 00258 ++mActiveCount; 00259 int exitCode = 0; // default = success 00260 static bool firstInstance = true; 00261 bool dontRedisplay = false; 00262 if (!firstInstance || !isRestored()) 00263 { 00264 TQString usage; 00265 KCmdLineArgs* args = KCmdLineArgs::parsedArgs(); 00266 00267 // Use a 'do' loop which is executed only once to allow easy error exits. 00268 // Errors use 'break' to skip to the end of the function. 00269 00270 // Note that DCOP handling is only set up once the command line parameters 00271 // have been checked, since we mustn't register with the alarm daemon only 00272 // to quit immediately afterwards. 00273 do 00274 { 00275 #define USAGE(message) { usage = message; break; } 00276 if (args->isSet("stop")) 00277 { 00278 // Stop the alarm daemon 00279 kdDebug(5950)<<"KAlarmApp::newInstance(): stop\n"; 00280 args->clear(); // free up memory 00281 if (!Daemon::stop()) 00282 { 00283 exitCode = 1; 00284 break; 00285 } 00286 dontRedisplay = true; // exit program if no other instances running 00287 } 00288 else 00289 if (args->isSet("reset")) 00290 { 00291 // Reset the alarm daemon, if it's running. 00292 // (If it's not running, it will reset automatically when it eventually starts.) 00293 kdDebug(5950)<<"KAlarmApp::newInstance(): reset\n"; 00294 args->clear(); // free up memory 00295 Daemon::reset(); 00296 dontRedisplay = true; // exit program if no other instances running 00297 } 00298 else 00299 if (args->isSet("tray")) 00300 { 00301 // Display only the system tray icon 00302 kdDebug(5950)<<"KAlarmApp::newInstance(): tray\n"; 00303 args->clear(); // free up memory 00304 if (!mHaveSystemTray) 00305 { 00306 exitCode = 1; 00307 break; 00308 } 00309 if (!initCheck()) // open the calendar, register with daemon 00310 { 00311 exitCode = 1; 00312 break; 00313 } 00314 if (!displayTrayIcon(true)) 00315 { 00316 exitCode = 1; 00317 break; 00318 } 00319 } 00320 else 00321 if (args->isSet("handleEvent") || args->isSet("triggerEvent") || args->isSet("cancelEvent") || args->isSet("calendarURL")) 00322 { 00323 // Display or delete the event with the specified event ID 00324 kdDebug(5950)<<"KAlarmApp::newInstance(): handle event\n"; 00325 EventFunc function = EVENT_HANDLE; 00326 int count = 0; 00327 const char* option = 0; 00328 if (args->isSet("handleEvent")) { function = EVENT_HANDLE; option = "handleEvent"; ++count; } 00329 if (args->isSet("triggerEvent")) { function = EVENT_TRIGGER; option = "triggerEvent"; ++count; } 00330 if (args->isSet("cancelEvent")) { function = EVENT_CANCEL; option = "cancelEvent"; ++count; } 00331 if (!count) 00332 USAGE(i18n("%1 requires %2, %3 or %4").arg(TQString::fromLatin1("--calendarURL")).arg(TQString::fromLatin1("--handleEvent")).arg(TQString::fromLatin1("--triggerEvent")).arg(TQString::fromLatin1("--cancelEvent"))) 00333 if (count > 1) 00334 USAGE(i18n("%1, %2, %3 mutually exclusive").arg(TQString::fromLatin1("--handleEvent")).arg(TQString::fromLatin1("--triggerEvent")).arg(TQString::fromLatin1("--cancelEvent"))); 00335 if (!initCheck(true)) // open the calendar, don't register with daemon yet 00336 { 00337 exitCode = 1; 00338 break; 00339 } 00340 if (args->isSet("calendarURL")) 00341 { 00342 TQString calendarUrl = args->getOption("calendarURL"); 00343 if (KURL(calendarUrl).url() != AlarmCalendar::activeCalendar()->urlString()) 00344 USAGE(i18n("%1: wrong calendar file").arg(TQString::fromLatin1("--calendarURL"))) 00345 } 00346 TQString eventID = args->getOption(option); 00347 args->clear(); // free up memory 00348 if (eventID.startsWith(TQString::fromLatin1("ad:"))) 00349 { 00350 // It's a notification from the alarm deamon 00351 eventID = eventID.mid(3); 00352 Daemon::queueEvent(eventID); 00353 } 00354 setUpDcop(); // start processing DCOP calls 00355 if (!handleEvent(eventID, function)) 00356 { 00357 exitCode = 1; 00358 break; 00359 } 00360 } 00361 else 00362 if (args->isSet("edit")) 00363 { 00364 TQString eventID = args->getOption("edit"); 00365 if (!initCheck()) 00366 { 00367 exitCode = 1; 00368 break; 00369 } 00370 if (!KAlarm::edit(eventID)) 00371 { 00372 USAGE(i18n("%1: Event %2 not found, or not editable").arg(TQString::fromLatin1("--edit")).arg(eventID)) 00373 exitCode = 1; 00374 break; 00375 } 00376 } 00377 else 00378 if (args->isSet("edit-new") || args->isSet("edit-new-preset")) 00379 { 00380 TQString templ; 00381 if (args->isSet("edit-new-preset")) 00382 templ = args->getOption("edit-new-preset"); 00383 if (!initCheck()) 00384 { 00385 exitCode = 1; 00386 break; 00387 } 00388 KAlarm::editNew(templ); 00389 } 00390 else 00391 if (args->isSet("file") || args->isSet("exec") || args->isSet("mail") || args->count()) 00392 { 00393 // Display a message or file, execute a command, or send an email 00394 KAEvent::Action action = KAEvent::MESSAGE; 00395 TQCString alMessage; 00396 uint alFromID = 0; 00397 EmailAddressList alAddresses; 00398 TQStringList alAttachments; 00399 TQCString alSubject; 00400 if (args->isSet("file")) 00401 { 00402 kdDebug(5950)<<"KAlarmApp::newInstance(): file\n"; 00403 if (args->isSet("exec")) 00404 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--exec")).arg(TQString::fromLatin1("--file"))) 00405 if (args->isSet("mail")) 00406 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--mail")).arg(TQString::fromLatin1("--file"))) 00407 if (args->count()) 00408 USAGE(i18n("message incompatible with %1").arg(TQString::fromLatin1("--file"))) 00409 alMessage = args->getOption("file"); 00410 action = KAEvent::FILE; 00411 } 00412 else if (args->isSet("exec")) 00413 { 00414 kdDebug(5950)<<"KAlarmApp::newInstance(): exec\n"; 00415 if (args->isSet("mail")) 00416 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--mail")).arg(TQString::fromLatin1("--exec"))) 00417 alMessage = args->getOption("exec"); 00418 int n = args->count(); 00419 for (int i = 0; i < n; ++i) 00420 { 00421 alMessage += ' '; 00422 alMessage += args->arg(i); 00423 } 00424 action = KAEvent::COMMAND; 00425 } 00426 else if (args->isSet("mail")) 00427 { 00428 kdDebug(5950)<<"KAlarmApp::newInstance(): mail\n"; 00429 if (args->isSet("subject")) 00430 alSubject = args->getOption("subject"); 00431 if (args->isSet("from-id")) 00432 alFromID = KAMail::identityUoid(args->getOption("from-id")); 00433 QCStringList params = args->getOptionList("mail"); 00434 for (QCStringList::Iterator i = params.begin(); i != params.end(); ++i) 00435 { 00436 TQString addr = TQString::fromLocal8Bit(*i); 00437 if (!KAMail::checkAddress(addr)) 00438 USAGE(i18n("%1: invalid email address").arg(TQString::fromLatin1("--mail"))) 00439 alAddresses += KCal::Person(TQString(), addr); 00440 } 00441 params = args->getOptionList("attach"); 00442 for (QCStringList::Iterator i = params.begin(); i != params.end(); ++i) 00443 alAttachments += TQString::fromLocal8Bit(*i); 00444 alMessage = args->arg(0); 00445 action = KAEvent::EMAIL; 00446 } 00447 else 00448 { 00449 kdDebug(5950)<<"KAlarmApp::newInstance(): message\n"; 00450 alMessage = args->arg(0); 00451 } 00452 00453 if (action != KAEvent::EMAIL) 00454 { 00455 if (args->isSet("subject")) 00456 USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--subject")).arg(TQString::fromLatin1("--mail"))) 00457 if (args->isSet("from-id")) 00458 USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--from-id")).arg(TQString::fromLatin1("--mail"))) 00459 if (args->isSet("attach")) 00460 USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--attach")).arg(TQString::fromLatin1("--mail"))) 00461 if (args->isSet("bcc")) 00462 USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--bcc")).arg(TQString::fromLatin1("--mail"))) 00463 } 00464 00465 bool alarmNoTime = false; 00466 TQDateTime alarmTime, endTime; 00467 TQColor bgColour = Preferences::defaultBgColour(); 00468 TQColor fgColour = Preferences::defaultFgColour(); 00469 KARecurrence recurrence; 00470 int repeatCount = 0; 00471 int repeatInterval = 0; 00472 if (args->isSet("color")) 00473 { 00474 // Background colour is specified 00475 TQCString colourText = args->getOption("color"); 00476 if (static_cast<const char*>(colourText)[0] == '0' 00477 && tolower(static_cast<const char*>(colourText)[1]) == 'x') 00478 colourText.replace(0, 2, "#"); 00479 bgColour.setNamedColor(colourText); 00480 if (!bgColour.isValid()) 00481 USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--color"))) 00482 } 00483 if (args->isSet("colorfg")) 00484 { 00485 // Foreground colour is specified 00486 TQCString colourText = args->getOption("colorfg"); 00487 if (static_cast<const char*>(colourText)[0] == '0' 00488 && tolower(static_cast<const char*>(colourText)[1]) == 'x') 00489 colourText.replace(0, 2, "#"); 00490 fgColour.setNamedColor(colourText); 00491 if (!fgColour.isValid()) 00492 USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--colorfg"))) 00493 } 00494 00495 if (args->isSet("time")) 00496 { 00497 TQCString dateTime = args->getOption("time"); 00498 if (!convWakeTime(dateTime, alarmTime, alarmNoTime)) 00499 USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--time"))) 00500 } 00501 else 00502 alarmTime = TQDateTime::currentDateTime(); 00503 00504 bool haveRecurrence = args->isSet("recurrence"); 00505 if (haveRecurrence) 00506 { 00507 if (args->isSet("login")) 00508 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--login")).arg(TQString::fromLatin1("--recurrence"))) 00509 if (args->isSet("until")) 00510 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--until")).arg(TQString::fromLatin1("--recurrence"))) 00511 TQCString rule = args->getOption("recurrence"); 00512 recurrence.set(TQString::fromLocal8Bit(static_cast<const char*>(rule))); 00513 } 00514 if (args->isSet("interval")) 00515 { 00516 // Repeat count is specified 00517 int count; 00518 if (args->isSet("login")) 00519 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--login")).arg(TQString::fromLatin1("--interval"))) 00520 bool ok; 00521 if (args->isSet("repeat")) 00522 { 00523 count = args->getOption("repeat").toInt(&ok); 00524 if (!ok || !count || count < -1 || (count < 0 && haveRecurrence)) 00525 USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--repeat"))) 00526 } 00527 else if (haveRecurrence) 00528 USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--interval")).arg(TQString::fromLatin1("--repeat"))) 00529 else if (args->isSet("until")) 00530 { 00531 count = 0; 00532 TQCString dateTime = args->getOption("until"); 00533 if (!convWakeTime(dateTime, endTime, alarmNoTime)) 00534 USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--until"))) 00535 if (endTime < alarmTime) 00536 USAGE(i18n("%1 earlier than %2").arg(TQString::fromLatin1("--until")).arg(TQString::fromLatin1("--time"))) 00537 } 00538 else 00539 count = -1; 00540 00541 // Get the recurrence interval 00542 int interval; 00543 KARecurrence::Type recurType; 00544 if (!convInterval(args->getOption("interval"), recurType, interval, !haveRecurrence) 00545 || interval < 0) 00546 USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--interval"))) 00547 if (alarmNoTime && recurType == KARecurrence::MINUTELY) 00548 USAGE(i18n("Invalid %1 parameter for date-only alarm").arg(TQString::fromLatin1("--interval"))) 00549 00550 if (haveRecurrence) 00551 { 00552 // There is a also a recurrence specified, so set up a sub-repetition 00553 int longestInterval = recurrence.longestInterval(); 00554 if (count * interval > longestInterval) 00555 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"))); 00556 repeatCount = count; 00557 repeatInterval = interval; 00558 } 00559 else 00560 { 00561 // There is no other recurrence specified, so convert the repetition 00562 // parameters into a KCal::Recurrence 00563 recurrence.set(recurType, interval, count, DateTime(alarmTime, alarmNoTime), endTime); 00564 } 00565 } 00566 else 00567 { 00568 if (args->isSet("repeat")) 00569 USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--repeat")).arg(TQString::fromLatin1("--interval"))) 00570 if (args->isSet("until")) 00571 USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--until")).arg(TQString::fromLatin1("--interval"))) 00572 } 00573 00574 TQCString audioFile; 00575 float audioVolume = -1; 00576 #ifdef WITHOUT_ARTS 00577 bool audioRepeat = false; 00578 #else 00579 bool audioRepeat = args->isSet("play-repeat"); 00580 #endif 00581 if (audioRepeat || args->isSet("play")) 00582 { 00583 // Play a sound with the alarm 00584 if (audioRepeat && args->isSet("play")) 00585 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--play")).arg(TQString::fromLatin1("--play-repeat"))) 00586 if (args->isSet("beep")) 00587 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--beep")).arg(TQString::fromLatin1(audioRepeat ? "--play-repeat" : "--play"))) 00588 if (args->isSet("speak")) 00589 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--speak")).arg(TQString::fromLatin1(audioRepeat ? "--play-repeat" : "--play"))) 00590 audioFile = args->getOption(audioRepeat ? "play-repeat" : "play"); 00591 #ifndef WITHOUT_ARTS 00592 if (args->isSet("volume")) 00593 { 00594 bool ok; 00595 int volumepc = args->getOption("volume").toInt(&ok); 00596 if (!ok || volumepc < 0 || volumepc > 100) 00597 USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--volume"))) 00598 audioVolume = static_cast<float>(volumepc) / 100; 00599 } 00600 #endif 00601 } 00602 #ifndef WITHOUT_ARTS 00603 else if (args->isSet("volume")) 00604 USAGE(i18n("%1 requires %2 or %3").arg(TQString::fromLatin1("--volume")).arg(TQString::fromLatin1("--play")).arg(TQString::fromLatin1("--play-repeat"))) 00605 #endif 00606 if (args->isSet("speak")) 00607 { 00608 if (args->isSet("beep")) 00609 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--beep")).arg(TQString::fromLatin1("--speak"))) 00610 if (!mSpeechEnabled) 00611 USAGE(i18n("%1 requires speech synthesis to be configured using KTTSD").arg(TQString::fromLatin1("--speak"))) 00612 } 00613 int reminderMinutes = 0; 00614 bool onceOnly = args->isSet("reminder-once"); 00615 if (args->isSet("reminder") || onceOnly) 00616 { 00617 // Issue a reminder alarm in advance of the main alarm 00618 if (onceOnly && args->isSet("reminder")) 00619 USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--reminder")).arg(TQString::fromLatin1("--reminder-once"))) 00620 TQString opt = onceOnly ? TQString::fromLatin1("--reminder-once") : TQString::fromLatin1("--reminder"); 00621 if (args->isSet("exec")) 00622 USAGE(i18n("%1 incompatible with %2").arg(opt).arg(TQString::fromLatin1("--exec"))) 00623 if (args->isSet("mail")) 00624 USAGE(i18n("%1 incompatible with %2").arg(opt).arg(TQString::fromLatin1("--mail"))) 00625 KARecurrence::Type recurType; 00626 TQString optval = args->getOption(onceOnly ? "reminder-once" : "reminder"); 00627 if (!convInterval(args->getOption(onceOnly ? "reminder-once" : "reminder"), recurType, reminderMinutes)) 00628 USAGE(i18n("Invalid %1 parameter").arg(opt)) 00629 if (recurType == KARecurrence::MINUTELY && alarmNoTime) 00630 USAGE(i18n("Invalid %1 parameter for date-only alarm").arg(opt)) 00631 } 00632 00633 int lateCancel = 0; 00634 if (args->isSet("late-cancel")) 00635 { 00636 KARecurrence::Type recurType; 00637 bool ok = convInterval(args->getOption("late-cancel"), recurType, lateCancel); 00638 if (!ok || lateCancel <= 0) 00639 USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("late-cancel"))) 00640 } 00641 else if (args->isSet("auto-close")) 00642 USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--auto-close")).arg(TQString::fromLatin1("--late-cancel"))) 00643 00644 int flags = KAEvent::DEFAULT_FONT; 00645 if (args->isSet("ack-confirm")) 00646 flags |= KAEvent::CONFIRM_ACK; 00647 if (args->isSet("auto-close")) 00648 flags |= KAEvent::AUTO_CLOSE; 00649 if (args->isSet("beep")) 00650 flags |= KAEvent::BEEP; 00651 if (args->isSet("speak")) 00652 flags |= KAEvent::SPEAK; 00653 if (args->isSet("korganizer")) 00654 flags |= KAEvent::COPY_KORGANIZER; 00655 if (args->isSet("disable")) 00656 flags |= KAEvent::DISABLED; 00657 if (audioRepeat) 00658 flags |= KAEvent::REPEAT_SOUND; 00659 if (args->isSet("login")) 00660 flags |= KAEvent::REPEAT_AT_LOGIN; 00661 if (args->isSet("bcc")) 00662 flags |= KAEvent::EMAIL_BCC; 00663 if (alarmNoTime) 00664 flags |= KAEvent::ANY_TIME; 00665 args->clear(); // free up memory 00666 00667 // Display or schedule the event 00668 if (!initCheck()) 00669 { 00670 exitCode = 1; 00671 break; 00672 } 00673 if (!scheduleEvent(action, alMessage, alarmTime, lateCancel, flags, bgColour, fgColour, TQFont(), audioFile, 00674 audioVolume, reminderMinutes, recurrence, repeatInterval, repeatCount, 00675 alFromID, alAddresses, alSubject, alAttachments)) 00676 { 00677 exitCode = 1; 00678 break; 00679 } 00680 } 00681 else 00682 { 00683 // No arguments - run interactively & display the main window 00684 kdDebug(5950)<<"KAlarmApp::newInstance(): interactive\n"; 00685 if (args->isSet("ack-confirm")) 00686 usage += TQString::fromLatin1("--ack-confirm "); 00687 if (args->isSet("attach")) 00688 usage += TQString::fromLatin1("--attach "); 00689 if (args->isSet("auto-close")) 00690 usage += TQString::fromLatin1("--auto-close "); 00691 if (args->isSet("bcc")) 00692 usage += TQString::fromLatin1("--bcc "); 00693 if (args->isSet("beep")) 00694 usage += TQString::fromLatin1("--beep "); 00695 if (args->isSet("color")) 00696 usage += TQString::fromLatin1("--color "); 00697 if (args->isSet("colorfg")) 00698 usage += TQString::fromLatin1("--colorfg "); 00699 if (args->isSet("disable")) 00700 usage += TQString::fromLatin1("--disable "); 00701 if (args->isSet("from-id")) 00702 usage += TQString::fromLatin1("--from-id "); 00703 if (args->isSet("korganizer")) 00704 usage += TQString::fromLatin1("--korganizer "); 00705 if (args->isSet("late-cancel")) 00706 usage += TQString::fromLatin1("--late-cancel "); 00707 if (args->isSet("login")) 00708 usage += TQString::fromLatin1("--login "); 00709 if (args->isSet("play")) 00710 usage += TQString::fromLatin1("--play "); 00711 #ifndef WITHOUT_ARTS 00712 if (args->isSet("play-repeat")) 00713 usage += TQString::fromLatin1("--play-repeat "); 00714 #endif 00715 if (args->isSet("reminder")) 00716 usage += TQString::fromLatin1("--reminder "); 00717 if (args->isSet("reminder-once")) 00718 usage += TQString::fromLatin1("--reminder-once "); 00719 if (args->isSet("speak")) 00720 usage += TQString::fromLatin1("--speak "); 00721 if (args->isSet("subject")) 00722 usage += TQString::fromLatin1("--subject "); 00723 if (args->isSet("time")) 00724 usage += TQString::fromLatin1("--time "); 00725 #ifndef WITHOUT_ARTS 00726 if (args->isSet("volume")) 00727 usage += TQString::fromLatin1("--volume "); 00728 #endif 00729 if (!usage.isEmpty()) 00730 { 00731 usage += i18n(": option(s) only valid with a message/%1/%2").arg(TQString::fromLatin1("--file")).arg(TQString::fromLatin1("--exec")); 00732 break; 00733 } 00734 00735 args->clear(); // free up memory 00736 if (!initCheck()) 00737 { 00738 exitCode = 1; 00739 break; 00740 } 00741 00742 (MainWindow::create())->show(); 00743 } 00744 } while (0); // only execute once 00745 00746 if (!usage.isEmpty()) 00747 { 00748 // Note: we can't use args->usage() since that also quits any other 00749 // running 'instances' of the program. 00750 std::cerr << usage.local8Bit().data() 00751 << i18n("\nUse --help to get a list of available command line options.\n").local8Bit().data(); 00752 exitCode = 1; 00753 } 00754 } 00755 if (firstInstance && !dontRedisplay && !exitCode) 00756 redisplayAlarms(); 00757 00758 --mActiveCount; 00759 firstInstance = false; 00760 00761 // Quit the application if this was the last/only running "instance" of the program. 00762 // Executing 'return' doesn't work very well since the program continues to 00763 // run if no windows were created. 00764 quitIf(exitCode); 00765 return exitCode; 00766 } 00767 00768 /****************************************************************************** 00769 * Quit the program, optionally only if there are no more "instances" running. 00770 */ 00771 void KAlarmApp::quitIf(int exitCode, bool force) 00772 { 00773 if (force) 00774 { 00775 // Quit regardless, except for message windows 00776 MainWindow::closeAll(); 00777 displayTrayIcon(false); 00778 if (MessageWin::instanceCount()) 00779 return; 00780 } 00781 else 00782 { 00783 // Quit only if there are no more "instances" running 00784 mPendingQuit = false; 00785 if (mActiveCount > 0 || MessageWin::instanceCount()) 00786 return; 00787 int mwcount = MainWindow::count(); 00788 MainWindow* mw = mwcount ? MainWindow::firstWindow() : 0; 00789 if (mwcount > 1 || mwcount && (!mw->isHidden() || !mw->isTrayParent())) 00790 return; 00791 // There are no windows left except perhaps a main window which is a hidden tray icon parent 00792 if (mTrayWindow) 00793 { 00794 // There is a system tray icon. 00795 // Don't exit unless the system tray doesn't seem to exist. 00796 if (checkSystemTray()) 00797 return; 00798 } 00799 if (!mDcopQueue.isEmpty() || !mCommandProcesses.isEmpty()) 00800 { 00801 // Don't quit yet if there are outstanding actions on the DCOP queue 00802 mPendingQuit = true; 00803 mPendingQuitCode = exitCode; 00804 return; 00805 } 00806 } 00807 00808 // This was the last/only running "instance" of the program, so exit completely. 00809 kdDebug(5950) << "KAlarmApp::quitIf(" << exitCode << "): quitting" << endl; 00810 BirthdayDlg::close(); 00811 exit(exitCode); 00812 } 00813 00814 /****************************************************************************** 00815 * Called when the Quit menu item is selected. 00816 * Closes the system tray window and all main windows, but does not exit the 00817 * program if other windows are still open. 00818 */ 00819 void KAlarmApp::doQuit(TQWidget* parent) 00820 { 00821 kdDebug(5950) << "KAlarmApp::doQuit()\n"; 00822 if (mDisableAlarmsIfStopped 00823 && MessageBox::warningContinueCancel(parent, KMessageBox::Cancel, 00824 i18n("Quitting will disable alarms\n(once any alarm message windows are closed)."), 00825 TQString(), KStdGuiItem::quit(), Preferences::QUIT_WARN 00826 ) != KMessageBox::Yes) 00827 return; 00828 quitIf(0, true); 00829 } 00830 00831 /****************************************************************************** 00832 * Called when the session manager is about to close down the application. 00833 */ 00834 void KAlarmApp::commitData(TQSessionManager& sm) 00835 { 00836 mSessionClosingDown = true; 00837 KUniqueApplication::commitData(sm); 00838 mSessionClosingDown = false; // reset in case shutdown is cancelled 00839 } 00840 00841 /****************************************************************************** 00842 * Display an error message for a fatal error. Prevent further actions since 00843 * the program state is unsafe. 00844 */ 00845 void KAlarmApp::displayFatalError(const TQString& message) 00846 { 00847 if (!mFatalError) 00848 { 00849 mFatalError = 1; 00850 mFatalMessage = message; 00851 if (theInstance) 00852 TQTimer::singleShot(0, theInstance, TQT_SLOT(quitFatal())); 00853 } 00854 } 00855 00856 /****************************************************************************** 00857 * Quit the program, once the fatal error message has been acknowledged. 00858 */ 00859 void KAlarmApp::quitFatal() 00860 { 00861 switch (mFatalError) 00862 { 00863 case 0: 00864 case 2: 00865 return; 00866 case 1: 00867 mFatalError = 2; 00868 KMessageBox::error(0, mFatalMessage); 00869 mFatalError = 3; 00870 // fall through to '3' 00871 case 3: 00872 if (theInstance) 00873 theInstance->quitIf(1, true); 00874 break; 00875 } 00876 TQTimer::singleShot(1000, this, TQT_SLOT(quitFatal())); 00877 } 00878 00879 /****************************************************************************** 00880 * The main processing loop for KAlarm. 00881 * All KAlarm operations involving opening or updating calendar files are called 00882 * from this loop to ensure that only one operation is active at any one time. 00883 * This precaution is necessary because KAlarm's activities are mostly 00884 * asynchronous, being in response to DCOP calls from the alarm daemon (or other 00885 * programs) or timer events, any of which can be received in the middle of 00886 * performing another operation. If a calendar file is opened or updated while 00887 * another calendar operation is in progress, the program has been observed to 00888 * hang, or the first calendar call has failed with data loss - clearly 00889 * unacceptable!! 00890 */ 00891 void KAlarmApp::processQueue() 00892 { 00893 if (mInitialised && !mProcessingQueue) 00894 { 00895 kdDebug(5950) << "KAlarmApp::processQueue()\n"; 00896 mProcessingQueue = true; 00897 00898 // Reset the alarm daemon if it's been queued 00899 KAlarm::resetDaemonIfQueued(); 00900 00901 // Process DCOP calls 00902 while (!mDcopQueue.isEmpty()) 00903 { 00904 DcopTQEntry& entry = mDcopQueue.first(); 00905 if (entry.eventId.isEmpty()) 00906 { 00907 // It's a new alarm 00908 switch (entry.function) 00909 { 00910 case EVENT_TRIGGER: 00911 execAlarm(entry.event, entry.event.firstAlarm(), false); 00912 break; 00913 case EVENT_HANDLE: 00914 KAlarm::addEvent(entry.event, 0); 00915 break; 00916 case EVENT_CANCEL: 00917 break; 00918 } 00919 } 00920 else 00921 handleEvent(entry.eventId, entry.function); 00922 mDcopQueue.pop_front(); 00923 } 00924 00925 // Purge the expired alarms calendar if it's time to do so 00926 AlarmCalendar::expiredCalendar()->purgeIfQueued(); 00927 00928 // Now that the queue has been processed, quit if a quit was queued 00929 if (mPendingQuit) 00930 quitIf(mPendingQuitCode); 00931 00932 mProcessingQueue = false; 00933 } 00934 } 00935 00936 /****************************************************************************** 00937 * Redisplay alarms which were being shown when the program last exited. 00938 * Normally, these alarms will have been displayed by session restoration, but 00939 * if the program crashed or was killed, we can redisplay them here so that 00940 * they won't be lost. 00941 */ 00942 void KAlarmApp::redisplayAlarms() 00943 { 00944 AlarmCalendar* cal = AlarmCalendar::displayCalendar(); 00945 if (cal->isOpen()) 00946 { 00947 KCal::Event::List events = cal->events(); 00948 for (KCal::Event::List::ConstIterator it = events.begin(); it != events.end(); ++it) 00949 { 00950 KCal::Event* kcalEvent = *it; 00951 KAEvent event(*kcalEvent); 00952 event.setUid(KAEvent::ACTIVE); 00953 if (!MessageWin::findEvent(event.id())) 00954 { 00955 // This event should be displayed, but currently isn't being 00956 kdDebug(5950) << "KAlarmApp::redisplayAlarms(): " << event.id() << endl; 00957 KAAlarm alarm = event.convertDisplayingAlarm(); 00958 (new MessageWin(event, alarm, false, !alarm.repeatAtLogin()))->show(); 00959 } 00960 } 00961 } 00962 } 00963 00964 /****************************************************************************** 00965 * Called when the system tray main window is closed. 00966 */ 00967 void KAlarmApp::removeWindow(TrayWindow*) 00968 { 00969 mTrayWindow = 0; 00970 quitIf(); 00971 } 00972 00973 /****************************************************************************** 00974 * Display or close the system tray icon. 00975 */ 00976 bool KAlarmApp::displayTrayIcon(bool show, MainWindow* parent) 00977 { 00978 static bool creating = false; 00979 if (show) 00980 { 00981 if (!mTrayWindow && !creating) 00982 { 00983 if (!mHaveSystemTray) 00984 return false; 00985 if (!MainWindow::count() && wantRunInSystemTray()) 00986 { 00987 creating = true; // prevent main window constructor from creating an additional tray icon 00988 parent = MainWindow::create(); 00989 creating = false; 00990 } 00991 mTrayWindow = new TrayWindow(parent ? parent : MainWindow::firstWindow()); 00992 connect(mTrayWindow, TQT_SIGNAL(deleted()), TQT_SIGNAL(trayIconToggled())); 00993 mTrayWindow->show(); 00994 emit trayIconToggled(); 00995 00996 // Set up a timer so that we can check after all events in the window system's 00997 // event queue have been processed, whether the system tray actually exists 00998 mCheckingSystemTray = true; 00999 mSavedNoSystemTray = mNoSystemTray; 01000 mNoSystemTray = false; 01001 TQTimer::singleShot(0, this, TQT_SLOT(slotSystemTrayTimer())); 01002 } 01003 } 01004 else if (mTrayWindow) 01005 { 01006 delete mTrayWindow; 01007 mTrayWindow = 0; 01008 } 01009 return true; 01010 } 01011 01012 /****************************************************************************** 01013 * Called by a timer to check whether the system tray icon has been housed in 01014 * the system tray. Because there is a delay between the system tray icon show 01015 * event and the icon being reparented by the system tray, we have to use a 01016 * timer to check whether the system tray has actually grabbed it, or whether 01017 * the system tray probably doesn't exist. 01018 */ 01019 void KAlarmApp::slotSystemTrayTimer() 01020 { 01021 mCheckingSystemTray = false; 01022 if (!checkSystemTray()) 01023 quitIf(0); // exit the application if there are no open windows 01024 } 01025 01026 /****************************************************************************** 01027 * Check whether the system tray icon has been housed in the system tray. 01028 * If the system tray doesn't seem to exist, tell the alarm daemon to notify us 01029 * of alarms regardless of whether we're running. 01030 */ 01031 bool KAlarmApp::checkSystemTray() 01032 { 01033 if (mCheckingSystemTray || !mTrayWindow) 01034 return true; 01035 if (mTrayWindow->inSystemTray() != !mSavedNoSystemTray) 01036 { 01037 kdDebug(5950) << "KAlarmApp::checkSystemTray(): changed -> " << mSavedNoSystemTray << endl; 01038 mNoSystemTray = mSavedNoSystemTray = !mSavedNoSystemTray; 01039 01040 // Store the new setting in the config file, so that if KAlarm exits and is then 01041 // next activated by the daemon to display a message, it will register with the 01042 // daemon with the correct NOTIFY type. If that happened when there was no system 01043 // tray and alarms are disabled when KAlarm is not running, registering with 01044 // NO_START_NOTIFY could result in alarms never being seen. 01045 KConfig* config = kapp->config(); 01046 config->setGroup(TQString::fromLatin1("General")); 01047 config->writeEntry(TQString::fromLatin1("NoSystemTray"), mNoSystemTray); 01048 config->sync(); 01049 01050 // Update other settings and reregister with the alarm daemon 01051 slotPreferencesChanged(); 01052 } 01053 else 01054 { 01055 kdDebug(5950) << "KAlarmApp::checkSystemTray(): no change = " << !mSavedNoSystemTray << endl; 01056 mNoSystemTray = mSavedNoSystemTray; 01057 } 01058 return !mNoSystemTray; 01059 } 01060 01061 /****************************************************************************** 01062 * Return the main window associated with the system tray icon. 01063 */ 01064 MainWindow* KAlarmApp::trayMainWindow() const 01065 { 01066 return mTrayWindow ? mTrayWindow->assocMainWindow() : 0; 01067 } 01068 01069 /****************************************************************************** 01070 * Called when KAlarm preferences have changed. 01071 */ 01072 void KAlarmApp::slotPreferencesChanged() 01073 { 01074 bool newRunInSysTray = wantRunInSystemTray(); 01075 if (newRunInSysTray != mOldRunInSystemTray) 01076 { 01077 // The system tray run mode has changed 01078 ++mActiveCount; // prevent the application from quitting 01079 MainWindow* win = mTrayWindow ? mTrayWindow->assocMainWindow() : 0; 01080 delete mTrayWindow; // remove the system tray icon if it is currently shown 01081 mTrayWindow = 0; 01082 mOldRunInSystemTray = newRunInSysTray; 01083 if (!newRunInSysTray) 01084 { 01085 if (win && win->isHidden()) 01086 delete win; 01087 } 01088 displayTrayIcon(true); 01089 --mActiveCount; 01090 } 01091 01092 bool newDisableIfStopped = wantRunInSystemTray() && !mNoSystemTray && Preferences::disableAlarmsIfStopped(); 01093 if (newDisableIfStopped != mDisableAlarmsIfStopped) 01094 { 01095 mDisableAlarmsIfStopped = newDisableIfStopped; // N.B. this setting is used by Daemon::reregister() 01096 Preferences::setQuitWarn(true); // since mode has changed, re-allow warning messages on Quit 01097 Daemon::reregister(); // re-register with the alarm daemon 01098 } 01099 01100 // Change alarm times for date-only alarms if the start of day time has changed 01101 if (Preferences::startOfDay() != mStartOfDay) 01102 changeStartOfDay(); 01103 01104 // In case the date for February 29th recurrences has changed 01105 KARecurrence::setDefaultFeb29Type(Preferences::defaultFeb29Type()); 01106 01107 if (Preferences::expiredColour() != mPrefsExpiredColour) 01108 { 01109 // The expired alarms text colour has changed 01110 mRefreshExpiredAlarms = true; 01111 mPrefsExpiredColour = Preferences::expiredColour(); 01112 } 01113 01114 if (Preferences::expiredKeepDays() != mPrefsExpiredKeepDays) 01115 { 01116 // How long expired alarms are being kept has changed. 01117 // N.B. This also adjusts for any change in start-of-day time. 01118 mPrefsExpiredKeepDays = Preferences::expiredKeepDays(); 01119 AlarmCalendar::expiredCalendar()->setPurgeDays(mPrefsExpiredKeepDays); 01120 } 01121 01122 if (mRefreshExpiredAlarms) 01123 { 01124 mRefreshExpiredAlarms = false; 01125 MainWindow::updateExpired(); 01126 } 01127 } 01128 01129 /****************************************************************************** 01130 * Change alarm times for date-only alarms after the start of day time has changed. 01131 */ 01132 void KAlarmApp::changeStartOfDay() 01133 { 01134 Daemon::notifyTimeChanged(); // tell the alarm daemon the new time 01135 TQTime sod = Preferences::startOfDay(); 01136 DateTime::setStartOfDay(sod); 01137 AlarmCalendar* cal = AlarmCalendar::activeCalendar(); 01138 if (KAEvent::adjustStartOfDay(cal->events())) 01139 cal->save(); 01140 Preferences::updateStartOfDayCheck(); // now that calendar is updated, set OK flag in config file 01141 mStartOfDay = sod; 01142 } 01143 01144 /****************************************************************************** 01145 * Called when the expired alarms calendar has been purged. 01146 * Updates the alarm list in all main windows. 01147 */ 01148 void KAlarmApp::slotExpiredPurged() 01149 { 01150 mRefreshExpiredAlarms = false; 01151 MainWindow::updateExpired(); 01152 } 01153 01154 /****************************************************************************** 01155 * Return whether the program is configured to be running in the system tray. 01156 */ 01157 bool KAlarmApp::wantRunInSystemTray() const 01158 { 01159 return Preferences::runInSystemTray() && mHaveSystemTray; 01160 } 01161 01162 /****************************************************************************** 01163 * Called to schedule a new alarm, either in response to a DCOP notification or 01164 * to command line options. 01165 * Reply = true unless there was a parameter error or an error opening calendar file. 01166 */ 01167 bool KAlarmApp::scheduleEvent(KAEvent::Action action, const TQString& text, const TQDateTime& dateTime, 01168 int lateCancel, int flags, const TQColor& bg, const TQColor& fg, const TQFont& font, 01169 const TQString& audioFile, float audioVolume, int reminderMinutes, 01170 const KARecurrence& recurrence, int repeatInterval, int repeatCount, 01171 uint mailFromID, const EmailAddressList& mailAddresses, 01172 const TQString& mailSubject, const TQStringList& mailAttachments) 01173 { 01174 kdDebug(5950) << "KAlarmApp::scheduleEvent(): " << text << endl; 01175 if (!dateTime.isValid()) 01176 return false; 01177 TQDateTime now = TQDateTime::currentDateTime(); 01178 if (lateCancel && dateTime < now.addSecs(-maxLateness(lateCancel))) 01179 return true; // alarm time was already expired too long ago 01180 TQDateTime alarmTime = dateTime; 01181 // Round down to the nearest minute to avoid scheduling being messed up 01182 alarmTime.setTime(TQTime(alarmTime.time().hour(), alarmTime.time().minute(), 0)); 01183 01184 KAEvent event(alarmTime, text, bg, fg, font, action, lateCancel, flags); 01185 if (reminderMinutes) 01186 { 01187 bool onceOnly = (reminderMinutes < 0); 01188 event.setReminder((onceOnly ? -reminderMinutes : reminderMinutes), onceOnly); 01189 } 01190 if (!audioFile.isEmpty()) 01191 event.setAudioFile(audioFile, audioVolume, -1, 0); 01192 if (!mailAddresses.isEmpty()) 01193 event.setEmail(mailFromID, mailAddresses, mailSubject, mailAttachments); 01194 event.setRecurrence(recurrence); 01195 event.setFirstRecurrence(); 01196 event.setRepetition(repeatInterval, repeatCount - 1); 01197 if (alarmTime <= now) 01198 { 01199 // Alarm is due for display already. 01200 // First execute it once without adding it to the calendar file. 01201 if (!mInitialised) 01202 mDcopQueue.append(DcopTQEntry(event, EVENT_TRIGGER)); 01203 else 01204 execAlarm(event, event.firstAlarm(), false); 01205 // If it's a recurring alarm, reschedule it for its next occurrence 01206 if (!event.recurs() 01207 || event.setNextOccurrence(now) == KAEvent::NO_OCCURRENCE) 01208 return true; 01209 // It has recurrences in the future 01210 } 01211 01212 // Queue the alarm for insertion into the calendar file 01213 mDcopQueue.append(DcopTQEntry(event)); 01214 if (mInitialised) 01215 TQTimer::singleShot(0, this, TQT_SLOT(processQueue())); 01216 return true; 01217 } 01218 01219 /****************************************************************************** 01220 * Called in response to a DCOP notification by the alarm daemon that an event 01221 * should be handled, i.e. displayed or cancelled. 01222 * Optionally display the event. Delete the event from the calendar file and 01223 * from every main window instance. 01224 */ 01225 bool KAlarmApp::handleEvent(const TQString& urlString, const TQString& eventID, EventFunc function) 01226 { 01227 kdDebug(5950) << "KAlarmApp::handleEvent(DCOP): " << eventID << endl; 01228 AlarmCalendar* cal = AlarmCalendar::activeCalendar(); // this can be called before calendars have been initialised 01229 if (cal && KURL(urlString).url() != cal->urlString()) 01230 { 01231 kdError(5950) << "KAlarmApp::handleEvent(DCOP): wrong calendar file " << urlString << endl; 01232 Daemon::eventHandled(eventID, false); 01233 return false; 01234 } 01235 mDcopQueue.append(DcopTQEntry(function, eventID)); 01236 if (mInitialised) 01237 TQTimer::singleShot(0, this, TQT_SLOT(processQueue())); 01238 return true; 01239 } 01240 01241 /****************************************************************************** 01242 * Either: 01243 * a) Display the event and then delete it if it has no outstanding repetitions. 01244 * b) Delete the event. 01245 * c) Reschedule the event for its next repetition. If none remain, delete it. 01246 * If the event is deleted, it is removed from the calendar file and from every 01247 * main window instance. 01248 */ 01249 bool KAlarmApp::handleEvent(const TQString& eventID, EventFunc function) 01250 { 01251 kdDebug(5950) << "KAlarmApp::handleEvent(): " << eventID << ", " << (function==EVENT_TRIGGER?"TRIGGER":function==EVENT_CANCEL?"CANCEL":function==EVENT_HANDLE?"HANDLE":"?") << endl; 01252 KCal::Event* kcalEvent = AlarmCalendar::activeCalendar()->event(eventID); 01253 if (!kcalEvent) 01254 { 01255 kdError(5950) << "KAlarmApp::handleEvent(): event ID not found: " << eventID << endl; 01256 Daemon::eventHandled(eventID, false); 01257 return false; 01258 } 01259 KAEvent event(*kcalEvent); 01260 switch (function) 01261 { 01262 case EVENT_CANCEL: 01263 KAlarm::deleteEvent(event, true); 01264 break; 01265 01266 case EVENT_TRIGGER: // handle it if it's due, else execute it regardless 01267 case EVENT_HANDLE: // handle it if it's due 01268 { 01269 TQDateTime now = TQDateTime::currentDateTime(); 01270 bool updateCalAndDisplay = false; 01271 bool alarmToExecuteValid = false; 01272 KAAlarm alarmToExecute; 01273 // Check all the alarms in turn. 01274 // Note that the main alarm is fetched before any other alarms. 01275 for (KAAlarm alarm = event.firstAlarm(); alarm.valid(); alarm = event.nextAlarm(alarm)) 01276 { 01277 // Check if the alarm is due yet. 01278 int secs = alarm.dateTime(true).dateTime().secsTo(now); 01279 if (secs < 0) 01280 { 01281 // This alarm is definitely not due yet 01282 kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": not due\n"; 01283 continue; 01284 } 01285 if (alarm.repeatAtLogin()) 01286 { 01287 // Alarm is to be displayed at every login. 01288 // Check if the alarm has only just been set up. 01289 // (The alarm daemon will immediately notify that it is due 01290 // since it is set up with a time in the past.) 01291 kdDebug(5950) << "KAlarmApp::handleEvent(): REPEAT_AT_LOGIN\n"; 01292 if (secs < maxLateness(1)) 01293 continue; 01294 01295 // Check if the main alarm is already being displayed. 01296 // (We don't want to display both at the same time.) 01297 if (alarmToExecute.valid()) 01298 continue; 01299 01300 // Set the time to display if it's a display alarm 01301 alarm.setTime(now); 01302 } 01303 if (alarm.lateCancel()) 01304 { 01305 // Alarm is due, and it is to be cancelled if too late. 01306 kdDebug(5950) << "KAlarmApp::handleEvent(): LATE_CANCEL\n"; 01307 bool late = false; 01308 bool cancel = false; 01309 if (alarm.dateTime().isDateOnly()) 01310 { 01311 // The alarm has no time, so cancel it if its date is too far past 01312 int maxlate = alarm.lateCancel() / 1440; // maximum lateness in days 01313 TQDateTime limit(alarm.date().addDays(maxlate + 1), Preferences::startOfDay()); 01314 if (now >= limit) 01315 { 01316 // It's too late to display the scheduled occurrence. 01317 // Find the last previous occurrence of the alarm. 01318 DateTime next; 01319 KAEvent::OccurType type = event.previousOccurrence(now, next, true); 01320 switch (type & ~KAEvent::OCCURRENCE_REPEAT) 01321 { 01322 case KAEvent::FIRST_OR_ONLY_OCCURRENCE: 01323 case KAEvent::RECURRENCE_DATE: 01324 case KAEvent::RECURRENCE_DATE_TIME: 01325 case KAEvent::LAST_RECURRENCE: 01326 limit.setDate(next.date().addDays(maxlate + 1)); 01327 limit.setTime(Preferences::startOfDay()); 01328 if (now >= limit) 01329 { 01330 if (type == KAEvent::LAST_RECURRENCE 01331 || type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event.recurs()) 01332 cancel = true; // last occurrence (and there are no repetitions) 01333 else 01334 late = true; 01335 } 01336 break; 01337 case KAEvent::NO_OCCURRENCE: 01338 default: 01339 late = true; 01340 break; 01341 } 01342 } 01343 } 01344 else 01345 { 01346 // The alarm is timed. Allow it to be the permitted amount late before cancelling it. 01347 int maxlate = maxLateness(alarm.lateCancel()); 01348 if (secs > maxlate) 01349 { 01350 // It's over the maximum interval late. 01351 // Find the most recent occurrence of the alarm. 01352 DateTime next; 01353 KAEvent::OccurType type = event.previousOccurrence(now, next, true); 01354 switch (type & ~KAEvent::OCCURRENCE_REPEAT) 01355 { 01356 case KAEvent::FIRST_OR_ONLY_OCCURRENCE: 01357 case KAEvent::RECURRENCE_DATE: 01358 case KAEvent::RECURRENCE_DATE_TIME: 01359 case KAEvent::LAST_RECURRENCE: 01360 if (next.dateTime().secsTo(now) > maxlate) 01361 { 01362 if (type == KAEvent::LAST_RECURRENCE 01363 || type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event.recurs()) 01364 cancel = true; // last occurrence (and there are no repetitions) 01365 else 01366 late = true; 01367 } 01368 break; 01369 case KAEvent::NO_OCCURRENCE: 01370 default: 01371 late = true; 01372 break; 01373 } 01374 } 01375 } 01376 01377 if (cancel) 01378 { 01379 // All recurrences are finished, so cancel the event 01380 event.setArchive(); 01381 cancelAlarm(event, alarm.type(), false); 01382 updateCalAndDisplay = true; 01383 continue; 01384 } 01385 if (late) 01386 { 01387 // The latest repetition was too long ago, so schedule the next one 01388 rescheduleAlarm(event, alarm, false); 01389 updateCalAndDisplay = true; 01390 continue; 01391 } 01392 } 01393 if (!alarmToExecuteValid) 01394 { 01395 kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": execute\n"; 01396 alarmToExecute = alarm; // note the alarm to be executed 01397 alarmToExecuteValid = true; // only trigger one alarm for the event 01398 } 01399 else 01400 kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": skip\n"; 01401 } 01402 01403 // If there is an alarm to execute, do this last after rescheduling/cancelling 01404 // any others. This ensures that the updated event is only saved once to the calendar. 01405 if (alarmToExecute.valid()) 01406 execAlarm(event, alarmToExecute, true, !alarmToExecute.repeatAtLogin()); 01407 else 01408 { 01409 if (function == EVENT_TRIGGER) 01410 { 01411 // The alarm is to be executed regardless of whether it's due. 01412 // Only trigger one alarm from the event - we don't want multiple 01413 // identical messages, for example. 01414 KAAlarm alarm = event.firstAlarm(); 01415 if (alarm.valid()) 01416 execAlarm(event, alarm, false); 01417 } 01418 if (updateCalAndDisplay) 01419 KAlarm::updateEvent(event, 0); // update the window lists and calendar file 01420 else if (function != EVENT_TRIGGER) 01421 { 01422 kdDebug(5950) << "KAlarmApp::handleEvent(): no action\n"; 01423 Daemon::eventHandled(eventID, false); 01424 } 01425 } 01426 break; 01427 } 01428 } 01429 return true; 01430 } 01431 01432 /****************************************************************************** 01433 * Called when an alarm is currently being displayed, to store a copy of the 01434 * alarm in the displaying calendar, and to reschedule it for its next repetition. 01435 * If no repetitions remain, cancel it. 01436 */ 01437 void KAlarmApp::alarmShowing(KAEvent& event, KAAlarm::Type alarmType, const DateTime& alarmTime) 01438 { 01439 kdDebug(5950) << "KAlarmApp::alarmShowing(" << event.id() << ", " << KAAlarm::debugType(alarmType) << ")\n"; 01440 KCal::Event* kcalEvent = AlarmCalendar::activeCalendar()->event(event.id()); 01441 if (!kcalEvent) 01442 kdError(5950) << "KAlarmApp::alarmShowing(): event ID not found: " << event.id() << endl; 01443 else 01444 { 01445 KAAlarm alarm = event.alarm(alarmType); 01446 if (!alarm.valid()) 01447 kdError(5950) << "KAlarmApp::alarmShowing(): alarm type not found: " << event.id() << ":" << alarmType << endl; 01448 else 01449 { 01450 // Copy the alarm to the displaying calendar in case of a crash, etc. 01451 KAEvent dispEvent; 01452 dispEvent.setDisplaying(event, alarmType, alarmTime.dateTime()); 01453 AlarmCalendar* cal = AlarmCalendar::displayCalendarOpen(); 01454 if (cal) 01455 { 01456 cal->deleteEvent(dispEvent.id()); // in case it already exists 01457 cal->addEvent(dispEvent); 01458 cal->save(); 01459 } 01460 01461 rescheduleAlarm(event, alarm, true); 01462 return; 01463 } 01464 } 01465 Daemon::eventHandled(event.id(), false); 01466 } 01467 01468 /****************************************************************************** 01469 * Called when an alarm action has completed, to perform any post-alarm actions. 01470 */ 01471 void KAlarmApp::alarmCompleted(const KAEvent& event) 01472 { 01473 if (!event.postAction().isEmpty() && ShellProcess::authorised()) 01474 { 01475 TQString command = event.postAction(); 01476 kdDebug(5950) << "KAlarmApp::alarmCompleted(" << event.id() << "): " << command << endl; 01477 doShellCommand(command, event, 0, ProcData::POST_ACTION); 01478 } 01479 } 01480 01481 /****************************************************************************** 01482 * Reschedule the alarm for its next recurrence. If none remain, delete it. 01483 * If the alarm is deleted and it is the last alarm for its event, the event is 01484 * removed from the calendar file and from every main window instance. 01485 */ 01486 void KAlarmApp::rescheduleAlarm(KAEvent& event, const KAAlarm& alarm, bool updateCalAndDisplay) 01487 { 01488 kdDebug(5950) << "KAlarmApp::rescheduleAlarm()" << endl; 01489 bool update = false; 01490 if (alarm.reminder() || alarm.deferred()) 01491 { 01492 // It's an advance warning alarm or an extra deferred alarm, so delete it 01493 event.removeExpiredAlarm(alarm.type()); 01494 update = true; 01495 } 01496 else if (alarm.repeatAtLogin()) 01497 { 01498 // Leave an alarm which repeats at every login until its main alarm is deleted 01499 if (updateCalAndDisplay && event.updated()) 01500 update = true; 01501 } 01502 else 01503 { 01504 // Reschedule the alarm for its next recurrence. 01505 KAEvent::OccurType type = event.setNextOccurrence(TQDateTime::currentDateTime()); 01506 switch (type) 01507 { 01508 case KAEvent::NO_OCCURRENCE: 01509 // All repetitions are finished, so cancel the event 01510 cancelAlarm(event, alarm.type(), updateCalAndDisplay); 01511 break; 01512 default: 01513 if (!(type & KAEvent::OCCURRENCE_REPEAT)) 01514 break; 01515 // Next occurrence is a repeat, so fall through to recurrence handling 01516 case KAEvent::RECURRENCE_DATE: 01517 case KAEvent::RECURRENCE_DATE_TIME: 01518 case KAEvent::LAST_RECURRENCE: 01519 // The event is due by now and repetitions still remain, so rewrite the event 01520 if (updateCalAndDisplay) 01521 update = true; 01522 else 01523 { 01524 event.cancelCancelledDeferral(); 01525 event.setUpdated(); // note that the calendar file needs to be updated 01526 } 01527 break; 01528 case KAEvent::FIRST_OR_ONLY_OCCURRENCE: 01529 // The first occurrence is still due?!?, so don't do anything 01530 break; 01531 } 01532 if (event.deferred()) 01533 { 01534 // Just in case there's also a deferred alarm, ensure it's removed 01535 event.removeExpiredAlarm(KAAlarm::DEFERRED_ALARM); 01536 update = true; 01537 } 01538 } 01539 if (update) 01540 { 01541 event.cancelCancelledDeferral(); 01542 KAlarm::updateEvent(event, 0); // update the window lists and calendar file 01543 } 01544 } 01545 01546 /****************************************************************************** 01547 * Delete the alarm. If it is the last alarm for its event, the event is removed 01548 * from the calendar file and from every main window instance. 01549 */ 01550 void KAlarmApp::cancelAlarm(KAEvent& event, KAAlarm::Type alarmType, bool updateCalAndDisplay) 01551 { 01552 kdDebug(5950) << "KAlarmApp::cancelAlarm()" << endl; 01553 event.cancelCancelledDeferral(); 01554 if (alarmType == KAAlarm::MAIN_ALARM && !event.displaying() && event.toBeArchived()) 01555 { 01556 // The event is being deleted. Save it in the expired calendar file first. 01557 TQString id = event.id(); // save event ID since KAlarm::addExpiredEvent() changes it 01558 KAlarm::addExpiredEvent(event); 01559 event.setEventID(id); // restore event ID 01560 } 01561 event.removeExpiredAlarm(alarmType); 01562 if (!event.alarmCount()) 01563 KAlarm::deleteEvent(event, false); 01564 else if (updateCalAndDisplay) 01565 KAlarm::updateEvent(event, 0); // update the window lists and calendar file 01566 } 01567 01568 /****************************************************************************** 01569 * Execute an alarm by displaying its message or file, or executing its command. 01570 * Reply = ShellProcess instance if a command alarm 01571 * != 0 if successful 01572 * = 0 if the alarm is disabled, or if an error message was output. 01573 */ 01574 void* KAlarmApp::execAlarm(KAEvent& event, const KAAlarm& alarm, bool reschedule, bool allowDefer, bool noPreAction) 01575 { 01576 if (!event.enabled()) 01577 { 01578 // The event is disabled. 01579 if (reschedule) 01580 rescheduleAlarm(event, alarm, true); 01581 return 0; 01582 } 01583 01584 void* result = (void*)1; 01585 event.setArchive(); 01586 switch (alarm.action()) 01587 { 01588 case KAAlarm::MESSAGE: 01589 case KAAlarm::FILE: 01590 { 01591 // Display a message or file, provided that the same event isn't already being displayed 01592 MessageWin* win = MessageWin::findEvent(event.id()); 01593 // Find if we're changing a reminder message to the real message 01594 bool reminder = (alarm.type() & KAAlarm::REMINDER_ALARM); 01595 bool replaceReminder = !reminder && win && (win->alarmType() & KAAlarm::REMINDER_ALARM); 01596 if (!reminder && !event.deferred() 01597 && (replaceReminder || !win) && !noPreAction 01598 && !event.preAction().isEmpty() && ShellProcess::authorised()) 01599 { 01600 // It's not a reminder or a deferred alarm, and there is no message window 01601 // (other than a reminder window) currently displayed for this alarm, 01602 // and we need to execute a command before displaying the new window. 01603 // Check whether the command is already being executed for this alarm. 01604 for (TQValueList<ProcData*>::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it) 01605 { 01606 ProcData* pd = *it; 01607 if (pd->event->id() == event.id() && (pd->flags & ProcData::PRE_ACTION)) 01608 { 01609 kdDebug(5950) << "KAlarmApp::execAlarm(): already executing pre-DISPLAY command" << endl; 01610 return pd->process; // already executing - don't duplicate the action 01611 } 01612 } 01613 01614 TQString command = event.preAction(); 01615 kdDebug(5950) << "KAlarmApp::execAlarm(): pre-DISPLAY command: " << command << endl; 01616 int flags = (reschedule ? ProcData::RESCHEDULE : 0) | (allowDefer ? ProcData::ALLOW_DEFER : 0); 01617 if (doShellCommand(command, event, &alarm, (flags | ProcData::PRE_ACTION))) 01618 return result; // display the message after the command completes 01619 // Error executing command - display the message even though it failed 01620 } 01621 if (!event.enabled()) 01622 delete win; // event is disabled - close its window 01623 else if (!win 01624 || !win->hasDefer() && !alarm.repeatAtLogin() 01625 || replaceReminder) 01626 { 01627 // Either there isn't already a message for this event, 01628 // or there is a repeat-at-login message with no Defer 01629 // button, which needs to be replaced with a new message, 01630 // or the caption needs to be changed from "Reminder" to "Message". 01631 if (win) 01632 win->setRecreating(); // prevent post-alarm actions 01633 delete win; 01634 (new MessageWin(event, alarm, reschedule, allowDefer))->show(); 01635 } 01636 else 01637 { 01638 // Raise the existing message window and replay any sound 01639 win->repeat(alarm); // N.B. this reschedules the alarm 01640 } 01641 break; 01642 } 01643 case KAAlarm::COMMAND: 01644 { 01645 int flags = event.commandXterm() ? ProcData::EXEC_IN_XTERM : 0; 01646 TQString command = event.cleanText(); 01647 if (event.commandScript()) 01648 { 01649 // Store the command script in a temporary file for execution 01650 kdDebug(5950) << "KAlarmApp::execAlarm(): COMMAND: (script)" << endl; 01651 TQString tmpfile = createTempScriptFile(command, false, event, alarm); 01652 if (tmpfile.isEmpty()) 01653 result = 0; 01654 else 01655 result = doShellCommand(tmpfile, event, &alarm, (flags | ProcData::TEMP_FILE)); 01656 } 01657 else 01658 { 01659 kdDebug(5950) << "KAlarmApp::execAlarm(): COMMAND: " << command << endl; 01660 result = doShellCommand(command, event, &alarm, flags); 01661 } 01662 if (reschedule) 01663 rescheduleAlarm(event, alarm, true); 01664 break; 01665 } 01666 case KAAlarm::EMAIL: 01667 { 01668 kdDebug(5950) << "KAlarmApp::execAlarm(): EMAIL to: " << event.emailAddresses(", ") << endl; 01669 TQStringList errmsgs; 01670 if (!KAMail::send(event, errmsgs, (reschedule || allowDefer))) 01671 result = 0; 01672 if (!errmsgs.isEmpty()) 01673 { 01674 // Some error occurred, although the email may have been sent successfully 01675 if (result) 01676 kdDebug(5950) << "KAlarmApp::execAlarm(): copy error: " << errmsgs[1] << endl; 01677 else 01678 kdDebug(5950) << "KAlarmApp::execAlarm(): failed: " << errmsgs[1] << endl; 01679 (new MessageWin(event, alarm.dateTime(), errmsgs))->show(); 01680 } 01681 if (reschedule) 01682 rescheduleAlarm(event, alarm, true); 01683 break; 01684 } 01685 default: 01686 return 0; 01687 } 01688 return result; 01689 } 01690 01691 /****************************************************************************** 01692 * Execute a shell command line specified by an alarm. 01693 * If the PRE_ACTION bit of 'flags' is set, the alarm will be executed via 01694 * execAlarm() once the command completes, the execAlarm() parameters being 01695 * derived from the remaining bits in 'flags'. 01696 */ 01697 ShellProcess* KAlarmApp::doShellCommand(const TQString& command, const KAEvent& event, const KAAlarm* alarm, int flags) 01698 { 01699 kdDebug(5950) << "KAlarmApp::doShellCommand(" << command << ", " << event.id() << ")" << endl; 01700 KProcess::Communication comms = KProcess::NoCommunication; 01701 TQString cmd; 01702 TQString tmpXtermFile; 01703 if (flags & ProcData::EXEC_IN_XTERM) 01704 { 01705 // Execute the command in a terminal window. 01706 cmd = Preferences::cmdXTermCommand(); 01707 cmd.replace("%t", aboutData()->programName()); // set the terminal window title 01708 if (cmd.find("%C") >= 0) 01709 { 01710 // Execute the command from a temporary script file 01711 if (flags & ProcData::TEMP_FILE) 01712 cmd.replace("%C", command); // the command is already calling a temporary file 01713 else 01714 { 01715 tmpXtermFile = createTempScriptFile(command, true, event, *alarm); 01716 if (tmpXtermFile.isEmpty()) 01717 return 0; 01718 cmd.replace("%C", tmpXtermFile); // %C indicates where to insert the command 01719 } 01720 } 01721 else if (cmd.find("%W") >= 0) 01722 { 01723 // Execute the command from a temporary script file, 01724 // with a sleep after the command is executed 01725 tmpXtermFile = createTempScriptFile(command + TQString::fromLatin1("\nsleep 86400\n"), true, event, *alarm); 01726 if (tmpXtermFile.isEmpty()) 01727 return 0; 01728 cmd.replace("%W", tmpXtermFile); // %w indicates where to insert the command 01729 } 01730 else if (cmd.find("%w") >= 0) 01731 { 01732 // Append a sleep to the command. 01733 // Quote the command in case it contains characters such as [>|;]. 01734 TQString exec = KShellProcess::quote(command + TQString::fromLatin1("; sleep 86400")); 01735 cmd.replace("%w", exec); // %w indicates where to insert the command string 01736 } 01737 else 01738 { 01739 // Set the command to execute. 01740 // Put it in quotes in case it contains characters such as [>|;]. 01741 TQString exec = KShellProcess::quote(command); 01742 if (cmd.find("%c") >= 0) 01743 cmd.replace("%c", exec); // %c indicates where to insert the command string 01744 else 01745 cmd.append(exec); // otherwise, simply append the command string 01746 } 01747 } 01748 else 01749 { 01750 cmd = command; 01751 comms = KProcess::AllOutput; 01752 } 01753 ShellProcess* proc = new ShellProcess(cmd); 01754 connect(proc, TQT_SIGNAL(shellExited(ShellProcess*)), TQT_SLOT(slotCommandExited(ShellProcess*))); 01755 TQGuardedPtr<ShellProcess> logproc = 0; 01756 if (comms == KProcess::AllOutput && !event.logFile().isEmpty()) 01757 { 01758 // Output is to be appended to a log file. 01759 // Set up a logging process to write the command's output to. 01760 connect(proc, TQT_SIGNAL(receivedStdout(KProcess*,char*,int)), TQT_SLOT(slotCommandOutput(KProcess*,char*,int))); 01761 connect(proc, TQT_SIGNAL(receivedStderr(KProcess*,char*,int)), TQT_SLOT(slotCommandOutput(KProcess*,char*,int))); 01762 logproc = new ShellProcess(TQString::fromLatin1("cat >>%1").arg(event.logFile())); 01763 connect(logproc, TQT_SIGNAL(shellExited(ShellProcess*)), TQT_SLOT(slotLogProcExited(ShellProcess*))); 01764 logproc->start(KProcess::Stdin); 01765 TQCString heading; 01766 if (alarm && alarm->dateTime().isValid()) 01767 { 01768 TQString dateTime = alarm->dateTime().isDateOnly() 01769 ? KGlobal::locale()->formatDate(alarm->dateTime().date(), true) 01770 : KGlobal::locale()->formatDateTime(alarm->dateTime().dateTime()); 01771 heading.sprintf("\n******* KAlarm %s *******\n", dateTime.latin1()); 01772 } 01773 else 01774 heading = "\n******* KAlarm *******\n"; 01775 logproc->writeStdin(heading, heading.length()+1); 01776 } 01777 ProcData* pd = new ProcData(proc, logproc, new KAEvent(event), (alarm ? new KAAlarm(*alarm) : 0), flags); 01778 if (flags & ProcData::TEMP_FILE) 01779 pd->tempFiles += command; 01780 if (!tmpXtermFile.isEmpty()) 01781 pd->tempFiles += tmpXtermFile; 01782 mCommandProcesses.append(pd); 01783 if (proc->start(comms)) 01784 return proc; 01785 01786 // Error executing command - report it 01787 kdError(5950) << "KAlarmApp::doShellCommand(): command failed to start\n"; 01788 commandErrorMsg(proc, event, alarm, flags); 01789 mCommandProcesses.remove(pd); 01790 delete pd; 01791 return 0; 01792 } 01793 01794 /****************************************************************************** 01795 * Create a temporary script file containing the specified command string. 01796 * Reply = path of temporary file, or null string if error. 01797 */ 01798 TQString KAlarmApp::createTempScriptFile(const TQString& command, bool insertShell, const KAEvent& event, const KAAlarm& alarm) 01799 { 01800 KTempFile tmpFile(TQString(), TQString(), 0700); 01801 tmpFile.setAutoDelete(false); // don't delete file when it is destructed 01802 TQTextStream* stream = tmpFile.textStream(); 01803 if (!stream) 01804 kdError(5950) << "KAlarmApp::createTempScript(): Unable to create a temporary script file" << endl; 01805 else 01806 { 01807 if (insertShell) 01808 *stream << "#!" << ShellProcess::shellPath() << "\n"; 01809 *stream << command; 01810 tmpFile.close(); 01811 if (tmpFile.status()) 01812 kdError(5950) << "KAlarmApp::createTempScript(): Error " << tmpFile.status() << " writing to temporary script file" << endl; 01813 else 01814 return tmpFile.name(); 01815 } 01816 01817 TQStringList errmsgs(i18n("Error creating temporary script file")); 01818 (new MessageWin(event, alarm.dateTime(), errmsgs))->show(); 01819 return TQString(); 01820 } 01821 01822 /****************************************************************************** 01823 * Called when an executing command alarm sends output to stdout or stderr. 01824 */ 01825 void KAlarmApp::slotCommandOutput(KProcess* proc, char* buffer, int bufflen) 01826 { 01827 //kdDebug(5950) << "KAlarmApp::slotCommandOutput(): '" << TQCString(buffer, bufflen+1) << "'\n"; 01828 // Find this command in the command list 01829 for (TQValueList<ProcData*>::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it) 01830 { 01831 ProcData* pd = *it; 01832 if (pd->process == proc && pd->logProcess) 01833 { 01834 pd->logProcess->writeStdin(buffer, bufflen); 01835 break; 01836 } 01837 } 01838 } 01839 01840 /****************************************************************************** 01841 * Called when a logging process completes. 01842 */ 01843 void KAlarmApp::slotLogProcExited(ShellProcess* proc) 01844 { 01845 // Because it's held as a guarded pointer in the ProcData structure, 01846 // we don't need to set any pointers to zero. 01847 delete proc; 01848 } 01849 01850 /****************************************************************************** 01851 * Called when a command alarm's execution completes. 01852 */ 01853 void KAlarmApp::slotCommandExited(ShellProcess* proc) 01854 { 01855 kdDebug(5950) << "KAlarmApp::slotCommandExited()\n"; 01856 // Find this command in the command list 01857 for (TQValueList<ProcData*>::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it) 01858 { 01859 ProcData* pd = *it; 01860 if (pd->process == proc) 01861 { 01862 // Found the command 01863 if (pd->logProcess) 01864 pd->logProcess->stdinExit(); // terminate the logging process 01865 01866 // Check its exit status 01867 if (!proc->normalExit()) 01868 { 01869 TQString errmsg = proc->errorMessage(); 01870 kdWarning(5950) << "KAlarmApp::slotCommandExited(" << pd->event->cleanText() << "): " << errmsg << endl; 01871 if (pd->messageBoxParent) 01872 { 01873 // Close the existing informational KMessageBox for this process 01874 TQObjectList* dialogs = pd->messageBoxParent->queryList("KDialogBase", 0, false, true); 01875 KDialogBase* dialog = (KDialogBase*)dialogs->getFirst(); 01876 delete dialog; 01877 delete dialogs; 01878 if (!pd->tempFile()) 01879 { 01880 errmsg += '\n'; 01881 errmsg += proc->command(); 01882 } 01883 KMessageBox::error(pd->messageBoxParent, errmsg); 01884 } 01885 else 01886 commandErrorMsg(proc, *pd->event, pd->alarm, pd->flags); 01887 } 01888 if (pd->preAction()) 01889 execAlarm(*pd->event, *pd->alarm, pd->reschedule(), pd->allowDefer(), true); 01890 mCommandProcesses.remove(it); 01891 delete pd; 01892 break; 01893 } 01894 } 01895 01896 // If there are now no executing shell commands, quit if a quit was queued 01897 if (mPendingQuit && mCommandProcesses.isEmpty()) 01898 quitIf(mPendingQuitCode); 01899 } 01900 01901 /****************************************************************************** 01902 * Output an error message for a shell command. 01903 */ 01904 void KAlarmApp::commandErrorMsg(const ShellProcess* proc, const KAEvent& event, const KAAlarm* alarm, int flags) 01905 { 01906 TQStringList errmsgs; 01907 if (flags & ProcData::PRE_ACTION) 01908 errmsgs += i18n("Pre-alarm action:"); 01909 else if (flags & ProcData::POST_ACTION) 01910 errmsgs += i18n("Post-alarm action:"); 01911 errmsgs += proc->errorMessage(); 01912 if (!(flags & ProcData::TEMP_FILE)) 01913 errmsgs += proc->command(); 01914 (new MessageWin(event, (alarm ? alarm->dateTime() : DateTime()), errmsgs))->show(); 01915 } 01916 01917 /****************************************************************************** 01918 * Notes that an informational KMessageBox is displayed for this process. 01919 */ 01920 void KAlarmApp::commandMessage(ShellProcess* proc, TQWidget* parent) 01921 { 01922 // Find this command in the command list 01923 for (TQValueList<ProcData*>::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it) 01924 { 01925 ProcData* pd = *it; 01926 if (pd->process == proc) 01927 { 01928 pd->messageBoxParent = parent; 01929 break; 01930 } 01931 } 01932 } 01933 01934 /****************************************************************************** 01935 * Set up remaining DCOP handlers and start processing DCOP calls. 01936 */ 01937 void KAlarmApp::setUpDcop() 01938 { 01939 if (!mInitialised) 01940 { 01941 mInitialised = true; // we're now ready to handle DCOP calls 01942 Daemon::createDcopHandler(); 01943 TQTimer::singleShot(0, this, TQT_SLOT(processQueue())); // process anything already queued 01944 } 01945 } 01946 01947 /****************************************************************************** 01948 * If this is the first time through, open the calendar file, optionally start 01949 * the alarm daemon and register with it, and set up the DCOP handler. 01950 */ 01951 bool KAlarmApp::initCheck(bool calendarOnly) 01952 { 01953 bool startdaemon; 01954 AlarmCalendar* cal = AlarmCalendar::activeCalendar(); 01955 if (!cal->isOpen()) 01956 { 01957 kdDebug(5950) << "KAlarmApp::initCheck(): opening active calendar\n"; 01958 01959 // First time through. Open the calendar file. 01960 if (!cal->open()) 01961 return false; 01962 01963 if (!mStartOfDay.isValid()) 01964 changeStartOfDay(); // start of day time has changed, so adjust date-only alarms 01965 01966 /* Need to open the display calendar now, since otherwise if the daemon 01967 * immediately notifies display alarms, they will often be processed while 01968 * redisplayAlarms() is executing open() (but before open() completes), 01969 * which causes problems!! 01970 */ 01971 AlarmCalendar::displayCalendar()->open(); 01972 01973 /* Need to open the expired alarm calendar now, since otherwise if the daemon 01974 * immediately notifies multiple alarms, the second alarm is likely to be 01975 * processed while the calendar is executing open() (but before open() completes), 01976 * which causes a hang!! 01977 */ 01978 AlarmCalendar::expiredCalendar()->open(); 01979 AlarmCalendar::expiredCalendar()->setPurgeDays(theInstance->mPrefsExpiredKeepDays); 01980 01981 startdaemon = true; 01982 } 01983 else 01984 startdaemon = !Daemon::isRegistered(); 01985 01986 if (!calendarOnly) 01987 { 01988 setUpDcop(); // start processing DCOP calls 01989 if (startdaemon) 01990 Daemon::start(); // make sure the alarm daemon is running 01991 } 01992 return true; 01993 } 01994 01995 /****************************************************************************** 01996 * Convert the --time parameter string into a date/time or date value. 01997 * The parameter is in the form [[[yyyy-]mm-]dd-]hh:mm or yyyy-mm-dd. 01998 * Reply = true if successful. 01999 */ 02000 static bool convWakeTime(const TQCString& timeParam, TQDateTime& dateTime, bool& noTime) 02001 { 02002 if (timeParam.length() > 19) 02003 return false; 02004 char timeStr[20]; 02005 strcpy(timeStr, timeParam); 02006 int dt[5] = { -1, -1, -1, -1, -1 }; 02007 char* s; 02008 char* end; 02009 // Get the minute value 02010 if ((s = strchr(timeStr, ':')) == 0) 02011 noTime = true; 02012 else 02013 { 02014 noTime = false; 02015 *s++ = 0; 02016 dt[4] = strtoul(s, &end, 10); 02017 if (end == s || *end || dt[4] >= 60) 02018 return false; 02019 // Get the hour value 02020 if ((s = strrchr(timeStr, '-')) == 0) 02021 s = timeStr; 02022 else 02023 *s++ = 0; 02024 dt[3] = strtoul(s, &end, 10); 02025 if (end == s || *end || dt[3] >= 24) 02026 return false; 02027 } 02028 bool dateSet = false; 02029 if (s != timeStr) 02030 { 02031 dateSet = true; 02032 // Get the day value 02033 if ((s = strrchr(timeStr, '-')) == 0) 02034 s = timeStr; 02035 else 02036 *s++ = 0; 02037 dt[2] = strtoul(s, &end, 10); 02038 if (end == s || *end || dt[2] == 0 || dt[2] > 31) 02039 return false; 02040 if (s != timeStr) 02041 { 02042 // Get the month value 02043 if ((s = strrchr(timeStr, '-')) == 0) 02044 s = timeStr; 02045 else 02046 *s++ = 0; 02047 dt[1] = strtoul(s, &end, 10); 02048 if (end == s || *end || dt[1] == 0 || dt[1] > 12) 02049 return false; 02050 if (s != timeStr) 02051 { 02052 // Get the year value 02053 dt[0] = strtoul(timeStr, &end, 10); 02054 if (end == timeStr || *end) 02055 return false; 02056 } 02057 } 02058 } 02059 02060 TQDate date(dt[0], dt[1], dt[2]); 02061 TQTime time(0, 0, 0); 02062 if (noTime) 02063 { 02064 // No time was specified, so the full date must have been specified 02065 if (dt[0] < 0) 02066 return false; 02067 } 02068 else 02069 { 02070 // Compile the values into a date/time structure 02071 TQDateTime now = TQDateTime::currentDateTime(); 02072 if (dt[0] < 0) 02073 date.setYMD(now.date().year(), 02074 (dt[1] < 0 ? now.date().month() : dt[1]), 02075 (dt[2] < 0 ? now.date().day() : dt[2])); 02076 time.setHMS(dt[3], dt[4], 0); 02077 if (!dateSet && time < now.time()) 02078 date = date.addDays(1); 02079 } 02080 if (!date.isValid()) 02081 return false; 02082 dateTime.setDate(date); 02083 dateTime.setTime(time); 02084 return true; 02085 } 02086 02087 /****************************************************************************** 02088 * Convert a time interval command line parameter. 02089 * 'timeInterval' receives the count for the recurType. If 'allowMonthYear' is 02090 * false, 'timeInterval' is converted to minutes. 02091 * Reply = true if successful. 02092 */ 02093 static bool convInterval(const TQCString& timeParam, KARecurrence::Type& recurType, int& timeInterval, bool allowMonthYear) 02094 { 02095 TQCString timeString = timeParam; 02096 // Get the recurrence interval 02097 bool ok = true; 02098 uint interval = 0; 02099 bool negative = (timeString[0] == '-'); 02100 if (negative) 02101 timeString = timeString.right(1); 02102 uint length = timeString.length(); 02103 switch (timeString[length - 1]) 02104 { 02105 case 'Y': 02106 if (!allowMonthYear) 02107 ok = false; 02108 recurType = KARecurrence::ANNUAL_DATE; 02109 timeString = timeString.left(length - 1); 02110 break; 02111 case 'W': 02112 recurType = KARecurrence::WEEKLY; 02113 timeString = timeString.left(length - 1); 02114 break; 02115 case 'D': 02116 recurType = KARecurrence::DAILY; 02117 timeString = timeString.left(length - 1); 02118 break; 02119 case 'M': 02120 { 02121 int i = timeString.find('H'); 02122 if (i < 0) 02123 { 02124 if (!allowMonthYear) 02125 ok = false; 02126 recurType = KARecurrence::MONTHLY_DAY; 02127 timeString = timeString.left(length - 1); 02128 } 02129 else 02130 { 02131 recurType = KARecurrence::MINUTELY; 02132 interval = timeString.left(i).toUInt(&ok) * 60; 02133 timeString = timeString.mid(i + 1, length - i - 2); 02134 } 02135 break; 02136 } 02137 default: // should be a digit 02138 recurType = KARecurrence::MINUTELY; 02139 break; 02140 } 02141 if (ok) 02142 interval += timeString.toUInt(&ok); 02143 if (!allowMonthYear) 02144 { 02145 // Convert time interval to minutes 02146 switch (recurType) 02147 { 02148 case KARecurrence::WEEKLY: 02149 interval *= 7; 02150 // fall through to DAILY 02151 case KARecurrence::DAILY: 02152 interval *= 24*60; 02153 break; 02154 default: 02155 break; 02156 } 02157 } 02158 timeInterval = static_cast<int>(interval); 02159 if (negative) 02160 timeInterval = -timeInterval; 02161 return ok; 02162 } 02163 02164 KAlarmApp::ProcData::ProcData(ShellProcess* p, ShellProcess* logp, KAEvent* e, KAAlarm* a, int f) 02165 : process(p), 02166 logProcess(logp), 02167 event(e), 02168 alarm(a), 02169 messageBoxParent(0), 02170 flags(f) 02171 { } 02172 02173 KAlarmApp::ProcData::~ProcData() 02174 { 02175 while (!tempFiles.isEmpty()) 02176 { 02177 // Delete the temporary file called by the XTerm command 02178 TQFile f(tempFiles.first()); 02179 f.remove(); 02180 tempFiles.remove(tempFiles.begin()); 02181 } 02182 delete process; 02183 delete event; 02184 delete alarm; 02185 }