Warning, file /pim/kalarm/src/commandoptions.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 * commandoptions.cpp - extract command line options 0003 * Program: kalarm 0004 * SPDX-FileCopyrightText: 2001-2024 David Jarvie <djarvie@kde.org> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "commandoptions.h" 0010 0011 #include "functions.h" 0012 #include "kamail.h" 0013 #include "preferences.h" 0014 #include "kalarmcalendar/identities.h" 0015 #include "kalarm_debug.h" 0016 0017 #include <KLocalizedString> 0018 0019 #include <QCommandLineParser> 0020 0021 #include <iostream> 0022 0023 namespace 0024 { 0025 enum Option 0026 { 0027 ACK_CONFIRM, 0028 ATTACH, 0029 AUTO_CLOSE, 0030 BCC, 0031 BEEP, 0032 COLOUR, 0033 COLOURFG, 0034 OptCANCEL_EVENT, 0035 DISABLE, 0036 DISABLE_ALL, 0037 EXEC, 0038 EXEC_DISPLAY, 0039 OptEDIT, 0040 EDIT_NEW_DISPLAY, 0041 EDIT_NEW_COMMAND, 0042 EDIT_NEW_EMAIL, 0043 EDIT_NEW_AUDIO, 0044 OptEDIT_NEW_PRESET, 0045 OptFILE, 0046 FROM_ID, 0047 INTERVAL, 0048 KORGANIZER, 0049 LATE_CANCEL, 0050 OptLIST, 0051 LOGIN, 0052 MAIL, 0053 NAME, 0054 NOTIFY, 0055 PLAY, 0056 PLAY_REPEAT, 0057 RECURRENCE, 0058 REMINDER, 0059 REMINDER_ONCE, 0060 REPEAT, 0061 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT 0062 SPEAK, 0063 #endif 0064 SUBJECT, 0065 #ifndef NDEBUG 0066 TEST_SET_TIME, 0067 #endif 0068 TIME, 0069 OptTRAY, 0070 OptTRIGGER_EVENT, 0071 UNTIL, 0072 VOLUME, 0073 Num_Options, // number of Option values 0074 Opt_Message // special value representing "message" 0075 }; 0076 0077 bool convInterval(const QString& timeParam, KARecurrence::Type&, int& timeInterval, bool allowMonthYear = false); 0078 } 0079 0080 class CommandOptions::Private 0081 { 0082 public: 0083 explicit Private(CommandOptions* parent) 0084 : p(parent) 0085 {} 0086 bool checkCommand(Option, Command, EditAlarmDlg::Type = EditAlarmDlg::NO_TYPE); 0087 void setError(const QString& error); 0088 void setErrorRequires(Option opt, Option opt2, Option opt3 = Num_Options); 0089 void setErrorParameter(Option); 0090 void setErrorIncompatible(Option opt1, Option opt2); 0091 void checkEditType(EditAlarmDlg::Type type, Option opt) 0092 { checkEditType(type, EditAlarmDlg::NO_TYPE, opt); } 0093 void checkEditType(EditAlarmDlg::Type, EditAlarmDlg::Type, Option); 0094 QString optionName(Option, bool shortName = false) const; 0095 0096 CommandOptions* const p; 0097 Option mCommandOpt; // option for the selected command 0098 }; 0099 0100 /*===========================================================================*/ 0101 0102 CommandOptions* CommandOptions::mFirstInstance = nullptr; 0103 0104 CommandOptions::CommandOptions() 0105 : d(new Private(this)) 0106 , mOptions(Num_Options, nullptr) 0107 , mBgColour(Preferences::defaultBgColour()) 0108 , mFgColour(Preferences::defaultFgColour()) 0109 , mFlags(KAEvent::DEFAULT_FONT) 0110 { 0111 if (!mFirstInstance) 0112 mFirstInstance = this; 0113 } 0114 0115 /****************************************************************************** 0116 * Set the command line options for the parser, and remove any arguments 0117 * following --exec or --exec-display (since the parser can't handle this). 0118 * Reply = command line arguments for parser. 0119 */ 0120 QStringList CommandOptions::setOptions(QCommandLineParser* parser, const QStringList& args) 0121 { 0122 mParser = parser; 0123 0124 mOptions[ACK_CONFIRM] 0125 = new QCommandLineOption(QStringList{QStringLiteral("a"), QStringLiteral("ack-confirm")}, 0126 i18n("Prompt for confirmation when alarm is acknowledged")); 0127 mOptions[ATTACH] 0128 = new QCommandLineOption(QStringList{QStringLiteral("A"), QStringLiteral("attach")}, 0129 i18n("Attach file to email (repeat as needed)"), 0130 QStringLiteral("url")); 0131 mOptions[AUTO_CLOSE] 0132 = new QCommandLineOption(QStringLiteral("auto-close"), 0133 i18n("Auto-close alarm window after --late-cancel period")); 0134 mOptions[BCC] 0135 = new QCommandLineOption(QStringLiteral("bcc"), 0136 i18n("Blind copy email to self")); 0137 mOptions[BEEP] 0138 = new QCommandLineOption(QStringList{QStringLiteral("b"), QStringLiteral("beep")}, 0139 i18n("Beep when message is displayed")); 0140 mOptions[COLOUR] 0141 = new QCommandLineOption(QStringList{QStringLiteral("c"), QStringLiteral("colour"), QStringLiteral("color")}, 0142 i18n("Message background color (name or hex 0xRRGGBB)"), 0143 QStringLiteral("color")); 0144 mOptions[COLOURFG] 0145 = new QCommandLineOption(QStringList{QStringLiteral("C"), QStringLiteral("colourfg"), QStringLiteral("colorfg")}, 0146 i18n("Message foreground color (name or hex 0xRRGGBB)"), 0147 QStringLiteral("color")); 0148 mOptions[OptCANCEL_EVENT] 0149 = new QCommandLineOption(QStringLiteral("cancelEvent"), 0150 i18n("Cancel alarm with the specified event ID"), 0151 QStringLiteral("eventID")); 0152 mOptions[DISABLE] 0153 = new QCommandLineOption(QStringList{QStringLiteral("d"), QStringLiteral("disable")}, 0154 i18n("Disable the alarm")); 0155 mOptions[DISABLE_ALL] 0156 = new QCommandLineOption(QStringLiteral("disable-all"), 0157 i18n("Disable monitoring of all alarms")); 0158 mOptions[EXEC] 0159 = new QCommandLineOption(QStringList{QStringLiteral("e"), QStringLiteral("exec")}, 0160 i18n("Execute a shell command line"), 0161 QStringLiteral("commandLine")); 0162 mOptions[EXEC_DISPLAY] 0163 = new QCommandLineOption(QStringList{QStringLiteral("E"), QStringLiteral("exec-display")}, 0164 i18n("Command line to generate alarm message text"), 0165 QStringLiteral("commandLine")); 0166 mOptions[OptEDIT] 0167 = new QCommandLineOption(QStringLiteral("edit"), 0168 i18n("Display the alarm edit dialog to edit the specified alarm"), 0169 QStringLiteral("eventID")); 0170 mOptions[EDIT_NEW_DISPLAY] 0171 = new QCommandLineOption(QStringLiteral("edit-new-display"), 0172 i18n("Display the alarm edit dialog to edit a new display alarm")); 0173 mOptions[EDIT_NEW_COMMAND] 0174 = new QCommandLineOption(QStringLiteral("edit-new-command"), 0175 i18n("Display the alarm edit dialog to edit a new command alarm")); 0176 mOptions[EDIT_NEW_EMAIL] 0177 = new QCommandLineOption(QStringLiteral("edit-new-email"), 0178 i18n("Display the alarm edit dialog to edit a new email alarm")); 0179 mOptions[EDIT_NEW_AUDIO] 0180 = new QCommandLineOption(QStringLiteral("edit-new-audio"), 0181 i18n("Display the alarm edit dialog to edit a new audio alarm")); 0182 mOptions[OptEDIT_NEW_PRESET] 0183 = new QCommandLineOption(QStringLiteral("edit-new-preset"), 0184 i18n("Display the alarm edit dialog, preset with a template"), 0185 QStringLiteral("templateName")); 0186 mOptions[OptFILE] 0187 = new QCommandLineOption(QStringList{QStringLiteral("f"), QStringLiteral("file")}, 0188 i18n("File to display"), 0189 QStringLiteral("url")); 0190 mOptions[FROM_ID] 0191 = new QCommandLineOption(QStringList{QStringLiteral("F"), QStringLiteral("from-id")}, 0192 i18n("KMail identity to use as sender of email"), 0193 QStringLiteral("ID")); 0194 mOptions[INTERVAL] 0195 = new QCommandLineOption(QStringList{QStringLiteral("i"), QStringLiteral("interval")}, 0196 i18n("Interval between alarm repetitions"), 0197 QStringLiteral("period")); 0198 mOptions[KORGANIZER] 0199 = new QCommandLineOption(QStringList{QStringLiteral("k"), QStringLiteral("korganizer")}, 0200 i18n("Show alarm as an event in KOrganizer")); 0201 mOptions[LATE_CANCEL] 0202 = new QCommandLineOption(QStringList{QStringLiteral("l"), QStringLiteral("late-cancel")}, 0203 i18n("Cancel alarm if more than 'period' late when triggered"), 0204 QStringLiteral("period"), 0205 QStringLiteral("1")); 0206 mOptions[OptLIST] 0207 = new QCommandLineOption(QStringLiteral("list"), 0208 i18n("Output list of scheduled alarms to stdout")); 0209 mOptions[LOGIN] 0210 = new QCommandLineOption(QStringList{QStringLiteral("L"), QStringLiteral("login")}, 0211 i18n("Repeat alarm at every login")); 0212 mOptions[MAIL] 0213 = new QCommandLineOption(QStringList{QStringLiteral("m"), QStringLiteral("mail")}, 0214 i18n("Send an email to the given address (repeat as needed)"), 0215 QStringLiteral("address")); 0216 mOptions[NAME] 0217 = new QCommandLineOption(QStringList{QStringLiteral("n"), QStringLiteral("name")}, 0218 i18n("Name of alarm"), 0219 QStringLiteral("name")); 0220 mOptions[NOTIFY] 0221 = new QCommandLineOption(QStringList{QStringLiteral("N"), QStringLiteral("notify")}, 0222 i18n("Display alarm message as a notification")); 0223 mOptions[PLAY] 0224 = new QCommandLineOption(QStringList{QStringLiteral("p"), QStringLiteral("play")}, 0225 i18n("Audio file to play once"), 0226 QStringLiteral("url")); 0227 mOptions[PLAY_REPEAT] 0228 = new QCommandLineOption(QStringList{QStringLiteral("P"), QStringLiteral("play-repeat")}, 0229 i18n("Audio file to play repeatedly"), 0230 QStringLiteral("url")); 0231 mOptions[RECURRENCE] 0232 = new QCommandLineOption(QStringLiteral("recurrence"), 0233 i18n("Specify alarm recurrence using iCalendar syntax"), 0234 QStringLiteral("spec")); 0235 mOptions[REMINDER] 0236 = new QCommandLineOption(QStringList{QStringLiteral("R"), QStringLiteral("reminder")}, 0237 i18n("Display reminder before or after alarm"), 0238 QStringLiteral("period")); 0239 mOptions[REMINDER_ONCE] 0240 = new QCommandLineOption(QStringLiteral("reminder-once"), 0241 i18n("Display reminder once, before or after first alarm recurrence"), 0242 QStringLiteral("period")); 0243 mOptions[REPEAT] 0244 = new QCommandLineOption(QStringList{QStringLiteral("r"), QStringLiteral("repeat")}, 0245 i18n("Number of times to repeat alarm (including initial occasion)"), 0246 QStringLiteral("count")); 0247 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT 0248 mOptions[SPEAK] 0249 = new QCommandLineOption(QStringList{QStringLiteral("s"), QStringLiteral("speak")}, 0250 i18n("Speak the message when it is displayed")); 0251 #endif 0252 mOptions[SUBJECT] 0253 = new QCommandLineOption(QStringList{QStringLiteral("S"), QStringLiteral("subject")}, 0254 i18n("Email subject line"), 0255 QStringLiteral("text")); 0256 #ifndef NDEBUG 0257 mOptions[TEST_SET_TIME] 0258 = new QCommandLineOption(QStringLiteral("test-set-time"), 0259 i18n("Simulate system time [[[yyyy-]mm-]dd-]hh:mm [TZ] (debug mode)"), 0260 QStringLiteral("time")); 0261 #endif 0262 mOptions[TIME] 0263 = new QCommandLineOption(QStringList{QStringLiteral("t"), QStringLiteral("time")}, 0264 i18n("Trigger alarm at time [[[yyyy-]mm-]dd-]hh:mm [TZ], or date yyyy-mm-dd [TZ]"), 0265 QStringLiteral("time")); 0266 mOptions[OptTRAY] 0267 = new QCommandLineOption(QStringLiteral("tray"), 0268 i18n("Display system tray icon")); 0269 mOptions[OptTRIGGER_EVENT] 0270 = new QCommandLineOption(QStringLiteral("triggerEvent"), 0271 i18n("Trigger alarm with the specified event ID"), 0272 QStringLiteral("eventID")); 0273 mOptions[UNTIL] 0274 = new QCommandLineOption(QStringList{QStringLiteral("u"), QStringLiteral("until")}, 0275 i18n("Repeat until time [[[yyyy-]mm-]dd-]hh:mm [TZ], or date yyyy-mm-dd [TZ]"), 0276 QStringLiteral("time")); 0277 mOptions[VOLUME] 0278 = new QCommandLineOption(QStringList{QStringLiteral("V"), QStringLiteral("volume")}, 0279 i18n("Volume to play audio file"), 0280 QStringLiteral("percent")); 0281 0282 for (int i = 0; i < mOptions.size(); ++i) 0283 { 0284 if (!mOptions.at(i)) 0285 qCCritical(KALARM_LOG) << "CommandOptions::setOptions: Command option" << i << "not initialised"; 0286 else 0287 mParser->addOption(*(mOptions.at(i))); 0288 } 0289 mParser->addPositionalArgument(QStringLiteral("message"), 0290 i18n("Message text to display"), 0291 QStringLiteral("[message]")); 0292 0293 // Check for any options which eat up all following arguments. 0294 mNonExecArguments.clear(); 0295 for (int i = 0; i < args.size(); ++i) 0296 { 0297 const QString arg = args[i]; 0298 if (arg == QLatin1String("--nofork")) 0299 continue; // Ignore debugging option 0300 mNonExecArguments << arg; 0301 if (arg == d->optionName(EXEC) || arg == d->optionName(EXEC, true) 0302 || arg == d->optionName(EXEC_DISPLAY) || arg == d->optionName(EXEC_DISPLAY, true)) 0303 { 0304 // All following arguments (including ones beginning with '-') 0305 // belong to this option. QCommandLineParser can't handle this, so 0306 // remove them from the command line. 0307 ++i; // leave the first argument, which is the command to be executed 0308 while (++i < args.size()) 0309 mExecArguments << args[i]; 0310 } 0311 } 0312 return mNonExecArguments; 0313 } 0314 0315 void CommandOptions::parse() 0316 { 0317 // First check for parse errors (unknown option, missing argument). 0318 // Simply calling mParser->process() would exit the program if an error is found, 0319 // which isn't the correct action if another KAlarm instance is running. 0320 if (!mParser->parse(mNonExecArguments)) 0321 { 0322 qCWarning(KALARM_LOG) << "CommandOptions::parse:" << mParser->errorText(); 0323 mError.clear(); 0324 d->setError(mParser->errorText()); 0325 } 0326 else 0327 mParser->process(mNonExecArguments); 0328 } 0329 0330 void CommandOptions::process() 0331 { 0332 if (mCommand == CMD_ERROR) 0333 return; 0334 0335 #ifndef NDEBUG 0336 if (mParser->isSet(*mOptions.at(TEST_SET_TIME))) 0337 { 0338 const QString time = mParser->value(*mOptions.at(TEST_SET_TIME)); 0339 if (!KAlarm::convertTimeString(time.toLatin1(), mSimulationTime, KADateTime::realCurrentLocalDateTime(), true)) 0340 d->setErrorParameter(TEST_SET_TIME); 0341 } 0342 #endif 0343 if (d->checkCommand(OptTRAY, TRAY)) 0344 { 0345 } 0346 if (d->checkCommand(OptLIST, LIST)) 0347 { 0348 if (!mParser->positionalArguments().empty()) 0349 d->setErrorParameter(OptLIST); 0350 } 0351 if (d->checkCommand(OptTRIGGER_EVENT, TRIGGER_EVENT) 0352 || d->checkCommand(OptCANCEL_EVENT, CANCEL_EVENT) 0353 || d->checkCommand(OptEDIT, EDIT)) 0354 { 0355 // Fetch the resource and event IDs. The supplied ID is the event ID, 0356 // optionally prefixed by the resource ID followed by a colon delimiter. 0357 mResourceId = EventId::extractIDs(mParser->value(*mOptions.at(d->mCommandOpt)), mEventId); 0358 } 0359 if (d->checkCommand(OptEDIT_NEW_PRESET, EDIT_NEW_PRESET)) 0360 { 0361 mName = mParser->value(*mOptions.at(d->mCommandOpt)); 0362 } 0363 if (d->checkCommand(OptFILE, NEW)) 0364 { 0365 mEditType = EditAlarmDlg::DISPLAY; 0366 mEditAction = KAEvent::SubAction::File; 0367 mEditActionSet = true; 0368 mText = mParser->value(*mOptions.at(d->mCommandOpt)); 0369 } 0370 if (d->checkCommand(EXEC_DISPLAY, NEW)) 0371 { 0372 mEditType = EditAlarmDlg::DISPLAY; 0373 mEditAction = KAEvent::SubAction::Command; 0374 mEditActionSet = true; 0375 mFlags |= KAEvent::DISPLAY_COMMAND; 0376 mText = mParser->value(*mOptions.at(d->mCommandOpt)) + QLatin1String(" ") + mExecArguments.join(QLatin1Char(' ')); 0377 } 0378 if (d->checkCommand(EXEC, NEW)) 0379 { 0380 mEditType = EditAlarmDlg::COMMAND; 0381 mEditAction = KAEvent::SubAction::Command; 0382 mEditActionSet = true; 0383 mText = mParser->value(*mOptions.at(d->mCommandOpt)) + QLatin1String(" ") + mExecArguments.join(QLatin1Char(' ')); 0384 } 0385 if (d->checkCommand(MAIL, NEW)) 0386 { 0387 mEditType = EditAlarmDlg::EMAIL; 0388 mEditAction = KAEvent::SubAction::Email; 0389 mEditActionSet = true; 0390 } 0391 if (d->checkCommand(EDIT_NEW_DISPLAY, EDIT_NEW, EditAlarmDlg::DISPLAY)) 0392 { 0393 mEditType = EditAlarmDlg::DISPLAY; 0394 if (!mEditActionSet || (mEditAction != KAEvent::SubAction::Command && mEditAction != KAEvent::SubAction::File)) 0395 { 0396 mEditAction = KAEvent::SubAction::Message; 0397 mEditActionSet = true; 0398 } 0399 const QStringList args = mParser->positionalArguments(); 0400 if (!args.empty()) 0401 mText = args[0]; 0402 } 0403 if (d->checkCommand(EDIT_NEW_COMMAND, EDIT_NEW)) 0404 { 0405 mEditType = EditAlarmDlg::COMMAND; 0406 mEditAction = KAEvent::SubAction::Command; 0407 mEditActionSet = true; 0408 } 0409 if (d->checkCommand(EDIT_NEW_EMAIL, EDIT_NEW, EditAlarmDlg::EMAIL)) 0410 { 0411 mEditType = EditAlarmDlg::EMAIL; 0412 mEditAction = KAEvent::SubAction::Email; 0413 mEditActionSet = true; 0414 } 0415 if (d->checkCommand(EDIT_NEW_AUDIO, EDIT_NEW, EditAlarmDlg::AUDIO)) 0416 { 0417 mEditType = EditAlarmDlg::AUDIO; 0418 mEditAction = KAEvent::SubAction::Audio; 0419 mEditActionSet = true; 0420 } 0421 if (mError.isEmpty() && mCommand == NONE) 0422 { 0423 if (mParser->positionalArguments().empty()) 0424 { 0425 if (d->checkCommand(PLAY, NEW) || d->checkCommand(PLAY_REPEAT, NEW)) 0426 { 0427 mEditType = EditAlarmDlg::AUDIO; 0428 mEditAction = KAEvent::SubAction::Audio; 0429 mEditActionSet = true; 0430 } 0431 } 0432 else 0433 { 0434 qCDebug(KALARM_LOG) << "CommandOptions::process: Message"; 0435 mCommand = NEW; 0436 d->mCommandOpt = Opt_Message; 0437 mEditType = EditAlarmDlg::DISPLAY; 0438 mEditAction = KAEvent::SubAction::Message; 0439 mEditActionSet = true; 0440 mText = arg(0); 0441 } 0442 } 0443 if (mEditActionSet && mEditAction == KAEvent::SubAction::Email) 0444 { 0445 if (mParser->isSet(*mOptions.at(SUBJECT))) 0446 mSubject = mParser->value(*mOptions.at(SUBJECT)); 0447 if (mParser->isSet(*mOptions.at(FROM_ID))) 0448 mFromID = Identities::identityUoid(mParser->value(*mOptions.at(FROM_ID))); 0449 const QStringList mailParams = mParser->values(*mOptions.at(MAIL)); 0450 for (const QString& addr : mailParams) 0451 { 0452 QString a(addr); 0453 if (!KAMail::checkAddress(a)) 0454 d->setError(xi18nc("@info:shell", "<icode>%1</icode>: invalid email address", d->optionName(MAIL))); 0455 KCalendarCore::Person person(QString(), addr); 0456 mAddressees += person; 0457 } 0458 const QStringList attParams = mParser->values(*mOptions.at(ATTACH)); 0459 for (const QString& att : attParams) 0460 mAttachments += att; 0461 const QStringList args = mParser->positionalArguments(); 0462 if (!args.empty()) 0463 mText = args[0]; 0464 } 0465 if (mParser->isSet(*mOptions.at(DISABLE_ALL))) 0466 { 0467 if (mCommand == TRIGGER_EVENT || mCommand == LIST) 0468 d->setErrorIncompatible(DISABLE_ALL, d->mCommandOpt); 0469 mDisableAll = true; 0470 } 0471 0472 // Check that other options are only specified for the 0473 // correct main command options. 0474 d->checkEditType(EditAlarmDlg::DISPLAY, COLOUR); 0475 d->checkEditType(EditAlarmDlg::DISPLAY, COLOURFG); 0476 d->checkEditType(EditAlarmDlg::DISPLAY, EditAlarmDlg::AUDIO, PLAY); 0477 d->checkEditType(EditAlarmDlg::DISPLAY, EditAlarmDlg::AUDIO, PLAY_REPEAT); 0478 d->checkEditType(EditAlarmDlg::DISPLAY, EditAlarmDlg::AUDIO, VOLUME); 0479 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT 0480 d->checkEditType(EditAlarmDlg::DISPLAY, SPEAK); 0481 #endif 0482 d->checkEditType(EditAlarmDlg::DISPLAY, BEEP); 0483 d->checkEditType(EditAlarmDlg::DISPLAY, REMINDER); 0484 d->checkEditType(EditAlarmDlg::DISPLAY, REMINDER_ONCE); 0485 d->checkEditType(EditAlarmDlg::DISPLAY, ACK_CONFIRM); 0486 d->checkEditType(EditAlarmDlg::DISPLAY, AUTO_CLOSE); 0487 d->checkEditType(EditAlarmDlg::DISPLAY, NOTIFY); 0488 d->checkEditType(EditAlarmDlg::EMAIL, SUBJECT); 0489 d->checkEditType(EditAlarmDlg::EMAIL, FROM_ID); 0490 d->checkEditType(EditAlarmDlg::EMAIL, ATTACH); 0491 d->checkEditType(EditAlarmDlg::EMAIL, BCC); 0492 0493 switch (mCommand) 0494 { 0495 case EDIT_NEW: 0496 if (mParser->isSet(*mOptions.at(DISABLE))) 0497 d->setErrorIncompatible(DISABLE, d->mCommandOpt); 0498 [[fallthrough]]; // fall through to NEW 0499 case NEW: 0500 { 0501 // Display a message or file, execute a command, or send an email 0502 if (mParser->isSet(*mOptions.at(NAME))) 0503 { 0504 // Alarm name is specified 0505 mName = mParser->value(*mOptions.at(NAME)); 0506 } 0507 if (mParser->isSet(*mOptions.at(COLOUR))) 0508 { 0509 // Background colour is specified 0510 QString colourText = mParser->value(*mOptions.at(COLOUR)); 0511 if (colourText.at(0) == QLatin1Char('0') 0512 && colourText.at(1).toLower() == QLatin1Char('x')) 0513 colourText.replace(0, 2, QStringLiteral("#")); 0514 mBgColour.setNamedColor(colourText); 0515 if (!mBgColour.isValid()) 0516 d->setErrorParameter(COLOUR); 0517 } 0518 if (mParser->isSet(*mOptions.at(COLOURFG))) 0519 { 0520 // Foreground colour is specified 0521 QString colourText = mParser->value(*mOptions.at(COLOURFG)); 0522 if (colourText.at(0) == QLatin1Char('0') 0523 && colourText.at(1).toLower() == QLatin1Char('x')) 0524 colourText.replace(0, 2, QStringLiteral("#")); 0525 mFgColour.setNamedColor(colourText); 0526 if (!mFgColour.isValid()) 0527 d->setErrorParameter(COLOURFG); 0528 } 0529 0530 if (mParser->isSet(*mOptions.at(TIME))) 0531 { 0532 const QByteArray dateTime = mParser->value(*mOptions.at(TIME)).toLocal8Bit(); 0533 if (!KAlarm::convertTimeString(dateTime, mAlarmTime)) 0534 d->setErrorParameter(TIME); 0535 } 0536 else 0537 mAlarmTime = KADateTime::currentLocalDateTime(); 0538 0539 const bool haveRecurrence = mParser->isSet(*mOptions.at(RECURRENCE)); 0540 if (haveRecurrence) 0541 { 0542 if (mParser->isSet(*mOptions.at(LOGIN))) 0543 d->setErrorIncompatible(LOGIN, RECURRENCE); 0544 else if (mParser->isSet(*mOptions.at(UNTIL))) 0545 d->setErrorIncompatible(UNTIL, RECURRENCE); 0546 const QString rule = mParser->value(*mOptions.at(RECURRENCE)); 0547 mRecurrence = new KARecurrence; 0548 mRecurrence->set(rule); 0549 } 0550 if (mParser->isSet(*mOptions.at(INTERVAL))) 0551 { 0552 // Repeat count is specified 0553 int count = 0; 0554 KADateTime endTime; 0555 if (mParser->isSet(*mOptions.at(LOGIN))) 0556 d->setErrorIncompatible(LOGIN, INTERVAL); 0557 bool ok; 0558 if (mParser->isSet(*mOptions.at(REPEAT))) 0559 { 0560 count = mParser->value(*mOptions.at(REPEAT)).toInt(&ok); 0561 if (!ok || !count || count < -1 || (count < 0 && haveRecurrence)) 0562 d->setErrorParameter(REPEAT); 0563 } 0564 else if (haveRecurrence) 0565 d->setErrorRequires(INTERVAL, REPEAT); 0566 else if (mParser->isSet(*mOptions.at(UNTIL))) 0567 { 0568 count = 0; 0569 const QByteArray dateTime = mParser->value(*mOptions.at(UNTIL)).toLocal8Bit(); 0570 if (mParser->isSet(*mOptions.at(TIME))) 0571 ok = KAlarm::convertTimeString(dateTime, endTime, mAlarmTime); 0572 else 0573 ok = KAlarm::convertTimeString(dateTime, endTime); 0574 if (!ok) 0575 d->setErrorParameter(UNTIL); 0576 else if (mAlarmTime.isDateOnly() && !endTime.isDateOnly()) 0577 d->setError(xi18nc("@info:shell", "Invalid <icode>%1</icode> parameter for date-only alarm", d->optionName(UNTIL))); 0578 if (!mAlarmTime.isDateOnly() && endTime.isDateOnly()) 0579 endTime.setTime(QTime(23,59,59)); 0580 if (endTime < mAlarmTime) 0581 d->setError(xi18nc("@info:shell", "<icode>%1</icode> earlier than <icode>%2</icode>", d->optionName(UNTIL), d->optionName(TIME))); 0582 } 0583 else 0584 count = -1; 0585 0586 // Get the recurrence interval 0587 int intervalOfType; 0588 KARecurrence::Type recurType; 0589 if (!convInterval(mParser->value(*mOptions.at(INTERVAL)), recurType, intervalOfType, !haveRecurrence)) 0590 d->setErrorParameter(INTERVAL); 0591 else if (mAlarmTime.isDateOnly() && recurType == KARecurrence::MINUTELY) 0592 d->setError(xi18nc("@info:shell", "Invalid <icode>%1</icode> parameter for date-only alarm", d->optionName(INTERVAL))); 0593 0594 if (haveRecurrence) 0595 { 0596 if (mRecurrence) 0597 { 0598 // There is a also a recurrence specified, so set up a sub-repetition. 0599 // In this case, 'intervalOfType' is in minutes. 0600 mRepeatInterval = KCalendarCore::Duration(intervalOfType * 60); 0601 const KCalendarCore::Duration longestInterval = mRecurrence->longestInterval(); 0602 if (mRepeatInterval * count > longestInterval) 0603 d->setError(xi18nc("@info:shell", "Invalid <icode>%1</icode> and <icode>%2</icode> parameters: repetition is longer than <icode>%3</icode> interval", 0604 d->optionName(INTERVAL), d->optionName(REPEAT), d->optionName(RECURRENCE))); 0605 mRepeatCount = count; 0606 } 0607 } 0608 else 0609 { 0610 // There is no other recurrence specified, so convert the repetition 0611 // parameters into a KCal::Recurrence 0612 mRecurrence = new KARecurrence; 0613 mRecurrence->set(recurType, intervalOfType, count, mAlarmTime, endTime); 0614 } 0615 } 0616 else 0617 { 0618 if (mParser->isSet(*mOptions.at(REPEAT))) 0619 d->setErrorRequires(REPEAT, INTERVAL); 0620 else if (mParser->isSet(*mOptions.at(UNTIL))) 0621 d->setErrorRequires(UNTIL, INTERVAL); 0622 } 0623 0624 const bool audioRepeat = mParser->isSet(*mOptions.at(PLAY_REPEAT)); 0625 if (audioRepeat || mParser->isSet(*mOptions.at(PLAY))) 0626 { 0627 // Play a sound with the alarm 0628 const Option opt = audioRepeat ? PLAY_REPEAT : PLAY; 0629 if (audioRepeat && mParser->isSet(*mOptions.at(PLAY))) 0630 d->setErrorIncompatible(PLAY, PLAY_REPEAT); 0631 if (mParser->isSet(*mOptions.at(BEEP))) 0632 d->setErrorIncompatible(BEEP, opt); 0633 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT 0634 else if (mParser->isSet(*mOptions.at(SPEAK))) 0635 d->setErrorIncompatible(SPEAK, opt); 0636 #endif 0637 mAudioFile = mParser->value(*mOptions.at(audioRepeat ? PLAY_REPEAT : PLAY)); 0638 if (mParser->isSet(*mOptions.at(VOLUME))) 0639 { 0640 bool ok; 0641 const int volumepc = mParser->value(*mOptions.at(VOLUME)).toInt(&ok); 0642 if (!ok || volumepc < 0 || volumepc > 100) 0643 d->setErrorParameter(VOLUME); 0644 mAudioVolume = static_cast<float>(volumepc) / 100; 0645 } 0646 } 0647 else if (mParser->isSet(*mOptions.at(VOLUME))) 0648 d->setErrorRequires(VOLUME, PLAY, PLAY_REPEAT); 0649 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT 0650 if (mParser->isSet(*mOptions.at(SPEAK))) 0651 { 0652 if (mParser->isSet(*mOptions.at(BEEP))) 0653 d->setErrorIncompatible(BEEP, SPEAK); 0654 } 0655 #endif 0656 const bool onceOnly = mParser->isSet(*mOptions.at(REMINDER_ONCE)); 0657 if (mParser->isSet(*mOptions.at(REMINDER)) || onceOnly) 0658 { 0659 // Issue a reminder alarm in advance of or after the main alarm 0660 if (onceOnly && mParser->isSet(*mOptions.at(REMINDER))) 0661 d->setErrorIncompatible(REMINDER, REMINDER_ONCE); 0662 const Option opt = onceOnly ? REMINDER_ONCE : REMINDER; 0663 KARecurrence::Type recurType; 0664 QString optval = mParser->value(*mOptions.at(onceOnly ? REMINDER_ONCE : REMINDER)); 0665 const bool after = (optval.at(0) == QLatin1Char('+')); 0666 if (after) 0667 optval.remove(0, 1); // it's a reminder after the main alarm 0668 if (!convInterval(optval, recurType, mReminderMinutes)) 0669 d->setErrorParameter(opt); 0670 else if (recurType == KARecurrence::MINUTELY && mAlarmTime.isDateOnly()) 0671 d->setError(xi18nc("@info:shell", "Invalid <icode>%1</icode> parameter for date-only alarm", d->optionName(opt))); 0672 if (after) 0673 mReminderMinutes = -mReminderMinutes; 0674 if (onceOnly) 0675 mFlags |= KAEvent::REMINDER_ONCE; 0676 } 0677 0678 if (mParser->isSet(*mOptions.at(LATE_CANCEL))) 0679 { 0680 KARecurrence::Type recurType; 0681 const bool ok = convInterval(mParser->value(*mOptions.at(LATE_CANCEL)), recurType, mLateCancel); 0682 if (!ok) 0683 d->setErrorParameter(LATE_CANCEL); 0684 } 0685 else if (mParser->isSet(*mOptions.at(AUTO_CLOSE))) 0686 d->setErrorRequires(AUTO_CLOSE, LATE_CANCEL); 0687 0688 if (mParser->isSet(*mOptions.at(NOTIFY))) 0689 { 0690 if (mParser->isSet(*mOptions.at(COLOUR))) 0691 d->setErrorIncompatible(NOTIFY, COLOUR); 0692 if (mParser->isSet(*mOptions.at(COLOURFG))) 0693 d->setErrorIncompatible(NOTIFY, COLOURFG); 0694 if (mParser->isSet(*mOptions.at(ACK_CONFIRM))) 0695 d->setErrorIncompatible(NOTIFY, ACK_CONFIRM); 0696 if (mParser->isSet(*mOptions.at(PLAY))) 0697 d->setErrorIncompatible(NOTIFY, PLAY); 0698 if (mParser->isSet(*mOptions.at(AUTO_CLOSE))) 0699 d->setErrorIncompatible(NOTIFY, AUTO_CLOSE); 0700 } 0701 0702 if (mParser->isSet(*mOptions.at(ACK_CONFIRM))) 0703 mFlags |= KAEvent::CONFIRM_ACK; 0704 if (mParser->isSet(*mOptions.at(AUTO_CLOSE))) 0705 mFlags |= KAEvent::AUTO_CLOSE; 0706 if (mParser->isSet(*mOptions.at(BEEP))) 0707 mFlags |= KAEvent::BEEP; 0708 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT 0709 if (mParser->isSet(*mOptions.at(SPEAK))) 0710 mFlags |= KAEvent::SPEAK; 0711 #endif 0712 if (mParser->isSet(*mOptions.at(NOTIFY))) 0713 mFlags |= KAEvent::NOTIFY; 0714 if (mParser->isSet(*mOptions.at(KORGANIZER))) 0715 mFlags |= KAEvent::COPY_KORGANIZER; 0716 if (mParser->isSet(*mOptions.at(DISABLE))) 0717 mFlags |= KAEvent::DISABLED; 0718 if (audioRepeat) 0719 mFlags |= KAEvent::REPEAT_SOUND; 0720 if (mParser->isSet(*mOptions.at(LOGIN))) 0721 mFlags |= KAEvent::REPEAT_AT_LOGIN; 0722 if (mParser->isSet(*mOptions.at(BCC))) 0723 mFlags |= KAEvent::EMAIL_BCC; 0724 if (mAlarmTime.isDateOnly()) 0725 mFlags |= KAEvent::ANY_TIME; 0726 break; 0727 } 0728 case NONE: 0729 { 0730 // No arguments - run interactively & display the main window 0731 if (!mError.isEmpty()) 0732 break; 0733 qCDebug(KALARM_LOG) << "CommandOptions::process: Interactive"; 0734 QStringList errors; 0735 if (mParser->isSet(*mOptions.at(ACK_CONFIRM))) 0736 errors << d->optionName(ACK_CONFIRM); 0737 if (mParser->isSet(*mOptions.at(ATTACH))) 0738 errors << d->optionName(ATTACH); 0739 if (mParser->isSet(*mOptions.at(AUTO_CLOSE))) 0740 errors << d->optionName(AUTO_CLOSE); 0741 if (mParser->isSet(*mOptions.at(BCC))) 0742 errors << d->optionName(BCC); 0743 if (mParser->isSet(*mOptions.at(BEEP))) 0744 errors << d->optionName(BEEP); 0745 if (mParser->isSet(*mOptions.at(COLOUR))) 0746 errors << d->optionName(COLOUR); 0747 if (mParser->isSet(*mOptions.at(COLOURFG))) 0748 errors << d->optionName(COLOURFG); 0749 if (mParser->isSet(*mOptions.at(DISABLE))) 0750 errors << d->optionName(DISABLE); 0751 if (mParser->isSet(*mOptions.at(FROM_ID))) 0752 errors << d->optionName(FROM_ID); 0753 if (mParser->isSet(*mOptions.at(KORGANIZER))) 0754 errors << d->optionName(KORGANIZER); 0755 if (mParser->isSet(*mOptions.at(LATE_CANCEL))) 0756 errors << d->optionName(LATE_CANCEL); 0757 if (mParser->isSet(*mOptions.at(LOGIN))) 0758 errors << d->optionName(LOGIN); 0759 if (mParser->isSet(*mOptions.at(NAME))) 0760 errors << d->optionName(NAME); 0761 if (mParser->isSet(*mOptions.at(NOTIFY))) 0762 errors << d->optionName(NOTIFY); 0763 if (mParser->isSet(*mOptions.at(PLAY))) 0764 errors << d->optionName(PLAY); 0765 if (mParser->isSet(*mOptions.at(PLAY_REPEAT))) 0766 errors << d->optionName(PLAY_REPEAT); 0767 if (mParser->isSet(*mOptions.at(REMINDER))) 0768 errors << d->optionName(REMINDER); 0769 if (mParser->isSet(*mOptions.at(REMINDER_ONCE))) 0770 errors << d->optionName(REMINDER_ONCE); 0771 #ifdef HAVE_TEXT_TO_SPEECH_SUPPORT 0772 if (mParser->isSet(*mOptions.at(SPEAK))) 0773 errors << d->optionName(SPEAK); 0774 #endif 0775 if (mParser->isSet(*mOptions.at(NOTIFY))) 0776 errors << d->optionName(NOTIFY); 0777 if (mParser->isSet(*mOptions.at(SUBJECT))) 0778 errors << d->optionName(SUBJECT); 0779 if (mParser->isSet(*mOptions.at(TIME))) 0780 errors << d->optionName(TIME); 0781 if (mParser->isSet(*mOptions.at(VOLUME))) 0782 errors << d->optionName(VOLUME); 0783 if (!errors.isEmpty()) 0784 mError = errors.join(QLatin1Char(' ')) + i18nc("@info:shell", ": option(s) only valid with an appropriate action option or message"); 0785 break; 0786 } 0787 default: 0788 break; 0789 } 0790 0791 if (!mError.isEmpty()) 0792 d->setError(mError); 0793 } 0794 0795 QString CommandOptions::commandName() const 0796 { 0797 return d->optionName(d->mCommandOpt); 0798 } 0799 0800 void CommandOptions::printError(const QString& errmsg) 0801 { 0802 // Note: we can't use mArgs->usage() since that also quits any other 0803 // running 'instances' of the program. 0804 std::cerr << errmsg.toLocal8Bit().data() 0805 << i18nc("@info:shell", "\nUse --help to get a list of available command line options.\n").toLocal8Bit().data(); 0806 } 0807 0808 // Fetch one of the arguments (i.e. not belonging to any option). 0809 QString CommandOptions::arg(int n) 0810 { 0811 const QStringList args = mParser->positionalArguments(); 0812 return (n < args.size()) ? args[n] : QString(); 0813 } 0814 0815 /*===========================================================================*/ 0816 0817 void CommandOptions::Private::setError(const QString& errmsg) 0818 { 0819 qCWarning(KALARM_LOG) << "CommandOptions::setError:" << errmsg; 0820 p->mCommand = CMD_ERROR; 0821 if (p->mError.isEmpty()) 0822 p->mError = errmsg + i18nc("@info:shell", "\nUse --help to get a list of available command line options.\n"); 0823 } 0824 0825 /****************************************************************************** 0826 * Check if the given command option is specified, and if so set mCommand etc. 0827 * If another command option has also been detected, issue an error. 0828 * If 'allowedEditType' is set, supersede any previous specification of that 0829 * edit type with the given command option - this allows, e.g., --mail to be 0830 * used along with --edit-new-email so the user can specify addressees. 0831 */ 0832 bool CommandOptions::Private::checkCommand(Option command, Command code, EditAlarmDlg::Type allowedEditType) 0833 { 0834 if (!p->mError.isEmpty() 0835 || !p->mParser->isSet(*p->mOptions.at(command))) 0836 return false; 0837 if (p->mCommand != NONE 0838 && (allowedEditType == EditAlarmDlg::NO_TYPE || (p->mCommand != NEW || p->mEditType != allowedEditType))) 0839 setErrorIncompatible(mCommandOpt, command); 0840 qCDebug(KALARM_LOG).nospace() << "CommandOptions::checkCommand: " << optionName(command); 0841 p->mCommand = code; 0842 mCommandOpt = command; 0843 return true; 0844 } 0845 0846 // Set the error message to "--opt requires --opt2" or "--opt requires --opt2 or --opt3". 0847 void CommandOptions::Private::setErrorRequires(Option opt, Option opt2, Option opt3) 0848 { 0849 if (opt3 == Num_Options) 0850 setError(xi18nc("@info:shell", "<icode>%1</icode> requires <icode>%2</icode>", optionName(opt), optionName(opt2))); 0851 else 0852 setError(xi18nc("@info:shell", "<icode>%1</icode> requires <icode>%2</icode> or <icode>%3</icode>", optionName(opt), optionName(opt2), optionName(opt3))); 0853 } 0854 0855 void CommandOptions::Private::setErrorParameter(Option opt) 0856 { 0857 setError(xi18nc("@info:shell", "Invalid <icode>%1</icode> parameter", optionName(opt))); 0858 } 0859 0860 void CommandOptions::Private::setErrorIncompatible(Option opt1, Option opt2) 0861 { 0862 setError(xi18nc("@info:shell", "<icode>%1</icode> incompatible with <icode>%2</icode>", optionName(opt1), optionName(opt2))); 0863 } 0864 0865 void CommandOptions::Private::checkEditType(EditAlarmDlg::Type type1, EditAlarmDlg::Type type2, Option opt) 0866 { 0867 if (p->mParser->isSet(*p->mOptions.at(opt)) && p->mCommand != NONE 0868 && ((p->mCommand != NEW && p->mCommand != EDIT_NEW) || (p->mEditType != type1 && (type2 == EditAlarmDlg::NO_TYPE || p->mEditType != type2)))) 0869 setErrorIncompatible(opt, mCommandOpt); 0870 } 0871 0872 QString CommandOptions::Private::optionName(Option opt, bool shortName) const 0873 { 0874 if (opt == Opt_Message) 0875 return QStringLiteral("message"); 0876 const QStringList names = p->mOptions.at(opt)->names(); 0877 if (names.empty()) 0878 return {}; 0879 for (const QString& name : names) 0880 { 0881 if (shortName && name.size() == 1) 0882 return QStringLiteral("-") + name; 0883 else if (!shortName && name.size() > 1) 0884 return QStringLiteral("--") + name; 0885 } 0886 return QStringLiteral("-") + names[0]; 0887 } 0888 0889 namespace 0890 { 0891 0892 /****************************************************************************** 0893 * Convert a non-zero positive time interval command line parameter. 0894 * 'timeInterval' receives the count for the recurType. If 'allowMonthYear' is 0895 * false, weeks and days are converted to minutes. 0896 * Reply = true if successful. 0897 */ 0898 bool convInterval(const QString& timeParam, KARecurrence::Type& recurType, int& timeInterval, bool allowMonthYear) 0899 { 0900 QByteArray timeString = timeParam.toLocal8Bit(); 0901 // Get the recurrence interval 0902 bool ok = true; 0903 uint interval = 0; 0904 uint length = timeString.length(); 0905 switch (timeString.at(length - 1)) 0906 { 0907 case 'Y': 0908 if (!allowMonthYear) 0909 ok = false; 0910 recurType = KARecurrence::ANNUAL_DATE; 0911 timeString = timeString.left(length - 1); 0912 break; 0913 case 'W': 0914 recurType = KARecurrence::WEEKLY; 0915 timeString = timeString.left(length - 1); 0916 break; 0917 case 'D': 0918 recurType = KARecurrence::DAILY; 0919 timeString = timeString.left(length - 1); 0920 break; 0921 case 'M': 0922 { 0923 int i = timeString.indexOf('H'); 0924 if (i < 0) 0925 { 0926 if (!allowMonthYear) 0927 ok = false; 0928 recurType = KARecurrence::MONTHLY_DAY; 0929 timeString = timeString.left(length - 1); 0930 } 0931 else 0932 { 0933 recurType = KARecurrence::MINUTELY; 0934 interval = timeString.left(i).toUInt(&ok) * 60; 0935 timeString = timeString.mid(i + 1, length - i - 2); 0936 } 0937 break; 0938 } 0939 default: // should be a digit 0940 recurType = KARecurrence::MINUTELY; 0941 break; 0942 } 0943 if (ok) 0944 interval += timeString.toUInt(&ok); 0945 if (!allowMonthYear) 0946 { 0947 // Convert time interval to minutes 0948 switch (recurType) 0949 { 0950 case KARecurrence::WEEKLY: 0951 interval *= 7; 0952 [[fallthrough]]; // fall through to DAILY 0953 case KARecurrence::DAILY: 0954 interval *= 24*60; 0955 recurType = KARecurrence::MINUTELY; 0956 break; 0957 default: 0958 break; 0959 } 0960 } 0961 timeInterval = static_cast<int>(interval); 0962 return ok && interval; 0963 } 0964 0965 } 0966 0967 // vim: et sw=4: