Userland: Remove IRC Client

The IRC Client application made some sense while our main communication
hub was an IRC channel. Now that we've moved on, IRC is just a random
protocol with no particular relevance to this project.

This also has the benefit of removing one major client of the single-
process Web::InProcessWebView class.
This commit is contained in:
Andreas Kling 2021-08-24 16:19:08 +02:00
parent 97f5383525
commit c915174563
38 changed files with 1 additions and 2835 deletions

View file

@ -218,10 +218,6 @@
#cmakedefine01 IMAGE_LOADER_DEBUG
#endif
#ifndef IRC_DEBUG
#cmakedefine01 IRC_DEBUG
#endif
#ifndef ITEM_RECTS_DEBUG
#cmakedefine01 ITEM_RECTS_DEBUG
#endif

View file

@ -1,7 +1,6 @@
#!sh
alias fm=FileManager
alias irc=IRCClient
alias mag=Magnifier
alias ms=Minesweeper
alias sh=Shell

View file

@ -1,20 +0,0 @@
[User]
Nickname=anon_seren1ty
[Connection]
Server=chat.freenode.net
Port=6667
AutoJoinChannels=
[CTCP]
VersionReply=IRC Client [x86] / Serenity OS
UserInfoReply=anon
FingerReply=anon
[Messaging]
ShowJoinPartMessages=1
ShowNickChangeMessages=1
[Notifications]
NotifyOnMessage=1
NotifyOnMention=1

View file

@ -28,4 +28,3 @@ directory=/bin/FileManager
gemini=/bin/Browser
http=/bin/Browser
https=/bin/Browser
irc=/bin/IRCClient

View file

@ -1,7 +0,0 @@
[App]
Name=IRC Client
Executable=/bin/IRCClient
Category=Internet
[Launcher]
Protocols=irc

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 B

View file

@ -86,7 +86,6 @@ set(IOAPIC_DEBUG ON)
set(IO_DEBUG ON)
set(IPV4_DEBUG ON)
set(IPV4_SOCKET_DEBUG ON)
set(IRC_DEBUG ON)
set(IRQ_DEBUG ON)
set(ISO9660_DEBUG ON)
set(ISO9660_VERY_DEBUG ON)

View file

@ -81,7 +81,6 @@ I'm also on [Patreon](https://www.patreon.com/serenityos) and [GitHub Sponsors](
* On-line help system (both terminal and GUI variants)
* Web browser (Browser)
* C++ IDE (HackStudio)
* IRC client
* Desktop synthesizer (Piano)
* E-mail client (Mail)
* Various desktop apps & games

View file

@ -12,7 +12,6 @@ add_subdirectory(FileManager)
add_subdirectory(FontEditor)
add_subdirectory(Help)
add_subdirectory(HexEditor)
add_subdirectory(IRCClient)
add_subdirectory(ImageViewer)
add_subdirectory(KeyboardMapper)
add_subdirectory(KeyboardSettings)

View file

@ -1,19 +0,0 @@
serenity_component(
IRCClient
TARGETS IRCClient
)
set(SOURCES
IRCAppWindow.cpp
IRCChannel.cpp
IRCChannelMemberListModel.cpp
IRCClient.cpp
IRCLogBuffer.cpp
IRCQuery.cpp
IRCWindow.cpp
IRCWindowListModel.cpp
main.cpp
)
serenity_app(IRCClient ICON app-irc-client)
target_link_libraries(IRCClient LibWeb LibGUI)

View file

@ -1,352 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "IRCAppWindow.h"
#include "IRCChannel.h"
#include "IRCWindow.h"
#include "IRCWindowListModel.h"
#include <LibGUI/Action.h>
#include <LibGUI/Application.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/InputBox.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Menubar.h>
#include <LibGUI/Splitter.h>
#include <LibGUI/StackWidget.h>
#include <LibGUI/TableView.h>
#include <LibGUI/Toolbar.h>
#include <LibGUI/ToolbarContainer.h>
static IRCAppWindow* s_the;
IRCAppWindow& IRCAppWindow::the()
{
return *s_the;
}
IRCAppWindow::IRCAppWindow(String server, int port)
: m_client(IRCClient::construct(server, port))
{
VERIFY(!s_the);
s_the = this;
set_icon(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/app-irc-client.png"));
update_title();
resize(600, 400);
setup_actions();
setup_menus();
setup_widgets();
setup_client();
}
IRCAppWindow::~IRCAppWindow()
{
}
void IRCAppWindow::update_title()
{
set_title(String::formatted("{}@{}:{} - IRC Client", m_client->nickname(), m_client->hostname(), m_client->port()));
}
void IRCAppWindow::setup_client()
{
m_client->aid_create_window = [this](void* owner, IRCWindow::Type type, const String& name) {
return create_window(owner, type, name);
};
m_client->aid_get_active_window = [this] {
return static_cast<IRCWindow*>(m_container->active_widget());
};
m_client->aid_update_window_list = [this] {
m_window_list->model()->invalidate();
};
m_client->on_nickname_changed = [this](const String&) {
update_title();
};
m_client->on_part_from_channel = [this](auto&) {
update_gui_actions();
};
if (m_client->hostname().is_empty()) {
String value;
if (GUI::InputBox::show(this, value, "Enter server:", "Connect to server") == GUI::InputBox::ExecCancel) {
GUI::Application::the()->quit();
return;
}
m_client->set_server(value, 6667);
}
update_title();
bool success = m_client->connect();
VERIFY(success);
}
void IRCAppWindow::setup_actions()
{
m_join_action = GUI::Action::create("&Join Channel...", { Mod_Ctrl, Key_J }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-join.png"), [&](auto&) {
String value;
if (GUI::InputBox::show(this, value, "Enter channel name:", "Join Channel") == GUI::InputBox::ExecOK && !value.is_empty())
m_client->handle_join_action(value);
});
m_list_channels_action = GUI::Action::create("&List Channels", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-list.png"), [&](auto&) {
m_client->handle_list_channels_action();
});
m_part_action = GUI::Action::create("&Part from Channel", { Mod_Ctrl, Key_P }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-part.png"), [this](auto&) {
auto* window = m_client->current_window();
if (!window || window->type() != IRCWindow::Type::Channel) {
return;
}
m_client->handle_part_action(window->channel().name());
});
m_whois_action = GUI::Action::create("&Whois User...", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-whois.png"), [&](auto&) {
String value;
if (GUI::InputBox::show(this, value, "Enter nickname:", "Whois User") == GUI::InputBox::ExecOK && !value.is_empty())
m_client->handle_whois_action(value);
});
m_open_query_action = GUI::Action::create("Open &Query...", { Mod_Ctrl, Key_O }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-open-query.png"), [&](auto&) {
String value;
if (GUI::InputBox::show(this, value, "Enter nickname:", "Open Query") == GUI::InputBox::ExecOK && !value.is_empty())
m_client->handle_open_query_action(value);
});
m_close_query_action = GUI::Action::create("&Close Query", { Mod_Ctrl, Key_D }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-close-query.png"), [](auto&) {
outln("FIXME: Implement close-query action");
});
m_change_nick_action = GUI::Action::create("Change &Nickname...", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-nick.png"), [this](auto&) {
String value;
if (GUI::InputBox::show(this, value, "Enter nickname:", "Change Nickname") == GUI::InputBox::ExecOK && !value.is_empty())
m_client->handle_change_nick_action(value);
});
m_change_topic_action = GUI::Action::create("Change &Topic...", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-topic.png"), [this](auto&) {
auto* window = m_client->current_window();
if (!window || window->type() != IRCWindow::Type::Channel) {
return;
}
String value;
if (GUI::InputBox::show(this, value, "Enter topic:", "Change Topic") == GUI::InputBox::ExecOK && !value.is_empty())
m_client->handle_change_topic_action(window->channel().name(), value);
});
m_invite_user_action = GUI::Action::create("&Invite User...", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-invite.png"), [this](auto&) {
auto* window = m_client->current_window();
if (!window || window->type() != IRCWindow::Type::Channel) {
return;
}
String value;
if (GUI::InputBox::show(this, value, "Enter nick:", "Invite User") == GUI::InputBox::ExecOK && !value.is_empty())
m_client->handle_invite_user_action(window->channel().name(), value);
});
m_banlist_action = GUI::Action::create("&Ban List", [this](auto&) {
auto* window = m_client->current_window();
if (!window || window->type() != IRCWindow::Type::Channel) {
return;
}
m_client->handle_banlist_action(window->channel().name());
});
m_voice_user_action = GUI::Action::create("&Voice User...", [this](auto&) {
auto* window = m_client->current_window();
if (!window || window->type() != IRCWindow::Type::Channel) {
return;
}
String value;
if (GUI::InputBox::show(this, value, "Enter nick:", "Voice User") == GUI::InputBox::ExecOK && !value.is_empty())
m_client->handle_voice_user_action(window->channel().name(), value);
});
m_devoice_user_action = GUI::Action::create("DeVoice User...", [this](auto&) {
auto* window = m_client->current_window();
if (!window || window->type() != IRCWindow::Type::Channel) {
return;
}
String value;
if (GUI::InputBox::show(this, value, "Enter nick:", "DeVoice user") == GUI::InputBox::ExecOK && !value.is_empty())
m_client->handle_devoice_user_action(window->channel().name(), value);
});
m_hop_user_action = GUI::Action::create("Hop User", [this](auto&) {
auto* window = m_client->current_window();
if (!window || window->type() != IRCWindow::Type::Channel) {
return;
}
String value;
if (GUI::InputBox::show(this, value, "Enter nick:", "Hop User") == GUI::InputBox::ExecOK && !value.is_empty())
m_client->handle_hop_user_action(window->channel().name(), value);
});
m_dehop_user_action = GUI::Action::create("DeHop User", [this](auto&) {
auto* window = m_client->current_window();
if (!window || window->type() != IRCWindow::Type::Channel) {
return;
}
String value;
if (GUI::InputBox::show(this, value, "Enter nick:", "DeHop User") == GUI::InputBox::ExecOK && !value.is_empty())
m_client->handle_dehop_user_action(window->channel().name(), value);
});
m_op_user_action = GUI::Action::create("&Op User", [this](auto&) {
auto* window = m_client->current_window();
if (!window || window->type() != IRCWindow::Type::Channel) {
return;
}
String value;
if (GUI::InputBox::show(this, value, "Enter nick:", "Op User") == GUI::InputBox::ExecOK && !value.is_empty())
m_client->handle_op_user_action(window->channel().name(), value);
});
m_deop_user_action = GUI::Action::create("DeOp user", [this](auto&) {
auto* window = m_client->current_window();
if (!window || window->type() != IRCWindow::Type::Channel) {
return;
}
String value;
if (GUI::InputBox::show(this, value, "Enter nick:", "DeOp User") == GUI::InputBox::ExecOK && !value.is_empty())
m_client->handle_deop_user_action(window->channel().name(), value);
});
m_kick_user_action = GUI::Action::create("&Kick User", [this](auto&) {
auto* window = m_client->current_window();
if (!window || window->type() != IRCWindow::Type::Channel) {
return;
}
String nick_value;
if (GUI::InputBox::show(this, nick_value, "Enter nick:", "Kick User") != GUI::InputBox::ExecOK || nick_value.is_empty())
return;
String reason_value;
if (GUI::InputBox::show(this, reason_value, "Enter reason:", "Reason") == GUI::InputBox::ExecOK)
m_client->handle_kick_user_action(window->channel().name(), nick_value, reason_value.characters());
});
m_cycle_channel_action = GUI::Action::create("C&ycle Channel", [this](auto&) {
auto* window = m_client->current_window();
if (!window || window->type() != IRCWindow::Type::Channel) {
return;
}
m_client->handle_cycle_channel_action(window->channel().name());
});
}
void IRCAppWindow::setup_menus()
{
auto& file_menu = add_menu("&File");
file_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) {
GUI::Application::the()->quit();
}));
auto& server_menu = add_menu("&Server");
server_menu.add_action(*m_change_nick_action);
server_menu.add_separator();
server_menu.add_action(*m_join_action);
server_menu.add_action(*m_list_channels_action);
server_menu.add_separator();
server_menu.add_action(*m_whois_action);
server_menu.add_action(*m_open_query_action);
server_menu.add_action(*m_close_query_action);
auto& channel_menu = add_menu("&Channel");
channel_menu.add_action(*m_change_topic_action);
channel_menu.add_action(*m_invite_user_action);
channel_menu.add_action(*m_banlist_action);
auto& channel_control_menu = channel_menu.add_submenu("Con&trol");
channel_control_menu.add_action(*m_voice_user_action);
channel_control_menu.add_action(*m_devoice_user_action);
channel_control_menu.add_action(*m_hop_user_action);
channel_control_menu.add_action(*m_dehop_user_action);
channel_control_menu.add_action(*m_op_user_action);
channel_control_menu.add_action(*m_deop_user_action);
channel_control_menu.add_separator();
channel_control_menu.add_action(*m_kick_user_action);
channel_menu.add_separator();
channel_menu.add_action(*m_cycle_channel_action);
channel_menu.add_action(*m_part_action);
auto& help_menu = add_menu("&Help");
help_menu.add_action(GUI::CommonActions::make_about_action("IRC Client", GUI::Icon::default_icon("app-irc-client"), this));
}
void IRCAppWindow::setup_widgets()
{
auto& widget = set_main_widget<GUI::Widget>();
widget.set_fill_with_background_color(true);
widget.set_layout<GUI::VerticalBoxLayout>();
widget.layout()->set_spacing(0);
auto& toolbar_container = widget.add<GUI::ToolbarContainer>();
auto& toolbar = toolbar_container.add<GUI::Toolbar>();
toolbar.set_has_frame(false);
toolbar.add_action(*m_change_nick_action);
toolbar.add_separator();
toolbar.add_action(*m_join_action);
toolbar.add_action(*m_part_action);
toolbar.add_separator();
toolbar.add_action(*m_whois_action);
toolbar.add_action(*m_open_query_action);
toolbar.add_action(*m_close_query_action);
auto& outer_container = widget.add<GUI::Widget>();
outer_container.set_layout<GUI::VerticalBoxLayout>();
outer_container.layout()->set_margins({ 0, 2, 2 });
auto& horizontal_container = outer_container.add<GUI::HorizontalSplitter>();
m_window_list = horizontal_container.add<GUI::TableView>();
m_window_list->set_column_headers_visible(false);
m_window_list->set_alternating_row_colors(false);
m_window_list->set_model(m_client->client_window_list_model());
m_window_list->set_activates_on_selection(true);
m_window_list->set_fixed_width(100);
m_window_list->on_activation = [this](auto& index) {
set_active_window(m_client->window_at(index.row()));
};
m_container = horizontal_container.add<GUI::StackWidget>();
m_container->on_active_widget_change = [this](auto*) {
update_gui_actions();
};
create_window(&m_client, IRCWindow::Server, "Server");
}
void IRCAppWindow::set_active_window(IRCWindow& window)
{
m_container->set_active_widget(&window);
window.clear_unread_count();
auto index = m_window_list->model()->index(m_client->window_index(window));
m_window_list->selection().set(index);
}
void IRCAppWindow::update_gui_actions()
{
auto* window = static_cast<IRCWindow*>(m_container->active_widget());
bool is_open_channel = window && window->type() == IRCWindow::Type::Channel && window->channel().is_open();
m_change_topic_action->set_enabled(is_open_channel);
m_invite_user_action->set_enabled(is_open_channel);
m_banlist_action->set_enabled(is_open_channel);
m_voice_user_action->set_enabled(is_open_channel);
m_devoice_user_action->set_enabled(is_open_channel);
m_hop_user_action->set_enabled(is_open_channel);
m_dehop_user_action->set_enabled(is_open_channel);
m_op_user_action->set_enabled(is_open_channel);
m_deop_user_action->set_enabled(is_open_channel);
m_kick_user_action->set_enabled(is_open_channel);
m_cycle_channel_action->set_enabled(is_open_channel);
m_part_action->set_enabled(is_open_channel);
}
NonnullRefPtr<IRCWindow> IRCAppWindow::create_window(void* owner, IRCWindow::Type type, const String& name)
{
return m_container->add<IRCWindow>(m_client, owner, type, name);
}

