Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
9628669f75 | |||
65f3269d0b | |||
417551a132 | |||
8f66edfa04 | |||
d232437f08 | |||
8435304147 | |||
bd2772ae0e | |||
4b8149fcb6 | |||
c2b3518bdd | |||
15655a6aec | |||
3053a0b8f8 | |||
cebbb1dc19 | |||
3599be9c61 | |||
7821233a52 | |||
5e6fe82bb3 | |||
33b5740184 | |||
e5c06b65eb | |||
032cd334ae | |||
163a583fa4 | |||
a0437093fb | |||
ba68c396d2 | |||
0f83170c43 | |||
4ae052ee18 |
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.vs/
|
||||||
|
build/
|
||||||
|
version.h
|
||||||
|
*.aps
|
48
WinChat/PreBuild.bat
Normal file
48
WinChat/PreBuild.bat
Normal 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
BIN
WinChat/WinChat.rc
Normal file
Binary file not shown.
130
WinChat/WinChat.vcxproj
Normal file
130
WinChat/WinChat.vcxproj
Normal 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>
|
64
WinChat/WinChat.vcxproj.filters
Normal file
64
WinChat/WinChat.vcxproj.filters
Normal 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>
|
14
WinChat/WinChat.vcxproj.user
Normal file
14
WinChat/WinChat.vcxproj.user
Normal 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
BIN
WinChat/res/wc.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
39
WinChat/resource.h
Normal file
39
WinChat/resource.h
Normal 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
334
WinChat/src/Application.cpp
Normal 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
29
WinChat/src/Application.h
Normal 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
272
WinChat/src/Chat.cpp
Normal 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
33
WinChat/src/Chat.h
Normal 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
16
WinChat/src/StrConv.cpp
Normal 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
7
WinChat/src/StrConv.h
Normal 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
38
WinChat/src/TSQueue.h
Normal 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
16
WinChat/src/main.cpp
Normal 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
1
WinChat/src/pch.cpp
Normal file
@ -0,0 +1 @@
|
|||||||
|
#include "pch.h"
|
16
WinChat/src/pch.h
Normal file
16
WinChat/src/pch.h
Normal 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
25
WinChatSln.sln
Normal 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
|
Loading…
x
Reference in New Issue
Block a user