Compare commits

...

23 Commits

Author SHA1 Message Date
9628669f75 Bump version to 1.0.0 2023-10-15 23:31:20 -05:00
65f3269d0b Update about dialog 2023-10-15 23:22:58 -05:00
417551a132 Alert user during incoming connection 2023-10-15 23:17:12 -05:00
8f66edfa04 Connect to any port and add quick start 2023-10-15 23:16:51 -05:00
d232437f08 Prevent multiple and loopback connections 2023-10-05 12:22:18 -05:00
8435304147 Add accept connection dialog 2023-10-01 13:48:29 -05:00
bd2772ae0e Use screen names and add auto scroll 2023-10-01 02:14:20 -05:00
4b8149fcb6 Add chat functionality 2023-10-01 01:02:05 -05:00
c2b3518bdd Start chat dialog 2023-09-29 13:36:08 -05:00
15655a6aec Add connection dialog 2023-09-25 18:36:00 -05:00
3053a0b8f8 Add listen loop, error messages, ip4 & 6 support 2023-09-24 10:31:19 -05:00
cebbb1dc19 Add basic sockets 2023-09-24 10:30:44 -05:00
3599be9c61 Add escape hotkey to main dialog 2023-09-05 15:32:54 -05:00
7821233a52 Save main dialog location and rearrange 2023-09-05 14:55:51 -05:00
5e6fe82bb3 Add Chat 2023-09-04 18:41:41 -05:00
33b5740184 Rearrange main dialog and add screen name 2023-09-04 16:55:24 -05:00
e5c06b65eb Add icon 2023-09-01 10:34:41 -05:00
032cd334ae Add address box and cleanup 2023-08-31 15:14:41 -05:00
163a583fa4 Add dialog functionality 2023-08-31 09:39:56 -05:00
a0437093fb Add main dialog 2023-08-30 14:50:34 -05:00
ba68c396d2 Add Application 2023-08-30 10:54:03 -05:00
0f83170c43 Add version system 2023-08-30 08:05:36 -05:00
4ae052ee18 Add project structure 2023-08-29 14:34:47 -05:00
19 changed files with 1086 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.vs/
build/
version.h
*.aps

48
WinChat/PreBuild.bat Normal file
View File

@ -0,0 +1,48 @@
@echo off
echo [Pre Build] Startup
::Set Version
echo [Pre Build] Setting version...
::First make sure git is in the path
where git >nul 2>&1
if %errorlevel% neq 0 goto noGit
goto yesGit
:noGit
echo [Pre Build] Error: git not found on PATH!
exit 1
::We have git on the command line, so set the version string
:yesGit
git describe HEAD > temp1
git branch --show-current > temp2
set /p commit= < temp1
set /p branch= < temp2
del temp1
del temp2
::If the current branch is stable, don't include it in the version string
if "%branch%" == "stable" goto noBranch
set version=%commit%+%branch%
goto testOutput
:noBranch
set version=%commit%
::Don't touch the version file if it's already up to date
:testOutput
set /p oldVersion= < src\version.h
if "%oldVersion%" == "#define APPVERSION "%version%"" goto sameVersion
echo #define APPVERSION "%version%"> src\version.h
goto continue
:sameVersion
echo [Pre Build] Same version
:continue
echo [Pre Build] Exit
exit

BIN
WinChat/WinChat.rc Normal file

Binary file not shown.