View file

@ -1,56 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "IRCClient.h"
#include "IRCWindow.h"
#include <LibGUI/Widget.h>
#include <LibGUI/Window.h>
class IRCAppWindow : public GUI::Window {
C_OBJECT(IRCAppWindow);
public:
virtual ~IRCAppWindow() override;
static IRCAppWindow& the();
void set_active_window(IRCWindow&);
private:
IRCAppWindow(String server, int port);
void setup_client();
void setup_actions();
void setup_menus();
void setup_widgets();
void update_title();
void update_gui_actions();
NonnullRefPtr<IRCWindow> create_window(void* owner, IRCWindow::Type, const String& name);
NonnullRefPtr<IRCClient> m_client;
RefPtr<GUI::StackWidget> m_container;
RefPtr<GUI::TableView> m_window_list;
RefPtr<GUI::Action> m_join_action;
RefPtr<GUI::Action> m_list_channels_action;
RefPtr<GUI::Action> m_part_action;
RefPtr<GUI::Action> m_cycle_channel_action;
RefPtr<GUI::Action> m_whois_action;
RefPtr<GUI::Action> m_open_query_action;
RefPtr<GUI::Action> m_close_query_action;
RefPtr<GUI::Action> m_change_nick_action;
RefPtr<GUI::Action> m_change_topic_action;
RefPtr<GUI::Action> m_invite_user_action;
RefPtr<GUI::Action> m_banlist_action;
RefPtr<GUI::Action> m_voice_user_action;
RefPtr<GUI::Action> m_devoice_user_action;
RefPtr<GUI::Action> m_hop_user_action;
RefPtr<GUI::Action> m_dehop_user_action;
RefPtr<GUI::Action> m_op_user_action;
RefPtr<GUI::Action> m_deop_user_action;
RefPtr<GUI::Action> m_kick_user_action;
};

View file

@ -1,123 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "IRCChannel.h"
#include "IRCChannelMemberListModel.h"
#include "IRCClient.h"
IRCChannel::IRCChannel(IRCClient& client, const String& name)
: m_client(client)
, m_name(name)
, m_log(IRCLogBuffer::create())
, m_member_model(IRCChannelMemberListModel::create(*this))
{
m_window = m_client.aid_create_window(this, IRCWindow::Channel, m_name);
m_window->set_log_buffer(*m_log);
}
IRCChannel::~IRCChannel()
{
}
NonnullRefPtr<IRCChannel> IRCChannel::create(IRCClient& client, const String& name)
{
return adopt_ref(*new IRCChannel(client, name));
}
void IRCChannel::add_member(const String& name, char prefix)
{
for (auto& member : m_members) {
if (member.name == name) {
member.prefix = prefix;
return;
}
}
m_members.append({ name, prefix });
m_member_model->invalidate();
}
void IRCChannel::remove_member(const String& name)
{
m_members.remove_first_matching([&](auto& member) { return name == member.name; });
}
void IRCChannel::add_message(char prefix, const String& name, const String& text, Color color)
{
log().add_message(prefix, name, text, color);
window().did_add_message(name, text);
}
void IRCChannel::add_message(const String& text, Color color)
{
log().add_message(text, color);
window().did_add_message();
}
void IRCChannel::say(const String& text)
{
m_client.send_privmsg(m_name, text);
add_message(' ', m_client.nickname(), text);
}
void IRCChannel::handle_join(const String& nick, const String& hostmask)
{
if (nick == m_client.nickname()) {
m_open = true;
return;
}
add_member(nick, (char)0);
m_member_model->invalidate();
if (m_client.show_join_part_messages())
add_message(String::formatted("*** {} [{}] has joined {}", nick, hostmask, m_name), Color::MidGreen);
}
void IRCChannel::handle_part(const String& nick, const String& hostmask)
{
if (nick == m_client.nickname()) {
m_open = false;
m_members.clear();
m_client.did_part_from_channel({}, *this);
} else {
remove_member(nick);
}
m_member_model->invalidate();
if (m_client.show_join_part_messages())
add_message(String::formatted("*** {} [{}] has parted from {}", nick, hostmask, m_name), Color::MidGreen);
}
void IRCChannel::handle_quit(const String& nick, const String& hostmask, const String& message)
{
if (nick == m_client.nickname()) {
m_open = false;
m_members.clear();
m_client.did_part_from_channel({}, *this);
} else {
remove_member(nick);
}
m_member_model->invalidate();
add_message(String::formatted("*** {} [{}] has quit ({})", nick, hostmask, message), Color::MidGreen);
}
void IRCChannel::handle_topic(const String& nick, const String& topic)
{
if (nick.is_null())
add_message(String::formatted("*** Topic is \"{}\"", topic), Color::MidBlue);
else
add_message(String::formatted("*** {} set topic to \"{}\"", nick, topic), Color::MidBlue);
}
void IRCChannel::notify_nick_changed(const String& old_nick, const String& new_nick)
{
for (auto& member : m_members) {
if (member.name == old_nick) {
member.name = new_nick;
m_member_model->invalidate();
if (m_client.show_nick_change_messages())
add_message(String::formatted("~ {} changed nickname to {}", old_nick, new_nick), Color::MidMagenta);
return;
}
}
}

