diff options
author | ouwou <26526779+ouwou@users.noreply.github.com> | 2022-11-29 15:53:02 -0500 |
---|---|---|
committer | ouwou <26526779+ouwou@users.noreply.github.com> | 2022-11-29 15:53:02 -0500 |
commit | 573a61919149d8d0119601a06e23322b71c778c3 (patch) | |
tree | 0f9fd234f4b7f5c409172a5d6afe9897b6f88097 /src | |
parent | 77dd9fabfa054bd8fa24f869b976da7ee69c8a87 (diff) | |
parent | c5807a3463aaefc89e2432730b997437305af59a (diff) | |
download | abaddon-portaudio-573a61919149d8d0119601a06e23322b71c778c3.tar.gz abaddon-portaudio-573a61919149d8d0119601a06e23322b71c778c3.zip |
Merge branch 'master' into keychain
Diffstat (limited to 'src')
31 files changed, 586 insertions, 339 deletions
diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 563a42c..d759426 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -6,7 +6,6 @@ #include "discord/discord.hpp" #include "dialogs/token.hpp" #include "dialogs/editmessage.hpp" -#include "dialogs/joinguild.hpp" #include "dialogs/confirm.hpp" #include "dialogs/setstatus.hpp" #include "dialogs/friendpicker.hpp" @@ -17,6 +16,7 @@ #include "windows/profilewindow.hpp" #include "windows/pinnedwindow.hpp" #include "windows/threadswindow.hpp" +#include "startup.hpp" #ifdef WITH_LIBHANDY #include <handy.h> @@ -68,14 +68,13 @@ Abaddon &Abaddon::Get() { return instance; } -#ifdef WITH_LIBHANDY - #ifdef _WIN32 +#ifdef _WIN32 constexpr static guint BUTTON_BACK = 4; constexpr static guint BUTTON_FORWARD = 5; - #else +#else constexpr static guint BUTTON_BACK = 8; constexpr static guint BUTTON_FORWARD = 9; - #endif +#endif static bool HandleButtonEvents(GdkEvent *event, MainWindow *main_window) { if (event->type != GDK_BUTTON_PRESS) return false; @@ -85,6 +84,7 @@ static bool HandleButtonEvents(GdkEvent *event, MainWindow *main_window) { auto *window = gtk_widget_get_toplevel(widget); if (static_cast<void *>(window) != static_cast<void *>(main_window->gobj())) return false; // is this the right way??? +#ifdef WITH_LIBHANDY switch (event->button.button) { case BUTTON_BACK: main_window->GoBack(); @@ -93,6 +93,7 @@ static bool HandleButtonEvents(GdkEvent *event, MainWindow *main_window) { main_window->GoForward(); break; } +#endif return false; } @@ -108,6 +109,15 @@ static bool HandleKeyEvents(GdkEvent *event, MainWindow *main_window) { const bool ctrl = (event->key.state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK; const bool shft = (event->key.state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK; + constexpr static guint EXCLUDE_STATES = GDK_CONTROL_MASK | GDK_SHIFT_MASK; + + if (!(event->key.state & EXCLUDE_STATES) && event->key.keyval == GDK_KEY_Alt_L) { + if (Abaddon::Get().GetSettings().AltMenu) { + main_window->ToggleMenuVisibility(); + } + } + +#ifdef WITH_LIBHANDY if (ctrl) { switch (event->key.keyval) { case GDK_KEY_Tab: @@ -134,6 +144,7 @@ static bool HandleKeyEvents(GdkEvent *event, MainWindow *main_window) { return true; } } +#endif return false; } @@ -143,7 +154,6 @@ static void MainEventHandler(GdkEvent *event, void *main_window) { if (HandleKeyEvents(event, static_cast<MainWindow *>(main_window))) return; gtk_main_do_event(event); } -#endif int Abaddon::StartGTK() { m_gtk_app = Gtk::Application::create("com.github.uowuo.abaddon"); @@ -228,7 +238,6 @@ int Abaddon::StartGTK() { m_main_window->signal_action_disconnect().connect(sigc::mem_fun(*this, &Abaddon::ActionDisconnect)); m_main_window->signal_action_set_token().connect(sigc::mem_fun(*this, &Abaddon::ActionSetToken)); m_main_window->signal_action_reload_css().connect(sigc::mem_fun(*this, &Abaddon::ActionReloadCSS)); - m_main_window->signal_action_join_guild().connect(sigc::mem_fun(*this, &Abaddon::ActionJoinGuildDialog)); m_main_window->signal_action_set_status().connect(sigc::mem_fun(*this, &Abaddon::ActionSetStatus)); m_main_window->signal_action_add_recipient().connect(sigc::mem_fun(*this, &Abaddon::ActionAddRecipient)); m_main_window->signal_action_view_pins().connect(sigc::mem_fun(*this, &Abaddon::ActionViewPins)); @@ -247,12 +256,29 @@ int Abaddon::StartGTK() { m_main_window->GetChatWindow()->signal_action_reaction_remove().connect(sigc::mem_fun(*this, &Abaddon::ActionReactionRemove)); ActionReloadCSS(); + if (m_settings.GetSettings().HideToTray) { + m_tray = Gtk::StatusIcon::create("discord"); + m_tray->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_tray_click)); + m_tray->signal_popup_menu().connect(sigc::mem_fun(*this, &Abaddon::on_tray_popup_menu)); + } + m_tray_menu = Gtk::make_managed<Gtk::Menu>(); + m_tray_exit = Gtk::make_managed<Gtk::MenuItem>("Quit", false); + m_tray_exit->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_tray_menu_click)); + + m_tray_menu->append(*m_tray_exit); + m_tray_menu->show_all(); + + m_main_window->signal_hide().connect(sigc::mem_fun(*this, &Abaddon::on_window_hide)); m_gtk_app->signal_shutdown().connect(sigc::mem_fun(*this, &Abaddon::OnShutdown), false); m_main_window->UpdateMenus(); + m_gtk_app->hold(); m_main_window->show(); + + RunFirstTimeDiscordStartup(); + return m_gtk_app->run(*m_main_window); } @@ -390,6 +416,7 @@ void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_ for (const auto child : m_user_menu_roles_submenu->get_children()) delete child; + if (guild.has_value() && user.has_value()) { const auto roles = user->GetSortedRoles(); m_user_menu_roles->set_visible(!roles.empty()); @@ -412,7 +439,7 @@ void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_ if (me == id) { m_user_menu_ban->set_visible(false); m_user_menu_kick->set_visible(false); - m_user_menu_open_dm->set_visible(false); + m_user_menu_open_dm->set_sensitive(false); } else { const bool has_kick = m_discord.HasGuildPermission(me, guild_id, Permission::KICK_MEMBERS); const bool has_ban = m_discord.HasGuildPermission(me, guild_id, Permission::BAN_MEMBERS); @@ -420,7 +447,7 @@ void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_ m_user_menu_kick->set_visible(has_kick && can_manage); m_user_menu_ban->set_visible(has_ban && can_manage); - m_user_menu_open_dm->set_visible(true); + m_user_menu_open_dm->set_sensitive(m_discord.FindDM(id).has_value()); } m_user_menu_remove_recipient->hide(); @@ -434,6 +461,48 @@ void Abaddon::ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_ m_user_menu->popup_at_pointer(event); } +void Abaddon::RunFirstTimeDiscordStartup() { + DiscordStartupDialog dlg(*m_main_window); + dlg.set_position(Gtk::WIN_POS_CENTER); + + std::optional<std::string> cookie; + std::optional<uint32_t> build_number; + + dlg.signal_response().connect([&](int response) { + if (response == Gtk::RESPONSE_OK) { + cookie = dlg.GetCookie(); + build_number = dlg.GetBuildNumber(); + } + }); + + dlg.run(); + + Glib::signal_idle().connect_once([this, cookie, build_number]() { + if (cookie.has_value()) { + m_discord.SetCookie(*cookie); + } else { + ConfirmDialog confirm(*m_main_window); + confirm.SetConfirmText("Cookies could not be fetched. This may increase your chances of being flagged by Discord's anti-spam"); + confirm.SetAcceptOnly(true); + confirm.run(); + } + + if (build_number.has_value()) { + m_discord.SetBuildNumber(*build_number); + } else { + ConfirmDialog confirm(*m_main_window); + confirm.SetConfirmText("Build number could not be fetched. This may increase your chances of being flagged by Discord's anti-spam"); + confirm.SetAcceptOnly(true); + confirm.run(); + } + + // autoconnect + if (cookie.has_value() && build_number.has_value() && GetSettings().Autoconnect && !GetDiscordToken().empty()) { + ActionConnect(); + } + }); +} + void Abaddon::ShowGuildVerificationGateDialog(Snowflake guild_id) { VerificationGateDialog dlg(*m_main_window, guild_id); if (dlg.run() == Gtk::RESPONSE_OK) { @@ -468,7 +537,7 @@ void Abaddon::SetupUserMenu() { m_user_menu_ban = Gtk::manage(new Gtk::MenuItem("Ban")); m_user_menu_kick = Gtk::manage(new Gtk::MenuItem("Kick")); m_user_menu_copy_id = Gtk::manage(new Gtk::MenuItem("Copy ID")); - m_user_menu_open_dm = Gtk::manage(new Gtk::MenuItem("Open DM")); + m_user_menu_open_dm = Gtk::manage(new Gtk::MenuItem("Go to DM")); m_user_menu_roles = Gtk::manage(new Gtk::MenuItem("Roles")); m_user_menu_info = Gtk::manage(new Gtk::MenuItem("View Profile")); m_user_menu_remove_recipient = Gtk::manage(new Gtk::MenuItem("Remove From Group")); @@ -579,18 +648,9 @@ void Abaddon::on_user_menu_copy_id() { void Abaddon::on_user_menu_open_dm() { const auto existing = m_discord.FindDM(m_shown_user_menu_id); - if (existing.has_value()) + if (existing.has_value()) { ActionChannelOpened(*existing); - else - m_discord.CreateDM(m_shown_user_menu_id, [this](DiscordError code, Snowflake channel_id) { - if (code == DiscordError::NONE) { - // give the gateway a little window to send CHANNEL_CREATE - auto cb = [this, channel_id] { - ActionChannelOpened(channel_id); - }; - Glib::signal_timeout().connect_once(sigc::track_obj(cb, *this), 200); - } - }); + } } void Abaddon::on_user_menu_remove_recipient() { @@ -646,22 +706,18 @@ void Abaddon::ActionSetToken() { m_main_window->UpdateMenus(); } -void Abaddon::ActionJoinGuildDialog() { - JoinGuildDialog dlg(*m_main_window); - auto response = dlg.run(); - if (response == Gtk::RESPONSE_OK) { - auto code = dlg.GetCode(); - m_discord.JoinGuild(code); - } -} - void Abaddon::ActionChannelOpened(Snowflake id, bool expand_to) { - if (!id.IsValid() || id == m_main_window->GetChatActiveChannel()) return; + if (!id.IsValid()) { + m_discord.SetReferringChannel(Snowflake::Invalid); + return; + } + if (id == m_main_window->GetChatActiveChannel()) return; m_main_window->GetChatWindow()->SetTopic(""); const auto channel = m_discord.GetChannel(id); if (!channel.has_value()) { + m_discord.SetReferringChannel(Snowflake::Invalid); m_main_window->UpdateChatActiveChannel(Snowflake::Invalid, false); m_main_window->UpdateChatWindowContents(); return; @@ -710,6 +766,7 @@ void Abaddon::ActionChannelOpened(Snowflake id, bool expand_to) { } m_main_window->UpdateMenus(); + m_discord.SetReferringChannel(id); } void Abaddon::ActionChatLoadHistory(Snowflake id) { @@ -898,6 +955,21 @@ EmojiResource &Abaddon::GetEmojis() { return m_emojis; } +void Abaddon::on_tray_click() { + m_main_window->set_visible(!m_main_window->is_visible()); +} +void Abaddon::on_tray_menu_click() { + m_gtk_app->quit(); +} +void Abaddon::on_tray_popup_menu(int button, int activate_time) { + m_tray->popup_menu_at_position(*m_tray_menu, button, activate_time); +} +void Abaddon::on_window_hide() { + if (!m_settings.GetSettings().HideToTray) { + m_gtk_app->quit(); + } +} + int main(int argc, char **argv) { if (std::getenv("ABADDON_NO_FC") == nullptr) Platform::SetupFonts(); diff --git a/src/abaddon.hpp b/src/abaddon.hpp index c267269..b067324 100644 --- a/src/abaddon.hpp +++ b/src/abaddon.hpp @@ -1,3 +1,4 @@ +#pragma once #include <gtkmm.h> #include <memory> #include <mutex> @@ -93,6 +94,8 @@ public: static std::string GetStateCachePath(const std::string &path); protected: + void RunFirstTimeDiscordStartup(); + void ShowGuildVerificationGateDialog(Snowflake guild_id); void CheckMessagesForMembers(const ChannelData &chan, const std::vector<Message> &msgs); @@ -114,6 +117,8 @@ protected: Gtk::MenuItem *m_user_menu_roles; Gtk::MenuItem *m_user_menu_remove_recipient; Gtk::Menu *m_user_menu_roles_submenu; + Gtk::Menu *m_tray_menu; + Gtk::MenuItem *m_tray_exit; void on_user_menu_insert_mention(); void on_user_menu_ban(); @@ -121,6 +126,10 @@ protected: void on_user_menu_copy_id(); void on_user_menu_open_dm(); void on_user_menu_remove_recipient(); + void on_tray_click(); + void on_tray_popup_menu(int button, int activate_time); + void on_tray_menu_click(); + void on_window_hide(); private: SettingsManager m_settings; @@ -139,5 +148,6 @@ private: Glib::RefPtr<Gtk::Application> m_gtk_app; Glib::RefPtr<Gtk::CssProvider> m_css_provider; Glib::RefPtr<Gtk::CssProvider> m_css_low_provider; // registered with a lower priority to allow better customization - std::unique_ptr<MainWindow> m_main_window; // wah wah cant create a gtkstylecontext fuck you + Glib::RefPtr<Gtk::StatusIcon> m_tray; + std::unique_ptr<MainWindow> m_main_window; // wah wah cant create a gtkstylecontext fuck you }; diff --git a/src/components/chatinput.cpp b/src/components/chatinput.cpp index 87892e0..2466965 100644 --- a/src/components/chatinput.cpp +++ b/src/components/chatinput.cpp @@ -176,11 +176,10 @@ bool ChatInputTextContainer::GetChildPosition(Gtk::Widget *child, Gdk::Rectangle Gtk::Requisition min, req; child->get_preferred_size(min, req); - // yummy hardcoded values - pos.set_x(5); + // let css move it around + pos.set_x(0); + pos.set_y(0); pos.set_width(std::max(min.width, std::min(main_alloc.get_width(), req.width))); - - pos.set_y(12); pos.set_height(std::max(min.height, std::min(main_alloc.get_height(), req.height))); return true; diff --git a/src/components/chatmessage.cpp b/src/components/chatmessage.cpp index 863349f..3afdf9f 100644 --- a/src/components/chatmessage.cpp +++ b/src/components/chatmessage.cpp @@ -32,7 +32,6 @@ ChatMessageItemContainer *ChatMessageItemContainer::FromMessage(const Message &d if (!data.Content.empty() || data.Type != MessageType::DEFAULT) { container->m_text_component = container->CreateTextComponent(data); - container->AttachEventHandlers(*container->m_text_component); container->m_main.add(*container->m_text_component); } @@ -101,7 +100,6 @@ void ChatMessageItemContainer::UpdateContent() { if (!data->Embeds.empty()) { m_embed_component = CreateEmbedsComponent(data->Embeds); - AttachEventHandlers(*m_embed_component); m_main.add(*m_embed_component); m_embed_component->show_all(); } @@ -152,12 +150,12 @@ void ChatMessageItemContainer::UpdateAttributes() { void ChatMessageItemContainer::AddClickHandler(Gtk::Widget *widget, const std::string &url) { // clang-format off - widget->signal_button_press_event().connect([url](GdkEventButton *event) -> bool { - if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) { + widget->signal_button_release_event().connect([url](GdkEventButton *event) -> bool { + if (event->type == GDK_BUTTON_RELEASE && event->button == GDK_BUTTON_PRIMARY) { LaunchBrowser(url); - return false; + return true; } - return true; + return false; }, false); // clang-format on } @@ -174,6 +172,8 @@ Gtk::TextView *ChatMessageItemContainer::CreateTextComponent(const Message &data tv->set_halign(Gtk::ALIGN_FILL); tv->set_hexpand(true); + tv->signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::OnTextViewButtonPress), false); + UpdateTextComponent(tv); return tv; @@ -281,8 +281,6 @@ void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) { tag->property_weight() = Pango::WEIGHT_BOLD; m_channel_tagmap[tag] = *data->MessageReference->ChannelID; b->insert_with_tag(iter, data->Content, tag); - - tv->signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::OnClickChannel), false); } else { b->insert_markup(s, "<i><span color='#999999'>" + author->GetEscapedBoldName() + " started a thread: </span><b>" + Glib::Markup::escape_text(data->Content) + "</b></i>"); } @@ -297,12 +295,10 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedsComponent(const std::vector<E if (IsEmbedImageOnly(embed)) { auto *widget = CreateImageComponent(*embed.Thumbnail->ProxyURL, *embed.Thumbnail->URL, *embed.Thumbnail->Width, *embed.Thumbnail->Height); widget->show(); - AttachEventHandlers(*widget); box->add(*widget); } else { auto *widget = CreateEmbedComponent(embed); widget->show(); - AttachEventHandlers(*widget); box->add(*widget); } } @@ -361,8 +357,8 @@ Gtk::Widget *ChatMessageItemContainer::CreateEmbedComponent(const EmbedData &emb if (embed.URL.has_value()) { AddPointerCursor(*title_ev); auto url = *embed.URL; - title_ev->signal_button_press_event().connect([url = std::move(url)](GdkEventButton *event) -> bool { - if (event->button == GDK_BUTTON_PRIMARY) { + title_ev->signal_button_release_event().connect([url = std::move(url)](GdkEventButton *event) -> bool { + if (event->type == GDK_BUTTON_RELEASE && event->button == GDK_BUTTON_PRIMARY) { LaunchBrowser(url); return true; } @@ -493,12 +489,22 @@ Gtk::Widget *ChatMessageItemContainer::CreateImageComponent(const std::string &p Gtk::EventBox *ev = Gtk::manage(new Gtk::EventBox); Gtk::Image *widget = Gtk::manage(new LazyImage(proxy_url, w, h, false)); ev->add(*widget); + ev->set_halign(Gtk::ALIGN_START); widget->set_halign(Gtk::ALIGN_START); widget->set_size_request(w, h); - AttachEventHandlers(*ev); AddClickHandler(ev, url); + const auto on_button_press_event = [this, url](GdkEventButton *e) -> bool { + if (e->type == GDK_BUTTON_PRESS && e->button == GDK_BUTTON_SECONDARY) { + m_selected_link = url; + m_link_menu.popup_at_pointer(reinterpret_cast<GdkEvent *>(e)); + return true; + } + return false; + }; + ev->signal_button_press_event().connect(on_button_press_event, false); + return ev; } @@ -510,9 +516,18 @@ Gtk::Widget *ChatMessageItemContainer::CreateAttachmentComponent(const Attachmen ev->get_style_context()->add_class("message-attachment-box"); ev->add(*btn); - AttachEventHandlers(*ev); AddClickHandler(ev, data.URL); + const auto on_button_press_event = [this, url = data.URL](GdkEventButton *e) -> bool { + if (e->type == GDK_BUTTON_PRESS && e->button == GDK_BUTTON_SECONDARY) { + m_selected_link = url; + m_link_menu.popup_at_pointer(reinterpret_cast<GdkEvent *>(e)); + return true; + } + return false; + }; + ev->signal_button_press_event().connect(on_button_press_event, false); + return ev; } @@ -534,7 +549,6 @@ Gtk::Widget *ChatMessageItemContainer::CreateStickersComponent(const std::vector box->show(); - AttachEventHandlers(*box); return box; } @@ -641,13 +655,19 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) const auto role = discord.GetRole(role_id); if (role.has_value()) { const auto author = discord.GetUser(author_id); - return "<b><span color=\"#" + IntToCSSColor(role->Color) + "\">" + author->GetEscapedString() + "</span></b>"; + if (author.has_value()) { + return "<b><span color=\"#" + IntToCSSColor(role->Color) + "\">" + author->GetEscapedString() + "</span></b>"; + } } } } const auto author = discord.GetUser(author_id); - return author->GetEscapedBoldString<false>(); + if (author.has_value()) { + return author->GetEscapedBoldString<false>(); + } + + return "<b>Unknown User</b>"; }; // if the message wasnt fetched from store it might have an un-fetched reference @@ -659,15 +679,15 @@ Gtk::Widget *ChatMessageItemContainer::CreateReplyComponent(const Message &data) } if (data.Interaction.has_value()) { - const auto user = *discord.GetUser(data.Interaction->User.ID); - if (data.GuildID.has_value()) { - lbl->set_markup(get_author_markup(user.ID, *data.GuildID) + + lbl->set_markup(get_author_markup(data.Interaction->User.ID, *data.GuildID) + " used <span color='#697ec4'>/" + Glib::Markup::escape_text(data.Interaction->Name) + "</span>"); + } else if (const auto user = discord.GetUser(data.Interaction->User.ID); user.has_value()) { + lbl->set_markup(user->GetEscapedBoldString<false>()); } else { - lbl->set_markup(user.GetEscapedBoldString<false>()); + lbl->set_markup("<b>Unknown User</b>"); } } else if (referenced_message.has_value()) { if (referenced_message.value() == nullptr) { @@ -956,7 +976,6 @@ void ChatMessageItemContainer::HandleChannelMentions(const Glib::RefPtr<Gtk::Tex } void ChatMessageItemContainer::HandleChannelMentions(Gtk::TextView *tv) { - tv->signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::OnClickChannel), false); HandleChannelMentions(tv->get_buffer()); } @@ -990,6 +1009,20 @@ bool ChatMessageItemContainer::OnClickChannel(GdkEventButton *ev) { return false; } +bool ChatMessageItemContainer::OnTextViewButtonPress(GdkEventButton *ev) { + // run all button press handlers and propagate if none return true + if (OnLinkClick(ev)) return true; + if (OnClickChannel(ev)) return true; + + if (ev->type == GDK_BUTTON_PRESS && ev->button == GDK_BUTTON_SECONDARY) { + // send the event upward skipping TextView's handler because we dont want it + gtk_propagate_event(GTK_WIDGET(m_main.gobj()), reinterpret_cast<GdkEvent *>(ev)); + return true; + } + + return false; +} + void ChatMessageItemContainer::on_link_menu_copy() { Gtk::Clipboard::get()->set_text(m_selected_link); } @@ -997,8 +1030,6 @@ void ChatMessageItemContainer::on_link_menu_copy() { void ChatMessageItemContainer::HandleLinks(Gtk::TextView &tv) { const auto rgx = Glib::Regex::create(R"(\bhttps?:\/\/[^\s]+\.[^\s]+\b)"); - tv.signal_button_press_event().connect(sigc::mem_fun(*this, &ChatMessageItemContainer::OnLinkClick), false); - auto buf = tv.get_buffer(); Glib::ustring text = GetText(buf); @@ -1070,18 +1101,6 @@ ChatMessageItemContainer::type_signal_action_reaction_remove ChatMessageItemCont return m_signal_action_reaction_remove; } -void ChatMessageItemContainer::AttachEventHandlers(Gtk::Widget &widget) { - const auto on_button_press_event = [this](GdkEventButton *e) -> bool { - if (e->type == GDK_BUTTON_PRESS && e->button == GDK_BUTTON_SECONDARY) { - event(reinterpret_cast<GdkEvent *>(e)); // illegal ooooooh - return true; - } - - return false; - }; - widget.signal_button_press_event().connect(on_button_press_event, false); -} - ChatMessageHeader::ChatMessageHeader(const Message &data) : m_main_box(Gtk::ORIENTATION_HORIZONTAL) , m_content_box(Gtk::ORIENTATION_VERTICAL) diff --git a/src/components/chatmessage.hpp b/src/components/chatmessage.hpp index 86c3fea..7851351 100644 --- a/src/components/chatmessage.hpp +++ b/src/components/chatmessage.hpp @@ -2,7 +2,7 @@ #include <gtkmm.h> #include "discord/discord.hpp" -class ChatMessageItemContainer : public Gtk::Box { +class ChatMessageItemContainer : public Gtk::EventBox { public: Snowflake ID; Snowflake ChannelID; @@ -44,6 +44,7 @@ protected: void HandleChannelMentions(const Glib::RefPtr<Gtk::TextBuffer> &buf); void HandleChannelMentions(Gtk::TextView *tv); bool OnClickChannel(GdkEventButton *ev); + bool OnTextViewButtonPress(GdkEventButton *ev); // reused for images and links Gtk::Menu m_link_menu; @@ -57,8 +58,6 @@ protected: std::map<Glib::RefPtr<Gtk::TextTag>, std::string> m_link_tagmap; std::map<Glib::RefPtr<Gtk::TextTag>, Snowflake> m_channel_tagmap; - void AttachEventHandlers(Gtk::Widget &widget); - Gtk::EventBox *_ev; Gtk::Box m_main; Gtk::Label *m_attrib_label = nullptr; diff --git a/src/dialogs/confirm.cpp b/src/dialogs/confirm.cpp index 39d8971..8e606df 100644 --- a/src/dialogs/confirm.cpp +++ b/src/dialogs/confirm.cpp @@ -34,3 +34,7 @@ ConfirmDialog::ConfirmDialog(Gtk::Window &parent) void ConfirmDialog::SetConfirmText(const Glib::ustring &text) { m_label.set_text(text); } + +void ConfirmDialog::SetAcceptOnly(bool accept_only) { + m_cancel.set_visible(!accept_only); +} diff --git a/src/dialogs/confirm.hpp b/src/dialogs/confirm.hpp index df1e185..84a089c 100644 --- a/src/dialogs/confirm.hpp +++ b/src/dialogs/confirm.hpp @@ -5,6 +5,7 @@ class ConfirmDialog : public Gtk::Dialog { public: ConfirmDialog(Gtk::Window &parent); void SetConfirmText(const Glib::ustring &text); + void SetAcceptOnly(bool accept_only); protected: Gtk::Label m_label; diff --git a/src/dialogs/joinguild.cpp b/src/dialogs/joinguild.cpp deleted file mode 100644 index 14fab53..0000000 --- a/src/dialogs/joinguild.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "joinguild.hpp" -#include "abaddon.hpp" -#include <nlohmann/json.hpp> -#include <regex> - -JoinGuildDialog::JoinGuildDialog(Gtk::Window &parent) - : Gtk::Dialog("Join Server", parent, true) - , m_layout(Gtk::ORIENTATION_VERTICAL) - , m_ok("OK") - , m_cancel("Cancel") - , m_info("Enter code") { - set_default_size(300, 50); - get_style_context()->add_class("app-window"); - get_style_context()->add_class("app-popup"); - - Glib::signal_idle().connect(sigc::mem_fun(*this, &JoinGuildDialog::on_idle_slot)); - - m_entry.signal_changed().connect(sigc::mem_fun(*this, &JoinGuildDialog::on_entry_changed)); - - m_ok.set_sensitive(false); - - m_ok.signal_clicked().connect([&]() { - response(Gtk::RESPONSE_OK); - }); - - m_cancel.signal_clicked().connect([&]() { - response(Gtk::RESPONSE_CANCEL); - }); - - m_entry.set_hexpand(true); - m_layout.add(m_entry); - m_lower.set_hexpand(true); - m_lower.pack_start(m_info); - m_info.set_halign(Gtk::ALIGN_START); - m_lower.pack_start(m_ok, Gtk::PACK_SHRINK); - m_lower.pack_start(m_cancel, Gtk::PACK_SHRINK); - m_ok.set_halign(Gtk::ALIGN_END); - m_cancel.set_halign(Gtk::ALIGN_END); - m_layout.add(m_lower); - get_content_area()->add(m_layout); - - show_all_children(); -} - -void JoinGuildDialog::on_entry_changed() { - std::string s = m_entry.get_text(); - std::regex invite_regex(R"((https?:\/\/)?discord\.(gg(\/invite)?\/|com\/invite\/)([A-Za-z0-9\-]+))", std::regex_constants::ECMAScript); - std::smatch match; - bool full_url = std::regex_search(s, match, invite_regex); - if (full_url || IsCode(s)) { - m_code = full_url ? match[4].str() : s; - m_needs_request = true; - m_ok.set_sensitive(false); - } else { - m_ok.set_sensitive(false); - } -} - -void JoinGuildDialog::CheckCode() { - auto cb = [this](const std::optional<InviteData> &invite) { - if (invite.has_value()) { - m_ok.set_sensitive(true); - if (invite->Guild.has_value()) { - if (invite->MemberCount.has_value()) - m_info.set_text(invite->Guild->Name + " (" + std::to_string(*invite->MemberCount) + " members)"); - else - m_info.set_text(invite->Guild->Name); - } else { - m_info.set_text("Group DM (" + std::to_string(*invite->MemberCount) + " members)"); - } - } else { - m_ok.set_sensitive(false); - m_info.set_text("Invalid invite"); - } - }; - Abaddon::Get().GetDiscordClient().FetchInvite(m_code, sigc::track_obj(cb, *this)); -} - -bool JoinGuildDialog::IsCode(std::string str) { - return str.length() >= 2 && std::all_of(str.begin(), str.end(), [](char c) -> bool { return std::isalnum(c) || c == '-'; }); -} - -std::string JoinGuildDialog::GetCode() { - return m_code; -} - -static const constexpr int RateLimitMS = 1500; -bool JoinGuildDialog::on_idle_slot() { - const auto now = std::chrono::steady_clock::now(); - if (m_needs_request && ((now - m_last_req_time) > std::chrono::milliseconds(RateLimitMS))) { - m_needs_request = false; - m_last_req_time = now; - CheckCode(); - } - - return true; -} diff --git a/src/dialogs/joinguild.hpp b/src/dialogs/joinguild.hpp deleted file mode 100644 index ba061f3..0000000 --- a/src/dialogs/joinguild.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include <gtkmm.h> -#include <string> -#include <chrono> - -class JoinGuildDialog : public Gtk::Dialog { -public: - JoinGuildDialog(Gtk::Window &parent); - std::string GetCode(); - -protected: - void on_entry_changed(); - static bool IsCode(std::string str); - - Gtk::Box m_layout; - Gtk::Button m_ok; - Gtk::Button m_cancel; - Gtk::Box m_lower; - Gtk::Label m_info; - Gtk::Entry m_entry; - - void CheckCode(); - - // needs a rate limit cuz if u hit it u get ip banned from /invites for a long time :( - bool m_needs_request = false; - std::chrono::time_point<std::chrono::steady_clock> m_last_req_time; - bool on_idle_slot(); - -private: - std::string m_code; -}; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index d2c8edd..2808e17 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -30,6 +30,7 @@ void DiscordClient::Start() { if (m_client_started) return; m_http.SetBase(GetAPIURL()); + SetHeaders(); std::memset(&m_zstream, 0, sizeof(m_zstream)); inflateInit2(&m_zstream, MAX_WBITS + 32); @@ -572,10 +573,6 @@ void DiscordClient::SendThreadLazyLoad(Snowflake id) { m_websocket.Send(msg); } -void DiscordClient::JoinGuild(const std::string &code) { - m_http.MakePOST("/invites/" + code, "{}", [](auto) {}); -} - void DiscordClient::LeaveGuild(Snowflake id) { m_http.MakeDELETE("/users/@me/guilds/" + std::to_string(id), [](auto) {}); } @@ -610,19 +607,6 @@ void DiscordClient::UpdateStatus(PresenceStatus status, bool is_afk, const Activ m_signal_presence_update.emit(GetUserData(), status); } -void DiscordClient::CreateDM(Snowflake user_id, const sigc::slot<void(DiscordError code, Snowflake channel_id)> &callback) { - CreateDMObject obj; - obj.Recipients.push_back(user_id); - m_http.MakePOST("/users/@me/channels", nlohmann::json(obj).dump(), [callback](const http::response &response) { - if (!CheckCode(response)) { - callback(DiscordError::NONE, Snowflake::Invalid); - return; - } - auto channel = nlohmann::json::parse(response.text).get<ChannelData>(); - callback(GetCodeFromResponse(response), channel.ID); - }); -} - void DiscordClient::CloseDM(Snowflake channel_id) { m_http.MakeDELETE("/channels/" + std::to_string(channel_id), [](const http::response &response) { CheckCode(response); @@ -1181,6 +1165,33 @@ void DiscordClient::AcceptVerificationGate(Snowflake guild_id, VerificationGateI }); } +void DiscordClient::SetReferringChannel(Snowflake id) { + if (!id.IsValid()) { + m_http.SetPersistentHeader("Referer", "https://discord.com/channels/@me"); + } else { + const auto channel = GetChannel(id); + if (channel.has_value()) { + if (channel->IsDM()) { + m_http.SetPersistentHeader("Referer", "https://discord.com/channels/@me/" + std::to_string(id)); + } else if (channel->GuildID.has_value()) { + m_http.SetPersistentHeader("Referer", "https://discord.com/channels/" + std::to_string(*channel->GuildID) + "/" + std::to_string(id)); + } else { + m_http.SetPersistentHeader("Referer", "https://discord.com/channels/@me"); + } + } else { + m_http.SetPersistentHeader("Referer", "https://discord.com/channels/@me"); + } + } +} + +void DiscordClient::SetBuildNumber(uint32_t build_number) { + m_build_number = build_number; +} + +void DiscordClient::SetCookie(std::string_view cookie) { + m_http.SetCookie(cookie); +} + void DiscordClient::UpdateToken(const std::string &token) { if (!IsStarted()) { m_token = token; @@ -2294,7 +2305,7 @@ void DiscordClient::HeartbeatThread() { void DiscordClient::SendIdentify() { IdentifyMessage msg; msg.Token = m_token; - msg.Capabilities = 125; // no idea what this is + msg.Capabilities = 509; // no idea what this is msg.Properties.OS = "Windows"; msg.Properties.Browser = "Chrome"; msg.Properties.Device = ""; @@ -2307,7 +2318,7 @@ void DiscordClient::SendIdentify() { msg.Properties.ReferrerCurrent = ""; msg.Properties.ReferringDomainCurrent = ""; msg.Properties.ReleaseChannel = "stable"; - msg.Properties.ClientBuildNumber = 105691; + msg.Properties.ClientBuildNumber = m_build_number; msg.Properties.ClientEventSource = ""; msg.Presence.Status = "online"; msg.Presence.Since = 0; @@ -2316,6 +2327,7 @@ void DiscordClient::SendIdentify() { msg.ClientState.HighestLastMessageID = "0"; msg.ClientState.ReadStateVersion = 0; msg.ClientState.UserGuildSettingsVersion = -1; + SetSuperPropertiesFromIdentity(msg); const bool b = m_websocket.GetPrintMessages(); m_websocket.SetPrintMessages(false); m_websocket.Send(msg); @@ -2330,6 +2342,36 @@ void DiscordClient::SendResume() { m_websocket.Send(msg); } +void DiscordClient::SetHeaders() { + m_http.SetPersistentHeader("Sec-Fetch-Dest", "empty"); + m_http.SetPersistentHeader("Sec-Fetch-Mode", "cors"); + m_http.SetPersistentHeader("Sec-Fetch-Site", "same-origin"); + m_http.SetPersistentHeader("X-Debug-Options", "bugReporterEnabled"); + m_http.SetPersistentHeader("Accept-Language", "en-US,en;q=0.9"); + + SetReferringChannel(Snowflake::Invalid); +} + +void DiscordClient::SetSuperPropertiesFromIdentity(const IdentifyMessage &identity) { + nlohmann::ordered_json j; + j["os"] = identity.Properties.OS; + j["browser"] = identity.Properties.Browser; + j["device"] = identity.Properties.Device; + j["system_locale"] = identity.Properties.SystemLocale; + j["browser_user_agent"] = identity.Properties.BrowserUserAgent; + j["browser_version"] = identity.Properties.BrowserVersion; + j["os_version"] = identity.Properties.OSVersion; + j["referrer"] = identity.Properties.Referrer; + j["referring_domain"] = identity.Properties.ReferringDomain; + j["referrer_current"] = identity.Properties.ReferrerCurrent; + j["referring_domain_current"] = identity.Properties.ReferringDomainCurrent; + j["release_channel"] = identity.Properties.ReleaseChannel; + j["client_build_number"] = identity.Properties.ClientBuildNumber; + j["client_event_source"] = nullptr; // probably will never be non-null ("") anyways + m_http.SetPersistentHeader("X-Super-Properties", Glib::Base64::encode(j.dump())); + m_http.SetPersistentHeader("X-Discord-Locale", identity.Properties.SystemLocale); +} + void DiscordClient::HandleSocketOpen() { } @@ -2395,9 +2437,11 @@ void DiscordClient::StoreMessageData(Message &msg) { if (msg.Member.has_value()) m_store.SetGuildMember(*msg.GuildID, msg.Author.ID, *msg.Member); - if (msg.Interaction.has_value() && msg.Interaction->Member.has_value()) { + if (msg.Interaction.has_value()) { m_store.SetUser(msg.Interaction->User.ID, msg.Interaction->User); - m_store.SetGuildMember(*msg.GuildID, msg.Interaction->User.ID, *msg.Interaction->Member); + if (msg.Interaction->Member.has_value()) { + m_store.SetGuildMember(*msg.GuildID, msg.Interaction->User.ID, *msg.Interaction->Member); + } } m_store.EndTransaction(); diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 718cb83..c2bea7d 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -113,13 +113,11 @@ public: void EditMessage(Snowflake channel_id, Snowflake id, std::string content); void SendLazyLoad(Snowflake id); void SendThreadLazyLoad(Snowflake id); - void JoinGuild(const std::string &code); void LeaveGuild(Snowflake id); void KickUser(Snowflake user_id, Snowflake guild_id); void BanUser(Snowflake user_id, Snowflake guild_id); // todo: reason, delete messages void UpdateStatus(PresenceStatus status, bool is_afk); void UpdateStatus(PresenceStatus status, bool is_afk, const ActivityData &obj); - void CreateDM(Snowflake user_id, const sigc::slot<void(DiscordError code, Snowflake channel_id)> &callback); void CloseDM(Snowflake channel_id); std::optional<Snowflake> FindDM(Snowflake user_id); // wont find group dms void AddReaction(Snowflake id, Glib::ustring param); @@ -205,6 +203,11 @@ public: void GetVerificationGateInfo(Snowflake guild_id, const sigc::slot<void(std::optional<VerificationGateInfoObject>)> &callback); void AcceptVerificationGate(Snowflake guild_id, VerificationGateInfoObject info, const sigc::slot<void(DiscordError code)> &callback); + void SetReferringChannel(Snowflake id); + + void SetBuildNumber(uint32_t build_number); + void SetCookie(std::string_view cookie); + void UpdateToken(const std::string &token); void SetUserAgent(const std::string &agent); @@ -286,6 +289,9 @@ private: void SendIdentify(); void SendResume(); + void SetHeaders(); + void SetSuperPropertiesFromIdentity(const IdentifyMessage &identity); + void HandleSocketOpen(); void HandleSocketClose(uint16_t code); @@ -299,6 +305,8 @@ private: std::string m_token; + uint32_t m_build_number = 142000; + void AddUserToGuild(Snowflake user_id, Snowflake guild_id); std::map<Snowflake, std::set<Snowflake>> m_guild_to_users; std::map<Snowflake, std::set<Snowflake>> m_guild_to_channels; diff --git a/src/discord/httpclient.cpp b/src/discord/httpclient.cpp index 6646bf3..37436ee 100644 --- a/src/discord/httpclient.cpp +++ b/src/discord/httpclient.cpp @@ -2,7 +2,6 @@ #include <utility> -//#define USE_LOCAL_PROXY HTTPClient::HTTPClient() { m_dispatcher.connect(sigc::mem_fun(*this, &HTTPClient::RunCallbacks)); } @@ -19,16 +18,22 @@ void HTTPClient::SetAuth(std::string auth) { m_authorization = std::move(auth); } +void HTTPClient::SetPersistentHeader(std::string name, std::string value) { + m_headers.insert_or_assign(std::move(name), std::move(value)); +} + +void HTTPClient::SetCookie(std::string_view cookie) { + m_cookie = cookie; +} + void HTTPClient::MakeDELETE(const std::string &path, const std::function<void(http::response_type r)> &cb) { printf("DELETE %s\n", path.c_str()); m_futures.push_back(std::async(std::launch::async, [this, path, cb] { http::request req(http::REQUEST_DELETE, m_api_base + path); + AddHeaders(req); req.set_header("Authorization", m_authorization); + req.set_header("Origin", "https://discord.com"); req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon"); -#ifdef USE_LOCAL_PROXY - req.set_proxy("http://127.0.0.1:8888"); - req.set_verify_ssl(false); -#endif auto res = req.execute(); @@ -40,14 +45,12 @@ void HTTPClient::MakePATCH(const std::string &path, const std::string &payload, printf("PATCH %s\n", path.c_str()); m_futures.push_back(std::async(std::launch::async, [this, path, cb, payload] { http::request req(http::REQUEST_PATCH, m_api_base + path); + AddHeaders(req); req.set_header("Authorization", m_authorization); req.set_header("Content-Type", "application/json"); + req.set_header("Origin", "https://discord.com"); req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon"); req.set_body(payload); -#ifdef USE_LOCAL_PROXY - req.set_proxy("http://127.0.0.1:8888"); - req.set_verify_ssl(false); -#endif auto res = req.execute(); @@ -59,14 +62,12 @@ void HTTPClient::MakePOST(const std::string &path, const std::string &payload, c printf("POST %s\n", path.c_str()); m_futures.push_back(std::async(std::launch::async, [this, path, cb, payload] { http::request req(http::REQUEST_POST, m_api_base + path); + AddHeaders(req); req.set_header("Authorization", m_authorization); req.set_header("Content-Type", "application/json"); + req.set_header("Origin", "https://discord.com"); req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon"); req.set_body(payload); -#ifdef USE_LOCAL_PROXY - req.set_proxy("http://127.0.0.1:8888"); - req.set_verify_ssl(false); -#endif auto res = req.execute(); @@ -78,15 +79,13 @@ void HTTPClient::MakePUT(const std::string &path, const std::string &payload, co printf("PUT %s\n", path.c_str()); m_futures.push_back(std::async(std::launch::async, [this, path, cb, payload] { http::request req(http::REQUEST_PUT, m_api_base + path); + AddHeaders(req); req.set_header("Authorization", m_authorization); + req.set_header("Origin", "https://discord.com"); if (!payload.empty()) req.set_header("Content-Type", "application/json"); req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon"); req.set_body(payload); -#ifdef USE_LOCAL_PROXY - req.set_proxy("http://127.0.0.1:8888"); - req.set_verify_ssl(false); -#endif auto res = req.execute(); @@ -98,13 +97,9 @@ void HTTPClient::MakeGET(const std::string &path, const std::function<void(http: printf("GET %s\n", path.c_str()); m_futures.push_back(std::async(std::launch::async, [this, path, cb] { http::request req(http::REQUEST_GET, m_api_base + path); + AddHeaders(req); req.set_header("Authorization", m_authorization); - req.set_header("Content-Type", "application/json"); req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon"); -#ifdef USE_LOCAL_PROXY - req.set_proxy("http://127.0.0.1:8888"); - req.set_verify_ssl(false); -#endif auto res = req.execute(); @@ -116,10 +111,6 @@ http::request HTTPClient::CreateRequest(http::EMethod method, std::string path) http::request req(method, m_api_base + path); req.set_header("Authorization", m_authorization); req.set_user_agent(!m_agent.empty() ? m_agent : "Abaddon"); -#ifdef USE_LOCAL_PROXY - req.set_proxy("http://127.0.0.1:8888"); - req.set_verify_ssl(false); -#endif return req; } @@ -147,6 +138,14 @@ void HTTPClient::RunCallbacks() { m_mutex.unlock(); } +void HTTPClient::AddHeaders(http::request &r) { + for (const auto &[name, val] : m_headers) { + r.set_header(name, val); + } + curl_easy_setopt(r.get_curl(), CURLOPT_COOKIE, m_cookie.c_str()); + curl_easy_setopt(r.get_curl(), CURLOPT_ACCEPT_ENCODING, ""); +} + void HTTPClient::OnResponse(const http::response_type &r, const std::function<void(http::response_type r)> &cb) { CleanupFutures(); try { diff --git a/src/discord/httpclient.hpp b/src/discord/httpclient.hpp index 366d4eb..e91a055 100644 --- a/src/discord/httpclient.hpp +++ b/src/discord/httpclient.hpp @@ -17,6 +17,9 @@ public: void SetUserAgent(std::string agent); void SetAuth(std::string auth); + void SetPersistentHeader(std::string name, std::string value); + void SetCookie(std::string_view cookie); + void MakeDELETE(const std::string &path, const std::function<void(http::response_type r)> &cb); void MakeGET(const std::string &path, const std::function<void(http::response_type r)> &cb); void MakePATCH(const std::string &path, const std::string &payload, const std::function<void(http::response_type r)> &cb); @@ -27,6 +30,8 @@ public: void Execute(http::request &&req, const std::function<void(http::response_type r)> &cb); private: + void AddHeaders(http::request &r); + void OnResponse(const http::response_type &r, const std::function<void(http::response_type r)> &cb); void CleanupFutures(); @@ -39,4 +44,6 @@ private: std::string m_api_base; std::string m_authorization; std::string m_agent; + std::unordered_map<std::string, std::string> m_headers; + std::string m_cookie; }; diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index f796000..e43e05a 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -262,6 +262,7 @@ void to_json(nlohmann::json &j, const ClientStateProperties &m) { j["highest_last_message_id"] = m.HighestLastMessageID; j["read_state_version"] = m.ReadStateVersion; j["user_guild_settings_version"] = m.UserGuildSettingsVersion; + j["user_settings_version"] = m.UserSettingsVersion; } void to_json(nlohmann::json &j, const IdentifyMessage &m) { diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index db5fd76..9db9369 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -382,6 +382,7 @@ struct ClientStateProperties { std::string HighestLastMessageID = "0"; int ReadStateVersion = 0; int UserGuildSettingsVersion = -1; + int UserSettingsVersion = -1; friend void to_json(nlohmann::json &j, const ClientStateProperties &m); }; diff --git a/src/discord/store.cpp b/src/discord/store.cpp index 663d113..892f4aa 100644 --- a/src/discord/store.cpp +++ b/src/discord/store.cpp @@ -254,6 +254,14 @@ void Store::SetGuildMember(Snowflake guild_id, Snowflake user_id, const GuildMem s->Reset(); { + auto &s = m_stmt_clr_member_roles; + s->Bind(1, user_id); + s->Bind(2, guild_id); + s->Step(); + s->Reset(); + } + + { auto &s = m_stmt_set_member_roles; BeginTransaction(); @@ -1882,6 +1890,20 @@ bool Store::CreateStatements() { return false; } + m_stmt_clr_member_roles = std::make_unique<Statement>(m_db, R"( + DELETE FROM member_roles + WHERE user = ? AND + EXISTS ( + SELECT 1 FROM roles + WHERE member_roles.role = roles.id + AND roles.guild = ? + ) + )"); + if (!m_stmt_clr_member_roles->OK()) { + fprintf(stderr, "failed to prepare clear member roles statement: %s\n", m_db.ErrStr()); + return false; + } + m_stmt_set_guild_emoji = std::make_unique<Statement>(m_db, R"( REPLACE INTO guild_emojis VALUES ( ?, ? diff --git a/src/discord/store.hpp b/src/discord/store.hpp index d863fa6..da97dd5 100644 --- a/src/discord/store.hpp +++ b/src/discord/store.hpp @@ -281,6 +281,7 @@ private: STMT(set_interaction); STMT(set_member_roles); STMT(get_member_roles); + STMT(clr_member_roles); STMT(set_guild_emoji); STMT(get_guild_emojis); STMT(clr_guild_emoji); diff --git a/src/discord/usersettings.cpp b/src/discord/usersettings.cpp index e4ab41a..8f57ee3 100644 --- a/src/discord/usersettings.cpp +++ b/src/discord/usersettings.cpp @@ -8,33 +8,5 @@ void from_json(const nlohmann::json &j, UserSettingsGuildFoldersEntry &m) { } void from_json(const nlohmann::json &j, UserSettings &m) { - JS_D("timezone_offset", m.TimezoneOffset); - JS_D("theme", m.Theme); - JS_D("stream_notifications_enabled", m.AreStreamNotificationsEnabled); - JS_D("status", m.Status); - JS_D("show_current_game", m.ShouldShowCurrentGame); - // JS_D("restricted_guilds", m.RestrictedGuilds); - JS_D("render_reactions", m.ShouldRenderReactions); - JS_D("render_embeds", m.ShouldRenderEmbeds); - JS_D("native_phone_integration_enabled", m.IsNativePhoneIntegrationEnabled); - JS_D("message_display_compact", m.ShouldMessageDisplayCompact); - JS_D("locale", m.Locale); - JS_D("inline_embed_media", m.ShouldInlineEmbedMedia); - JS_D("inline_attachment_media", m.ShouldInlineAttachmentMedia); - JS_D("guild_positions", m.GuildPositions); JS_D("guild_folders", m.GuildFolders); - JS_D("gif_auto_play", m.ShouldGIFAutoplay); - // JS_D("friend_source_flags", m.FriendSourceFlags); - JS_D("explicit_content_filter", m.ExplicitContentFilter); - JS_D("enable_tts_command", m.IsTTSCommandEnabled); - JS_D("disable_games_tab", m.ShouldDisableGamesTab); - JS_D("developer_mode", m.DeveloperMode); - JS_D("detect_platform_accounts", m.ShouldDetectPlatformAccounts); - JS_D("default_guilds_restricted", m.AreDefaultGuildsRestricted); - // JS_N("custom_status", m.CustomStatus); - JS_D("convert_emoticons", m.ShouldConvertEmoticons); - JS_D("contact_sync_enabled", m.IsContactSyncEnabled); - JS_D("animate_emoji", m.ShouldAnimateEmojis); - JS_D("allow_accessibility_detection", m.IsAccessibilityDetectionAllowed); - JS_D("afk_timeout", m.AFKTimeout); } diff --git a/src/discord/usersettings.hpp b/src/discord/usersettings.hpp index 2baf61e..2631c45 100644 --- a/src/discord/usersettings.hpp +++ b/src/discord/usersettings.hpp @@ -13,35 +13,36 @@ struct UserSettingsGuildFoldersEntry { }; struct UserSettings { - int TimezoneOffset; // - std::string Theme; // - bool AreStreamNotificationsEnabled; // - std::string Status; // - bool ShouldShowCurrentGame; // - // std::vector<Unknown> RestrictedGuilds; // - bool ShouldRenderReactions; // - bool ShouldRenderEmbeds; // - bool IsNativePhoneIntegrationEnabled; // - bool ShouldMessageDisplayCompact; // - std::string Locale; // - bool ShouldInlineEmbedMedia; // - bool ShouldInlineAttachmentMedia; // - std::vector<Snowflake> GuildPositions; // deprecated? - std::vector<UserSettingsGuildFoldersEntry> GuildFolders; // - bool ShouldGIFAutoplay; // - // Unknown FriendSourceFlags; // - int ExplicitContentFilter; // - bool IsTTSCommandEnabled; // - bool ShouldDisableGamesTab; // - bool DeveloperMode; // - bool ShouldDetectPlatformAccounts; // - bool AreDefaultGuildsRestricted; // + std::vector<UserSettingsGuildFoldersEntry> GuildFolders; + /* + int TimezoneOffset; + std::string Theme; + bool AreStreamNotificationsEnabled; + std::string Status; + bool ShouldShowCurrentGame; + // std::vector<Unknown> RestrictedGuilds; + bool ShouldRenderReactions; + bool ShouldRenderEmbeds; + bool IsNativePhoneIntegrationEnabled; + bool ShouldMessageDisplayCompact; + std::string Locale; + bool ShouldInlineEmbedMedia; + bool ShouldInlineAttachmentMedia; + std::vector<Snowflake> GuildPositions; // deprecated? + bool ShouldGIFAutoplay; + // Unknown FriendSourceFlags; + int ExplicitContentFilter; + bool IsTTSCommandEnabled; + bool ShouldDisableGamesTab; + bool DeveloperMode; + bool ShouldDetectPlatformAccounts; + bool AreDefaultGuildsRestricted; // Unknown CustomStatus; // null - bool ShouldConvertEmoticons; // - bool IsContactSyncEnabled; // - bool ShouldAnimateEmojis; // - bool IsAccessibilityDetectionAllowed; // - int AFKTimeout; + bool ShouldConvertEmoticons; + bool IsContactSyncEnabled; + bool ShouldAnimateEmojis; + bool IsAccessibilityDetectionAllowed; + int AFKTimeout;*/ friend void from_json(const nlohmann::json &j, UserSettings &m); }; diff --git a/src/emojis.cpp b/src/emojis.cpp index 25516d3..1952a7f 100644 --- a/src/emojis.cpp +++ b/src/emojis.cpp @@ -2,6 +2,22 @@ #include <sstream> #include <utility> +#ifdef ABADDON_IS_BIG_ENDIAN +/* Allows processing emojis.bin correctly on big-endian systems. */ +int emojis_int32_correct_endian(int little_endian_in) { + /* this does the same thing as __bswap_32() but can be done without + non-standard headers. */ + return ((little_endian_in >> 24) & 0xff) | // move byte 3 to byte 0 + ((little_endian_in << 8) & 0xff0000) | // move byte 1 to byte 2 + ((little_endian_in >> 8) & 0xff00) | // move byte 2 to byte 1 + ((little_endian_in << 24) & 0xff000000); // byte 0 to byte 3 +} +#else +int emojis_int32_correct_endian(int little_endian_in) { + return little_endian_in; +} +#endif + EmojiResource::EmojiResource(std::string filepath) : m_filepath(std::move(filepath)) {} @@ -11,18 +27,22 @@ bool EmojiResource::Load() { int index_offset; std::fread(&index_offset, 4, 1, m_fp); + index_offset = emojis_int32_correct_endian(index_offset); std::fseek(m_fp, index_offset, SEEK_SET); int emojis_count; std::fread(&emojis_count, 4, 1, m_fp); + emojis_count = emojis_int32_correct_endian(emojis_count); for (int i = 0; i < emojis_count; i++) { std::vector<std::string> shortcodes; int shortcodes_count; std::fread(&shortcodes_count, 4, 1, m_fp); + shortcodes_count = emojis_int32_correct_endian(shortcodes_count); for (int j = 0; j < shortcodes_count; j++) { int shortcode_length; std::fread(&shortcode_length, 4, 1, m_fp); + shortcode_length = emojis_int32_correct_endian(shortcode_length); std::string shortcode(shortcode_length, '\0'); std::fread(shortcode.data(), shortcode_length, 1, m_fp); shortcodes.push_back(std::move(shortcode)); @@ -30,13 +50,16 @@ bool EmojiResource::Load() { int surrogates_count; std::fread(&surrogates_count, 4, 1, m_fp); + surrogates_count = emojis_int32_correct_endian(surrogates_count); std::string surrogates(surrogates_count, '\0'); std::fread(surrogates.data(), surrogates_count, 1, m_fp); m_patterns.emplace_back(surrogates); int data_size, data_offset; std::fread(&data_size, 4, 1, m_fp); + data_size = emojis_int32_correct_endian(data_size); std::fread(&data_offset, 4, 1, m_fp); + data_offset = emojis_int32_correct_endian(data_offset); m_index[surrogates] = { data_offset, data_size }; for (const auto &shortcode : shortcodes) diff --git a/src/http.cpp b/src/http.cpp index ba3ce3c..f0bb85b 100644 --- a/src/http.cpp +++ b/src/http.cpp @@ -2,6 +2,8 @@ #include <utility> +// #define USE_LOCAL_PROXY + namespace http { request::request(EMethod method, std::string url) : m_url(std::move(url)) { @@ -34,7 +36,6 @@ request::request(request &&other) noexcept , m_url(std::exchange(other.m_url, "")) , m_method(std::exchange(other.m_method, nullptr)) , m_header_list(std::exchange(other.m_header_list, nullptr)) - , m_error_buf(other.m_error_buf) , m_form(std::exchange(other.m_form, nullptr)) , m_read_streams(std::move(other.m_read_streams)) , m_progress_callback(std::move(other.m_progress_callback)) { @@ -98,6 +99,10 @@ void request::set_user_agent(const std::string &data) { curl_easy_setopt(m_curl, CURLOPT_USERAGENT, data.c_str()); } +CURL *request::get_curl() { + return m_curl; +} + void request::make_form() { m_form = curl_mime_init(m_curl); } @@ -143,14 +148,16 @@ response request::execute() { detail::check_init(); std::string str; +#ifdef USE_LOCAL_PROXY + set_proxy("http://127.0.0.1:8888"); + set_verify_ssl(false); +#endif curl_easy_setopt(m_curl, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(m_curl, CURLOPT_CUSTOMREQUEST, m_method); curl_easy_setopt(m_curl, CURLOPT_URL, m_url.c_str()); curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, detail::curl_write_data_callback); curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &str); - curl_easy_setopt(m_curl, CURLOPT_ERRORBUFFER, m_error_buf); - m_error_buf[0] = '\0'; if (m_header_list != nullptr) curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, m_header_list); if (m_form != nullptr) @@ -160,7 +167,6 @@ response request::execute() { if (result != CURLE_OK) { auto response = detail::make_response(m_url, EStatusCode::ClientErrorCURLPerform); response.error_string = curl_easy_strerror(result); - response.error_string += " " + std::string(m_error_buf.data()); return response; } diff --git a/src/http.hpp b/src/http.hpp index 90a514a..63b6e38 100644 --- a/src/http.hpp +++ b/src/http.hpp @@ -122,6 +122,8 @@ struct request { response execute(); + CURL *get_curl(); + private: void prepare(); @@ -129,7 +131,6 @@ private: std::string m_url; const char *m_method; curl_slist *m_header_list = nullptr; - std::array<char, CURL_ERROR_SIZE> m_error_buf = { 0 }; curl_mime *m_form = nullptr; std::function<void(curl_off_t, curl_off_t)> m_progress_callback; diff --git a/src/settings.cpp b/src/settings.cpp index 6afc351..c1a2fd2 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -47,6 +47,7 @@ void SettingsManager::ReadSettings() { SMSTR("discord", "gateway", GatewayURL); SMBOOL("discord", "memory_db", UseMemoryDB); SMBOOL("discord", "prefetch", Prefetch); + SMBOOL("discord", "autoconnect", Autoconnect); SMSTR("gui", "css", MainCSS); SMBOOL("gui", "animated_guild_hover_only", AnimatedGuildHoverOnly); SMBOOL("gui", "animations", ShowAnimations); @@ -56,6 +57,8 @@ void SettingsManager::ReadSettings() { SMBOOL("gui", "save_state", SaveState); SMBOOL("gui", "stock_emojis", ShowStockEmojis); SMBOOL("gui", "unreads", Unreads); + SMBOOL("gui", "alt_menu", AltMenu); + SMBOOL("gui", "hide_to_tray", HideToTray); SMINT("http", "concurrent", CacheHTTPConcurrency); SMSTR("http", "user_agent", UserAgent); SMSTR("style", "expandercolor", ChannelsExpanderColor); @@ -125,6 +128,7 @@ void SettingsManager::Close() { SMSTR("discord", "gateway", GatewayURL); SMBOOL("discord", "memory_db", UseMemoryDB); SMBOOL("discord", "prefetch", Prefetch); + SMBOOL("discord", "autoconnect", Autoconnect); SMSTR("gui", "css", MainCSS); SMBOOL("gui", "animated_guild_hover_only", AnimatedGuildHoverOnly); SMBOOL("gui", "animations", ShowAnimations); @@ -134,6 +138,8 @@ void SettingsManager::Close() { SMBOOL("gui", "save_state", SaveState); SMBOOL("gui", "stock_emojis", ShowStockEmojis); SMBOOL("gui", "unreads", Unreads); + SMBOOL("gui", "alt_menu", AltMenu); + SMBOOL("gui", "hide_to_tray", HideToTray); SMINT("http", "concurrent", CacheHTTPConcurrency); SMSTR("http", "user_agent", UserAgent); SMSTR("style", "expandercolor", ChannelsExpanderColor); diff --git a/src/settings.hpp b/src/settings.hpp index 3c9aebb..9d32d2e 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -12,6 +12,7 @@ public: std::string DiscordToken; bool UseMemoryDB { false }; bool Prefetch { false }; + bool Autoconnect { false }; // [gui] std::string MainCSS { "main.css" }; @@ -27,6 +28,8 @@ public: bool ShowStockEmojis { true }; #endif bool Unreads { true }; + bool AltMenu { false }; + bool HideToTray { false }; // [http] int CacheHTTPConcurrency { 20 }; diff --git a/src/startup.cpp b/src/startup.cpp new file mode 100644 index 0000000..baedf76 --- /dev/null +++ b/src/startup.cpp @@ -0,0 +1,126 @@ +#include "startup.hpp" +#include "abaddon.hpp" +#include <future> +#include <memory> + +DiscordStartupDialog::DiscordStartupDialog(Gtk::Window &window) + : Gtk::MessageDialog(window, "", false, Gtk::MESSAGE_INFO, Gtk::BUTTONS_NONE, true) { + m_dispatcher.connect(sigc::mem_fun(*this, &DiscordStartupDialog::DispatchCallback)); + + property_text() = "Getting connection info..."; + + RunAsync(); +} + +std::optional<std::string> DiscordStartupDialog::GetCookie() const { + return m_cookie; +} + +std::optional<uint32_t> DiscordStartupDialog::GetBuildNumber() const { + return m_build_number; +} + +// good enough +std::optional<std::pair<std::string, std::string>> ParseCookie(const Glib::ustring &str) { + auto regex = Glib::Regex::create("\\t"); + const std::vector<Glib::ustring> split = regex->split(str); + if (split.size() < 7) return {}; + + return { { split[5], split[6] } }; +} + +std::optional<Glib::ustring> GetJavascriptFileFromAppPage(const Glib::ustring &contents) { + auto regex = Glib::Regex::create(R"(app-mount.*(/assets/[\w\d]*.js).*/assets/[\w\d]*.js)"); + Glib::MatchInfo match; + if (regex->match(contents, match)) { + return match.fetch(1); + } + + return {}; +} + +std::optional<uint32_t> GetBuildNumberFromJSURL(const Glib::ustring &url, const std::string &cookie) { + http::request req(http::REQUEST_GET, "https://discord.com" + url); + req.set_header("Accept-Language", "en-US,en;q=0.9"); + req.set_header("Sec-Fetch-Dest", "document"); + req.set_header("Sec-Fetch-Mode", "navigate"); + req.set_header("Sec-Fetch-Site", "none"); + req.set_header("Sec-Fetch-User", "?1"); + req.set_user_agent(Abaddon::Get().GetSettings().UserAgent); + + curl_easy_setopt(req.get_curl(), CURLOPT_COOKIE, cookie.c_str()); + + auto res = req.execute(); + if (res.error) return {}; + + auto regex = Glib::Regex::create(R"("buildNumber",null!==\(t="(\d+)\"\))"); + Glib::MatchInfo match; + if (regex->match(res.text, match)) { + const auto str_value = match.fetch(1); + try { + return std::stoul(str_value); + } catch (...) { return {}; } + } + + return {}; +} + +std::pair<std::optional<std::string>, std::string> GetCookieTask() { + http::request req(http::REQUEST_GET, "https://discord.com/app"); + req.set_header("Accept-Language", "en-US,en;q=0.9"); + req.set_header("Sec-Fetch-Dest", "document"); + req.set_header("Sec-Fetch-Mode", "navigate"); + req.set_header("Sec-Fetch-Site", "none"); + req.set_header("Sec-Fetch-User", "?1"); + req.set_user_agent(Abaddon::Get().GetSettings().UserAgent); + + curl_easy_setopt(req.get_curl(), CURLOPT_COOKIEFILE, ""); + + auto res = req.execute(); + if (res.error) return {}; + + curl_slist *slist; + if (curl_easy_getinfo(req.get_curl(), CURLINFO_COOKIELIST, &slist) != CURLE_OK) { + return {}; + } + + std::string dcfduid; + std::string sdcfduid; + + for (auto *cur = slist; cur != nullptr; cur = cur->next) { + const auto cookie = ParseCookie(cur->data); + if (cookie.has_value()) { + if (cookie->first == "__dcfduid") { + dcfduid = cookie->second; + } else if (cookie->first == "__sdcfduid") { + sdcfduid = cookie->second; + } + } + } + curl_slist_free_all(slist); + + if (!dcfduid.empty() && !sdcfduid.empty()) { + return { "__dcfduid=" + dcfduid + "; __sdcfduid=" + sdcfduid, res.text }; + } + + return {}; +} + +void DiscordStartupDialog::RunAsync() { + auto futptr = std::make_shared<std::future<void>>(); + *futptr = std::async(std::launch::async, [this, futptr] { + auto [opt_cookie, app_page] = GetCookieTask(); + m_cookie = opt_cookie; + if (opt_cookie.has_value()) { + auto js_url = GetJavascriptFileFromAppPage(app_page); + if (js_url.has_value()) { + m_build_number = GetBuildNumberFromJSURL(*js_url, *opt_cookie); + } + } + m_dispatcher.emit(); + }); +} + +void DiscordStartupDialog::DispatchCallback() { + response(Gtk::RESPONSE_OK); +} diff --git a/src/startup.hpp b/src/startup.hpp new file mode 100644 index 0000000..ba1459d --- /dev/null +++ b/src/startup.hpp @@ -0,0 +1,25 @@ +#pragma once +#include <glibmm/dispatcher.h> +#include <gtkmm/messagedialog.h> +#include <gtkmm/window.h> +#include <optional> + +// fetch cookies, build number async + +class DiscordStartupDialog : public Gtk::MessageDialog { +public: + DiscordStartupDialog(Gtk::Window &window); + + [[nodiscard]] std::optional<std::string> GetCookie() const; + [[nodiscard]] std::optional<uint32_t> GetBuildNumber() const; + +private: + void RunAsync(); + + void DispatchCallback(); + + Glib::Dispatcher m_dispatcher; + + std::optional<std::string> m_cookie; + std::optional<uint32_t> m_build_number; +}; diff --git a/src/windows/guildsettings/infopane.cpp b/src/windows/guildsettings/infopane.cpp index 578aaac..a27c1a8 100644 --- a/src/windows/guildsettings/infopane.cpp +++ b/src/windows/guildsettings/infopane.cpp @@ -56,10 +56,9 @@ GuildSettingsInfoPane::GuildSettingsInfoPane(Snowflake id) guild_icon_url = guild.GetIconURL("gif", "512"); else guild_icon_url = guild.GetIconURL("png", "512"); - m_guild_icon_ev.signal_button_press_event().connect([guild_icon_url](GdkEventButton *event) -> bool { - if (event->type == GDK_BUTTON_PRESS) - if (event->button == GDK_BUTTON_PRIMARY) - LaunchBrowser(guild_icon_url); + m_guild_icon_ev.signal_button_release_event().connect([guild_icon_url](GdkEventButton *event) -> bool { + if (event->type == GDK_BUTTON_RELEASE && event->button == GDK_BUTTON_PRIMARY) + LaunchBrowser(guild_icon_url); return false; }); diff --git a/src/windows/mainwindow.cpp b/src/windows/mainwindow.cpp index 17edfa3..8a85d49 100644 --- a/src/windows/mainwindow.cpp +++ b/src/windows/mainwindow.cpp @@ -158,6 +158,10 @@ void MainWindow::UpdateMenus() { OnViewSubmenuPopup(); } +void MainWindow::ToggleMenuVisibility() { + m_menu_bar.set_visible(!m_menu_bar.get_visible()); +} + #ifdef WITH_LIBHANDY void MainWindow::GoBack() { m_chat.GoBack(); @@ -195,7 +199,6 @@ void MainWindow::OnDiscordSubmenuPopup() { std::string token = Abaddon::Get().GetDiscordToken(); m_menu_discord_connect.set_sensitive(!token.empty() && !discord_active); m_menu_discord_disconnect.set_sensitive(discord_active); - m_menu_discord_join_guild.set_sensitive(discord_active); m_menu_discord_set_token.set_sensitive(!discord_active); m_menu_discord_set_status.set_sensitive(discord_active); } @@ -238,15 +241,12 @@ void MainWindow::SetupMenu() { m_menu_discord_disconnect.set_label("Disconnect"); m_menu_discord_disconnect.set_sensitive(false); m_menu_discord_set_token.set_label("Set Token"); - m_menu_discord_join_guild.set_label("Accept Invite"); - m_menu_discord_join_guild.set_sensitive(false); m_menu_discord_set_status.set_label("Set Status"); m_menu_discord_set_status.set_sensitive(false); m_menu_discord_add_recipient.set_label("Add user to DM"); m_menu_discord_sub.append(m_menu_discord_connect); m_menu_discord_sub.append(m_menu_discord_disconnect); m_menu_discord_sub.append(m_menu_discord_set_token); - m_menu_discord_sub.append(m_menu_discord_join_guild); m_menu_discord_sub.append(m_menu_discord_set_status); m_menu_discord_sub.append(m_menu_discord_add_recipient); m_menu_discord.set_submenu(m_menu_discord_sub); @@ -265,6 +265,12 @@ void MainWindow::SetupMenu() { m_menu_view_threads.set_label("Threads"); m_menu_view_mark_guild_as_read.set_label("Mark Server as Read"); m_menu_view_mark_guild_as_read.add_accelerator("activate", m_accels, GDK_KEY_Escape, Gdk::SHIFT_MASK, Gtk::ACCEL_VISIBLE); + m_menu_view_channels.set_label("Channels"); + m_menu_view_channels.add_accelerator("activate", m_accels, GDK_KEY_L, Gdk::CONTROL_MASK, Gtk::ACCEL_VISIBLE); + m_menu_view_channels.set_active(true); + m_menu_view_members.set_label("Members"); + m_menu_view_members.add_accelerator("activate", m_accels, GDK_KEY_M, Gdk::CONTROL_MASK, Gtk::ACCEL_VISIBLE); + m_menu_view_members.set_active(true); #ifdef WITH_LIBHANDY m_menu_view_go_back.set_label("Go Back"); m_menu_view_go_forward.set_label("Go Forward"); @@ -275,6 +281,8 @@ void MainWindow::SetupMenu() { m_menu_view_sub.append(m_menu_view_pins); m_menu_view_sub.append(m_menu_view_threads); m_menu_view_sub.append(m_menu_view_mark_guild_as_read); + m_menu_view_sub.append(m_menu_view_channels); + m_menu_view_sub.append(m_menu_view_members); #ifdef WITH_LIBHANDY m_menu_view_sub.append(m_menu_view_go_back); m_menu_view_sub.append(m_menu_view_go_forward); @@ -283,7 +291,25 @@ void MainWindow::SetupMenu() { m_menu_bar.append(m_menu_file); m_menu_bar.append(m_menu_discord); m_menu_bar.append(m_menu_view); - m_menu_bar.show_all(); + + if (Abaddon::Get().GetSettings().AltMenu) { + auto set_hide_cb = [this](Gtk::Menu &menu) { + for (auto *child : menu.get_children()) { + auto *item = dynamic_cast<Gtk::MenuItem *>(child); + if (item != nullptr) { + item->signal_activate().connect([this]() { + m_menu_bar.hide(); + }); + } + } + }; + set_hide_cb(m_menu_discord_sub); + set_hide_cb(m_menu_file_sub); + set_hide_cb(m_menu_view_sub); + m_menu_bar.show_all_children(); + } else { + m_menu_bar.show_all(); + } m_menu_discord_connect.signal_activate().connect([this] { m_signal_action_connect.emit(); @@ -297,10 +323,6 @@ void MainWindow::SetupMenu() { m_signal_action_set_token.emit(); }); - m_menu_discord_join_guild.signal_activate().connect([this] { - m_signal_action_join_guild.emit(); - }); - m_menu_file_reload_css.signal_activate().connect([this] { m_signal_action_reload_css.emit(); }); @@ -340,6 +362,14 @@ void MainWindow::SetupMenu() { } }); + m_menu_view_channels.signal_activate().connect([this]() { + m_channel_list.set_visible(m_menu_view_channels.get_active()); + }); + + m_menu_view_members.signal_activate().connect([this]() { + m_members.GetRoot()->set_visible(m_menu_view_members.get_active()); + }); + #ifdef WITH_LIBHANDY m_menu_view_go_back.signal_activate().connect([this] { GoBack(); @@ -383,10 +413,6 @@ MainWindow::type_signal_action_reload_css MainWindow::signal_action_reload_css() return m_signal_action_reload_css; } -MainWindow::type_signal_action_join_guild MainWindow::signal_action_join_guild() { - return m_signal_action_join_guild; -} - MainWindow::type_signal_action_set_status MainWindow::signal_action_set_status() { return m_signal_action_set_status; } @@ -401,4 +427,4 @@ MainWindow::type_signal_action_view_pins MainWindow::signal_action_view_pins() { MainWindow::type_signal_action_view_threads MainWindow::signal_action_view_threads() { return m_signal_action_view_threads; -} +}
\ No newline at end of file diff --git a/src/windows/mainwindow.hpp b/src/windows/mainwindow.hpp index b5b6fc1..78e0115 100644 --- a/src/windows/mainwindow.hpp +++ b/src/windows/mainwindow.hpp @@ -24,6 +24,7 @@ public: void UpdateChatReactionAdd(Snowflake id, const Glib::ustring ¶m); void UpdateChatReactionRemove(Snowflake id, const Glib::ustring ¶m); void UpdateMenus(); + void ToggleMenuVisibility(); #ifdef WITH_LIBHANDY void GoBack(); @@ -63,7 +64,6 @@ private: Gtk::MenuItem m_menu_discord_connect; Gtk::MenuItem m_menu_discord_disconnect; Gtk::MenuItem m_menu_discord_set_token; - Gtk::MenuItem m_menu_discord_join_guild; Gtk::MenuItem m_menu_discord_set_status; Gtk::MenuItem m_menu_discord_add_recipient; // move me somewhere else some day void OnDiscordSubmenuPopup(); @@ -79,10 +79,13 @@ private: Gtk::MenuItem m_menu_view_pins; Gtk::MenuItem m_menu_view_threads; Gtk::MenuItem m_menu_view_mark_guild_as_read; + Gtk::CheckMenuItem m_menu_view_channels; + Gtk::CheckMenuItem m_menu_view_members; #ifdef WITH_LIBHANDY Gtk::MenuItem m_menu_view_go_back; Gtk::MenuItem m_menu_view_go_forward; #endif + void OnViewSubmenuPopup(); public: @@ -90,7 +93,6 @@ public: typedef sigc::signal<void> type_signal_action_disconnect; typedef sigc::signal<void> type_signal_action_set_token; typedef sigc::signal<void> type_signal_action_reload_css; - typedef sigc::signal<void> type_signal_action_join_guild; typedef sigc::signal<void> type_signal_action_set_status; // this should probably be removed typedef sigc::signal<void, Snowflake> type_signal_action_add_recipient; // channel id @@ -101,7 +103,6 @@ public: type_signal_action_disconnect signal_action_disconnect(); type_signal_action_set_token signal_action_set_token(); type_signal_action_reload_css signal_action_reload_css(); - type_signal_action_join_guild signal_action_join_guild(); type_signal_action_set_status signal_action_set_status(); type_signal_action_add_recipient signal_action_add_recipient(); type_signal_action_view_pins signal_action_view_pins(); @@ -112,7 +113,6 @@ private: type_signal_action_disconnect m_signal_action_disconnect; type_signal_action_set_token m_signal_action_set_token; type_signal_action_reload_css m_signal_action_reload_css; - type_signal_action_join_guild m_signal_action_join_guild; type_signal_action_set_status m_signal_action_set_status; type_signal_action_add_recipient m_signal_action_add_recipient; type_signal_action_view_pins m_signal_action_view_pins; diff --git a/src/windows/profile/userinfopane.cpp b/src/windows/profile/userinfopane.cpp index a17dbff..b62da93 100644 --- a/src/windows/profile/userinfopane.cpp +++ b/src/windows/profile/userinfopane.cpp @@ -41,13 +41,13 @@ ConnectionItem::ConnectionItem(const ConnectionData &conn) m_box.add(m_name); if (!url.empty()) { auto cb = [url](GdkEventButton *event) -> bool { - if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) { + if (event->type == GDK_BUTTON_RELEASE && event->button == GDK_BUTTON_PRIMARY) { LaunchBrowser(url); return true; } return false; }; - signal_button_press_event().connect(sigc::track_obj(cb, *this)); + signal_button_release_event().connect(sigc::track_obj(cb, *this)); AddPointerCursor(*this); } m_overlay.add(m_box); diff --git a/src/windows/profilewindow.cpp b/src/windows/profilewindow.cpp index aff98c5..d73731d 100644 --- a/src/windows/profilewindow.cpp +++ b/src/windows/profilewindow.cpp @@ -34,8 +34,8 @@ ProfileWindow::ProfileWindow(Snowflake user_id) if (user.HasAvatar()) AddPointerCursor(m_avatar_ev); - m_avatar_ev.signal_button_press_event().connect([user](GdkEventButton *event) -> bool { - if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) { + m_avatar_ev.signal_button_release_event().connect([user](GdkEventButton *event) -> bool { + if (event->type == GDK_BUTTON_RELEASE && event->button == GDK_BUTTON_PRIMARY) { if (user.HasAnimatedAvatar()) LaunchBrowser(user.GetAvatarURL("gif", "512")); else |