130
WinChat/WinChat.vcxproj Normal file
View File

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{807f7b0f-884d-4a96-8a2b-57cd89232f61}</ProjectGuid>
<RootNamespace>WinChat</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>$(ProjectDir)build\bin\$(Configuration)\</OutDir>
<IntDir>$(ProjectDir)build\int\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>$(ProjectDir)build\bin\$(Configuration)\</OutDir>
<IntDir>$(ProjectDir)build\int\$(Configuration)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PreBuildEvent>
<Command>PreBuild.bat</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp20</LanguageStandard>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
<AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PreBuildEvent>
<Command>PreBuild.bat</Command>
</PreBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="src\Application.cpp" />
<ClCompile Include="src\Chat.cpp" />
<ClCompile Include="src\main.cpp" />
<ClCompile Include="src\pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
</PrecompiledHeaderFile>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
<PrecompiledHeaderFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
</PrecompiledHeaderFile>
</ClCompile>
<ClCompile Include="src\StrConv.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="resource.h" />
<ClInclude Include="src\Application.h" />
<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" />
</ItemGroup>
<ItemGroup>
<Image Include="res\wc.ico" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\Application.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\Chat.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\StrConv.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\Application.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\Chat.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<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">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<Image Include="res\wc.ico">
<Filter>Resource Files</Filter>
</Image>
</ItemGroup>
</Project>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ShowAllFiles>true</ShowAllFiles>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LocalDebuggerWorkingDirectory>$(OutDir)</LocalDebuggerWorkingDirectory>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LocalDebuggerWorkingDirectory>$(OutDir)</LocalDebuggerWorkingDirectory>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
</PropertyGroup>
</Project>

BIN
WinChat/res/wc.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

39
WinChat/resource.h Normal file
View File

@ -0,0 +1,39 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by WinChat.rc
//
#define IDD_DIALOGMAIN 101
#define IDR_MENUMAIN 104
#define IDI_ICONMAIN 106
#define IDD_DIALOGCONNECTING 107
#define IDD_DIALOGCHAT 109
#define IDD_DIALOGACCEPTCONNECTION 111
#define IDC_STATICTITLE 1001
#define IDC_BUTTONEXIT 1003
#define IDC_BUTTONCONNECT 1005
#define IDC_EDITADDRESS 1006
#define IDC_ICONMAIN 1007
#define IDC_EDITSCREENNAME 1008
#define IDC_STATICDESC 1009
#define IDC_STATICADDRESS 1010
#define IDC_PROGRESS 1011
#define IDC_EDITCHATDISPLAY 1012
#define IDC_EDITCHATINPUT 1013
#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 40004
#define _APS_NEXT_CONTROL_VALUE 1019
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

334
WinChat/src/Application.cpp Normal file
View File

