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 @@
+
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 @@
Header Files
+
+ Header Files
+
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(mainDlgProc), reinterpret_cast(input))) {
- //Then create a Chat with the collected input
+ //Create a Chat with the collected input or the incoming connection
MainDlgOutput* output = reinterpret_cast(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(&no), sizeof(no));
- if (bind(sock, hostInfo->ai_addr, static_cast(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(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(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(LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICONMAIN))));
@@ -160,7 +156,7 @@ namespace wc {
SendDlgItemMessage(dlg, IDC_EDITADDRESS, EM_SETCUEBANNER, TRUE, reinterpret_cast(L"Address"));
SendDlgItemMessage(dlg, IDC_EDITSCREENNAME, EM_SETCUEBANNER, TRUE, reinterpret_cast(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(out));
+ EndDialog(dlg, reinterpret_cast(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(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
+#include
#include
@@ -18,5 +19,8 @@ namespace wc {
const std::wstring m_appName;
const std::wstring m_appVersion;
bool m_running;
+
+ std::atomic 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(connDlgProc), reinterpret_cast(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(connDlgProc), reinterpret_cast(&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(chatDlgProc), reinterpret_cast(this));
+ DialogBoxParam(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_DIALOGCHAT), nullptr, reinterpret_cast(chatDlgProc), reinterpret_cast(&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(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(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(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(sendStr.c_str()), static_cast(sendStr.size()) * sizeof(wchar_t), NULL);
+ }
+
+ bytesRecvd = recv(sock, reinterpret_cast(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(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(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(lParam);
+ case WM_INITDIALOG: {
+ ChatDlgInput* in = reinterpret_cast(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(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(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(&balloon));
+ return TRUE;
+ }
+
+ std::wstring buffer(inputLength, 0);
+ GetDlgItemText(dlg, IDC_EDITCHATINPUT, buffer.data(), static_cast(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(si.nPage) + si.nPos > si.nMax;
+
+ std::wstring buffer(GetWindowTextLength(GetDlgItem(dlg, IDC_EDITCHATDISPLAY)), 0);
+ GetDlgItemText(dlg, IDC_EDITCHATDISPLAY, buffer.data(), static_cast(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
+#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 m_sendQueue;
+ TSQueue 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
+#include
+
+namespace wc {
+ template
+ 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 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
#include
#include
+#include
+#include
+#include
//Windows
#define WIN32_LEAN_AND_MEAN