diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | .gitmodules | 6 | ||||
-rw-r--r-- | .kdev4/dchat.kdev4 | 2 | ||||
-rw-r--r-- | .vscode/settings.json | 63 | ||||
-rw-r--r-- | bridge/BridgeService.cpp | 41 | ||||
-rw-r--r-- | bridge/BridgeService.hpp | 41 | ||||
-rw-r--r-- | bridge/DiscordService.cpp | 43 | ||||
-rw-r--r-- | bridge/DiscordService.hpp | 28 | ||||
m--------- | depends/odhtdb | 0 | ||||
m--------- | depends/sibs-functional | 0 | ||||
-rw-r--r-- | include/Channel.hpp | 21 | ||||
-rw-r--r-- | include/Chatbar.hpp | 2 | ||||
-rw-r--r-- | include/MessageBoard.hpp | 3 | ||||
-rw-r--r-- | include/Rpc.hpp | 21 | ||||
-rw-r--r-- | include/Suggestions.hpp | 19 | ||||
-rw-r--r-- | include/User.hpp | 15 | ||||
-rw-r--r-- | project.conf | 2 | ||||
-rw-r--r-- | screenshot.png | bin | 72955 -> 124927 bytes | |||
-rw-r--r-- | src/Cache.cpp | 51 | ||||
-rw-r--r-- | src/Channel.cpp | 86 | ||||
-rw-r--r-- | src/ChannelTopPanel.cpp | 2 | ||||
-rw-r--r-- | src/Chatbar.cpp | 14 | ||||
-rw-r--r-- | src/MessageBoard.cpp | 51 | ||||
-rw-r--r-- | src/ResourceCache.cpp | 24 | ||||
-rw-r--r-- | src/Rpc.cpp | 39 | ||||
-rw-r--r-- | src/Suggestions.cpp | 54 | ||||
-rw-r--r-- | src/Text.cpp | 55 | ||||
-rw-r--r-- | src/User.cpp | 25 | ||||
-rw-r--r-- | src/UsersSidePanel.cpp | 126 | ||||
-rw-r--r-- | src/main.cpp | 361 |
30 files changed, 1057 insertions, 140 deletions
@@ -1,3 +1,5 @@ sibs-build/ dchat.kdev4/ .gdb_history +compile_commands.json +.vscode/ diff --git a/.gitmodules b/.gitmodules index 8fd227f..76cb6bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "depends/odhtdb"] path = depends/odhtdb url = https://gitlab.com/DEC05EBA/odhtdb.git +[submodule "depends/sibs-pubsub"] + path = depends/sibs-pubsub + url = https://gitlab.com/DEC05EBA/sibs-pubsub +[submodule "depends/sibs-functional"] + path = depends/sibs-functional + url = https://gitlab.com/DEC05EBA/sibs-functional.git diff --git a/.kdev4/dchat.kdev4 b/.kdev4/dchat.kdev4 index b45716a..4150483 100644 --- a/.kdev4/dchat.kdev4 +++ b/.kdev4/dchat.kdev4 @@ -17,6 +17,8 @@ Name=Clang 4=/home/dec05eba/.cache/sibs/lib/ntpclient/0.2.1/include 5=/home/dec05eba/.cache/sibs/lib/fmt/4.1.0 6=/home/dec05eba/.cache/sibs/lib/libpreview/0.2.0/include +7=/home/dec05eba/git/dchat/depends/odhtdb/depends/sibs-pubsub/include +8=/home/dec05eba/.cache/sibs/lib/udt/4.11/include [Project] VersionControlSupport=kdevgit diff --git a/.vscode/settings.json b/.vscode/settings.json index 7a10503..34ba634 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,65 @@ "hash_map": "cpp", "hash_set": "cpp", "*.tcc": "cpp", - "string_view": "cpp" - } + "string_view": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "__config": "cpp", + "__nullptr": "cpp", + "array": "cpp", + "atomic": "cpp", + "strstream": "cpp", + "bitset": "cpp", + "chrono": "cpp", + "cinttypes": "cpp", + "codecvt": "cpp", + "complex": "cpp", + "condition_variable": "cpp", + "cstdint": "cpp", + "deque": "cpp", + "forward_list": "cpp", + "list": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "fstream": "cpp", + "functional": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "numeric": "cpp", + "optional": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "system_error": "cpp", + "thread": "cpp", + "type_traits": "cpp", + "tuple": "cpp", + "utility": "cpp", + "variant": "cpp" + }, + "cquery.cacheDirectory": "${workspaceFolder}/.vscode/cquery_cached_index/" }
\ No newline at end of file diff --git a/bridge/BridgeService.cpp b/bridge/BridgeService.cpp new file mode 100644 index 0000000..0b52643 --- /dev/null +++ b/bridge/BridgeService.cpp @@ -0,0 +1,41 @@ +#include "BridgeService.hpp" + +namespace dchat +{ + BridgeServiceUser::BridgeServiceUser(Type _type, const std::string &_name) : + User(User::Type::OTHER), + type(_type), + name(_name) + { + + } + + BridgeService::BridgeService() + { + + } + + BridgeService::~BridgeService() + { + for(User *user : users) + { + delete user; + } + } + + bool BridgeService::addUser(BridgeServiceUser *user) + { + for(User *existingUser : users) + { + if(static_cast<BridgeServiceUser*>(existingUser)->equals(user)) + return false; + } + users.push_back(user); + return true; + } + + const std::vector<User*>& BridgeService::getUsers() const + { + return users; + } +}
\ No newline at end of file diff --git a/bridge/BridgeService.hpp b/bridge/BridgeService.hpp new file mode 100644 index 0000000..e536c81 --- /dev/null +++ b/bridge/BridgeService.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include <vector> +#include <string> +#include "../include/User.hpp" + +namespace dchat +{ + class BridgeServiceUser : public User + { + public: + enum class Type + { + DISCORD + }; + + BridgeServiceUser(Type type, const std::string &name); + virtual ~BridgeServiceUser(){} + + virtual const std::string& getName() const override { return name; } + virtual bool isConnected(i64 timestampUtcSec) const override { return true; } + + virtual bool equals(BridgeServiceUser *other) const = 0; + + const Type type; + private: + std::string name; + }; + + class BridgeService + { + public: + BridgeService(); + virtual ~BridgeService(); + + bool addUser(BridgeServiceUser *user); + const std::vector<User*>& getUsers() const; + protected: + std::vector<User*> users; + }; +}
\ No newline at end of file diff --git a/bridge/DiscordService.cpp b/bridge/DiscordService.cpp new file mode 100644 index 0000000..a4546a8 --- /dev/null +++ b/bridge/DiscordService.cpp @@ -0,0 +1,43 @@ +#include "DiscordService.hpp" + +namespace dchat +{ + DiscordServiceUser::DiscordServiceUser(const std::string &name, const u64 _id, bool _connected) : + BridgeServiceUser(Type::DISCORD, name), + id(_id), + connected(_connected) + { + + } + + bool DiscordServiceUser::equals(BridgeServiceUser *other) const + { + return other && type == other->type && id == static_cast<DiscordServiceUser*>(other)->id; + } + + bool DiscordService::removeUser(u64 discordUserId) + { + for(auto it = users.begin(), end = users.end(); it != end; ++it) + { + BridgeServiceUser *serviceUser = static_cast<BridgeServiceUser*>(*it); + if(serviceUser->type == BridgeServiceUser::Type::DISCORD && static_cast<DiscordServiceUser*>(serviceUser)->id == discordUserId) + { + delete serviceUser; + users.erase(it); + return true; + } + } + return false; + } + + DiscordServiceUser* DiscordService::getUserById(u64 discordUserId) + { + for(User *user : users) + { + BridgeServiceUser *serviceUser = static_cast<BridgeServiceUser*>(user); + if(serviceUser->type == BridgeServiceUser::Type::DISCORD && static_cast<DiscordServiceUser*>(serviceUser)->id == discordUserId) + return static_cast<DiscordServiceUser*>(serviceUser); + } + return nullptr; + } +}
\ No newline at end of file diff --git a/bridge/DiscordService.hpp b/bridge/DiscordService.hpp new file mode 100644 index 0000000..c778c41 --- /dev/null +++ b/bridge/DiscordService.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "BridgeService.hpp" + +namespace dchat +{ + class DiscordServiceUser : public BridgeServiceUser + { + public: + DiscordServiceUser(const std::string &name, const u64 id, bool connected); + virtual ~DiscordServiceUser(){} + + virtual bool isConnected(i64 timestampUtcSec) const override { return connected; } + bool equals(BridgeServiceUser *other) const override; + + const u64 id; + bool connected; + }; + + class DiscordService : public BridgeService + { + public: + virtual ~DiscordService(){} + + bool removeUser(u64 discordUserId); + DiscordServiceUser* getUserById(u64 discordUserId); + }; +}
\ No newline at end of file diff --git a/depends/odhtdb b/depends/odhtdb -Subproject 92217f9654284d2b3db4c4e17c154658a212545 +Subproject 6ac05b4598f4b9133384bb586b50a9c1b275379 diff --git a/depends/sibs-functional b/depends/sibs-functional new file mode 160000 +Subproject d03d8c260fbae29f6f87d459670a25130beb2b4 diff --git a/include/Channel.hpp b/include/Channel.hpp index af6cbf2..663b163 100644 --- a/include/Channel.hpp +++ b/include/Channel.hpp @@ -5,7 +5,10 @@ #include "User.hpp" #include "Channel.hpp" #include "types.hpp" +#include "Suggestions.hpp" +#include "../bridge/DiscordService.hpp" #include <vector> +#include <unordered_map> #include <SFML/System/Clock.hpp> #include <odhtdb/DatabaseNode.hpp> #include <odhtdb/Signature.hpp> @@ -28,7 +31,9 @@ namespace dchat DELETE_MESSAGE, NICKNAME_CHANGE, CHANGE_AVATAR, - CHANGE_CHANNEL_NAME + CHANGE_CHANNEL_NAME, + + ADD_DISCORD_MESSAGE }; class Channel @@ -44,7 +49,7 @@ namespace dchat MessageBoard& getMessageBoard(); const std::string& getName() const; - const std::vector<User*> getUsers() const; + const std::vector<User*>& getUsers() const; OnlineUser* getUserByPublicKey(const odhtdb::Signature::PublicKey &publicKey); const odhtdb::DatabaseNode& getNodeInfo() const; Message* getLatestMessage(); @@ -52,8 +57,10 @@ namespace dchat // If timestamp is 0, then current time is used void addLocalMessage(const std::string &msg, User *owner, u64 timestampSeconds = 0); void addLocalMessage(const std::string &msg, User *owner, u64 timestampSeconds, const odhtdb::Hash &id); + void addLocalDiscordMessage(const std::string &discordUserName, u64 discordUserId, const std::string &msg, User *owner, u64 timestampSeconds, const odhtdb::Hash &id); void addSystemMessage(const std::string &msg, bool plainText = true); void addMessage(const std::string &msg); + void addDiscordMessage(const std::string &discordUserName, u64 discordUserId, const std::string &msg); void deleteLocalMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser); void deleteMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser); @@ -64,6 +71,9 @@ namespace dchat void setAvatar(const std::string &newAvatarUrl); void setNameLocally(const std::string &name); void setName(const std::string &name); + + // Returns -1 on failure + int getUserLowestPermissionLevel(OnlineUser *user) const; void processEvent(const sf::Event &event, Cache &cache); void draw(sf::RenderWindow &window, Cache &cache); @@ -71,6 +81,9 @@ namespace dchat void update(); // Returns 0 if we are offline u32 getSyncedTimestampUtcInSec(); + + const std::vector<BridgeService*>& getBridgeServices() const; + DiscordService* getDiscordService(); static void setCurrent(Channel *channel); static Channel* getCurrent(); @@ -82,12 +95,16 @@ namespace dchat std::string name; MessageBoard messageBoard; Chatbar chatbar; + Suggestions suggestions; User *localUser; SystemUser systemUser; std::vector<User*> users; + std::unordered_map<u64, OnlineDiscordUser*> discordUserById; odhtdb::Signature::MapPublicKey<OnlineUser*> publicKeyOnlineUsersMap; dht::InfoHash pingKey; std::future<size_t> pingListener; sf::Clock pingTimer; + + std::vector<BridgeService*> bridgeServices; }; } diff --git a/include/Chatbar.hpp b/include/Chatbar.hpp index df492ac..10bbebd 100644 --- a/include/Chatbar.hpp +++ b/include/Chatbar.hpp @@ -24,6 +24,8 @@ namespace dchat void processEvent(const sf::Event &event, Cache &cache, Channel *channel); void draw(sf::RenderWindow &window, Cache &cache); + static sf::Vector2f getInputPosition(sf::RenderWindow &window); + static sf::Vector2f getInputSize(sf::RenderWindow &window); static float getHeight(); static bool addBind(const std::string &key, const std::string &value, bool updateFile = true); diff --git a/include/MessageBoard.hpp b/include/MessageBoard.hpp index fdde8c7..1d1f523 100644 --- a/include/MessageBoard.hpp +++ b/include/MessageBoard.hpp @@ -27,11 +27,12 @@ namespace dchat void draw(sf::RenderWindow &window, Cache &cache); Message* getLatestMessage(); + const std::vector<Message*>& getMessages() const; private: usize findPositionToInsertMessageByTimestamp(Message *message); void updateStaticContentTexture(const sf::Vector2u &newSize); - void addMessage(Message *message, const odhtdb::Hash &id); + bool addMessage(Message *message, const odhtdb::Hash &id); void deleteMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser); void drawDefault(sf::RenderWindow &window, Cache &cache); diff --git a/include/Rpc.hpp b/include/Rpc.hpp new file mode 100644 index 0000000..c8b47e0 --- /dev/null +++ b/include/Rpc.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include <zmq.hpp> +#include "types.hpp" +#include <functional> + +namespace dchat +{ + using RpcRecvCallbackFunc = std::function<void(zmq::message_t*)>; + + class Rpc + { + public: + Rpc(u16 port); + void recv(RpcRecvCallbackFunc recvCallbackFunc); + bool send(const void *data, const usize size); + private: + zmq::context_t context; + zmq::socket_t socket; + }; +}
\ No newline at end of file diff --git a/include/Suggestions.hpp b/include/Suggestions.hpp new file mode 100644 index 0000000..56f3afa --- /dev/null +++ b/include/Suggestions.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "Text.hpp" +#include "Cache.hpp" +#include <vector> +#include <string> +#include <SFML/Graphics/RenderWindow.hpp> + +namespace dchat +{ + class Suggestions + { + public: + void show(const std::vector<std::string> &texts); + void draw(sf::RenderWindow &window, Cache &cache); + private: + std::vector<std::unique_ptr<Text>> texts; + }; +}
\ No newline at end of file diff --git a/include/User.hpp b/include/User.hpp index c2874c2..7e99c60 100644 --- a/include/User.hpp +++ b/include/User.hpp @@ -11,8 +11,10 @@ namespace dchat public: enum class Type { + OTHER, ONLINE_REMOTE_USER, ONLINE_LOCAL_USER, + ONLINE_DISCORD_USER, OFFLINE, SYSTEM }; @@ -21,6 +23,8 @@ namespace dchat virtual ~User(){} virtual const std::string& getName() const = 0; virtual bool isOnlineUser() const { return false; } + + virtual bool isConnected(i64 timestampUtcSec) const { return true; } const Type type; std::string avatarUrl; @@ -35,6 +39,7 @@ namespace dchat virtual const std::string& getName() const override; virtual const odhtdb::Signature::PublicKey& getPublicKey() const = 0; + bool isConnected(i64 timestampUtcSec) const override; bool isOnlineUser() const override { return true; } std::string name; @@ -58,6 +63,16 @@ namespace dchat const odhtdb::Signature::KeyPair keyPair; }; + + class OnlineDiscordUser : public OnlineUser + { + public: + OnlineDiscordUser(const std::string &discordUserName, u64 discordUserId, User *bridgeOwner); + virtual const odhtdb::Signature::PublicKey& getPublicKey() const override; + + u64 discordUserId; + User *bridgeOwner; + }; class OfflineUser : public User { diff --git a/project.conf b/project.conf index 09891f7..ebb949f 100644 --- a/project.conf +++ b/project.conf @@ -15,3 +15,5 @@ gl = "17.3" x11 = "1.6.5" libnsgif = "0.2.0" libpreview = "0.2.0" +libzmq = "4.2" +libgd = "2.2.5"
\ No newline at end of file diff --git a/screenshot.png b/screenshot.png Binary files differindex 8bb3994..be04d50 100644 --- a/screenshot.png +++ b/screenshot.png diff --git a/src/Cache.cpp b/src/Cache.cpp index 2a09591..77ba515 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -14,6 +14,7 @@ #include <sibs/SafeSerializer.hpp> #include <sibs/SafeDeserializer.hpp> #include <libpreview.h> +#include <gd.h> #if OS_FAMILY == OS_FAMILY_POSIX #include <pwd.h> @@ -119,8 +120,42 @@ namespace dchat } fileReplace(getDchatDir() / "binds", StringView((const char*)serializer.getBuffer().data(), serializer.getBuffer().size())); } + + static bool downscaleImage(const boost::filesystem::path &filepath, void *data, const int size, const int newWidth, const int newHeight) + { + gdImagePtr imgPtr = gdImageCreateFromPngPtr(size, data); + if(!imgPtr) + return false; + + int width = gdImageSX(imgPtr); + if(width < newWidth) + { + gdImageDestroy(imgPtr); + return false; + } + + int height = gdImageSX(imgPtr); + if(height < newHeight) + { + gdImageDestroy(imgPtr); + return false; + } + + gdImageSetInterpolationMethod(imgPtr, GD_BILINEAR_FIXED); + gdImagePtr newImgPtr = gdImageScale(imgPtr, newWidth, newHeight); + if(!newImgPtr) + { + gdImageDestroy(imgPtr); + return false; + } + + bool success = (gdImageFile(newImgPtr, filepath.c_str()) == 0); + gdImageDestroy(imgPtr); + gdImageDestroy(newImgPtr); + return success; + } - static ContentByUrlResult loadImageFromFile(const boost::filesystem::path &filepath) + static ContentByUrlResult loadImageFromFile(const boost::filesystem::path &filepath, bool loadFromCache) { StringView fileContent; try @@ -147,8 +182,16 @@ namespace dchat } else { + if(!loadFromCache) + { + if(!downscaleImage(filepath, (void*)fileContent.data, fileContent.size, 100, 100)) + { + fprintf(stderr, "Failed to resize image: %s, using original file\n", filepath.c_str()); + } + } + sf::Texture *texture = new sf::Texture(); - if(texture->loadFromMemory(fileContent.data, fileContent.size)) + if(texture->loadFromFile(filepath.c_str())) { delete[] fileContent.data; fileContent.data = nullptr; @@ -213,7 +256,7 @@ namespace dchat } else { - contentByUrlResult = loadImageFromFile(filepath); + contentByUrlResult = loadImageFromFile(filepath, false); contentByUrlResult.lastAccessed = chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count(); if(contentByUrlResult.type == ContentByUrlResult::Type::CACHED) { @@ -350,7 +393,7 @@ namespace dchat } // TODO: Do not load content in this thread. Return LOADING status and load it in another thread, because with a lot of images, chat can freeze - ContentByUrlResult contentByUrlResult = loadImageFromFile(filepath); + ContentByUrlResult contentByUrlResult = loadImageFromFile(filepath, true); if(contentByUrlResult.type == ContentByUrlResult::Type::CACHED) { contentByUrlResult.lastAccessed = chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now().time_since_epoch()).count(); diff --git a/src/Channel.cpp b/src/Channel.cpp index 75d805c..d13c476 100644 --- a/src/Channel.cpp +++ b/src/Channel.cpp @@ -17,12 +17,25 @@ namespace dchat messageBoard(this), localUser(_localUser ? _localUser : new OfflineUser("You")) { + bridgeServices.push_back(new DiscordService()); addUserLocally(localUser); //addLocalMessage(u8"[emoji](https://discordemoji.com/assets/emoji/PepeDab.gif) deaf [emoji](https://discordemoji.com/assets/emoji/COGGERS.gif)", &systemUser, 0, odhtdb::Hash()); //addLocalMessage(u8"[emoji](https://discordemoji.com/assets/emoji/PepeDab.gif)[emoji](https://discordemoji.com/assets/emoji/COGGERS.gif)", &systemUser, 0, odhtdb::Hash()); //addLocalMessage(u8"pepedab https://discordemoji.com/assets/emoji/PepeDab.gif coggers https://discordemoji.com/assets/emoji/COGGERS.gif check out this url http://www.grandtournation.com/6808/start-date-of-the-grand-tour-season-3-confirmed-mark-your-calendars/ owo", &systemUser, 0, odhtdb::Hash()); - // addLocalMessage(u8"ht clic", &systemUser, 0, odhtdb::Hash()); + //addLocalMessage(u8"ht clic", &systemUser, 0, odhtdb::Hash()); + + auto binds = Chatbar::getBinds(); + vector<string> suggestionsStr; + suggestionsStr.reserve(binds.size()); + for(auto &bind : binds) + { + string suggestion = bind.first; + suggestion += " "; + suggestion += bind.second; + suggestionsStr.emplace_back(move(suggestion)); + } + suggestions.show(suggestionsStr); if(database) { @@ -67,6 +80,11 @@ namespace dchat Channel::~Channel() { + for(BridgeService *bridgeService : bridgeServices) + { + delete bridgeService; + } + if(database) { database->cancelNodeListener(pingKey, pingListener); @@ -78,6 +96,11 @@ namespace dchat { delete user; } + + for(auto &discordUserIt : discordUserById) + { + delete discordUserIt.second; + } } User* Channel::getLocalUser() @@ -100,7 +123,7 @@ namespace dchat return name; } - const vector<User*> Channel::getUsers() const + const vector<User*>& Channel::getUsers() const { return users; } @@ -137,6 +160,31 @@ namespace dchat } messageBoard.addMessage(new Message(owner, msg, timestampSeconds), id); } + + void Channel::addLocalDiscordMessage(const string &discordUserName, u64 discordUserId, const string &msg, User *owner, u64 timestampSeconds, const odhtdb::Hash &id) + { + assert(owner); + if(timestampSeconds == 0) + { + timestampSeconds = time(NULL); + } + + OnlineDiscordUser *discordUser = nullptr; + auto discordUserIt = discordUserById.find(discordUserId); + if(discordUserIt == discordUserById.end()) + { + discordUser = new OnlineDiscordUser(discordUserName, discordUserId, owner); + discordUserById[discordUserId] = discordUser; + } + else + { + // TODO: What if several users bridge same chat? the same discord user id could belong to different owners. + // Dchat channels should only allow one user to bridge data from one discord channel. Bridging data between multiple discord channels to + // one dchat channel should be allowed. + discordUser = discordUserIt->second; + } + messageBoard.addMessage(new Message(discordUser, msg, timestampSeconds), id); + } void Channel::addSystemMessage(const string &msg, bool plainText) { @@ -159,6 +207,21 @@ namespace dchat else addLocalMessage(msg, localUser, 0, odhtdb::Hash()); } + + void Channel::addDiscordMessage(const string &discordUserName, u64 discordUserId, const string &msg) + { + assert(database && localUser->type == User::Type::ONLINE_LOCAL_USER); + auto onlineLocalUser = static_cast<OnlineLocalUser*>(localUser); + + sibs::SafeSerializer serializer; + serializer.add(ChannelDataType::ADD_DISCORD_MESSAGE); + serializer.add(discordUserId); + serializer.add((u8)discordUserName.size()); + serializer.add((const u8*)discordUserName.data(), discordUserName.size()); + serializer.add((const u8*)msg.data(), msg.size()); + + database->addData(databaseNodeInfo, onlineLocalUser->keyPair, odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size())); + } void Channel::deleteLocalMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser) { @@ -282,6 +345,12 @@ namespace dchat database->addData(databaseNodeInfo, onlineLocalUser->keyPair, odhtdb::DataView(serializer.getBuffer().data(), serializer.getBuffer().size())); } } + + int Channel::getUserLowestPermissionLevel(OnlineUser *user) const + { + if(!database) return -1; + return database->getUserLowestPermissionLevel(*databaseNodeInfo.getRequestHash(), user->getPublicKey()); + } void Channel::processEvent(const sf::Event &event, Cache &cache) { @@ -293,6 +362,7 @@ namespace dchat { messageBoard.draw(window, cache); chatbar.draw(window, cache); + //suggestions.draw(window, cache); } void Channel::update() @@ -300,7 +370,7 @@ namespace dchat if(database && localUser->type == User::Type::ONLINE_LOCAL_USER && pingTimer.getElapsedTime().asMilliseconds() > 5000) { pingTimer.restart(); - sendPing(database->getSyncedTimestampUtc().seconds); + //sendPing(database->getSyncedTimestampUtc().seconds); } } @@ -327,6 +397,16 @@ namespace dchat return 0; return database->getSyncedTimestampUtc().seconds; } + + const vector<BridgeService*>& Channel::getBridgeServices() const + { + return bridgeServices; + } + + DiscordService* Channel::getDiscordService() + { + return (DiscordService*)bridgeServices[0]; + } void Channel::setCurrent(Channel *channel) { diff --git a/src/ChannelTopPanel.cpp b/src/ChannelTopPanel.cpp index bf122d2..9d4869e 100644 --- a/src/ChannelTopPanel.cpp +++ b/src/ChannelTopPanel.cpp @@ -45,7 +45,7 @@ namespace dchat const sf::Color lineCenterColor = lineSideColor + sf::Color(40, 0, 0); auto windowSize = window.getSize(); - sf::RectangleShape rect(sf::Vector2f(floor(windowSize.x - ChannelSidePanel::getWidth() - UsersSidePanel::getWidth()), getHeight() - BOTTOM_LINE_HEIGHT)); + sf::RectangleShape rect(sf::Vector2f(floor(windowSize.x - ChannelSidePanel::getWidth()), getHeight() - BOTTOM_LINE_HEIGHT)); rect.setPosition(ChannelSidePanel::getWidth(), 0.0f); rect.setFillColor(ColorScheme::getBackgroundColor()); window.draw(rect); diff --git a/src/Chatbar.cpp b/src/Chatbar.cpp index bb06308..a3e1d0f 100644 --- a/src/Chatbar.cpp +++ b/src/Chatbar.cpp @@ -217,6 +217,20 @@ namespace dchat window.draw(inputBackground); text.draw(window, cache); } + + sf::Vector2f Chatbar::getInputPosition(sf::RenderWindow &window) + { + auto windowSize = window.getSize(); + return { floor(ChannelSidePanel::getWidth() + PADDING_SIDE * Settings::getScaling()), floor(windowSize.y - getInputSize(window).y - PADDING_BOTTOM * Settings::getScaling()) }; + } + + sf::Vector2f Chatbar::getInputSize(sf::RenderWindow &window) + { + auto windowSize = window.getSize(); + const float fontSize = FONT_SIZE * Settings::getScaling(); + const float fontHeight = ResourceCache::getFont("fonts/Nunito-Regular.ttf")->getLineSpacing(fontSize); + return { floor(windowSize.x - ChannelSidePanel::getWidth() - UsersSidePanel::getWidth() - PADDING_SIDE * Settings::getScaling() * 2.0f), floor(fontHeight * 1.7f + BOX_PADDING_Y * Settings::getScaling() * 2.0f) }; + } float Chatbar::getHeight() { diff --git a/src/MessageBoard.cpp b/src/MessageBoard.cpp index aa92d46..2c89be3 100644 --- a/src/MessageBoard.cpp +++ b/src/MessageBoard.cpp @@ -36,7 +36,7 @@ namespace dchat const float TEXT_LINE_SPACING = 5.0f; const float USERNAME_TIMESTAMP_SIDE_PADDING = 15.0f; const float AVATAR_DIAMETER = 70.0f; - const float AVATAR_PADDING_SIDE = 30.0f; + const float AVATAR_PADDING_SIDE = 20.0f; const double SCROLL_MAX_SPEED = 20.0; // Merge messages from same user that are sent within one minute @@ -52,7 +52,7 @@ namespace dchat visibleMessageEndIndex(-1) { scrollbar.backgroundColor = sf::Color(49, 52, 57); - scrollbar.scrollColor = sf::Color(42, 44, 49); + scrollbar.scrollColor = sf::Color(37, 39, 44); } MessageBoard::~MessageBoard() @@ -81,16 +81,24 @@ namespace dchat dirty = true; } - void MessageBoard::addMessage(Message *message, const odhtdb::Hash &id) + bool MessageBoard::addMessage(Message *message, const odhtdb::Hash &id) { lock_guard<mutex> lock(messageProcessMutex); + bool emptyHash = id.isEmpty(); + if(!emptyHash && messageIdMap.find(id) != messageIdMap.end()) + { + delete message; + return false; + } auto positionToAddMessage = findPositionToInsertMessageByTimestamp(message); if(positionToAddMessage == messages.size()) scrollToBottom = true; messages.insert(messages.begin() + positionToAddMessage, message); message->id = id; - messageIdMap[id] = message; + if(!emptyHash) + messageIdMap[id] = message; dirty = true; + return true; } void MessageBoard::deleteMessage(const odhtdb::Hash &id, const odhtdb::Signature::PublicKey &requestedByUser) @@ -167,6 +175,8 @@ namespace dchat } bool visible = false; + + float startX = floor(position.x + LINE_SIDE_PADDING * Settings::getScaling() - PADDING_SIDE * Settings::getScaling()); if(!mergeTextWithPrev) { @@ -174,9 +184,19 @@ namespace dchat if(position.y + usernameTextHeight > 0.0f && position.y < backgroundPos.y + backgroundSize.y) { visible = true; - sf::Text usernameText(sf::String::fromUtf8(message->user->getName().begin(), message->user->getName().end()), *usernameFont, usernameTextCharacterSize); + string usernameStr; + if(message->user->type == User::Type::ONLINE_DISCORD_USER) + { + usernameStr = "(Discord) "; + usernameStr += message->user->getName(); + } + else + { + usernameStr = message->user->getName(); + } + sf::Text usernameText(sf::String::fromUtf8(usernameStr.begin(), usernameStr.end()), *usernameFont, usernameTextCharacterSize); usernameText.setFillColor(sf::Color(15, 192, 252)); - usernameText.setPosition(sf::Vector2f(floor(position.x + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y))); + usernameText.setPosition(sf::Vector2f(floor(startX + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y))); window.draw(usernameText); if(message->timestampSeconds) @@ -188,7 +208,7 @@ namespace dchat sf::Text timestamp(date, *timestampFont, timestampTextCharacterSize); timestamp.setFillColor(ColorScheme::getTextRegularColor() * sf::Color(255, 255, 255, 50)); - timestamp.setPosition(sf::Vector2f(floor(position.x + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling() + usernameText.getLocalBounds().width + USERNAME_TIMESTAMP_SIDE_PADDING * Settings::getScaling()), floor(position.y + 2.0f * Settings::getScaling() + usernameTextHeight * 0.5f - timestampTextHeight * 0.5f))); + timestamp.setPosition(sf::Vector2f(floor(startX + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling() + usernameText.getLocalBounds().width + USERNAME_TIMESTAMP_SIDE_PADDING * Settings::getScaling()), floor(position.y + 2.0f * Settings::getScaling() + usernameTextHeight * 0.5f - timestampTextHeight * 0.5f))); window.draw(timestamp); } @@ -203,14 +223,14 @@ namespace dchat // TODO: Store this sprite somewhere, might not be efficient to create a new sprite object every frame sf::Sprite sprite(*avatarResult.texture); auto textureSize = avatarResult.texture->getSize(); - sprite.setPosition(sf::Vector2f(floor(position.x), floor(position.y))); + sprite.setPosition(sf::Vector2f(startX, floor(position.y))); sprite.setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.y)); window.draw(sprite, circleShader); } else if(avatarResult.cachedType == ContentByUrlResult::CachedType::GIF) { auto gifSize = avatarResult.gif->getSize(); - avatarResult.gif->setPosition(sf::Vector2f(floor(position.x), floor(position.y))); + avatarResult.gif->setPosition(sf::Vector2f(startX, floor(position.y))); avatarResult.gif->setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.y)); avatarResult.gif->setColor(sf::Color::White); avatarResult.gif->draw(window, circleShader); @@ -219,7 +239,7 @@ namespace dchat else { sf::CircleShape avatarCircle(AVATAR_DIAMETER * 0.5f * Settings::getScaling(), 60 * Settings::getScaling()); - avatarCircle.setPosition(sf::Vector2f(floor(position.x), floor(position.y))); + avatarCircle.setPosition(sf::Vector2f(startX, floor(position.y))); avatarCircle.setFillColor(ColorScheme::getBackgroundColor() + sf::Color(30, 30, 30)); window.draw(avatarCircle); } @@ -230,8 +250,8 @@ namespace dchat // No need to perform culling here, that is done in @Text draw function message->text.setCharacterSize(18.0f * Settings::getScaling()); - message->text.setMaxWidth(backgroundSize.x - (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()); - message->text.setPosition(sf::Vector2f(floor(position.x + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y))); + message->text.setMaxWidth(lineRect.getSize().x - (AVATAR_DIAMETER + AVATAR_PADDING_SIDE * 2.0f) * Settings::getScaling()); + message->text.setPosition(sf::Vector2f(floor(startX + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y))); bool textDrawn = message->text.draw(window, cache); if(!visible) visible = textDrawn; @@ -243,7 +263,7 @@ namespace dchat if(position.y + LINE_HEIGHT > 0.0f && position.y < backgroundPos.y + backgroundSize.y && i + 1 != numMessages) { visible = true; - lineRect.setPosition(sf::Vector2f(position.x + LINE_SIDE_PADDING * Settings::getScaling() - PADDING_SIDE * Settings::getScaling(), floor(position.y))); + lineRect.setPosition(sf::Vector2f(startX, floor(position.y))); window.draw(lineRect); //drawGradientLine(sf::Vector2f(position.x + LINE_SIDE_PADDING, floor(position.y)), sf::Vector2f(backgroundSizeWithoutPadding.x - LINE_SIDE_PADDING * 2.0f, LINE_HEIGHT), LINE_COLOR, window); } @@ -459,4 +479,9 @@ namespace dchat return messages.back(); return nullptr; } + + const std::vector<Message*>& MessageBoard::getMessages() const + { + return messages; + } } diff --git a/src/ResourceCache.cpp b/src/ResourceCache.cpp index 4664bf0..f6f8fad 100644 --- a/src/ResourceCache.cpp +++ b/src/ResourceCache.cpp @@ -1,5 +1,6 @@ #include "../include/ResourceCache.hpp" #include <unordered_map> +#include <gd.h> using namespace std; @@ -33,6 +34,29 @@ namespace dchat auto it = textures.find(filepath); if(it != textures.end()) return it->second; + + gdImagePtr imgPtr = gdImageCreateFromFile(filepath.c_str()); + if(!imgPtr) + { + string errMsg = "Failed to load texture with gd: "; + errMsg += filepath; + throw FailedToLoadResourceException(errMsg); + } + + gdImageSetInterpolationMethod(imgPtr, GD_BILINEAR_FIXED); + gdImagePtr newImgPtr = gdImageScale(imgPtr, 100, 100); + if(!newImgPtr) + { + gdImageDestroy(imgPtr); + string errMsg = "Failed to scale image with gd: "; + errMsg += filepath; + throw FailedToLoadResourceException(errMsg); + } + + gdImageFile(newImgPtr, filepath.c_str()); + + gdImageDestroy(imgPtr); + gdImageDestroy(newImgPtr); sf::Texture *texture = new sf::Texture(); if(!texture->loadFromFile(filepath)) diff --git a/src/Rpc.cpp b/src/Rpc.cpp new file mode 100644 index 0000000..2bf54fb --- /dev/null +++ b/src/Rpc.cpp @@ -0,0 +1,39 @@ +#include "../include/Rpc.hpp" +#include <string> +#include <cassert> + +namespace dchat +{ + Rpc::Rpc(u16 port) : + context(1), + socket(context, ZMQ_PAIR) + { + std::string addr = "tcp://*:"; + addr += std::to_string(port); + socket.bind(addr); + } + + void Rpc::recv(RpcRecvCallbackFunc recvCallbackFunc) + { + assert(recvCallbackFunc); + zmq::message_t request; + if(socket.recv(&request, ZMQ_NOBLOCK)) + { + recvCallbackFunc(&request); + } + } + + bool Rpc::send(const void *data, const usize size) + { + if(size == 0) return false; + try + { + return socket.send(data, size, ZMQ_NOBLOCK) > 0; + } + catch(zmq::error_t &e) + { + fprintf(stderr, "Rpc::send failed, reason: %s\n", e.what()); + return false; + } + } +}
\ No newline at end of file diff --git a/src/Suggestions.cpp b/src/Suggestions.cpp new file mode 100644 index 0000000..113cd7e --- /dev/null +++ b/src/Suggestions.cpp @@ -0,0 +1,54 @@ +#include "../include/Suggestions.hpp" +#include "../include/Text.hpp" +#include "../include/ResourceCache.hpp" +#include "../include/Settings.hpp" +#include "../include/ColorScheme.hpp" +#include "../include/Chatbar.hpp" +#include <cmath> + +namespace dchat +{ + static const char *FONT_PATH = "fonts/Nunito-Regular.ttf"; + static float FONT_SCALING = 18.0f; + + static sf::Vector2f floor(const sf::Vector2f &vec) + { + return { std::floor(vec.x), std::floor(vec.y) }; + } + + void Suggestions::show(const std::vector<std::string> &_texts) + { + for(const auto &text : _texts) + { + sf::String str = sf::String::fromUtf8(text.begin(), text.end()); + texts.emplace_back(std::make_unique<Text>(str, ResourceCache::getFont(FONT_PATH), FONT_SCALING * Settings::getScaling(), 0.0f, false)); + } + } + + void Suggestions::draw(sf::RenderWindow &window, Cache &cache) + { + if(texts.empty()) return; + + sf::Vector2f position = Chatbar::getInputPosition(window); + sf::Vector2f size = Chatbar::getInputSize(window); + size.y = FONT_SCALING * Settings::getScaling() * (1 + texts.size()) * 1.7f; + position.y -= size.y; + + position = floor(position); + size = floor(size); + + sf::RectangleShape rect(size); + rect.setPosition(position); + rect.setFillColor(ColorScheme::getPanelColor()); + window.draw(rect); + + for(const auto &text : texts) + { + text->setCharacterSize(FONT_SCALING * Settings::getScaling()); + text->setMaxWidth(size.x); + text->setPosition(position.x, std::floor(position.y)); + text->draw(window, cache); + position.y += text->getHeight(); + } + } +}
\ No newline at end of file diff --git a/src/Text.cpp b/src/Text.cpp index c8ee731..39e339f 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -6,8 +6,8 @@ #include "../include/ImagePreview.hpp" #include "../include/StringUtils.hpp" #include <SFML/Graphics/RectangleShape.hpp> +#include <SFML/Window/Clipboard.hpp> #include <cmath> -#include <process.hpp> namespace dchat { @@ -341,6 +341,7 @@ namespace dchat sf::Uint32 prevCodePoint = 0; size_t lastSpacingWordWrapIndex = -1; float lastSpacingAccumulatedOffset = 0.0f; + bool lineHasEmoji = false; for(usize textElementIndex = 0; textElementIndex < textElements.size(); ++textElementIndex) { TextElement &textElement = textElements[textElementIndex]; @@ -377,11 +378,12 @@ namespace dchat { textElement.position.y = glyphPos.y; // TODO: Find a better way to do this, @totalHeight is wrong because we add emojiSize and then vspace - glyphPos.y += floor(emojiSize - vspace); + glyphPos.y += floor(emojiSize - vspace) + lineSpacing; } else { textElement.position.y = glyphPos.y + vspace * 0.5f - emojiSize * 0.5f; + lineHasEmoji = true; } glyphPos.x += emojiSize + EMOJI_PADDING + characterSpacing; if(glyphPos.x > maxWidth) @@ -443,16 +445,13 @@ namespace dchat vertices[vertexStart + 2] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; vertices[vertexStart + 3] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; glyphPos.x = 0.0f; - glyphPos.y += floor(vspace + lineSpacing); - continue; - } - case '\v': - { - vertices[vertexStart + 0] = { sf::Vector2f(glyphPos.x, glyphPos.y - vspace), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertexStart + 1] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertexStart + 2] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; - vertices[vertexStart + 3] = { sf::Vector2f(0.0f, glyphPos.y), sf::Color::Transparent, sf::Vector2f() }; - glyphPos.y += floor(vspace * TAB_WIDTH + lineSpacing); + if(lineHasEmoji) + { + lineHasEmoji = false; + glyphPos.y += floor(vspace * EMOJI_SCALE_WITH_TEXT + lineSpacing); + } + else + glyphPos.y += floor(vspace + lineSpacing); continue; } } @@ -481,7 +480,13 @@ namespace dchat else glyphPos.x = 0.0f; - glyphPos.y += floor(vspace + lineSpacing); + if(lineHasEmoji) + { + lineHasEmoji = false; + glyphPos.y += floor(vspace * EMOJI_SCALE_WITH_TEXT + lineSpacing); + } + else + glyphPos.y += floor(vspace + lineSpacing); } sf::Vector2f vertexTopLeft(glyphPos.x + glyph.bounds.left, glyphPos.y + glyph.bounds.top); @@ -513,7 +518,7 @@ namespace dchat if(textElement.type == TextElement::Type::URL) { - glyphPos.y += vspace + lineSpacing; + glyphPos.y += floor(vspace + lineSpacing); textElement.position.y = glyphPos.y; glyphPos.x = 0.0f; @@ -521,7 +526,12 @@ namespace dchat } } - boundingBox.height = glyphPos.y + vspace + lineSpacing; + boundingBox.height = glyphPos.y + lineSpacing; + if(lineHasEmoji) + boundingBox.height += vspace * EMOJI_SCALE_WITH_TEXT; + else + boundingBox.height += vspace; + usize numVertices = vertices.getVertexCount(); for(usize i = 0; i < numVertices; i += 4) { @@ -708,18 +718,6 @@ namespace dchat return static_cast<int>(1.0f + position.y / (vspace + lineSpacing)); } - static std::string getClipboard() - { - std::string result; - TinyProcessLib::Process process("xsel -o -b", "", [&result](const char *bytes, size_t n) - { - result.append(bytes, n); - }); - if(process.get_exit_status() != 0) - fprintf(stderr, "Failed to get clipboard content\n"); - return result; - } - void Text::onMouseClick(const sf::Event::MouseButtonEvent &event, Cache &cache) { if(event.button != sf::Mouse::Button::Left) return; @@ -869,8 +867,7 @@ namespace dchat sf::String stringToAdd; if(event.text.unicode == 22) // ctrl+v { - auto clipboardString = getClipboard(); - stringToAdd = sf::String::fromUtf8(clipboardString.begin(), clipboardString.end()); + stringToAdd = sf::Clipboard::getString(); } else if(event.text.unicode >= 32 || event.text.unicode == 9) // 9 == tab stringToAdd = event.text.unicode; diff --git a/src/User.cpp b/src/User.cpp index aabfd0b..510f884 100644 --- a/src/User.cpp +++ b/src/User.cpp @@ -1,8 +1,10 @@ #include "../include/User.hpp" +#include <cassert> namespace dchat { const static std::string SYSTEM_USER_NAME = "System"; + const i64 USER_TIMEOUT_SEC = 25; User::User(Type _type) : type(_type) @@ -17,12 +19,18 @@ namespace dchat { } - + const std::string& OnlineUser::getName() const { return name; } + bool OnlineUser::isConnected(i64 timestampUtcSec) const + { + i64 pingTimeDiffSec = timestampUtcSec - (i64)pingTimestampSec; + return pingTimeDiffSec <= USER_TIMEOUT_SEC; + } + OnlineRemoteUser::OnlineRemoteUser(const std::string &name, const odhtdb::Signature::PublicKey &_publicKey) : OnlineUser(name, Type::ONLINE_REMOTE_USER), publicKey(_publicKey) @@ -41,11 +49,24 @@ namespace dchat { } - + const odhtdb::Signature::PublicKey& OnlineLocalUser::getPublicKey() const { return keyPair.getPublicKey(); } + + OnlineDiscordUser::OnlineDiscordUser(const std::string &discordUserName, u64 _discordUserId, User *_bridgeOwner) : + OnlineUser(discordUserName, Type::ONLINE_DISCORD_USER), + discordUserId(_discordUserId), + bridgeOwner(_bridgeOwner) + { + assert(bridgeOwner); + } + + const odhtdb::Signature::PublicKey& OnlineDiscordUser::getPublicKey() const + { + return odhtdb::Signature::PublicKey::ZERO; + } OfflineUser::OfflineUser(const std::string &_name) : User(Type::OFFLINE), diff --git a/src/UsersSidePanel.cpp b/src/UsersSidePanel.cpp index 687b976..b1f7843 100644 --- a/src/UsersSidePanel.cpp +++ b/src/UsersSidePanel.cpp @@ -11,6 +11,7 @@ #include <SFML/Graphics/Text.hpp> #include <vector> #include <cmath> +#include <sibs/Functional.hpp> using namespace std; @@ -24,47 +25,50 @@ namespace dchat const float PADDING_BOTTOM = 20.0f; const i64 USER_TIMEOUT_SEC = 25; - static void renderUser(Cache &cache, User *user, sf::Shader *circleShader, sf::RenderWindow &window, sf::Vector2f &position, const sf::Font *font, const float textHeight, bool isUserOnline) + static void renderUser(Cache &cache, const User *user, sf::Shader *circleShader, sf::RenderWindow &window, sf::Vector2f &position, const sf::Font *font, const float textHeight, bool isUserOnline) { - // Max avatar size = 1mb - const ContentByUrlResult avatarResult = cache.getContentByUrl(user->avatarUrl, 1024 * 1024); - if(avatarResult.type == ContentByUrlResult::Type::CACHED) + if(position.y + AVATAR_DIAMETER > 0.0f && position.y < window.getSize().y) { - circleShader->setUniform("texture", sf::Shader::CurrentTexture); - - if(avatarResult.cachedType == ContentByUrlResult::CachedType::TEXTURE) + // Max avatar size = 1mb + const ContentByUrlResult avatarResult = cache.getContentByUrl(user->avatarUrl, 1024 * 1024); + if(avatarResult.type == ContentByUrlResult::Type::CACHED) { - // TODO: Store this sprite somewhere, might not be efficient to create a new sprite object every frame - sf::Sprite sprite(*avatarResult.texture); - auto textureSize = avatarResult.texture->getSize(); - sprite.setPosition(sf::Vector2f(floor(position.x), floor(position.y))); - sprite.setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.y)); - sprite.setColor(isUserOnline ? sf::Color::White : sf::Color(255, 255, 255, 100)); - window.draw(sprite, circleShader); + circleShader->setUniform("texture", sf::Shader::CurrentTexture); + + if(avatarResult.cachedType == ContentByUrlResult::CachedType::TEXTURE) + { + // TODO: Store this sprite somewhere, might not be efficient to create a new sprite object every frame + sf::Sprite sprite(*avatarResult.texture); + auto textureSize = avatarResult.texture->getSize(); + sprite.setPosition(sf::Vector2f(floor(position.x), floor(position.y))); + sprite.setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)textureSize.y)); + sprite.setColor(isUserOnline ? sf::Color::White : sf::Color(255, 255, 255, 100)); + window.draw(sprite, circleShader); + } + else if(avatarResult.cachedType == ContentByUrlResult::CachedType::GIF) + { + auto gifSize = avatarResult.gif->getSize(); + avatarResult.gif->setPosition(sf::Vector2f(floor(position.x), floor(position.y))); + avatarResult.gif->setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.y)); + avatarResult.gif->setColor(isUserOnline ? sf::Color::White : sf::Color(255, 255, 255, 100)); + avatarResult.gif->draw(window, circleShader); + } } - else if(avatarResult.cachedType == ContentByUrlResult::CachedType::GIF) + else { - auto gifSize = avatarResult.gif->getSize(); - avatarResult.gif->setPosition(sf::Vector2f(floor(position.x), floor(position.y))); - avatarResult.gif->setScale(sf::Vector2f(AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.x, AVATAR_DIAMETER * Settings::getScaling() / (float)gifSize.y)); - avatarResult.gif->setColor(isUserOnline ? sf::Color::White : sf::Color(255, 255, 255, 100)); - avatarResult.gif->draw(window, circleShader); + sf::CircleShape avatarCircle(AVATAR_DIAMETER * 0.5f * Settings::getScaling(), 60 * Settings::getScaling()); + avatarCircle.setPosition(sf::Vector2f(floor(position.x), floor(position.y))); + avatarCircle.setFillColor(isUserOnline ? ColorScheme::getBackgroundColor() + sf::Color(30, 30, 30) : ColorScheme::getBackgroundColor() * sf::Color(255, 255, 255, 100)); + window.draw(avatarCircle); } + + // TODO: Remove this shit + sf::String str = sf::String::fromUtf8(user->getName().begin(), user->getName().end()); + sf::Text text(str, *font, FONT_SIZE * Settings::getScaling()); + text.setPosition(floor(position.x + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y + AVATAR_DIAMETER * 0.5f - textHeight * 0.5f)); + text.setFillColor(isUserOnline ? sf::Color(15, 192, 252) : sf::Color(15, 192, 252, 100)); + window.draw(text); } - else - { - sf::CircleShape avatarCircle(AVATAR_DIAMETER * 0.5f * Settings::getScaling(), 60 * Settings::getScaling()); - avatarCircle.setPosition(sf::Vector2f(floor(position.x), floor(position.y))); - avatarCircle.setFillColor(isUserOnline ? ColorScheme::getBackgroundColor() + sf::Color(30, 30, 30) : ColorScheme::getBackgroundColor() * sf::Color(255, 255, 255, 100)); - window.draw(avatarCircle); - } - - // TODO: Remove this shit - sf::String str = sf::String::fromUtf8(user->getName().begin(), user->getName().end()); - sf::Text text(str, *font, FONT_SIZE * Settings::getScaling()); - text.setPosition(floor(position.x + (AVATAR_DIAMETER + AVATAR_PADDING_SIDE) * Settings::getScaling()), floor(position.y + AVATAR_DIAMETER * 0.5f - textHeight * 0.5f)); - text.setFillColor(isUserOnline ? sf::Color(15, 192, 252) : sf::Color(15, 192, 252, 100)); - window.draw(text); position.y += ((AVATAR_DIAMETER + PADDING_BOTTOM) * Settings::getScaling()); } @@ -86,24 +90,22 @@ namespace dchat const float textHeight = font->getLineSpacing(FONT_SIZE * Settings::getScaling()); i64 timestampSec = currentChannel->getSyncedTimestampUtcInSec(); + auto &channelUsers = currentChannel->getUsers(); + auto currentChannelUsers = sibs::makeFunction(channelUsers.data(), channelUsers.data() + channelUsers.size()); + for(BridgeService *bridgeService : currentChannel->getBridgeServices()) + { + auto &bridgeServiceUsers = bridgeService->getUsers(); + currentChannelUsers.merge(sibs::makeFunction(bridgeServiceUsers.data(), bridgeServiceUsers.data() + bridgeServiceUsers.size())); + } u32 numOnlineUsers = 0; u32 numOfflineUsers = 0; - for(User *user : currentChannel->getUsers()) + for(const User *user : currentChannelUsers) { - bool hasUserTimedOut = false; - if(user->isOnlineUser()) - { - auto onlineUser = static_cast<OnlineUser*>(user); - i64 pingTimeDiffSec = timestampSec - (i64)onlineUser->pingTimestampSec; - if(pingTimeDiffSec > USER_TIMEOUT_SEC) - hasUserTimedOut = true; - } - - if(hasUserTimedOut) - ++numOfflineUsers; - else + if(user->isConnected(timestampSec)) ++numOnlineUsers; + else + ++numOfflineUsers; } // TODO: Remove this shit @@ -118,19 +120,10 @@ namespace dchat sf::Shader *circleShader = ResourceCache::getShader("shaders/circleMask.glsl", sf::Shader::Fragment); - for(User *user : currentChannel->getUsers()) + for(const User *user : currentChannelUsers) { - bool isUserOnline = true; - if(user->isOnlineUser()) - { - auto onlineUser = static_cast<OnlineUser*>(user); - i64 pingTimeDiffSec = timestampSec - (i64)onlineUser->pingTimestampSec; - if(pingTimeDiffSec > USER_TIMEOUT_SEC) - isUserOnline = false; - } - - if(isUserOnline) - renderUser(cache, user, circleShader, window, position, font, textHeight, isUserOnline); + if(user->isConnected(timestampSec)) + renderUser(cache, user, circleShader, window, position, font, textHeight, true); } if(numOfflineUsers == 0) return; @@ -146,19 +139,10 @@ namespace dchat position.y += floor(font->getLineSpacing(text.getCharacterSize())); position.y += PADDING_BOTTOM * Settings::getScaling() * 0.5f; - for(User *user : currentChannel->getUsers()) + for(const User *user : currentChannelUsers) { - bool isUserOnline = true; - if(user->isOnlineUser()) - { - auto onlineUser = static_cast<OnlineUser*>(user); - i64 pingTimeDiffSec = timestampSec - (i64)onlineUser->pingTimestampSec; - if(pingTimeDiffSec > USER_TIMEOUT_SEC) - isUserOnline = false; - } - - if(!isUserOnline) - renderUser(cache, user, circleShader, window, position, font, textHeight, isUserOnline); + if(!user->isConnected(timestampSec)) + renderUser(cache, user, circleShader, window, position, font, textHeight, false); } } diff --git a/src/main.cpp b/src/main.cpp index 0836278..efb354f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,9 @@ #include "../include/GlobalContextMenu.hpp" #include "../include/StringUtils.hpp" #include "../include/ImagePreview.hpp" +#include "../include/Rpc.hpp" +#include <msgpack.hpp> +#include <sstream> #include <string> #include <SFML/Graphics.hpp> #include <cstring> @@ -94,6 +97,13 @@ static void channelChangeChannelName(Channel *channel, const StringView data, co fprintf(stderr, "Channel change name: user with public key %s not found in channel %s\n", userPublicKey.toString().c_str(), channel->getName().c_str()); return; } + + int userPermissionLevel = channel->getUserLowestPermissionLevel(user); + if(userPermissionLevel != odhtdb::PERMISSION_LEVEL_ADMIN) + { + fprintf(stderr, "Channel change name: attempted by user %s who is not an admin\n", user->getName().c_str()); + return; + } sibs::SafeDeserializer deserializer((const u8*)data.data, data.size); u16 channelNameLength = deserializer.extract<u16>(); @@ -107,6 +117,40 @@ static void channelChangeChannelName(Channel *channel, const StringView data, co // We dont care if there is more data to read (malicious packet), we already got all the data we need } +static void channelAddDiscordMessage(Channel *channel, const StringView data, const odhtdb::Signature::PublicKey &userPublicKey, u64 timestamp, const odhtdb::Hash &requestHash) +{ + auto bridgeOwner = channel->getUserByPublicKey(userPublicKey); + if(!bridgeOwner) + { + fprintf(stderr, "Channel add discord message: user with public key %s not found in channel %s\n", userPublicKey.toString().c_str(), channel->getName().c_str()); + return; + } + + int userPermissionLevel = channel->getUserLowestPermissionLevel(bridgeOwner); + if(userPermissionLevel != odhtdb::PERMISSION_LEVEL_ADMIN) + { + fprintf(stderr, "Channel add discord message: attempted by user %s who is not an admin\n", bridgeOwner->getName().c_str()); + return; + } + + sibs::SafeDeserializer deserializer((const u8*)data.data, data.size); + u64 discordUserId = deserializer.extract<u64>(); + u8 discordNameLength = deserializer.extract<u8>(); + if(discordNameLength == 0) return; + + string discordUserName; + discordUserName.resize(discordNameLength); + deserializer.extract((u8*)&discordUserName[0], discordNameLength); + + usize msgSize = deserializer.getSize(); + if(msgSize == 0) return; + string msg(deserializer.getBuffer(), deserializer.getBuffer() + deserializer.getSize()); + + auto timestampSeconds = ntp::NtpTimestamp::fromCombined(timestamp).seconds; + channel->addLocalDiscordMessage(discordUserName, discordUserId, msg, bridgeOwner, timestampSeconds, requestHash); + // We dont care if there is more data to read (malicious packet), we already got all the data we need +} + static void channelAddStoredMessage(Channel *channel, const odhtdb::Hash &requestHash, const odhtdb::Signature::PublicKey &creatorPublicKey, const StringView decryptedObject, u64 timestamp, bool loadedFromCache) { User *user = channel->getUserByPublicKey(creatorPublicKey); @@ -187,6 +231,18 @@ static void channelAddStoredMessage(Channel *channel, const odhtdb::Hash &reques } break; } + case ChannelDataType::ADD_DISCORD_MESSAGE: + { + try + { + channelAddDiscordMessage(channel, decryptedData, creatorPublicKey, timestamp, requestHash); + } + catch(sibs::DeserializeException &e) + { + fprintf(stderr, "Failed to deserialize channel add discord message\n"); + } + break; + } default: fprintf(stderr, "Got unexpected channel data type: %u\n", channelDataType); break; @@ -207,7 +263,7 @@ int main(int argc, char **argv) printf("Resource path set to: %s\n", resourcesPath.string().c_str()); } else - printf("Resource directory not defined, using currently directory"); + printf("Resource directory not defined, using currently directory\n"); const sf::Int64 FRAMERATE_FOCUSED = 144; const sf::Int64 FRAMERATE_NOT_FOCUSED = 10; @@ -239,6 +295,9 @@ int main(int argc, char **argv) odhtdb::Database *database = nullptr; odhtdb::DatabaseCallbackFuncs callbackFuncs; + + using LocalUserMessageCallback = function<void(const odhtdb::DatabaseAddNodeRequest &request)>; + LocalUserMessageCallback onMessageByLocalUser = nullptr; callbackFuncs.createNodeCallbackFunc = [&waitingToJoinChannels, &database, &channels, &channelMessageMutex, &waitingToJoin, &localNodeUsers, &lastFocusedTimer](const odhtdb::DatabaseCreateNodeRequest &request) { @@ -280,7 +339,7 @@ int main(int argc, char **argv) } }; - callbackFuncs.addNodeCallbackFunc = [&channels, &channelMessageMutex, &lastFocusedTimer](const odhtdb::DatabaseAddNodeRequest &request) + callbackFuncs.addNodeCallbackFunc = [&channels, &channelMessageMutex, &lastFocusedTimer, &onMessageByLocalUser](const odhtdb::DatabaseAddNodeRequest &request) { lock_guard<recursive_mutex> lock(channelMessageMutex); //printf("Add node callback func %s\n", request.requestHash->toString().c_str()); @@ -291,6 +350,12 @@ int main(int argc, char **argv) channelAddStoredMessage(channel, *request.requestHash, *request.creatorPublicKey, StringView((const char*)request.decryptedData.data, request.decryptedData.size), request.timestamp, request.loadedFromCache); if(channel == Channel::getCurrent()) lastFocusedTimer.restart(); + + if(!request.loadedFromCache && *request.creatorPublicKey == static_cast<OnlineLocalUser*>(channel->getLocalUser())->getPublicKey()) + { + if(onMessageByLocalUser) + onMessageByLocalUser(request); + } return; } } @@ -808,7 +873,7 @@ int main(int argc, char **argv) }); // Change name of the current channel - Command::add("channelname", [&loggedIn, &offlineChannel, addSystemMessage](const vector<string> &args) + Command::add("channelname", [&offlineChannel, addSystemMessage](const vector<string> &args) { if(args.size() != 1) { @@ -818,16 +883,17 @@ int main(int argc, char **argv) addSystemMessage(errMsg); return; } - - if(!loggedIn) + + Channel *currentChannel = Channel::getCurrent(); + if(currentChannel == &offlineChannel) { - addSystemMessage("You need to be logged in to change channel name"); + addSystemMessage("You need to be in a channel to change channel name"); return; } - if(Channel::getCurrent() == &offlineChannel) + if(!currentChannel->getLocalUser()->isOnlineUser()) { - addSystemMessage("You need to be in a channel to change channel name"); + addSystemMessage("You need to be logged in to change channel name"); return; } @@ -836,8 +902,15 @@ int main(int argc, char **argv) addSystemMessage("Channel name has to be between 1 and 32 bytes long"); return; } + + int localUserPermissionLevel = currentChannel->getUserLowestPermissionLevel(static_cast<OnlineLocalUser*>(currentChannel->getLocalUser())); + if(localUserPermissionLevel != odhtdb::PERMISSION_LEVEL_ADMIN) + { + addSystemMessage("You need to be admin to change channel name"); + return; + } - Channel::getCurrent()->setName(args[0]); + currentChannel->setName(args[0]); string msg = "Channel name has been changed to "; msg += args[0]; addSystemMessage(msg); @@ -855,10 +928,268 @@ int main(int argc, char **argv) commandsMsg += commandIt.first; } addSystemMessage(commandsMsg); + + odhtdb::MapHash<u64> myDiscordIdsByChannel; + + bool running = true; + thread rpcThread([&running, &onMessageByLocalUser, &channels, &lastFocusedTimer, &myDiscordIdsByChannel]() + { + Rpc rpc(5555); + mutex messageMutex; + vector<msgpack::sbuffer> messagesToSend; + onMessageByLocalUser = [&messagesToSend, &messageMutex](const odhtdb::DatabaseAddNodeRequest &request) + { + lock_guard<mutex> lock(messageMutex); + auto channelDataType = (ChannelDataType)static_cast<const char*>(request.decryptedData.data)[0]; + usize size = request.decryptedData.size - 1; + const char *data = (const char*)request.decryptedData.data + 1; + const char *action = nullptr; + if(channelDataType == ChannelDataType::ADD_MESSAGE) + action = "addMessage"; + + if(!action) return; + vector<string> msg = { action, string(data, data + size), request.nodeHash->toString() }; + msgpack::sbuffer buffer; + msgpack::pack(buffer, msg); + messagesToSend.emplace_back(move(buffer)); + }; + + while(running) + { + rpc.recv([&channels, &lastFocusedTimer, &myDiscordIdsByChannel](zmq::message_t *message) + { + try + { + msgpack::object_handle oh = msgpack::unpack((const char*)message->data(), message->size()); + auto deserialized = oh.get(); + vector<string> msg; + deserialized.convert(msg); + if(msg.size() < 2) + { + fprintf(stderr, "Rpc receive, data length expected to be at least 2, was %u\n", msg.size()); + return; + } + auto &action = msg[0]; + + string dchatChannelIdRaw = odhtdb::hex2bin(msg[1].c_str(), msg[1].size()); + odhtdb::Hash dchatChannelId; + memcpy(dchatChannelId.getData(), dchatChannelIdRaw.data(), dchatChannelIdRaw.size()); + Channel *bridgedChannel = nullptr; + for(Channel *channel : channels) + { + if(*channel->getNodeInfo().getRequestHash() == dchatChannelId) + { + bridgedChannel = channel; + break; + } + } + + if(!bridgedChannel) + { + fprintf(stderr, "Rcp addMessage, invalid dchat channel %s\n", msg[1].c_str()); + return; + } + + if(bridgedChannel == Channel::getCurrent()) + lastFocusedTimer.restart(); + + fprintf(stderr, "Received rpc, action: %s\n", action.c_str()); + if(action == "addMessage") + { + if((msg.size() - 2) % 4 != 0) + { + fprintf(stderr, "Rpc addMessage, request was malformed\n"); + return; + } + + for(size_t i = 2; i < msg.size(); i += 4) + { + auto &content = msg[i]; + auto &discordUserId = msg[i + 1]; + u64 discordUserIdNumber = 0; + auto &discordUserName = msg[i + 2]; + auto &messageTimestampMillisec = msg[i + 3]; + u64 messageTimestampSecondsNumber = 0; + + try + { + discordUserIdNumber = stoull(discordUserId); + } + catch(...) + { + fprintf(stderr, "Rpc receive, failed to convert discord id to uint64_t: %s\n", discordUserId.c_str()); + return; + } + + try + { + messageTimestampSecondsNumber = stoull(messageTimestampMillisec) / 1000; + } + catch(...) + { + fprintf(stderr, "Rpc receive, failed to convert discord message timestamp to uint64_t: %s\n", messageTimestampMillisec.c_str()); + return; + } + + auto myDiscordIdIt = myDiscordIdsByChannel.find(dchatChannelId); + if(myDiscordIdIt != myDiscordIdsByChannel.end() && myDiscordIdIt->second == discordUserIdNumber) + { + auto &channelMessages = bridgedChannel->getMessageBoard().getMessages(); + Message *myLatestMessage = nullptr; + for(auto it = channelMessages.rbegin(), end = channelMessages.rend(); it != end; ++it) + { + if((*it)->user == bridgedChannel->getLocalUser()) + { + myLatestMessage = *it; + break; + } + } + + if(myLatestMessage && (i64)messageTimestampSecondsNumber - (i64)myLatestMessage->timestampSeconds <= 3) + { + return; + /* + auto myMessageUtf8 = myLatestMessage->text.getString().toUtf8(); + odhtdb::Hash myMessageHash(myMessageUtf8.data(), myMessageUtf8.size()); + odhtdb::Hash myDiscordMessageHash(content.data(), content.size()); + if(myMessageHash == myDiscordMessageHash) + return; + */ + } + } + + bridgedChannel->addLocalDiscordMessage(discordUserName, discordUserIdNumber, content, bridgedChannel->getLocalUser(), messageTimestampSecondsNumber, odhtdb::Hash(messageTimestampMillisec.c_str(), messageTimestampMillisec.size())); + } + } + else if(action == "addUser") + { + if((msg.size() - 2) % 4 != 0) + { + fprintf(stderr, "Rpc addUser, request was malformed\n"); + return; + } + + for(size_t i = 2; i < msg.size(); i += 4) + { + auto &discordUsername = msg[i]; + auto &discordUserId = msg[i + 1]; + auto &userStatus = msg[i + 2]; + auto &avatarURL = msg[i + 3]; + + try + { + u64 discordUserIdNumber = stoull(discordUserId); + bool online = (userStatus != "offline"); + printf("Rpc, adding user %s with status %s\n", discordUsername.c_str(), userStatus.c_str()); + DiscordServiceUser *discordUser = bridgedChannel->getDiscordService()->getUserById(discordUserIdNumber); + if(discordUser) + { + discordUser->connected = online; + } + else + { + discordUser = new DiscordServiceUser(discordUsername, discordUserIdNumber, online); + bridgedChannel->getDiscordService()->addUser(discordUser); + } + discordUser->avatarUrl = avatarURL; + } + catch(...) + { + fprintf(stderr, "Warning: Rpc receive, failed to convert discord id to uint64_t: %s\n", discordUserId.c_str()); + // Ignore for now.. should we really handle this error other than showing warning? + } + } + } + else if(action == "removeUser") + { + for(size_t i = 2; i < msg.size(); ++i) + { + auto &discordUserId = msg[i]; + try + { + u64 discordUserIdNumber = stoull(discordUserId); + bridgedChannel->getDiscordService()->removeUser(discordUserIdNumber); + } + catch(...) + { + fprintf(stderr, "Warning: Rpc receive, failed to convert discord id to uint64_t: %s\n", discordUserId.c_str()); + // Ignore for now.. should we really handle this error other than showing warning? + } + } + } + else if(action == "statusChange") + { + if((msg.size() - 2) != 2) + { + fprintf(stderr, "Rpc statusChange, request was malformed\n"); + return; + } + + auto &discordUserId = msg[2]; + auto &userStatus = msg[3]; + + try + { + u64 discordUserIdNumber = stoull(discordUserId); + DiscordServiceUser *discordUser = bridgedChannel->getDiscordService()->getUserById(discordUserIdNumber); + if(discordUser) + { + discordUser->connected = (userStatus != "offline"); + printf("Rcp statusChange, changed user %s (%s) status to %s\n", discordUserId.c_str(), discordUser->getName().c_str(), userStatus.c_str()); + } + } + catch(...) + { + fprintf(stderr, "Warning: Rpc receive, failed to convert discord id to uint64_t: %s\n", discordUserId.c_str()); + // Ignore for now.. should we really handle this error other than showing warning? + } + } + else if(action == "addMe") + { + if((msg.size() - 2) != 1) + { + fprintf(stderr, "Rpc addMe, request was malformed\n"); + return; + } + + auto &myDiscordId = msg[2]; + try + { + u64 myDiscordIdNumber = stoull(myDiscordId); + myDiscordIdsByChannel[dchatChannelId] = myDiscordIdNumber; + } + catch(...) + { + fprintf(stderr, "Warning: Rpc receive, failed to convert discord id to uint64_t: %s\n", myDiscordId.c_str()); + // Ignore for now.. should we really handle this error other than showing warning? + } + } + else + { + fprintf(stderr, "Rcp received unknown action %s\n", action.c_str()); + } + } + catch(msgpack::type_error &e) + { + fprintf(stderr, "Failed to deserialize received rpc, error: %s\n", e.what()); + } + }); + { + lock_guard<mutex> lock(messageMutex); + for(auto &messageToSend : messagesToSend) + { + fprintf(stderr, "Rpc, sending message\n"); + rpc.send(messageToSend.data(), messageToSend.size()); + } + messagesToSend.clear(); + } + this_thread::sleep_for(chrono::milliseconds(50)); + } + }); sf::Clock frameTimer; - while (window.isOpen()) + while (running) { frameTimer.restart(); Channel *currentChannel = Channel::getCurrent(); @@ -867,11 +1198,14 @@ int main(int argc, char **argv) while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) + { window.close(); + running = false; + } else if(event.type == sf::Event::Resized) { sf::FloatRect viewRect(0.0f, 0.0f, event.size.width, event.size.height); - /* // TODO: Use xlib to set window minimum size instead + /* // TODO: Use xlib/xcb to set window minimum size instead const int minWidth = 800; if(event.size.width < minWidth) { @@ -916,7 +1250,7 @@ int main(int argc, char **argv) channel->update(); } - if(lastFocusedTimer.getElapsedTime().asMilliseconds() > 5000) + if((!windowFocused || !focused) && lastFocusedTimer.getElapsedTime().asMilliseconds() > 5000) { this_thread::sleep_for(chrono::milliseconds(250)); continue; @@ -948,6 +1282,9 @@ int main(int argc, char **argv) //video.draw(window); window.display(); } + + onMessageByLocalUser = nullptr; + rpcThread.join(); for(Channel *channel : channels) { |