@ -0,0 +1,334 @@
#include "pch.h"
#include "Application.h"
#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* appPtr;
int xPos, yPos;
};
struct MainDlgOutput {
std::wstring address;
std::wstring port;
std::wstring screenname;
int xPos, yPos;
SOCKET inSock;
};
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_defaultPort(defaultPort)
, m_listenPort()
, m_running(false)
, m_inSocket(INVALID_SOCKET)
{
std::wcout << std::format(L"{} {}\n", m_appName, m_appVersion);
//Initialize Windows Sockets 2
WSADATA data = { };
if (WSAStartup(MAKEWORD(2, 2), &data) != 0) {
MessageBox(nullptr, L"Error: could not initialize WinSock 2!", L"Error", MB_ICONERROR);
std::exit(1);
}
if (LOBYTE(data.wVersion) != 2 || HIBYTE(data.wVersion) != 2) {
MessageBox(nullptr, L"Error: WinSock version 2.2 not available!", L"Error", MB_ICONERROR);
std::exit(1);
}
}
void Application::run() {
//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<DLGPROC>(mainDlgProc), reinterpret_cast<LPARAM>(input))) {
//Create a Chat with the collected input or the incoming connection
MainDlgOutput* output = reinterpret_cast<MainDlgOutput*>(result);
const auto [address, port, screenname, x, y, inSocket] = *output;
delete output;
{
Chat chat(address, port, screenname);
chat.run(x, y, inSocket);
}
//And repeat until the user exits from the main dialog
input = new MainDlgInput{ this, x, y };
}
m_running = false;
listenThread.join();
}
void Application::startListen() {
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);
std::exit(1);
}
unsigned long yes = 1;
unsigned long no = 0;
setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char*>(&no), sizeof(no));
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<int>(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;
int errorCode = 0;
while (m_running) {
conSock = accept(sock, reinterpret_cast<sockaddr*>(&remoteAddr), &remoteSize);
errorCode = WSAGetLastError();
if (errorCode && errorCode != WSAEWOULDBLOCK) {
MessageBox(nullptr, L"Error: could not accept connection!", L"Error", MB_ICONERROR);
std::exit(1);
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (errorCode == WSAEWOULDBLOCK)
continue;
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));
m_inAddress = toWideStr(remoteStr);
m_inSocket = conSock;
//If the socket isn't dealt with by the other thread in time, we most likely already have a connection
//This also prevents connecting to yourself
std::this_thread::sleep_for(std::chrono::seconds(1));
if (m_inSocket != INVALID_SOCKET) {
closesocket(m_inSocket);
m_inSocket = INVALID_SOCKET;
}
}
closesocket(sock);
}
BOOL CALLBACK Application::mainDlgProc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam) {
static Application* app = nullptr;
switch (msg) {
case WM_INITDIALOG: {
MainDlgInput* in = reinterpret_cast<MainDlgInput*>(lParam);
auto [appPtr, xPos, yPos] = *in;
delete in;
app = appPtr;
SetWindowText(dlg, app->m_appName.c_str());
SendMessage(dlg, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICONMAIN))));
if (xPos == -1 && yPos == -1) {
POINT pt = { };
GetCursorPos(&pt);
MONITORINFO mi = { };
mi.cbSize = sizeof(mi);
GetMonitorInfo(MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST), &mi);
xPos = mi.rcMonitor.left + 100, yPos = mi.rcMonitor.top + 100;
}
SetWindowPos(dlg, nullptr, xPos, yPos, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
SetDlgItemText(dlg, IDC_STATICTITLE, app->m_appName.c_str());
LOGFONT lFont = { .lfHeight = 50 };
HFONT font = CreateFontIndirect(&lFont);
SendMessage(GetDlgItem(dlg, IDC_STATICTITLE), WM_SETFONT, reinterpret_cast<LPARAM>(font), NULL);
SetDlgItemText(dlg, IDC_STATICDESC, L"A simple Windows chat app");
SendDlgItemMessage(dlg, IDC_EDITADDRESS, EM_SETCUEBANNER, TRUE, reinterpret_cast<LPARAM>(L"Address(/Port)"));
SendDlgItemMessage(dlg, IDC_EDITSCREENNAME, EM_SETCUEBANNER, TRUE, reinterpret_cast<LPARAM>(L"User"));
SetDlgItemText(dlg, IDC_STATICLISTENPORT, std::format(L"Listening on port {}", app->m_listenPort).c_str());
SetTimer(dlg, IDT_CHECKINCONN, 100, nullptr);
return TRUE;
}
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDC_BUTTONCONNECT: {
int addressSize = GetWindowTextLength(GetDlgItem(dlg, IDC_EDITADDRESS));
int screennameSize = GetWindowTextLength(GetDlgItem(dlg, IDC_EDITSCREENNAME));
if (!addressSize || !screennameSize) {
EDITBALLOONTIP balloon = { .cbStruct = sizeof(balloon), .pszTitle = L"Alert", .pszText = L"You must enter an address and screen name." };
SendDlgItemMessage(dlg, addressSize ? IDC_EDITSCREENNAME : IDC_EDITADDRESS, EM_SHOWBALLOONTIP, NULL, reinterpret_cast<LPARAM>(&balloon));
return TRUE;
}
std::wstring address;
address.resize(addressSize);
GetDlgItemText(dlg, IDC_EDITADDRESS, address.data(), static_cast<int>(address.size() + 1));
std::wstring screenname;
screenname.resize(screennameSize);
GetDlgItemText(dlg, IDC_EDITSCREENNAME, screenname.data(), static_cast<int>(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<LPARAM>(&balloon));
return TRUE;
}
}
else
port = std::to_wstring(app->m_defaultPort);
RECT dlgRect = { };
GetWindowRect(dlg, &dlgRect);
EndDialog(dlg, reinterpret_cast<INT_PTR>(new MainDlgOutput{ address, port, screenname, dlgRect.left, dlgRect.top, INVALID_SOCKET }));
return TRUE;
}
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: {
std::wstring aboutStr = L"{} {}\nCopyright Grayson Riffe 2023\ngraysonriffe.com";
MessageBox(dlg, std::vformat(aboutStr, std::make_wformat_args(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
PostMessage(dlg, WM_CLOSE, NULL, NULL);
return TRUE;
}
return FALSE;
case WM_TIMER:
if (wParam == IDT_CHECKINCONN && app->m_inSocket != INVALID_SOCKET) {
KillTimer(dlg, IDT_CHECKINCONN);
SOCKET tempSock = app->m_inSocket;
app->m_inSocket = INVALID_SOCKET;
RECT dlgRect = { };
GetWindowRect(dlg, &dlgRect);
MainDlgInput acceptInput = { app, dlgRect.left, dlgRect.top };
INT_PTR result = DialogBoxParam(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_DIALOGACCEPTCONNECTION), dlg, reinterpret_cast<DLGPROC>(acceptDlgProc), reinterpret_cast<LPARAM>(&acceptInput));
if (result == IDCANCEL) {
closesocket(tempSock);
SetTimer(dlg, IDT_CHECKINCONN, 100, nullptr);
return TRUE;
}
MainDlgOutput* out = reinterpret_cast<MainDlgOutput*>(result);
EndDialog(dlg, reinterpret_cast<INT_PTR>(new MainDlgOutput{ app->m_inAddress, std::wstring(), out->screenname, dlgRect.left, dlgRect.top, tempSock }));
delete out;
return TRUE;
}
return FALSE;
case WM_CLOSE:
EndDialog(dlg, 0);
return TRUE;
}
return FALSE;
}
BOOL CALLBACK Application::acceptDlgProc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam) {
static Application* app = nullptr;
switch (msg) {
case WM_INITDIALOG: {
MainDlgInput* in = reinterpret_cast<MainDlgInput*>(lParam);
app = in->appPtr;
SendMessage(dlg, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICONMAIN))));
SetWindowPos(dlg, nullptr, in->xPos + 30, in->yPos + 30, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
SetDlgItemText(dlg, IDC_STATICREMOTEINFO, app->m_inAddress.c_str());
SendDlgItemMessage(dlg, IDC_EDITSCREENNAME, EM_SETCUEBANNER, TRUE, reinterpret_cast<LPARAM>(L"User"));
//Get the attention of the user
FLASHWINFO fw = { .cbSize = sizeof(fw), .hwnd = GetParent(dlg), .dwFlags = FLASHW_TRAY | FLASHW_TIMERNOFG };
FlashWindowEx(&fw);
MessageBeep(MB_OK);
return TRUE;
}
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDOK: {
int inputLength = GetWindowTextLength(GetDlgItem(dlg, IDC_EDITSCREENNAME));
if (!inputLength) {
EDITBALLOONTIP balloon = { .cbStruct = sizeof(balloon), .pszTitle = L"Alert", .pszText = L"You must enter a screen name." };
SendDlgItemMessage(dlg, IDC_EDITSCREENNAME, EM_SHOWBALLOONTIP, NULL, reinterpret_cast<LPARAM>(&balloon));
return TRUE;
}
std::wstring buffer(inputLength, 0);
GetDlgItemText(dlg, IDC_EDITSCREENNAME, buffer.data(), static_cast<int>(buffer.size() + 1));
EndDialog(dlg, reinterpret_cast<INT_PTR>(new MainDlgOutput{ std::wstring(), std::wstring(), buffer, NULL, NULL, INVALID_SOCKET }));
return TRUE;
}
case IDCANCEL:
EndDialog(dlg, wParam);
return TRUE;
}
return FALSE;
}
return FALSE;
}
Application::~Application() {
WSACleanup();
}
}

