diff --git a/WinChat/WinChat.rc b/WinChat/WinChat.rc index 0116b46..ab3e20c 100644 Binary files a/WinChat/WinChat.rc and b/WinChat/WinChat.rc differ diff --git a/WinChat/resource.h b/WinChat/resource.h index afd17b3..845888e 100644 --- a/WinChat/resource.h +++ b/WinChat/resource.h @@ -22,16 +22,18 @@ #define IDC_BUTTONSEND 1014 #define IDC_BUTTONDISCONNECT 1015 #define IDC_STATICREMOTEINFO 1017 +#define IDC_STATICLISTENPORT 1018 #define ID_FILE_EXIT 40001 #define ID_HELP_ABOUT 40002 +#define ID_HELP_QUCKSTART 40003 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 113 -#define _APS_NEXT_COMMAND_VALUE 40003 -#define _APS_NEXT_CONTROL_VALUE 1018 +#define _APS_NEXT_COMMAND_VALUE 40004 +#define _APS_NEXT_CONTROL_VALUE 1019 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif diff --git a/WinChat/src/Application.cpp b/WinChat/src/Application.cpp index 9e56f07..d3e76d4 100644 --- a/WinChat/src/Application.cpp +++ b/WinChat/src/Application.cpp @@ -20,17 +20,19 @@ namespace wc { struct MainDlgOutput { std::wstring address; + std::wstring port; std::wstring screenname; int xPos, yPos; SOCKET inSock; }; - Application::Application(std::string& appName, std::string& appVersion) + Application::Application(const std::string& appName, const std::string& appVersion, const int defaultPort) : m_appName(appName.begin(), appName.end()) , m_appVersion(appVersion.begin(), appVersion.end()) - , m_running(true) + , m_defaultPort(defaultPort) + , m_listenPort() + , m_running(false) , m_inSocket(INVALID_SOCKET) - , m_inAddress() { std::wcout << std::format(L"{} {}\n", m_appName, m_appVersion); @@ -51,17 +53,21 @@ namespace wc { //First, start a thread to listen for incoming connections std::thread listenThread(&Application::startListen, this); + //And wait until we are successfully listening + while (!m_running) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + //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))) { //Create a Chat with the collected input or the incoming connection MainDlgOutput* output = reinterpret_cast(result); - const auto [address, screenname, x, y, inSocket] = *output; + const auto [address, port, screenname, x, y, inSocket] = *output; delete output; { - Chat chat(address, screenname); + Chat chat(address, port, screenname); chat.run(x, y, inSocket); } @@ -74,9 +80,6 @@ namespace wc { } void Application::startListen() { - addrinfo* hostInfo = nullptr; - getaddrinfo("::", "9430", nullptr, &hostInfo); - SOCKET sock = socket(AF_INET6, SOCK_STREAM, NULL); if (sock == INVALID_SOCKET) { MessageBox(nullptr, L"Error: could not create network socket!", L"Error", MB_ICONERROR); @@ -87,16 +90,23 @@ namespace wc { unsigned long no = 0; setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&no), sizeof(no)); - while (bind(sock, hostInfo->ai_addr, static_cast(hostInfo->ai_addrlen)) != 0) { - freeaddrinfo(hostInfo); - getaddrinfo("::", "9431", nullptr, &hostInfo); - } + int tryPort = m_defaultPort - 1; + addrinfo* hostInfo = nullptr; + do { + freeaddrinfo(hostInfo); + tryPort++; + getaddrinfo("::", std::to_string(tryPort).c_str(), nullptr, &hostInfo); + } while (bind(sock, hostInfo->ai_addr, static_cast(hostInfo->ai_addrlen)) != 0); + + m_listenPort = tryPort; freeaddrinfo(hostInfo); listen(sock, 1); ioctlsocket(sock, FIONBIO, &yes); + m_running = true; + sockaddr_in6 remoteAddr = { }; int remoteSize = sizeof(remoteAddr); SOCKET conSock = INVALID_SOCKET; @@ -160,8 +170,9 @@ namespace wc { HFONT font = CreateFontIndirect(&lFont); SendMessage(GetDlgItem(dlg, IDC_STATICTITLE), WM_SETFONT, reinterpret_cast(font), NULL); SetDlgItemText(dlg, IDC_STATICDESC, L"A simple Windows chat app"); - SendDlgItemMessage(dlg, IDC_EDITADDRESS, EM_SETCUEBANNER, TRUE, reinterpret_cast(L"Address")); + SendDlgItemMessage(dlg, IDC_EDITADDRESS, EM_SETCUEBANNER, TRUE, reinterpret_cast(L"Address(/Port)")); SendDlgItemMessage(dlg, IDC_EDITSCREENNAME, EM_SETCUEBANNER, TRUE, reinterpret_cast(L"User")); + SetDlgItemText(dlg, IDC_STATICLISTENPORT, std::format(L"Listening on port {}", app->m_listenPort).c_str()); SetTimer(dlg, IDT_CHECKINCONN, 100, nullptr); @@ -186,17 +197,43 @@ namespace wc { screenname.resize(screennameSize); GetDlgItemText(dlg, IDC_EDITSCREENNAME, screenname.data(), static_cast(screenname.size() + 1)); + //Check port if present + std::wstring port; + size_t portStart = address.find(L"/"); + if (portStart != std::wstring::npos) { + port = address.substr(portStart + 1); + address.resize(portStart); + bool isANumber = std::all_of(port.begin(), port.end(), [](wchar_t in) {return std::isdigit(in); }); + + if (port.empty() || !isANumber || std::stoi(port) < 1024 || std::stoi(port) > 49151) { + EDITBALLOONTIP balloon = { .cbStruct = sizeof(balloon), .pszTitle = L"Alert", .pszText = L"An invalid port was entered." }; + SendDlgItemMessage(dlg, IDC_EDITADDRESS, EM_SHOWBALLOONTIP, NULL, reinterpret_cast(&balloon)); + return TRUE; + } + } + else + port = std::to_wstring(app->m_defaultPort); + RECT dlgRect = { }; GetWindowRect(dlg, &dlgRect); - EndDialog(dlg, reinterpret_cast(new MainDlgOutput{ address, screenname, dlgRect.left, dlgRect.top, INVALID_SOCKET })); + EndDialog(dlg, reinterpret_cast(new MainDlgOutput{ address, port, screenname, dlgRect.left, dlgRect.top, INVALID_SOCKET })); return TRUE; } - case ID_HELP_ABOUT: { - MessageBox(dlg, std::format(L"{} {}\nGrayson Riffe 2023", app->m_appName, app->m_appVersion).c_str(), L"About", MB_OK); + case ID_HELP_QUCKSTART: { + std::wstring helpStr = L"To start a chat, start the program on both computers\n" + "and enter the IP address or hostname of one into the other's address box.\n\n" + "If the program is not listening on the default port of {}, the\n" + "other computer must enter this port when connecting to it."; + std::wstring defPortStr = std::to_wstring(app->m_defaultPort); + MessageBox(dlg, std::vformat(helpStr, std::make_wformat_args(defPortStr)).c_str(), L"Quick Start", MB_OK); return TRUE; } + case ID_HELP_ABOUT: + MessageBox(dlg, std::format(L"{} {}\nGrayson Riffe 2023", app->m_appName, app->m_appVersion).c_str(), L"About", MB_OK); + return TRUE; + case IDC_BUTTONEXIT: case ID_FILE_EXIT: case IDCANCEL: //Escape key press @@ -225,7 +262,7 @@ namespace wc { MainDlgOutput* out = reinterpret_cast(result); - EndDialog(dlg, reinterpret_cast(new MainDlgOutput{ app->m_inAddress, out->screenname, dlgRect.left, dlgRect.top, tempSock })); + EndDialog(dlg, reinterpret_cast(new MainDlgOutput{ app->m_inAddress, std::wstring(), out->screenname, dlgRect.left, dlgRect.top, tempSock })); delete out; return TRUE; } @@ -269,7 +306,7 @@ namespace wc { std::wstring buffer(inputLength, 0); GetDlgItemText(dlg, IDC_EDITSCREENNAME, buffer.data(), static_cast(buffer.size() + 1)); - EndDialog(dlg, reinterpret_cast(new MainDlgOutput{ std::wstring(), buffer, NULL, NULL, INVALID_SOCKET })); + EndDialog(dlg, reinterpret_cast(new MainDlgOutput{ std::wstring(), std::wstring(), buffer, NULL, NULL, INVALID_SOCKET })); return TRUE; } diff --git a/WinChat/src/Application.h b/WinChat/src/Application.h index d7c55f3..e1fc9fe 100644 --- a/WinChat/src/Application.h +++ b/WinChat/src/Application.h @@ -7,7 +7,7 @@ namespace wc { class Application { public: - Application(std::string& appName, std::string& appVersion); + Application(const std::string& appName, const std::string& appVersion, const int defaultPort); void run(); @@ -19,6 +19,8 @@ namespace wc { const std::wstring m_appName; const std::wstring m_appVersion; + const int m_defaultPort; + int m_listenPort; bool m_running; std::atomic m_inSocket; diff --git a/WinChat/src/Chat.cpp b/WinChat/src/Chat.cpp index 75defdc..0d0db8f 100644 --- a/WinChat/src/Chat.cpp +++ b/WinChat/src/Chat.cpp @@ -13,8 +13,9 @@ namespace wc { int xPos, yPos; }; - Chat::Chat(std::wstring address, std::wstring screenname) + Chat::Chat(std::wstring address, std::wstring port, std::wstring screenname) : m_address(address) + , m_port(port) , m_screenname(screenname) , m_remoteScreenname(1000, 0) , m_connected(false) @@ -45,7 +46,7 @@ namespace wc { SOCKET sock = inSocket; if (sock == INVALID_SOCKET) { addrinfo* destInfo = nullptr; - if (getaddrinfo(toStr(m_address).c_str(), "9430", nullptr, &destInfo) != 0) { + if (getaddrinfo(toStr(m_address).c_str(), toStr(m_port).c_str(), nullptr, &destInfo) != 0) { MessageBox(nullptr, L"Error: Could not resolve host or invalid address!", L"Error", MB_ICONERROR); m_connectionError = true; return; @@ -128,7 +129,7 @@ namespace wc { break; case WSAECONNREFUSED: - out = L"Connection refused (WinChat may not be running on remote host)"; + out = L"Connection refused"; break; case WSAENETUNREACH: @@ -158,7 +159,7 @@ namespace wc { SendMessage(dlg, WM_SETICON, ICON_BIG, reinterpret_cast(LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICONMAIN)))); SetWindowPos(dlg, nullptr, in->xPos + 100, in->yPos + 100, 0, 0, SWP_NOSIZE | SWP_NOZORDER); - SetDlgItemText(dlg, IDC_STATICADDRESS, chat->m_address.c_str()); + SetDlgItemText(dlg, IDC_STATICADDRESS, std::format(L"{} on port {}", chat->m_address, chat->m_port).c_str()); SendDlgItemMessage(dlg, IDC_PROGRESS, PBM_SETMARQUEE, TRUE, NULL); SetTimer(dlg, IDT_CHECKCONN, 100, nullptr); @@ -184,7 +185,7 @@ namespace wc { ChatDlgInput* in = reinterpret_cast(lParam); chat = in->chatPtr; - SetWindowText(dlg, std::format(L"{} - WinChat", chat->m_remoteScreenname).c_str()); + SetWindowText(dlg, std::format(L"Chat with {}", chat->m_remoteScreenname).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")); @@ -210,7 +211,7 @@ namespace wc { GetDlgItemText(dlg, IDC_EDITCHATINPUT, buffer.data(), static_cast(buffer.size() + 1)); chat->m_sendQueue.push(buffer); - chat->addMessage(dlg, std::format(L"{} - {}", chat->m_screenname, buffer).c_str()); + chat->addMessage(dlg, std::format(L"{} - {}", chat->m_screenname, buffer)); SetDlgItemText(dlg, IDC_EDITCHATINPUT, L""); @@ -233,7 +234,7 @@ namespace wc { std::wstring remoteStr; while (!chat->m_recvQueue.empty()) { remoteStr = chat->m_recvQueue.pop(); - chat->addMessage(dlg, std::format(L"{} - {}", chat->m_remoteScreenname, remoteStr).c_str()); + chat->addMessage(dlg, std::format(L"{} - {}", chat->m_remoteScreenname, remoteStr)); } return TRUE; diff --git a/WinChat/src/Chat.h b/WinChat/src/Chat.h index 314101c..cabad1c 100644 --- a/WinChat/src/Chat.h +++ b/WinChat/src/Chat.h @@ -8,7 +8,7 @@ namespace wc { class Chat { public: - Chat(std::wstring address, std::wstring screenname); + Chat(std::wstring address, std::wstring port, std::wstring screenname); void run(int xPos, int yPos, SOCKET socket); @@ -21,6 +21,7 @@ namespace wc { void addMessage(HWND dlg, std::wstring str); const std::wstring m_address; + const std::wstring m_port; const std::wstring m_screenname; std::wstring m_remoteScreenname; bool m_connected; diff --git a/WinChat/src/main.cpp b/WinChat/src/main.cpp index ca0047a..d2bb93c 100644 --- a/WinChat/src/main.cpp +++ b/WinChat/src/main.cpp @@ -3,13 +3,12 @@ #include "Application.h" #define APPNAME "WinChat" +#define DEFAULTPORT 9430 int main(int argc, char* argv[]) { - std::string appName = APPNAME; - std::string appVer = APPVERSION; { - wc::Application app(appName, appVer); + wc::Application app(APPNAME, APPVERSION, DEFAULTPORT); app.run(); }