View file

@ -1,74 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "IRCLogBuffer.h"
#include <AK/RefCounted.h>
#include <AK/RefPtr.h>
#include <AK/String.h>
#include <AK/Vector.h>
class IRCClient;
class IRCChannelMemberListModel;
class IRCWindow;
class IRCChannel : public RefCounted<IRCChannel> {
public:
static NonnullRefPtr<IRCChannel> create(IRCClient&, const String&);
~IRCChannel();
bool is_open() const { return m_open; }
void set_open(bool b) { m_open = b; }
String name() const { return m_name; }
void add_member(const String& name, char prefix);
void remove_member(const String& name);
void add_message(char prefix, const String& name, const String& text, Color = Color::Black);
void add_message(const String& text, Color = Color::Black);
void say(const String&);
const IRCLogBuffer& log() const { return *m_log; }
IRCLogBuffer& log() { return *m_log; }
IRCChannelMemberListModel* member_model() { return m_member_model.ptr(); }
const IRCChannelMemberListModel* member_model() const { return m_member_model.ptr(); }
int member_count() const { return m_members.size(); }
String member_at(int i) { return m_members[i].name; }
void handle_join(const String& nick, const String& hostmask);
void handle_part(const String& nick, const String& hostmask);
void handle_quit(const String& nick, const String& hostmask, const String& message);
void handle_topic(const String& nick, const String& topic);
IRCWindow& window() { return *m_window; }
const IRCWindow& window() const { return *m_window; }
String topic() const { return m_topic; }
void notify_nick_changed(const String& old_nick, const String& new_nick);
private:
IRCChannel(IRCClient&, const String&);
IRCClient& m_client;
String m_name;
String m_topic;
struct Member {
String name;
char prefix { 0 };
};
Vector<Member> m_members;
bool m_open { false };
NonnullRefPtr<IRCLogBuffer> m_log;
NonnullRefPtr<IRCChannelMemberListModel> m_member_model;
IRCWindow* m_window { nullptr };
};

View file

@ -1,56 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "IRCChannelMemberListModel.h"
#include "IRCChannel.h"
#include <stdio.h>
#include <time.h>
IRCChannelMemberListModel::IRCChannelMemberListModel(IRCChannel& channel)
: m_channel(channel)
{
}
IRCChannelMemberListModel::~IRCChannelMemberListModel()
{
}
int IRCChannelMemberListModel::row_count(const GUI::ModelIndex&) const
{
return m_channel.member_count();
}
int IRCChannelMemberListModel::column_count(const GUI::ModelIndex&) const
{
return 1;
}
String IRCChannelMemberListModel::column_name(int column) const
{
switch (column) {
case Column::Name:
return "Name";
}
VERIFY_NOT_REACHED();
}
GUI::Variant IRCChannelMemberListModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
{
if (role == GUI::ModelRole::TextAlignment)
return Gfx::TextAlignment::CenterLeft;
if (role == GUI::ModelRole::Display) {
switch (index.column()) {
case Column::Name:
return m_channel.member_at(index.row());
}
}
return {};
}
String IRCChannelMemberListModel::nick_at(const GUI::ModelIndex& index) const
{
return data(index, GUI::ModelRole::Display).to_string();
}

View file

@ -1,32 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Function.h>
#include <LibGUI/Model.h>
class IRCChannel;
class IRCChannelMemberListModel final : public GUI::Model {
public:
enum Column {
Name
};
static NonnullRefPtr<IRCChannelMemberListModel> create(IRCChannel& channel) { return adopt_ref(*new IRCChannelMemberListModel(channel)); }
virtual ~IRCChannelMemberListModel() override;
virtual int row_count(const GUI::ModelIndex&) const override;
virtual int column_count(const GUI::ModelIndex&) const override;
virtual String column_name(int column) const override;
virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
virtual String nick_at(const GUI::ModelIndex& index) const;
private:
explicit IRCChannelMemberListModel(IRCChannel&);
IRCChannel& m_channel;
};

File diff suppressed because it is too large Load diff

View file

@ -1,216 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "IRCLogBuffer.h"
#include "IRCWindow.h"
#include <AK/CircularQueue.h>
#include <AK/Function.h>
#include <AK/HashMap.h>
#include <AK/String.h>
#include <LibCore/ConfigFile.h>
#include <LibCore/TCPSocket.h>
class IRCChannel;
class IRCQuery;
class IRCWindowListModel;
class IRCClient final : public Core::Object {
C_OBJECT(IRCClient)
friend class IRCChannel;
friend class IRCQuery;
public:
virtual ~IRCClient() override;
void set_server(const String& hostname, int port = 6667);
bool connect();
String hostname() const { return m_hostname; }
int port() const { return m_port; }
String nickname() const { return m_nickname; }
String ctcp_version_reply() const { return m_ctcp_version_reply; }
String ctcp_userinfo_reply() const { return m_ctcp_userinfo_reply; }
String ctcp_finger_reply() const { return m_ctcp_finger_reply; }
bool show_join_part_messages() const { return m_show_join_part_messages; }
bool show_nick_change_messages() const { return m_show_nick_change_messages; }
bool notify_on_message() const { return m_notify_on_message; }
bool notify_on_mention() const { return m_notify_on_mention; }
void join_channel(const String&);
void part_channel(const String&);
void change_nick(const String&);
static bool is_nick_prefix(char);
static bool is_channel_prefix(char);
String nick_without_prefix(const String& nick);
IRCWindow* current_window() { return aid_get_active_window(); }
const IRCWindow* current_window() const { return aid_get_active_window(); }
Function<void()> on_disconnect;
Function<void()> on_server_message;
Function<void(const String&)> on_nickname_changed;
Function<void(IRCChannel&)> on_part_from_channel;
Function<NonnullRefPtr<IRCWindow>(void*, IRCWindow::Type, const String&)> aid_create_window;
Function<IRCWindow*()> aid_get_active_window;
Function<void()> aid_update_window_list;
void register_subwindow(IRCWindow&);
void unregister_subwindow(IRCWindow&);
IRCWindowListModel* client_window_list_model() { return m_client_window_list_model.ptr(); }
const IRCWindowListModel* client_window_list_model() const { return m_client_window_list_model.ptr(); }
int window_count() const { return m_windows.size(); }
const IRCWindow& window_at(int index) const { return *m_windows.at(index); }
IRCWindow& window_at(int index) { return *m_windows.at(index); }
size_t window_index(const IRCWindow& window) const
{
for (size_t i = 0; i < m_windows.size(); ++i) {
if (m_windows[i] == &window)
return i;
}
VERIFY_NOT_REACHED();
}
void did_part_from_channel(Badge<IRCChannel>, IRCChannel&);
void handle_user_input_in_channel(const String& channel_name, const String&);
void handle_user_input_in_query(const String& query_name, const String&);
void handle_user_input_in_server(const String&);
void handle_list_channels_action();
void handle_whois_action(const String& nick);
void handle_ctcp_user_action(const String& nick, const String& message);
void handle_open_query_action(const String&);
void handle_close_query_action(const String&);
void handle_join_action(const String& channel_name);
void handle_part_action(const String& channel_name);
void handle_cycle_channel_action(const String& channel_name);
void handle_change_nick_action(const String& nick);
void handle_change_topic_action(const String& channel_name, const String&);
void handle_invite_user_action(const String& channel_name, const String& nick);
void handle_banlist_action(const String& channel_name);
void handle_voice_user_action(const String& channel_name, const String& nick);
void handle_devoice_user_action(const String& channel_name, const String& nick);
void handle_hop_user_action(const String& channel_name, const String& nick);
void handle_dehop_user_action(const String& channel_name, const String& nick);
void handle_op_user_action(const String& channel_name, const String& nick);
void handle_deop_user_action(const String& channel_name, const String& nick);
void handle_kick_user_action(const String& channel_name, const String& nick, const String&);
IRCQuery* query_with_name(const String&);
IRCQuery& ensure_query(const String& name);
IRCChannel& ensure_channel(const String& name);
void add_server_message(const String&, Color = Color::Black);
private:
IRCClient(String server, int port);
struct Message {
String prefix;
String command;
Vector<String> arguments;
};
enum class PrivmsgOrNotice {
Privmsg,
Notice,
};
void receive_from_server();
void send(const String&);
void send_user();
void send_nick();
void send_pong(const String& server);
void send_privmsg(const String& target, const String&);
void send_notice(const String& target, const String&);
void send_topic(const String& channel_name, const String&);
void send_invite(const String& channel_name, const String& nick);
void send_banlist(const String& channel_name);
void send_voice_user(const String& channel_name, const String& nick);
void send_devoice_user(const String& channel_name, const String& nick);
void send_hop_user(const String& channel_name, const String& nick);
void send_dehop_user(const String& channel_name, const String& nick);
void send_op_user(const String& channel_name, const String& nick);
void send_deop_user(const String& channel_name, const String& nick);
void send_kick(const String& channel_name, const String& nick, const String&);
void send_list();
void send_whois(const String&);
void process_line(const String&);
void handle_join(const Message&);
void handle_part(const Message&);
void handle_quit(const Message&);
void handle_ping(const Message&);
void handle_topic(const Message&);
void handle_rpl_welcome(const Message&);
void handle_rpl_topic(const Message&);
void handle_rpl_whoisuser(const Message&);
void handle_rpl_whoisserver(const Message&);
void handle_rpl_whoisoperator(const Message&);
void handle_rpl_whoisidle(const Message&);
void handle_rpl_endofwho(const Message&);
void handle_rpl_endofwhois(const Message&);
void handle_rpl_endofwhowas(const Message&);
void handle_rpl_endofmotd(const Message&);
void handle_rpl_whoischannels(const Message&);
void handle_rpl_topicwhotime(const Message&);
void handle_rpl_endofnames(const Message&);
void handle_rpl_endofbanlist(const Message&);
void handle_rpl_namreply(const Message&);
void handle_rpl_banlist(const Message&);
void handle_err_nosuchnick(const Message&);
void handle_err_unknowncommand(const Message&);
void handle_err_nicknameinuse(const Message&);
void handle_privmsg_or_notice(const Message&, PrivmsgOrNotice);
void handle_nick(const Message&);
void handle(const Message&);
void handle_user_command(const String&);
void handle_ctcp_request(const StringView& peer, const StringView& payload);
void handle_ctcp_response(const StringView& peer, const StringView& payload);
void send_ctcp_request(const StringView& peer, const StringView& payload);
void send_ctcp_response(const StringView& peer, const StringView& payload);
void on_socket_connected();
String m_hostname;
int m_port { 6667 };
RefPtr<Core::TCPSocket> m_socket;
String m_nickname;
RefPtr<Core::Notifier> m_notifier;
HashMap<String, RefPtr<IRCChannel>, CaseInsensitiveStringTraits> m_channels;
HashMap<String, RefPtr<IRCQuery>, CaseInsensitiveStringTraits> m_queries;
bool m_show_join_part_messages { 1 };
bool m_show_nick_change_messages { 1 };
bool m_notify_on_message { 1 };
bool m_notify_on_mention { 1 };
String m_ctcp_version_reply;
String m_ctcp_userinfo_reply;
String m_ctcp_finger_reply;
Vector<IRCWindow*> m_windows;
IRCWindow* m_server_subwindow { nullptr };
NonnullRefPtr<IRCWindowListModel> m_client_window_list_model;
NonnullRefPtr<IRCLogBuffer> m_log;
NonnullRefPtr<Core::ConfigFile> m_config;
};

View file