29
WinChat/src/Application.h Normal file
View File

@ -0,0 +1,29 @@
#pragma once
#include <string>
#include <atomic>
#include <Windows.h>
namespace wc {
class Application {
public:
Application(const std::string& appName, const std::string& appVersion, const int defaultPort);
void run();
~Application();
private:
void startListen();
static BOOL CALLBACK mainDlgProc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam);
static BOOL CALLBACK acceptDlgProc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam);
const std::wstring m_appName;
const std::wstring m_appVersion;
const int m_defaultPort;
int m_listenPort;
bool m_running;
std::atomic<SOCKET> m_inSocket;
std::wstring m_inAddress;
};
}

272
WinChat/src/Chat.cpp Normal file
View File

@ -0,0 +1,272 @@
#include "pch.h"
#include "Chat.h"
#include "StrConv.h"
#include "../resource.h"
#define IDT_CHECKCONN 2
#define IDT_UPDATECHAT 3
namespace wc {
struct ChatDlgInput {
Chat* chatPtr;
int xPos, yPos;
};
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)
, m_connectionError(false)
{
}
void Chat::run(int xPos, int yPos, SOCKET socket) {
//Run net thread to connect and communicate
std::thread netThread(&Chat::runNetThread, this, socket);
//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));
//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>(&input));
netThread.join();
}
void Chat::runNetThread(SOCKET inSocket) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
SOCKET sock = inSocket;
if (sock == INVALID_SOCKET) {
addrinfo* destInfo = nullptr;
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;
}
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);
}
unsigned long yes = 1;
unsigned long no = 0;
ioctlsocket(sock, FIONBIO, &no);
//Send and receive screen names
send(sock, reinterpret_cast<const char*>(m_screenname.c_str()), static_cast<int>(m_screenname.size()) * sizeof(wchar_t), NULL);
int bytesRemoteScreenname = recv(sock, reinterpret_cast<char*>(m_remoteScreenname.data()), 1000, NULL);
if (!bytesRemoteScreenname || WSAGetLastError() == WSAECONNRESET) {
MessageBox(nullptr, L"The remote host denied the connection.", L"Error", MB_ICONERROR);
m_connectionError = true;
closesocket(sock);
return;
}
m_remoteScreenname.resize(bytesRemoteScreenname / 2);
ioctlsocket(sock, FIONBIO, &yes);
m_connected = true;
std::wstring recvBuffer(2000, 0);
std::wstring sendStr;
int bytesRecvd = 0;
while (m_connected && !m_connectionError) {
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);
}
std::wstring Chat::getErrorString() {
int errorCode = WSAGetLastError();
std::wstring out;
switch (errorCode) {
case WSAETIMEDOUT:
out = L"Timed out";
break;
case WSAECONNREFUSED:
out = L"Connection refused";
break;
case WSAENETUNREACH:
case WSAEHOSTUNREACH:
out = L"Remote host unreachable (You may not be connected to the internet)";
break;
case WSAEADDRNOTAVAIL:
out = L"Not a valid address to connect to";
break;
default:
out = std::format(L"Error Code {}", errorCode);
break;
}
return out;
}
BOOL CALLBACK Chat::connDlgProc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam) {
static Chat* chat = nullptr;
switch (msg) {
case WM_INITDIALOG: {
ChatDlgInput* in = reinterpret_cast<ChatDlgInput*>(lParam);
chat = in->chatPtr;
SendMessage(dlg, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(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, 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);
return TRUE;
}
case WM_TIMER:
if (wParam == IDT_CHECKCONN && (chat->m_connected || chat->m_connectionError)) {
EndDialog(dlg, 0);
return TRUE;
}
}
return FALSE;
}
BOOL CALLBACK Chat::chatDlgProc(HWND dlg, UINT msg, WPARAM wParam, LPARAM lParam) {
static Chat* chat = nullptr;
switch (msg) {
case WM_INITDIALOG: {
ChatDlgInput* in = reinterpret_cast<ChatDlgInput*>(lParam);
chat = in->chatPtr;
SetWindowText(dlg, std::format(L"Chat with {}", chat->m_remoteScreenname).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 {} at {}", chat->m_remoteScreenname, 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, std::format(L"{} - {}", chat->m_screenname, 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, std::format(L"{} - {}", chat->m_remoteScreenname, remoteStr));
}
return TRUE;
}
return FALSE;
case WM_CLOSE:
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);
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\r\n{}", in);
SetDlgItemText(dlg, IDC_EDITCHATDISPLAY, buffer.c_str());
//If scrolled to the bottom before adding the text
if (static_cast<int>(si.nPage) + si.nPos > si.nMax)
SendDlgItemMessage(dlg, IDC_EDITCHATDISPLAY, EM_LINESCROLL, 0, si.nMax);
else
SendDlgItemMessage(dlg, IDC_EDITCHATDISPLAY, EM_LINESCROLL, 0, si.nPos);
}
Chat::~Chat() {
}
}

