From 3db205de39289ee249cd4587ede88249963201f7 Mon Sep 17 00:00:00 2001 From: Joursoir Date: Sun, 22 Nov 2020 01:34:39 +0300 Subject: start feature: clui --- Makefile | 2 +- src/client/client.cpp | 157 ++++++++++++++++++++++---------------------------- src/client/clui.cpp | 154 +++++++++++++++++++++++++++++++++++++++++++++++++ src/client/clui.hpp | 57 ++++++++++++++++++ src/client/user.cpp | 122 +++++++++++++++++++++++++++++++++++++++ src/client/user.hpp | 29 ++++++++++ 6 files changed, 433 insertions(+), 88 deletions(-) create mode 100644 src/client/clui.cpp create mode 100644 src/client/clui.hpp create mode 100644 src/client/user.cpp create mode 100644 src/client/user.hpp diff --git a/Makefile b/Makefile index 1c55ab6..26ef7b4 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ OBJECTS = client.o all: client client: - g++ $(CPPFLAGS) $(SOURCES) -o client + $(CPP) $(CPPFLAGS) $(SOURCES) -o client clean: rm -rf src/client/client.o client \ No newline at end of file diff --git a/src/client/client.cpp b/src/client/client.cpp index f09b349..8ad25d8 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1,107 +1,90 @@ -#include // for close -#include -#include -#include // sockaddr_in -#include // for bind, connect -#include // for bind, connect -#include // for iten_aton, iten_ntoa -#include // for iten_aton, iten_ntoa -#include // for fcntl +#include +#include +#include +#include "user.hpp" + +#define EXIT_BUTTON 4 #define SERVER_IP "127.0.0.1" static int port = 7777; +int showMainMenu(int max_row, int max_col) +{ + int num_items = 5; + const char *items[num_items] = { + "Connect to room", + "Create room", + "Help", + "About WantChat", + "Exit" + }; + + int height_menu = num_items + 2; + int width_menu = strlen(items[0]) + 2; + + SelectionMenu main_menu = SelectionMenu("Hello. Welcome to \ + WantChat", items, num_items, height_menu, width_menu, + (max_row-height_menu)/2, (max_col-width_menu)/2, 0); + main_menu.Update(); + + keypad(main_menu.GetWindow(), true); + int choice = main_menu.Handling(); + main_menu.Hide(); + return choice; + // дескриптор будет вызван после выхода из функции неявно +} + int main(int argc, char *argv[]) { - int client = socket(AF_INET, SOCK_STREAM, 0); - if(client == -1) { - perror("socket"); + initscr(); + cbreak(); + noecho(); + // keypad(stdscr, TRUE); + curs_set(false); + + int rows, columns; + getmaxyx(stdscr, rows, columns); + if(rows != 24 || columns != 80) { + endwin(); + printf("Please use terminal with size 24x80\n"); return 1; } - // remove "port sticking" aka socket in TIME_WAIT - int opt = 1; - setsockopt(client, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); - - /* call bind optional, the system chooses - the address automatically */ - - struct sockaddr_in server_adress; - server_adress.sin_family = AF_INET; - server_adress.sin_port = htons(port); - if(!inet_aton(SERVER_IP, &(server_adress.sin_addr))) { - perror("inet_aton"); + Client *user = Client::Start(SERVER_IP, port); + if(!user) { + endwin(); + perror("client"); return 1; } - - int res = connect(client, (struct sockaddr*) &server_adress, - sizeof(server_adress)); - if(res == -1) { - perror("connect"); - return 1; - } - - printf("=> Connection to server %s with port number: %d\n", - inet_ntoa(server_adress.sin_addr), port); - - // work with server - char buffer[1024]; - memset(buffer, 0, sizeof(buffer)); // clear - - int fd_stdin = STDIN_FILENO; - const int flags = fcntl(fd_stdin, F_GETFL, 0); - fcntl(fd_stdin, F_SETFL, flags | O_NONBLOCK); - - bool exit = false; - do { - struct timeval *timeout; - timeout->tv_sec = 0; - timeout->tv_usec = 5000000; - fd_set rds, wrs; - FD_ZERO(&rds); - FD_ZERO(&wrs); + int choice; + while( (choice = showMainMenu(rows, columns)) != EXIT_BUTTON) + { + switch(choice) + { + case 0: { + ChatRoom *room = new ChatRoom(); + user->Run(room); - FD_SET(client, &rds); - - int val = read(fd_stdin, buffer, sizeof(buffer)); - if(val > 0) FD_SET(fd_stdin, &wrs); - - res = select(client+1, &rds, &wrs, 0, timeout); - if(res < 0) { - if(errno == EINTR) - continue; - else { - perror("select"); + delete room; break; } } + } - if(res > 0) { - if(FD_ISSET(fd_stdin, &wrs)) // if client want to send any message - { - if(buffer[0] == '#') // exit - exit = true; - else write(client, buffer, strlen(buffer)*sizeof(char)); - - memset(buffer, 0, sizeof(buffer)); // clear - } - if(FD_ISSET(client, &rds)) // if server want to send any message - { - read(client, buffer, sizeof(buffer)); - - printf("%s", buffer); - getchar(); /* FIXME: if you remove this thing, - then output will not be produced only - if you add '\n' to the end of the line */ + endwin(); + return 0; +} - memset(buffer, 0, sizeof(buffer)); // clear - } - } - } while (!exit); + /*WINDOW *chat = newwin(21, 59, 0, 0); + box(chat, 0, 0); + wrefresh(chat); - printf("=> GoodBye...\n"); + WINDOW *players = newwin(21, 20, 0, 60); + box(players, 0, 0); + wrefresh(players); - close(client); - return 0; -} \ No newline at end of file + WINDOW *input = newwin(2, 80, 22, 0); + //box(input, ' ', ' '); + wmove(input, 0, 2); + wrefresh(input);*/ \ No newline at end of file diff --git a/src/client/clui.cpp b/src/client/clui.cpp new file mode 100644 index 0000000..4ae6a5a --- /dev/null +++ b/src/client/clui.cpp @@ -0,0 +1,154 @@ +#include +#include + +#include "clui.hpp" + +#define CHAT_WIDTH 20 +#define CHAT_HEIGHT 59 +#define PLAYERS_WIDTH 20 +#define PLAYERS_HEIGHT 20 +#define INPUT_WIDTH 4 +#define INPUT_HEIGHT 80 + +#define MAXLEN_MSG 158 + +Interface_wc::Interface_wc(int num_y, int num_x, int by, + int bx, char ch) +{ + w = newwin(num_y, num_x, by, bx); + box(w, ch, ch); + this->Update(); +} + +void Interface_wc::Hide() +{ + werase(this->GetWindow()); // clear + + this->Update(); + this->Delete(); +} + +//////////////////////////////////////////////////////////////////////// + +SelectionMenu::SelectionMenu(char const *i_title, const char**i_choises, int choises_num, + int num_y, int num_x, int by, int bx, char ch) + : Interface_wc(num_y, num_x, by, bx, ch), title(i_title), + choises(i_choises), choises_number(choises_num), current_choice(0) +{ } + +int SelectionMenu::Handling() +{ + WINDOW *menu = this->GetWindow(); + while(true) { + for(int i = 0; i < choises_number; i++) + { + // remake: + if(i == current_choice) + wattron(menu, A_REVERSE); + mvwprintw(menu, i+1, 1, choises[i]); + wattroff(menu, A_REVERSE); + } + + switch(wgetch(menu)) + { + case KEY_UP: + { + current_choice--; + if(current_choice < 0) current_choice = choises_number-1; + break; + } + case KEY_DOWN: + { + current_choice++; + if(current_choice > choises_number-1) current_choice = 0; + break; + } + case '\n': return current_choice; + default: break; + } + } +} + +//////////////////////////////////////////////////////////////////////// + +ChatRoom::ChatRoom() +{ + chat = new Interface_wc(CHAT_WIDTH, CHAT_HEIGHT, 0, 0, 0); + players = new Interface_wc(PLAYERS_WIDTH, PLAYERS_HEIGHT, 0, 60, 0); + input = new Interface_wc(INPUT_WIDTH, INPUT_HEIGHT, 20, 0, 0); + i_nx = 1; + i_ny = 1; + + /* WINDOW *newwin(int nlines, int ncols, int begin_y, int begin_x) */ + /*WINDOW *chat = newwin(20, 59, 0, 0); + box(chat, 0, 0); + wrefresh(chat); + + WINDOW *players = newwin(20, 20, 0, 60); + box(players, 0, 0); + wrefresh(players); + + WINDOW *input = newwin(4, 80, 20, 0); + box(input, 0, 0); + wmove(input, 1, 1); + wrefresh(input);*/ +} + +ChatRoom::~ChatRoom() +{ + if(chat) + delete chat; + if(players) + delete players; + if(input) + delete input; +} + +void ChatRoom::PrintMessage(const char *msg) +{ + // если msg <= **, то одна строка + // иначе 2 строки + WINDOW *win = chat->GetWindow(); + wmove(win, 1, 2); + wprintw(win, msg); + + chat->Update(); +} + +bool ChatRoom::AddCharToSendMsg(char ch) +{ + if(i_ny == 2 && i_nx == MAXLEN_MSG/2-1) + return 0; + + mvwaddch(input->GetWindow(), i_ny, i_nx, ch); + i_nx++; + if(i_nx >= MAXLEN_MSG/2) { + if(i_ny == 1) { + i_ny++; + i_nx = 1; + } + else if(i_ny == 2) + i_nx--; + wmove(input->GetWindow(), i_ny, i_nx); + } + + input->Update(); + return 1; +} + +bool ChatRoom::RemoveCharFromMsg() +{ + if(i_ny == 1 && i_nx == 1) + return 0; + + i_nx--; + if(i_nx < 1) { + i_ny--; + i_nx = MAXLEN_MSG/2-1; + } + mvwaddch(input->GetWindow(), i_ny, i_nx, ' '); + wmove(input->GetWindow(), i_ny, i_nx); + + input->Update(); + return 1; +} \ No newline at end of file diff --git a/src/client/clui.hpp b/src/client/clui.hpp new file mode 100644 index 0000000..ff447ef --- /dev/null +++ b/src/client/clui.hpp @@ -0,0 +1,57 @@ +// CLUI - Command Line User Interface + +#ifndef COMMANDLINEUI_H +#define COMMANDLINEUI_H + +#include + +class Interface_wc { + WINDOW *w; + int ny, nx; + int beg_y, beg_x; +public: + Interface_wc(int num_y, int num_x, int by, int bx, char ch); + + WINDOW *GetWindow() { return w; } + void Update() { wrefresh(w); } + void Delete() { delwin(w); } + + void Hide(); +}; + +class SelectionMenu : public Interface_wc { + char const *title; + const char**choises; + int choises_number; + int current_choice; + +public: + SelectionMenu(char const *i_title, const char**i_choises, int choises_num, + int num_y, int num_x, int by, int bx, char ch); + + int Handling(); +}; + +class ChatRoom { + Interface_wc *chat; + Interface_wc *players; + Interface_wc *input; + int i_nx, i_ny; +public: + ChatRoom(); + ~ChatRoom(); + + // for chat: + void PrintMessage(const char *msg); + + // for players: + //void AddPlayer() + + // for input: + bool AddCharToSendMsg(char ch); + bool RemoveCharFromMsg(); + + WINDOW *GetWin() { return input->GetWindow(); } +}; + +#endif \ No newline at end of file diff --git a/src/client/user.cpp b/src/client/user.cpp new file mode 100644 index 0000000..1a60891 --- /dev/null +++ b/src/client/user.cpp @@ -0,0 +1,122 @@ +#include +#include // for close +#include // for iten_aton, iten_ntoa, sockaddr_in +#include // for iten_aton, iten_ntoa +#include // for bind, connect +#include // for bind, connect +#include // for fcntl +#include // for errno +#include + +#include "user.hpp" + +const int key_enter = 10; +const int key_escape = 27; +const int key_backspace = 127; + +Client *Client::Start(const char* ip, int port) +{ + int client; + client = socket(AF_INET, SOCK_STREAM, 0); + if(client == -1) return 0; + + // remove "port sticking" aka socket in TIME_WAIT + int opt = 1; + setsockopt(client, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + /* call bind optional, the system chooses + the address automatically */ + + struct sockaddr_in server_adress; + server_adress.sin_family = AF_INET; + server_adress.sin_port = htons(port); + if(!inet_aton(ip, &(server_adress.sin_addr))) return 0; + + int res = connect(client, (struct sockaddr*) &server_adress, + sizeof(server_adress)); + if(res == -1) return 0; + + return new Client(client); +} + +void Client::Run(ChatRoom *room) +{ + /*int fd_stdin = STDIN_FILENO; + const int flags = fcntl(fd_stdin, F_GETFL, 0); + fcntl(fd_stdin, F_SETFL, flags | O_NONBLOCK);*/ + + const int flags = fcntl(fd, F_GETFL, 0); + fcntl(fd, F_SETFL, flags | O_NONBLOCK); + + nodelay(room->GetWin(), true); + curs_set(true); + do { + this->HandleButton(room); + + int recive = read(fd, out_buffer, sizeof(out_buffer)); + if(recive < 0) { + if(errno == EINTR || errno == EAGAIN) continue; + else break; + } + else if(recive > 0) { + room->PrintMessage(out_buffer); + } + + unsigned int usecs = 0125000; + usleep(usecs); + + /*struct timeval timeout = { 0 }; + timeout.tv_usec = 5000000; + + fd_set rds, wrs; + FD_ZERO(&rds); + FD_ZERO(&wrs); + + FD_SET(fd, &rds); + + int val = read(fd_stdin, buffer, sizeof(buffer)); + if(val > 0) FD_SET(fd_stdin, &wrs); + + res = select(fd+1, &rds, &wrs, 0, &timeout); + if(res < 0) { + if(errno == EINTR) continue; + else break; + } + else if(res > 0) { + if(FD_ISSET(fd_stdin, &wrs)) // if client want to send any message + { + if(buffer[0] == '#') // exit + exit_flag = true; + else write(fd, buffer, strlen(buffer)*sizeof(char)); + } + if(FD_ISSET(fd, &rds)) // if server want to send any message + { + read(fd, buffer, sizeof(buffer)); + //getchar(); + } + }*/ + } while (!exit_flag); +} + +void Client::HandleButton(ChatRoom *room) +{ + int key = wgetch(room->GetWin()); + switch(key) + { + // ascii table 32...126 + case ' '...'~': { + room->AddCharToSendMsg(key); + break; + } + case '\n': { // send message + // some code + break; + } + case key_backspace: { + room->RemoveCharFromMsg(); + break; + } + default: break; + } + +} \ No newline at end of file diff --git a/src/client/user.hpp b/src/client/user.hpp new file mode 100644 index 0000000..a6e6c15 --- /dev/null +++ b/src/client/user.hpp @@ -0,0 +1,29 @@ +#ifndef USER_H +#define USER_H + +#include "clui.hpp" + +const int max_line_length = 156; + +class Client { + int fd; + char in_buffer[max_line_length]; // мы готовим к отправке + char in_buf_used; + char out_buffer[max_line_length]; // нам пришло + char out_buf_used; + bool ignoring; + bool exit_flag; + + Client(int i_fd) + : fd(i_fd), in_buf_used(0), out_buf_used(0), ignoring(false) { } +public: + ~Client() { close(fd); } + + static Client *Start(const char* ip, int port); + void Run(ChatRoom *room); + void HandleButton(ChatRoom *room); + + int getFd() const { return fd; } // not used +}; + +#endif \ No newline at end of file -- cgit v1.2.3-18-g5258