@ -1,76 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "IRCLogBuffer.h"
#include <LibWeb/DOM/DocumentFragment.h>
#include <LibWeb/DOM/DocumentType.h>
#include <LibWeb/DOM/ElementFactory.h>
#include <LibWeb/DOM/Text.h>
#include <time.h>
NonnullRefPtr<IRCLogBuffer> IRCLogBuffer::create()
{
return adopt_ref(*new IRCLogBuffer);
}
IRCLogBuffer::IRCLogBuffer()
{
m_document = Web::DOM::Document::create();
m_document->append_child(adopt_ref(*new Web::DOM::DocumentType(document())));
auto html_element = m_document->create_element("html");
m_document->append_child(html_element);
auto head_element = m_document->create_element("head");
html_element->append_child(head_element);
auto style_element = m_document->create_element("style");
style_element->append_child(adopt_ref(*new Web::DOM::Text(document(), "div { font-family: Csilla; font-weight: lighter; }")));
head_element->append_child(style_element);
auto body_element = m_document->create_element("body");
html_element->append_child(body_element);
m_container_element = body_element;
}
IRCLogBuffer::~IRCLogBuffer()
{
}
static String timestamp_string()
{
auto now = time(nullptr);
auto* tm = localtime(&now);
return String::formatted("{:02}:{:02}:{:02} ", tm->tm_hour, tm->tm_min, tm->tm_sec);
}
void IRCLogBuffer::add_message(char prefix, const String& name, const String& text, Color color)
{
auto nick_string = String::formatted("<{}{}> ", prefix ? prefix : ' ', name.characters());
auto html = String::formatted(
"<span>{}</span>"
"<b>{}</b>"
"<span>{}</span>",
timestamp_string(),
escape_html_entities(nick_string),
escape_html_entities(text));
auto wrapper = m_document->create_element(Web::HTML::TagNames::div);
wrapper->set_attribute(Web::HTML::AttributeNames::style, String::formatted("color: {}", color.to_string()));
wrapper->set_inner_html(html);
m_container_element->append_child(wrapper);
m_document->force_layout();
}
void IRCLogBuffer::add_message(const String& text, Color color)
{
auto html = String::formatted(
"<span>{}</span>"
"<span>{}</span>",
timestamp_string(),
escape_html_entities(text));
auto wrapper = m_document->create_element(Web::HTML::TagNames::div);
wrapper->set_attribute(Web::HTML::AttributeNames::style, String::formatted("color: {}", color.to_string()));
wrapper->set_inner_html(html);
m_container_element->append_child(wrapper);
m_document->force_layout();
}

View file

@ -1,38 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/RefCounted.h>
#include <AK/RefPtr.h>
#include <AK/String.h>
#include <LibGfx/Color.h>
#include <LibWeb/DOM/Document.h>
class IRCLogBuffer : public RefCounted<IRCLogBuffer> {
public:
static NonnullRefPtr<IRCLogBuffer> create();
~IRCLogBuffer();
struct Message {
time_t timestamp { 0 };
char prefix { 0 };
String sender;
String text;
Color color { Color::Black };
};
void add_message(char prefix, const String& name, const String& text, Color = Color::Black);
void add_message(const String& text, Color = Color::Black);
const Web::DOM::Document& document() const { return *m_document; }
Web::DOM::Document& document() { return *m_document; }
private:
IRCLogBuffer();
RefPtr<Web::DOM::Document> m_document;
RefPtr<Web::DOM::Element> m_container_element;
};

View file

@ -1,44 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "IRCQuery.h"
#include "IRCClient.h"
IRCQuery::IRCQuery(IRCClient& client, const String& name)
: m_client(client)
, m_name(name)
, m_log(IRCLogBuffer::create())
{
m_window = m_client->aid_create_window(this, IRCWindow::Query, m_name);
m_window->set_log_buffer(*m_log);
}
IRCQuery::~IRCQuery()
{
}
NonnullRefPtr<IRCQuery> IRCQuery::create(IRCClient& client, const String& name)
{
return adopt_ref(*new IRCQuery(client, name));
}
void IRCQuery::add_message(char prefix, const String& name, const String& text, Color color)
{
log().add_message(prefix, name, text, color);
window().did_add_message(name, text);
}
void IRCQuery::add_message(const String& text, Color color)
{
log().add_message(text, color);
window().did_add_message();
}
void IRCQuery::say(const String& text)
{
m_client->send_privmsg(m_name, text);
add_message(' ', m_client->nickname(), text);
}

View file

@ -1,44 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "IRCLogBuffer.h"
#include <AK/CircularQueue.h>
#include <AK/RefCounted.h>
#include <AK/RefPtr.h>
#include <AK/String.h>
#include <AK/Vector.h>
class IRCClient;
class IRCWindow;
class IRCQuery : public RefCounted<IRCQuery> {
public:
static NonnullRefPtr<IRCQuery> create(IRCClient&, const String& name);
~IRCQuery();
String name() const { return m_name; }
void add_message(char prefix, const String& name, const String& text, Color = Color::Black);
void add_message(const String& text, Color = Color::Black);
const IRCLogBuffer& log() const { return *m_log; }
IRCLogBuffer& log() { return *m_log; }
void say(const String&);
IRCWindow& window() { return *m_window; }
const IRCWindow& window() const { return *m_window; }
private:
IRCQuery(IRCClient&, const String& name);
NonnullRefPtr<IRCClient> m_client;
String m_name;
RefPtr<IRCWindow> m_window;
NonnullRefPtr<IRCLogBuffer> m_log;
};

View file

