kalarm

kalarmapp.cpp
1 /*
2  * kalarmapp.cpp - the KAlarm application object
3  * Program: kalarm
4  * Copyright © 2001-2009 by David Jarvie <djarvie@kde.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20 
21 #include "kalarm.h"
22 
23 #include <stdlib.h>
24 #include <ctype.h>
25 #include <iostream>
26 
27 #include <tqobjectlist.h>
28 #include <tqtimer.h>
29 #include <tqregexp.h>
30 #include <tqfile.h>
31 
32 #include <tdecmdlineargs.h>
33 #include <tdelocale.h>
34 #include <kstandarddirs.h>
35 #include <tdeconfig.h>
36 #include <tdeaboutdata.h>
37 #include <dcopclient.h>
38 #include <tdeprocess.h>
39 #include <tdetempfile.h>
40 #include <tdefileitem.h>
41 #include <kstdguiitem.h>
42 #include <ktrader.h>
43 #include <kstaticdeleter.h>
44 #include <kdebug.h>
45 
46 #include <libkcal/calformat.h>
47 
48 #include <kalarmd/clientinfo.h>
49 
50 #include "alarmcalendar.h"
51 #include "alarmlistview.h"
52 #include "birthdaydlg.h"
53 #include "editdlg.h"
54 #include "daemon.h"
55 #include "dcophandler.h"
56 #include "functions.h"
57 #include "kamail.h"
58 #include "karecurrence.h"
59 #include "mainwindow.h"
60 #include "messagebox.h"
61 #include "messagewin.h"
62 #include "preferences.h"
63 #include "prefdlg.h"
64 #include "shellprocess.h"
65 #include "traywindow.h"
66 #include "kalarmapp.moc"
67 
68 #include <netwm.h>
69 
70 
71 static bool convWakeTime(const TQCString& timeParam, TQDateTime&, bool& noTime);
72 static bool convInterval(const TQCString& timeParam, KARecurrence::Type&, int& timeInterval, bool allowMonthYear = false);
73 
74 /******************************************************************************
75 * Find the maximum number of seconds late which a late-cancel alarm is allowed
76 * to be. This is calculated as the alarm daemon's check interval, plus a few
77 * seconds leeway to cater for any timing irregularities.
78 */
79 static inline int maxLateness(int lateCancel)
80 {
81  static const int LATENESS_LEEWAY = 5;
82  int lc = (lateCancel >= 1) ? (lateCancel - 1)*60 : 0;
83  return Daemon::maxTimeSinceCheck() + LATENESS_LEEWAY + lc;
84 }
85 
86 
87 KAlarmApp* KAlarmApp::theInstance = 0;
88 int KAlarmApp::mActiveCount = 0;
89 int KAlarmApp::mFatalError = 0;
90 TQString KAlarmApp::mFatalMessage;
91 
92 
93 /******************************************************************************
94 * Construct the application.
95 */
96 KAlarmApp::KAlarmApp()
97  : TDEUniqueApplication(),
98  mInitialised(false),
99  mDcopHandler(new DcopHandler()),
100 #ifdef OLD_DCOP
101  mDcopHandlerOld(new DcopHandlerOld()),
102 #endif
103  mTrayWindow(0),
104  mPendingQuit(false),
105  mProcessingQueue(false),
106  mCheckingSystemTray(false),
107  mSessionClosingDown(false),
108  mRefreshExpiredAlarms(false),
109  mSpeechEnabled(false)
110 {
111  Preferences::initialise();
112  Preferences::connect(TQ_SIGNAL(preferencesChanged()), this, TQ_SLOT(slotPreferencesChanged()));
113  KCal::CalFormat::setApplication(aboutData()->programName(), AlarmCalendar::icalProductId());
114  KARecurrence::setDefaultFeb29Type(Preferences::defaultFeb29Type());
115 
116  // Check if the system tray is supported by this window manager
117  mHaveSystemTray = true; // assume yes in lieu of a test which works
118 
119  if (AlarmCalendar::initialiseCalendars())
120  {
121  connect(AlarmCalendar::expiredCalendar(), TQ_SIGNAL(purged()), TQ_SLOT(slotExpiredPurged()));
122 
123  TDEConfig* config = kapp->config();
124  config->setGroup(TQString::fromLatin1("General"));
125  mNoSystemTray = config->readBoolEntry(TQString::fromLatin1("NoSystemTray"), false);
126  mSavedNoSystemTray = mNoSystemTray;
127  mOldRunInSystemTray = wantRunInSystemTray();
128  mDisableAlarmsIfStopped = mOldRunInSystemTray && !mNoSystemTray && Preferences::disableAlarmsIfStopped();
129  mStartOfDay = Preferences::startOfDay();
130  if (Preferences::hasStartOfDayChanged())
131  mStartOfDay.setHMS(100,0,0); // start of day time has changed: flag it as invalid
132  DateTime::setStartOfDay(mStartOfDay);
133  mPrefsExpiredColour = Preferences::expiredColour();
134  mPrefsExpiredKeepDays = Preferences::expiredKeepDays();
135  }
136 
137  // Check if the speech synthesis daemon is installed
138  mSpeechEnabled = (TDETrader::self()->query("DCOP/Text-to-Speech", "Name == 'KTTSD'").count() > 0);
139  if (!mSpeechEnabled)
140  kdDebug(5950) << "KAlarmApp::KAlarmApp(): speech synthesis disabled (KTTSD not found)" << endl;
141  // Check if KOrganizer is installed
142  TQString korg = TQString::fromLatin1("korganizer");
143  mKOrganizerEnabled = !locate("exe", korg).isNull() || !TDEStandardDirs::findExe(korg).isNull();
144  if (!mKOrganizerEnabled)
145  kdDebug(5950) << "KAlarmApp::KAlarmApp(): KOrganizer options disabled (KOrganizer not found)" << endl;
146 }
147 
148 /******************************************************************************
149 */
150 KAlarmApp::~KAlarmApp()
151 {
152  while (!mCommandProcesses.isEmpty())
153  {
154  ProcData* pd = mCommandProcesses.first();
155  mCommandProcesses.pop_front();
156  delete pd;
157  }
158  AlarmCalendar::terminateCalendars();
159 }
160 
161 /******************************************************************************
162 * Return the one and only KAlarmApp instance.
163 * If it doesn't already exist, it is created first.
164 */
165 KAlarmApp* KAlarmApp::getInstance()
166 {
167  if (!theInstance)
168  {
169  theInstance = new KAlarmApp;
170 
171  if (mFatalError)
172  theInstance->quitFatal();
173  else
174  {
175  // This is here instead of in the constructor to avoid recursion
176  Daemon::initialise(); // calendars must be initialised before calling this
177  }
178  }
179  return theInstance;
180 }
181 
182 /******************************************************************************
183 * Restore the saved session if required.
184 */
185 bool KAlarmApp::restoreSession()
186 {
187  if (!isRestored())
188  return false;
189  if (mFatalError)
190  {
191  quitFatal();
192  return false;
193  }
194 
195  // Process is being restored by session management.
196  kdDebug(5950) << "KAlarmApp::restoreSession(): Restoring\n";
197  ++mActiveCount;
198  if (!initCheck(true)) // open the calendar file (needed for main windows)
199  {
200  --mActiveCount;
201  quitIf(1, true); // error opening the main calendar - quit
202  return true;
203  }
204  MainWindow* trayParent = 0;
205  for (int i = 1; TDEMainWindow::canBeRestored(i); ++i)
206  {
207  TQString type = TDEMainWindow::classNameOfToplevel(i);
208  if (type == TQString::fromLatin1("MainWindow"))
209  {
210  MainWindow* win = MainWindow::create(true);
211  win->restore(i, false);
212  if (win->isHiddenTrayParent())
213  trayParent = win;
214  else
215  win->show();
216  }
217  else if (type == TQString::fromLatin1("MessageWin"))
218  {
219  MessageWin* win = new MessageWin;
220  win->restore(i, false);
221  if (win->isValid())
222  win->show();
223  else
224  delete win;
225  }
226  }
227  initCheck(); // register with the alarm daemon
228 
229  // Try to display the system tray icon if it is configured to be autostarted,
230  // or if we're in run-in-system-tray mode.
231  if (Preferences::autostartTrayIcon()
232  || (MainWindow::count() && wantRunInSystemTray()))
233  {
234  displayTrayIcon(true, trayParent);
235  // Occasionally for no obvious reason, the main main window is
236  // shown when it should be hidden, so hide it just to be sure.
237  if (trayParent)
238  trayParent->hide();
239  }
240 
241  --mActiveCount;
242  quitIf(0); // quit if no windows are open
243  return true;
244 }
245 
246 /******************************************************************************
247 * Called for a TDEUniqueApplication when a new instance of the application is
248 * started.
249 */
250 int KAlarmApp::newInstance()
251 {
252  kdDebug(5950)<<"KAlarmApp::newInstance()\n";
253  if (mFatalError)
254  {
255  quitFatal();
256  return 1;
257  }
258  ++mActiveCount;
259  int exitCode = 0; // default = success
260  static bool firstInstance = true;
261  bool dontRedisplay = false;
262  if (!firstInstance || !isRestored())
263  {
264  TQString usage;
265  TDECmdLineArgs* args = TDECmdLineArgs::parsedArgs();
266 
267  // Use a 'do' loop which is executed only once to allow easy error exits.
268  // Errors use 'break' to skip to the end of the function.
269 
270  // Note that DCOP handling is only set up once the command line parameters
271  // have been checked, since we mustn't register with the alarm daemon only
272  // to quit immediately afterwards.
273  do
274  {
275  #define USAGE(message) { usage = message; break; }
276  if (args->isSet("stop"))
277  {
278  // Stop the alarm daemon
279  kdDebug(5950)<<"KAlarmApp::newInstance(): stop\n";
280  args->clear(); // free up memory
281  if (!Daemon::stop())
282  {
283  exitCode = 1;
284  break;
285  }
286  dontRedisplay = true; // exit program if no other instances running
287  }
288  else
289  if (args->isSet("reset"))
290  {
291  // Reset the alarm daemon, if it's running.
292  // (If it's not running, it will reset automatically when it eventually starts.)
293  kdDebug(5950)<<"KAlarmApp::newInstance(): reset\n";
294  args->clear(); // free up memory
295  Daemon::reset();
296  dontRedisplay = true; // exit program if no other instances running
297  }
298  else
299  if (args->isSet("tray"))
300  {
301  // Display only the system tray icon
302  kdDebug(5950)<<"KAlarmApp::newInstance(): tray\n";
303  args->clear(); // free up memory
304  if (!mHaveSystemTray)
305  {
306  exitCode = 1;
307  break;
308  }
309  if (!initCheck()) // open the calendar, register with daemon
310  {
311  exitCode = 1;
312  break;
313  }
314  if (!displayTrayIcon(true))
315  {
316  exitCode = 1;
317  break;
318  }
319  }
320  else
321  if (args->isSet("handleEvent") || args->isSet("triggerEvent") || args->isSet("cancelEvent") || args->isSet("calendarURL"))
322  {
323  // Display or delete the event with the specified event ID
324  kdDebug(5950)<<"KAlarmApp::newInstance(): handle event\n";
325  EventFunc function = EVENT_HANDLE;
326  int count = 0;
327  const char* option = 0;
328  if (args->isSet("handleEvent")) { function = EVENT_HANDLE; option = "handleEvent"; ++count; }
329  if (args->isSet("triggerEvent")) { function = EVENT_TRIGGER; option = "triggerEvent"; ++count; }
330  if (args->isSet("cancelEvent")) { function = EVENT_CANCEL; option = "cancelEvent"; ++count; }
331  if (!count)
332  USAGE(i18n("%1 requires %2, %3 or %4").arg(TQString::fromLatin1("--calendarURL")).arg(TQString::fromLatin1("--handleEvent")).arg(TQString::fromLatin1("--triggerEvent")).arg(TQString::fromLatin1("--cancelEvent")))
333  if (count > 1)
334  USAGE(i18n("%1, %2, %3 mutually exclusive").arg(TQString::fromLatin1("--handleEvent")).arg(TQString::fromLatin1("--triggerEvent")).arg(TQString::fromLatin1("--cancelEvent")));
335  if (!initCheck(true)) // open the calendar, don't register with daemon yet
336  {
337  exitCode = 1;
338  break;
339  }
340  if (args->isSet("calendarURL"))
341  {
342  TQString calendarUrl = args->getOption("calendarURL");
343  if (KURL(calendarUrl).url() != AlarmCalendar::activeCalendar()->urlString())
344  USAGE(i18n("%1: wrong calendar file").arg(TQString::fromLatin1("--calendarURL")))
345  }
346  TQString eventID = args->getOption(option);
347  args->clear(); // free up memory
348  if (eventID.startsWith(TQString::fromLatin1("ad:")))
349  {
350  // It's a notification from the alarm deamon
351  eventID = eventID.mid(3);
352  Daemon::queueEvent(eventID);
353  }
354  setUpDcop(); // start processing DCOP calls
355  if (!handleEvent(eventID, function))
356  {
357  exitCode = 1;
358  break;
359  }
360  }
361  else
362  if (args->isSet("edit"))
363  {
364  TQString eventID = args->getOption("edit");
365  if (!initCheck())
366  {
367  exitCode = 1;
368  break;
369  }
370  if (!KAlarm::edit(eventID))
371  {
372  USAGE(i18n("%1: Event %2 not found, or not editable").arg(TQString::fromLatin1("--edit")).arg(eventID))
373  exitCode = 1;
374  break;
375  }
376  }
377  else
378  if (args->isSet("edit-new") || args->isSet("edit-new-preset"))
379  {
380  TQString templ;
381  if (args->isSet("edit-new-preset"))
382  templ = args->getOption("edit-new-preset");
383  if (!initCheck())
384  {
385  exitCode = 1;
386  break;
387  }
388  KAlarm::editNew(templ);
389  }
390  else
391  if (args->isSet("file") || args->isSet("exec") || args->isSet("mail") || args->count())
392  {
393  // Display a message or file, execute a command, or send an email
394  KAEvent::Action action = KAEvent::MESSAGE;
395  TQCString alMessage;
396  uint alFromID = 0;
397  EmailAddressList alAddresses;
398  TQStringList alAttachments;
399  TQCString alSubject;
400  if (args->isSet("file"))
401  {
402  kdDebug(5950)<<"KAlarmApp::newInstance(): file\n";
403  if (args->isSet("exec"))
404  USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--exec")).arg(TQString::fromLatin1("--file")))
405  if (args->isSet("mail"))
406  USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--mail")).arg(TQString::fromLatin1("--file")))
407  if (args->count())
408  USAGE(i18n("message incompatible with %1").arg(TQString::fromLatin1("--file")))
409  alMessage = args->getOption("file");
410  action = KAEvent::FILE;
411  }
412  else if (args->isSet("exec"))
413  {
414  kdDebug(5950)<<"KAlarmApp::newInstance(): exec\n";
415  if (args->isSet("mail"))
416  USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--mail")).arg(TQString::fromLatin1("--exec")))
417  alMessage = args->getOption("exec");
418  int n = args->count();
419  for (int i = 0; i < n; ++i)
420  {
421  alMessage += ' ';
422  alMessage += args->arg(i);
423  }
424  action = KAEvent::COMMAND;
425  }
426  else if (args->isSet("mail"))
427  {
428  kdDebug(5950)<<"KAlarmApp::newInstance(): mail\n";
429  if (args->isSet("subject"))
430  alSubject = args->getOption("subject");
431  if (args->isSet("from-id"))
432  alFromID = KAMail::identityUoid(args->getOption("from-id"));
433  QCStringList params = args->getOptionList("mail");
434  for (QCStringList::Iterator i = params.begin(); i != params.end(); ++i)
435  {
436  TQString addr = TQString::fromLocal8Bit(*i);
437  if (!KAMail::checkAddress(addr))
438  USAGE(i18n("%1: invalid email address").arg(TQString::fromLatin1("--mail")))
439  alAddresses += KCal::Person(TQString(), addr);
440  }
441  params = args->getOptionList("attach");
442  for (QCStringList::Iterator i = params.begin(); i != params.end(); ++i)
443  alAttachments += TQString::fromLocal8Bit(*i);
444  alMessage = args->arg(0);
445  action = KAEvent::EMAIL;
446  }
447  else
448  {
449  kdDebug(5950)<<"KAlarmApp::newInstance(): message\n";
450  alMessage = args->arg(0);
451  }
452 
453  if (action != KAEvent::EMAIL)
454  {
455  if (args->isSet("subject"))
456  USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--subject")).arg(TQString::fromLatin1("--mail")))
457  if (args->isSet("from-id"))
458  USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--from-id")).arg(TQString::fromLatin1("--mail")))
459  if (args->isSet("attach"))
460  USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--attach")).arg(TQString::fromLatin1("--mail")))
461  if (args->isSet("bcc"))
462  USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--bcc")).arg(TQString::fromLatin1("--mail")))
463  }
464 
465  bool alarmNoTime = false;
466  TQDateTime alarmTime, endTime;
467  TQColor bgColour = Preferences::defaultBgColour();
468  TQColor fgColour = Preferences::defaultFgColour();
469  KARecurrence recurrence;
470  int repeatCount = 0;
471  int repeatInterval = 0;
472  if (args->isSet("color"))
473  {
474  // Background colour is specified
475  TQCString colourText = args->getOption("color");
476  if (static_cast<const char*>(colourText)[0] == '0'
477  && tolower(static_cast<const char*>(colourText)[1]) == 'x')
478  colourText.replace(0, 2, "#");
479  bgColour.setNamedColor(colourText);
480  if (!bgColour.isValid())
481  USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--color")))
482  }
483  if (args->isSet("colorfg"))
484  {
485  // Foreground colour is specified
486  TQCString colourText = args->getOption("colorfg");
487  if (static_cast<const char*>(colourText)[0] == '0'
488  && tolower(static_cast<const char*>(colourText)[1]) == 'x')
489  colourText.replace(0, 2, "#");
490  fgColour.setNamedColor(colourText);
491  if (!fgColour.isValid())
492  USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--colorfg")))
493  }
494 
495  if (args->isSet("time"))
496  {
497  TQCString dateTime = args->getOption("time");
498  if (!convWakeTime(dateTime, alarmTime, alarmNoTime))
499  USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--time")))
500  }
501  else
502  alarmTime = TQDateTime::currentDateTime();
503 
504  bool haveRecurrence = args->isSet("recurrence");
505  if (haveRecurrence)
506  {
507  if (args->isSet("login"))
508  USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--login")).arg(TQString::fromLatin1("--recurrence")))
509  if (args->isSet("until"))
510  USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--until")).arg(TQString::fromLatin1("--recurrence")))
511  TQCString rule = args->getOption("recurrence");
512  recurrence.set(TQString::fromLocal8Bit(static_cast<const char*>(rule)));
513  }
514  if (args->isSet("interval"))
515  {
516  // Repeat count is specified
517  int count;
518  if (args->isSet("login"))
519  USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--login")).arg(TQString::fromLatin1("--interval")))
520  bool ok;
521  if (args->isSet("repeat"))
522  {
523  count = args->getOption("repeat").toInt(&ok);
524  if (!ok || !count || count < -1 || (count < 0 && haveRecurrence))
525  USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--repeat")))
526  }
527  else if (haveRecurrence)
528  USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--interval")).arg(TQString::fromLatin1("--repeat")))
529  else if (args->isSet("until"))
530  {
531  count = 0;
532  TQCString dateTime = args->getOption("until");
533  if (!convWakeTime(dateTime, endTime, alarmNoTime))
534  USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--until")))
535  if (endTime < alarmTime)
536  USAGE(i18n("%1 earlier than %2").arg(TQString::fromLatin1("--until")).arg(TQString::fromLatin1("--time")))
537  }
538  else
539  count = -1;
540 
541  // Get the recurrence interval
542  int interval;
543  KARecurrence::Type recurType;
544  if (!convInterval(args->getOption("interval"), recurType, interval, !haveRecurrence)
545  || interval < 0)
546  USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--interval")))
547  if (alarmNoTime && recurType == KARecurrence::MINUTELY)
548  USAGE(i18n("Invalid %1 parameter for date-only alarm").arg(TQString::fromLatin1("--interval")))
549 
550  if (haveRecurrence)
551  {
552  // There is a also a recurrence specified, so set up a sub-repetition
553  int longestInterval = recurrence.longestInterval();
554  if (count * interval > longestInterval)
555  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")));
556  repeatCount = count;
557  repeatInterval = interval;
558  }
559  else
560  {
561  // There is no other recurrence specified, so convert the repetition
562  // parameters into a KCal::Recurrence
563  recurrence.set(recurType, interval, count, DateTime(alarmTime, alarmNoTime), endTime);
564  }
565  }
566  else
567  {
568  if (args->isSet("repeat"))
569  USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--repeat")).arg(TQString::fromLatin1("--interval")))
570  if (args->isSet("until"))
571  USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--until")).arg(TQString::fromLatin1("--interval")))
572  }
573 
574  TQCString audioFile;
575  float audioVolume = -1;
576 #ifdef WITHOUT_ARTS
577  bool audioRepeat = false;
578 #else
579  bool audioRepeat = args->isSet("play-repeat");
580 #endif
581  if (audioRepeat || args->isSet("play"))
582  {
583  // Play a sound with the alarm
584  if (audioRepeat && args->isSet("play"))
585  USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--play")).arg(TQString::fromLatin1("--play-repeat")))
586  if (args->isSet("beep"))
587  USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--beep")).arg(TQString::fromLatin1(audioRepeat ? "--play-repeat" : "--play")))
588  if (args->isSet("speak"))
589  USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--speak")).arg(TQString::fromLatin1(audioRepeat ? "--play-repeat" : "--play")))
590  audioFile = args->getOption(audioRepeat ? "play-repeat" : "play");
591 #ifndef WITHOUT_ARTS
592  if (args->isSet("volume"))
593  {
594  bool ok;
595  int volumepc = args->getOption("volume").toInt(&ok);
596  if (!ok || volumepc < 0 || volumepc > 100)
597  USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("--volume")))
598  audioVolume = static_cast<float>(volumepc) / 100;
599  }
600 #endif
601  }
602 #ifndef WITHOUT_ARTS
603  else if (args->isSet("volume"))
604  USAGE(i18n("%1 requires %2 or %3").arg(TQString::fromLatin1("--volume")).arg(TQString::fromLatin1("--play")).arg(TQString::fromLatin1("--play-repeat")))
605 #endif
606  if (args->isSet("speak"))
607  {
608  if (args->isSet("beep"))
609  USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--beep")).arg(TQString::fromLatin1("--speak")))
610  if (!mSpeechEnabled)
611  USAGE(i18n("%1 requires speech synthesis to be configured using KTTSD").arg(TQString::fromLatin1("--speak")))
612  }
613  int reminderMinutes = 0;
614  bool onceOnly = args->isSet("reminder-once");
615  if (args->isSet("reminder") || onceOnly)
616  {
617  // Issue a reminder alarm in advance of the main alarm
618  if (onceOnly && args->isSet("reminder"))
619  USAGE(i18n("%1 incompatible with %2").arg(TQString::fromLatin1("--reminder")).arg(TQString::fromLatin1("--reminder-once")))
620  TQString opt = onceOnly ? TQString::fromLatin1("--reminder-once") : TQString::fromLatin1("--reminder");
621  if (args->isSet("exec"))
622  USAGE(i18n("%1 incompatible with %2").arg(opt).arg(TQString::fromLatin1("--exec")))
623  if (args->isSet("mail"))
624  USAGE(i18n("%1 incompatible with %2").arg(opt).arg(TQString::fromLatin1("--mail")))
625  KARecurrence::Type recurType;
626  TQString optval = args->getOption(onceOnly ? "reminder-once" : "reminder");
627  if (!convInterval(args->getOption(onceOnly ? "reminder-once" : "reminder"), recurType, reminderMinutes))
628  USAGE(i18n("Invalid %1 parameter").arg(opt))
629  if (recurType == KARecurrence::MINUTELY && alarmNoTime)
630  USAGE(i18n("Invalid %1 parameter for date-only alarm").arg(opt))
631  }
632 
633  int lateCancel = 0;
634  if (args->isSet("late-cancel"))
635  {
636  KARecurrence::Type recurType;
637  bool ok = convInterval(args->getOption("late-cancel"), recurType, lateCancel);
638  if (!ok || lateCancel <= 0)
639  USAGE(i18n("Invalid %1 parameter").arg(TQString::fromLatin1("late-cancel")))
640  }
641  else if (args->isSet("auto-close"))
642  USAGE(i18n("%1 requires %2").arg(TQString::fromLatin1("--auto-close")).arg(TQString::fromLatin1("--late-cancel")))
643 
644  int flags = KAEvent::DEFAULT_FONT;
645  if (args->isSet("ack-confirm"))
646  flags |= KAEvent::CONFIRM_ACK;
647  if (args->isSet("auto-close"))
648  flags |= KAEvent::AUTO_CLOSE;
649  if (args->isSet("beep"))
650  flags |= KAEvent::BEEP;
651  if (args->isSet("speak"))
652  flags |= KAEvent::SPEAK;
653  if (args->isSet("korganizer"))
654  flags |= KAEvent::COPY_KORGANIZER;
655  if (args->isSet("disable"))
656  flags |= KAEvent::DISABLED;
657  if (audioRepeat)
658  flags |= KAEvent::REPEAT_SOUND;
659  if (args->isSet("login"))
660  flags |= KAEvent::REPEAT_AT_LOGIN;
661  if (args->isSet("bcc"))
662  flags |= KAEvent::EMAIL_BCC;
663  if (alarmNoTime)
664  flags |= KAEvent::ANY_TIME;
665  args->clear(); // free up memory
666 
667  // Display or schedule the event
668  if (!initCheck())
669  {
670  exitCode = 1;
671  break;
672  }
673  if (!scheduleEvent(action, alMessage, alarmTime, lateCancel, flags, bgColour, fgColour, TQFont(), audioFile,
674  audioVolume, reminderMinutes, recurrence, repeatInterval, repeatCount,
675  alFromID, alAddresses, alSubject, alAttachments))
676  {
677  exitCode = 1;
678  break;
679  }
680  }
681  else
682  {
683  // No arguments - run interactively & display the main window
684  kdDebug(5950)<<"KAlarmApp::newInstance(): interactive\n";
685  if (args->isSet("ack-confirm"))
686  usage += TQString::fromLatin1("--ack-confirm ");
687  if (args->isSet("attach"))
688  usage += TQString::fromLatin1("--attach ");
689  if (args->isSet("auto-close"))
690  usage += TQString::fromLatin1("--auto-close ");
691  if (args->isSet("bcc"))
692  usage += TQString::fromLatin1("--bcc ");
693  if (args->isSet("beep"))
694  usage += TQString::fromLatin1("--beep ");
695  if (args->isSet("color"))
696  usage += TQString::fromLatin1("--color ");
697  if (args->isSet("colorfg"))
698  usage += TQString::fromLatin1("--colorfg ");
699  if (args->isSet("disable"))
700  usage += TQString::fromLatin1("--disable ");
701  if (args->isSet("from-id"))
702  usage += TQString::fromLatin1("--from-id ");
703  if (args->isSet("korganizer"))
704  usage += TQString::fromLatin1("--korganizer ");
705  if (args->isSet("late-cancel"))
706  usage += TQString::fromLatin1("--late-cancel ");
707  if (args->isSet("login"))
708  usage += TQString::fromLatin1("--login ");
709  if (args->isSet("play"))
710  usage += TQString::fromLatin1("--play ");
711 #ifndef WITHOUT_ARTS
712  if (args->isSet("play-repeat"))
713  usage += TQString::fromLatin1("--play-repeat ");
714 #endif
715  if (args->isSet("reminder"))
716  usage += TQString::fromLatin1("--reminder ");
717  if (args->isSet("reminder-once"))
718  usage += TQString::fromLatin1("--reminder-once ");
719  if (args->isSet("speak"))
720  usage += TQString::fromLatin1("--speak ");
721  if (args->isSet("subject"))
722  usage += TQString::fromLatin1("--subject ");
723  if (args->isSet("time"))
724  usage += TQString::fromLatin1("--time ");
725 #ifndef WITHOUT_ARTS
726  if (args->isSet("volume"))
727  usage += TQString::fromLatin1("--volume ");
728 #endif
729  if (!usage.isEmpty())
730  {
731  usage += i18n(": option(s) only valid with a message/%1/%2").arg(TQString::fromLatin1("--file")).arg(TQString::fromLatin1("--exec"));
732  break;
733  }
734 
735  args->clear(); // free up memory
736  if (!initCheck())
737  {
738  exitCode = 1;
739  break;
740  }
741 
742  (MainWindow::create())->show();
743  }
744  } while (0); // only execute once
745 
746  if (!usage.isEmpty())
747  {
748  // Note: we can't use args->usage() since that also quits any other
749  // running 'instances' of the program.
750  std::cerr << usage.local8Bit().data()
751  << i18n("\nUse --help to get a list of available command line options.\n").local8Bit().data();
752  exitCode = 1;
753  }
754  }
755  if (firstInstance && !dontRedisplay && !exitCode)
756  redisplayAlarms();
757 
758  --mActiveCount;
759  firstInstance = false;
760 
761  // Quit the application if this was the last/only running "instance" of the program.
762  // Executing 'return' doesn't work very well since the program continues to
763  // run if no windows were created.
764  quitIf(exitCode);
765  return exitCode;
766 }
767 
768 /******************************************************************************
769 * Quit the program, optionally only if there are no more "instances" running.
770 */
771 void KAlarmApp::quitIf(int exitCode, bool force)
772 {
773  if (force)
774  {
775  // Quit regardless, except for message windows
776  MainWindow::closeAll();
777  displayTrayIcon(false);
778  if (MessageWin::instanceCount())
779  return;
780  }
781  else
782  {
783  // Quit only if there are no more "instances" running
784  mPendingQuit = false;
785  if (mActiveCount > 0 || MessageWin::instanceCount())
786  return;
787  int mwcount = MainWindow::count();
788  MainWindow* mw = mwcount ? MainWindow::firstWindow() : 0;
789  if (mwcount > 1 || (mwcount && (!mw->isHidden() || !mw->isTrayParent())))
790  return;
791  // There are no windows left except perhaps a main window which is a hidden tray icon parent
792  if (mTrayWindow)
793  {
794  // There is a system tray icon.
795  // Don't exit unless the system tray doesn't seem to exist.
796  if (checkSystemTray())
797  return;
798  }
799  if (!mDcopQueue.isEmpty() || !mCommandProcesses.isEmpty())
800  {
801  // Don't quit yet if there are outstanding actions on the DCOP queue
802  mPendingQuit = true;
803  mPendingQuitCode = exitCode;
804  return;
805  }
806  }
807 
808  // This was the last/only running "instance" of the program, so exit completely.
809  kdDebug(5950) << "KAlarmApp::quitIf(" << exitCode << "): quitting" << endl;
810  BirthdayDlg::close();
811  exit(exitCode);
812 }
813 
814 /******************************************************************************
815 * Called when the Quit menu item is selected.
816 * Closes the system tray window and all main windows, but does not exit the
817 * program if other windows are still open.
818 */
819 void KAlarmApp::doQuit(TQWidget* parent)
820 {
821  kdDebug(5950) << "KAlarmApp::doQuit()\n";
822  if (mDisableAlarmsIfStopped
823  && MessageBox::warningContinueCancel(parent, KMessageBox::Cancel,
824  i18n("Quitting will disable alarms\n(once any alarm message windows are closed)."),
825  TQString(), KStdGuiItem::quit(), Preferences::QUIT_WARN
826  ) != KMessageBox::Yes)
827  return;
828  quitIf(0, true);
829 }
830 
831 /******************************************************************************
832 * Called when the session manager is about to close down the application.
833 */
834 void KAlarmApp::commitData(TQSessionManager& sm)
835 {
836  mSessionClosingDown = true;
837  TDEUniqueApplication::commitData(sm);
838  mSessionClosingDown = false; // reset in case shutdown is cancelled
839 }
840 
841 /******************************************************************************
842 * Display an error message for a fatal error. Prevent further actions since
843 * the program state is unsafe.
844 */
845 void KAlarmApp::displayFatalError(const TQString& message)
846 {
847  if (!mFatalError)
848  {
849  mFatalError = 1;
850  mFatalMessage = message;
851  if (theInstance)
852  TQTimer::singleShot(0, theInstance, TQ_SLOT(quitFatal()));
853  }
854 }
855 
856 /******************************************************************************
857 * Quit the program, once the fatal error message has been acknowledged.
858 */
859 void KAlarmApp::quitFatal()
860 {
861  switch (mFatalError)
862  {
863  case 0:
864  case 2:
865  return;
866  case 1:
867  mFatalError = 2;
868  KMessageBox::error(0, mFatalMessage);
869  mFatalError = 3;
870  // fall through to '3'
871  case 3:
872  if (theInstance)
873  theInstance->quitIf(1, true);
874  break;
875  }
876  TQTimer::singleShot(1000, this, TQ_SLOT(quitFatal()));
877 }
878 
879 /******************************************************************************
880 * The main processing loop for KAlarm.
881 * All KAlarm operations involving opening or updating calendar files are called
882 * from this loop to ensure that only one operation is active at any one time.
883 * This precaution is necessary because KAlarm's activities are mostly
884 * asynchronous, being in response to DCOP calls from the alarm daemon (or other
885 * programs) or timer events, any of which can be received in the middle of
886 * performing another operation. If a calendar file is opened or updated while
887 * another calendar operation is in progress, the program has been observed to
888 * hang, or the first calendar call has failed with data loss - clearly
889 * unacceptable!!
890 */
891 void KAlarmApp::processQueue()
892 {
893  if (mInitialised && !mProcessingQueue)
894  {
895  kdDebug(5950) << "KAlarmApp::processQueue()\n";
896  mProcessingQueue = true;
897 
898  // Reset the alarm daemon if it's been queued
899  KAlarm::resetDaemonIfQueued();
900 
901  // Process DCOP calls
902  while (!mDcopQueue.isEmpty())
903  {
904  DcopTQEntry& entry = mDcopQueue.first();
905  if (entry.eventId.isEmpty())
906  {
907  // It's a new alarm
908  switch (entry.function)
909  {
910  case EVENT_TRIGGER:
911  execAlarm(entry.event, entry.event.firstAlarm(), false);
912  break;
913  case EVENT_HANDLE:
914  KAlarm::addEvent(entry.event, 0);
915  break;
916  case EVENT_CANCEL:
917  break;
918  }
919  }
920  else
921  handleEvent(entry.eventId, entry.function);
922  mDcopQueue.pop_front();
923  }
924 
925  // Purge the expired alarms calendar if it's time to do so
926  AlarmCalendar::expiredCalendar()->purgeIfQueued();
927 
928  // Now that the queue has been processed, quit if a quit was queued
929  if (mPendingQuit)
930  quitIf(mPendingQuitCode);
931 
932  mProcessingQueue = false;
933  }
934 }
935 
936 /******************************************************************************
937 * Redisplay alarms which were being shown when the program last exited.
938 * Normally, these alarms will have been displayed by session restoration, but
939 * if the program crashed or was killed, we can redisplay them here so that
940 * they won't be lost.
941 */
942 void KAlarmApp::redisplayAlarms()
943 {
944  AlarmCalendar* cal = AlarmCalendar::displayCalendar();
945  if (cal->isOpen())
946  {
947  KCal::Event::List events = cal->events();
948  for (KCal::Event::List::ConstIterator it = events.begin(); it != events.end(); ++it)
949  {
950  KCal::Event* kcalEvent = *it;
951  KAEvent event(*kcalEvent);
952  event.setUid(KAEvent::ACTIVE);
953  if (!MessageWin::findEvent(event.id()))
954  {
955  // This event should be displayed, but currently isn't being
956  kdDebug(5950) << "KAlarmApp::redisplayAlarms(): " << event.id() << endl;
957  KAAlarm alarm = event.convertDisplayingAlarm();
958  (new MessageWin(event, alarm, false, !alarm.repeatAtLogin()))->show();
959  }
960  }
961  }
962 }
963 
964 /******************************************************************************
965 * Called when the system tray main window is closed.
966 */
967 void KAlarmApp::removeWindow(TrayWindow*)
968 {
969  mTrayWindow = 0;
970  quitIf();
971 }
972 
973 /******************************************************************************
974 * Display or close the system tray icon.
975 */
976 bool KAlarmApp::displayTrayIcon(bool show, MainWindow* parent)
977 {
978  static bool creating = false;
979  if (show)
980  {
981  if (!mTrayWindow && !creating)
982  {
983  if (!mHaveSystemTray)
984  return false;
985  if (!MainWindow::count() && wantRunInSystemTray())
986  {
987  creating = true; // prevent main window constructor from creating an additional tray icon
988  parent = MainWindow::create();
989  creating = false;
990  }
991  mTrayWindow = new TrayWindow(parent ? parent : MainWindow::firstWindow());
992  connect(mTrayWindow, TQ_SIGNAL(deleted()), TQ_SIGNAL(trayIconToggled()));
993  mTrayWindow->show();
994  emit trayIconToggled();
995 
996  // Set up a timer so that we can check after all events in the window system's
997  // event queue have been processed, whether the system tray actually exists
998  mCheckingSystemTray = true;
999  mSavedNoSystemTray = mNoSystemTray;
1000  mNoSystemTray = false;
1001  TQTimer::singleShot(0, this, TQ_SLOT(slotSystemTrayTimer()));
1002  }
1003  }
1004  else if (mTrayWindow)
1005  {
1006  delete mTrayWindow;
1007  mTrayWindow = 0;
1008  }
1009  return true;
1010 }
1011 
1012 /******************************************************************************
1013 * Called by a timer to check whether the system tray icon has been housed in
1014 * the system tray. Because there is a delay between the system tray icon show
1015 * event and the icon being reparented by the system tray, we have to use a
1016 * timer to check whether the system tray has actually grabbed it, or whether
1017 * the system tray probably doesn't exist.
1018 */
1019 void KAlarmApp::slotSystemTrayTimer()
1020 {
1021  mCheckingSystemTray = false;
1022  if (!checkSystemTray())
1023  quitIf(0); // exit the application if there are no open windows
1024 }
1025 
1026 /******************************************************************************
1027 * Check whether the system tray icon has been housed in the system tray.
1028 * If the system tray doesn't seem to exist, tell the alarm daemon to notify us
1029 * of alarms regardless of whether we're running.
1030 */
1031 bool KAlarmApp::checkSystemTray()
1032 {
1033  if (mCheckingSystemTray || !mTrayWindow)
1034  return true;
1035  if (mTrayWindow->inSystemTray() != !mSavedNoSystemTray)
1036  {
1037  kdDebug(5950) << "KAlarmApp::checkSystemTray(): changed -> " << mSavedNoSystemTray << endl;
1038  mNoSystemTray = mSavedNoSystemTray = !mSavedNoSystemTray;
1039 
1040  // Store the new setting in the config file, so that if KAlarm exits and is then
1041  // next activated by the daemon to display a message, it will register with the
1042  // daemon with the correct NOTIFY type. If that happened when there was no system
1043  // tray and alarms are disabled when KAlarm is not running, registering with
1044  // NO_START_NOTIFY could result in alarms never being seen.
1045  TDEConfig* config = kapp->config();
1046  config->setGroup(TQString::fromLatin1("General"));
1047  config->writeEntry(TQString::fromLatin1("NoSystemTray"), mNoSystemTray);
1048  config->sync();
1049 
1050  // Update other settings and reregister with the alarm daemon
1051  slotPreferencesChanged();
1052  }
1053  else
1054  {
1055  kdDebug(5950) << "KAlarmApp::checkSystemTray(): no change = " << !mSavedNoSystemTray << endl;
1056  mNoSystemTray = mSavedNoSystemTray;
1057  }
1058  return !mNoSystemTray;
1059 }
1060 
1061 /******************************************************************************
1062 * Return the main window associated with the system tray icon.
1063 */
1064 MainWindow* KAlarmApp::trayMainWindow() const
1065 {
1066  return mTrayWindow ? mTrayWindow->assocMainWindow() : 0;
1067 }
1068 
1069 /******************************************************************************
1070 * Called when KAlarm preferences have changed.
1071 */
1072 void KAlarmApp::slotPreferencesChanged()
1073 {
1074  bool newRunInSysTray = wantRunInSystemTray();
1075  if (newRunInSysTray != mOldRunInSystemTray)
1076  {
1077  // The system tray run mode has changed
1078  ++mActiveCount; // prevent the application from quitting
1079  MainWindow* win = mTrayWindow ? mTrayWindow->assocMainWindow() : 0;
1080  delete mTrayWindow; // remove the system tray icon if it is currently shown
1081  mTrayWindow = 0;
1082  mOldRunInSystemTray = newRunInSysTray;
1083  if (!newRunInSysTray)
1084  {
1085  if (win && win->isHidden())
1086  delete win;
1087  }
1088  displayTrayIcon(true);
1089  --mActiveCount;
1090  }
1091 
1092  bool newDisableIfStopped = wantRunInSystemTray() && !mNoSystemTray && Preferences::disableAlarmsIfStopped();
1093  if (newDisableIfStopped != mDisableAlarmsIfStopped)
1094  {
1095  mDisableAlarmsIfStopped = newDisableIfStopped; // N.B. this setting is used by Daemon::reregister()
1096  Preferences::setQuitWarn(true); // since mode has changed, re-allow warning messages on Quit
1097  Daemon::reregister(); // re-register with the alarm daemon
1098  }
1099 
1100  // Change alarm times for date-only alarms if the start of day time has changed
1101  if (Preferences::startOfDay() != mStartOfDay)
1102  changeStartOfDay();
1103 
1104  // In case the date for February 29th recurrences has changed
1105  KARecurrence::setDefaultFeb29Type(Preferences::defaultFeb29Type());
1106 
1107  if (Preferences::expiredColour() != mPrefsExpiredColour)
1108  {
1109  // The expired alarms text colour has changed
1110  mRefreshExpiredAlarms = true;
1111  mPrefsExpiredColour = Preferences::expiredColour();
1112  }
1113 
1114  if (Preferences::expiredKeepDays() != mPrefsExpiredKeepDays)
1115  {
1116  // How long expired alarms are being kept has changed.
1117  // N.B. This also adjusts for any change in start-of-day time.
1118  mPrefsExpiredKeepDays = Preferences::expiredKeepDays();
1119  AlarmCalendar::expiredCalendar()->setPurgeDays(mPrefsExpiredKeepDays);
1120  }
1121 
1122  if (mRefreshExpiredAlarms)
1123  {
1124  mRefreshExpiredAlarms = false;
1125  MainWindow::updateExpired();
1126  }
1127 }
1128 
1129 /******************************************************************************
1130 * Change alarm times for date-only alarms after the start of day time has changed.
1131 */
1132 void KAlarmApp::changeStartOfDay()
1133 {
1134  Daemon::notifyTimeChanged(); // tell the alarm daemon the new time
1135  TQTime sod = Preferences::startOfDay();
1136  DateTime::setStartOfDay(sod);
1137  AlarmCalendar* cal = AlarmCalendar::activeCalendar();
1138  if (KAEvent::adjustStartOfDay(cal->events()))
1139  cal->save();
1140  Preferences::updateStartOfDayCheck(); // now that calendar is updated, set OK flag in config file
1141  mStartOfDay = sod;
1142 }
1143 
1144 /******************************************************************************
1145 * Called when the expired alarms calendar has been purged.
1146 * Updates the alarm list in all main windows.
1147 */
1148 void KAlarmApp::slotExpiredPurged()
1149 {
1150  mRefreshExpiredAlarms = false;
1151  MainWindow::updateExpired();
1152 }
1153 
1154 /******************************************************************************
1155 * Return whether the program is configured to be running in the system tray.
1156 */
1157 bool KAlarmApp::wantRunInSystemTray() const
1158 {
1159  return Preferences::runInSystemTray() && mHaveSystemTray;
1160 }
1161 
1162 /******************************************************************************
1163 * Called to schedule a new alarm, either in response to a DCOP notification or
1164 * to command line options.
1165 * Reply = true unless there was a parameter error or an error opening calendar file.
1166 */
1167 bool KAlarmApp::scheduleEvent(KAEvent::Action action, const TQString& text, const TQDateTime& dateTime,
1168  int lateCancel, int flags, const TQColor& bg, const TQColor& fg, const TQFont& font,
1169  const TQString& audioFile, float audioVolume, int reminderMinutes,
1170  const KARecurrence& recurrence, int repeatInterval, int repeatCount,
1171  uint mailFromID, const EmailAddressList& mailAddresses,
1172  const TQString& mailSubject, const TQStringList& mailAttachments)
1173 {
1174  kdDebug(5950) << "KAlarmApp::scheduleEvent(): " << text << endl;
1175  if (!dateTime.isValid())
1176  return false;
1177  TQDateTime now = TQDateTime::currentDateTime();
1178  if (lateCancel && dateTime < now.addSecs(-maxLateness(lateCancel)))
1179  return true; // alarm time was already expired too long ago
1180  TQDateTime alarmTime = dateTime;
1181  // Round down to the nearest minute to avoid scheduling being messed up
1182  alarmTime.setTime(TQTime(alarmTime.time().hour(), alarmTime.time().minute(), 0));
1183 
1184  KAEvent event(alarmTime, text, bg, fg, font, action, lateCancel, flags);
1185  if (reminderMinutes)
1186  {
1187  bool onceOnly = (reminderMinutes < 0);
1188  event.setReminder((onceOnly ? -reminderMinutes : reminderMinutes), onceOnly);
1189  }
1190  if (!audioFile.isEmpty())
1191  event.setAudioFile(audioFile, audioVolume, -1, 0);
1192  if (!mailAddresses.isEmpty())
1193  event.setEmail(mailFromID, mailAddresses, mailSubject, mailAttachments);
1194  event.setRecurrence(recurrence);
1195  event.setFirstRecurrence();
1196  event.setRepetition(repeatInterval, repeatCount - 1);
1197  if (alarmTime <= now)
1198  {
1199  // Alarm is due for display already.
1200  // First execute it once without adding it to the calendar file.
1201  if (!mInitialised)
1202  mDcopQueue.append(DcopTQEntry(event, EVENT_TRIGGER));
1203  else
1204  execAlarm(event, event.firstAlarm(), false);
1205  // If it's a recurring alarm, reschedule it for its next occurrence
1206  if (!event.recurs()
1207  || event.setNextOccurrence(now) == KAEvent::NO_OCCURRENCE)
1208  return true;
1209  // It has recurrences in the future
1210  }
1211 
1212  // Queue the alarm for insertion into the calendar file
1213  mDcopQueue.append(DcopTQEntry(event));
1214  if (mInitialised)
1215  TQTimer::singleShot(0, this, TQ_SLOT(processQueue()));
1216  return true;
1217 }
1218 
1219 /******************************************************************************
1220 * Called in response to a DCOP notification by the alarm daemon that an event
1221 * should be handled, i.e. displayed or cancelled.
1222 * Optionally display the event. Delete the event from the calendar file and
1223 * from every main window instance.
1224 */
1225 bool KAlarmApp::handleEvent(const TQString& urlString, const TQString& eventID, EventFunc function)
1226 {
1227  kdDebug(5950) << "KAlarmApp::handleEvent(DCOP): " << eventID << endl;
1228  AlarmCalendar* cal = AlarmCalendar::activeCalendar(); // this can be called before calendars have been initialised
1229  if (cal && KURL(urlString).url() != cal->urlString())
1230  {
1231  kdError(5950) << "KAlarmApp::handleEvent(DCOP): wrong calendar file " << urlString << endl;
1232  Daemon::eventHandled(eventID, false);
1233  return false;
1234  }
1235  mDcopQueue.append(DcopTQEntry(function, eventID));
1236  if (mInitialised)
1237  TQTimer::singleShot(0, this, TQ_SLOT(processQueue()));
1238  return true;
1239 }
1240 
1241 /******************************************************************************
1242 * Either:
1243 * a) Display the event and then delete it if it has no outstanding repetitions.
1244 * b) Delete the event.
1245 * c) Reschedule the event for its next repetition. If none remain, delete it.
1246 * If the event is deleted, it is removed from the calendar file and from every
1247 * main window instance.
1248 */
1249 bool KAlarmApp::handleEvent(const TQString& eventID, EventFunc function)
1250 {
1251  kdDebug(5950) << "KAlarmApp::handleEvent(): " << eventID << ", " << (function==EVENT_TRIGGER?"TRIGGER":function==EVENT_CANCEL?"CANCEL":function==EVENT_HANDLE?"HANDLE":"?") << endl;
1252  KCal::Event* kcalEvent = AlarmCalendar::activeCalendar()->event(eventID);
1253  if (!kcalEvent)
1254  {
1255  kdError(5950) << "KAlarmApp::handleEvent(): event ID not found: " << eventID << endl;
1256  Daemon::eventHandled(eventID, false);
1257  return false;
1258  }
1259  KAEvent event(*kcalEvent);
1260  switch (function)
1261  {
1262  case EVENT_CANCEL:
1263  KAlarm::deleteEvent(event, true);
1264  break;
1265 
1266  case EVENT_TRIGGER: // handle it if it's due, else execute it regardless
1267  case EVENT_HANDLE: // handle it if it's due
1268  {
1269  TQDateTime now = TQDateTime::currentDateTime();
1270  bool updateCalAndDisplay = false;
1271  bool alarmToExecuteValid = false;
1272  KAAlarm alarmToExecute;
1273  // Check all the alarms in turn.
1274  // Note that the main alarm is fetched before any other alarms.
1275  for (KAAlarm alarm = event.firstAlarm(); alarm.valid(); alarm = event.nextAlarm(alarm))
1276  {
1277  // Check if the alarm is due yet.
1278  int secs = alarm.dateTime(true).dateTime().secsTo(now);
1279  if (secs < 0)
1280  {
1281  // This alarm is definitely not due yet
1282  kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": not due\n";
1283  continue;
1284  }
1285  if (alarm.repeatAtLogin())
1286  {
1287  // Alarm is to be displayed at every login.
1288  // Check if the alarm has only just been set up.
1289  // (The alarm daemon will immediately notify that it is due
1290  // since it is set up with a time in the past.)
1291  kdDebug(5950) << "KAlarmApp::handleEvent(): REPEAT_AT_LOGIN\n";
1292  if (secs < maxLateness(1))
1293  continue;
1294 
1295  // Check if the main alarm is already being displayed.
1296  // (We don't want to display both at the same time.)
1297  if (alarmToExecute.valid())
1298  continue;
1299 
1300  // Set the time to display if it's a display alarm
1301  alarm.setTime(now);
1302  }
1303  if (alarm.lateCancel())
1304  {
1305  // Alarm is due, and it is to be cancelled if too late.
1306  kdDebug(5950) << "KAlarmApp::handleEvent(): LATE_CANCEL\n";
1307  bool late = false;
1308  bool cancel = false;
1309  if (alarm.dateTime().isDateOnly())
1310  {
1311  // The alarm has no time, so cancel it if its date is too far past
1312  int maxlate = alarm.lateCancel() / 1440; // maximum lateness in days
1313  TQDateTime limit(alarm.date().addDays(maxlate + 1), Preferences::startOfDay());
1314  if (now >= limit)
1315  {
1316  // It's too late to display the scheduled occurrence.
1317  // Find the last previous occurrence of the alarm.
1318  DateTime next;
1319  KAEvent::OccurType type = event.previousOccurrence(now, next, true);
1320  switch (type & ~KAEvent::OCCURRENCE_REPEAT)
1321  {
1322  case KAEvent::FIRST_OR_ONLY_OCCURRENCE:
1323  case KAEvent::RECURRENCE_DATE:
1324  case KAEvent::RECURRENCE_DATE_TIME:
1325  case KAEvent::LAST_RECURRENCE:
1326  limit.setDate(next.date().addDays(maxlate + 1));
1327  limit.setTime(Preferences::startOfDay());
1328  if (now >= limit)
1329  {
1330  if (type == KAEvent::LAST_RECURRENCE
1331  || (type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event.recurs()))
1332  cancel = true; // last occurrence (and there are no repetitions)
1333  else
1334  late = true;
1335  }
1336  break;
1337  case KAEvent::NO_OCCURRENCE:
1338  default:
1339  late = true;
1340  break;
1341  }
1342  }
1343  }
1344  else
1345  {
1346  // The alarm is timed. Allow it to be the permitted amount late before cancelling it.
1347  int maxlate = maxLateness(alarm.lateCancel());
1348  if (secs > maxlate)
1349  {
1350  // It's over the maximum interval late.
1351  // Find the most recent occurrence of the alarm.
1352  DateTime next;
1353  KAEvent::OccurType type = event.previousOccurrence(now, next, true);
1354  switch (type & ~KAEvent::OCCURRENCE_REPEAT)
1355  {
1356  case KAEvent::FIRST_OR_ONLY_OCCURRENCE:
1357  case KAEvent::RECURRENCE_DATE:
1358  case KAEvent::RECURRENCE_DATE_TIME:
1359  case KAEvent::LAST_RECURRENCE:
1360  if (next.dateTime().secsTo(now) > maxlate)
1361  {
1362  if (type == KAEvent::LAST_RECURRENCE
1363  || (type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event.recurs()))
1364  cancel = true; // last occurrence (and there are no repetitions)
1365  else
1366  late = true;
1367  }
1368  break;
1369  case KAEvent::NO_OCCURRENCE:
1370  default:
1371  late = true;
1372  break;
1373  }
1374  }
1375  }
1376 
1377  if (cancel)
1378  {
1379  // All recurrences are finished, so cancel the event
1380  event.setArchive();
1381  cancelAlarm(event, alarm.type(), false);
1382  updateCalAndDisplay = true;
1383  continue;
1384  }
1385  if (late)
1386  {
1387  // The latest repetition was too long ago, so schedule the next one
1388  rescheduleAlarm(event, alarm, false);
1389  updateCalAndDisplay = true;
1390  continue;
1391  }
1392  }
1393  if (!alarmToExecuteValid)
1394  {
1395  kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": execute\n";
1396  alarmToExecute = alarm; // note the alarm to be executed
1397  alarmToExecuteValid = true; // only trigger one alarm for the event
1398  }
1399  else
1400  kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": skip\n";
1401  }
1402 
1403  // If there is an alarm to execute, do this last after rescheduling/cancelling
1404  // any others. This ensures that the updated event is only saved once to the calendar.
1405  if (alarmToExecute.valid())
1406  execAlarm(event, alarmToExecute, true, !alarmToExecute.repeatAtLogin());
1407  else
1408  {
1409  if (function == EVENT_TRIGGER)
1410  {
1411  // The alarm is to be executed regardless of whether it's due.
1412  // Only trigger one alarm from the event - we don't want multiple
1413  // identical messages, for example.
1414  KAAlarm alarm = event.firstAlarm();
1415  if (alarm.valid())
1416  execAlarm(event, alarm, false);
1417  }
1418  if (updateCalAndDisplay)
1419  KAlarm::updateEvent(event, 0); // update the window lists and calendar file
1420  else if (function != EVENT_TRIGGER)
1421  {
1422  kdDebug(5950) << "KAlarmApp::handleEvent(): no action\n";
1423  Daemon::eventHandled(eventID, false);
1424  }
1425  }
1426  break;
1427  }
1428  }
1429  return true;
1430 }
1431 
1432 /******************************************************************************
1433 * Called when an alarm is currently being displayed, to store a copy of the
1434 * alarm in the displaying calendar, and to reschedule it for its next repetition.
1435 * If no repetitions remain, cancel it.
1436 */
1437 void KAlarmApp::alarmShowing(KAEvent& event, KAAlarm::Type alarmType, const DateTime& alarmTime)
1438 {
1439  kdDebug(5950) << "KAlarmApp::alarmShowing(" << event.id() << ", " << KAAlarm::debugType(alarmType) << ")\n";
1440  KCal::Event* kcalEvent = AlarmCalendar::activeCalendar()->event(event.id());
1441  if (!kcalEvent)
1442  kdError(5950) << "KAlarmApp::alarmShowing(): event ID not found: " << event.id() << endl;
1443  else
1444  {
1445  KAAlarm alarm = event.alarm(alarmType);
1446  if (!alarm.valid())
1447  kdError(5950) << "KAlarmApp::alarmShowing(): alarm type not found: " << event.id() << ":" << alarmType << endl;
1448  else
1449  {
1450  // Copy the alarm to the displaying calendar in case of a crash, etc.
1451  KAEvent dispEvent;
1452  dispEvent.setDisplaying(event, alarmType, alarmTime.dateTime());
1453  AlarmCalendar* cal = AlarmCalendar::displayCalendarOpen();
1454  if (cal)
1455  {
1456  cal->deleteEvent(dispEvent.id()); // in case it already exists
1457  cal->addEvent(dispEvent);
1458  cal->save();
1459  }
1460 
1461  rescheduleAlarm(event, alarm, true);
1462  return;
1463  }
1464  }
1465  Daemon::eventHandled(event.id(), false);
1466 }
1467 
1468 /******************************************************************************
1469 * Called when an alarm action has completed, to perform any post-alarm actions.
1470 */
1471 void KAlarmApp::alarmCompleted(const KAEvent& event)
1472 {
1473  if (!event.postAction().isEmpty() && ShellProcess::authorised())
1474  {
1475  TQString command = event.postAction();
1476  kdDebug(5950) << "KAlarmApp::alarmCompleted(" << event.id() << "): " << command << endl;
1477  doShellCommand(command, event, 0, ProcData::POST_ACTION);
1478  }
1479 }
1480 
1481 /******************************************************************************
1482 * Reschedule the alarm for its next recurrence. If none remain, delete it.
1483 * If the alarm is deleted and it is the last alarm for its event, the event is
1484 * removed from the calendar file and from every main window instance.
1485 */
1486 void KAlarmApp::rescheduleAlarm(KAEvent& event, const KAAlarm& alarm, bool updateCalAndDisplay)
1487 {
1488  kdDebug(5950) << "KAlarmApp::rescheduleAlarm()" << endl;
1489  bool update = false;
1490  if (alarm.reminder() || alarm.deferred())
1491  {
1492  // It's an advance warning alarm or an extra deferred alarm, so delete it
1493  event.removeExpiredAlarm(alarm.type());
1494  update = true;
1495  }
1496  else if (alarm.repeatAtLogin())
1497  {
1498  // Leave an alarm which repeats at every login until its main alarm is deleted
1499  if (updateCalAndDisplay && event.updated())
1500  update = true;
1501  }
1502  else
1503  {
1504  // Reschedule the alarm for its next recurrence.
1505  KAEvent::OccurType type = event.setNextOccurrence(TQDateTime::currentDateTime());
1506  switch (type)
1507  {
1508  case KAEvent::NO_OCCURRENCE:
1509  // All repetitions are finished, so cancel the event
1510  cancelAlarm(event, alarm.type(), updateCalAndDisplay);
1511  break;
1512  default:
1513  if (!(type & KAEvent::OCCURRENCE_REPEAT))
1514  break;
1515  // Next occurrence is a repeat, so fall through to recurrence handling
1516  case KAEvent::RECURRENCE_DATE:
1517  case KAEvent::RECURRENCE_DATE_TIME:
1518  case KAEvent::LAST_RECURRENCE:
1519  // The event is due by now and repetitions still remain, so rewrite the event
1520  if (updateCalAndDisplay)
1521  update = true;
1522  else
1523  {
1524  event.cancelCancelledDeferral();
1525  event.setUpdated(); // note that the calendar file needs to be updated
1526  }
1527  break;
1528  case KAEvent::FIRST_OR_ONLY_OCCURRENCE:
1529  // The first occurrence is still due?!?, so don't do anything
1530  break;
1531  }
1532  if (event.deferred())
1533  {
1534  // Just in case there's also a deferred alarm, ensure it's removed
1535  event.removeExpiredAlarm(KAAlarm::DEFERRED_ALARM);
1536  update = true;
1537  }
1538  }
1539  if (update)
1540  {
1541  event.cancelCancelledDeferral();
1542  KAlarm::updateEvent(event, 0); // update the window lists and calendar file
1543  }
1544 }
1545 
1546 /******************************************************************************
1547 * Delete the alarm. If it is the last alarm for its event, the event is removed
1548 * from the calendar file and from every main window instance.
1549 */
1550 void KAlarmApp::cancelAlarm(KAEvent& event, KAAlarm::Type alarmType, bool updateCalAndDisplay)
1551 {
1552  kdDebug(5950) << "KAlarmApp::cancelAlarm()" << endl;
1553  event.cancelCancelledDeferral();
1554  if (alarmType == KAAlarm::MAIN_ALARM && !event.displaying() && event.toBeArchived())
1555  {
1556  // The event is being deleted. Save it in the expired calendar file first.
1557  TQString id = event.id(); // save event ID since KAlarm::addExpiredEvent() changes it
1558  KAlarm::addExpiredEvent(event);
1559  event.setEventID(id); // restore event ID
1560  }
1561  event.removeExpiredAlarm(alarmType);
1562  if (!event.alarmCount())
1563  KAlarm::deleteEvent(event, false);
1564  else if (updateCalAndDisplay)
1565  KAlarm::updateEvent(event, 0); // update the window lists and calendar file
1566 }
1567 
1568 /******************************************************************************
1569 * Execute an alarm by displaying its message or file, or executing its command.
1570 * Reply = ShellProcess instance if a command alarm
1571 * != 0 if successful
1572 * = 0 if the alarm is disabled, or if an error message was output.
1573 */
1574 void* KAlarmApp::execAlarm(KAEvent& event, const KAAlarm& alarm, bool reschedule, bool allowDefer, bool noPreAction)
1575 {
1576  if (!event.enabled())
1577  {
1578  // The event is disabled.
1579  if (reschedule)
1580  rescheduleAlarm(event, alarm, true);
1581  return 0;
1582  }
1583 
1584  void* result = (void*)1;
1585  event.setArchive();
1586  switch (alarm.action())
1587  {
1588  case KAAlarm::MESSAGE:
1589  case KAAlarm::FILE:
1590  {
1591  // Display a message or file, provided that the same event isn't already being displayed
1592  MessageWin* win = MessageWin::findEvent(event.id());
1593  // Find if we're changing a reminder message to the real message
1594  bool reminder = (alarm.type() & KAAlarm::REMINDER_ALARM);
1595  bool replaceReminder = !reminder && win && (win->alarmType() & KAAlarm::REMINDER_ALARM);
1596  if (!reminder && !event.deferred()
1597  && (replaceReminder || !win) && !noPreAction
1598  && !event.preAction().isEmpty() && ShellProcess::authorised())
1599  {
1600  // It's not a reminder or a deferred alarm, and there is no message window
1601  // (other than a reminder window) currently displayed for this alarm,
1602  // and we need to execute a command before displaying the new window.
1603  // Check whether the command is already being executed for this alarm.
1604  for (TQValueList<ProcData*>::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it)
1605  {
1606  ProcData* pd = *it;
1607  if (pd->event->id() == event.id() && (pd->flags & ProcData::PRE_ACTION))
1608  {
1609  kdDebug(5950) << "KAlarmApp::execAlarm(): already executing pre-DISPLAY command" << endl;
1610  return pd->process; // already executing - don't duplicate the action
1611  }
1612  }
1613 
1614  TQString command = event.preAction();
1615  kdDebug(5950) << "KAlarmApp::execAlarm(): pre-DISPLAY command: " << command << endl;
1616  int flags = (reschedule ? ProcData::RESCHEDULE : 0) | (allowDefer ? ProcData::ALLOW_DEFER : 0);
1617  if (doShellCommand(command, event, &alarm, (flags | ProcData::PRE_ACTION)))
1618  return result; // display the message after the command completes
1619  // Error executing command - display the message even though it failed
1620  }
1621  if (!event.enabled())
1622  delete win; // event is disabled - close its window
1623  else if (!win
1624  || (!win->hasDefer() && !alarm.repeatAtLogin())
1625  || replaceReminder)
1626  {
1627  // Either there isn't already a message for this event,
1628  // or there is a repeat-at-login message with no Defer
1629  // button, which needs to be replaced with a new message,
1630  // or the caption needs to be changed from "Reminder" to "Message".
1631  if (win)
1632  win->setRecreating(); // prevent post-alarm actions
1633  delete win;
1634  (new MessageWin(event, alarm, reschedule, allowDefer))->show();
1635  }
1636  else
1637  {
1638  // Raise the existing message window and replay any sound
1639  win->repeat(alarm); // N.B. this reschedules the alarm
1640  }
1641  break;
1642  }
1643  case KAAlarm::COMMAND:
1644  {
1645  int flags = event.commandXterm() ? ProcData::EXEC_IN_XTERM : 0;
1646  TQString command = event.cleanText();
1647  if (event.commandScript())
1648  {
1649  // Store the command script in a temporary file for execution
1650  kdDebug(5950) << "KAlarmApp::execAlarm(): COMMAND: (script)" << endl;
1651  TQString tmpfile = createTempScriptFile(command, false, event, alarm);
1652  if (tmpfile.isEmpty())
1653  result = 0;
1654  else
1655  result = doShellCommand(tmpfile, event, &alarm, (flags | ProcData::TEMP_FILE));
1656  }
1657  else
1658  {
1659  kdDebug(5950) << "KAlarmApp::execAlarm(): COMMAND: " << command << endl;
1660  result = doShellCommand(command, event, &alarm, flags);
1661  }
1662  if (reschedule)
1663  rescheduleAlarm(event, alarm, true);
1664  break;
1665  }
1666  case KAAlarm::EMAIL:
1667  {
1668  kdDebug(5950) << "KAlarmApp::execAlarm(): EMAIL to: " << event.emailAddresses(", ") << endl;
1669  TQStringList errmsgs;
1670  if (!KAMail::send(event, errmsgs, (reschedule || allowDefer)))
1671  result = 0;
1672  if (!errmsgs.isEmpty())
1673  {
1674  // Some error occurred, although the email may have been sent successfully
1675  if (result)
1676  kdDebug(5950) << "KAlarmApp::execAlarm(): copy error: " << errmsgs[1] << endl;
1677  else
1678  kdDebug(5950) << "KAlarmApp::execAlarm(): failed: " << errmsgs[1] << endl;
1679  (new MessageWin(event, alarm.dateTime(), errmsgs))->show();
1680  }
1681  if (reschedule)
1682  rescheduleAlarm(event, alarm, true);
1683  break;
1684  }
1685  default:
1686  return 0;
1687  }
1688  return result;
1689 }
1690 
1691 /******************************************************************************
1692 * Execute a shell command line specified by an alarm.
1693 * If the PRE_ACTION bit of 'flags' is set, the alarm will be executed via
1694 * execAlarm() once the command completes, the execAlarm() parameters being
1695 * derived from the remaining bits in 'flags'.
1696 */
1697 ShellProcess* KAlarmApp::doShellCommand(const TQString& command, const KAEvent& event, const KAAlarm* alarm, int flags)
1698 {
1699  kdDebug(5950) << "KAlarmApp::doShellCommand(" << command << ", " << event.id() << ")" << endl;
1700  TDEProcess::Communication comms = TDEProcess::NoCommunication;
1701  TQString cmd;
1702  TQString tmpXtermFile;
1703  if (flags & ProcData::EXEC_IN_XTERM)
1704  {
1705  // Execute the command in a terminal window.
1706  cmd = Preferences::cmdXTermCommand();
1707  cmd.replace("%t", aboutData()->programName()); // set the terminal window title
1708  if (cmd.find("%C") >= 0)
1709  {
1710  // Execute the command from a temporary script file
1711  if (flags & ProcData::TEMP_FILE)
1712  cmd.replace("%C", command); // the command is already calling a temporary file
1713  else
1714  {
1715  tmpXtermFile = createTempScriptFile(command, true, event, *alarm);
1716  if (tmpXtermFile.isEmpty())
1717  return 0;
1718  cmd.replace("%C", tmpXtermFile); // %C indicates where to insert the command
1719  }
1720  }
1721  else if (cmd.find("%W") >= 0)
1722  {
1723  // Execute the command from a temporary script file,
1724  // with a sleep after the command is executed
1725  tmpXtermFile = createTempScriptFile(command + TQString::fromLatin1("\nsleep 86400\n"), true, event, *alarm);
1726  if (tmpXtermFile.isEmpty())
1727  return 0;
1728  cmd.replace("%W", tmpXtermFile); // %w indicates where to insert the command
1729  }
1730  else if (cmd.find("%w") >= 0)
1731  {
1732  // Append a sleep to the command.
1733  // Quote the command in case it contains characters such as [>|;].
1734  TQString exec = KShellProcess::quote(command + TQString::fromLatin1("; sleep 86400"));
1735  cmd.replace("%w", exec); // %w indicates where to insert the command string
1736  }
1737  else
1738  {
1739  // Set the command to execute.
1740  // Put it in quotes in case it contains characters such as [>|;].
1741  TQString exec = KShellProcess::quote(command);
1742  if (cmd.find("%c") >= 0)
1743  cmd.replace("%c", exec); // %c indicates where to insert the command string
1744  else
1745  cmd.append(exec); // otherwise, simply append the command string
1746  }
1747  }
1748  else
1749  {
1750  cmd = command;
1751  comms = TDEProcess::AllOutput;
1752  }
1753  ShellProcess* proc = new ShellProcess(cmd);
1754  connect(proc, TQ_SIGNAL(shellExited(ShellProcess*)), TQ_SLOT(slotCommandExited(ShellProcess*)));
1755  TQGuardedPtr<ShellProcess> logproc = 0;
1756  if (comms == TDEProcess::AllOutput && !event.logFile().isEmpty())
1757  {
1758  // Output is to be appended to a log file.
1759  // Set up a logging process to write the command's output to.
1760  connect(proc, TQ_SIGNAL(receivedStdout(TDEProcess*,char*,int)), TQ_SLOT(slotCommandOutput(TDEProcess*,char*,int)));
1761  connect(proc, TQ_SIGNAL(receivedStderr(TDEProcess*,char*,int)), TQ_SLOT(slotCommandOutput(TDEProcess*,char*,int)));
1762  logproc = new ShellProcess(TQString::fromLatin1("cat >>%1").arg(event.logFile()));
1763  connect(logproc, TQ_SIGNAL(shellExited(ShellProcess*)), TQ_SLOT(slotLogProcExited(ShellProcess*)));
1764  logproc->start(TDEProcess::Stdin);
1765  TQCString heading;
1766  if (alarm && alarm->dateTime().isValid())
1767  {
1768  TQString dateTime = alarm->dateTime().isDateOnly()
1769  ? TDEGlobal::locale()->formatDate(alarm->dateTime().date(), true)
1770  : TDEGlobal::locale()->formatDateTime(alarm->dateTime().dateTime());
1771  heading.sprintf("\n******* KAlarm %s *******\n", dateTime.latin1());
1772  }
1773  else
1774  heading = "\n******* KAlarm *******\n";
1775  logproc->writeStdin(heading, heading.length()+1);
1776  }
1777  ProcData* pd = new ProcData(proc, logproc, new KAEvent(event), (alarm ? new KAAlarm(*alarm) : 0), flags);
1778  if (flags & ProcData::TEMP_FILE)
1779  pd->tempFiles += command;
1780  if (!tmpXtermFile.isEmpty())
1781  pd->tempFiles += tmpXtermFile;
1782  mCommandProcesses.append(pd);
1783  if (proc->start(comms))
1784  return proc;
1785 
1786  // Error executing command - report it
1787  kdError(5950) << "KAlarmApp::doShellCommand(): command failed to start\n";
1788  commandErrorMsg(proc, event, alarm, flags);
1789  mCommandProcesses.remove(pd);
1790  delete pd;
1791  return 0;
1792 }
1793 
1794 /******************************************************************************
1795 * Create a temporary script file containing the specified command string.
1796 * Reply = path of temporary file, or null string if error.
1797 */
1798 TQString KAlarmApp::createTempScriptFile(const TQString& command, bool insertShell, const KAEvent& event, const KAAlarm& alarm)
1799 {
1800  KTempFile tmpFile(TQString(), TQString(), 0700);
1801  tmpFile.setAutoDelete(false); // don't delete file when it is destructed
1802  TQTextStream* stream = tmpFile.textStream();
1803  if (!stream)
1804  kdError(5950) << "KAlarmApp::createTempScript(): Unable to create a temporary script file" << endl;
1805  else
1806  {
1807  if (insertShell)
1808  *stream << "#!" << ShellProcess::shellPath() << "\n";
1809  *stream << command;
1810  tmpFile.close();
1811  if (tmpFile.status())
1812  kdError(5950) << "KAlarmApp::createTempScript(): Error " << tmpFile.status() << " writing to temporary script file" << endl;
1813  else
1814  return tmpFile.name();
1815  }
1816 
1817  TQStringList errmsgs(i18n("Error creating temporary script file"));
1818  (new MessageWin(event, alarm.dateTime(), errmsgs))->show();
1819  return TQString();
1820 }
1821 
1822 /******************************************************************************
1823 * Called when an executing command alarm sends output to stdout or stderr.
1824 */
1825 void KAlarmApp::slotCommandOutput(TDEProcess* proc, char* buffer, int bufflen)
1826 {
1827 //kdDebug(5950) << "KAlarmApp::slotCommandOutput(): '" << TQCString(buffer, bufflen+1) << "'\n";
1828  // Find this command in the command list
1829  for (TQValueList<ProcData*>::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it)
1830  {
1831  ProcData* pd = *it;
1832  if (pd->process == proc && pd->logProcess)
1833  {
1834  pd->logProcess->writeStdin(buffer, bufflen);
1835  break;
1836  }
1837  }
1838 }
1839 
1840 /******************************************************************************
1841 * Called when a logging process completes.
1842 */
1843 void KAlarmApp::slotLogProcExited(ShellProcess* proc)
1844 {
1845  // Because it's held as a guarded pointer in the ProcData structure,
1846  // we don't need to set any pointers to zero.
1847  delete proc;
1848 }
1849 
1850 /******************************************************************************
1851 * Called when a command alarm's execution completes.
1852 */
1853 void KAlarmApp::slotCommandExited(ShellProcess* proc)
1854 {
1855  kdDebug(5950) << "KAlarmApp::slotCommandExited()\n";
1856  // Find this command in the command list
1857  for (TQValueList<ProcData*>::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it)
1858  {
1859  ProcData* pd = *it;
1860  if (pd->process == proc)
1861  {
1862  // Found the command
1863  if (pd->logProcess)
1864  pd->logProcess->stdinExit(); // terminate the logging process
1865 
1866  // Check its exit status
1867  if (!proc->normalExit())
1868  {
1869  TQString errmsg = proc->errorMessage();
1870  kdWarning(5950) << "KAlarmApp::slotCommandExited(" << pd->event->cleanText() << "): " << errmsg << endl;
1871  if (pd->messageBoxParent)
1872  {
1873  // Close the existing informational KMessageBox for this process
1874  TQObjectList* dialogs = pd->messageBoxParent->queryList("KDialogBase", 0, false, true);
1875  KDialogBase* dialog = (KDialogBase*)dialogs->getFirst();
1876  delete dialog;
1877  delete dialogs;
1878  if (!pd->tempFile())
1879  {
1880  errmsg += '\n';
1881  errmsg += proc->command();
1882  }
1883  KMessageBox::error(pd->messageBoxParent, errmsg);
1884  }
1885  else
1886  commandErrorMsg(proc, *pd->event, pd->alarm, pd->flags);
1887  }
1888  if (pd->preAction())
1889  execAlarm(*pd->event, *pd->alarm, pd->reschedule(), pd->allowDefer(), true);
1890  mCommandProcesses.remove(it);
1891  delete pd;
1892  break;
1893  }
1894  }
1895 
1896  // If there are now no executing shell commands, quit if a quit was queued
1897  if (mPendingQuit && mCommandProcesses.isEmpty())
1898  quitIf(mPendingQuitCode);
1899 }
1900 
1901 /******************************************************************************
1902 * Output an error message for a shell command.
1903 */
1904 void KAlarmApp::commandErrorMsg(const ShellProcess* proc, const KAEvent& event, const KAAlarm* alarm, int flags)
1905 {
1906  TQStringList errmsgs;
1907  if (flags & ProcData::PRE_ACTION)
1908  errmsgs += i18n("Pre-alarm action:");
1909  else if (flags & ProcData::POST_ACTION)
1910  errmsgs += i18n("Post-alarm action:");
1911  errmsgs += proc->errorMessage();
1912  if (!(flags & ProcData::TEMP_FILE))
1913  errmsgs += proc->command();
1914  (new MessageWin(event, (alarm ? alarm->dateTime() : DateTime()), errmsgs))->show();
1915 }
1916 
1917 /******************************************************************************
1918 * Notes that an informational KMessageBox is displayed for this process.
1919 */
1920 void KAlarmApp::commandMessage(ShellProcess* proc, TQWidget* parent)
1921 {
1922  // Find this command in the command list
1923  for (TQValueList<ProcData*>::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it)
1924  {
1925  ProcData* pd = *it;
1926  if (pd->process == proc)
1927  {
1928  pd->messageBoxParent = parent;
1929  break;
1930  }
1931  }
1932 }
1933 
1934 /******************************************************************************
1935 * Set up remaining DCOP handlers and start processing DCOP calls.
1936 */
1937 void KAlarmApp::setUpDcop()
1938 {
1939  if (!mInitialised)
1940  {
1941  mInitialised = true; // we're now ready to handle DCOP calls
1942  Daemon::createDcopHandler();
1943  TQTimer::singleShot(0, this, TQ_SLOT(processQueue())); // process anything already queued
1944  }
1945 }
1946 
1947 /******************************************************************************
1948 * If this is the first time through, open the calendar file, optionally start
1949 * the alarm daemon and register with it, and set up the DCOP handler.
1950 */
1951 bool KAlarmApp::initCheck(bool calendarOnly)
1952 {
1953  bool startdaemon;
1954  AlarmCalendar* cal = AlarmCalendar::activeCalendar();
1955  if (!cal->isOpen())
1956  {
1957  kdDebug(5950) << "KAlarmApp::initCheck(): opening active calendar\n";
1958 
1959  // First time through. Open the calendar file.
1960  if (!cal->open())
1961  return false;
1962 
1963  if (!mStartOfDay.isValid())
1964  changeStartOfDay(); // start of day time has changed, so adjust date-only alarms
1965 
1966  /* Need to open the display calendar now, since otherwise if the daemon
1967  * immediately notifies display alarms, they will often be processed while
1968  * redisplayAlarms() is executing open() (but before open() completes),
1969  * which causes problems!!
1970  */
1971  AlarmCalendar::displayCalendar()->open();
1972 
1973  /* Need to open the expired alarm calendar now, since otherwise if the daemon
1974  * immediately notifies multiple alarms, the second alarm is likely to be
1975  * processed while the calendar is executing open() (but before open() completes),
1976  * which causes a hang!!
1977  */
1978  AlarmCalendar::expiredCalendar()->open();
1979  AlarmCalendar::expiredCalendar()->setPurgeDays(theInstance->mPrefsExpiredKeepDays);
1980 
1981  startdaemon = true;
1982  }
1983  else
1984  startdaemon = !Daemon::isRegistered();
1985 
1986  if (!calendarOnly)
1987  {
1988  setUpDcop(); // start processing DCOP calls
1989  if (startdaemon)
1990  Daemon::start(); // make sure the alarm daemon is running
1991  }
1992  return true;
1993 }
1994 
1995 /******************************************************************************
1996 * Convert the --time parameter string into a date/time or date value.
1997 * The parameter is in the form [[[yyyy-]mm-]dd-]hh:mm or yyyy-mm-dd.
1998 * Reply = true if successful.
1999 */
2000 static bool convWakeTime(const TQCString& timeParam, TQDateTime& dateTime, bool& noTime)
2001 {
2002  if (timeParam.length() > 19)
2003  return false;
2004  char timeStr[20];
2005  strcpy(timeStr, timeParam);
2006  int dt[5] = { -1, -1, -1, -1, -1 };
2007  char* s;
2008  char* end;
2009  // Get the minute value
2010  if ((s = strchr(timeStr, ':')) == 0)
2011  noTime = true;
2012  else
2013  {
2014  noTime = false;
2015  *s++ = 0;
2016  dt[4] = strtoul(s, &end, 10);
2017  if (end == s || *end || dt[4] >= 60)
2018  return false;
2019  // Get the hour value
2020  if ((s = strrchr(timeStr, '-')) == 0)
2021  s = timeStr;
2022  else
2023  *s++ = 0;
2024  dt[3] = strtoul(s, &end, 10);
2025  if (end == s || *end || dt[3] >= 24)
2026  return false;
2027  }
2028  bool dateSet = false;
2029  if (s != timeStr)
2030  {
2031  dateSet = true;
2032  // Get the day value
2033  if ((s = strrchr(timeStr, '-')) == 0)
2034  s = timeStr;
2035  else
2036  *s++ = 0;
2037  dt[2] = strtoul(s, &end, 10);
2038  if (end == s || *end || dt[2] == 0 || dt[2] > 31)
2039  return false;
2040  if (s != timeStr)
2041  {
2042  // Get the month value
2043  if ((s = strrchr(timeStr, '-')) == 0)
2044  s = timeStr;
2045  else
2046  *s++ = 0;
2047  dt[1] = strtoul(s, &end, 10);
2048  if (end == s || *end || dt[1] == 0 || dt[1] > 12)
2049  return false;
2050  if (s != timeStr)
2051  {
2052  // Get the year value
2053  dt[0] = strtoul(timeStr, &end, 10);
2054  if (end == timeStr || *end)
2055  return false;
2056  }
2057  }
2058  }
2059 
2060  TQDate date(dt[0], dt[1], dt[2]);
2061  TQTime time(0, 0, 0);
2062  if (noTime)
2063  {
2064  // No time was specified, so the full date must have been specified
2065  if (dt[0] < 0)
2066  return false;
2067  }
2068  else
2069  {
2070  // Compile the values into a date/time structure
2071  TQDateTime now = TQDateTime::currentDateTime();
2072  if (dt[0] < 0)
2073  date.setYMD(now.date().year(),
2074  (dt[1] < 0 ? now.date().month() : dt[1]),
2075  (dt[2] < 0 ? now.date().day() : dt[2]));
2076  time.setHMS(dt[3], dt[4], 0);
2077  if (!dateSet && time < now.time())
2078  date = date.addDays(1);
2079  }
2080  if (!date.isValid())
2081  return false;
2082  dateTime.setDate(date);
2083  dateTime.setTime(time);
2084  return true;
2085 }
2086 
2087 /******************************************************************************
2088 * Convert a time interval command line parameter.
2089 * 'timeInterval' receives the count for the recurType. If 'allowMonthYear' is
2090 * false, 'timeInterval' is converted to minutes.
2091 * Reply = true if successful.
2092 */
2093 static bool convInterval(const TQCString& timeParam, KARecurrence::Type& recurType, int& timeInterval, bool allowMonthYear)
2094 {
2095  TQCString timeString = timeParam;
2096  // Get the recurrence interval
2097  bool ok = true;
2098  uint interval = 0;
2099  bool negative = (timeString[0] == '-');
2100  if (negative)
2101  timeString = timeString.right(1);
2102  uint length = timeString.length();
2103  switch (timeString[length - 1])
2104  {
2105  case 'Y':
2106  if (!allowMonthYear)
2107  ok = false;
2108  recurType = KARecurrence::ANNUAL_DATE;
2109  timeString = timeString.left(length - 1);
2110  break;
2111  case 'W':
2112  recurType = KARecurrence::WEEKLY;
2113  timeString = timeString.left(length - 1);
2114  break;
2115  case 'D':
2116  recurType = KARecurrence::DAILY;
2117  timeString = timeString.left(length - 1);
2118  break;
2119  case 'M':
2120  {
2121  int i = timeString.find('H');
2122  if (i < 0)
2123  {
2124  if (!allowMonthYear)
2125  ok = false;
2126  recurType = KARecurrence::MONTHLY_DAY;
2127  timeString = timeString.left(length - 1);
2128  }
2129  else
2130  {
2131  recurType = KARecurrence::MINUTELY;
2132  interval = timeString.left(i).toUInt(&ok) * 60;
2133  timeString = timeString.mid(i + 1, length - i - 2);
2134  }
2135  break;
2136  }
2137  default: // should be a digit
2138  recurType = KARecurrence::MINUTELY;
2139  break;
2140  }
2141  if (ok)
2142  interval += timeString.toUInt(&ok);
2143  if (!allowMonthYear)
2144  {
2145  // Convert time interval to minutes
2146  switch (recurType)
2147  {
2148  case KARecurrence::WEEKLY:
2149  interval *= 7;
2150  // fall through to DAILY
2151  case KARecurrence::DAILY:
2152  interval *= 24*60;
2153  break;
2154  default:
2155  break;
2156  }
2157  }
2158  timeInterval = static_cast<int>(interval);
2159  if (negative)
2160  timeInterval = -timeInterval;
2161  return ok;
2162 }
2163 
2164 KAlarmApp::ProcData::ProcData(ShellProcess* p, ShellProcess* logp, KAEvent* e, KAAlarm* a, int f)
2165  : process(p),
2166  logProcess(logp),
2167  event(e),
2168  alarm(a),
2169  messageBoxParent(0),
2170  flags(f)
2171 { }
2172 
2173 KAlarmApp::ProcData::~ProcData()
2174 {
2175  while (!tempFiles.isEmpty())
2176  {
2177  // Delete the temporary file called by the XTerm command
2178  TQFile f(tempFiles.first());
2179  f.remove();
2180  tempFiles.remove(tempFiles.begin());
2181  }
2182  delete process;
2183  delete event;
2184  delete alarm;
2185 }
Provides read and write access to calendar files.
Definition: alarmcalendar.h:37
KAEvent corresponds to a KCal::Event instance.
Definition: alarmevent.h:232
static void setApplication(const TQString &app, const TQString &productID)
MessageWin: A window to display an alarm message.
Definition: messagewin.h:45
miscellaneous functions
main application window
displays an alarm message