33
WinChat/src/Chat.h Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include <string>
#include <Windows.h>
#include "TSQueue.h"
namespace wc {
class Chat {
public:
Chat(std::wstring address, std::wstring port, std::wstring screenname);
void run(int xPos, int yPos, SOCKET socket);
~Chat();
private:
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_port;
const std::wstring m_screenname;
std::wstring m_remoteScreenname;
bool m_connected;
bool m_connectionError;
TSQueue<std::wstring> m_sendQueue;
TSQueue<std::wstring> m_recvQueue;
};
}

16
WinChat/src/StrConv.cpp Normal file
View File

@ -0,0 +1,16 @@
#include "pch.h"
#include "StrConv.h"
namespace wc {
std::string toStr(const std::wstring& in) {
std::string out;
out.resize(in.size() + 1);
size_t tempSize = 0;
wcstombs_s(&tempSize, out.data(), out.size(), in.c_str(), out.size());
return out;
}
std::wstring toWideStr(const std::string& in){
return std::wstring(in.begin(), in.end());
}
}

7
WinChat/src/StrConv.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <string>
namespace wc {
std::string toStr(const std::wstring& in);
std::wstring toWideStr(const std::string& in);
}

38
WinChat/src/TSQueue.h Normal file
View File

@ -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;
};
}

