mirror of
https://github.com/celisej567/abaddon.git
synced 2026-09-13 20:18:50 +03:00
render emojis
This commit is contained in:
@@ -132,6 +132,8 @@ add_executable(abaddon
|
||||
filecache.cpp
|
||||
imgmanager.hpp
|
||||
imgmanager.cpp
|
||||
emojis.hpp
|
||||
emojis.cpp
|
||||
components/channels.hpp
|
||||
components/channels.cpp
|
||||
components/chatmessage.hpp
|
||||
|
||||
12
abaddon.cpp
12
abaddon.cpp
@@ -14,7 +14,8 @@
|
||||
#endif
|
||||
|
||||
Abaddon::Abaddon()
|
||||
: m_settings("abaddon.ini") {
|
||||
: m_settings("abaddon.ini")
|
||||
, m_emojis("res/emojis.bin") {
|
||||
LoadFromSettings();
|
||||
|
||||
m_discord.signal_gateway_ready().connect(sigc::mem_fun(*this, &Abaddon::DiscordOnReady));
|
||||
@@ -88,6 +89,11 @@ int Abaddon::StartGTK() {
|
||||
dlg.run();
|
||||
}
|
||||
|
||||
if (!m_emojis.Load()) {
|
||||
Gtk::MessageDialog dlg(*m_main_window, "The emoji file couldn't be loaded!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.run();
|
||||
}
|
||||
|
||||
return m_gtk_app->run(*m_main_window);
|
||||
}
|
||||
|
||||
@@ -376,6 +382,10 @@ ImageManager &Abaddon::GetImageManager() {
|
||||
return m_img_mgr;
|
||||
}
|
||||
|
||||
EmojiResource &Abaddon::GetEmojis() {
|
||||
return m_emojis;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
Gtk::Main::init_gtkmm_internals(); // why???
|
||||
return Abaddon::Get().StartGTK();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "windows/mainwindow.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "imgmanager.hpp"
|
||||
#include "emojis.hpp"
|
||||
|
||||
#define APP_TITLE "Abaddon"
|
||||
|
||||
@@ -47,6 +48,7 @@ public:
|
||||
void ActionReloadCSS();
|
||||
|
||||
ImageManager &GetImageManager();
|
||||
EmojiResource &GetEmojis();
|
||||
|
||||
std::string GetDiscordToken() const;
|
||||
bool IsDiscordActive() const;
|
||||
@@ -77,6 +79,7 @@ private:
|
||||
std::unordered_set<Snowflake> m_channels_history_loading;
|
||||
|
||||
ImageManager m_img_mgr;
|
||||
EmojiResource m_emojis;
|
||||
|
||||
mutable std::mutex m_mutex;
|
||||
Glib::RefPtr<Gtk::Application> m_gtk_app;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "chatmessage.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
#include "../util.hpp"
|
||||
#include <unordered_map>
|
||||
|
||||
ChatMessageItemContainer::ChatMessageItemContainer() {
|
||||
m_main = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
|
||||
@@ -178,6 +179,7 @@ void ChatMessageItemContainer::UpdateTextComponent(Gtk::TextView *tv) {
|
||||
HandleUserMentions(tv);
|
||||
HandleLinks(tv);
|
||||
HandleChannelMentions(tv);
|
||||
HandleEmojis(tv);
|
||||
break;
|
||||
case MessageType::GUILD_MEMBER_JOIN:
|
||||
b->insert_markup(s, "<span color='#999999'><i>[user joined]</i></span>");
|
||||
@@ -367,6 +369,22 @@ void ChatMessageItemContainer::HandleImage(const AttachmentData &data, Gtk::Imag
|
||||
Glib::signal_idle().connect(sigc::bind(sigc::mem_fun(*this, &ChatMessageItemContainer::EmitImageLoad), url));
|
||||
}
|
||||
|
||||
Glib::ustring ChatMessageItemContainer::GetTextFiltered(const Glib::RefPtr<Gtk::TextBuffer> &buf) {
|
||||
Gtk::TextBuffer::iterator a, b;
|
||||
buf->get_bounds(a, b);
|
||||
auto slice = buf->get_slice(a, b, true);
|
||||
// make all the 0xFFFC chars fuck off because it breaks regex
|
||||
while (true) {
|
||||
int r = slice.find(u8"\uFFFC");
|
||||
if (r == Glib::ustring::npos) break;
|
||||
auto iter = buf->get_iter_at_offset(r);
|
||||
auto len = Glib::ustring(u8"\uFFFC").size();
|
||||
slice.erase(r, len);
|
||||
slice.insert(r, "\x80");
|
||||
}
|
||||
return slice;
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::HandleUserMentions(Gtk::TextView *tv) {
|
||||
constexpr static const auto mentions_regex = R"(<@!?(\d+)>)";
|
||||
|
||||
@@ -412,6 +430,97 @@ void ChatMessageItemContainer::HandleUserMentions(Gtk::TextView *tv) {
|
||||
}
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::HandleStockEmojis(Gtk::TextView *tv) {
|
||||
auto get_text = [&]() -> Glib::ustring {
|
||||
auto x = tv->get_buffer();
|
||||
Gtk::TextBuffer::iterator a, b;
|
||||
x->get_bounds(a, b);
|
||||
return x->get_slice(a, b, true);
|
||||
};
|
||||
|
||||
auto buf = tv->get_buffer();
|
||||
auto text = get_text();
|
||||
|
||||
auto &emojis = Abaddon::Get().GetEmojis();
|
||||
int searchpos;
|
||||
Glib::MatchInfo match;
|
||||
for (const auto &pattern : emojis.GetPatterns()) {
|
||||
searchpos = 0;
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||
while (true) {
|
||||
size_t r = text.find(pattern, searchpos);
|
||||
if (r == Glib::ustring::npos) break;
|
||||
if (!pixbuf) pixbuf = emojis.GetPixBuf(pattern);
|
||||
if (!pixbuf) break;
|
||||
searchpos = r + pattern.size();
|
||||
auto start_it = buf->get_iter_at_offset(r);
|
||||
auto end_it = buf->get_iter_at_offset(r + pattern.size());
|
||||
auto it = buf->erase(start_it, end_it);
|
||||
buf->insert_pixbuf(it, pixbuf->scale_simple(24, 24, Gdk::INTERP_BILINEAR));
|
||||
int alen = text.size();
|
||||
text = get_text();
|
||||
int blen = text.size();
|
||||
searchpos -= (alen - blen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::HandleCustomEmojis(Gtk::TextView *tv) {
|
||||
static auto rgx = Glib::Regex::create(R"(<a?:([\w\d_]+):(\d+)>)");
|
||||
|
||||
auto &img = Abaddon::Get().GetImageManager();
|
||||
|
||||
auto buf = tv->get_buffer();
|
||||
auto text = GetTextFiltered(buf);
|
||||
|
||||
Glib::MatchInfo match;
|
||||
int startpos = 0;
|
||||
while (rgx->match(text, startpos, match)) {
|
||||
int mstart, mend;
|
||||
if (!match.fetch_pos(0, mstart, mend)) break;
|
||||
auto start_it = buf->get_iter_at_offset(mstart);
|
||||
auto end_it = buf->get_iter_at_offset(mend);
|
||||
startpos = mend;
|
||||
auto pixbuf = img.GetFromURLIfCached(Emoji::URLFromID(match.fetch(2)));
|
||||
if (pixbuf) {
|
||||
auto it = buf->erase(start_it, end_it);
|
||||
int alen = text.size();
|
||||
text = GetTextFiltered(buf);
|
||||
int blen = text.size();
|
||||
startpos -= (alen - blen);
|
||||
buf->insert_pixbuf(it, pixbuf->scale_simple(24, 24, Gdk::INTERP_BILINEAR));
|
||||
} else {
|
||||
// clang-format off
|
||||
// can't erase before pixbuf is ready or else marks that are in the same pos get mixed up
|
||||
auto mark_start = buf->create_mark(start_it, false);
|
||||
end_it.backward_char();
|
||||
auto mark_end = buf->create_mark(end_it, false);
|
||||
img.LoadFromURL(Emoji::URLFromID(match.fetch(2)), sigc::track_obj([this, buf, mark_start, mark_end](Glib::RefPtr<Gdk::Pixbuf> pixbuf) {
|
||||
auto start_it = mark_start->get_iter();
|
||||
auto end_it = mark_end->get_iter();
|
||||
end_it.forward_char();
|
||||
buf->delete_mark(mark_start);
|
||||
buf->delete_mark(mark_end);
|
||||
auto it = buf->erase(start_it, end_it);
|
||||
buf->insert_pixbuf(it, pixbuf->scale_simple(24, 24, Gdk::INTERP_BILINEAR));
|
||||
}, tv));
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
int alen = text.size();
|
||||
text = GetTextFiltered(buf);
|
||||
int blen = text.size();
|
||||
}
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::HandleEmojis(Gtk::TextView *tv) {
|
||||
static bool emojis = Abaddon::Get().GetSettings().GetSettingString("gui", "emojis", "true") != "false";
|
||||
if (emojis) {
|
||||
HandleStockEmojis(tv);
|
||||
HandleCustomEmojis(tv);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatMessageItemContainer::HandleChannelMentions(Gtk::TextView *tv) {
|
||||
constexpr static const auto chan_regex = R"(<#(\d+)>)";
|
||||
|
||||
|
||||
@@ -26,7 +26,12 @@ protected:
|
||||
Gtk::Box *CreateAttachmentComponent(const AttachmentData &data); // non-image attachments
|
||||
void HandleImage(const AttachmentData &data, Gtk::Image *img, std::string url);
|
||||
|
||||
static Glib::ustring GetTextFiltered(const Glib::RefPtr<Gtk::TextBuffer> &buf);
|
||||
|
||||
void HandleUserMentions(Gtk::TextView *tv);
|
||||
void HandleStockEmojis(Gtk::TextView *tv);
|
||||
void HandleCustomEmojis(Gtk::TextView *tv);
|
||||
void HandleEmojis(Gtk::TextView *tv);
|
||||
|
||||
void HandleChannelMentions(Gtk::TextView *tv);
|
||||
bool OnClickChannel(GdkEventButton *ev);
|
||||
|
||||
78
emojis.cpp
Normal file
78
emojis.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#include "emojis.hpp"
|
||||
#include <sstream>
|
||||
|
||||
EmojiResource::EmojiResource(std::string filepath)
|
||||
: m_filepath(filepath) {}
|
||||
|
||||
bool EmojiResource::Load() {
|
||||
m_fp = std::fopen(m_filepath.c_str(), "rb");
|
||||
if (m_fp == nullptr) return false;
|
||||
int index_pos;
|
||||
std::fread(&index_pos, 4, 1, m_fp);
|
||||
std::fseek(m_fp, index_pos, SEEK_SET);
|
||||
int num_entries;
|
||||
std::fread(&num_entries, 4, 1, m_fp);
|
||||
for (int i = 0; i < num_entries; i++) {
|
||||
static int strsize, len, pos;
|
||||
std::fread(&strsize, 4, 1, m_fp);
|
||||
std::vector<char> str(strsize);
|
||||
std::fread(str.data(), strsize, 1, m_fp);
|
||||
std::fread(&len, 4, 1, m_fp);
|
||||
std::fread(&pos, 4, 1, m_fp);
|
||||
m_index[std::string(str.begin(), str.end())] = std::make_pair(pos, len);
|
||||
m_patterns.push_back(HexToPattern(Glib::ustring(str.begin(), str.end())));
|
||||
}
|
||||
std::sort(m_patterns.begin(), m_patterns.end(), [](const Glib::ustring &a, const Glib::ustring &b) {
|
||||
return a.size() > b.size();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gdk::Pixbuf> EmojiResource::GetPixBuf(const Glib::ustring &pattern) {
|
||||
Glib::ustring key = PatternToHex(pattern);
|
||||
const auto it = m_index.find(key);
|
||||
if (it == m_index.end()) return Glib::RefPtr<Gdk::Pixbuf>();
|
||||
const int pos = it->second.first;
|
||||
const int len = it->second.second;
|
||||
std::fseek(m_fp, pos, SEEK_SET);
|
||||
std::vector<uint8_t> data(len);
|
||||
std::fread(data.data(), len, 1, m_fp);
|
||||
auto loader = Gdk::PixbufLoader::create();
|
||||
loader->write(static_cast<const guint8 *>(data.data()), data.size());
|
||||
loader->close();
|
||||
return loader->get_pixbuf();
|
||||
}
|
||||
|
||||
Glib::ustring EmojiResource::PatternToHex(const Glib::ustring &pattern) {
|
||||
Glib::ustring ret;
|
||||
for (int i = 0; i < pattern.size(); i++) {
|
||||
auto c = pattern.at(i);
|
||||
ret += Glib::ustring::format(std::hex, c);
|
||||
ret += "-";
|
||||
}
|
||||
return ret.substr(0, ret.size() - 1);
|
||||
}
|
||||
|
||||
Glib::ustring EmojiResource::HexToPattern(Glib::ustring hex) {
|
||||
std::vector<Glib::ustring> parts;
|
||||
Glib::ustring::size_type pos = 0;
|
||||
Glib::ustring token;
|
||||
while ((pos = hex.find("-")) != Glib::ustring::npos) {
|
||||
token = hex.substr(0, pos);
|
||||
if (token.length() % 2 == 1)
|
||||
token = "0" + token;
|
||||
parts.push_back(token);
|
||||
hex.erase(0, pos + 1);
|
||||
}
|
||||
parts.push_back(hex);
|
||||
Glib::ustring ret;
|
||||
for (const auto &part : parts) {
|
||||
auto x = static_cast<gunichar>(std::stoul(part.c_str(), nullptr, 16));
|
||||
ret += x;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const std::vector<Glib::ustring> &EmojiResource::GetPatterns() const {
|
||||
return m_patterns;
|
||||
}
|
||||
24
emojis.hpp
Normal file
24
emojis.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <gtkmm.h>
|
||||
|
||||
// shoutout to gtk for only supporting .svg's sometimes
|
||||
|
||||
class EmojiResource {
|
||||
public:
|
||||
EmojiResource(std::string filepath);
|
||||
bool Load();
|
||||
Glib::RefPtr<Gdk::Pixbuf> GetPixBuf(const Glib::ustring &pattern);
|
||||
static Glib::ustring PatternToHex(const Glib::ustring &pattern);
|
||||
static Glib::ustring HexToPattern(Glib::ustring hex);
|
||||
const std::vector<Glib::ustring> &GetPatterns() const;
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, std::pair<int, int>> m_index; // pattern -> [pos, len]
|
||||
FILE *m_fp = nullptr;
|
||||
std::string m_filepath;
|
||||
std::vector<Glib::ustring> m_patterns;
|
||||
};
|
||||
BIN
res/emojis.bin
Normal file
BIN
res/emojis.bin
Normal file
Binary file not shown.
Reference in New Issue
Block a user