diff --git a/WinChat/WinChat.rc b/WinChat/WinChat.rc index 6286a4f..f9e0905 100644 Binary files a/WinChat/WinChat.rc and b/WinChat/WinChat.rc differ diff --git a/WinChat/WinChat.vcxproj b/WinChat/WinChat.vcxproj index e9ce627..1e4a424 100644 --- a/WinChat/WinChat.vcxproj +++ b/WinChat/WinChat.vcxproj @@ -116,6 +116,7 @@ <ClInclude Include="src\Chat.h" /> <ClInclude Include="src\pch.h" /> <ClInclude Include="src\StrConv.h" /> + <ClInclude Include="src\TSQueue.h" /> </ItemGroup> <ItemGroup> <ResourceCompile Include="WinChat.rc" /> diff --git a/WinChat/WinChat.vcxproj.filters b/WinChat/WinChat.vcxproj.filters index 07bc997..961e217 100644 --- a/WinChat/WinChat.vcxproj.filters +++ b/WinChat/WinChat.vcxproj.filters @@ -47,6 +47,9 @@ <ClInclude Include="src\StrConv.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="src\TSQueue.h"> + <Filter>Header Files</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ResourceCompile Include="WinChat.rc"> diff --git a/WinChat/src/Application.cpp b/WinChat/src/Application.cpp index 8d7fe87..81e080f 100644 --- a/WinChat/src/Application.cpp +++ b/WinChat/src/Application.cpp @@ -3,15 +3,18 @@ #include "../resource.h" #include "Chat.h" +#include "StrConv.h" //This pragma enables visual styles, which makes dialogs and their controls look modern. #pragma comment(linker,"\"/manifestdependency:type='win32' \ name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \ processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") +#define IDT_CHECKINCONN 1 + namespace wc { struct MainDlgInput { - Application* app; + Application* appPtr; int xPos, yPos; }; @@ -19,12 +22,15 @@ namespace wc { std::wstring address; std::wstring screenname; int xPos, yPos; + SOCKET inSock; }; Application::Application(std::string& appName, std::string& appVersion) : m_appName(appName.begin(), appName.end()) , m_appVersion(appVersion.begin(), appVersion.end()) , m_running(true) + , m_inSocket(INVALID_SOCKET) + , m_inAddress() { std::wcout << std::format(L"{} {}\n", m_appName, m_appVersion); @@ -45,18 +51,18 @@ namespace wc { //First, start a thread to listen for incoming connections std::thread listenThread(&Application::startListen, this); - //Then, run the main dialog to get needed input + //Then, run the main dialog to get needed input for outgoing connections INT_PTR result = NULL; MainDlgInput* input = new MainDlgInput{ this, -1, -1 }; while (result = DialogBoxParam(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_DIALOGMAIN), nullptr, reinterpret_cast<DLGPROC>(mainDlgProc), reinterpret_cast<LPARAM>(input))) { - //Then create a Chat with the collected input + //Create a Chat with the collected input or the incoming connection MainDlgOutput* output = reinterpret_cast<MainDlgOutput*>(result); - const auto [address, screenname, x, y] = *output; + const auto [address, screenname, x, y, inSocket] = *output; delete output; { Chat chat(address, screenname); - chat.run(x, y); + chat.run(x, y, inSocket); } //And repeat until the user exits from the main dialog @@ -81,9 +87,9 @@ namespace wc { unsigned long no = 0; setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char*>(&no), sizeof(no)); - if (bind(sock, hostInfo->ai_addr, static_cast<int>(hostInfo->ai_addrlen)) != 0) { - MessageBox(nullptr, L"Error: could not bind network socket!", L"Error", MB_ICONERROR); - std::exit(1); + while (bind(sock, hostInfo->ai_addr, static_cast<int>(hostInfo->ai_addrlen)) != 0) { + freeaddrinfo(hostInfo); + getaddrinfo("::", "9431", nullptr, &hostInfo); } freeaddrinfo(hostInfo); @@ -106,22 +112,12 @@ namespace wc { if (errorCode == WSAEWOULDBLOCK) continue; - ioctlsocket(conSock, FIONBIO, &no); - std::string remoteStr(INET6_ADDRSTRLEN, 0); inet_ntop(AF_INET6, &remoteAddr.sin6_addr, remoteStr.data(), INET6_ADDRSTRLEN); remoteStr.resize(remoteStr.find_first_of('\0', 0)); - std::cout << std::format("Connected to: {}\n", remoteStr); - std::string buf(1000, 0); - int bytesRecvd = 0; - while (bytesRecvd = recv(conSock, buf.data(), 1000, NULL)) { - buf.resize(bytesRecvd); - std::cout << std::format("Remote: {}\n", buf); - buf.resize(1000); - } - - closesocket(conSock); + m_inAddress = toWideStr(remoteStr); + m_inSocket = conSock; } closesocket(sock); @@ -133,9 +129,9 @@ namespace wc { switch (msg) { case WM_INITDIALOG: { MainDlgInput* in = reinterpret_cast<MainDlgInput*>(lParam); - auto [appTemp, xPos, yPos] = *in; + auto [appPtr, xPos, yPos] = *in; delete in; - app = appTemp; + app = appPtr; SetWindowText(dlg, app->m_appName.c_str()); SendMessage(dlg, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICONMAIN)))); @@ -160,7 +156,7 @@ namespace wc { SendDlgItemMessage(dlg, IDC_EDITADDRESS, EM_SETCUEBANNER, TRUE, reinterpret_cast<LPARAM>(L"Address")); SendDlgItemMessage(dlg, IDC_EDITSCREENNAME, EM_SETCUEBANNER, TRUE, reinterpret_cast<LPARAM>(L"User")); - RegisterHotKey(dlg, 1, MOD_NOREPEAT, VK_ESCAPE); + SetTimer(dlg, IDT_CHECKINCONN, 100, nullptr); return TRUE; } @@ -185,8 +181,7 @@ namespace wc { RECT dlgRect = { }; GetWindowRect(dlg, &dlgRect); - MainDlgOutput* out = new MainDlgOutput{ address, screenname, dlgRect.left, dlgRect.top }; - EndDialog(dlg, reinterpret_cast<INT_PTR>(out)); + EndDialog(dlg, reinterpret_cast<INT_PTR>(new MainDlgOutput{ address, screenname, dlgRect.left, dlgRect.top, INVALID_SOCKET})); return TRUE; } @@ -197,16 +192,26 @@ namespace wc { case IDC_BUTTONEXIT: case ID_FILE_EXIT: + case IDCANCEL: //Escape key press PostMessage(dlg, WM_CLOSE, NULL, NULL); return TRUE; } return FALSE; - case WM_HOTKEY: - if (wParam != 1 || GetForegroundWindow() != dlg) - return FALSE; - [[fallthrough]]; + case WM_TIMER: + if (wParam == IDT_CHECKINCONN && app->m_inSocket != INVALID_SOCKET) { + KillTimer(dlg, IDT_CHECKINCONN); + //Ask if to connect and for screen name here + RECT dlgRect = { }; + GetWindowRect(dlg, &dlgRect); + EndDialog(dlg, reinterpret_cast<INT_PTR>(new MainDlgOutput{ app->m_inAddress, L"Temp screen name", dlgRect.left, dlgRect.top, app->m_inSocket })); + app->m_inSocket = INVALID_SOCKET; + return TRUE; + } + + return FALSE; + case WM_CLOSE: EndDialog(dlg, 0); return TRUE; diff --git a/WinChat/src/Application.h b/WinChat/src/Application.h index a0d5c20..08d8f74 100644 --- a/WinChat/src/Application.h +++ b/WinChat/src/Application.h @@ -1,5 +1,6 @@ #pragma once #include <string> +#include <atomic> #include <Windows.h> @@ -18,5 +19,8 @@ namespace wc { const std::wstring m_appName; const std::wstring m_appVersion; bool m_running; + + std::atomic<SOCKET> m_inSocket; + std::wstring m_inAddress; }; } \ No newline at end of file diff --git a/WinChat/src/Chat.cpp b/WinChat/src/Chat.cpp index 14b08b6..af4685a 100644 --- a/WinChat/src/Chat.cpp +++ b/WinChat/src/Chat.cpp @@ -4,11 +4,12 @@ #include "StrConv.h" #include "../resource.h" -#define IDT_CHECKCONN 1 +#define IDT_CHECKCONN 2 +#define IDT_UPDATECHAT 3 namespace wc { - struct ConnDlgInput { - Chat* chat; + struct ChatDlgInput { + Chat* chatPtr; int xPos, yPos; }; @@ -21,55 +22,81 @@ namespace wc { } - void Chat::run(int xPos, int yPos) { + void Chat::run(int xPos, int yPos, SOCKET socket) { //Run net thread to connect and communicate - std::thread netThread(&Chat::runNetThread, this); + std::thread netThread(&Chat::runNetThread, this, socket); - //Show connection dialog until net thread connects - ConnDlgInput* input = new ConnDlgInput{ this, xPos, yPos }; - DialogBoxParam(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_DIALOGCONNECTING), nullptr, reinterpret_cast<DLGPROC>(connDlgProc), reinterpret_cast<LPARAM>(input)); + //Show connection dialog until net thread connects unless the listen thread already did that + ChatDlgInput input = { this, xPos, yPos }; + if (!m_connected) + DialogBoxParam(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_DIALOGCONNECTING), nullptr, reinterpret_cast<DLGPROC>(connDlgProc), reinterpret_cast<LPARAM>(&input)); - //If we're connected, open the chat window + //Either way now, if we're connected, open the chat window if (m_connected) - DialogBoxParam(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_DIALOGCHAT), nullptr, reinterpret_cast<DLGPROC>(chatDlgProc), reinterpret_cast<LPARAM>(this)); + DialogBoxParam(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_DIALOGCHAT), nullptr, reinterpret_cast<DLGPROC>(chatDlgProc), reinterpret_cast<LPARAM>(&input)); netThread.join(); } - void Chat::runNetThread() { + void Chat::runNetThread(SOCKET inSocket) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); - addrinfo* destInfo = nullptr; - if (getaddrinfo(toStr(m_address).c_str(), "9430", nullptr, &destInfo) != 0) { - MessageBox(nullptr, L"Error: Could not resolve host or invalid address!", L"Error", MB_ICONERROR); - m_connectionError = true; - return; - } - - SOCKET sock = socket(destInfo->ai_family, SOCK_STREAM, NULL); + SOCKET sock = inSocket; if (sock == INVALID_SOCKET) { - MessageBox(nullptr, L"Error: could not create network socket!", L"Error", MB_ICONERROR); + addrinfo* destInfo = nullptr; + if (getaddrinfo(toStr(m_address).c_str(), "9430", nullptr, &destInfo) != 0) { + MessageBox(nullptr, L"Error: Could not resolve host or invalid address!", L"Error", MB_ICONERROR); + m_connectionError = true; + return; + } + + sock = socket(destInfo->ai_family, SOCK_STREAM, NULL); + if (sock == INVALID_SOCKET) { + MessageBox(nullptr, L"Error: could not create network socket!", L"Error", MB_ICONERROR); + freeaddrinfo(destInfo); + m_connectionError = true; + return; + } + + if (connect(sock, destInfo->ai_addr, static_cast<int>(destInfo->ai_addrlen)) != 0) { + std::wstring errorStr = getErrorString(); + MessageBox(nullptr, std::format(L"Error: Could not connect - {}", errorStr).c_str(), L"Error", MB_ICONERROR); + freeaddrinfo(destInfo); + closesocket(sock); + m_connectionError = true; + return; + } + freeaddrinfo(destInfo); - m_connectionError = true; - return; } - if (connect(sock, destInfo->ai_addr, static_cast<int>(destInfo->ai_addrlen)) != 0) { - std::wstring errorStr = getErrorString(); - MessageBox(nullptr, std::format(L"Error: Could not connect - {}", errorStr).c_str(), L"Error", MB_ICONERROR); - freeaddrinfo(destInfo); - closesocket(sock); - m_connectionError = true; - return; - } - - freeaddrinfo(destInfo); + unsigned long yes = 1; + ioctlsocket(sock, FIONBIO, &yes); m_connected = true; - std::string data; - while (data != "end") { - std::getline(std::cin, data); - send(sock, data.data(), static_cast<int>(data.size()), NULL); + std::wstring recvBuffer(2000, 0); + std::wstring sendStr; + int bytesRecvd = 0; + while (m_connected) { + while (!m_sendQueue.empty()) { + sendStr = m_sendQueue.pop(); + send(sock, reinterpret_cast<const char*>(sendStr.c_str()), static_cast<int>(sendStr.size()) * sizeof(wchar_t), NULL); + } + + bytesRecvd = recv(sock, reinterpret_cast<char*>(recvBuffer.data()), 2000, NULL); + + if (!bytesRecvd) { + m_connected = false; + break; + } + + if (WSAGetLastError() != WSAEWOULDBLOCK) { + recvBuffer.resize(bytesRecvd / 2); + m_recvQueue.push(recvBuffer); + recvBuffer.assign(2000, 0); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } closesocket(sock); @@ -109,11 +136,10 @@ namespace wc { switch (msg) { case WM_INITDIALOG: { - ConnDlgInput* in = reinterpret_cast<ConnDlgInput*>(lParam); - auto [chatTemp, x, y] = *in; - delete in; - chat = chatTemp; - SetWindowPos(dlg, nullptr, x + 100, y + 100, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + ChatDlgInput* in = reinterpret_cast<ChatDlgInput*>(lParam); + chat = in->chatPtr; + + SetWindowPos(dlg, nullptr, in->xPos + 100, in->yPos + 100, 0, 0, SWP_NOSIZE | SWP_NOZORDER); SetDlgItemText(dlg, IDC_STATICADDRESS, chat->m_address.c_str()); SendDlgItemMessage(dlg, IDC_PROGRESS, PBM_SETMARQUEE, TRUE, NULL); SetTimer(dlg, IDT_CHECKCONN, 100, nullptr); @@ -135,20 +161,93 @@ namespace wc { static Chat* chat = nullptr; switch (msg) { - case WM_INITDIALOG: - chat = reinterpret_cast<Chat*>(lParam); + case WM_INITDIALOG: { + ChatDlgInput* in = reinterpret_cast<ChatDlgInput*>(lParam); + chat = in->chatPtr; + SetWindowText(dlg, std::format(L"remote screenname at {} - WinChat", chat->m_address).c_str()); SendMessage(dlg, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICONMAIN)))); + + SetWindowPos(dlg, nullptr, in->xPos - 50, in->yPos - 50, 0, 0, SWP_NOSIZE | SWP_NOZORDER); + + SendDlgItemMessage(dlg, IDC_EDITCHATINPUT, EM_SETCUEBANNER, TRUE, reinterpret_cast<LPARAM>(L"Message")); + + SetDlgItemText(dlg, IDC_EDITCHATDISPLAY, std::format(L"Connected to remote screenname at {}", chat->m_address).c_str()); + + SetTimer(dlg, IDT_UPDATECHAT, 100, nullptr); + return TRUE; + } + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_BUTTONSEND: { + int inputLength = GetWindowTextLength(GetDlgItem(dlg, IDC_EDITCHATINPUT)); + if (!inputLength) { + EDITBALLOONTIP balloon = { .cbStruct = sizeof(balloon), .pszTitle = L"Alert", .pszText = L"Enter your message here." }; + SendDlgItemMessage(dlg, IDC_EDITCHATINPUT, EM_SHOWBALLOONTIP, NULL, reinterpret_cast<LPARAM>(&balloon)); + return TRUE; + } + + std::wstring buffer(inputLength, 0); + GetDlgItemText(dlg, IDC_EDITCHATINPUT, buffer.data(), static_cast<int>(buffer.size() + 1)); + chat->m_sendQueue.push(buffer); + + chat->addMessage(dlg, buffer); + + SetDlgItemText(dlg, IDC_EDITCHATINPUT, L""); + + return TRUE; + } + + case IDC_BUTTONDISCONNECT: + case IDCANCEL: + PostMessage(dlg, WM_CLOSE, NULL, NULL); + return TRUE; + } + + return FALSE; + + case WM_TIMER: + if (wParam == IDT_UPDATECHAT) { + if (!chat->m_connected) + EndDialog(dlg, 0); + + std::wstring remoteStr; + while (!chat->m_recvQueue.empty()) { + remoteStr = chat->m_recvQueue.pop(); + chat->addMessage(dlg, remoteStr); + } + + return TRUE; + } + + return FALSE; case WM_CLOSE: - EndDialog(dlg, 0); + chat->m_connected = false; return TRUE; } return FALSE; } + void Chat::addMessage(HWND dlg, std::wstring& in) { + SCROLLINFO si = { .cbSize = sizeof(si), .fMask = SIF_POS | SIF_PAGE | SIF_RANGE }; + GetScrollInfo(GetDlgItem(dlg, IDC_EDITCHATDISPLAY), SB_VERT, &si); + bool shouldScroll = static_cast<int>(si.nPage) + si.nPos > si.nMax; + + std::wstring buffer(GetWindowTextLength(GetDlgItem(dlg, IDC_EDITCHATDISPLAY)), 0); + GetDlgItemText(dlg, IDC_EDITCHATDISPLAY, buffer.data(), static_cast<int>(buffer.size() + 1)); + buffer += std::format(L"\r\n{}", in); + SetDlgItemText(dlg, IDC_EDITCHATDISPLAY, buffer.c_str()); + + if (shouldScroll) { + SendDlgItemMessage(dlg, IDC_EDITCHATDISPLAY, EM_SETSEL, 0, -1); + SendDlgItemMessage(dlg, IDC_EDITCHATDISPLAY, EM_SETSEL, -1, -1); + SendDlgItemMessage(dlg, IDC_EDITCHATDISPLAY, EM_SCROLLCARET, 0, 0); + } + } Chat::~Chat() { diff --git a/WinChat/src/Chat.h b/WinChat/src/Chat.h index 9a8d74b..4763f2e 100644 --- a/WinChat/src/Chat.h +++ b/WinChat/src/Chat.h @@ -3,23 +3,29 @@ #include <Windows.h> +#include "TSQueue.h" + namespace wc { class Chat { public: Chat(std::wstring address, std::wstring screenname); - void run(int xPos, int yPos); + void run(int xPos, int yPos, SOCKET socket); ~Chat(); private: - void runNetThread(); + void runNetThread(SOCKET inSocket); std::wstring getErrorString(); static BOOL CALLBACK connDlgProc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam); static BOOL CALLBACK chatDlgProc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam); + void addMessage(HWND dlg, std::wstring& str); const std::wstring m_address; const std::wstring m_screenname; bool m_connected; bool m_connectionError; + + TSQueue<std::wstring> m_sendQueue; + TSQueue<std::wstring> m_recvQueue; }; } \ No newline at end of file diff --git a/WinChat/src/TSQueue.h b/WinChat/src/TSQueue.h new file mode 100644 index 0000000..cf00c0b --- /dev/null +++ b/WinChat/src/TSQueue.h @@ -0,0 +1,38 @@ +#pragma once +#include <mutex> +#include <queue> + +namespace wc { + template<typename T> + class TSQueue { + public: + TSQueue() + : m_m() + , m_q() + { + + } + + void push(T t) { + std::lock_guard lock(m_m); + m_q.push(t); + } + + T pop() { + std::lock_guard lock(m_m); + T t = std::move(m_q.front()); + m_q.pop(); + return t; + } + + bool empty() { + std::lock_guard lock(m_m); + return m_q.empty(); + } + + ~TSQueue() { } + private: + std::mutex m_m; + std::queue<T> m_q; + }; +} \ No newline at end of file diff --git a/WinChat/src/pch.h b/WinChat/src/pch.h index b6e077a..4999a58 100644 --- a/WinChat/src/pch.h +++ b/WinChat/src/pch.h @@ -4,6 +4,9 @@ #include <iostream> #include <format> #include <thread> +#include <mutex> +#include <atomic> +#include <queue> //Windows #define WIN32_LEAN_AND_MEAN