@ -1,258 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "IRCWindow.h"
#include "IRCChannel.h"
#include "IRCChannelMemberListModel.h"
#include "IRCClient.h"
#include <AK/StringBuilder.h>
#include <LibGUI/Action.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/InputBox.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Notification.h>
#include <LibGUI/Splitter.h>
#include <LibGUI/TableView.h>
#include <LibGUI/TextBox.h>
#include <LibGUI/TextEditor.h>
#include <LibGUI/Window.h>
#include <LibWeb/InProcessWebView.h>
IRCWindow::IRCWindow(IRCClient& client, void* owner, Type type, const String& name)
: m_client(client)
, m_owner(owner)
, m_type(type)
, m_name(name)
{
set_layout<GUI::VerticalBoxLayout>();
// Make a container for the log buffer view + (optional) member list.
auto& container = add<GUI::HorizontalSplitter>();
m_page_view = container.add<Web::InProcessWebView>();
if (m_type == Channel) {
auto& member_view = container.add<GUI::TableView>();
member_view.set_column_headers_visible(false);
member_view.set_fixed_width(100);
member_view.set_alternating_row_colors(false);
member_view.set_model(channel().member_model());
member_view.set_activates_on_selection(true);
member_view.on_activation = [&](auto& index) {
if (!index.is_valid())
return;
auto nick = channel().member_model()->nick_at(member_view.selection().first());
if (nick.is_empty())
return;
m_client->handle_open_query_action(m_client->nick_without_prefix(nick.characters()));
};
member_view.on_context_menu_request = [&](const GUI::ModelIndex& index, const GUI::ContextMenuEvent& event) {
if (!index.is_valid())
return;
m_context_menu = GUI::Menu::construct();
m_context_menu->add_action(GUI::Action::create("Open &Query", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-open-query.png"), [&](const GUI::Action&) {
auto nick = channel().member_model()->nick_at(member_view.selection().first());
if (nick.is_empty())
return;
m_client->handle_open_query_action(m_client->nick_without_prefix(nick.characters()));
}));
m_context_menu->add_action(GUI::Action::create("Whois", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/irc-whois.png"), [&](const GUI::Action&) {
auto nick = channel().member_model()->nick_at(member_view.selection().first());
if (nick.is_empty())
return;
m_client->handle_whois_action(m_client->nick_without_prefix(nick.characters()));
}));
auto& context_control_menu = m_context_menu->add_submenu("Control");
context_control_menu.add_action(GUI::Action::create("&Voice", [&](const GUI::Action&) {
auto nick = channel().member_model()->nick_at(member_view.selection().first());
if (nick.is_empty())
return;
m_client->handle_voice_user_action(m_name.characters(), m_client->nick_without_prefix(nick.characters()));
}));
context_control_menu.add_action(GUI::Action::create("DeVoice", [&](const GUI::Action&) {
auto nick = channel().member_model()->nick_at(member_view.selection().first());
if (nick.is_empty())
return;
m_client->handle_devoice_user_action(m_name.characters(), m_client->nick_without_prefix(nick.characters()));
}));
context_control_menu.add_action(GUI::Action::create("Hop", [&](const GUI::Action&) {
auto nick = channel().member_model()->nick_at(member_view.selection().first());
if (nick.is_empty())
return;
m_client->handle_hop_user_action(m_name.characters(), m_client->nick_without_prefix(nick.characters()));
}));
context_control_menu.add_action(GUI::Action::create("DeHop", [&](const GUI::Action&) {
auto nick = channel().member_model()->nick_at(member_view.selection().first());
if (nick.is_empty())
return;
m_client->handle_dehop_user_action(m_name.characters(), m_client->nick_without_prefix(nick.characters()));
}));
context_control_menu.add_action(GUI::Action::create("&Op", [&](const GUI::Action&) {
auto nick = channel().member_model()->nick_at(member_view.selection().first());
if (nick.is_empty())
return;
m_client->handle_op_user_action(m_name.characters(), m_client->nick_without_prefix(nick.characters()));
}));
context_control_menu.add_action(GUI::Action::create("DeOp", [&](const GUI::Action&) {
auto nick = channel().member_model()->nick_at(member_view.selection().first());
if (nick.is_empty())
return;
m_client->handle_deop_user_action(m_name.characters(), m_client->nick_without_prefix(nick.characters()));
}));
context_control_menu.add_separator();
context_control_menu.add_action(GUI::Action::create("&Kick", [&](const GUI::Action&) {
auto nick = channel().member_model()->nick_at(member_view.selection().first());
if (nick.is_empty())
return;
if (IRCClient::is_nick_prefix(nick[0]))
nick = nick.substring(1, nick.length() - 1);
String value;
if (GUI::InputBox::show(window(), value, "Enter reason:", "Reason") == GUI::InputBox::ExecOK)
m_client->handle_kick_user_action(m_name.characters(), m_client->nick_without_prefix(nick.characters()), value);
}));
auto& context_ctcp_menu = m_context_menu->add_submenu("CTCP");
context_ctcp_menu.add_action(GUI::Action::create("&User Info", [&](const GUI::Action&) {
auto nick = channel().member_model()->nick_at(member_view.selection().first());
if (nick.is_empty())
return;
m_client->handle_ctcp_user_action(m_client->nick_without_prefix(nick.characters()), "USERINFO");
}));
context_ctcp_menu.add_action(GUI::Action::create("&Finger", [&](const GUI::Action&) {
auto nick = channel().member_model()->nick_at(member_view.selection().first());
if (nick.is_empty())
return;
m_client->handle_ctcp_user_action(m_client->nick_without_prefix(nick.characters()), "FINGER");
}));
context_ctcp_menu.add_action(GUI::Action::create("&Time", [&](const GUI::Action&) {
auto nick = channel().member_model()->nick_at(member_view.selection().first());
if (nick.is_empty())
return;
m_client->handle_ctcp_user_action(m_client->nick_without_prefix(nick.characters()), "TIME");
}));
context_ctcp_menu.add_action(GUI::Action::create("&Version", [&](const GUI::Action&) {
auto nick = channel().member_model()->nick_at(member_view.selection().first());
if (nick.is_empty())
return;
m_client->handle_ctcp_user_action(m_client->nick_without_prefix(nick.characters()), "VERSION");
}));
context_ctcp_menu.add_action(GUI::Action::create("&Client Info", [&](const GUI::Action&) {
auto nick = channel().member_model()->nick_at(member_view.selection().first());
if (nick.is_empty())
return;
m_client->handle_ctcp_user_action(m_client->nick_without_prefix(nick.characters()), "CLIENTINFO");
}));
m_context_menu->popup(event.screen_position());
};
}
m_text_box = add<GUI::TextBox>();
m_text_box->set_fixed_height(19);
m_text_box->on_return_pressed = [this] {
if (m_type == Channel)
m_client->handle_user_input_in_channel(m_name, m_text_box->text());
else if (m_type == Query)
m_client->handle_user_input_in_query(m_name, m_text_box->text());
else if (m_type == Server)
m_client->handle_user_input_in_server(m_text_box->text());
m_text_box->add_current_text_to_history();
m_text_box->clear();
};
m_text_box->set_history_enabled(true);
m_text_box->set_placeholder("Message");
m_client->register_subwindow(*this);
}
IRCWindow::~IRCWindow()
{
m_client->unregister_subwindow(*this);
}
void IRCWindow::set_log_buffer(const IRCLogBuffer& log_buffer)
{
m_log_buffer = &log_buffer;
m_page_view->set_document(const_cast<Web::DOM::Document*>(&log_buffer.document()));
}
bool IRCWindow::is_active() const
{
return m_client->current_window() == this;
}
void IRCWindow::post_notification_if_needed(const String& name, const String& message)
{
if (name.is_null() || message.is_null())
return;
if (is_active() && window()->is_active())
return;
auto notification = GUI::Notification::construct();
if (type() == Type::Channel) {
if (!m_client->notify_on_mention())
return;
if (!message.contains(m_client->nickname()))
return;
StringBuilder builder;
builder.append(name);
builder.append(" in ");
builder.append(m_name);
notification->set_title(builder.to_string());
} else {
if (!m_client->notify_on_message())
return;
notification->set_title(name);
}
notification->set_icon(Gfx::Bitmap::try_load_from_file("/res/icons/32x32/app-irc-client.png"));
notification->set_text(message);
notification->show();
}
void IRCWindow::did_add_message(const String& name, const String& message)
{
post_notification_if_needed(name, message);
if (!is_active()) {
++m_unread_count;
m_client->aid_update_window_list();
return;
}
m_page_view->scroll_to_bottom();
}
void IRCWindow::clear_unread_count()
{
if (!m_unread_count)
return;
m_unread_count = 0;
m_client->aid_update_window_list();
}
int IRCWindow::unread_count() const
{
return m_unread_count;
}

