diff --git a/Changelog b/Changelog index 86aa9c82..448d4a64 100644 --- a/Changelog +++ b/Changelog @@ -1,1190 +1,1193 @@ KAlarm Change Log === Version 3.0.0 --- 26 April 2020 === + Provide option to use file system resources instead of Akonadi resources. + Enable selection of multiple calendar files in Import Alarms dialogue. + Show alarm calendars sorted by name in calendars list. + Refactor AlarmCalendar to split out resources and display calendars. +=== Version 2.14.2 (KDE Applications 20.04.2) --- 13 May 2020 === ++ Make multiple KAlarm invocations work (Qt >5.12, Frameworks >5.60) [KDE Bug 417108] + === Version 2.14.1 (KDE Applications 20.04.1) --- 11 May 2020 === + Correctly interpret resource IDs in command line and DBus calls. + Fix sizing and reconfiguration of columns in alarm and template lists. === Version 2.14.0 (KDE Applications 20.04) --- 27 March 2020 === + Warn user if archiving but no default archived alarms calendar is set. + Fix some error messages not being displayed. + Refactor to use generic resource classes (part 2). === Version 2.13.3 (KDE Applications 19.12.3) --- 20 February 2020 === + Fix failure of command line options requiring calendar access [KDE Bug 417108] === Version 2.13.2 (KDE Applications 19.12.2) --- 9 January 2020 === + Add Show/Hide Menubar menu option; change New Email Alarm shortcut to Ctrl-L. === Version 2.13.1 (KDE Applications 19.12.1) --- 30 December 2019 === + Make defer dialogue accessible when a full screen window is active [KDE Bug 414383] + Only show 'Cancel Deferral' in defer dialogue if a deferral is already active. === Version 2.13.0 (KDE Applications 19.12) --- 19 November 2019 === + Fix user not always being prompted to update new resource if in old format. + Terminate application after executing 'kalarm --list'. + Fix alarm type column being too wide in alarm template list. + Fix failure to display image when alarm is configured to display an image file. + Fix failure to set no-autostart for non-KDE desktops, if no autostart directory exists. + Refactor to use generic resource classes (part 1). === Version 2.12.8 (KDE Applications 19.08.3) --- 16 October 2019 === + Fix error on redo of an active alarm deletion. + Archive repeat-at-login alarms if previously triggered, when they are deleted. + Fix layout of defer alarm dialogue. + Make user settings changes take effect immediately (fixes regression introduced in 2.10.11). === Version 2.12.7 (KDE Applications 19.08.2) --- 7 October 2019 === + Show correct read-only status of an alarm in its context menu. + Fix errors deleting and reactivating alarms (regression introduced in 2.12.5). + Fix error on undo of an active alarm deletion. + Don't trigger repeat-at-login alarms when they are edited or imported. === Version 2.12.6 (KDE Applications 19.08.1) --- 26 August 2019 === + Fix crash sometimes when a resource is enabled [KDE Bug 410596] + Fix D-Bus alarm creation failing if time zone is omitted from start time [KDE Bug 411296] + Fix command line options which don't work if KAlarm not already running: --edit, --list, --triggerEvent, --cancelEvent. === Version 2.12.5 (KDE Applications 19.08) --- 26 July 2019 === + Enable alarm list columns to be hidden using context menu on list header [KDE Bug 397093] + Fix regression introduced in version 2.12.0: Show time zone abbreviation in message window if alarm time has non-local time zone. + If only one writable archived alarm calendar exists, automatically set it as the default. + Don't allow user to create a new resource using same calendar file as an existing resource. + Remove duplicate resources (i.e. which use the same calendar file) at startup [KDE Bug 403124] + Fix drag and drop of emails from KMail, and KMail button in message window. + Improve drag and drop of events and todos from KOrganizer. === Version 2.12.4 (KDE Applications 19.04.3) --- 4 July 2019 === + Fix calendar resource dialogue not configuring resource correctly [KDE Bug 407882] + Fix calendar resource dialogue creating new resources unusable until restart [KDE Bug 407882] + Enable resource after creating with the calendar resource dialogue [KDE Bug 407882] + Fix colour and alarm type columns being too wide in alarm list. === Version 2.12.3 (KDE Applications 19.04.2) --- 23 May 2019 === * Fix calendar configuration dialogue not appearing. * Fix errors creating calendar resources on first run of KAlarm [KDE Bug 407544] * Display alarm message windows within current screen in multi-head systems. === Version 2.12.2 (KDE Applications 18.08.2) --- 27 September 2018 === * Fix Defer button being disabled for recurring alarms [KDE Bug 398658] === Version 2.12.1 (KDE Applications 18.08.1) --- 18 August 2018 === * Align and right adjust 'Time to' column values in main window [KDE Bug 397130] * Remove seconds values from Time column (erroneously added in 2.12.0). === Version 2.12.0 (KDE Applications 18.08) --- 29 July 2018 === * Use KAlarmCal::KADateTime instead of deprecated KDateTime. * Remove 'clock time' option, in favour of local system time zone. * Fix times being truncated and showing ellipsis in main window [KDE Bug 365257] * Fix evaluation of work days. * Fix reminder-once alarms not being correctly loaded from calendar file. * Fix some regressions introduced in version 2.11.0, including: Make global shortcuts available. Default sound file selection dialogue to the system sound files directory. === Version 2.11.16 (KDE Applications 17.04.1) --- 15 April 2017 === * Fix option text for using default email address from KMail/System Settings [KDE Bug 378722] === Version 2.11.15 (KDE Applications 17.04) --- 15 January 2017 === * Report if terminal for command alarms is not configured. * Don't allow 'auto-hide in system tray' on Unity desktop [KDE Bug 373848] === Version 2.11.14 --- 19 February 2017 === * Fix not showing main window if activated again while already running with --tray [KDE Bug 374520] * Fix --help, --version and option errors not being reported if KAlarm is already running. * Make command options --edit-new-* work [KDE Bug 376209] === Version 2.11.13 (KDE Applications 16.12.2) --- 29 January 2017 === * Fix system tray icon used for "some alarms disabled" * Improved system tray icons (requires Plasma 5.9) [KDE Bug 362631] * Don't show misleading "Failed to update alarm" if command alarm fails [KDE Bug 375615] === Version 2.11.12 (KDE Applications 16.12.1) --- 1 January 2017 === * Fix Export Alarms file save error [KDE Bug 374337] * Fix arrow/page up/down keys not working in date edit control (needs KDE Frameworks 5.30) [KDE Bug 373886] === Version 2.11.11 (KDE Applications 16.12.0) --- 16 November 2016 === * Fix crash on exit [KDE Bug 372223] === Version 2.11.10 (KDE Applications 16.08.3) --- 31 October 2016 === * Fix default calendar files not being created on first run [KDE Bug 362962] * Fix crash when a second instance of KAlarm is started [KDE Bug 371628] * Don't output error messages about temporary files in directory calendar [KDE Bug 370627] === Version 2.11.9 (KDE Applications 16.08.1) --- 18 August 2016 === * Prevent KAlarm autostarting on non-KDE desktops if it has never been run [KDE Bug 366562] === Version 2.11.8 (KDE Applications 16.08.0) --- 13 July 2016 === * Use the default time format in alarm list and system tray status popup [KAlarm Forum: https://forum.kde.org/viewtopic.php?f=229&t=133788] === Version 2.11.7 (KDE Applications 16.04.3) --- 11 June 2016 === * Always use current setting for email sender address when sending emails [KDE Bug 359163] === Version 2.11.6 (KDE Applications 16.04.1) --- 20 April 2016 === * Prevent KAlarm autostarting on non-KDE desktops if start-at-login is disabled [KAlarm Forum: https://forum.kde.org/viewtopic.php?f=229&t=131410] === Version 2.11.5 (KDE Applications 16.04.0) --- 13 April 2016 === * Fix alarm times out by an hour in daylight savings time (needs kcalcore 16.04) [KDE Bug 336738] * Don't show spurious extra calendar after adding new calendar [KDE Bug 361543] * Fix crash when adding new calendar [KDE Bugs 361539, 361717] === Version 2.11.4 (KDE Applications 15.12.3) --- 1 February 2016 === * Fix reminder time edit being covered by 'in advance' combo [KDE Bug 357018] * Fix crash after editing an alarm, if spell check is enabled [KDE Bug 356048] * Fix occasional crash on startup [KDE Bug 358217] * Fix specification on command line of a reminder after the alarm. * Fix deferral time of date-only recurring alarms [KDE Bug 346060] * Fix frequency edit field missing from recurrence editor. === Version 2.11.3 (KDE Applications 15.08.3) --- 4 November 2015 === * Re-enable use of sendmail for email alarms. * Fix conversion error in sub-repetition interval from command line. === Version 2.11.2 (KDE Applications 15.08.2) --- 24 September 2015 === * Enable typing into New Alarm dialogue while alarm is displayed (Unity desktop) [KDE Bug 352889] === Version 2.11.1 (KDE Applications 15.08.1) --- 1 September 2015 === * Fix conversion error in sub-repetition value from command line or D-Bus command. === Version 2.11.0 (KDE Applications 15.08.0) --- 30 July 2015 === * Use KDE Frameworks. * Disable use of sendmail for email alarms, due to removal from Akonadi. === Version 2.10.12 (KDE 4.14.2) --- 30 September 2014 === * Make New Audio Alarm dialogue use sound file repeat preference setting. === Version 2.10.11 (KDE 4.14.0) --- 12 August 2014 === * [Akonadi] Fix alarms not being redisplayed after Akonadi server restarts (requires kdepimlibs 4.14.0) [KDE Bug 336942] === Version 2.10.10 (KDE 4.13.2) --- 10 May 2014 === * [Akonadi] Fix no Defer button in alarm windows restored after login [KDE Bug 334334] * Fix display of duplicate alarm windows after login. === Version 2.10.9 (KDE 4.13.1) --- 4 May 2014 === * [Akonadi] Fix no Defer button in alarm windows restored after crash [KDE Bug 334334] === Version 2.10.8 (KDE 4.12.5) --- 18 April 2014 === * [Akonadi] Fix wrong startup message about no writable active alarm calendar. * [Akonadi] Fix setting Akonadi resource read-only making it unusable (requires kdepim-runtime 4.12.5) [KDE Bug 332889] === Version 2.10.7 (KDE 4.12.4, 4.13.0) --- 21 March 2014 === * [Akonadi] Fix deletion of alarm copies from KOrganiser not working. * Fix crash after session restoration has nothing to restore [KDE Bug 331719] * Prevent data in birthday import dialogue being editable. === Version 2.10.6 (KDE 4.11.1) --- 27 August 2013 === * [Akonadi] Fix error saving template when closing Edit Template dialogue [KDE Bug 323965] === Version 2.10.5 (KDE 4.11.0) --- 3 August 2013 === * Fix memory leak whenever the edit dialogue is closed. * Fix auto-close alarms not displaying when KAlarm defaults to UTC time zone. * Fix display alarm deferral limit when KAlarm defaults to UTC time zone. === Version 2.10.4 (KDE 4.11 beta2) --- 15 June 2013 === * Show startup warning if no writable active alarm calendar is enabled [KDE Bug 316338] === Version 2.10.3 (KDE 4.10.5) --- 15 June 2013 === * Fix sound repetition pause not working in audio alarms [KDE Bug 319261] * Fix Stop Play button being left enabled after closing alarm window. === Version 2.10.2 (KDE 4.10.4) --- 4 May 2013 === * [Akonadi] Fix infinite loop on shutdown if display alarms are active [KDE Bug 317806] === Version 2.10.1 (KDE 4.10.0) --- 10 December 2012 === * [Akonadi] Fix memory leak when an alarm message window is displayed. * [Akonadi] Fix memory leak on alarm edit. === Version 2.10.0 (KDE 4.10 beta1)--- 13 November 2012 === * Add --list command line option to list scheduled alarms to stdout. * Add 'list' D-Bus command to return list of scheduled alarms. * [Akonadi] Wait until calendars are populated before using them at startup. === Version 2.9.3 (KDE 4.9.4) --- 13 November 2012 === * [Akonadi] Fix alarm list not sorting new alarms when calendar is enabled [KDE Bug 306178] === Version 2.9.2 (KDE 4.9.1) --- 22 August 2012 === * Fix Quit not working in system tray icon context menu. * [KResources] Fix KAlarm button not highlighting the alarm in the main window [KDE Bug 266082] === Version 2.9.1 (KDE 4.9.0) --- 7 July 2012 === * Add option to execute a pre-alarm action before deferred alarms. * Provide options to auto-hide system tray icon when no alarms are due. * Store KAlarm version and backend in config file. === Version 2.8.6 (KDE 4.8.5) --- 14 July 2012 === * [Akonadi] Don't display calendars which have no Akonadi resource. * [Akonadi resources] Fix resource if config is missing. * [Akonadi resources] Make resource work if location is set by path OR URL. * Fix crash when closing alarm window for alarm which plays audio file. * Fix "server did not accept the sender address" errors sending emails [KDE Bug 301946] === Version 2.8.5 (KDE 4.8.4) --- 6 June 2012 === * [Akonadi] Warn user and disable KAlarm if Akonadi fails to run [KDE Bug 300083] * [Akonadi] Fix crash when saving new alarm [KDE Bug Bug 300376] === Version 2.8.3 (KDE 4.8.3) --- 22 April 2012 === * Store KAlarm version and backend in config file. * Use the last selected sound file picker directory as the default next time. === Version 2.8.2 (KDE 4.8.2) --- 29 March 2012 === * [Akonadi] Fix error saving changed alarms when closing Edit Alarm dialogue. * [Akonadi] Show old-format calendars in read-only colour in calendar list. * [KResources] Fail cleanly if calendar resources fail to open [KDE Bug 296383] * Prevent multiple email success messages after Try used in Edit Alarm dialogue. === Version 2.8.1 (KDE 4.8.1) --- 19 February 2012 === * [Akonadi] Don't give option to save new alarms in old format calendars. * [Akonadi] Prevent duplicate prompts to update format of new calendar resource. * [Akonadi] Automatically disable duplicated calendar resources [KDE Bug 293193] * [Akonadi] Fix errors when creating default calendar resources [KDE Bug 293208] * [Akonadi] Prevent multiple standard calendars for any alarm type. * [Akonadi] Fix various crashes. * Output cmake error if Akonadi option incompatible with kdepimlibs/kalarmcal. === Version 2.8.0 (KDE 4.8.0) --- 16 January 2012 === * Use Akonadi as the default calendar access method. * Use configurable colours and KDE colour scheme for calendar list. * Allow user to stop playback after clicking Try in audio alarm edit dialogue. === Version 2.7.5 (KDE 4.7.4) --- 23 November 2011 === * Fix crash due to audio thread not being correctly deleted. === Version 2.7.4 (KDE 4.7.1) --- 28 August 2011 === * Fix crash when last recurrence of late-cancel alarm triggers too late. * Fix conversion of pre-version 1.4.14 subsidiary alarms. * Fix new alarm not being scheduled after editing alarm from alarm window. * Don't do search if invalid regular expression is entered in Find dialogue. * Don't prevent interaction with alarm windows when a prompt or warning message window is displayed [using KDE 4.7.1 or later]. * Only reset visible tab in multi-tab settings sections when Defaults is clicked in Configuration dialogue, and Current tab option is selected. * Disable command output option for display alarms in edit alarm dialogue if user not authorised to run shell commands. * Always output "not authorised" error message if unauthorised user tries to run shell commands. === Version 2.7.3 --- 26 July 2011 === * Fix crash when Wake From Suspend dialogue is shown with no alarm selected. === Version 2.7.2 --- 15 July 2011 === * Fix KAlarm not quitting when no visible windows or system tray icon remain. * Cancel wake-from-suspend if alarm is disabled, or if all alarms are disabled. * Various improvements and bug fixes to Wake From Suspend dialogue. * In calendar list show calendar colours by text background, not coloured square. * In alarm list show multi-line tooltip for alarm text when appropriate. === Version 2.7.1 (KDE 4.7.0) --- 6 July 2011 === * Make wake-from-suspend schedule a time-from-now, to make it work correctly on systems whose hardware clock is out of sync with the system clock. * Include Content-Transfer-Encoding header in emails to allow correct display. === Version 2.7.0 --- 9 May 2011 === * Add option to set a reminder AFTER the main alarm. * Add option to wake computer from suspend when a selected alarm is triggered. * Add command line option to disable alarm monitoring. * Replace EMAILID, SPEAK, ERRCANCEL, ERRNOSHOW calendar properties with FLAGS property parameters. === Version 2.6.3 --- 27 April 2011 === * Add option to not notify execution errors for pre-alarm actions. * Set environment variable KALARM_UID to event UID for pre- & post-alarm actions. * Warn user if only UTC time zone is available (if ktimezoned not installed). * Don't reactivate start-at-login without prompting, after user switches it off, except if KAlarm is session restored. * Show error message and set read-only if location is blank for new resource. * Fix crash on some systems when New Alarm dialogue is displayed from system tray icon menu. * Fix KAlarm button in alarm window not always showing main window and not highlighting the alarm in the main window. * Move New Alarm From Template action into New alarm menu to simplify toolbar. === Version 2.4.11 (KDEPIM 4.4.11) --- 16 April 2011 === * Fix bad borders round left hand buttons of time spinboxes in Oxygen style. * Fix initialisation of library global statics. * Ensure sound volume is not out of range when reading from calendar. * Fix New Alarm dialogue from system tray menu restoring other windows. === Version 2.4.10 (KDEPIM 4.4.8) --- 2 December 2010 === * Fix KAlarm showing in system tray at login when configured not to show in tray. * Fix working-time-only alarms not triggering if KAlarm is started up outside working hours, after the last trigger time during working hours was missed. * Don't quit if no window is visible when 'show in system tray' is deselected. * Disable Defer button in new message window when deferral limit has been reached. * Fix reminder time shown when editing a non-recurring alarm's deferred reminder. * Fix conversion of pre-version 1.9.10 non-recurring alarms with simple repetition. * Make disabled system tray icon more distinguishable for colour blind users. === Version 2.4.9 (KDEPIM 4.4.7) --- 19 October 2010 === * Fix crash if alarm triggers while its deletion confirmation prompt is visible. * Fix crash when Try button is clicked while creating new display alarm. * Fix crash on KAlarm exit. * Fix possible crash when enabling individual alarms. * Prevent long file name from expanding the width of file display alarm window. * Allow pre- & post-alarm actions for alarms whose text is generated by a command. * Combine 4 New Alarm icons in toolbar, to fix icon texts not fitting into width. === Version 2.4.8 (KDEPIM 4.4.6) --- 4 September 2010 === * Fix crash when a reminder alarm is being redisplayed. * Fix possible crash: on alarm deletion, always update next alarm to trigger. * Fix Sound File selection dialogue Play button not playing any sound. * Always show current storage location choice in Configuration dialogue. * Fix inability to leave file name blank in audio alarm templates. * Fix changes to volume not enabling OK button when editing an audio alarm template with no audio file specified. === Version 2.4.7 (KDE 4.4.5) --- 3 June 2010 === * Fix inability to defer non-recurring alarms. * Fix crash when selecting calendar type in calendar selector, if text widths and selector width are "exactly wrong". * Fix loss of time zone specification for date only alarms when converting a pre-2.3.2 calendar, if start-of-day time in calendar is not midnight. * Enable alarm edit dialogue Time Zone button in read-only mode. === Version 2.4.6 (KDE 4.4.4) --- 20 May 2010 === * Fix alarm edit dialog not saving changes when invoked from alarm message window's Edit button. * Fix main window close action not working when system tray icon is not shown. === Version 2.4.5 (KDE 4.4.3) --- 7 April 2010 === * Fix audio files playing silently when no volume level has been specified. === Version 2.4.4 (KDE 4.4.2) --- 17 March 2010 === * Fix display alarm whose text is generated by a command and which has an audio file, being converted into an audio-only alarm when reloaded. === Version 2.4.3 (KDE 4.4.1) --- 21 February 2010 === * Disable resource calendars which contain only wrong alarm types. === Version 2.4.2 (KDE 4.4.0) --- 30 January 2010 === * Fix non-ASCII text being corrupted in emails sent by KAlarm. * Show error message if selected email identity has no email address. === Version 2.4.1 (KDE 4.4.0 RC1) --- 8 December 2009 === * Fix date-only recurring alarms triggering repeatedly at high frequency. === Version 2.4.0 --- 24 November 2009 === * New audio alarm option, without displaying alarm window. * Add configuration setting for event duration for alarms copied to KOrganizer. * Provide 'any time' option in Defer Alarm dialogue, for date-only alarms. * Use KDE system settings to determine default working days in the week. * Improve organisation of main menu. * If dual screens, show alarm in other screen if any full screen window exists. * Fix recurring date-only alarm triggering repeatedly and eating up CPU, if the start-of-day time is after midnight and the alarm is due, but current UTC time of day is earlier than the start-of-day time of day in the alarm's time zone. * Update date-only alarm trigger times when user changes the start-of-day time. * Don't write start-of-day time into calendar, to avoid clashes if it is shared. * Don't waste processing time calculating next trigger time for archived alarms. * Disable 'New Alarm from Template' action when no alarm templates exist. * Interpret '~' (i.e. home directory) properly in entered file names. * Fix crash if calendar formats are updated at login, during session restoration. * Fix crash if editing alarm from alarm window Edit button, and window changes from reminder to normal, or window changes from at-login to final at-login trigger time, or window auto-closes. * Prevent infinite loop if NEXTRECUR time in alarm is before alarm start time. * Fix error saving the alarm after editing a repeat-at-login alarm. * Don't set reminder/late-cancel/show-in-KOrganizer when saving repeat-at-login alarms. * Improve error feedback in sound file selection. * Prevent sound file configuration dialogue closing after showing error message. === Version 2.3.0 --- 10 July 2009 === * Alarm edit: warn user if entered start time needs adjustment to fit recurrence. * Command alarm edit: show error message if no command/script has been entered. * Allow use of other command line options with --edit-new-* to initialise edit dialogue options. * Improve detection of conflicting command line options. === Version 2.2.4 --- 23 June 2009 === * Alarm edit: keep existing display file name if file select dialogue cancelled. * Guard against crashes if KAlarm quits while a modal dialogue is open. * Fix crash creating alarm from command line, if KAlarm not already running. * Fix --reminder-once command line option being treated same as --reminder. === Version 2.2.3 --- 14 June 2009 === * Fix crash when more than one alarm with audio is displayed simultaneously. === Version 2.2.2 --- 10 June 2009 === * Fix email alarms sending multiple mails, when sent by KMail. * Fix crash when closing remote calendars. === Version 2.2.1 --- 25 May 2009 === * Include new handbook translation: Ukrainian. === Version 2.2.0 --- 29 April 2009 === * Provide facility to export alarms to a new calendar file. * Provide option to spread alarm and error messages over screen. * Show command execution error indication for alarms in main window alarm list. * Add configuration setting for default deferral time in Defer Alarm dialogue. * Accept drag and drop of Todo entries to create a new alarm. === Version 2.1.8 (KDE 4.2.4) --- 25 May 2009 === * Fix crash on exit from birthday import dialogue. * Fix crash when an alarm is open for edit when its last occurrence triggers, and the edit is then saved. * Fix another possible crash when KAlarm quits. * Don't show time in alarm list for date-only alarms without time zone (e.g. those created by Import Birthdays). === Version 2.1.7 (KDE 4.2.3) --- 29 April 2009 === * Fix recurring alarms being missed when deferred to earlier than next due alarm, when next due alarm is earlier than the next recurrence. * Fix crash at startup if a non-recurring cancel-if-late alarm has been missed. * Fix speech mode not working when alarm messages are displayed. * Fix KAlarm hanging sometimes while trying to play an audio file. * Fix crash when KAlarm quits. * Fix memory leak with undo/redo. === Version 2.1.6 (KDE 4.2.2) --- 18 March 2009 === * Fix memory leaks. * Fix crash when KAlarm quits. === Version 2.1.5 (KDE 4.2.1) --- 7 February 2009 === * Disable inapplicable alarm types in alarm edit dialogue Load Template list. * Prevent multiple identical error messages being displayed for the same alarm. * Fix possible crash on alarm refresh, or removal or disabling of a resource. === Version 2.1.4 (KDE 4.2) --- 18 January 2009 === * Prevent corrupt alarms if deferral reinstates from archived alarm instead of from the displaying calendar. * Ignore events in calendar without usable alarms (which prevents them getting stuck in the alarm list, and fixes high CPU usage). * Show error message when New Template selected but no writable resource exists. * Fix crash when iCalendar item is dragged and dropped onto KAlarm. * Make New Alarm shortcuts work. * Fix alarms not being saved if created by drag-and-drop but not edited further. === Version 2.1.3 (KDE 4.2 RC1) --- 5 January 2009 === * Fix invalid alarm remaining in calendar when pre-alarm action failure message is acknowledged before the alarm is deferred. === Version 2.1.2 --- 27 December 2008 === * New KAlarm icon. * Distinguish disabled from enabled alarm colour when highlighted in alarm list. * Ensure alarm windows show on top of full-screen windows. * Fix crash if KAlarm is activated again while restoring from previous session. * Fix kalarmautostart crash on logout while kalarmautostart is still running. * Fix click on system tray icon not showing main window if 'Show in system tray' configuration setting deselected. === Version 2.1.1 (KDE 4.2 beta2) --- 8 December 2008 === * Allow global shortcuts for New Alarm actions. * Fix failure to update alarms in KOrganizer when Kontact is running but Kontact's calendar component is not loaded. * Fix toolbar configuration being lost after quitting KAlarm. === Version 2.1.0 (KDE 4.2 beta1) --- 13 November 2008 === * Add option to exclude holidays from recurring alarms. * Provide More/Less Options button in edit alarm dialogue. * Improve Configuration dialogue layout, split pages into tabs. * Show separate toolbar buttons for new display, command and email alarms. * Show 'Time Zone' button instead of time zone selection controls when using default time zone. * Set file display alarm font & colour in same way as for text display alarms. * Set default reminder time units according to how long until alarm is due. === Version 2.0.6 (KDE 4.1.3) --- 22 October 2008 === * Fix alarms not triggering correctly after laptop wakes from hibernation. * Fix inability to change or cancel alarm deferral times. * Prevent defer dialogue date being set outside the allowed range. * Set background colour for file display alarm text. * Don't wrap lines in file display alarm message windows. * Fix addition and deletion of alarms to KOrganizer. === Version 2.0.5 --- 27 September 2008 === * Fix very high CPU usage by KAlarm when there are alarms with sub-repetitions, or deferrals, with periods greater than 1 week. Fix requires kdepimlibs 4.1.3. === Version 2.0.4 (KDE 4.1.2)--- 24 September 2008 === * Add work-time-only parameter for D-Bus calls to create new alarms. === Version 2.0.3 --- 7 September 2008 === * Double click accepts selected template in pick list. * Make text in edit alarm dialogue change colour when foreground colour changed. * Replace colour combo boxes by buttons which display standard KDE colour picker. === Version 2.0.2 (KDE 4.1.1) --- 27 August 2008 === * Show alarm text entry fields in the current alarm message colours. * Show background colour selector for file display alarms. * Set KDE sound files directory as default for picking sound files. * Fix width of buttons containing only an icon. * Change Control Center references to System Settings. * Fix formatting of file display alarms for non-HTML text files. * Fix crash when birthday dialogue is opened more than once. * Prevent quitting when main window is closed but system tray icon is visible. === Version 2.0.2 --- 4 August 2008 === * Set KDE sound files directory as default for picking sound files. * Fix width of buttons containing only an icon. * Change Control Center references to System Settings. === Version 2.0.1 (KDE 4.1) --- 17 July 2008 === * Double click in template dialogue list activates template edit dialogue. * Fix KAlarm quitting on closing message window when no main window visible. * Fix KAlarm crashing when quitting. === Version 2.0.0 --- 7 July 2008 === * New facility to use multiple alarm calendar resources. * Add facility to select time zone for alarm times. * Handle summer/winter time changes correctly. * New option to trigger a recurring alarm only during working hours. * Add option for display alarm text to be generated by a command. * Provide "Don't show again for this alarm" option for command error messages. * Alarm edit dialogue layout improvements. * Make alarm edit and preferences dialogues scrollable if too high for screen. * Choose new alarm/template type from menu instead of in alarm edit dialogue. * Add option to show alarm windows in centre of screen, with buttons initially disabled to prevent accidental acknowledgement. * Remove alarm daemon (kalarmd) and do alarm monitoring in KAlarm itself. * Remove --handleEvent command line option. * Use custom properties instead of CATEGORIES in calendar events for KAlarm data. * Don't discard non-KAlarm custom event properties when editing alarms. * Use kconf_update to convert old config file settings. * Change numeric codes in config file to strings for long-term maintainability. * Rename Defaults section options in config file. * Fix detection of yearly February 29th recurrences on Feb 28th or Mar 1st. === Version 1.5.3 --- 16 June 2008 === * In New From Template menu, show list of template names in sorted order. * Fix recurrence count being lost when using alarm templates. * Prevent invalid negative values appearing in 'Time from now' edit field. * Fix time shown in alarm edit dialogue for recurring alarms. * Fix recurrence count shown in alarm edit dialogue once alarm has triggered. * Fix Find not working with a new search text after a failed search. * Display correct error message when a search fails. * Prevent user changing font/colour dialogue when editing read-only alarms. === Version 1.5.2 --- 13 February 2008 === * Prevent repetition duration error message when saving alarm which never recurs. === Version 1.5.1 (KDE 3.5.9) --- 13 February 2008 === * Fix inability to set up sub-repetitions for simple yearly recurrences. === Version 1.5.0 --- 23 January 2008 === * Replace simple repetitions with recurrence sub-repetitions, to save confusion. * Add option to enter reminder times in minutes, in addition to hours/minutes. * Replace alarm edit dialogue background colour selector with font/colour sample. * Store email unique IDs instead of names in email alarms to prevent problems if email IDs are renamed. * Fix error "Sender verify failed (in reply to RCPT TO command)" using sendmail on some systems, by adding envelope sender address to emails. * Fix OpenSolaris build error. === Version 1.4.21 --- 19 December 2007 === * Remember last used main window show/hide options instead of setting them in Preferences dialogue. * Make the Menu key work in the alarm list. * Fix crash when saving preferences, if 'xterm' is not installed in the system. * Prevent multiple identical error messages being displayed for the same alarm. === Version 1.4.20 --- 18 November 2007 === * Fix deferral of non-recurring alarms not working. * Fix loss of reminder details in archive when alarm has had a reminder deferred. * Fix inability to reactivate deleted alarms which still have repetitions to go. * Fix incorrect interpretation of --late-cancel weekly parameter on command line. === Version 1.4.19 --- 11 November 2007 === * Fix KAlarm hanging and freezing the system for a while, especially on startup. * Fix next occurrence time set after editing alarm, when it's a sub-repetition. * Prevent error messages while typing date value, until user finishes entering it. === Version 1.4.18 --- 2 November 2007 === * Fix failure to trigger some recurring date-only alarms (e.g. after suspend-resume). * Fix date-only alarms triggering every minute from midnight to start-of-day time. * Simplify recurrence text shown in alarm edit dialogue Alarm tab when possible. * Prevent error after browsing for command log file, due to file:// prefix. === Version 1.4.17 (KDE 3.5.8) --- 8 October 2007 === * Allow time-from-now values up to 999 hours to be entered. * Fix incorrect email headers resulting in failure to send some emails. === Version 1.4.16a --- 12 September 2007 === * Fix failure to retrieve font and colour settings for display alarms. === Version 1.4.16 --- 10 September 2007 === * Attempt to fix failure to retrieve font and colour settings for display alarms. * Disable reminder etc. controls for at-login recurrence in alarm edit dialogue. === Version 1.4.15 --- 7 September 2007 === * Fix deferrals of recurring alarms not triggering correctly. * Fix failure to archive details of repetitions within a recurrence. * Enable/disable "Show expired alarms" action when preferences change. === Version 1.4.14 --- 5 August 2007 === * Fix handling of exception dates in recurrences. * In sound file dialogue change Play button to a Stop button while playing a file. === Version 1.4.13 --- 18 May 2007 === * Fix time value in templates not being stored. * Expand time spin boxes to make room for all digits. * Make Preferences dialogue non-modal. === Version 1.4.12 (KDE 3.5.7) --- 11 May 2007 === * Display advance reminders for each occurrence of recurring alarms. * Fix Undo of deletion of active alarms. * Disable simple repetition controls if repetitions can't fit between recurrences. * Make the system tray tooltip take account of alarm repetitions. * Show repetition & special action status by button states in alarm edit dialogue. * Fix reminder alarms displaying very big numbers for how long until alarm is due. * Fix KMail omitting attachments from email alarms (if KMail is the email client). === Version 1.4.11 --- 16 April 2007 === * Prevent pre-alarm actions being executed multiple times when alarm is triggered. * Prevent alarm daemon triggering alarms multiple times. * Only execute pre-alarm actions once (not for reminders or deferrals). * Only execute post-alarm actions once when alarm is finally acknowledged (after any deferrals), and not after reminders. * Show file name as a tooltip on sound type combo box when "file" is selected. === Version 1.4.10 --- 3 March 2007 === * Add play button to sound file selection dialogue. * Prevent simple repetitions triggering again when KAlarm is restarted. * Fix recurring alarms being triggered on exception days. * Fix start-of-day time being ignored for date-only alarms. * Disable Defer button in new message window when deferral limit has been reached. * Fix failure to save "Execute in terminal window" option in Preferences dialogue. * Ensure up-to-date menus are displayed if user has a customised toolbar. === Version 1.4.9 (KDE 3.5.6) --- 3 January 2007 === * Minor changes. === Version 1.4.8 --- 28 December 2006 === * Fix Find always using first search text entered even after entering a new one. === Version 1.4.7 --- 14 December 2006 === * Fix crash saving Preferences dialogue (due to command alarm terminal setting). === Version 1.4.6 --- 30 November 2006 === * Fix crash if an alarm triggers while user is deleting it. * Fix "Start alarm monitoring at login" value shown in preferences dialogue. * Fix deselecting "Start alarm monitoring at login" when daemon not running. * Fix editing of 29th February alarm options for non-leap years. * Tidy up preferences dialogue Run mode options. * Tidy up alarm edit/preferences dialogue sound type options into a combo box. * Add context help for sound file fade options. === Version 1.4.5 (KDE 3.5.5) --- 29 September 2006 === * Improve alarm edit dialogue layout (Reminder controls moved to below Time box). === Version 1.4.4 --- 11 July 2006 === * Use an alarm's previous deferral time interval as default for its next deferral. === Version 1.4.3 (KDE 3.5.4) --- 11 July 2006 === * Add facility to import alarms from other calendar files. * Fix Defer dialog time interval maximum to match maximum date/time value. * Fix crash when a deferred expired recurring alarm is edited from message window. * Fix crash when a message is redisplayed after login. * Prevent inapplicable 'Unable to speak' error when alarm redisplayed after login. * Save main window column order changes to use on restart (except message column). === Version 1.3.10 (KDE 3.5.3) --- 22 May 2006 === * Add DCOP calls and command line options to display the edit alarm dialogue. * Add Select All and Deselect actions & shortcuts for import birthdays list. * Make system tray icon appear in non-KDE window managers. * Output error message if deleting copy of alarm from KOrganizer fails. * Fix corruption of alarms displayed at logout and then deferred after login. * Fix reminder time not being saved in alarm templates. * Fix erroneous date adjustment of start of recurrence when saving alarm. * Fix crash when --play command line option is used, if compiled without aRts. * Don't show disabled alarms in system tray tooltip alarm list. === Version 1.3.9 (KDE 3.5.2) --- 7 March 2006 === * Notify daemon by DCOP that alarm has been processed: to prevent alarm loss, and to prevent defunct kalarm processes when run mode is on-demand. * Add Select All and Deselect actions & shortcuts for alarm and template lists. === Version 1.3.8 --- 24 January 2006 === * Fix kalarmd hang when triggering late alarm and KAlarm run mode is on-demand. === Version 1.3.7 --- 22 January 2006 === * Fix column widths when main window is resized, if columns have been reordered. === Version 1.3.6 (KDE 3.5.1) --- 10 January 2006 === * Make autoclose of message windows work. * Fix New From Template not creating alarm if template contents are not changed. * Ensure that day and month names translations are independent of locale calendar. * Display alarm message windows within current screen in multi-head systems. * Reduce size of Preferences dialog to fit in 1024x768 screen. === Version 1.3.5 --- 14 December 2005 === * Fix email attachments being forgotten when saving alarms. * Fix toolbar configuration being lost after quitting KAlarm. === Version 1.3.4 (KDE 3.5) --- 30 October 2005 === * Fix incorrect recurrence frequency in Alarm Edit dialogue's Alarm tab. === Version 1.3.3 --- 22 September 2005 === * Add day-of-week selection to daily recurrence dialog. === Version 1.3.2 (KDE 3.5 beta 1) --- 10 September 2005 === * Add option to show alarms in KOrganizer's active calendar. * Add option for email text alarms to locate the email in KMail. * When email alarm triggers and KMail isn't running, start KMail and send mail automatically instead of opening KMail composer window. * Provide per-alarm option for yearly February 29th recurrences. * Wait longer (20 seconds) before reporting alarm daemon registration failed. * Minimise KMix window if KMix is started by KAlarm when displaying a message. * Fix Plastik style 'enabled' indication for time spinbox left-hand buttons. * Prevent message windows always being full screen after a big message is shown. * Prevent message windows being initially larger than the desktop. * Prevent message windows initially overlapping the KDE panel. * Prevent session restoration displaying main windows which should be hidden. * Fix alarms getting stuck if due during a daylight savings clock change. * Change --volume command line option short form to -V (-v is used by --version). * Fix reported shell errors when output from command alarm is discarded. * Use 'KAlarm' untranslated in calendar product ID, to cater for locale changes. === Version 1.3.1 --- 30 May 2005 === * Add Undo/Redo facility for alarm edit/creation/deletion/reactivation. * Add text search facility. * Add option to speak alarm messages (if speech synthesis is installed). * Add command line option --speak. * Add 'New alarm from template' menu option and toolbar button. * Add 'Time from now' option in alarm templates. * Add fade option for playing sound files. * Add option to log command alarm output to a file. * Add Edit button to alarm message window to allow the alarm to be edited. * Enable drag and drop of alarms to other applications. * Email drag-and-drop from KMail (KDE >= 3.5) now presets alarm edit dialog with full From/To/Cc/Subject headers and body text. === Version 1.2.8 (KDE 3.4.1) --- 9 May 2005 === * Fix failure to enable "Reminder for first recurrence only" checkbox. === Version 1.2.7 --- 20 April 2005 === * Use a sensible default for terminal window command in Preferences dialog. * Validate terminal window command entered in Preferences dialog. * Fix date range no longer being validated in Defer dialog. * Don't ignore Sound setting in Preferences dialog Edit tab. * Reset sound volume (if it was set) as soon as audio file playing is complete. * Don't start KMix when an alarm is displayed if no sound volume is specified. * Add command script and execute-in-terminal options to DCOP interface. === Version 1.2.6 (KDE 3.4) --- 22 February 2005 === * Pop up message windows far from cursor to avoid accidental acknowledgement. * Start KMix if not already running, for setting alarm sound level. * Fix alarms not triggering if IDs are duplicated in different calendar files. * Improve validation when reading configuration file values. === Version 1.2.5 (KDE 3.4 beta2) --- 21 January 2005 === * Prevent multiple "Failed to start Alarm Daemon" error messages at startup. * Fix missing left border for time spinboxes in Plastik style. === Version 1.2.4 (KDE 3.4 beta1) --- 9 January 2005 === * Provide option to enter a script for a command alarm, instead of a command line. * Add option to run command alarms in terminal windows. * Accept drag and drop of KAddressBook entries to alarm edit dialog email fields. * Drag and drop now inserts text where appropriate, rather than replacing it. * Display correct controls after loading a template in alarm edit dialog. === Version 1.2.3 --- 7 December 2004 === * Put alarm type icons in a separate, sortable, column in alarm list. * Align times in alarm list. * Fix crash when the last recurrence of an alarm is reached. * Fix random limit on expired alarm discard time if stepping with spinbox buttons. * Fix dialog layouts for right-to-left languages. * Fix time spin box layout for right-to-left languages. === Version 1.2.2 --- 27 November 2004 === * Make alarm daemon (kalarmd) exclusive to KAlarm. * Move control options for alarm daemon into KAlarm preferences dialog. * Allow user to specify the late-cancellation period for an alarm. * Add option to automatically close window after late-cancellation period. * Add facility to enable and disable individual alarms. * Add simple repetition facility, including repetition within a recurrence. * Add option to pick a KMail identity to use as sender of email alarms. * Add option to copy emails sent via sendmail, to KMail sent-mail folder. * Show scheduled times, not reminder times, in alarm list and system tray tooltip. * Make time edit controls use 12-hour clock when that is the user's default. * Also fill in alarm edit dialog email fields when email is dropped onto KAlarm. * New revised DCOP request interface (old interface still kept for compatibility). * Make detection of email message display alarms independent of language. * Use KMix whenever possible to set hardware sound volume. * Limit range of entered date/time to valid values in deferral dialogue. * Prevent kalarm failing to register with kalarmd except when really necessary. * Fix time-to-alarm column in main window not always updating every minute. === Version 1.1.7 (KDE 3.3.2) --- 27 November 2004 === * Fix KAlarm button on message windows to make it always display main window. * Show scheduled times, not reminder times, in alarm list and system tray tooltip. * Fix time-to-alarm column in main window not always updating every minute. === Version 1.1.6 (KDE 3.3.1) --- 30 September 2004 === * Prevent crash, and output error message, if menu creation fails. * Unsuppress Quit warning message box if default answer is Cancel quit. * Prevent blind copy to self of email alarms via KMail when bcc is deselected. === Version 1.1.5 --- 1 September 2004 === * Show erroneous control in alarm edit dialog when an error message is displayed. * Make alarm edit dialog always appear on current desktop. * Make weekly/monthly/yearly recurrences scheduled from command line correspond correctly to the start date. * Fix start date for monthly/yearly recurrences scheduled from the command line. * Fix DCOP triggerEvent() call to not reschedule alarm if it isn't due yet. === Version 1.1.4 --- 21 August 2004 === * Fix errors when altering or cancelling deferrals of expired recurrences. === Version 1.1.3 (KDE 3.3) --- 28 July 2004 === * Fix dialog sizing the first time KAlarm is run. === Version 1.1.2 (KDE 3.3 beta2) --- 11 July 2004 === * Fix hangup in interactions with alarm daemon introduced in version 1.1.1. * Only tick Alarms Enabled menu items once alarms have actually been enabled. * Fix build for "./configure --without-arts". === Version 1.1.1 (KDE 3.3 beta1) --- 20 June 2004 === * Output error message and disable alarms if can't register with alarm daemon. * Exit if error in alarm calendar name configuration. * Fix bug where sound file is selected even when Cancel is pressed. === Version 1.1.0 --- 1 June 2004 === * Add facility to define alarm templates. * Add facility to specify pre- and post-alarm shell command actions. * Add option to play sound file repeatedly until alarm window is closed. * Add volume control for playing sound file. * Add 'stop sound' button to alarm message window when sound file is played. * Rename command line option --sound to --play, add option --play-repeat. * Add command line option --volume. * Add 'Configure Shortcuts' and 'Configure Toolbars' menu options in main window. * After creating/editing alarm, prompt to re-enable alarms if currently disabled. * Middle mouse button over system tray icon displays new alarm dialog. * Add option to display a reminder once only before the first alarm recurrence. * Display time-to-alarm in reminder message window. * For message texts which are truncated in main window, show full text in tooltip. * Allow time of day to be entered in format HHMM in time spin boxes. * Allow hour to be omitted when colon format time is entered in time spin boxes. * Add "Don't ask again" option to alarm deletion confirmation prompt. * Prevent expired alarm calendar purges clashing with other alarm actions. * Fix initial recurrence date/time for weekly/monthly/yearly recurrences. * Fix yearly recurrences of the last day in the month. * Disable yearly recurrence's month checkboxes depending on selected day of month. * Update which time columns are displayed in alarm list when Preferences change. * Don't store audio/reminder details in email/command alarms. * Don't store email details in message/file/command alarms. * Don't close message windows when quit is selected. * Fix "Warn before quitting" configuration option. * Don't redisplay error message windows on session restoration. * Remove obsolete --displayEvent command line option (replaced by --triggerEvent). * Remove obsolete pre-version 0.7 DCOP calls. === Version 1.0.7 --- 2 May 2004 === * Fix scheduleCommand() and scheduleEmail() DCOP handling. * Make KAlarm build for "./configure --without-arts". * Fix email body text not being saved in email alarms. * Fix loss of --exec command line arguments. * Remove wasted vertical space from message windows. === Version 1.0.6 (KDE 3.2.2) --- 26 March 2004 === * Make the Quit menu item in main window quit the program. * Update time entry field after editing as soon as mouse cursor leaves it. * Cancel deferral if reminder is set before it, to prevent it becoming stuck. * Prevent undeleted recurring alarms being triggered immediately. * Don't allow alarms to be undeleted if they are completely expired. === Version 1.0.5 (KDE 3.2.1) --- 24 February 2004 === * Fix whatsThis text on bottom row of alarm list. === Version 1.0.4 --- 22 February 2004 === * Fix freeze at login when multiple alarms trigger. * Show all audio file types in sound file chooser dialogue. === Version 1.0.3 --- 15 February 2004 === * Prevent email alarms from being sent if no 'From' address is configured. * Omit 'Bcc' when sending email alarms if no 'Bcc' address is configured. * Fix freeze when starting the alarm daemon. * Fix memory leaks displaying dialogs. * Fix scheduleCommand() and scheduleEmail() DCOP handling. * Fix errors saving expired alarm calendar. === Version 1.0.2 (KDE 3.2) --- 29 January 2004 === * Prevent editing alarm and saving without changes from deleting the alarm. === Version 1.0.1 --- 4 January 2004 === * Fix failure to see alarms if KAlarm is reactivated while restoring session. === Version 1.0.0 --- 7 December 2003 === * Allow entered start date for timed recurrence events to be earlier than now. * Prevent attempted entry of recurrence end date earlier than start date or today. * Fix error displaying time of expired repeat-at-login alarms. * Fix memory leak when sending emails with attachments. * Fix error trying to send emails with very small attachments. * Eliminate duplicate reload-calendar calls to alarm daemon. === Version 0.9.6 (KDE 3.2 beta1) --- 7 November 2003 === * Add option to choose foreground colour for alarm messages. * Create new alarm by dragging KMail email onto main window or system tray icon. * Set initial recurrence defaults to correspond to alarm start date. * Add option for how February 29th recurrences are handled in non-leap years. * Monthly/yearly recurrence edit: adhere to user preference for start day of week. * Eliminate multiple confirmation prompts when deleting multiple alarms. * Eliminate duplicate alarms in system tray tooltip. * Fix crash after reporting error opening calendar file. * Fix wrong status in system tray icon if KAlarm starts up with alarms disabled. * Fix wrong number of days in Time-to-alarm column in main window. * Fix omission of deferred alarms from system tray tooltip. === Version 0.9.5 --- 3 September 2003 === * Add option for non-modal alarm message windows. * Add option to display a notification when an email alarm queues an email. * Emails via KMail are sent without opening composer window, if KMail is running. * Provide separate configuration for 'From' and 'Bcc' addresses for email alarms. * Add exceptions to recurrence specification. * Add multiple month selection to yearly recurrence. * Add day of month selection in yearly recurrence. * Add last day of month option in monthly and yearly recurrences. * Add 2nd - 5th last week of month options in monthly and yearly recurrences. * Add filename completion to file and command alarm edit fields. * Display alarms-disabled indication in system tray tooltip. * Enable file alarms to display image files. * Fix file alarms not dislaying some text files, and improve HTML file display. * Fix loss of changes to attachment list after editing email alarms. * Fix wrong recurrence end date being displayed when editing an existing alarm. === Version 0.9.4 --- 3 July 2003 === * Add time-to-alarm display option to main alarm list. * Add option to list next 24 hours' alarms in system tray tooltip. * Create new alarm by dragging text or URL onto main window or system tray icon. * Display reasons for failure to send an email. * Allow editing of the list of message colours. * Edit new alarm by context menu or double click on white space in alarm list. * Add show expired alarms option to preferences dialog. * Display HTML files correctly in file display alarms. === Version 0.9.3 --- 4 March 2003 === * Add preferences option to set default sound file for the Edit Alarm dialog. * Fix display of "Invalid date" message before Edit Alarm dialog displays. === Version 0.9.2 --- 28 February 2003 === * Option to set font for individual alarm messages. * Allow multiple alarm selection in the main window. * KAlarm icon in alarm message window selects the alarm in the main window. * In Edit Alarm dialog, move all recurrence edit controls into Recurrence tab. * Add quit warning message option to preferences dialog. * Add "New Alarm" option to system tray context menu. * Disallow command alarms when KDE is running in kiosk mode. * Revised storage of beep, font, colour and program arguments in calendar file. * Always save alarms in iCalendar format (but vCalendar may still be read). * Add reminder, recurrence and font parameters to DCOP calls. * Fix failure to enable alarms when running in on-demand mode. === Version 0.9.1 --- 16 January 2003 === * Add option to set advance reminders for display alarms. * In run-in-system-tray mode, warn that alarms will be disabled before quitting. * Fix monthly and yearly recurrences on nth Monday etc. of the month. * Fix yearly recurrences on February 29th. * Fix recurrence start times stored in expired calendar file. * Fix extra empty events being stored in expired calendar file. === Version 0.9.0 --- 3 January 2003 === * Add facility to import birthdays from KAddressBook * Add option to send an email instead of displaying an alarm message. * Add option to store and view expired alarms. * Add copy, view and undelete actions (as applicable) for the selected alarm. * In alarm message window, message text can be copied to clipboard using mouse. * Allow message text to be scrolled in alarm message window if too big to fit. * Shift key with left mouse button steps time edit arrows by 5 minutes/6 hours. * Report failure to run command alarm (bash, ksh shells only). * Retain repeat-at-login status on alarm deferral. * Restore alarm messages which were displayed before KAlarm was killed or crashed. * Store alarm data in the calendar file in a more standard way. * Alarm message defer dialog: update recurrence deferral time limit in real time. * Weekly recurrence edit: adhere to user preference for start day of week. * Use standard action icons. === Version 0.8.5 (KDE 3.1.1) --- 21 February 2003 === * Fix monthly and yearly recurrences on nth Monday etc. of the month. * Fix yearly recurrences on February 29th. * Fix failure to enable alarms when running in on-demand mode. === Version 0.8.4 (KDE 3.1) --- 8 January 2003 === * Make KAlarm icon in message window bring main window to current desktop. * Fix detection of KDE desktop. * Fix entry of yearly recurrences on a specified date in the year. === Version 0.8.3 --- 9 November 2002 === * Fix no system tray icon being displayed. * Fix multiple system tray icons being displayed. * Fix alarms being missed after changing "Disable alarms when not running" status. === Version 0.8.2 --- 2 November 2002 === * Fix audio files not playing. === Version 0.8.1 --- 1 November 2002 === * Adhere to KDE single/double click setting when clicking on alarm list. * Fix possible loss of alarms if KAlarm has previously used another calendar file. * Fix coordination between "At time" and "After time" values when they change. * Always remove alarm deferral even when next recurrence triggers instead. * When alarm triggers, replace any existing repeat-at-login alarm message window. * Fix deselection of Sound not working after selecting a sound file. * Fix display of hour spin buttons in time edit spin boxes. * Prevent time edit spin box buttons from selecting the text. * Clean up previous alarm list highlight properly when a new alarm is selected. * Set sensible initial focus when edit alarm dialog pages are displayed. * Fix Quit duplicate entry in system tray context menu. === Version 0.8 (KDE 3.1 beta2) --- 16 September 2002 === * Move recurrence edit to separate tab in alarm dialog (now fits 800x600 display). * Add accelerator keys in dialogs. * Provide date picker for entering dates. === Version 0.7.5 --- 1 September 2002 === * Add preferences options to choose default settings for the Edit Alarm dialog. * Fix right-to-left character sets not being displayed in message edit control. * Make "Help -> Report Bug" use the KDE bug system (bug #43250). * Fix session restoration not occurring. === Version 0.7.4 (KDE 3.1 beta1) --- 5 August 2002 === * Add option to prompt for confirmation on alarm deletion. * Add option to prompt for confirmation on alarm acknowedgement. * Display KAlarm handbook Preferences section when Help clicked in config dialog. * Correctly adjust wrong summer times stored by version 0.5.7 (KDE 3.0.0). === Version 0.7.3 --- 24 July 2002 === * Fix loss of alarm times after saving pre-version 0.7 calendar file. * Fix main alarm list display of hours or hours/minutes repeat interval. * Display KAlarm handbook when Help clicked in configuration dialog. === Version 0.7.2 --- 2 July 2002 === * Fix reading wrong alarm times from pre-version 0.7 calendar file. * Partially fix loss of alarm times after saving pre-version 0.7 calendar file. === Version 0.7.1 --- 29 June 2002 === * Prevent duplicate message windows from being displayed. * Make Close button on message window not the default button to reduce chance of accidental acknowledgement. * Fix non-ASCII message texts being saved as question marks. * Fix memory leak with recurrences. === Version 0.7.0 --- 15 June 2002 === * Add option to play audio file when message is displayed. * Add daily, weekly, monthly, annual recurrences. * Allow deferring only up to next scheduled repetition time. * Don't defer repetitions when an alarm is deferred. * Make regular repetition and repeat-at-login mutually exclusive. * Double click on alarm in main window opens alarm edit dialog. * Change Reset Daemon menu option to Refresh Alarms. * Save and restore window sizes. === Version 0.6.4 --- 8 May 2002 === * Make click on system tray icon always bring KAlarm to top on current desktop. * Fix alarms not being triggered (depending on time zone). === Version 0.6.0 --- 8 March 2002 === * Add option to execute a command instead of displaying an alarm message. * Add Try button to alarm message edit dialog. * Add icons in the alarm list to indicate each alarm's type. * Display error message if a file to be displayed is not a text file. * Reduce chance of lost late-cancel alarms when daemon check interval is reduced. * Rename command line option --displayEvent to --triggerEvent. * Rename DCOP function displayMessage() to triggerEvent(). * Rename DCOP function cancelMessage() to cancelEvent(). === Version 0.5.8 (KDE 3.0.5A) --- 23 November 2002 === * Fix detection of KDE desktop. === Version 0.5.8 (KDE 3.0.5) --- 4 October 2002 === * Fix possible loss of alarms if KAlarm has previously used another calendar file. === Version 0.5.8 (KDE 3.0.4) --- 18 August 2002 === * Make "Help -> Report Bug" use the KDE bug system (bug #43250). * Fix right-to-left character sets not being displayed in message edit control. === Version 0.5.8 (KDE 3.0.3) --- 5 August 2002 === * Adjust wrong summer times stored by version 0.5.7 (KDE 3.0.0). * Display KAlarm handbook when Help clicked in configuration dialog. * Make Close button on message window not the default button to reduce chance of accidental acknowledgement. * Fix session restoration often not occurring at login. === Version 0.5.7 (KDE 3.0.1) --- 9 May 2002 === * Use local time for alarm times instead of using a time zone. * Make click on system tray icon always bring KAlarm to top on current desktop. === Version 0.5.7 (KDE 3.0) --- 17 March 2002 === * Show system tray icon on deferring command line-initiated message (run-in- system-tray mode). * Associate main window with system tray icon when displayed from message window. * Don't start KAlarm at login, until it has been run for the first time. * Add startup notification to kalarm.desktop. * Prevent open main window from cancelling KDE session shutdown. * Fix failure to display messages after daemon is restarted (run-on-demand mode). * Fix possible failure to display command line-initiated message. * Fix crash in some circumstances on changing run mode to run-on-demand. * Fix crash on clicking KAlarm icon in command line-initiated message window. * Fix crash on deferring alarm in command line-initiated message window. * Fix duplication of repeat-at-login alarms at login. * Fix error displaying text file messages. === Version 0.5.4 --- 7 February 2002 === * Fix extra window being displayed in session restoration. === Version 0.5.2 --- 31 January 2002 === * Fix session restore crash if in 'run continuously in system tray' mode. === Version 0.5.1 --- 30 January 2002 === * Change configuration defaults. === Version 0.5 --- 29 January 2002 === * Incorporate system tray icon into KAlarm, add --tray option. * Add 'run continuously in system tray' operating mode. * Don't use alarm daemon GUI application. * Add enable/disable alarms option to main window menu. * Add show/hide system tray icon option to main window menu. * Add toolbar. * Rename alarm dialog Set Alarm button to OK. * Rename message window OK button to Close. * Remove keyboard accelerator for Reset Daemon. * Fix magnified system tray icon. * Include README, etc. files in installation. === Version 0.4 --- 22 December 2001 === * Modify to use split alarm daemon/alarm daemon GUI. * Prevent a command line error exiting all open KAlarm windows. * Ensure the program exits after starting with --stop or --reset options. === Version 0.3.5 --- 5 December 2001 === * Add option to repeat alarms at login. * Add context help button to main window and message window. * Fix occasional crash on displaying non-repeating alarms. * Fix possible failure to display alarms at login. * Fix blank title bar when main window restored at login. * Fix alarms not deleted from main window when displayed at login. * Fix handling of zero-length calendar file. * Improve error messages. * Make documentation files installation dependent on KDE version. === Version 0.3.1 --- 20 November 2001 === * Fix build fault when using ./configure --enable-final === Version 0.3 --- 4 November 2001 === * Add option to display a file's contents instead of specifying a message. * Add dialog option to set an alarm's time as an interval from the current time. * Add defer option to alarm message window. * Provide button in alarm message window to activate KAlarm. * Make dialogs modal only for their parent window. === Version 0.2 --- 20 October 2001 === * Implement repeating alarms. * Add extra pair of arrow buttons to time spinbox to change the hour. * Fix sorting by colour column. * Better What's This? texts for the main window. * Remove -r, -s short options (use --reset, --stop instead). === Version 0.1.1 --- 1 September 2001 === * Fix documentation not being created by build. === Version 0.1 --- 31 August 2001 === * Initial release. diff --git a/src/kalarmapp.cpp b/src/kalarmapp.cpp index 43264c0e..d9d8c22e 100644 --- a/src/kalarmapp.cpp +++ b/src/kalarmapp.cpp @@ -1,2733 +1,2726 @@ /* * kalarmapp.cpp - the KAlarm application object * Program: kalarm * Copyright © 2001-2020 David Jarvie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kalarmapp.h" #include "alarmcalendar.h" #include "commandoptions.h" #include "dbushandler.h" #include "editdlgtypes.h" #include "functions.h" #include "kamail.h" #include "mainwindow.h" #include "messagewin.h" #include "kalarmmigrateapplication.h" #include "preferences.h" #include "prefdlg.h" #include "startdaytimer.h" #include "traywindow.h" #include "resources/datamodel.h" #include "resources/resources.h" #include "resources/eventmodel.h" #include "lib/desktop.h" #include "lib/messagebox.h" #include "lib/shellprocess.h" #include "kalarm_debug.h" #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { const int RESOURCES_TIMEOUT = 30; // timeout (seconds) for resources to be populated /****************************************************************************** * Find the maximum number of seconds late which a late-cancel alarm is allowed * to be. This is calculated as the late cancel interval, plus a few seconds * leeway to cater for any timing irregularities. */ inline int maxLateness(int lateCancel) { static const int LATENESS_LEEWAY = 5; int lc = (lateCancel >= 1) ? (lateCancel - 1)*60 : 0; return LATENESS_LEEWAY + lc; } QWidget* mainWidget() { return MainWindow::mainMainWindow(); } } KAlarmApp* KAlarmApp::mInstance = nullptr; int KAlarmApp::mActiveCount = 0; int KAlarmApp::mFatalError = 0; QString KAlarmApp::mFatalMessage; /****************************************************************************** * Construct the application. */ KAlarmApp::KAlarmApp(int& argc, char** argv) : QApplication(argc, argv) , mDBusHandler(new DBusHandler()) { qCDebug(KALARM_LOG) << "KAlarmApp:"; +} + +/****************************************************************************** +*/ +KAlarmApp::~KAlarmApp() +{ + while (!mCommandProcesses.isEmpty()) + { + ProcData* pd = mCommandProcesses.at(0); + mCommandProcesses.pop_front(); + delete pd; + } + ResourcesCalendar::terminate(); + DisplayCalendar::terminate(); + DataModel::terminate(); +} + +/****************************************************************************** +* Return the one and only KAlarmApp instance. +* If it doesn't already exist, it is created first. +*/ +KAlarmApp* KAlarmApp::create(int& argc, char** argv) +{ + if (!mInstance) + { + mInstance = new KAlarmApp(argc, argv); + + if (mFatalError) + mInstance->quitFatal(); + } + return mInstance; +} + +/****************************************************************************** +* Perform initialisations which may require KAboutData to have been set up. +*/ +void KAlarmApp::initialise() +{ KAlarmMigrateApplication migrate; migrate.migrate(); #ifndef NDEBUG KAlarm::setTestModeConditions(); #endif setQuitOnLastWindowClosed(false); Preferences::self(); // read KAlarm configuration if (!Preferences::noAutoStart()) { // Strip out any "OnlyShowIn=KDE" list from kalarm.autostart.desktop Preferences::setNoAutoStart(false); // Enable kalarm.autostart.desktop to start KAlarm Preferences::setAutoStart(true); Preferences::self()->save(); } Preferences::connect(SIGNAL(startOfDayChanged(QTime)), this, SLOT(changeStartOfDay())); Preferences::connect(SIGNAL(workTimeChanged(QTime,QTime,QBitArray)), this, SLOT(slotWorkTimeChanged(QTime,QTime,QBitArray))); Preferences::connect(SIGNAL(holidaysChanged(KHolidays::HolidayRegion)), this, SLOT(slotHolidaysChanged(KHolidays::HolidayRegion))); Preferences::connect(SIGNAL(feb29TypeChanged(Feb29Type)), this, SLOT(slotFeb29TypeChanged(Feb29Type))); Preferences::connect(SIGNAL(showInSystemTrayChanged(bool)), this, SLOT(slotShowInSystemTrayChanged())); Preferences::connect(SIGNAL(archivedKeepDaysChanged(int)), this, SLOT(setArchivePurgeDays())); Preferences::connect(SIGNAL(messageFontChanged(QFont)), this, SLOT(slotMessageFontChanged(QFont))); slotFeb29TypeChanged(Preferences::defaultFeb29Type()); KAEvent::setStartOfDay(Preferences::startOfDay()); KAEvent::setWorkTime(Preferences::workDays(), Preferences::workDayStart(), Preferences::workDayEnd()); KAEvent::setHolidays(Preferences::holidays()); KAEvent::setDefaultFont(Preferences::messageFont()); // Check if KOrganizer is installed const QString korg = QStringLiteral("korganizer"); mKOrganizerEnabled = !QStandardPaths::findExecutable(korg).isEmpty(); if (!mKOrganizerEnabled) { qCDebug(KALARM_LOG) << "KAlarmApp: KOrganizer options disabled (KOrganizer not found)"; } // Check if the window manager can't handle keyboard focus transfer between windows mWindowFocusBroken = (Desktop::currentIdentity() == Desktop::Unity); if (mWindowFocusBroken) { qCDebug(KALARM_LOG) << "KAlarmApp: Window keyboard focus broken"; } -} -/****************************************************************************** -*/ -KAlarmApp::~KAlarmApp() -{ - while (!mCommandProcesses.isEmpty()) - { - ProcData* pd = mCommandProcesses.at(0); - mCommandProcesses.pop_front(); - delete pd; - } - ResourcesCalendar::terminate(); - DisplayCalendar::terminate(); - DataModel::terminate(); -} - -/****************************************************************************** -* Return the one and only KAlarmApp instance. -* If it doesn't already exist, it is created first. -*/ -KAlarmApp* KAlarmApp::create(int& argc, char** argv) -{ - if (!mInstance) - { - mInstance = new KAlarmApp(argc, argv); - - if (mFatalError) - mInstance->quitFatal(); - } - return mInstance; -} - -/****************************************************************************** -* Perform initialisations which may require the constructor to have completed -* and KAboutData to have been set up. -*/ -void KAlarmApp::initialise() -{ if (initialiseTimerResources()) // initialise calendars and alarm timer { Resources* resources = Resources::instance(); connect(resources, &Resources::resourceAdded, this, &KAlarmApp::slotResourceAdded); connect(resources, &Resources::resourcePopulated, this, &KAlarmApp::slotResourcePopulated); connect(resources, &Resources::resourcePopulated, this, &KAlarmApp::purgeNewArchivedDefault); connect(resources, &Resources::resourcesCreated, this, &KAlarmApp::slotResourcesCreated); connect(resources, &Resources::resourcesPopulated, this, &KAlarmApp::processQueue); KConfigGroup config(KSharedConfig::openConfig(), "General"); mNoSystemTray = config.readEntry("NoSystemTray", false); mOldShowInSystemTray = wantShowInSystemTray(); DateTime::setStartOfDay(Preferences::startOfDay()); mPrefsArchivedColour = Preferences::archivedColour(); } } /****************************************************************************** * Initialise or reinitialise things which are tidied up/closed by quitIf(). * Reinitialisation can be necessary if session restoration finds nothing to * restore and starts quitting the application, but KAlarm then starts up again * before the application has exited. * Reply = true if calendars were initialised successfully, * false if they were already initialised, or if initialisation failed. */ bool KAlarmApp::initialiseTimerResources() { if (!mAlarmTimer) { mAlarmTimer = new QTimer(this); mAlarmTimer->setSingleShot(true); connect(mAlarmTimer, &QTimer::timeout, this, &KAlarmApp::checkNextDueAlarm); } if (!ResourcesCalendar::instance()) { qCDebug(KALARM_LOG) << "KAlarmApp::initialise: initialising calendars"; Desktop::setMainWindowFunc(&mainWidget); DataModel::initialise(); ResourcesCalendar::initialise(); DisplayCalendar::initialise(); connect(ResourcesCalendar::instance(), &ResourcesCalendar::earliestAlarmChanged, this, &KAlarmApp::checkNextDueAlarm); connect(ResourcesCalendar::instance(), &ResourcesCalendar::atLoginEventAdded, this, &KAlarmApp::atLoginEventAdded); return true; } return false; } /****************************************************************************** * Restore the saved session if required. */ bool KAlarmApp::restoreSession() { if (!isSessionRestored()) return false; if (mFatalError) { quitFatal(); return false; } // Process is being restored by session management. qCDebug(KALARM_LOG) << "KAlarmApp::restoreSession: Restoring"; ++mActiveCount; // Create the session config object now. // This is necessary since if initCheck() below causes calendars to be updated, // the session config created after that points to an invalid file, resulting // in no windows being restored followed by a later crash. KConfigGui::sessionConfig(); // When KAlarm is session restored, automatically set start-at-login to true. Preferences::self()->load(); Preferences::setAutoStart(true); Preferences::setNoAutoStart(false); Preferences::setAskAutoStart(true); // cancel any start-at-login prompt suppression Preferences::self()->save(); if (!initCheck(true)) // open the calendar file (needed for main windows), don't process queue yet { --mActiveCount; quitIf(1, true); // error opening the main calendar - quit return false; } MainWindow* trayParent = nullptr; for (int i = 1; KMainWindow::canBeRestored(i); ++i) { const QString type = KMainWindow::classNameOfToplevel(i); if (type == QLatin1String("MainWindow")) { MainWindow* win = MainWindow::create(true); win->restore(i, false); if (win->isHiddenTrayParent()) trayParent = win; else win->show(); } else if (type == QLatin1String("MessageWin")) { MessageWin* win = new MessageWin; win->restore(i, false); if (win->isValid()) { if (Resources::allCreated()) win->show(); } else delete win; } } // Try to display the system tray icon if it is configured to be shown if (trayParent || wantShowInSystemTray()) { if (!MainWindow::count()) qCWarning(KALARM_LOG) << "KAlarmApp::restoreSession: no main window to be restored!?"; else { displayTrayIcon(true, trayParent); // Occasionally for no obvious reason, the main main window is // shown when it should be hidden, so hide it just to be sure. if (trayParent) trayParent->hide(); } } --mActiveCount; if (quitIf(0)) // quit if no windows are open return false; // quitIf() can sometimes return, despite calling exit() startProcessQueue(); // start processing the execution queue return true; } /****************************************************************************** -* Called for a unique QApplication when a new instance of the application is -* started. -* Reply: exit code (>= 0), or -1 to continue execution. -* If exit code >= 0, 'outputText' holds text to output before terminating. -*/ -void KAlarmApp::activateByDBus(const QStringList& args, const QString& workingDirectory) -{ - activateInstance(args, workingDirectory, nullptr); -} - -/****************************************************************************** -* Called to start a new instance of the application. +* Called to start a new instance of the unique QApplication. * Reply: exit code (>= 0), or -1 to continue execution. * If exit code >= 0, 'outputText' holds text to output before terminating. */ int KAlarmApp::activateInstance(const QStringList& args, const QString& workingDirectory, QString* outputText) { Q_UNUSED(workingDirectory) - qCDebug(KALARM_LOG) << "KAlarmApp::activateInstance"; + qCDebug(KALARM_LOG) << "KAlarmApp::activateInstance" << args; if (outputText) outputText->clear(); if (mFatalError) { + Q_EMIT setExitValue(1); quitFatal(); return 1; } // The D-Bus call to activate a subsequent instance of KAlarm may not supply // any arguments, but we need one. if (!args.isEmpty() && mActivateArg0.isEmpty()) mActivateArg0 = args[0]; QStringList fixedArgs(args); if (args.isEmpty() && !mActivateArg0.isEmpty()) fixedArgs << mActivateArg0; // Parse and interpret command line arguments. QCommandLineParser parser; KAboutData::applicationData().setupCommandLine(&parser); parser.setApplicationDescription(QApplication::applicationDisplayName()); CommandOptions* options = new CommandOptions; - const QStringList newArgs = options->setOptions(&parser, fixedArgs); + const QStringList nonexecArgs = options->setOptions(&parser, fixedArgs); options->parse(); KAboutData::applicationData().processCommandLine(&parser); ++mActiveCount; int exitCode = 0; // default = success static bool firstInstance = true; bool dontRedisplay = false; CommandOptions::Command command = CommandOptions::NONE; const bool processOptions = (!firstInstance || !isSessionRestored()); if (processOptions) { options->process(); #ifndef NDEBUG if (options->simulationTime().isValid()) KAlarm::setSimulatedSystemTime(options->simulationTime()); #endif command = options->command(); if (options->disableAll()) setAlarmsEnabled(false); // disable alarm monitoring // Handle options which exit with a terminal message, before // making the application a unique application, since a // unique application won't output to the terminal if another // instance is already running. switch (command) { case CommandOptions::CMD_ERROR: + Q_EMIT setExitValue(1); if (outputText) { *outputText = options->outputText(); delete options; return 1; } mReadOnly = true; // don't need write access to calendars exitCode = 1; break; default: break; } } - // Make this a unique application. - KDBusService* s = new KDBusService(KDBusService::Unique, this); - connect(this, &KAlarmApp::aboutToQuit, s, &KDBusService::deleteLater); - connect(s, &KDBusService::activateRequested, this, &KAlarmApp::activateByDBus); - if (processOptions) { switch (command) { case CommandOptions::TRIGGER_EVENT: case CommandOptions::CANCEL_EVENT: { // Display or delete the event with the specified event ID QueuedAction action = static_cast(int((command == CommandOptions::TRIGGER_EVENT) ? QueuedAction::Trigger : QueuedAction::Cancel) | int(QueuedAction::Exit)); // Open the calendar, don't start processing execution queue yet, // and wait for the calendar resources to be populated. if (!initCheck(true)) exitCode = 1; else { mCommandOption = options->commandName(); // Get the resource ID string and event UID. Note that if // resources have not been created yet, the numeric // resource ID can't yet be looked up. if (options->resourceId().isEmpty()) action = static_cast((int)action | int(QueuedAction::FindId)); mActionQueue.enqueue(ActionQEntry(action, EventId(options->eventId()), options->resourceId())); startProcessQueue(); // start processing the execution queue dontRedisplay = true; } break; } case CommandOptions::LIST: // Output a list of scheduled alarms to stdout. // Open the calendar, don't start processing execution queue yet, // and wait for all calendar resources to be populated. mReadOnly = true; // don't need write access to calendars mAlarmsEnabled = false; // prevent alarms being processed if (!initCheck(true)) exitCode = 1; else { const QueuedAction action = static_cast(int(QueuedAction::List) | int(QueuedAction::Exit)); mActionQueue.enqueue(ActionQEntry(action, EventId())); startProcessQueue(); // start processing the execution queue dontRedisplay = true; } break; case CommandOptions::EDIT: // Edit a specified existing alarm. // Open the calendar and wait for the calendar resources to be populated. if (!initCheck(false)) exitCode = 1; else { mCommandOption = options->commandName(); if (firstInstance) mEditingCmdLineAlarm = 0x10; // want to redisplay alarms if successful // Get the resource ID string and event UID. Note that if // resources have not been created yet, the numeric // resource ID can't yet be looked up. mActionQueue.enqueue(ActionQEntry(QueuedAction::Edit, EventId(options->eventId()), options->resourceId())); startProcessQueue(); // start processing the execution queue dontRedisplay = true; } break; case CommandOptions::EDIT_NEW: { // Edit a new alarm, and optionally preset selected values if (!initCheck()) exitCode = 1; else { EditAlarmDlg* editDlg = EditAlarmDlg::create(false, options->editType(), MainWindow::mainMainWindow()); if (options->alarmTime().isValid()) editDlg->setTime(options->alarmTime()); if (options->recurrence()) editDlg->setRecurrence(*options->recurrence(), options->subRepeatInterval(), options->subRepeatCount()); else if (options->flags() & KAEvent::REPEAT_AT_LOGIN) editDlg->setRepeatAtLogin(); editDlg->setAction(options->editAction(), AlarmText(options->text())); if (options->lateCancel()) editDlg->setLateCancel(options->lateCancel()); if (options->flags() & KAEvent::COPY_KORGANIZER) editDlg->setShowInKOrganizer(true); switch (options->editType()) { case EditAlarmDlg::DISPLAY: { // EditAlarmDlg::create() always returns EditDisplayAlarmDlg for type = DISPLAY EditDisplayAlarmDlg* dlg = qobject_cast(editDlg); if (options->fgColour().isValid()) dlg->setFgColour(options->fgColour()); if (options->bgColour().isValid()) dlg->setBgColour(options->bgColour()); if (!options->audioFile().isEmpty() || options->flags() & (KAEvent::BEEP | KAEvent::SPEAK)) { const KAEvent::Flags flags = options->flags(); const Preferences::SoundType type = (flags & KAEvent::BEEP) ? Preferences::Sound_Beep : (flags & KAEvent::SPEAK) ? Preferences::Sound_Speak : Preferences::Sound_File; dlg->setAudio(type, options->audioFile(), options->audioVolume(), (flags & KAEvent::REPEAT_SOUND ? 0 : -1)); } if (options->reminderMinutes()) dlg->setReminder(options->reminderMinutes(), (options->flags() & KAEvent::REMINDER_ONCE)); if (options->flags() & KAEvent::CONFIRM_ACK) dlg->setConfirmAck(true); if (options->flags() & KAEvent::AUTO_CLOSE) dlg->setAutoClose(true); break; } case EditAlarmDlg::COMMAND: break; case EditAlarmDlg::EMAIL: { // EditAlarmDlg::create() always returns EditEmailAlarmDlg for type = EMAIL EditEmailAlarmDlg* dlg = qobject_cast(editDlg); if (options->fromID() || !options->addressees().isEmpty() || !options->subject().isEmpty() || !options->attachments().isEmpty()) dlg->setEmailFields(options->fromID(), options->addressees(), options->subject(), options->attachments()); if (options->flags() & KAEvent::EMAIL_BCC) dlg->setBcc(true); break; } case EditAlarmDlg::AUDIO: { // EditAlarmDlg::create() always returns EditAudioAlarmDlg for type = AUDIO EditAudioAlarmDlg* dlg = qobject_cast(editDlg); if (!options->audioFile().isEmpty() || options->audioVolume() >= 0) dlg->setAudio(options->audioFile(), options->audioVolume()); break; } case EditAlarmDlg::NO_TYPE: break; } // Execute the edit dialogue. Note that if no other instance of KAlarm is // running, this new instance will not exit after the dialogue is closed. // This is deliberate, since exiting would mean that KAlarm wouldn't // trigger the new alarm. KAlarm::execNewAlarmDlg(editDlg); createOnlyMainWindow(); // prevent the application from quitting } break; } case CommandOptions::EDIT_NEW_PRESET: // Edit a new alarm, preset with a template if (!initCheck()) exitCode = 1; else { // Execute the edit dialogue. Note that if no other instance of KAlarm is // running, this new instance will not exit after the dialogue is closed. // This is deliberate, since exiting would mean that KAlarm wouldn't // trigger the new alarm. KAlarm::editNewAlarm(options->templateName()); createOnlyMainWindow(); // prevent the application from quitting } break; case CommandOptions::NEW: // Display a message or file, execute a command, or send an email setResourcesTimeout(); // set timeout for resource initialisation if (!initCheck()) exitCode = 1; else { if (!scheduleEvent(options->editAction(), options->text(), options->alarmTime(), options->lateCancel(), options->flags(), options->bgColour(), options->fgColour(), QFont(), options->audioFile(), options->audioVolume(), options->reminderMinutes(), (options->recurrence() ? *options->recurrence() : KARecurrence()), options->subRepeatInterval(), options->subRepeatCount(), options->fromID(), options->addressees(), options->subject(), options->attachments())) exitCode = 1; else createOnlyMainWindow(); // prevent the application from quitting } break; case CommandOptions::TRAY: // Display only the system tray icon if (Preferences::showInSystemTray() && QSystemTrayIcon::isSystemTrayAvailable()) { if (!initCheck()) // open the calendar, start processing execution queue exitCode = 1; else { if (!displayTrayIcon(true)) exitCode = 1; } break; } Q_FALLTHROUGH(); // fall through to NONE case CommandOptions::NONE: // No arguments - run interactively & display the main window #ifndef NDEBUG if (options->simulationTime().isValid() && !firstInstance) break; // simulating time: don't open main window if already running #endif if (!initCheck()) exitCode = 1; else { if (mTrayWindow && mTrayWindow->assocMainWindow() && !mTrayWindow->assocMainWindow()->isVisible()) mTrayWindow->showAssocMainWindow(); else { MainWindow* win = MainWindow::create(); if (command == CommandOptions::TRAY) win->setWindowState(win->windowState() | Qt::WindowMinimized); win->show(); } } break; default: break; } } if (options != CommandOptions::firstInstance()) delete options; // If this is the first time through, redisplay any alarm message windows // from last time. if (firstInstance && !dontRedisplay && !exitCode) { /* First time through, so redisplay alarm message windows from last time. * But it is possible for session restoration in some circumstances to * not create any windows, in which case the alarm calendars will have * been deleted - if so, don't try to do anything. (This has been known * to happen under the Xfce desktop.) */ if (ResourcesCalendar::instance()) { if (Resources::allCreated()) { mRedisplayAlarms = false; MessageWin::redisplayAlarms(); } else mRedisplayAlarms = true; } } --mActiveCount; firstInstance = false; // Quit the application if this was the last/only running "instance" of the program. // Executing 'return' doesn't work very well since the program continues to // run if no windows were created. if (quitIf(exitCode >= 0 ? exitCode : 0)) return exitCode; // exit this application instance return -1; // continue executing the application instance } /****************************************************************************** * Create a minimised main window if none already exists. * This prevents the application from quitting. */ void KAlarmApp::createOnlyMainWindow() { if (!MainWindow::count()) { if (Preferences::showInSystemTray() && QSystemTrayIcon::isSystemTrayAvailable()) { if (displayTrayIcon(true)) return; } MainWindow* win = MainWindow::create(); win->setWindowState(Qt::WindowMinimized); win->show(); } } /****************************************************************************** * Quit the program, optionally only if there are no more "instances" running. * Reply = true if program exited. */ bool KAlarmApp::quitIf(int exitCode, bool force) { if (force) { // Quit regardless, except for message windows mQuitting = true; MainWindow::closeAll(); mQuitting = false; displayTrayIcon(false); if (MessageWin::instanceCount(true)) // ignore always-hidden windows (e.g. audio alarms) return false; } else if (mQuitting) return false; // MainWindow::closeAll() causes quitIf() to be called again else { // Quit only if there are no more "instances" running mPendingQuit = false; if (mActiveCount > 0 || MessageWin::instanceCount(true)) // ignore always-hidden windows (e.g. audio alarms) return false; const int mwcount = MainWindow::count(); MainWindow* mw = mwcount ? MainWindow::firstWindow() : nullptr; if (mwcount > 1 || (mwcount && (!mw->isHidden() || !mw->isTrayParent()))) return false; // There are no windows left except perhaps a main window which is a hidden // tray icon parent, or an always-hidden message window. if (mTrayWindow) { // There is a system tray icon. // Don't exit unless the system tray doesn't seem to exist. if (checkSystemTray()) return false; } if (!mActionQueue.isEmpty() || !mCommandProcesses.isEmpty()) { // Don't quit yet if there are outstanding actions on the execution queue mPendingQuit = true; mPendingQuitCode = exitCode; return false; } } // This was the last/only running "instance" of the program, so exit completely. // NOTE: Everything which is terminated/deleted here must where applicable // be initialised in the initialiseTimerResources() method, in case // KAlarm is started again before application exit completes! qCDebug(KALARM_LOG) << "KAlarmApp::quitIf:" << exitCode << ": quitting"; MessageWin::stopAudio(true); if (mCancelRtcWake) { KAlarm::setRtcWakeTime(0, nullptr); KAlarm::deleteRtcWakeConfig(); } delete mAlarmTimer; // prevent checking for alarms after deleting calendars mAlarmTimer = nullptr; mInitialised = false; // prevent processQueue() from running ResourcesCalendar::terminate(); DisplayCalendar::terminate(); DataModel::terminate(); + Q_EMIT setExitValue(exitCode); exit(exitCode); return true; // sometimes we actually get to here, despite calling exit() } /****************************************************************************** * Called when the Quit menu item is selected. * Closes the system tray window and all main windows, but does not exit the * program if other windows are still open. */ void KAlarmApp::doQuit(QWidget* parent) { qCDebug(KALARM_LOG) << "KAlarmApp::doQuit"; if (KAMessageBox::warningCancelContinue(parent, i18nc("@info", "Quitting will disable alarms (once any alarm message windows are closed)."), QString(), KStandardGuiItem::quit(), KStandardGuiItem::cancel(), Preferences::QUIT_WARN ) != KMessageBox::Continue) return; if (!KAlarm::checkRtcWakeConfig(true).isEmpty()) { // A wake-on-suspend alarm is set if (KAMessageBox::warningCancelContinue(parent, i18nc("@info", "Quitting will cancel the scheduled Wake from Suspend."), QString(), KStandardGuiItem::quit() ) != KMessageBox::Continue) return; mCancelRtcWake = true; } if (!Preferences::autoStart()) { int option = KMessageBox::No; if (!Preferences::autoStartChangedByUser()) { option = KAMessageBox::questionYesNoCancel(parent, xi18nc("@info", "Do you want to start KAlarm at login?" "(Note that alarms will be disabled if KAlarm is not started.)"), QString(), KStandardGuiItem::yes(), KStandardGuiItem::no(), KStandardGuiItem::cancel(), Preferences::ASK_AUTO_START); } switch (option) { case KMessageBox::Yes: Preferences::setAutoStart(true); Preferences::setNoAutoStart(false); break; case KMessageBox::No: Preferences::setNoAutoStart(true); break; case KMessageBox::Cancel: default: return; } Preferences::self()->save(); } quitIf(0, true); } /****************************************************************************** * Display an error message for a fatal error. Prevent further actions since * the program state is unsafe. */ void KAlarmApp::displayFatalError(const QString& message) { if (!mFatalError) { mFatalError = 1; mFatalMessage = message; if (mInstance) QTimer::singleShot(0, mInstance, &KAlarmApp::quitFatal); } } /****************************************************************************** * Quit the program, once the fatal error message has been acknowledged. */ void KAlarmApp::quitFatal() { switch (mFatalError) { case 0: case 2: return; case 1: mFatalError = 2; KMessageBox::error(nullptr, mFatalMessage); // this is an application modal window mFatalError = 3; Q_FALLTHROUGH(); // fall through to '3' case 3: if (mInstance) mInstance->quitIf(1, true); break; } QTimer::singleShot(1000, this, &KAlarmApp::quitFatal); } /****************************************************************************** * Called by the alarm timer when the next alarm is due. * Also called when the execution queue has finished processing to check for the * next alarm. */ void KAlarmApp::checkNextDueAlarm() { if (!mAlarmsEnabled) return; // Find the first alarm due const KAEvent* nextEvent = ResourcesCalendar::instance()->earliestAlarm(); if (!nextEvent) return; // there are no alarms pending const KADateTime nextDt = nextEvent->nextTrigger(KAEvent::ALL_TRIGGER).effectiveKDateTime(); const KADateTime now = KADateTime::currentDateTime(Preferences::timeSpec()); qint64 interval = now.msecsTo(nextDt); qCDebug(KALARM_LOG) << "KAlarmApp::checkNextDueAlarm: now:" << qPrintable(now.toString(QStringLiteral("%Y-%m-%d %H:%M %:Z"))) << ", next:" << qPrintable(nextDt.toString(QStringLiteral("%Y-%m-%d %H:%M %:Z"))) << ", due:" << interval; if (interval <= 0) { // Queue the alarm queueAlarmId(*nextEvent); qCDebug(KALARM_LOG) << "KAlarmApp::checkNextDueAlarm:" << nextEvent->id() << ": due now"; QTimer::singleShot(0, this, &KAlarmApp::processQueue); } else { // No alarm is due yet, so set timer to wake us when it's due. // Check for integer overflow before setting timer. #pragma message("TODO: use hibernation wakeup signal") #ifndef HIBERNATION_SIGNAL /* TODO: REPLACE THIS CODE WHEN A SYSTEM NOTIFICATION SIGNAL BECOMES * AVAILABLE FOR WAKEUP FROM HIBERNATION. * Re-evaluate the next alarm time every minute, in case the * system clock jumps. The most common case when the clock jumps * is when a laptop wakes from hibernation. If timers were left to * run, they would trigger late by the length of time the system * was asleep. */ if (interval > 60000) // 1 minute interval = 60000; #endif ++interval; // ensure we don't trigger just before the minute boundary if (interval > INT_MAX) interval = INT_MAX; qCDebug(KALARM_LOG) << "KAlarmApp::checkNextDueAlarm:" << nextEvent->id() << "wait" << interval/1000 << "seconds"; mAlarmTimer->start(static_cast(interval)); } } /****************************************************************************** * Called by the alarm timer when the next alarm is due. * Also called when the execution queue has finished processing to check for the * next alarm. */ void KAlarmApp::queueAlarmId(const KAEvent& event) { const EventId id(event); for (const ActionQEntry& entry : qAsConst(mActionQueue)) { if (entry.action == QueuedAction::Handle && entry.eventId == id) return; // the alarm is already queued } mActionQueue.enqueue(ActionQEntry(QueuedAction::Handle, id)); } /****************************************************************************** * Start processing the execution queue. */ void KAlarmApp::startProcessQueue() { if (!mInitialised) { qCDebug(KALARM_LOG) << "KAlarmApp::startProcessQueue"; mInitialised = true; QTimer::singleShot(0, this, &KAlarmApp::processQueue); // process anything already queued } } /****************************************************************************** * The main processing loop for KAlarm. * All KAlarm operations involving opening or updating calendar files are called * from this loop to ensure that only one operation is active at any one time. * This precaution is necessary because KAlarm's activities are mostly * asynchronous, being in response to D-Bus calls from other programs or timer * events, any of which can be received in the middle of performing another * operation. If a calendar file is opened or updated while another calendar * operation is in progress, the program has been observed to hang, or the first * calendar call has failed with data loss - clearly unacceptable!! */ void KAlarmApp::processQueue() { if (mInitialised && !mProcessingQueue) { qCDebug(KALARM_LOG) << "KAlarmApp::processQueue"; mProcessingQueue = true; // Refresh alarms if that's been queued KAlarm::refreshAlarmsIfQueued(); // Process queued events while (!mActionQueue.isEmpty()) { ActionQEntry& entry = mActionQueue.head(); // If the first action's resource ID is a string, can't process it // until its numeric resource ID can be found. if (!entry.resourceId.isEmpty()) { if (!Resources::allCreated()) { // If resource population has timed out, discard all queued events. if (mResourcesTimedOut) { qCCritical(KALARM_LOG) << "Error! Timeout creating calendars"; mActionQueue.clear(); } break; } // Convert the resource ID string to the numeric resource ID. entry.eventId.setResourceId(EventId::getResourceId(entry.resourceId)); entry.resourceId.clear(); } // Can't process the first action until its resource has been populated. const ResourceId id = entry.eventId.resourceId(); if ((id < 0 && !Resources::allPopulated()) || (id >= 0 && !Resources::resource(id).isPopulated())) { // If resource population has timed out, discard all queued events. if (mResourcesTimedOut) { qCCritical(KALARM_LOG) << "Error! Timeout reading calendars"; mActionQueue.clear(); } break; } // Process the first action in the queue. const bool findUniqueId = int(entry.action) & int(QueuedAction::FindId); const bool exitAfter = int(entry.action) & int(QueuedAction::Exit); const QueuedAction action = static_cast(int(entry.action) & int(QueuedAction::ActionMask)); bool ok = true; if (entry.eventId.isEmpty()) { // It's a new alarm switch (action) { case QueuedAction::Trigger: execAlarm(entry.event, entry.event.firstAlarm(), false); break; case QueuedAction::Handle: KAlarm::addEvent(entry.event, nullptr, nullptr, KAlarm::ALLOW_KORG_UPDATE | KAlarm::NO_RESOURCE_PROMPT); break; case QueuedAction::List: { const QStringList alarms = scheduledAlarmList(); for (const QString& alarm : alarms) std::cout << alarm.toUtf8().constData() << std::endl; break; } default: break; } } else { if (action == QueuedAction::Edit) { int editingCmdLineAlarm = mEditingCmdLineAlarm & 3; bool keepQueued = editingCmdLineAlarm <= 1; switch (editingCmdLineAlarm) { case 0: // Initiate editing an alarm specified on the command line. mEditingCmdLineAlarm |= 1; QTimer::singleShot(0, this, &KAlarmApp::slotEditAlarmById); break; case 1: // Currently editing the alarm. break; case 2: // The edit has completed. mEditingCmdLineAlarm = 0; break; default: break; } if (keepQueued) break; } else { ok = handleEvent(entry.eventId, action, findUniqueId); if (!ok && exitAfter) CommandOptions::printError(xi18nc("@info:shell", "%1: Event %2 not found, or not unique", mCommandOption, entry.eventId.eventId())); } } if (exitAfter) { mActionQueue.clear(); // ensure that quitIf() actually exits the program quitIf(ok ? 0 : 1); return; // quitIf() can sometimes return, despite calling exit() } mActionQueue.dequeue(); } // Purge the default archived alarms resource if it's time to do so if (mPurgeDaysQueued >= 0) { KAlarm::purgeArchive(mPurgeDaysQueued); mPurgeDaysQueued = -1; } // Now that the queue has been processed, quit if a quit was queued if (mPendingQuit) { if (quitIf(mPendingQuitCode)) return; // quitIf() can sometimes return, despite calling exit() } mProcessingQueue = false; if (!mEditingCmdLineAlarm) { // Schedule the application to be woken when the next alarm is due checkNextDueAlarm(); } } } /****************************************************************************** * Called when a repeat-at-login alarm has been added externally. * Queues the alarm for triggering. * First, cancel any scheduled reminder or deferral for it, since these will be * superseded by the new at-login trigger. */ void KAlarmApp::atLoginEventAdded(const KAEvent& event) { KAEvent ev = event; if (!cancelReminderAndDeferral(ev)) { if (mAlarmsEnabled) { mActionQueue.enqueue(ActionQEntry(QueuedAction::Handle, EventId(ev))); if (mInitialised) QTimer::singleShot(0, this, &KAlarmApp::processQueue); } } } /****************************************************************************** * Called when the system tray main window is closed. */ void KAlarmApp::removeWindow(TrayWindow*) { mTrayWindow = nullptr; } /****************************************************************************** * Display or close the system tray icon. */ bool KAlarmApp::displayTrayIcon(bool show, MainWindow* parent) { qCDebug(KALARM_LOG) << "KAlarmApp::displayTrayIcon"; static bool creating = false; if (show) { if (!mTrayWindow && !creating) { if (!QSystemTrayIcon::isSystemTrayAvailable()) return false; if (!MainWindow::count()) { // We have to have at least one main window to act // as parent to the system tray icon (even if the // window is hidden). creating = true; // prevent main window constructor from creating an additional tray icon parent = MainWindow::create(); creating = false; } mTrayWindow = new TrayWindow(parent ? parent : MainWindow::firstWindow()); connect(mTrayWindow, &TrayWindow::deleted, this, &KAlarmApp::trayIconToggled); Q_EMIT trayIconToggled(); if (!checkSystemTray()) quitIf(0); // exit the application if there are no open windows } } else { delete mTrayWindow; mTrayWindow = nullptr; } return true; } /****************************************************************************** * Check whether the system tray icon has been housed in the system tray. */ bool KAlarmApp::checkSystemTray() { if (!mTrayWindow) return true; if (QSystemTrayIcon::isSystemTrayAvailable() == mNoSystemTray) { qCDebug(KALARM_LOG) << "KAlarmApp::checkSystemTray: changed ->" << mNoSystemTray; mNoSystemTray = !mNoSystemTray; // Store the new setting in the config file, so that if KAlarm exits it will // restart with the correct default. KConfigGroup config(KSharedConfig::openConfig(), "General"); config.writeEntry("NoSystemTray", mNoSystemTray); config.sync(); // Update other settings slotShowInSystemTrayChanged(); } return !mNoSystemTray; } /****************************************************************************** * Return the main window associated with the system tray icon. */ MainWindow* KAlarmApp::trayMainWindow() const { return mTrayWindow ? mTrayWindow->assocMainWindow() : nullptr; } /****************************************************************************** * Called when the show-in-system-tray preference setting has changed, to show * or hide the system tray icon. */ void KAlarmApp::slotShowInSystemTrayChanged() { const bool newShowInSysTray = wantShowInSystemTray(); if (newShowInSysTray != mOldShowInSystemTray) { // The system tray run mode has changed ++mActiveCount; // prevent the application from quitting MainWindow* win = mTrayWindow ? mTrayWindow->assocMainWindow() : nullptr; delete mTrayWindow; // remove the system tray icon if it is currently shown mTrayWindow = nullptr; mOldShowInSystemTray = newShowInSysTray; if (newShowInSysTray) { // Show the system tray icon displayTrayIcon(true); } else { // Stop showing the system tray icon if (win && win->isHidden()) { if (MainWindow::count() > 1) delete win; else { win->setWindowState(win->windowState() | Qt::WindowMinimized); win->show(); } } } --mActiveCount; } } /****************************************************************************** * Called when the start-of-day time preference setting has changed. * Change alarm times for date-only alarms. */ void KAlarmApp::changeStartOfDay() { DateTime::setStartOfDay(Preferences::startOfDay()); KAEvent::setStartOfDay(Preferences::startOfDay()); ResourcesCalendar::instance()->adjustStartOfDay(); } /****************************************************************************** * Called when the default alarm message font preference setting has changed. * Notify KAEvent. */ void KAlarmApp::slotMessageFontChanged(const QFont& font) { KAEvent::setDefaultFont(font); } /****************************************************************************** * Called when the working time preference settings have changed. * Notify KAEvent. */ void KAlarmApp::slotWorkTimeChanged(const QTime& start, const QTime& end, const QBitArray& days) { KAEvent::setWorkTime(days, start, end); } /****************************************************************************** * Called when the holiday region preference setting has changed. * Notify KAEvent. */ void KAlarmApp::slotHolidaysChanged(const KHolidays::HolidayRegion& holidays) { KAEvent::setHolidays(holidays); } /****************************************************************************** * Called when the date for February 29th recurrences has changed in the * preferences settings. */ void KAlarmApp::slotFeb29TypeChanged(Preferences::Feb29Type type) { KARecurrence::Feb29Type rtype; switch (type) { default: case Preferences::Feb29_None: rtype = KARecurrence::Feb29_None; break; case Preferences::Feb29_Feb28: rtype = KARecurrence::Feb29_Feb28; break; case Preferences::Feb29_Mar1: rtype = KARecurrence::Feb29_Mar1; break; } KARecurrence::setDefaultFeb29Type(rtype); } /****************************************************************************** * Return whether the program is configured to be running in the system tray. */ bool KAlarmApp::wantShowInSystemTray() const { return Preferences::showInSystemTray() && QSystemTrayIcon::isSystemTrayAvailable(); } /****************************************************************************** * Set a timeout for populating resources. */ void KAlarmApp::setResourcesTimeout() { QTimer::singleShot(RESOURCES_TIMEOUT * 1000, this, &KAlarmApp::slotResourcesTimeout); } /****************************************************************************** * Called on a timeout to check whether resources have been populated. * If not, exit the program with code 1. */ void KAlarmApp::slotResourcesTimeout() { if (!Resources::allPopulated()) { // Resource population has timed out. mResourcesTimedOut = true; quitIf(1); } } /****************************************************************************** * Called when all resources have been created at startup. * Check whether there are any writable active calendars, and if not, warn the * user. * If alarms are being archived, check whether there is a default archived * calendar, and if not, warn the user. */ void KAlarmApp::slotResourcesCreated() { if (mRedisplayAlarms) { mRedisplayAlarms = false; MessageWin::redisplayAlarms(); } checkWritableCalendar(); checkArchivedCalendar(); processQueue(); } /****************************************************************************** * Called when all calendars have been fetched at startup, or calendar migration * has completed. * Check whether there are any writable active calendars, and if not, warn the * user. */ void KAlarmApp::checkWritableCalendar() { if (mReadOnly) return; // don't need write access to calendars if (!Resources::allCreated() || !DataModel::isMigrationComplete()) return; static bool done = false; if (done) return; done = true; qCDebug(KALARM_LOG) << "KAlarmApp::checkWritableCalendar"; // Check for, and remove, any duplicate resources, i.e. those which use the // same calendar file/directory. DataModel::removeDuplicateResources(); // Find whether there are any writable active alarm calendars const bool active = !Resources::enabledResources(CalEvent::ACTIVE, true).isEmpty(); if (!active) { qCWarning(KALARM_LOG) << "KAlarmApp::checkWritableCalendar: No writable active calendar"; KAMessageBox::information(MainWindow::mainMainWindow(), xi18nc("@info", "Alarms cannot be created or updated, because no writable active alarm calendar is enabled." "To fix this, use View | Show Calendars to check or change calendar statuses."), QString(), QStringLiteral("noWritableCal")); } } /****************************************************************************** * If alarms are being archived, check whether there is a default archived * calendar, and if not, warn the user. */ void KAlarmApp::checkArchivedCalendar() { static bool done = false; if (done) return; done = true; // If alarms are to be archived, check that the default archived alarm // calendar is writable. if (Preferences::archivedKeepDays()) { Resource standard = Resources::getStandard(CalEvent::ARCHIVED); if (!standard.isValid()) { // Schedule the display of a user prompt, without holding up // other processing. QTimer::singleShot(0, this, &KAlarmApp::promptArchivedCalendar); } } } /****************************************************************************** * Edit an alarm specified on the command line. */ void KAlarmApp::slotEditAlarmById() { qCDebug(KALARM_LOG) << "KAlarmApp::slotEditAlarmById"; ActionQEntry& entry = mActionQueue.head(); if (!KAlarm::editAlarmById(entry.eventId)) { CommandOptions::printError(xi18nc("@info:shell", "%1: Event %2 not found, or not editable", mCommandOption, entry.eventId.eventId())); mActionQueue.clear(); quitIf(1); } else { createOnlyMainWindow(); // prevent the application from quitting if (mEditingCmdLineAlarm & 0x10) { mRedisplayAlarms = false; MessageWin::redisplayAlarms(); } mEditingCmdLineAlarm = 2; // indicate edit completion QTimer::singleShot(0, this, &KAlarmApp::processQueue); } } /****************************************************************************** * If alarms are being archived, check whether there is a default archived * calendar, and if not, warn the user. */ void KAlarmApp::promptArchivedCalendar() { const bool archived = !Resources::enabledResources(CalEvent::ARCHIVED, true).isEmpty(); if (archived) { qCWarning(KALARM_LOG) << "KAlarmApp::checkArchivedCalendar: Archiving, but no writable archived calendar"; KAMessageBox::information(MainWindow::mainMainWindow(), xi18nc("@info", "Alarms are configured to be archived, but this is not possible because no writable archived alarm calendar is enabled." "To fix this, use View | Show Calendars to check or change calendar statuses."), QString(), QStringLiteral("noWritableArch")); } else { qCWarning(KALARM_LOG) << "KAlarmApp::checkArchivedCalendar: Archiving, but no standard archived calendar"; KAMessageBox::information(MainWindow::mainMainWindow(), xi18nc("@info", "Alarms are configured to be archived, but this is not possible because no archived alarm calendar is set as default." "To fix this, use View | Show Calendars, select an archived alarms calendar, and check Use as Default for Archived Alarms."), QString(), QStringLiteral("noStandardArch")); } } /****************************************************************************** * Called when a new resource has been added, to note the possible need to purge * its old alarms if it is the default archived calendar. */ void KAlarmApp::slotResourceAdded(const Resource& resource) { if (resource.alarmTypes() & CalEvent::ARCHIVED) mPendingPurges += resource.id(); } /****************************************************************************** * Called when a resource has been populated, to purge its old alarms if it is * the default archived calendar. */ void KAlarmApp::slotResourcePopulated(const Resource& resource) { if (mPendingPurges.removeAll(resource.id()) > 0) purgeNewArchivedDefault(resource); } /****************************************************************************** * Called when a new resource has been populated, or when a resource has been * set as the standard resource for its type. * If it is the default archived calendar, purge its old alarms if necessary. */ void KAlarmApp::purgeNewArchivedDefault(const Resource& resource) { if (Resources::isStandard(resource, CalEvent::ARCHIVED)) { qCDebug(KALARM_LOG) << "KAlarmApp::purgeNewArchivedDefault:" << resource.displayId() << ": standard archived..."; if (mArchivedPurgeDays >= 0) purge(mArchivedPurgeDays); else setArchivePurgeDays(); } } /****************************************************************************** * Called when the length of time to keep archived alarms changes in KAlarm's * preferences. * Set the number of days to keep archived alarms. * Alarms which are older are purged immediately, and at the start of each day. */ void KAlarmApp::setArchivePurgeDays() { const int newDays = Preferences::archivedKeepDays(); if (newDays != mArchivedPurgeDays) { const int oldDays = mArchivedPurgeDays; mArchivedPurgeDays = newDays; if (mArchivedPurgeDays <= 0) StartOfDayTimer::disconnect(this); if (mArchivedPurgeDays < 0) return; // keep indefinitely, so don't purge if (oldDays < 0 || mArchivedPurgeDays < oldDays) { // Alarms are now being kept for less long, so purge them purge(mArchivedPurgeDays); if (!mArchivedPurgeDays) return; // don't archive any alarms } // Start the purge timer to expire at the start of the next day // (using the user-defined start-of-day time). StartOfDayTimer::connect(this, SLOT(slotPurge())); } } /****************************************************************************** * Purge all archived events from the calendar whose end time is longer ago than * 'daysToKeep'. All events are deleted if 'daysToKeep' is zero. */ void KAlarmApp::purge(int daysToKeep) { if (mPurgeDaysQueued < 0 || daysToKeep < mPurgeDaysQueued) mPurgeDaysQueued = daysToKeep; // Do the purge once any other current operations are completed processQueue(); } /****************************************************************************** * Output a list of pending alarms, with their next scheduled occurrence. */ QStringList KAlarmApp::scheduledAlarmList() { QStringList alarms; const QVector events = KAlarm::getSortedActiveEvents(this); for (const KAEvent& event : events) { const KADateTime dateTime = event.nextTrigger(KAEvent::DISPLAY_TRIGGER).effectiveKDateTime().toLocalZone(); const Resource resource = Resources::resource(event.resourceId()); QString text(resource.configName() + QLatin1String(":")); text += event.id() + QLatin1Char(' ') + dateTime.toString(QStringLiteral("%Y%m%dT%H%M ")) + AlarmText::summary(event, 1); alarms << text; } return alarms; } /****************************************************************************** * Enable or disable alarm monitoring. */ void KAlarmApp::setAlarmsEnabled(bool enabled) { if (enabled != mAlarmsEnabled) { mAlarmsEnabled = enabled; Q_EMIT alarmEnabledToggled(enabled); if (!enabled) KAlarm::cancelRtcWake(nullptr); else if (!mProcessingQueue) checkNextDueAlarm(); } } /****************************************************************************** * Spread or collect alarm message and error message windows. */ void KAlarmApp::spreadWindows(bool spread) { spread = MessageWin::spread(spread); Q_EMIT spreadWindowsToggled(spread); } /****************************************************************************** * Called when the spread status of message windows changes. * Set the 'spread windows' action state. */ void KAlarmApp::setSpreadWindowsState(bool spread) { Q_EMIT spreadWindowsToggled(spread); } /****************************************************************************** * Check whether the window manager's handling of keyboard focus transfer * between application windows is broken. This is true for Ubuntu's Unity * desktop, where MessageWin windows steal keyboard focus from EditAlarmDlg * windows. */ bool KAlarmApp::windowFocusBroken() const { return mWindowFocusBroken; } /****************************************************************************** * Check whether window/keyboard focus currently needs to be fixed manually due * to the window manager not handling it correctly. This will occur if there are * both EditAlarmDlg and MessageWin windows currently active. */ bool KAlarmApp::needWindowFocusFix() const { return mWindowFocusBroken && MessageWin::instanceCount(true) && EditAlarmDlg::instanceCount(); } /****************************************************************************** -* Called to schedule a new alarm, either in response to a DCOP notification or +* Called to schedule a new alarm, either in response to a D-Bus notification or * to command line options. * Reply = true unless there was a parameter error or an error opening calendar file. */ bool KAlarmApp::scheduleEvent(KAEvent::SubAction action, const QString& text, const KADateTime& dateTime, int lateCancel, KAEvent::Flags flags, const QColor& bg, const QColor& fg, const QFont& font, const QString& audioFile, float audioVolume, int reminderMinutes, const KARecurrence& recurrence, const KCalendarCore::Duration& repeatInterval, int repeatCount, uint mailFromID, const KCalendarCore::Person::List& mailAddresses, const QString& mailSubject, const QStringList& mailAttachments) { - qCDebug(KALARM_LOG) << "KAlarmApp::scheduleEvent:" << text; if (!dateTime.isValid()) + { + qCWarning(KALARM_LOG) << "KAlarmApp::scheduleEvent: Error! Invalid time" << text; return false; + } const KADateTime now = KADateTime::currentUtcDateTime(); if (lateCancel && dateTime < now.addSecs(-maxLateness(lateCancel))) + { + qCDebug(KALARM_LOG) << "KAlarmApp::scheduleEvent: not executed (late-cancel)" << text; return true; // alarm time was already archived too long ago + } KADateTime alarmTime = dateTime; // Round down to the nearest minute to avoid scheduling being messed up if (!dateTime.isDateOnly()) alarmTime.setTime(QTime(alarmTime.time().hour(), alarmTime.time().minute(), 0)); KAEvent event(alarmTime, text, bg, fg, font, action, lateCancel, flags, true); if (reminderMinutes) { const bool onceOnly = flags & KAEvent::REMINDER_ONCE; event.setReminder(reminderMinutes, onceOnly); } if (!audioFile.isEmpty()) event.setAudioFile(audioFile, audioVolume, -1, 0, (flags & KAEvent::REPEAT_SOUND) ? 0 : -1); if (!mailAddresses.isEmpty()) event.setEmail(mailFromID, mailAddresses, mailSubject, mailAttachments); event.setRecurrence(recurrence); event.setFirstRecurrence(); event.setRepetition(Repetition(repeatInterval, repeatCount - 1)); event.endChanges(); if (alarmTime <= now) { // Alarm is due for display already. // First execute it once without adding it to the calendar file. + qCDebug(KALARM_LOG) << "KAlarmApp::scheduleEvent: executing" << text; if (!mInitialised) mActionQueue.enqueue(ActionQEntry(event, QueuedAction::Trigger)); else execAlarm(event, event.firstAlarm(), false); // If it's a recurring alarm, reschedule it for its next occurrence if (!event.recurs() || event.setNextOccurrence(now) == KAEvent::NO_OCCURRENCE) return true; // It has recurrences in the future } // Queue the alarm for insertion into the calendar file + qCDebug(KALARM_LOG) << "KAlarmApp::scheduleEvent: creating new alarm" << text; mActionQueue.enqueue(ActionQEntry(event)); if (mInitialised) QTimer::singleShot(0, this, &KAlarmApp::processQueue); return true; } /****************************************************************************** * Called in response to a D-Bus request to trigger or cancel an event. * Optionally display the event. Delete the event from the calendar file and * from every main window instance. */ bool KAlarmApp::dbusHandleEvent(const EventId& eventID, QueuedAction action) { qCDebug(KALARM_LOG) << "KAlarmApp::dbusHandleEvent:" << eventID; mActionQueue.append(ActionQEntry(action, eventID)); if (mInitialised) QTimer::singleShot(0, this, &KAlarmApp::processQueue); return true; } /****************************************************************************** * Called in response to a D-Bus request to list all pending alarms. */ QString KAlarmApp::dbusList() { qCDebug(KALARM_LOG) << "KAlarmApp::dbusList"; return scheduledAlarmList().join(QLatin1Char('\n')) + QLatin1Char('\n'); } /****************************************************************************** * Either: * a) Display the event and then delete it if it has no outstanding repetitions. * b) Delete the event. * c) Reschedule the event for its next repetition. If none remain, delete it. * If the event is deleted, it is removed from the calendar file and from every * main window instance. * If 'findUniqueId' is true and 'id' does not specify a resource, all resources * will be searched for the event's unique ID. * Reply = false if event ID not found, or if more than one event with the same * ID is found. */ bool KAlarmApp::handleEvent(const EventId& id, QueuedAction action, bool findUniqueId) { Q_ASSERT(!(int(action) & ~int(QueuedAction::ActionMask))); // Delete any expired wake-on-suspend config data KAlarm::checkRtcWakeConfig(); const QString eventID(id.eventId()); KAEvent* event = ResourcesCalendar::instance()->event(id, findUniqueId); if (!event) { if (id.resourceId() != -1) qCWarning(KALARM_LOG) << "KAlarmApp::handleEvent: Event ID not found, or duplicated:" << eventID; else qCWarning(KALARM_LOG) << "KAlarmApp::handleEvent: Event ID not found:" << eventID; return false; } switch (action) { case QueuedAction::Cancel: qCDebug(KALARM_LOG) << "KAlarmApp::handleEvent:" << eventID << ", CANCEL"; KAlarm::deleteEvent(*event, true); break; case QueuedAction::Trigger: // handle it if it's due, else execute it regardless case QueuedAction::Handle: // handle it if it's due { const KADateTime now = KADateTime::currentUtcDateTime(); qCDebug(KALARM_LOG) << "KAlarmApp::handleEvent:" << eventID << "," << (action==QueuedAction::Trigger?"TRIGGER:":"HANDLE:") << qPrintable(now.qDateTime().toString(QStringLiteral("yyyy-MM-dd hh:mm"))) << "UTC"; bool updateCalAndDisplay = false; bool alarmToExecuteValid = false; KAAlarm alarmToExecute; bool restart = false; // Check all the alarms in turn. // Note that the main alarm is fetched before any other alarms. for (KAAlarm alarm = event->firstAlarm(); alarm.isValid(); alarm = (restart ? event->firstAlarm() : event->nextAlarm(alarm)), restart = false) { // Check if the alarm is due yet. const KADateTime nextDT = alarm.dateTime(true).effectiveKDateTime(); const int secs = nextDT.secsTo(now); if (secs < 0) { // The alarm appears to be in the future. // Check if it's an invalid local time during a daylight // saving time shift, which has actually passed. if (alarm.dateTime().timeSpec() != KADateTime::LocalZone || nextDT > now.toTimeSpec(KADateTime::LocalZone)) { // This alarm is definitely not due yet qCDebug(KALARM_LOG) << "KAlarmApp::handleEvent: Alarm" << alarm.type() << "at" << nextDT.qDateTime() << ": not due"; continue; } } bool reschedule = false; bool rescheduleWork = false; if ((event->workTimeOnly() || event->holidaysExcluded()) && !alarm.deferred()) { // The alarm is restricted to working hours and/or non-holidays // (apart from deferrals). This needs to be re-evaluated every // time it triggers, since working hours could change. if (alarm.dateTime().isDateOnly()) { KADateTime dt(nextDT); dt.setDateOnly(true); reschedule = !event->isWorkingTime(dt); } else reschedule = !event->isWorkingTime(nextDT); rescheduleWork = reschedule; if (reschedule) qCDebug(KALARM_LOG) << "KAlarmApp::handleEvent: Alarm" << alarm.type() << "at" << nextDT.qDateTime() << ": not during working hours"; } if (!reschedule && alarm.repeatAtLogin()) { // Alarm is to be displayed at every login. qCDebug(KALARM_LOG) << "KAlarmApp::handleEvent: REPEAT_AT_LOGIN"; // Check if the main alarm is already being displayed. // (We don't want to display both at the same time.) if (alarmToExecute.isValid()) continue; // Set the time to display if it's a display alarm alarm.setTime(now); } if (!reschedule && event->lateCancel()) { // Alarm is due, and it is to be cancelled if too late. qCDebug(KALARM_LOG) << "KAlarmApp::handleEvent: LATE_CANCEL"; bool cancel = false; if (alarm.dateTime().isDateOnly()) { // The alarm has no time, so cancel it if its date is too far past const int maxlate = event->lateCancel() / 1440; // maximum lateness in days KADateTime limit(DateTime(nextDT.addDays(maxlate + 1)).effectiveKDateTime()); if (now >= limit) { // It's too late to display the scheduled occurrence. // Find the last previous occurrence of the alarm. DateTime next; const KAEvent::OccurType type = event->previousOccurrence(now, next, true); switch (type & ~KAEvent::OCCURRENCE_REPEAT) { case KAEvent::FIRST_OR_ONLY_OCCURRENCE: case KAEvent::RECURRENCE_DATE: case KAEvent::RECURRENCE_DATE_TIME: case KAEvent::LAST_RECURRENCE: limit.setDate(next.date().addDays(maxlate + 1)); if (now >= limit) { if (type == KAEvent::LAST_RECURRENCE || (type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event->recurs())) cancel = true; // last occurrence (and there are no repetitions) else reschedule = true; } break; case KAEvent::NO_OCCURRENCE: default: reschedule = true; break; } } } else { // The alarm is timed. Allow it to be the permitted amount late before cancelling it. const int maxlate = maxLateness(event->lateCancel()); if (secs > maxlate) { // It's over the maximum interval late. // Find the most recent occurrence of the alarm. DateTime next; const KAEvent::OccurType type = event->previousOccurrence(now, next, true); switch (type & ~KAEvent::OCCURRENCE_REPEAT) { case KAEvent::FIRST_OR_ONLY_OCCURRENCE: case KAEvent::RECURRENCE_DATE: case KAEvent::RECURRENCE_DATE_TIME: case KAEvent::LAST_RECURRENCE: if (next.effectiveKDateTime().secsTo(now) > maxlate) { if (type == KAEvent::LAST_RECURRENCE || (type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event->recurs())) cancel = true; // last occurrence (and there are no repetitions) else reschedule = true; } break; case KAEvent::NO_OCCURRENCE: default: reschedule = true; break; } } } if (cancel) { // All recurrences are finished, so cancel the event event->setArchive(); if (cancelAlarm(*event, alarm.type(), false)) return true; // event has been deleted updateCalAndDisplay = true; continue; } } if (reschedule) { // The latest repetition was too long ago, so schedule the next one switch (rescheduleAlarm(*event, alarm, false, (rescheduleWork ? nextDT : KADateTime()))) { case 1: // A working-time-only alarm has been rescheduled and the // rescheduled time is already due. Start processing the // event again. alarmToExecuteValid = false; restart = true; break; case -1: return true; // event has been deleted default: break; } updateCalAndDisplay = true; continue; } if (!alarmToExecuteValid) { qCDebug(KALARM_LOG) << "KAlarmApp::handleEvent: Alarm" << alarm.type() << ": execute"; alarmToExecute = alarm; // note the alarm to be displayed alarmToExecuteValid = true; // only trigger one alarm for the event } else qCDebug(KALARM_LOG) << "KAlarmApp::handleEvent: Alarm" << alarm.type() << ": skip"; } // If there is an alarm to execute, do this last after rescheduling/cancelling // any others. This ensures that the updated event is only saved once to the calendar. if (alarmToExecute.isValid()) execAlarm(*event, alarmToExecute, true, !alarmToExecute.repeatAtLogin()); else { if (action == QueuedAction::Trigger) { // The alarm is to be executed regardless of whether it's due. // Only trigger one alarm from the event - we don't want multiple // identical messages, for example. const KAAlarm alarm = event->firstAlarm(); if (alarm.isValid()) execAlarm(*event, alarm, false); } if (updateCalAndDisplay) KAlarm::updateEvent(*event); // update the window lists and calendar file else if (action != QueuedAction::Trigger) { qCDebug(KALARM_LOG) << "KAlarmApp::handleEvent: No action"; } } break; } default: break; } return true; } /****************************************************************************** * Called when an alarm action has completed, to perform any post-alarm actions. */ void KAlarmApp::alarmCompleted(const KAEvent& event) { if (!event.postAction().isEmpty()) { // doShellCommand() will error if the user is not authorised to run // shell commands. const QString command = event.postAction(); qCDebug(KALARM_LOG) << "KAlarmApp::alarmCompleted:" << event.id() << ":" << command; doShellCommand(command, event, nullptr, ProcData::POST_ACTION); } } /****************************************************************************** * Reschedule the alarm for its next recurrence after now. If none remain, * delete it. If the alarm is deleted and it is the last alarm for its event, * the event is removed from the calendar file and from every main window * instance. * If 'nextDt' is valid, the event is rescheduled for the next non-working * time occurrence after that. * Reply = 1 if 'nextDt' is valid and the rescheduled event is already due * = -1 if the event has been deleted * = 0 otherwise. */ int KAlarmApp::rescheduleAlarm(KAEvent& event, const KAAlarm& alarm, bool updateCalAndDisplay, const KADateTime& nextDt) { qCDebug(KALARM_LOG) << "KAlarmApp::rescheduleAlarm: Alarm type:" << alarm.type(); int reply = 0; bool update = false; event.startChanges(); if (alarm.repeatAtLogin()) { // Leave an alarm which repeats at every login until its main alarm triggers if (!event.reminderActive() && event.reminderMinutes() < 0) { // Executing an at-login alarm: first schedule the reminder // which occurs AFTER the main alarm. event.activateReminderAfter(KADateTime::currentUtcDateTime()); } // Repeat-at-login alarms are usually unchanged after triggering. // Ensure that the archive flag (which was set in execAlarm()) is saved. update = true; } else if (alarm.isReminder() || alarm.deferred()) { // It's a reminder alarm or an extra deferred alarm, so delete it event.removeExpiredAlarm(alarm.type()); update = true; } else { // Reschedule the alarm for its next occurrence. bool cancelled = false; DateTime last = event.mainDateTime(false); // note this trigger time if (last != event.mainDateTime(true)) last = DateTime(); // but ignore sub-repetition triggers bool next = nextDt.isValid(); KADateTime next_dt = nextDt; const KADateTime now = KADateTime::currentUtcDateTime(); do { const KAEvent::OccurType type = event.setNextOccurrence(next ? next_dt : now); switch (type) { case KAEvent::NO_OCCURRENCE: // All repetitions are finished, so cancel the event qCDebug(KALARM_LOG) << "KAlarmApp::rescheduleAlarm: No occurrence"; if (event.reminderMinutes() < 0 && last.isValid() && alarm.type() != KAAlarm::AT_LOGIN_ALARM && !event.mainExpired()) { // Set the reminder which is now due after the last main alarm trigger. // Note that at-login reminders are scheduled in execAlarm(). event.activateReminderAfter(last); updateCalAndDisplay = true; } if (cancelAlarm(event, alarm.type(), updateCalAndDisplay)) return -1; break; default: if (!(type & KAEvent::OCCURRENCE_REPEAT)) break; // Next occurrence is a repeat, so fall through to recurrence handling Q_FALLTHROUGH(); case KAEvent::RECURRENCE_DATE: case KAEvent::RECURRENCE_DATE_TIME: case KAEvent::LAST_RECURRENCE: // The event is due by now and repetitions still remain, so rewrite the event if (updateCalAndDisplay) update = true; break; case KAEvent::FIRST_OR_ONLY_OCCURRENCE: // The first occurrence is still due?!?, so don't do anything break; } if (cancelled) break; if (event.deferred()) { // Just in case there's also a deferred alarm, ensure it's removed event.removeExpiredAlarm(KAAlarm::DEFERRED_ALARM); update = true; } if (next) { // The alarm is restricted to working hours and/or non-holidays. // Check if the calculated next time is valid. next_dt = event.mainDateTime(true).effectiveKDateTime(); if (event.mainDateTime(false).isDateOnly()) { KADateTime dt(next_dt); dt.setDateOnly(true); next = !event.isWorkingTime(dt); } else next = !event.isWorkingTime(next_dt); } } while (next && next_dt <= now); reply = (!cancelled && next_dt.isValid() && (next_dt <= now)) ? 1 : 0; if (event.reminderMinutes() < 0 && last.isValid() && alarm.type() != KAAlarm::AT_LOGIN_ALARM) { // Set the reminder which is now due after the last main alarm trigger. // Note that at-login reminders are scheduled in execAlarm(). event.activateReminderAfter(last); } } event.endChanges(); if (update) KAlarm::updateEvent(event); // update the window lists and calendar file return reply; } /****************************************************************************** * Delete the alarm. If it is the last alarm for its event, the event is removed * from the calendar file and from every main window instance. * Reply = true if event has been deleted. */ bool KAlarmApp::cancelAlarm(KAEvent& event, KAAlarm::Type alarmType, bool updateCalAndDisplay) { qCDebug(KALARM_LOG) << "KAlarmApp::cancelAlarm"; if (alarmType == KAAlarm::MAIN_ALARM && !event.displaying() && event.toBeArchived()) { // The event is being deleted. Save it in the archived resources first. KAEvent ev(event); KAlarm::addArchivedEvent(ev); } event.removeExpiredAlarm(alarmType); if (!event.alarmCount()) { // If it's a command alarm being executed, mark it as deleted ProcData* pd = findCommandProcess(event.id()); if (pd) pd->eventDeleted = true; // Delete it KAlarm::deleteEvent(event, false); return true; } if (updateCalAndDisplay) KAlarm::updateEvent(event); // update the window lists and calendar file return false; } /****************************************************************************** * Cancel any reminder or deferred alarms in an repeat-at-login event. * This should be called when the event is first loaded. * If there are no more alarms left in the event, the event is removed from the * calendar file and from every main window instance. * Reply = true if event has been deleted. */ bool KAlarmApp::cancelReminderAndDeferral(KAEvent& event) { return cancelAlarm(event, KAAlarm::REMINDER_ALARM, false) || cancelAlarm(event, KAAlarm::DEFERRED_REMINDER_ALARM, false) || cancelAlarm(event, KAAlarm::DEFERRED_ALARM, true); } /****************************************************************************** * Execute an alarm by displaying its message or file, or executing its command. * Reply = ShellProcess instance if a command alarm * = MessageWin if an audio alarm * != 0 if successful * = -1 if execution has not completed * = 0 if the alarm is disabled, or if an error message was output. */ void* KAlarmApp::execAlarm(KAEvent& event, const KAAlarm& alarm, bool reschedule, bool allowDefer, bool noPreAction) { if (!mAlarmsEnabled || !event.enabled()) { // The event (or all events) is disabled qCDebug(KALARM_LOG) << "KAlarmApp::execAlarm:" << event.id() << ": disabled"; if (reschedule) rescheduleAlarm(event, alarm, true); return nullptr; } void* result = (void*)1; event.setArchive(); switch (alarm.action()) { case KAAlarm::COMMAND: if (!event.commandDisplay()) { // execCommandAlarm() will error if the user is not authorised // to run shell commands. result = execCommandAlarm(event, alarm); if (reschedule) rescheduleAlarm(event, alarm, true); break; } Q_FALLTHROUGH(); // fall through to MESSAGE case KAAlarm::MESSAGE: case KAAlarm::FILE: { // Display a message, file or command output, provided that the same event // isn't already being displayed MessageWin* win = MessageWin::findEvent(EventId(event)); // Find if we're changing a reminder message to the real message const bool reminder = (alarm.type() & KAAlarm::REMINDER_ALARM); const bool replaceReminder = !reminder && win && (win->alarmType() & KAAlarm::REMINDER_ALARM); if (!reminder && (!event.deferred() || (event.extraActionOptions() & KAEvent::ExecPreActOnDeferral)) && (replaceReminder || !win) && !noPreAction && !event.preAction().isEmpty()) { // It's not a reminder alarm, and it's not a deferred alarm unless the // pre-alarm action applies to deferred alarms, and there is no message // window (other than a reminder window) currently displayed for this // alarm, and we need to execute a command before displaying the new window. // // NOTE: The pre-action is not executed for a recurring alarm if an // alarm message window for a previous occurrence is still visible. // Check whether the command is already being executed for this alarm. for (const ProcData* pd : qAsConst(mCommandProcesses)) { if (pd->event->id() == event.id() && (pd->flags & ProcData::PRE_ACTION)) { qCDebug(KALARM_LOG) << "KAlarmApp::execAlarm: Already executing pre-DISPLAY command"; return pd->process; // already executing - don't duplicate the action } } // doShellCommand() will error if the user is not authorised to run // shell commands. const QString command = event.preAction(); qCDebug(KALARM_LOG) << "KAlarmApp::execAlarm: Pre-DISPLAY command:" << command; const int flags = (reschedule ? ProcData::RESCHEDULE : 0) | (allowDefer ? ProcData::ALLOW_DEFER : 0); if (doShellCommand(command, event, &alarm, (flags | ProcData::PRE_ACTION))) { ResourcesCalendar::instance()->setAlarmPending(&event); return result; // display the message after the command completes } // Error executing command if (event.extraActionOptions() & KAEvent::CancelOnPreActError) { // Cancel the rest of the alarm execution qCDebug(KALARM_LOG) << "KAlarmApp::execAlarm:" << event.id() << ": pre-action failed: cancelled"; if (reschedule) rescheduleAlarm(event, alarm, true); return nullptr; } // Display the message even though it failed } if (!win) { // There isn't already a message for this event const int flags = (reschedule ? 0 : MessageWin::NO_RESCHEDULE) | (allowDefer ? 0 : MessageWin::NO_DEFER); (new MessageWin(&event, alarm, flags))->show(); } else if (replaceReminder) { // The caption needs to be changed from "Reminder" to "Message" win->cancelReminder(event, alarm); } else if (!win->hasDefer() && !alarm.repeatAtLogin()) { // It's a repeat-at-login message with no Defer button, // which has now reached its final trigger time and needs // to be replaced with a new message. win->showDefer(); win->showDateTime(event, alarm); } else { // Use the existing message window } if (win) { // Raise the existing message window and replay any sound win->repeat(alarm); // N.B. this reschedules the alarm } break; } case KAAlarm::EMAIL: { qCDebug(KALARM_LOG) << "KAlarmApp::execAlarm: EMAIL to:" << event.emailAddresses(QStringLiteral(",")); QStringList errmsgs; KAMail::JobData data(event, alarm, reschedule, (reschedule || allowDefer)); data.queued = true; int ans = KAMail::send(data, errmsgs); if (ans) { // The email has either been sent or failed - not queued if (ans < 0) result = nullptr; // failure data.queued = false; emailSent(data, errmsgs, (ans > 0)); } else { result = (void*)-1; // email has been queued } if (reschedule) rescheduleAlarm(event, alarm, true); break; } case KAAlarm::AUDIO: { // Play the sound, provided that the same event // isn't already playing MessageWin* win = MessageWin::findEvent(EventId(event)); if (!win) { // There isn't already a message for this event. const int flags = (reschedule ? 0 : MessageWin::NO_RESCHEDULE) | MessageWin::ALWAYS_HIDE; win = new MessageWin(&event, alarm, flags); } else { // There's an existing message window: replay the sound win->repeat(alarm); // N.B. this reschedules the alarm } return win; } default: return nullptr; } return result; } /****************************************************************************** * Called when sending an email has completed. */ void KAlarmApp::emailSent(KAMail::JobData& data, const QStringList& errmsgs, bool copyerr) { if (!errmsgs.isEmpty()) { // Some error occurred, although the email may have been sent successfully if (errmsgs.count() > 1) qCDebug(KALARM_LOG) << "KAlarmApp::emailSent:" << (copyerr ? "Copy error:" : "Failed:") << errmsgs[1]; MessageWin::showError(data.event, data.alarm.dateTime(), errmsgs); } else if (data.queued) Q_EMIT execAlarmSuccess(); } /****************************************************************************** * Execute the command specified in a command alarm. * To connect to the output ready signals of the process, specify a slot to be * called by supplying 'receiver' and 'slot' parameters. */ ShellProcess* KAlarmApp::execCommandAlarm(const KAEvent& event, const KAAlarm& alarm, const QObject* receiver, const char* slot) { // doShellCommand() will error if the user is not authorised to run // shell commands. const int flags = (event.commandXterm() ? ProcData::EXEC_IN_XTERM : 0) | (event.commandDisplay() ? ProcData::DISP_OUTPUT : 0); const QString command = event.cleanText(); if (event.commandScript()) { // Store the command script in a temporary file for execution qCDebug(KALARM_LOG) << "KAlarmApp::execCommandAlarm: Script"; const QString tmpfile = createTempScriptFile(command, false, event, alarm); if (tmpfile.isEmpty()) { setEventCommandError(event, KAEvent::CMD_ERROR); return nullptr; } return doShellCommand(tmpfile, event, &alarm, (flags | ProcData::TEMP_FILE), receiver, slot); } else { qCDebug(KALARM_LOG) << "KAlarmApp::execCommandAlarm:" << command; return doShellCommand(command, event, &alarm, flags, receiver, slot); } } /****************************************************************************** * Execute a shell command line specified by an alarm. * If the PRE_ACTION bit of 'flags' is set, the alarm will be executed via * execAlarm() once the command completes, the execAlarm() parameters being * derived from the remaining bits in 'flags'. * 'flags' must contain the bit PRE_ACTION or POST_ACTION if and only if it is * a pre- or post-alarm action respectively. * To connect to the output ready signals of the process, specify a slot to be * called by supplying 'receiver' and 'slot' parameters. * * Note that if shell access is not authorised, the attempt to run the command * will be errored. */ ShellProcess* KAlarmApp::doShellCommand(const QString& command, const KAEvent& event, const KAAlarm* alarm, int flags, const QObject* receiver, const char* slot) { qCDebug(KALARM_LOG) << "KAlarmApp::doShellCommand:" << command << "," << event.id(); QIODevice::OpenMode mode = QIODevice::WriteOnly; QString cmd; QString tmpXtermFile; if (flags & ProcData::EXEC_IN_XTERM) { // Execute the command in a terminal window. cmd = composeXTermCommand(command, event, alarm, flags, tmpXtermFile); if (cmd.isEmpty()) { qCWarning(KALARM_LOG) << "KAlarmApp::doShellCommand: Command failed (no terminal selected)"; const QStringList errors{i18nc("@info", "Failed to execute command\n(no terminal selected for command alarms)")}; commandErrorMsg(nullptr, event, alarm, flags, errors); return nullptr; } } else { cmd = command; mode = QIODevice::ReadWrite; } ProcData* pd = nullptr; ShellProcess* proc = nullptr; if (!cmd.isEmpty()) { // Use ShellProcess, which automatically checks whether the user is // authorised to run shell commands. proc = new ShellProcess(cmd); proc->setEnv(QStringLiteral("KALARM_UID"), event.id(), true); proc->setOutputChannelMode(KProcess::MergedChannels); // combine stdout & stderr connect(proc, &ShellProcess::shellExited, this, &KAlarmApp::slotCommandExited); if ((flags & ProcData::DISP_OUTPUT) && receiver && slot) { connect(proc, SIGNAL(receivedStdout(ShellProcess*)), receiver, slot); connect(proc, SIGNAL(receivedStderr(ShellProcess*)), receiver, slot); } if (mode == QIODevice::ReadWrite && !event.logFile().isEmpty()) { // Output is to be appended to a log file. // Set up a logging process to write the command's output to. QString heading; if (alarm && alarm->dateTime().isValid()) { const QString dateTime = alarm->dateTime().formatLocale(); heading = QStringLiteral("\n******* KAlarm %1 *******\n").arg(dateTime); } else heading = QStringLiteral("\n******* KAlarm *******\n"); QFile logfile(event.logFile()); if (logfile.open(QIODevice::Append | QIODevice::Text)) { QTextStream out(&logfile); out << heading; logfile.close(); } proc->setStandardOutputFile(event.logFile(), QIODevice::Append); } pd = new ProcData(proc, new KAEvent(event), (alarm ? new KAAlarm(*alarm) : nullptr), flags); if (flags & ProcData::TEMP_FILE) pd->tempFiles += command; if (!tmpXtermFile.isEmpty()) pd->tempFiles += tmpXtermFile; mCommandProcesses.append(pd); if (proc->start(mode)) return proc; } // Error executing command - report it qCWarning(KALARM_LOG) << "KAlarmApp::doShellCommand: Command failed to start"; commandErrorMsg(proc, event, alarm, flags); if (pd) { mCommandProcesses.removeAt(mCommandProcesses.indexOf(pd)); delete pd; } return nullptr; } /****************************************************************************** * Compose a command line to execute the given command in a terminal window. * 'tempScriptFile' receives the name of a temporary script file which is * invoked by the command line, if applicable. * Reply = command line, or empty string if error. */ QString KAlarmApp::composeXTermCommand(const QString& command, const KAEvent& event, const KAAlarm* alarm, int flags, QString& tempScriptFile) const { qCDebug(KALARM_LOG) << "KAlarmApp::composeXTermCommand:" << command << "," << event.id(); tempScriptFile.clear(); QString cmd = Preferences::cmdXTermCommand(); if (cmd.isEmpty()) return QString(); // no terminal application is configured cmd.replace(QLatin1String("%t"), KAboutData::applicationData().displayName()); // set the terminal window title if (cmd.indexOf(QLatin1String("%C")) >= 0) { // Execute the command from a temporary script file if (flags & ProcData::TEMP_FILE) cmd.replace(QLatin1String("%C"), command); // the command is already calling a temporary file else { tempScriptFile = createTempScriptFile(command, true, event, *alarm); if (tempScriptFile.isEmpty()) return QString(); cmd.replace(QLatin1String("%C"), tempScriptFile); // %C indicates where to insert the command } } else if (cmd.indexOf(QLatin1String("%W")) >= 0) { // Execute the command from a temporary script file, // with a sleep after the command is executed tempScriptFile = createTempScriptFile(command + QLatin1String("\nsleep 86400\n"), true, event, *alarm); if (tempScriptFile.isEmpty()) return QString(); cmd.replace(QLatin1String("%W"), tempScriptFile); // %w indicates where to insert the command } else if (cmd.indexOf(QLatin1String("%w")) >= 0) { // Append a sleep to the command. // Quote the command in case it contains characters such as [>|;]. const QString exec = KShell::quoteArg(command + QLatin1String("; sleep 86400")); cmd.replace(QLatin1String("%w"), exec); // %w indicates where to insert the command string } else { // Set the command to execute. // Put it in quotes in case it contains characters such as [>|;]. const QString exec = KShell::quoteArg(command); if (cmd.indexOf(QLatin1String("%c")) >= 0) cmd.replace(QLatin1String("%c"), exec); // %c indicates where to insert the command string else cmd.append(exec); // otherwise, simply append the command string } return cmd; } /****************************************************************************** * Create a temporary script file containing the specified command string. * Reply = path of temporary file, or null string if error. */ QString KAlarmApp::createTempScriptFile(const QString& command, bool insertShell, const KAEvent& event, const KAAlarm& alarm) const { QTemporaryFile tmpFile; tmpFile.setAutoRemove(false); // don't delete file when it is destructed if (!tmpFile.open()) qCCritical(KALARM_LOG) << "Unable to create a temporary script file"; else { tmpFile.setPermissions(QFile::ReadUser | QFile::WriteUser | QFile::ExeUser); QTextStream stream(&tmpFile); if (insertShell) stream << "#!" << ShellProcess::shellPath() << "\n"; stream << command; stream.flush(); if (tmpFile.error() != QFile::NoError) qCCritical(KALARM_LOG) << "Error" << tmpFile.errorString() << " writing to temporary script file"; else return tmpFile.fileName(); } const QStringList errmsgs(i18nc("@info", "Error creating temporary script file")); MessageWin::showError(event, alarm.dateTime(), errmsgs, QStringLiteral("Script")); return QString(); } /****************************************************************************** * Called when a command alarm's execution completes. */ void KAlarmApp::slotCommandExited(ShellProcess* proc) { qCDebug(KALARM_LOG) << "KAlarmApp::slotCommandExited"; // Find this command in the command list for (int i = 0, end = mCommandProcesses.count(); i < end; ++i) { ProcData* pd = mCommandProcesses.at(i); if (pd->process == proc) { // Found the command. Check its exit status. bool executeAlarm = pd->preAction(); const ShellProcess::Status status = proc->status(); if (status == ShellProcess::SUCCESS && !proc->exitCode()) { qCDebug(KALARM_LOG) << "KAlarmApp::slotCommandExited:" << pd->event->id() << ": SUCCESS"; clearEventCommandError(*pd->event, pd->preAction() ? KAEvent::CMD_ERROR_PRE : pd->postAction() ? KAEvent::CMD_ERROR_POST : KAEvent::CMD_ERROR); } else { QString errmsg = proc->errorMessage(); if (status == ShellProcess::SUCCESS || status == ShellProcess::NOT_FOUND) qCWarning(KALARM_LOG) << "KAlarmApp::slotCommandExited:" << pd->event->id() << ":" << errmsg << "exit status =" << status << ", code =" << proc->exitCode(); else qCWarning(KALARM_LOG) << "KAlarmApp::slotCommandExited:" << pd->event->id() << ":" << errmsg << "exit status =" << status; if (pd->messageBoxParent) { // Close the existing informational KMessageBox for this process const QList dialogs = pd->messageBoxParent->findChildren(); if (!dialogs.isEmpty()) delete dialogs[0]; setEventCommandError(*pd->event, pd->preAction() ? KAEvent::CMD_ERROR_PRE : pd->postAction() ? KAEvent::CMD_ERROR_POST : KAEvent::CMD_ERROR); if (!pd->tempFile()) { errmsg += QLatin1Char('\n'); errmsg += proc->command(); } KAMessageBox::error(pd->messageBoxParent, errmsg); } else commandErrorMsg(proc, *pd->event, pd->alarm, pd->flags); if (executeAlarm && (pd->event->extraActionOptions() & KAEvent::CancelOnPreActError)) { qCDebug(KALARM_LOG) << "KAlarmApp::slotCommandExited:" << pd->event->id() << ": pre-action failed: cancelled"; if (pd->reschedule()) rescheduleAlarm(*pd->event, *pd->alarm, true); executeAlarm = false; } } if (pd->preAction()) ResourcesCalendar::instance()->setAlarmPending(pd->event, false); if (executeAlarm) execAlarm(*pd->event, *pd->alarm, pd->reschedule(), pd->allowDefer(), true); mCommandProcesses.removeAt(i); delete pd; break; } } // If there are now no executing shell commands, quit if a quit was queued if (mPendingQuit && mCommandProcesses.isEmpty()) quitIf(mPendingQuitCode); } /****************************************************************************** * Output an error message for a shell command, and record the alarm's error status. */ void KAlarmApp::commandErrorMsg(const ShellProcess* proc, const KAEvent& event, const KAAlarm* alarm, int flags, const QStringList& errors) { KAEvent::CmdErrType cmderr; QString dontShowAgain; QStringList errmsgs = errors; if (flags & ProcData::PRE_ACTION) { if (event.extraActionOptions() & KAEvent::DontShowPreActError) return; // don't notify user of any errors for the alarm errmsgs += i18nc("@info", "Pre-alarm action:"); dontShowAgain = QStringLiteral("Pre"); cmderr = KAEvent::CMD_ERROR_PRE; } else if (flags & ProcData::POST_ACTION) { errmsgs += i18nc("@info", "Post-alarm action:"); dontShowAgain = QStringLiteral("Post"); cmderr = (event.commandError() == KAEvent::CMD_ERROR_PRE) ? KAEvent::CMD_ERROR_PRE_POST : KAEvent::CMD_ERROR_POST; } else { dontShowAgain = QStringLiteral("Exec"); cmderr = KAEvent::CMD_ERROR; } // Record the alarm's error status setEventCommandError(event, cmderr); // Display an error message if (proc) { errmsgs += proc->errorMessage(); if (!(flags & ProcData::TEMP_FILE)) errmsgs += proc->command(); dontShowAgain += QString::number(proc->status()); } MessageWin::showError(event, (alarm ? alarm->dateTime() : DateTime()), errmsgs, dontShowAgain); } /****************************************************************************** * Notes that an informational KMessageBox is displayed for this process. */ void KAlarmApp::commandMessage(ShellProcess* proc, QWidget* parent) { // Find this command in the command list for (ProcData* pd : qAsConst(mCommandProcesses)) { if (pd->process == proc) { pd->messageBoxParent = parent; break; } } } /****************************************************************************** * If this is the first time through, open the calendar file, and start * processing the execution queue. */ bool KAlarmApp::initCheck(bool calendarOnly) { static bool firstTime = true; if (firstTime) qCDebug(KALARM_LOG) << "KAlarmApp::initCheck: first time"; if (initialiseTimerResources() || firstTime) { /* Need to open the display calendar now, since otherwise if display * alarms are immediately due, they will often be processed while * MessageWin::redisplayAlarms() is executing open() (but before open() * completes), which causes problems!! */ DisplayCalendar::instance()->open(); } if (firstTime) { setArchivePurgeDays(); firstTime = false; } if (!calendarOnly) startProcessQueue(); // start processing the execution queue return true; } /****************************************************************************** * Called when an audio thread starts or stops. */ void KAlarmApp::notifyAudioPlaying(bool playing) { Q_EMIT audioPlaying(playing); } /****************************************************************************** * Stop audio play. */ void KAlarmApp::stopAudio() { MessageWin::stopAudio(); } /****************************************************************************** * Set the command error for the specified alarm. */ void KAlarmApp::setEventCommandError(const KAEvent& event, KAEvent::CmdErrType err) const { ProcData* pd = findCommandProcess(event.id()); if (pd && pd->eventDeleted) return; // the alarm has been deleted, so can't set error status if (err == KAEvent::CMD_ERROR_POST && event.commandError() == KAEvent::CMD_ERROR_PRE) err = KAEvent::CMD_ERROR_PRE_POST; event.setCommandError(err); KAEvent* ev = ResourcesCalendar::instance()->event(EventId(event)); if (ev && ev->commandError() != err) ev->setCommandError(err); Resource resource = Resources::resourceForEvent(event.id()); resource.handleCommandErrorChange(event); } /****************************************************************************** * Clear the command error for the specified alarm. */ void KAlarmApp::clearEventCommandError(const KAEvent& event, KAEvent::CmdErrType err) const { ProcData* pd = findCommandProcess(event.id()); if (pd && pd->eventDeleted) return; // the alarm has been deleted, so can't set error status KAEvent::CmdErrType newerr = static_cast(event.commandError() & ~err); event.setCommandError(newerr); KAEvent* ev = ResourcesCalendar::instance()->event(EventId(event)); if (ev) { newerr = static_cast(ev->commandError() & ~err); ev->setCommandError(newerr); } Resource resource = Resources::resourceForEvent(event.id()); resource.handleCommandErrorChange(event); } /****************************************************************************** * Find the currently executing command process for an event ID, if any. */ KAlarmApp::ProcData* KAlarmApp::findCommandProcess(const QString& eventId) const { for (ProcData* pd : qAsConst(mCommandProcesses)) { if (pd->event->id() == eventId) return pd; } return nullptr; } KAlarmApp::ProcData::ProcData(ShellProcess* p, KAEvent* e, KAAlarm* a, int f) : process(p) , event(e) , alarm(a) , messageBoxParent(nullptr) , flags(f) , eventDeleted(false) { } KAlarmApp::ProcData::~ProcData() { while (!tempFiles.isEmpty()) { // Delete the temporary file called by the XTerm command QFile f(tempFiles.first()); f.remove(); tempFiles.removeFirst(); } delete process; delete event; delete alarm; } // vim: et sw=4: diff --git a/src/kalarmapp.h b/src/kalarmapp.h index 53fc6a99..107f109f 100644 --- a/src/kalarmapp.h +++ b/src/kalarmapp.h @@ -1,259 +1,259 @@ /* * kalarmapp.h - the KAlarm application object * Program: kalarm * Copyright © 2001-2020 David Jarvie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KALARMAPP_H #define KALARMAPP_H /** @file kalarmapp.h - the KAlarm application object */ #include "eventid.h" #include "kamail.h" #include "preferences.h" #include #include #include #include #include namespace KCal { class Event; } class Resource; class DBusHandler; class MainWindow; class TrayWindow; class ShellProcess; using namespace KAlarmCal; class KAlarmApp : public QApplication { Q_OBJECT public: ~KAlarmApp() override; /** Create the unique instance. */ static KAlarmApp* create(int& argc, char** argv); /** Must be called to complete initialisation after KAboutData is set, * but before the application is activated or restored. */ void initialise(); /** Return the unique instance. */ static KAlarmApp* instance() { return mInstance; } bool checkCalendar() { return initCheck(); } bool wantShowInSystemTray() const; bool alarmsEnabled() const { return mAlarmsEnabled; } bool korganizerEnabled() const { return mKOrganizerEnabled; } - int activate(const QStringList& args, const QString& workingDirectory, QString& outputText) - { return activateInstance(args, workingDirectory, &outputText); } + int activateInstance(const QStringList& args, const QString& workingDirectory, QString* outputText = nullptr); bool restoreSession(); bool quitIf() { return quitIf(0); } void doQuit(QWidget* parent); static void displayFatalError(const QString& message); void addWindow(TrayWindow* w) { mTrayWindow = w; } void removeWindow(TrayWindow*); TrayWindow* trayWindow() const { return mTrayWindow; } MainWindow* trayMainWindow() const; bool displayTrayIcon(bool show, MainWindow* = nullptr); bool trayIconDisplayed() const { return mTrayWindow; } bool editNewAlarm(MainWindow* = nullptr); void* execAlarm(KAEvent&, const KAAlarm&, bool reschedule, bool allowDefer = true, bool noPreAction = false); ShellProcess* execCommandAlarm(const KAEvent&, const KAAlarm&, const QObject* receiver = nullptr, const char* slot = nullptr); void alarmCompleted(const KAEvent&); void rescheduleAlarm(KAEvent& e, const KAAlarm& a) { rescheduleAlarm(e, a, true); } void purgeAll() { purge(0); } void commandMessage(ShellProcess*, QWidget* parent); void notifyAudioPlaying(bool playing); void setSpreadWindowsState(bool spread); bool windowFocusBroken() const; bool needWindowFocusFix() const; - // Methods called indirectly by the DCOP interface + // Methods called indirectly by the D-Bus interface bool scheduleEvent(KAEvent::SubAction, const QString& text, const KADateTime&, int lateCancel, KAEvent::Flags flags, const QColor& bg, const QColor& fg, const QFont&, const QString& audioFile, float audioVolume, int reminderMinutes, const KARecurrence& recurrence, const KCalendarCore::Duration& repeatInterval, int repeatCount, uint mailFromID = 0, const KCalendarCore::Person::List& mailAddresses = KCalendarCore::Person::List(), const QString& mailSubject = QString(), const QStringList& mailAttachments = QStringList()); bool dbusTriggerEvent(const EventId& eventID) { return dbusHandleEvent(eventID, QueuedAction::Trigger); } bool dbusDeleteEvent(const EventId& eventID) { return dbusHandleEvent(eventID, QueuedAction::Cancel); } QString dbusList(); public Q_SLOTS: - void activateByDBus(const QStringList& args, const QString& workingDirectory); + void activateByDBus(const QStringList& args, const QString& workingDirectory) + { activateInstance(args, workingDirectory); } void processQueue(); void setAlarmsEnabled(bool); void purgeNewArchivedDefault(const Resource&); void atLoginEventAdded(const KAEvent&); void notifyAudioStopped() { notifyAudioPlaying(false); } void stopAudio(); void spreadWindows(bool); void emailSent(KAMail::JobData&, const QStringList& errmsgs, bool copyerr = false); Q_SIGNALS: + void setExitValue(int); // set exit code for duplicate application instances void trayIconToggled(); void alarmEnabledToggled(bool); void audioPlaying(bool); void spreadWindowsToggled(bool); void execAlarmSuccess(); private: typedef Preferences::Feb29Type Feb29Type; // allow it to be used in SIGNAL mechanism private Q_SLOTS: void quitFatal(); void checkNextDueAlarm(); void slotShowInSystemTrayChanged(); void changeStartOfDay(); void slotWorkTimeChanged(const QTime& start, const QTime& end, const QBitArray& days); void slotHolidaysChanged(const KHolidays::HolidayRegion&); void slotFeb29TypeChanged(Feb29Type); void slotResourcesTimeout(); void slotResourcesCreated(); void slotEditAlarmById(); void promptArchivedCalendar(); void slotMessageFontChanged(const QFont&); void setArchivePurgeDays(); void slotResourceAdded(const Resource&); void slotResourcePopulated(const Resource&); void slotPurge() { purge(mArchivedPurgeDays); } void slotCommandExited(ShellProcess*); private: // Actions to execute in processQueue(). May be OR'ed together. enum class QueuedAction { // Action to execute ActionMask = 0x07, // bit mask to extract action to execute Handle = 0x01, // if the alarm is due, execute it and then reschedule it Trigger = 0x02, // execute the alarm regardless, and then reschedule it if it's already due Cancel = 0x03, // delete the alarm Edit = 0x04, // edit an alarm (command line option) List = 0x05, // list all alarms (command line option) // Modifier flags FindId = 0x10, // search all resources for unique event ID Exit = 0x20 // exit application after executing action }; struct ProcData { ProcData(ShellProcess*, KAEvent*, KAAlarm*, int flags = 0); ~ProcData(); enum { PRE_ACTION = 0x01, POST_ACTION = 0x02, RESCHEDULE = 0x04, ALLOW_DEFER = 0x08, TEMP_FILE = 0x10, EXEC_IN_XTERM = 0x20, DISP_OUTPUT = 0x40 }; bool preAction() const { return flags & PRE_ACTION; } bool postAction() const { return flags & POST_ACTION; } bool reschedule() const { return flags & RESCHEDULE; } bool allowDefer() const { return flags & ALLOW_DEFER; } bool tempFile() const { return flags & TEMP_FILE; } bool execInXterm() const { return flags & EXEC_IN_XTERM; } bool dispOutput() const { return flags & DISP_OUTPUT; } ShellProcess* process; KAEvent* event; KAAlarm* alarm; QPointer messageBoxParent; QStringList tempFiles; int flags; bool eventDeleted; }; struct ActionQEntry { ActionQEntry(QueuedAction a, const EventId& id) : action(a), eventId(id) { } ActionQEntry(QueuedAction a, const EventId& id, const QString& resId) : action(a), eventId(id), resourceId(resId) { } ActionQEntry(const KAEvent& e, QueuedAction a = QueuedAction::Handle) : action(a), event(e) { } ActionQEntry() { } QueuedAction action; EventId eventId; KAEvent event; QString resourceId; // resource ID or name, if resources not created yet }; KAlarmApp(int& argc, char** argv); bool initialiseTimerResources(); - int activateInstance(const QStringList& args, const QString& workingDirectory, QString* outputText); bool initCheck(bool calendarOnly = false); bool quitIf(int exitCode, bool force = false); void createOnlyMainWindow(); bool checkSystemTray(); void startProcessQueue(); void setResourcesTimeout(); void checkWritableCalendar(); void checkArchivedCalendar(); void queueAlarmId(const KAEvent&); bool dbusHandleEvent(const EventId&, QueuedAction); bool handleEvent(const EventId&, QueuedAction, bool findUniqueId = false); int rescheduleAlarm(KAEvent&, const KAAlarm&, bool updateCalAndDisplay, const KADateTime& nextDt = KADateTime()); bool cancelAlarm(KAEvent&, KAAlarm::Type, bool updateCalAndDisplay); bool cancelReminderAndDeferral(KAEvent&); ShellProcess* doShellCommand(const QString& command, const KAEvent&, const KAAlarm*, int flags = 0, const QObject* receiver = nullptr, const char* slot = nullptr); QString composeXTermCommand(const QString& command, const KAEvent&, const KAAlarm*, int flags, QString& tempScriptFile) const; QString createTempScriptFile(const QString& command, bool insertShell, const KAEvent&, const KAAlarm&) const; void commandErrorMsg(const ShellProcess*, const KAEvent&, const KAAlarm*, int flags = 0, const QStringList& errmsgs = QStringList()); void purge(int daysToKeep); QStringList scheduledAlarmList(); void setEventCommandError(const KAEvent&, KAEvent::CmdErrType) const; void clearEventCommandError(const KAEvent&, KAEvent::CmdErrType) const; ProcData* findCommandProcess(const QString& eventId) const; static KAlarmApp* mInstance; // the one and only KAlarmApp instance static int mActiveCount; // number of active instances without main windows static int mFatalError; // a fatal error has occurred - just wait to exit static QString mFatalMessage; // fatal error message to output QString mCommandOption; // command option used on command line bool mInitialised {false}; // initialisation complete: ready to process execution queue bool mRedisplayAlarms {false}; // need to redisplay alarms when collection tree fetched bool mQuitting {false}; // a forced quit is in progress bool mReadOnly {false}; // only read-only access to calendars is needed QString mActivateArg0; // activate()'s first arg the first time it was called - DBusHandler* mDBusHandler; // the parent of the main DCOP receiver object + DBusHandler* mDBusHandler; // the parent of the main D-Bus receiver object TrayWindow* mTrayWindow {nullptr}; // active system tray icon QTimer* mAlarmTimer {nullptr}; // activates KAlarm when next alarm is due QColor mPrefsArchivedColour; // archived alarms text colour int mArchivedPurgeDays {-1}; // how long to keep archived alarms, 0 = don't keep, -1 = keep indefinitely int mPurgeDaysQueued {-1}; // >= 0 to purge the archive calendar from KAlarmApp::processLoop() QVector mPendingPurges; // new resources which may need to be purged when populated QList mCommandProcesses; // currently active command alarm processes QQueue mActionQueue; // queued commands and actions int mEditingCmdLineAlarm {0}; // whether currently editing alarm specified on command line int mPendingQuitCode; // exit code for a pending quit - bool mPendingQuit {false}; // quit once the DCOP command and shell command queues have been processed + bool mPendingQuit {false}; // quit once the D-Bus command and shell command queues have been processed bool mCancelRtcWake {false}; // cancel RTC wake on quitting bool mProcessingQueue {false}; // a mActionQueue entry is currently being processed bool mNoSystemTray; // no system tray exists bool mOldShowInSystemTray; // showing in system tray was selected bool mAlarmsEnabled {true}; // alarms are enabled bool mKOrganizerEnabled; // KOrganizer options are enabled (korganizer exists) bool mWindowFocusBroken; // keyboard focus transfer between windows doesn't work bool mResourcesTimedOut {false}; // timeout has expired for populating resources }; inline KAlarmApp* theApp() { return KAlarmApp::instance(); } #endif // KALARMAPP_H // vim: et sw=4: diff --git a/src/main.cpp b/src/main.cpp index f9adfd1d..9b903bc3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,99 +1,104 @@ /* * main.cpp * Program: kalarm * Copyright © 2001-2020 David Jarvie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kalarm.h" #include "kalarm-version-string.h" #include "kalarmapp.h" #include "kalarm_debug.h" #include #include #include +#include #include #include #include #include #include #define PROGRAM_NAME "kalarm" int main(int argc, char* argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); // Use QScopedPointer to ensure the QCoreApplication instance is deleted // before libraries unload, to avoid crashes during clean-up. QScopedPointer app(KAlarmApp::create(argc, argv)); const QStringList args = app->arguments(); KCrash::initialize(); - KLocalizedString::setApplicationDomain("kalarm"); - KAboutData aboutData(QStringLiteral(PROGRAM_NAME), i18n("KAlarm"), + KAboutData aboutData(QStringLiteral(PROGRAM_NAME), i18n(KALARM_NAME), QStringLiteral(KALARM_FULL_VERSION), i18n("Personal alarm message, command and email scheduler by KDE"), KAboutLicense::GPL, ki18n("Copyright 2001-%1, David Jarvie").subs(2020).toString(), QString(), QStringLiteral("http://www.astrojar.org.uk/kalarm")); aboutData.addAuthor(i18n("David Jarvie"), i18n("Author"), QStringLiteral("djarvie@kde.org")); aboutData.setOrganizationDomain("kde.org"); - aboutData.setDesktopFileName(QStringLiteral("org.kde.kalarm")); + aboutData.setDesktopFileName(QStringLiteral(KALARM_DBUS_SERVICE)); KAboutData::setApplicationData(aboutData); + // Make this a unique application. + KDBusService service(KDBusService::Unique); + QObject::connect(&service, &KDBusService::activateRequested, app.data(), &KAlarmApp::activateByDBus); + QObject::connect(app.data(), &KAlarmApp::setExitValue, &service, [&service](int ret) { service.setExitValue(ret); }); + qCDebug(KALARM_LOG) << "initialising"; app->initialise(); QString outputText; - int exitCode = app->activate(args, QDir::currentPath(), outputText); + int exitCode = app->activateInstance(args, QDir::currentPath(), &outputText); if (exitCode >= 0) { if (exitCode > 0) std::cout << qPrintable(outputText) << std::endl; else std::cerr << qPrintable(outputText) << std::endl; exit(exitCode); } app->restoreSession(); return app->exec(); } namespace KAlarm { /****************************************************************************** * Return the current KAlarm version number. */ int Version() { static int version = 0; if (!version) version = KAlarmCal::getVersionNumber(QStringLiteral(KALARM_VERSION)); return version; } } // namespace KAlarm // vim: et sw=4: