Compare commits

...

16 Commits

Author SHA1 Message Date
Tomatix 31a9c6efdf #126 Increased the height of config dialog to fixed 800px 2 years ago
Tomatix 0fc9373fc5 #138 Grouping DCC Chat windows with Private chat windows. Added /command handling from DCC Chat windows. Added support for /me in DCC Chat windows. 2 years ago
Tomatix 2ae8e41479 #84 Added auto complete for built-in and IRC commands. 2 years ago
Tomatix fa3fa74d02 #130 Print /MSG in active window (instead of status only) if target has no subwindow 2 years ago
Tomatix 11f120d7ca #139 Re-ordered /CTCP arguments: /CTCP command target message 2 years ago
Tomatix 0c7ece7d1d #146 Reverting fix for "quit on last window closed". Force-exit the program via a half second timer instead, in case the desktop environment or otherwise prevents program exit. 2 years ago
Tomatix 4ff6717820 #150 Removed use of qt5_use_modules in CMake 2 years ago
Tomatix 1a8d3b12a3 #142 Remove obsolete comment 2 years ago
Tomatix 7e5ea6fa9a #142 Safer handling of the currentMessage pointer in IRC core 2 years ago
Tomatix bbd086cd83 #149 Put logic to select correct function for inbound numerics in an unordered_map. Spread the implementation of the numerics in own source files. 2 years ago
Tomatix 1c874fc468 #141 Put logic to select correct function for inbound commands in an unordered_map. 2 years ago
Tomatix 13d569894e #140 Timeout on application exit if a connection is dangling, separate dialog appears with option to abort, force exit or wait a few seconds to force exit. 2 years ago
Tomatix 6b13c67ed3 #148 Reset server password if we are /server'ing to somewhere else 2 years ago
Tomatix 49d9f8bd1d #145 Show a message when connecting to a server 2 years ago
Tomatix 087c4ff8e9 #147 Corrected argument checking for /oper 2 years ago
Tomatix 4429931e50 #146 Added "delete on close" on the IdealIRC dialog and "quit on last window closed" for QApplication. Though this might add some instabilities during shutdown. 2 years ago
  1. 116
      ICommand/AutoComplete.cpp
  2. 3
      ICommand/CMakeLists.txt
  3. 4
      ICommand/Commands.h
  4. 6
      ICommand/External/notice.cpp
  5. 11
      ICommand/External/privmsg.cpp
  6. 8
      ICommand/ICommand.cpp
  7. 2
      ICommand/ICommand.h
  8. 15
      ICommand/Internal/server.cpp
  9. 3
      IConfig/CMakeLists.txt
  10. 6
      IConfig/IConfig.ui
  11. 19
      IRCClient/CMakeLists.txt
  12. 4
      IRCClient/Commands.h
  13. 2
      IRCClient/Commands/Unhandled.cpp
  14. 4
      IRCClient/Commands/cmdV3Cap.cpp
  15. 135
      IRCClient/Commands/handleNumeric.cpp
  16. 17
      IRCClient/IRCBasePriv.h
  17. 22
      IRCClient/Numerics/numErrSaslFail.cpp
  18. 21
      IRCClient/Numerics/numRplChannelModeIs.cpp
  19. 25
      IRCClient/Numerics/numRplEndOfMotd.cpp
  20. 16
      IRCClient/Numerics/numRplEndOfNames.cpp
  21. 36
      IRCClient/Numerics/numRplIsupport.cpp
  22. 60
      IRCClient/Numerics/numRplNamreply.cpp
  23. 18
      IRCClient/Numerics/numRplSaslSuccess.cpp
  24. 16
      IRCClient/Numerics/numRplTopic.cpp
  25. 190
      IRCClient/Private/parseIncoming.cpp
  26. 3
      IWin/CMakeLists.txt
  27. 5
      IWin/IWinChannel.cpp
  28. 71
      IWin/IWinDCCChat.cpp
  29. 4
      IWin/IWinDCCChat.h
  30. 3
      IWin/IWinPrivate.cpp
  31. 7
      IWin/IWinStatus.cpp
  32. 24
      IdealIRC/ButtonbarMgr.cpp
  33. 11
      IdealIRC/ButtonbarMgr.h
  34. 5
      IdealIRC/CMakeLists.txt
  35. 44
      IdealIRC/ExitTimeoutDialog.cpp
  36. 33
      IdealIRC/ExitTimeoutDialog.h
  37. 62
      IdealIRC/ExitTimeoutDialog.ui
  38. 47
      IdealIRC/IdealIRC.cpp
  39. 3
      IdealIRC/IdealIRC.h
  40. 4
      IdealIRC/main.cpp
  41. 2
      Resources/CMakeLists.txt
  42. 4
      Script/Builtin/CMakeLists.txt
  43. 3
      Script/CMakeLists.txt
  44. 2
      ScriptDialog/CMakeLists.txt
  45. 2
      ScriptFunctions/CMakeLists.txt
  46. 2
      Widgets/CMakeLists.txt

@ -0,0 +1,116 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2023 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "ICommand.h"
#include "Commands.h"
#include "IRCClient/Commands.h"
using namespace Command::Internal;
using namespace Command::IRC;
int ICommand::AutoComplete(int index, QString& pattern)
{
static QStringList commandList = {
/*
* Internal commands
*/
ACTION,
CTCP,
DCC,
CTCPREPLY,
ME,
ECHO,
QUERY,
MUTERESP,
UNMUTERESP,
CLEAR,
MSG,
QUOTE,
RAW,
SERVER,
/*
* IRC commands
*/
PASS,
NICK,
USER,
OPER,
MODE,
QUIT,
SQUIT,
JOIN,
PART,
TOPIC,
NAMES,
LIST,
INVITE,
KICK,
PRIVMSG,
NOTICE,
MOTD,
LUSERS,
VERSION,
STATS,
LINKS,
TIME,
CONNECT,
TRACE,
ADMIN,
INFO,
SERVLIST,
SQUERY,
WHOWAS,
WHOIS,
WHO,
KILL,
PING,
PONG,
AWAY,
REHASH,
DIE,
RESTART,
SUMMON,
USERS,
WALLOPS,
USERHOST,
ISON
};
if (pattern.isEmpty())
return 0;
int searchIdx = 0;
bool changed = false;
Retry:
for (const auto& command : commandList)
{
if (pattern.length() > command.length())
continue;
if (command.left(pattern.length() - 1).compare(pattern.mid(1), Qt::CaseInsensitive) == 0)
{
if (searchIdx == index) {
pattern = command;
pattern.prepend('/');
changed = true;
}
++searchIdx;
}
}
if (!changed && searchIdx > 0) {
changed = false;
searchIdx = 0;
index = 0;
goto Retry;
}
return searchIdx;
}

@ -1,6 +1,7 @@
set(component "ICommand")
list(APPEND ${component}_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/AutoComplete.cpp
${CMAKE_CURRENT_SOURCE_DIR}/CommandData.cpp
${CMAKE_CURRENT_SOURCE_DIR}/CommandData.h
${CMAKE_CURRENT_SOURCE_DIR}/Commands.h
@ -26,5 +27,5 @@ list(APPEND ${component}_SOURCES_External
)
add_library(${component} STATIC ${${component}_SOURCES} ${${component}_SOURCES_Internal} ${${component}_SOURCES_External})
qt5_use_modules(${component} Widgets)
target_link_libraries(${component} Qt5::Widgets)
target_include_directories(${component} PRIVATE ${CMAKE_SOURCE_DIR})

@ -8,6 +8,10 @@
#ifndef ICOMMAND_COMMANDS_H
#define ICOMMAND_COMMANDS_H
/*
* When adding new commands, consider adding it to ICommand/AutoComplete.cpp as well.
*/
namespace Command::Internal {
constexpr auto* ACTION = "ACTION";
constexpr auto* CTCP = "CTCP";

@ -21,11 +21,9 @@ void ICommandPriv::cmd_notice(const std::string& target, const std::string& mess
if (!target.empty() && !message.empty()) {
if (winTarget && winTarget != active)
winTarget->print(PrintType::OwnText, QStringLiteral("->%1<- %2")
.arg(target.c_str())
.arg(message.c_str()));
.arg(target.c_str(), message.c_str()));
if (active && active != winTarget)
active->print(PrintType::OwnText, QStringLiteral("->%1<- %2")
.arg(target.c_str())
.arg(message.c_str()));
.arg(target.c_str(), message.c_str()));
}
}

@ -20,13 +20,14 @@ void ICommandPriv::cmd_privmsg(const std::string& target, const std::string& mes
if (win) {
const auto* myNick = connection.getNickname().c_str();
win->print(PrintType::OwnText, QStringLiteral("<%1> %2")
.arg(myNick)
.arg(message.c_str()));
.arg(myNick, message.c_str()));
}
else
status.print(PrintType::OwnText, QStringLiteral(">%1< %2")
.arg(target.c_str())
.arg(message.c_str()));
{
IWin* active = status.getActiveWindow();
active->print(PrintType::OwnText, QStringLiteral(">%1< %2")
.arg(target.c_str(), message.c_str()));
}
}
}

@ -104,7 +104,7 @@ bool ICommand::tryParseIRC(QString cmdLine)
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_AnyWord });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (argv.size() < 2)
else if (!argv[0])
print_notEnoughParameters(command);
else
connection.command(OPER, { *argv[0], *argv[1] });
@ -478,16 +478,16 @@ bool ICommand::tryParseInternal(QString cmdLine)
using namespace Command::IRC;
if (command == CTCP || command == CTCPREPLY) {
CommandData argv(cmdLine, { &ICommand::prd_AnyWord, &ICommand::prd_AnyWordToUpper, &ICommand::prd_Message });
CommandData argv(cmdLine, { &ICommand::prd_AnyWordToUpper, &ICommand::prd_AnyWord, &ICommand::prd_Message });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[0] || !argv[1])
print_notEnoughParameters(command);
else {
if (command == CTCP)
mp->cmd_ctcp(*argv[0], *argv[1], argv[2].value_or(""));
mp->cmd_ctcp(*argv[1], *argv[0], argv[2].value_or(""));
else
mp->cmd_ctcpreply(*argv[0], *argv[1], argv[2].value_or(""));
mp->cmd_ctcpreply(*argv[1], *argv[0], argv[2].value_or(""));
}
return true;
}

@ -31,6 +31,8 @@ public:
void parse(QString text);
IRC& getConnection();
static int AutoComplete(int index, QString& pattern);
private:
bool tryParseIRC(QString cmdLine);
bool tryParseInternal(QString cmdLine);

@ -7,6 +7,7 @@
#include "ICommand/ICommandPriv.h"
#include "ConfigMgr.h"
#include <QString>
#include <chrono>
void ICommandPriv::cmd_server(bool ssl, bool newstatus, bool activate, const std::string& host, const std::string& port)
@ -30,6 +31,13 @@ void ICommandPriv::cmd_server(bool ssl, bool newstatus, bool activate, const std
QString realname = conf.connection("Realname");
QString username = conf.connection("Username");
if (con.getHostname() != host ||
con.getPort() != port ||
con.isSSL() != ssl)
{
con.setPassword("");
}
con.setHostname(host, ssl);
con.setPort(port);
con.setRealname(realname.toStdString());
@ -47,8 +55,13 @@ void ICommandPriv::cmd_server(bool ssl, bool newstatus, bool activate, const std
con.exceptSSL_SelfSigned(cfgSSLExcept_Selfsigned == "1");
con.exceptSSL_CNMismatch(cfgSSLExcept_CNMismatch == "1");
const auto qhost = QString::fromStdString(host);
const auto qport = QString::fromStdString(port);
auto e = con.tryConnect();
if (e != IRCError::NoError)
if (e == IRCError::NoError)
sw->print(PrintType::ProgramInfo, QStringLiteral("Connecting to %1:%2...").arg(qhost, qport));
else
sw->print(PrintType::ProgramInfo, IRCErrorToString(e).c_str());
}
}

@ -27,6 +27,5 @@ list(APPEND ${component}_SOURCES
)
add_library(${component} STATIC ${${component}_SOURCES})
qt5_use_modules(${component} Widgets)
target_link_libraries(${component} Resources)
target_link_libraries(${component} Qt5::Widgets Resources Script)
target_include_directories(${component} PRIVATE ${CMAKE_SOURCE_DIR})

@ -10,19 +10,19 @@
<x>0</x>
<y>0</y>
<width>650</width>
<height>710</height>
<height>800</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>650</width>
<height>710</height>
<height>800</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>650</width>
<height>710</height>
<height>800</height>
</size>
</property>
<property name="windowTitle">

@ -23,6 +23,17 @@ list(APPEND ${component}_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/Utilities.h
)
list(APPEND ${component}_NUMERICS
${CMAKE_CURRENT_SOURCE_DIR}/Numerics/numRplIsupport.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Numerics/numRplEndOfMotd.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Numerics/numRplNamreply.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Numerics/numRplEndOfNames.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Numerics/numRplTopic.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Numerics/numRplChannelModeIs.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Numerics/numRplSaslSuccess.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Numerics/numErrSaslFail.cpp
)
list(APPEND ${component}_COMMANDS
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdError.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdInvite.cpp
@ -43,7 +54,6 @@ list(APPEND ${component}_COMMANDS
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdV3Away.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdV3Cap.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/cmdWallops.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/handleNumeric.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Commands/Unhandled.cpp
)
@ -69,7 +79,12 @@ list(APPEND ${component}_PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/Private/writeNoMsg.cpp
)
add_library(${component} STATIC ${${component}_SOURCES} ${${component}_COMMANDS} ${${component}_PRIVATE})
add_library(${component} STATIC
${${component}_SOURCES}
${${component}_NUMERICS}
${${component}_COMMANDS}
${${component}_PRIVATE}
)
target_link_libraries(${component}
NATUtils

@ -8,6 +8,10 @@
#ifndef IRCCLIENT_COMMANDS_H
#define IRCCLIENT_COMMANDS_H
/*
* When adding new commands, consider adding it to ICommand/AutoComplete.cpp as well.
*/
namespace Command {
namespace IRC {
constexpr auto* PASS = "PASS";

@ -8,7 +8,7 @@
#include "../IRCBase.h"
#include "../IRCBasePriv.h"
void IRCBasePriv::handleUnhandled(const IRCMessage& ircmessage)
void IRCBasePriv::handleUnhandledCommand(const IRCMessage& ircmessage)
{
super.onMsgUnhandled(ircmessage.getSender(), ircmessage.getCommand(), ircmessage.getArgs(), ircmessage.getMessage());
}

@ -13,6 +13,10 @@ void IRCBasePriv::cmdV3Cap(const IRCMessage& ircmessage)
{
using namespace Command::IRC;
/* We don't support re-negotiation at the moment. */
if (isOnline)
return;
bool saslSupported{ std::find(registeredV3support.begin(), registeredV3support.end(), "sasl") != registeredV3support.end() };
/*

@ -1,135 +0,0 @@
/*
* IdealIRC Core - Internet Relay Chat API
* Copyright (C) 2021 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "../IRCBase.h"
#include "../IRCBasePriv.h"
#include "../Commands.h"
void IRCBasePriv::handleNumeric(const IRCMessage& ircmessage) {
using namespace Numeric;
if (!isOnline && ircmessage == RPL_ISUPPORT) {
// Note: skip the first item (argument), it is always our nickname
for (auto it = ircmessage.getArgs().cbegin() + 1; it != ircmessage.getArgs().cend(); ++it) {
const auto& arg = *it;
std::string key, val;
auto delim = std::find(arg.begin(), arg.end(), '=');
if (delim != arg.end()) {
key = std::string(arg.begin(), delim);
val = std::string(delim + 1, arg.end());
}
else
key = arg;
isupport.insert_or_assign(key, val);
}
const std::string& prefix = isupport.find("PREFIX")->second;
validPrivilegeModes = std::string(std::find(prefix.begin(), prefix.end(), '(') + 1,
std::find(prefix.begin(), prefix.end(), ')'));
validPrivilegeSymbols = std::string(std::find(prefix.begin(), prefix.end(), ')') + 1,
prefix.end());
}
else if (!isOnline && (ircmessage == RPL_ENDOFMOTD || ircmessage == ERR_NOMOTD)) {
nickname = ircmessage[0];
isOnline = true;
if (keepaliveFreq > std::chrono::seconds(0))
startKeepaliveTimer();
// Emplace ourself in the all-members list.
allMembers.emplace_back(std::make_shared<IRCMember>(IRCPrefix::fromNickname(nickname)));
super.onRegistered();
}
else if (ircmessage == RPL_NAMREPLY) {
auto chan = super.getChannel(ircmessage[2]);
if (chan && chan->isPopulating()) {
std::istringstream ss(ircmessage.getMessage());
/*
* Note these things:
* A member may contain a usermode.
* IRCv3 may enable multiple usermodes (multi-prefix).
* IRCv3 may enable each member entry to be a full hostmask (userhost-in-names) and not only a nickname.
*/
auto members = std::vector<std::string>(std::istream_iterator<std::string>{ ss },
std::istream_iterator<std::string>());
for (std::string& namem : members) {
/* Extract mode symbols */
auto memBegin = std::find_if_not(namem.begin(), namem.end(),
[this](char ms) {
return isMemberPrivilegeSymbol(ms);
});
std::string modesymbols = std::string(namem.begin(), memBegin);
namem.erase(namem.begin(), memBegin);
std::optional<IRCPrefix> memprefix;
/* IRC standard reply */
if (namem.find('!') == std::string::npos)
memprefix.emplace(IRCPrefix::fromNickname(namem));
/* IRCv3 reply */
else
memprefix.emplace(namem);
auto mem = super.getMember(memprefix->nickname());
if (!mem) {
mem = std::make_shared<IRCMember>(*memprefix);
allMembers.push_back(mem);
}
if (mem->prefix().host().empty() && !memprefix->host().empty())
mem->setPrefix(*memprefix);
mem->addChannel(chan);
auto& entry = chan->addMember(mem);
for (char s : modesymbols) {
char letter = memberPrivilegeSymbolToMode(s);
if (letter != '\0')
entry.addMode(letter);
}
}
}
}
else if (ircmessage == RPL_ENDOFNAMES) {
auto chan = super.getChannel(ircmessage[1]);
if (chan && chan->isPopulating())
chan->donePopulating();
}
else if (ircmessage == RPL_TOPIC) {
auto chan = super.getChannel(ircmessage[1]);
if (chan)
chan->setTopic(ircmessage.getMessage());
}
else if (ircmessage == RPL_CHANNELMODEIS) {
auto chan = super.getChannel(ircmessage[1]);
const std::string& modes = ircmessage[2];
auto args = ircmessage.getArgs();
args.erase(args.begin(), args.begin() + 3);
if (chan)
parseChannelModeMessage(chan, modes, args);
}
else if (ircmessage == NumericV3::RPL_SASLSUCCESS && saslInProgress) {
saslInProgress = false;
writeNoMsg(Command::IRCv3::CAP, { Command::IRCv3::END });
}
else if (ircmessage == NumericV3::ERR_SASLFAIL && saslInProgress) {
saslInProgress = false;
super.disconnectFromServer();
}
super.onMsgNumeric(ircmessage.getSender(), ircmessage.getCommand(), ircmessage.getArgs(), ircmessage.getMessage());
}

@ -134,9 +134,20 @@ struct IRCBasePriv
void parseIncoming(const std::string& line);
/*
* Message handlers for the various messages we receive from the IRC server.
* Numeric message handlers
*/
void numRplIsupport(const IRCMessage& ircmessage);
void numRplEndOfMotd(const IRCMessage& ircmessage);
void numRplNamreply(const IRCMessage& ircmessage);
void numRplEndOfNames(const IRCMessage& ircmessage);
void numRplTopic(const IRCMessage& ircmessage);
void numRplChannelModeIs(const IRCMessage& ircmessage);
void numRplSaslSuccess(const IRCMessage& ircmessage);
void numErrSaslFail(const IRCMessage& ircmessage);
/*
* Command message handlers
*/
void handleNumeric(const IRCMessage& ircmessage);
void cmdNick(const IRCMessage& ircmessage);
void cmdMode(const IRCMessage& ircmessage);
void cmdQuit(const IRCMessage& ircmessage);
@ -156,7 +167,7 @@ struct IRCBasePriv
void cmdV3Away(const IRCMessage& ircmessage);
void cmdV3Account(const IRCMessage& ircmessage);
void cmdV3Authenticate(const IRCMessage& ircmessage);
void handleUnhandled(const IRCMessage& ircmessage);
void handleUnhandledCommand(const IRCMessage& ircmessage);
};
#endif // IRCBASEPRIV_H

@ -0,0 +1,22 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2023 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "../IRCBase.h"
#include "../IRCBasePriv.h"
#include <iostream>
void IRCBasePriv::numErrSaslFail(const IRCMessage& ircmessage)
{
if (!saslInProgress)
return;
saslInProgress = false;
if (super.disconnectFromServer() != IRCError::NoError)
std::cout << "Disconnected from an already closed connection." << std::endl;
}

@ -0,0 +1,21 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2023 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "../IRCBase.h"
#include "../IRCBasePriv.h"
void IRCBasePriv::numRplChannelModeIs(const IRCMessage& ircmessage)
{
auto chan = super.getChannel(ircmessage[1]);
const std::string& modes = ircmessage[2];
auto args = ircmessage.getArgs();
args.erase(args.begin(), args.begin() + 3);
if (chan)
parseChannelModeMessage(chan, modes, args);
}

@ -0,0 +1,25 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2023 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "../IRCBase.h"
#include "../IRCBasePriv.h"
void IRCBasePriv::numRplEndOfMotd(const IRCMessage& ircmessage)
{
if (isOnline)
return;
nickname = ircmessage[0];
isOnline = true;
if (keepaliveFreq > std::chrono::seconds(0))
startKeepaliveTimer();
// Emplace ourselves in the all-members list.
allMembers.emplace_back(std::make_shared<IRCMember>(IRCPrefix::fromNickname(nickname)));
super.onRegistered();
}

@ -0,0 +1,16 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2023 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "../IRCBase.h"
#include "../IRCBasePriv.h"
void IRCBasePriv::numRplEndOfNames(const IRCMessage& ircmessage)
{
auto chan = super.getChannel(ircmessage[1]);
if (chan && chan->isPopulating())
chan->donePopulating();
}

@ -0,0 +1,36 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2023 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "../IRCBasePriv.h"
void IRCBasePriv::numRplIsupport(const IRCMessage& ircmessage)
{
if (isOnline)
return;
// Note: skip the first item (argument), it is always our nickname
for (auto it = ircmessage.getArgs().cbegin() + 1; it != ircmessage.getArgs().cend(); ++it) {
const auto& arg = *it;
std::string key, val;
auto delim = std::find(arg.begin(), arg.end(), '=');
if (delim != arg.end()) {
key = std::string(arg.begin(), delim);
val = std::string(delim + 1, arg.end());
}
else
key = arg;
isupport.insert_or_assign(key, val);
}
const std::string& prefix = isupport.find("PREFIX")->second;
validPrivilegeModes = std::string(std::find(prefix.begin(), prefix.end(), '(') + 1,
std::find(prefix.begin(), prefix.end(), ')'));
validPrivilegeSymbols = std::string(std::find(prefix.begin(), prefix.end(), ')') + 1,
prefix.end());
}

@ -0,0 +1,60 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2023 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "../IRCBase.h"
#include "../IRCBasePriv.h"
void IRCBasePriv::numRplNamreply(const IRCMessage& ircmessage)
{
auto chan = super.getChannel(ircmessage[2]);
if (chan && chan->isPopulating()) {
std::istringstream ss(ircmessage.getMessage());
/*
* Note these things:
* A member may contain a usermode.
* IRCv3 may enable multiple usermodes (multi-prefix).
* IRCv3 may enable each member entry to be a full hostmask (userhost-in-names) and not only a nickname.
*/
auto members = std::vector<std::string>(std::istream_iterator<std::string>{ ss },
std::istream_iterator<std::string>());
for (std::string& namem : members) {
/* Extract mode symbols */
auto memBegin = std::find_if_not(namem.begin(), namem.end(),
[this](char ms) {
return isMemberPrivilegeSymbol(ms);
});
std::string modesymbols = std::string(namem.begin(), memBegin);
namem.erase(namem.begin(), memBegin);
std::optional<IRCPrefix> memprefix;
/* IRC standard reply */
if (namem.find('!') == std::string::npos)
memprefix.emplace(IRCPrefix::fromNickname(namem));
/* IRCv3 reply */
else
memprefix.emplace(namem);
auto mem = super.getMember(memprefix->nickname());
if (!mem) {
mem = std::make_shared<IRCMember>(*memprefix);
allMembers.push_back(mem);
}
if (mem->prefix().host().empty() && !memprefix->host().empty())
mem->setPrefix(*memprefix);
mem->addChannel(chan);
auto& entry = chan->addMember(mem);
for (char s : modesymbols) {
char letter = memberPrivilegeSymbolToMode(s);
if (letter != '\0')
entry.addMode(letter);
}
}
}
}

@ -0,0 +1,18 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2023 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "../Commands.h"
#include "../IRCBasePriv.h"
void IRCBasePriv::numRplSaslSuccess(const IRCMessage& ircmessage)
{
if (!saslInProgress)
return;
saslInProgress = false;
writeNoMsg(Command::IRCv3::CAP, { Command::IRCv3::END });
}

@ -0,0 +1,16 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2023 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "../IRCBase.h"
#include "../IRCBasePriv.h"
void IRCBasePriv::numRplTopic(const IRCMessage& ircmessage)
{
auto chan = super.getChannel(ircmessage[1]);
if (chan)
chan->setTopic(ircmessage.getMessage());
}

@ -9,21 +9,92 @@
#include "../IRCBasePriv.h"
#include "../Commands.h"
#include <fmt/format.h>
#include <unordered_map>
#include <functional>
#include <iostream> // Needs better logger, spdlog :)
using CommandFunction = std::function<void(IRCBasePriv*, const IRCMessage&)>;
namespace
{
/*
* RAII template class that temporarily assigns a pointer.
* Upon construction, assigns pointer 'to' to the pointer reference 'ptrRef'.
* Upon destruction, sets the pointer reference to nullptr.
*/
template<typename T>
class TempNonOwningPtr
{
T*& m_ptrRef;
public:
explicit TempNonOwningPtr(T*& ptrRef, T* to)
: m_ptrRef(ptrRef)
{
m_ptrRef = to;
}
~TempNonOwningPtr()
{
m_ptrRef = nullptr;
}
T*& get() { return m_ptrRef; }
};
}
void IRCBasePriv::parseIncoming(const std::string& line)
{
using namespace Command::IRC;
/*
* Do NOT EVER do an "early return" from this function.
* Make sure it always reaches the end!
* No throwing from any of the message handler functions either.
*/
using namespace Numeric;
IRCMessage ircmessage(IRCPrefix(hostname), line);
currentMessage = &ircmessage;
TempNonOwningPtr<IRCMessage> ircmessagePtr(currentMessage, &ircmessage);
/* ****************************** *
* Commands *
* ****************************** */
static std::unordered_map<std::string, CommandFunction> commandFunctionMap = {
{ NICK, &IRCBasePriv::cmdNick },
{ MODE, &IRCBasePriv::cmdMode },
{ JOIN, &IRCBasePriv::cmdJoin },
{ PART, &IRCBasePriv::cmdPart },
{ QUIT, &IRCBasePriv::cmdQuit },
{ TOPIC, &IRCBasePriv::cmdTopic },
{ INVITE, &IRCBasePriv::cmdInvite },
{ KICK, &IRCBasePriv::cmdKick },
{ PRIVMSG, &IRCBasePriv::cmdPrivmsg },
{ NOTICE, &IRCBasePriv::cmdNotice },
{ KILL, &IRCBasePriv::cmdKill },
{ PING, &IRCBasePriv::cmdPing },
{ PONG, &IRCBasePriv::cmdPong },
{ ERROR_, &IRCBasePriv::cmdError },
{ WALLOPS, &IRCBasePriv::cmdWallops },
/* v3 */
{ Command::IRC::AWAY, &IRCBasePriv::cmdV3Away },
{ Command::IRCv3::CAP, &IRCBasePriv::cmdV3Cap },
{ Command::Extension::ACCOUNT, &IRCBasePriv::cmdV3Account },
{ Command::Extension::AUTHENTICATE, &IRCBasePriv::cmdV3Authenticate }
};
/* ****************************** *
* Numerics *
* ****************************** */
static std::unordered_map<std::string, CommandFunction> numericFunctionMap = {
{ RPL_ISUPPORT, &IRCBasePriv::numRplIsupport },
{ RPL_ENDOFMOTD, &IRCBasePriv::numRplEndOfMotd },
{ ERR_NOMOTD, &IRCBasePriv::numRplEndOfMotd },
{ RPL_NAMREPLY, &IRCBasePriv::numRplNamreply },
{ RPL_ENDOFNAMES, &IRCBasePriv::numRplEndOfNames },
{ RPL_TOPIC, &IRCBasePriv::numRplTopic },
{ RPL_CHANNELMODEIS, &IRCBasePriv::numRplChannelModeIs },
/* v3 */
{ NumericV3::RPL_SASLSUCCESS, &IRCBasePriv::numRplSaslSuccess },
{ NumericV3::ERR_SASLFAIL, &IRCBasePriv::numErrSaslFail },
};
if constexpr (DumpReadData) {
std::cout << "[RCV] ";
@ -45,99 +116,22 @@ void IRCBasePriv::parseIncoming(const std::string& line)
std::cout << std::endl;
}
using namespace Command::IRC;
using namespace Numeric;
/*
* Numeric reply
*/
if (ircmessage.isNumeric()) {
handleNumeric(ircmessage);
}
else if (ircmessage == NICK) {
cmdNick(ircmessage);
}
else if (ircmessage == MODE) {
cmdMode(ircmessage);
}
else if (ircmessage == QUIT) {
cmdQuit(ircmessage);
}
else if (ircmessage == JOIN) {
cmdJoin(ircmessage);
}
else if (ircmessage == PART) {
cmdPart(ircmessage);
}
else if (ircmessage == TOPIC) {
cmdTopic(ircmessage);
}
else if (ircmessage == INVITE) {
cmdInvite(ircmessage);
}
else if (ircmessage == KICK) {
cmdKick(ircmessage);
}
else if (ircmessage == PRIVMSG) {
cmdPrivmsg(ircmessage);
}
else if (ircmessage == NOTICE) {
cmdNotice(ircmessage);
}
else if (ircmessage == KILL) {
cmdKill(ircmessage);
}
else if (ircmessage == PING) {
cmdPing(ircmessage);
}
else if (ircmessage == PONG) {
cmdPong(ircmessage);
}
else if (ircmessage == ERROR_) {
cmdError(ircmessage);
}
else if (ircmessage == WALLOPS) {
cmdWallops(ircmessage);
}
else if (ircmessage == Command::IRCv3::CAP && !isOnline) {
cmdV3Cap(ircmessage);
}
/* IRCv3 capability "away-notify" */
else if (ircmessage == Command::IRC::AWAY) {
cmdV3Away(ircmessage);
}
/* IRCv3 capability "account-notify" */
else if (ircmessage == Command::Extension::ACCOUNT) {
cmdV3Account(ircmessage);
}
try {
auto& numericFn = numericFunctionMap.at(ircmessage.getCommand());
numericFn(this, ircmessage);
}
catch (const std::out_of_range&) { /* don't care */ }
/* IRCv3 capability "sasl" */
else if (ircmessage == Command::Extension::AUTHENTICATE) {
cmdV3Authenticate(ircmessage);
super.onMsgNumeric(ircmessage.getSender(), ircmessage.getCommand(), ircmessage.getArgs(), ircmessage.getMessage());
}
else {
handleUnhandled(ircmessage);
try {
auto& commandFn = commandFunctionMap.at(ircmessage.getCommand());
commandFn(this, ircmessage);
}
catch (const std::out_of_range&) {
handleUnhandledCommand(ircmessage);
}
}
currentMessage = nullptr;
}

@ -16,6 +16,5 @@ list(APPEND ${component}_SOURCES
)
add_library(${component} STATIC ${${component}_SOURCES})
qt5_use_modules(${component} Widgets)
target_link_libraries(${component} IWidgets)
target_link_libraries(${component} Qt5::Widgets IWidgets ICommand)
target_include_directories(${component} PRIVATE ${CMAKE_SOURCE_DIR})

@ -8,7 +8,7 @@
#include "IWinChannel.h"
#include "IWinStatus.h"
#include "MdiManager.h"
#include "ICommand/Commands.h"
#include "ICommand/ICommand.h"
#include "Script/Manager.h"
#include "IRCClient/IRCChannel.h"
#include "IRCClient/IRCMember.h"
@ -192,6 +192,9 @@ int IWinChannel::tabComplete(int index, QString& pattern)
if (connection.isChannelSymbol( pattern[0].toLatin1() ))
return connection.channelAutoComplete(index, pattern);
if (pattern.front() == '/')
return ICommand::AutoComplete(index, pattern);
auto chan = connection.getChannel(getButtonText().toStdString());
auto members = chan->members();
int searchIdx = 0;

@ -7,12 +7,14 @@
#include "IWinDCCChat.h"
#include "IWinStatus.h"
#include <IRCClient/IRCBase.h>
#include "IRCClient/IRCBase.h"
#include "IRCClient/Utilities.h"
#include <QMessageBox>
#include <iostream>
IWinDCCChat::IWinDCCChat(IWinStatus* statusParent, std::shared_ptr<DCC> dcc_, const QString& targetNickname)
: IWin(IWin::Type::DCCChat, statusParent)
, cmdHndl(statusParent->getConnection())
, dcc(std::move(dcc_))
{
setButtonText(targetNickname);
@ -99,18 +101,13 @@ void IWinDCCChat::onDisconnected(IRCError e)
void IWinDCCChat::newLine(const QString& line)
{
if (dcc->isConnected()) {
const auto nickname{ QString::fromStdString(dcc->context().getNickname()) };
print(PrintType::OwnText, tr("<%1> %2").arg(nickname, line));
auto data = DCC::stringToByteString(line.toStdString());
data.push_back(std::byte('\r'));
data.push_back(std::byte('\n'));
dcc->write(data);
}
else {
if (line.isEmpty())
return;
if (dcc->isConnected())
handleOutboundMessage(line);
else
print(PrintType::ProgramInfo, tr("Not connected with remote peer"));
}
}
void IWinDCCChat::onRead(const DCC::ByteString& data, std::size_t length)
@ -134,7 +131,7 @@ void IWinDCCChat::onRead(const DCC::ByteString& data, std::size_t length)
if (!lineBuffer.isEmpty() && lineBuffer.back() == '\r')
lineBuffer.remove(lineBuffer.length()-1, 1);
print(PrintType::Normal, tr("<%1> %2").arg(getButtonText(), lineBuffer));
handleInboundMessage(lineBuffer);
lineBuffer.clear();
@ -144,9 +141,57 @@ void IWinDCCChat::onRead(const DCC::ByteString& data, std::size_t length)
int IWinDCCChat::tabComplete(int index, QString& pattern)
{
if (pattern.front() == '/')
return ICommand::AutoComplete(index, pattern);
return 0;
}
void IWinDCCChat::handleOutboundMessage(const QString& message)
{
const auto nickname{ QString::fromStdString(dcc->context().getNickname()) };
if (message.front() == '/') {
/*
* /ME needs special handling for DCC. Otherwise, it will be sent to the recipient over IRC.
*/
if (message.size() > 4 && message.mid(0, 4).toUpper() == "/ME ") {
auto actionMessage = message.mid(4);
print(PrintType::Action, tr("%1 %2").arg(nickname, actionMessage));
actionMessage = QStringLiteral("%1ACTION %2%1").arg(CTCPflag).arg(actionMessage);
auto data = DCC::stringToByteString(actionMessage.toStdString());
data.push_back(std::byte('\r'));
data.push_back(std::byte('\n'));
dcc->write(data);
}
else {
cmdHndl.parse(message);
}
return;
}
print(PrintType::OwnText, tr("<%1> %2").arg(nickname, message));
auto data = DCC::stringToByteString(message.toStdString());
data.push_back(std::byte('\r'));
data.push_back(std::byte('\n'));
dcc->write(data);
}
void IWinDCCChat::handleInboundMessage(const QString& message)
{
if (message.front() == CTCPflag && message.mid(1, 6) == "ACTION")
{
auto actionMessage = message.mid(8);
actionMessage.remove(actionMessage.size() - 1, 1);
print(PrintType::Action, tr("%1 %2").arg(getButtonText(), actionMessage));
return;
}
print(PrintType::Normal, tr("<%1> %2").arg(getButtonText(), message));
}
void IWinDCCChat::disconnectFromDCC()
{
dcc->disconnect();

@ -11,6 +11,7 @@
#include "IWin.h"
#include "IRCClient/DCC.h"
#include "Widgets/ILineEdit.h"
#include "ICommand/ICommand.h"
#include <QVBoxLayout>
#include <QCloseEvent>
#include <memory>
@ -43,11 +44,14 @@ private:
void newLine(const QString& line);
void onRead(const DCC::ByteString& data, std::size_t length);
int tabComplete(int index, QString& pattern);
void handleOutboundMessage(const QString& message);
void handleInboundMessage(const QString& message);
QVBoxLayout* layout;
IIRCView* view;
ILineEdit* input;
QString lineBuffer;
ICommand cmdHndl;
bool reverseInitiatePending{ false };
std::shared_ptr<DCC> dcc;

@ -96,5 +96,8 @@ int IWinPrivate::tabComplete(int index, QString& pattern)
if (connection.isChannelSymbol( pattern[0].toLatin1() ))
return connection.channelAutoComplete(index, pattern);
if (pattern.front() == '/')
return ICommand::AutoComplete(index, pattern);
return 0;
}

@ -224,10 +224,13 @@ void IWinStatus::disconnectedFromServer()
int IWinStatus::tabComplete(int index, QString& pattern)
{
if (pattern.isEmpty() || !connection.isOnline())
if (pattern.isEmpty())
return 0;
if (connection.isChannelSymbol( pattern[0].toLatin1() ))
if (pattern.front() == '/')
return ICommand::AutoComplete(index, pattern);
if (connection.isOnline() && connection.isChannelSymbol( pattern[0].toLatin1() ))
return connection.channelAutoComplete(index, pattern);
return 0;

@ -9,19 +9,7 @@
#include "MdiManager.h"
#include "ConfigMgr.h"
int ButtonbarMgr::WindowTypeToGroupPos(const IWin* subwin)
{
switch (subwin->getType()) {
case IWin::Type::Status:
return 1;
case IWin::Type::Channel:
return 2;
case IWin::Type::Private:
return 3;
default:
return 0;
}
}
constexpr auto ButtonMaxWidth = 150;
ButtonbarMgr::ButtonbarMgr(QToolBar *parent)
: QObject(parent)
@ -53,7 +41,7 @@ void ButtonbarMgr::addButton(IWin* subwin, QMdiSubWindow* mdiwin)
auto* nn = findNextNeighbour(subwin);
auto btn = Button(mdiwin);
btn.toolButton->setMaximumWidth(150);
btn.toolButton->setMaximumWidth(ButtonMaxWidth);
if (m_buttonBar.orientation() == Qt::Vertical)
btn.toolButton->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
else
@ -86,7 +74,8 @@ void ButtonbarMgr::delButton(IWin* subwin)
m_winbtn.remove(subwin);
if (subwin->getType() != IWin::Type::Status
&& subwin->getType() != IWin::Type::Channel
&& subwin->getType() != IWin::Type::Private)
&& subwin->getType() != IWin::Type::Private
&& subwin->getType() != IWin::Type::DCCChat )
{
int otherPos = buttonPosition(m_others, subwin);
if (otherPos > -1) {
@ -221,7 +210,8 @@ QList<ButtonbarMgr::Button>& ButtonbarMgr::buttons(IWin* subwin)
{
if (subwin->getType() != IWin::Type::Status
&& subwin->getType() != IWin::Type::Channel
&& subwin->getType() != IWin::Type::Private)
&& subwin->getType() != IWin::Type::Private
&& subwin->getType() != IWin::Type::DCCChat)
{
return m_others;
}
@ -261,7 +251,7 @@ QAction* ButtonbarMgr::findNextNeighbour(IWin* subwin)
else
return group->privmsg.front().action;
}
else if (subwin->getType() == IWin::Type::Private) {
else if (subwin->getType() == IWin::Type::Private || subwin->getType() == IWin::Type::DCCChat) {
return tryFindGroup(subwin->getParent())->rightSep;
}
else {

@ -48,10 +48,11 @@ class ButtonbarMgr : public QObject
{
QList<Button>& operator[](int i) {
switch (i) {
case 0: return status;
case 1: return channel;
case 2: return privmsg;
default: return blank;
case 0: return status;
case 1: return channel;
case 2: [[fallthrough]];
case 3: return privmsg;
default: return blank;
}
}
@ -64,8 +65,6 @@ class ButtonbarMgr : public QObject
QList<Button> blank;
};
static int WindowTypeToGroupPos(const IWin* subwin);
public:
explicit ButtonbarMgr(QToolBar *parent);
void addButton(IWin* subwin, QMdiSubWindow* mdiwin);

@ -6,6 +6,9 @@ if (MSVC)
endif()
list(APPEND ${component}_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/ExitTimeoutDialog.h
${CMAKE_CURRENT_SOURCE_DIR}/ExitTimeoutDialog.cpp
${CMAKE_CURRENT_SOURCE_DIR}/ExitTimeoutDialog.ui
${CMAKE_CURRENT_SOURCE_DIR}/DCCQuery.cpp
${CMAKE_CURRENT_SOURCE_DIR}/DCCQuery.h
${CMAKE_CURRENT_SOURCE_DIR}/DCCQuery.ui
@ -43,8 +46,8 @@ if (MSVC)
set_property(TARGET ${component} PROPERTY WIN32_EXECUTABLE true)
endif()
qt5_use_modules(${component} Widgets)
target_link_libraries(${component}
Qt5::Widgets
ICommand
IConfig
IRCClient

@ -0,0 +1,44 @@
#include "ExitTimeoutDialog.h"
#include "ui_ExitTimeoutDialog.h"
#include <QString>
ExitTimeoutDialog::ExitTimeoutDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::ExitTimeoutDialog)
{
ui->setupUi(this);
m_timer.setInterval(1000);
connect(&m_timer, &QTimer::timeout, [this, i = 9]() mutable {
static QString abortLabel{ "Force exit (%1)" };
ui->btnExit->setText(abortLabel.arg(i));
--i;
if (i < 0)
{
m_timer.stop();
on_btnExit_clicked();
}
});
m_timer.start();
}
ExitTimeoutDialog::~ExitTimeoutDialog()
{
delete ui;
}
void ExitTimeoutDialog::on_btnAbort_clicked()
{
emit abortTimeout();
}
void ExitTimeoutDialog::on_btnExit_clicked()
{
emit forceExit();
}

@ -0,0 +1,33 @@
#ifndef EXITTIMEOUTDIALOG_H
#define EXITTIMEOUTDIALOG_H
#include <QDialog>
#include <QTimer>
namespace Ui {
class ExitTimeoutDialog;
}
class ExitTimeoutDialog : public QDialog
{
Q_OBJECT
public:
explicit ExitTimeoutDialog(QWidget *parent = nullptr);
~ExitTimeoutDialog();
signals:
void abortTimeout();
void forceExit();
private slots:
void on_btnAbort_clicked();
void on_btnExit_clicked();
private:
Ui::ExitTimeoutDialog *ui;
QTimer m_timer;
};
#endif // EXITTIMEOUTDIALOG_H

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ExitTimeoutDialog</class>
<widget class="QDialog" name="ExitTimeoutDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>100</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Server(s) not responding</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Some servers are not acknowledging the departure.
Slow or dead connections may cause this.</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="btnAbort">
<property name="text">
<string>Abort</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnExit">
<property name="text">
<string>Force exit (10)</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

@ -11,6 +11,7 @@
#include "IWin/IWinStatus.h"
#include "IWin/IWinDCCChat.h"
#include "ExitTimeoutDialog.h"
#include <ConfigMgr.h>
#include <QDebug>
@ -19,6 +20,8 @@
#include <QScreen>
#include <QDesktopServices>
constexpr auto ExitDisconnectWaitMsecs = 2000;
// Used externally. Initialized in the IdealIRC constructor.
IdealIRC* IIRC_MainWindow_Ptr{ nullptr };
@ -88,6 +91,10 @@ IdealIRC::IdealIRC(QWidget* parent)
statusw->refreshWindowTitle();
QTimer::singleShot(10, [this]{ startup(); });
exitDisconnectTimer.setInterval(ExitDisconnectWaitMsecs);
exitDisconnectTimer.setSingleShot(true);
connect(&exitDisconnectTimer, &QTimer::timeout, this, &IdealIRC::onExitDisconnectTimeout);
}
IdealIRC::~IdealIRC()
@ -183,11 +190,17 @@ void IdealIRC::closeEvent(QCloseEvent* evt)
/*
* Initialize closing
*/
int serversWaiting = mdiManager->connectionsOnlineCount();
if (serversWaiting > 0) {
if (serversWaiting > 0 && !forceExit) {
auto btn = QMessageBox::question(this, tr("Close IdealIRC"), tr("There are %1 connections active.\nDo you really want to exit IdealIRC?").arg(serversWaiting));
if (btn == QMessageBox::Yes)
{
mdiManager->broadcastProgramExit();
if (!exitDisconnectTimer.isActive())
exitDisconnectTimer.start();
}
evt->ignore();
}
else {
@ -197,6 +210,9 @@ void IdealIRC::closeEvent(QCloseEvent* evt)
ScriptManager::instance()->unloadAllScripts();
emit aboutToClose();
evt->accept();
// Some desktop environments (eg. KDE) doesn't exit the application upon closure. Force it.
QTimer::singleShot(500, []{ QApplication::exit(); });
}
}
@ -336,14 +352,19 @@ void IdealIRC::connectToServer(bool newStatus)
if (con.disconnectFromServer(quitMsg) == IRCError::NotConnected)
qWarning() << "Trying to disconnect from an already closed connection.";
auto e = con.tryConnect();
if (e != IRCError::NoError)
if (e == IRCError::NoError)
status->print(PrintType::ProgramInfo, QStringLiteral("Connecting to %1:%2...").arg(host).arg(port));
else
status->print(PrintType::ProgramInfo, IRCErrorToString(e).c_str());
}
else {
setConnectButtonState(ConnectButtonState::Disconnect);
auto e = con.tryConnect();
if (e != IRCError::NoError)
if (e == IRCError::NoError)
status->print(PrintType::ProgramInfo, QStringLiteral("Connecting to %1:%2...").arg(host).arg(port));
else
status->print(PrintType::ProgramInfo, IRCErrorToString(e).c_str());
}
}
@ -361,6 +382,26 @@ void IdealIRC::disconnectFromServer()
qWarning() << "Trying to disconnect from an already closed connection.";
}
void IdealIRC::onExitDisconnectTimeout()
{
auto dlg = new ExitTimeoutDialog(this);
connect(dlg, &ExitTimeoutDialog::abortTimeout, [this, dlg] {
forceExit = false;
dlg->close();
dlg->deleteLater();
});
connect(dlg, &ExitTimeoutDialog::forceExit, [this, dlg] {
forceExit = true;
dlg->close();
dlg->deleteLater();
close();
});
dlg->show();
}
void IdealIRC::onStatusWindowCreated(IWinStatus* window)
{
connect(&(window->getConnection()), &IRC::showTrayMessage, [this] (const QString& title, const QString& message) {

@ -34,6 +34,7 @@ public:
~IdealIRC();
private slots:
void onExitDisconnectTimeout();
void onStatusWindowCreated(IWinStatus* window);
void onDCCRequested(const IWinStatus& statusParent, const std::shared_ptr<DCC>& dcc, const IRCPrefix& sender, const QString& type, const QString& message);
void on_actionConnect_triggered();
@ -68,6 +69,8 @@ private:
IConfig confDlg;
AboutIIRC aboutDlg;
DCCQuery dccQueryDlg;
QTimer exitDisconnectTimer;
bool forceExit{};
signals:
void aboutToClose();

@ -73,7 +73,9 @@ int main(int argc, char *argv[])
{
qSetMessagePattern("[%{time}] %{file}:%{line} %{message}");
Q_INIT_RESOURCE(resources);
QApplication a(argc, argv);
Q_UNUSED(a);
qInfo() << "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~";
qInfo() << " IdealIRC version" << VERSION_STRING << "started.";
@ -85,5 +87,5 @@ int main(int argc, char *argv[])
IdealIRC w;
w.show();
return a.exec();
return QApplication::exec();
}

@ -3,5 +3,5 @@ set(component "Resources")
qt5_add_resources(${component}_SOURCES ${IIRC_SOURCE_DIR}/Resources/resources.qrc)
add_library(${component} STATIC ${${component}_SOURCES})
qt5_use_modules(${component} Core)
target_link_libraries(${component} Qt5::Core)
target_include_directories(${component} PRIVATE ${CMAKE_SOURCE_DIR})

@ -20,8 +20,10 @@ list(APPEND ${component}_SOURCES
)
add_library(${component} STATIC ${${component}_SOURCES})
qt5_use_modules(${component} Core)
if (UNIX)
target_link_libraries(${component} m)
endif()
target_link_libraries(${component} Qt5::Core)
target_include_directories(${component} PRIVATE ${CMAKE_SOURCE_DIR})

@ -32,6 +32,5 @@ list(APPEND ${component}_SOURCES
)
add_library(${component} STATIC ${${component}_SOURCES})
qt5_use_modules(${component} Widgets)
target_link_libraries(${component} ScriptBuiltinFunctions Resources)
target_link_libraries(${component} Qt5::Widgets ScriptBuiltinFunctions Resources)
target_include_directories(${component} PRIVATE ${CMAKE_SOURCE_DIR})

@ -6,5 +6,5 @@ list(APPEND ${component}_SOURCES
)
add_library(${component} STATIC ${${component}_SOURCES})
qt5_use_modules(${component} Widgets)
target_link_libraries(${component} Qt5::Widgets)
target_include_directories(${component} PRIVATE ${CMAKE_SOURCE_DIR})

@ -10,5 +10,5 @@ list(APPEND ${component}_SOURCES
)
add_library(${component} STATIC ${${component}_SOURCES})
qt5_use_modules(${component} Widgets)
target_link_libraries(${component} Qt5::Widgets)
target_include_directories(${component} PRIVATE ${CMAKE_SOURCE_DIR})

@ -10,5 +10,5 @@ list(APPEND ${component}_SOURCES
)
add_library(${component} STATIC ${${component}_SOURCES})
qt5_use_modules(${component} Widgets)
target_link_libraries(${component} Qt5::Widgets)
target_include_directories(${component} PRIVATE ${CMAKE_SOURCE_DIR})

Loading…
Cancel
Save