#include "../include/RoomSidePanel.hpp" #include "../include/UsersSidePanel.hpp" #include "../include/RoomTopPanel.hpp" #include "../include/ResourceCache.hpp" #include "../include/Video.hpp" #include "../include/Command.hpp" #include "../include/Settings.hpp" #include "../include/ColorScheme.hpp" #include "../include/GlobalContextMenu.hpp" #include "../include/ImagePreview.hpp" #include "../include/Room.hpp" #include "../include/RoomContainer.hpp" #include #include #include #include #include #include #include #include #include using namespace std; using namespace dchat; static bool focused = true; static bool windowFocused = true; static bool looksLikeUrl(const string &str) { if(str.find('.') == string::npos) return false; if(str.size() >= 7 && strncmp(str.c_str(), "http://", 7) == 0) return true; if(str.size() >= 8 && strncmp(str.c_str(), "https://", 8) == 0) return true; if(str.size() >= 4 && strncmp(str.c_str(), "www.", 4) == 0) return true; return false; } int main(int argc, char **argv) { if(argc > 1) { boost::filesystem::path resourcesPath(argv[1]); boost::filesystem::current_path(resourcesPath); printf("Resource path set to: %s\n", resourcesPath.string().c_str()); } else printf("Resource directory not defined, using current directory\n"); const sf::Int64 FRAMERATE_FOCUSED = 144; const sf::Int64 FRAMERATE_NOT_FOCUSED = 10; XInitThreads(); sf::RenderWindow window(sf::VideoMode(1280, 768), "dchat"); window.setVerticalSyncEnabled(false); window.setFramerateLimit(FRAMERATE_FOCUSED); //Video video(500, 500, "https://www.youtube.com/watch?v=bs0-EX9mJmg"); //Channel offlineChannel("Offline"); //ChannelSidePanel::addChannel(&offlineChannel); //Channel::setCurrent(&offlineChannel); recursive_mutex channelMessageMutex; sf::Clock lastFocusedTimer; const char *bootstrapNode = "31.208.83.179"; std::shared_ptr offlineRoom = nullptr; auto addSystemMessage = [&lastFocusedTimer](const std::string &msg, bool plainText = true) { auto currentRoom = getCurrentRoom(); if(currentRoom && currentRoom->userdata) { auto *roomContainer = static_cast(currentRoom->userdata); roomContainer->messageBoard.addSystemUserMessage(msg, plainText); } lastFocusedTimer.restart(); }; RoomCallbackFuncs roomCallbackFuncs; roomCallbackFuncs.connectCallbackFunc = [bootstrapNode, &channelMessageMutex, &offlineRoom, &lastFocusedTimer, &addSystemMessage](std::shared_ptr rooms, const char *errMsg) { lock_guard lock(channelMessageMutex); if(rooms) { std::string msg = "Connected to "; msg += bootstrapNode; msg += ":27130"; fprintf(stderr, "%s\n", msg.c_str()); setRooms(rooms); auto emptyId = make_shared(); offlineRoom = make_shared(rooms.get(), emptyId); offlineRoom->name = "Offline"; auto *roomContainer = new RoomContainer(nullptr); roomContainer->offlineRoom = true; offlineRoom->userdata = roomContainer; setCurrentRoom(offlineRoom); string commandsMsg = "Available commands: "; for(const auto &commandIt : Command::getCommands()) { commandsMsg += "\n/"; commandsMsg += commandIt.first; } addSystemMessage(commandsMsg); } else { std::string errMsgToShow = "Failed to connect to boostrap node, reason: "; errMsgToShow += (errMsg ? errMsg : "unknown"); fprintf(stderr, "%s\n", errMsgToShow.c_str()); } lastFocusedTimer.restart(); }; roomCallbackFuncs.createRoomCallbackFunc = [&channelMessageMutex, &lastFocusedTimer](std::shared_ptr room) { lock_guard lock(channelMessageMutex); // TODO: Update side panel //RoomSidePanel::addRoom(room); room->userdata = new RoomContainer(room); lastFocusedTimer.restart(); }; roomCallbackFuncs.addUserCallbackFunc = [&channelMessageMutex, &lastFocusedTimer](const RoomAddUserRequest &request) { lock_guard lock(channelMessageMutex); // TODO: Update users side panel //chatWindow.addUser(request); if(request.waitedToJoin) fprintf(stderr, "Waited to join. We were added to room by %s\n", request.addedByUser->publicKey.toString().c_str()); request.user->nickname = "Anonymous"; lastFocusedTimer.restart(); }; roomCallbackFuncs.addMessageCallbackFunc = [&channelMessageMutex, &lastFocusedTimer](const RoomAddMessageRequest &request) { lock_guard lock(channelMessageMutex); assert(request.room->userdata); auto *roomContainer = static_cast(request.room->userdata); Message *message = new Message(request.message, false); roomContainer->messageBoard.addMessage(message); // Show notification if the message is new and window is not focused and the message was sent not sent by us if(!request.loadedFromCache && !windowFocused) { // TODO: Should this be done? #if 0 if(!request.prevMessage || request.message->timestampSeconds >= request.prevMessage->timestampSeconds) { } #endif if(request.room->localUser && request.room->localUser != request.message->creator) { string cmd = "notify-send dchat "; cmd += escapeCommandArg(request.message->text); system(cmd.c_str()); } } lastFocusedTimer.restart(); }; roomCallbackFuncs.userChangeNicknameCallbackFunc = [&channelMessageMutex, &lastFocusedTimer](const UserChangeNicknameRequest &request) { lock_guard lock(channelMessageMutex); // TODO: Update panels //chatWindow.setUserNickname(request); lastFocusedTimer.restart(); }; roomCallbackFuncs.userChangeAvatarCallbackFunc = [&channelMessageMutex, &lastFocusedTimer](const UserChangeAvatarRequest &request) { lock_guard lock(channelMessageMutex); // TODO: Update panels //chatWindow.setUserAvatar(request); lastFocusedTimer.restart(); }; roomCallbackFuncs.changeRoomNameCallbackFunc = [&channelMessageMutex, &lastFocusedTimer](const RoomChangeNameRequest &request) { lock_guard lock(channelMessageMutex); // TODO: Update panels //chatWindow.changeRoomName(request); lastFocusedTimer.restart(); }; roomCallbackFuncs.receiveInviteUserCallbackFunc = [&channelMessageMutex](const InviteUserRequest &request) { lock_guard lock(channelMessageMutex); // TODO: Add invite to a list which we can accept the user from //chatWindow.addInviteRequest(request); // For now automatically add user to the room fprintf(stderr, "User invite request message: %s\n", request.message.c_str()); assert(!request.room->localUser->groups.empty()); // TODO: Add user to guest group. Right now we are adding the user to our group, which in this case is the admin group... try { request.room->addUser(request.userPublicKey, request.room->localUser->groups[0]); fprintf(stderr, "Successfully added user %s to our group\n", request.userPublicKey.toString().c_str()); } catch(odhtdb::PermissionDeniedException &e) { fprintf(stderr, "Failed to add user %s to our group, reason: %s\n", request.userPublicKey.toString().c_str(), e.what()); } }; // Login to account Command::add("login", [&channelMessageMutex, &addSystemMessage](const vector &args) { lock_guard lock(channelMessageMutex); if(args.size() != 2) { fprintf(stderr, "Expected 2 arguments for command login (username and password), got %u argument(s)\n", args.size()); return; } auto rooms = getRooms(); if(!rooms) return; try { rooms->loginUser(args[0], args[1]); std::string msg = "Successfully logged into user "; msg += args[0]; addSystemMessage(msg); printf("%s\n", msg.c_str()); } catch(std::exception &e) { fprintf(stderr, "Failed to login to user %s, reason: %s\n", args[0].c_str(), e.what()); } }); // Register account Command::add("register", [&channelMessageMutex, &addSystemMessage](const vector &args) { lock_guard lock(channelMessageMutex); if(args.size() != 2) { fprintf(stderr, "Expected 2 arguments for command register (username and password), got %u argument(s)\n", args.size()); return; } auto rooms = getRooms(); if(!rooms) return; try { rooms->registerUser(args[0], args[1]); std::string msg = "Successfully registered user "; msg += args[0]; addSystemMessage(msg); printf("%s\n", msg.c_str()); } catch(odhtdb::SqlExecException &e) { fprintf(stderr, "Failed to register user %s, reason: %s", args[0].c_str(), e.what()); return; } }); // TODO: Use database->addData to change channel name // Create channel Command::add("cc", [&channelMessageMutex, &lastFocusedTimer](const vector &args) { lock_guard lock(channelMessageMutex); if(args.size() != 1) { fprintf(stderr, "Expected 1 argument for command cc (channel name), got %u argument(s)\n", args.size()); return; } auto rooms = getRooms(); if(!rooms) return; if(!rooms->isLoggedIn()) { fprintf(stderr, "You are not logged in. Please login before creating a room\n"); return; } try { rooms->createRoom(args[0]); printf("Successfully created room %s\n", args[0].c_str()); lastFocusedTimer.restart(); } catch(std::exception &e) { fprintf(stderr, "Failed to create room %s, reason: %s\n", args[0].c_str(), e.what()); } }); // Get invite key Command::add("invitekey", [&channelMessageMutex, &addSystemMessage](const vector &args) { lock_guard lock(channelMessageMutex); if(args.size() != 0) { fprintf(stderr, "Expected 0 arguments for command invitekey, got %u argument(s)\n", args.size()); return; } auto currentRoom = getCurrentRoom(); if(!currentRoom || static_cast(currentRoom->userdata)->offlineRoom || currentRoom->inviteKey.empty()) { fprintf(stderr, "You need to be in a room to get the invite key\n"); return; } std::string msg = "Invite key: "; msg += currentRoom->inviteKey; addSystemMessage(msg); fprintf(stderr, "%s\n", msg.c_str()); }); // Join channel using invite key Command::add("jc", [&channelMessageMutex](const vector &args) { lock_guard lock(channelMessageMutex); if(args.size() != 2) { fprintf(stderr, "Expected 2 arguments for command jc (channel join key and message), got %u argument(s)\n", args.size()); return; } auto rooms = getRooms(); if(!rooms) return; if(!rooms->isLoggedIn()) { fprintf(stderr, "You are not logged in. Please login before joining a room\n"); return; } try { if(rooms->requestJoinRoom(args[0], args[1])) fprintf(stderr, "No error while joining room. Waiting to be added..."); } catch(std::exception &e) { fprintf(stderr, "Failed to join room %s, reason: %s", args[0].c_str(), e.what()); } }); // Scale UI Command::add("scale", [](const vector &args) { if(args.size() != 1) { fprintf(stderr, "Expected 1 argument for command scale, got %u argument(s)\n", args.size()); return; } float scaling = stof(args[0]); Settings::setScaling(scaling); printf("UI scaling set to %f\n", scaling); }); Command::add("addbind", [addSystemMessage](const vector &args) { if(args.size() != 2) { string errMsg = "Expected 2 arguments for command addbind, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } string key = ":"; key += args[0]; key += ":"; if(key.size() > 255) { // 253 = bind + two colons addSystemMessage("Bind is too long. Max size is 253 bytes"); return; } bool bindAdded = Chatbar::addBind(key, args[1]); if(bindAdded) addSystemMessage("Bind added"); else addSystemMessage("Bind already exists. Remove it first if you want to replace it"); }); Command::add("removebind", [addSystemMessage](const vector &args) { if(args.size() != 1) { string errMsg = "Expected 1 argument for command removebind, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } string key = ":"; key += args[0]; key += ":"; if(key.size() > 255) { // 253 = bind + two colons addSystemMessage("Bind is too long. Max size is 253 bytes"); return; } bool bindRemoved = Chatbar::removeBind(key); if(bindRemoved) addSystemMessage("Bind removed"); else addSystemMessage("Bind doesn't exist, nothing was removed"); }); Command::add("binds", [addSystemMessage](const vector &args) { if(args.size() != 0) { string errMsg = "Expected 0 arguments for command removebind, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } string msg = "Binds:"; auto binds = Chatbar::getBinds(); for(auto &bind : binds) { msg += "\n"; msg += bind.first; msg += " "; msg += bind.second; } addSystemMessage(msg, false); }); // Change nick of current user in current room Command::add("nick", [addSystemMessage](const vector &args) { if(args.size() != 1) { string errMsg = "Expected 1 argument for command nick, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } auto currentRoom = getCurrentRoom(); if(!currentRoom || static_cast(currentRoom->userdata)->offlineRoom) { fprintf(stderr, "You need to be in a room to change your name\n"); return; } if(args[0].size() == 0 || args[0].size() > 255) { addSystemMessage("Invalid nickname. Nickname has to be between 1 and 255 characters"); return; } currentRoom->setUserNickname(args[0]); string msg = "Your nickname was changed to "; msg += args[0]; addSystemMessage(msg); }); // Change avatar of current user in current channel Command::add("avatar", [addSystemMessage](const vector &args) { if(args.size() != 1) { string errMsg = "Expected 1 argument for command avatar, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } auto currentRoom = getCurrentRoom(); if(!currentRoom || static_cast(currentRoom->userdata)->offlineRoom) { fprintf(stderr, "You need to be in a room to change your avatar\n"); return; } if(args[0].size() < 10 || args[0].size() > 512) { addSystemMessage("Invalid avatar url size, expected to be between 10 and 512 bytes"); return; } if(looksLikeUrl(args[0])) { currentRoom->setAvatarUrl(args[0]); addSystemMessage("Your avatar has been changed (Note: max avatar size is 1 Mb, if your avatar is larger then it will not be visible)"); } else { addSystemMessage("Avatar url needs to start with either http://, https:// or www."); } }); // Change name of the current room Command::add("roomname", [addSystemMessage](const vector &args) { if(args.size() != 1) { string errMsg = "Expected 1 argument for command roomname, got "; errMsg += to_string(args.size()); errMsg += " argument(s)"; addSystemMessage(errMsg); return; } auto currentRoom = getCurrentRoom(); if(!currentRoom || static_cast(currentRoom->userdata)->offlineRoom) { fprintf(stderr, "You can't change room name size you are not in a room\n"); return; } if(args[0].size() == 0 || args[0].size() > 32) { addSystemMessage("Channel name has to be between 1 and 32 bytes long"); return; } currentRoom->setName(args[0]); string msg = "Channel name has been changed to "; msg += args[0]; addSystemMessage(msg); }); //Command::add("clearcache", [](const vector &args) //{ // printf("Cleared cache (%d bytes)\n", database->clearCache()); //}); bool running = true; Cache *cache = ResourceCache::getCache(); Rooms::connect(bootstrapNode, 27130, roomCallbackFuncs); Chatbar::loadBindsFromFile(); while (running) { std::shared_ptr currentRoom = getCurrentRoom(); sf::Event event; 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/xcb to set window minimum size instead const int minWidth = 800; if(event.size.width < minWidth) { viewRect.width = minWidth; window.setSize(sf::Vector2u(minWidth, event.size.height)); } */ sf::View view(viewRect); window.setView(view); } else if(event.type == sf::Event::MouseEntered) window.setFramerateLimit(FRAMERATE_FOCUSED); //else if(event.type == sf::Event::MouseLeft) // window.setFramerateLimit(FRAMERATE_NOT_FOCUSED); if(event.type == sf::Event::GainedFocus) windowFocused = true; else if(event.type == sf::Event::LostFocus) windowFocused = false; if(event.type == sf::Event::MouseEntered) focused = true; else if(event.type == sf::Event::MouseLeft) focused = false; if(focused) { ImagePreview::processEvent(event); } if(!ImagePreview::getPreviewContentPtr() && ImagePreview::getTimeSinceLastSeenMs() > 250) { lock_guard lock(channelMessageMutex); GlobalContextMenu::processEvent(event); if(currentRoom && currentRoom->userdata) { auto *roomContainer = static_cast(currentRoom->userdata); roomContainer->processEvent(event, cache); } } lastFocusedTimer.restart(); } //for(Channel *channel : channels) // { // channel->update(); // } if((!windowFocused || !focused) && lastFocusedTimer.getElapsedTime().asMilliseconds() > 5000) { this_thread::sleep_for(chrono::milliseconds(250)); continue; } window.clear(ColorScheme::getBackgroundColor()); { lock_guard lock(channelMessageMutex); RoomSidePanel::draw(window); if(currentRoom && currentRoom->userdata) { auto *roomContainer = static_cast(currentRoom->userdata); roomContainer->draw(window, cache); } UsersSidePanel::draw(window, cache); RoomTopPanel::draw(window); GlobalContextMenu::draw(window); ImagePreview::draw(window); } //video.draw(window); window.display(); } // We need to wait until our `ping` packet for disconnecting is sent to all channels //printf("Shutting down...\n"); //this_thread::sleep_for(chrono::seconds(3)); return 0; }