16
WinChat/src/main.cpp Normal file
View File

@ -0,0 +1,16 @@
#include "pch.h"
#include "version.h"
#include "Application.h"
#define APPNAME "WinChat"
#define DEFAULTPORT 9430
int main(int argc, char* argv[]) {
{
wc::Application app(APPNAME, APPVERSION, DEFAULTPORT);
app.run();
}
return EXIT_SUCCESS;
}

1
WinChat/src/pch.cpp Normal file
View File

@ -0,0 +1 @@
#include "pch.h"

16
WinChat/src/pch.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
//STL
#include <iostream>
#include <format>
#include <thread>
#include <mutex>
#include <atomic>
#include <queue>
//Windows
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <CommCtrl.h>
#include <WinSock2.h>
#include <WS2tcpip.h>

25
WinChatSln.sln Normal file
View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34024.191
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinChat", "WinChat\WinChat.vcxproj", "{807F7B0F-884D-4A96-8A2B-57CD89232F61}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{807F7B0F-884D-4A96-8A2B-57CD89232F61}.Debug|x64.ActiveCfg = Debug|x64
{807F7B0F-884D-4A96-8A2B-57CD89232F61}.Debug|x64.Build.0 = Debug|x64
{807F7B0F-884D-4A96-8A2B-57CD89232F61}.Release|x64.ActiveCfg = Release|x64
{807F7B0F-884D-4A96-8A2B-57CD89232F61}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1E26CF85-CF33-4C67-AFFE-1CF6C8612DEA}
EndGlobalSection
EndGlobal