View file

@ -1,62 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGUI/Widget.h>
#include <LibWeb/Forward.h>
class IRCChannel;
class IRCClient;
class IRCQuery;
class IRCLogBuffer;
class IRCWindow : public GUI::Widget {
C_OBJECT(IRCWindow)
public:
enum Type {
Server,
Channel,
Query,
};
virtual ~IRCWindow() override;
String name() const { return m_name; }
void set_name(const String& name) { m_name = name; }
Type type() const { return m_type; }
void set_log_buffer(const IRCLogBuffer&);
bool is_active() const;
int unread_count() const;
void clear_unread_count();
void did_add_message(const String& name = {}, const String& message = {});
IRCChannel& channel() { return *(IRCChannel*)m_owner; }
const IRCChannel& channel() const { return *(const IRCChannel*)m_owner; }
IRCQuery& query() { return *(IRCQuery*)m_owner; }
const IRCQuery& query() const { return *(const IRCQuery*)m_owner; }
private:
IRCWindow(IRCClient&, void* owner, Type, const String& name);
void post_notification_if_needed(const String& name, const String& message);
NonnullRefPtr<IRCClient> m_client;
void* m_owner { nullptr };
Type m_type;
String m_name;
RefPtr<Web::InProcessWebView> m_page_view;
RefPtr<GUI::TextBox> m_text_box;
RefPtr<IRCLogBuffer> m_log_buffer;
RefPtr<GUI::Menu> m_context_menu;
int m_unread_count { 0 };
};

View file

@ -1,66 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "IRCWindowListModel.h"
#include "IRCChannel.h"
#include "IRCClient.h"
IRCWindowListModel::IRCWindowListModel(IRCClient& client)
: m_client(client)
{
}
IRCWindowListModel::~IRCWindowListModel()
{
}
int IRCWindowListModel::row_count(const GUI::ModelIndex&) const
{
return m_client->window_count();
}
int IRCWindowListModel::column_count(const GUI::ModelIndex&) const
{
return 1;
}
String IRCWindowListModel::column_name(int column) const
{
switch (column) {
case Column::Name:
return "Name";
}
VERIFY_NOT_REACHED();
}
GUI::Variant IRCWindowListModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const
{
if (role == GUI::ModelRole::TextAlignment)
return Gfx::TextAlignment::CenterLeft;
if (role == GUI::ModelRole::Display) {
switch (index.column()) {
case Column::Name: {
auto& window = m_client->window_at(index.row());
if (window.unread_count())
return String::formatted("{} ({})", window.name(), window.unread_count());
return window.name();
}
}
}
if (role == GUI::ModelRole::ForegroundColor) {
switch (index.column()) {
case Column::Name: {
auto& window = m_client->window_at(index.row());
if (window.unread_count())
return Color(Color::Red);
if (!window.channel().is_open())
return Color(Color::WarmGray);
return Color(Color::Black);
}
}
}
return {};
}

View file

@ -1,33 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Function.h>
#include <LibGUI/Model.h>
class IRCClient;
class IRCWindow;
class IRCWindowListModel final : public GUI::Model {
public:
enum Column {
Name,
};
static NonnullRefPtr<IRCWindowListModel> create(IRCClient& client) { return adopt_ref(*new IRCWindowListModel(client)); }
virtual ~IRCWindowListModel() override;
virtual int row_count(const GUI::ModelIndex&) const override;
virtual int column_count(const GUI::ModelIndex&) const override;
virtual String column_name(int column) const override;
virtual GUI::Variant data(const GUI::ModelIndex&, GUI::ModelRole) const override;
private:
explicit IRCWindowListModel(IRCClient&);
NonnullRefPtr<IRCClient> m_client;
};

View file

@ -1,85 +0,0 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "IRCAppWindow.h"
#include "IRCClient.h"
#include <LibCore/StandardPaths.h>
#include <LibGUI/Application.h>
#include <LibGUI/MessageBox.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char** argv)
{
if (pledge("stdio inet unix recvfd sendfd cpath rpath wpath", nullptr) < 0) {
perror("pledge");
return 1;
}
if (getuid() == 0) {
warnln("Refusing to run as root");
return 1;
}
auto app = GUI::Application::construct(argc, argv);
if (unveil("/tmp/portal/lookup", "rw") < 0) {
perror("unveil");
return 1;
}
if (unveil("/tmp/portal/notify", "rw") < 0) {
perror("unveil");
return 1;
}
if (unveil("/etc/passwd", "r") < 0) {
perror("unveil");
return 1;
}
if (unveil(Core::StandardPaths::home_directory().characters(), "rwc") < 0) {
perror("unveil");
return 1;
}
if (unveil("/res", "r") < 0) {
perror("unveil");
return 1;
}
if (unveil(nullptr, nullptr) < 0) {
perror("unveil");
return 1;
}
URL url = "";
if (app->args().size() >= 1) {
url = URL::create_with_url_or_path(app->args()[0]);
if (url.protocol().to_lowercase() == "ircs") {
warnln("Secure IRC over SSL/TLS (ircs) is not supported");
return 1;
}
if (url.protocol().to_lowercase() != "irc") {
warnln("Unsupported protocol");
return 1;
}
if (url.host().is_empty()) {
warnln("Invalid URL");
return 1;
}
if (!url.port() || url.port() == 80)
url.set_port(6667);
}
auto app_window = IRCAppWindow::construct(url.host(), url.port());
app_window->show();
return app->exec();
}

View file

@ -131,7 +131,7 @@ GalleryWidget::GalleryWidget()
};
m_msgbox_button = basics_tab.find_descendant_of_type_named<GUI::Button>("msgbox_button");
m_msgbox_button->set_icon(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/app-irc-client.png"));
m_msgbox_button->set_icon(Gfx::Bitmap::try_load_from_file("/res/icons/16x16/app-browser.png"));
m_msgbox_type = GUI::MessageBox::Type::None;
m_msgbox_input_type = GUI::MessageBox::InputType::OK;