AsyncSocket support in DlangUI; IRC Client example for AsyncSocket
|
@ -49,6 +49,11 @@ Project("{002A2DE9-8BB6-484D-9802-7E4AD4084715}") = "dwindow", "..\dwindow\dwind
|
||||||
EndProject
|
EndProject
|
||||||
Project("{002A2DE9-8BB6-484D-9802-7E4AD4084715}") = "dmdbug", "examples\dmdbug\dmdbug.visualdproj", "{6FD15ECD-35AF-457D-9327-DD8C2A0EADF1}"
|
Project("{002A2DE9-8BB6-484D-9802-7E4AD4084715}") = "dmdbug", "examples\dmdbug\dmdbug.visualdproj", "{6FD15ECD-35AF-457D-9327-DD8C2A0EADF1}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{002A2DE9-8BB6-484D-9802-7E4AD4084715}") = "ircclient", "examples\ircclient\ircclient.visualdproj", "{20722E6B-CA27-467F-8BB8-07F80106B478}"
|
||||||
|
ProjectSection(ProjectDependencies) = postProject
|
||||||
|
{52A2ABB9-2CF7-4D5F-AE8C-75B21F8585A5} = {52A2ABB9-2CF7-4D5F-AE8C-75B21F8585A5}
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Win32 = Debug|Win32
|
Debug|Win32 = Debug|Win32
|
||||||
|
@ -182,6 +187,16 @@ Global
|
||||||
{6FD15ECD-35AF-457D-9327-DD8C2A0EADF1}.Unittest|Win32.Build.0 = Release|Win32
|
{6FD15ECD-35AF-457D-9327-DD8C2A0EADF1}.Unittest|Win32.Build.0 = Release|Win32
|
||||||
{6FD15ECD-35AF-457D-9327-DD8C2A0EADF1}.Unittest|x64.ActiveCfg = Release|Win32
|
{6FD15ECD-35AF-457D-9327-DD8C2A0EADF1}.Unittest|x64.ActiveCfg = Release|Win32
|
||||||
{6FD15ECD-35AF-457D-9327-DD8C2A0EADF1}.Unittest|x64.Build.0 = Release|Win32
|
{6FD15ECD-35AF-457D-9327-DD8C2A0EADF1}.Unittest|x64.Build.0 = Release|Win32
|
||||||
|
{20722E6B-CA27-467F-8BB8-07F80106B478}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||||
|
{20722E6B-CA27-467F-8BB8-07F80106B478}.Debug|Win32.Build.0 = Debug|Win32
|
||||||
|
{20722E6B-CA27-467F-8BB8-07F80106B478}.Debug|x64.ActiveCfg = Debug|Win32
|
||||||
|
{20722E6B-CA27-467F-8BB8-07F80106B478}.Release|Win32.ActiveCfg = Release|Win32
|
||||||
|
{20722E6B-CA27-467F-8BB8-07F80106B478}.Release|Win32.Build.0 = Release|Win32
|
||||||
|
{20722E6B-CA27-467F-8BB8-07F80106B478}.Release|x64.ActiveCfg = Release|Win32
|
||||||
|
{20722E6B-CA27-467F-8BB8-07F80106B478}.Unittest|Win32.ActiveCfg = Release|Win32
|
||||||
|
{20722E6B-CA27-467F-8BB8-07F80106B478}.Unittest|Win32.Build.0 = Release|Win32
|
||||||
|
{20722E6B-CA27-467F-8BB8-07F80106B478}.Unittest|x64.ActiveCfg = Release|Win32
|
||||||
|
{20722E6B-CA27-467F-8BB8-07F80106B478}.Unittest|x64.Build.0 = Release|Win32
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
@ -735,6 +735,7 @@
|
||||||
<Folder name="src">
|
<Folder name="src">
|
||||||
<Folder name="dlangui">
|
<Folder name="dlangui">
|
||||||
<Folder name="core">
|
<Folder name="core">
|
||||||
|
<File path="src\dlangui\core\asyncsocket.d" />
|
||||||
<File path="src\dlangui\core\collections.d" />
|
<File path="src\dlangui\core\collections.d" />
|
||||||
<File path="src\dlangui\core\config.d" />
|
<File path="src\dlangui\core\config.d" />
|
||||||
<File path="src\dlangui\core\css.d" />
|
<File path="src\dlangui\core\css.d" />
|
||||||
|
@ -747,6 +748,7 @@
|
||||||
<File path="src\dlangui\core\linestream.d" />
|
<File path="src\dlangui\core\linestream.d" />
|
||||||
<File path="src\dlangui\core\logger.d" />
|
<File path="src\dlangui\core\logger.d" />
|
||||||
<File path="src\dlangui\core\math3d.d" />
|
<File path="src\dlangui\core\math3d.d" />
|
||||||
|
<File path="src\dlangui\core\queue.d" />
|
||||||
<File path="src\dlangui\core\settings.d" />
|
<File path="src\dlangui\core\settings.d" />
|
||||||
<File path="src\dlangui\core\signals.d" />
|
<File path="src\dlangui\core\signals.d" />
|
||||||
<File path="src\dlangui\core\stdaction.d" />
|
<File path="src\dlangui\core\stdaction.d" />
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "ircclient",
|
||||||
|
"description": "dlangui library example IRC Client",
|
||||||
|
"homepage": "https://github.com/buggins/dlangui",
|
||||||
|
"license": "Boost",
|
||||||
|
"authors": ["Vadim Lopatin"],
|
||||||
|
|
||||||
|
"stringImportPaths": ["views", "views/res", "views/res/i18n", "views/res/mdpi"],
|
||||||
|
|
||||||
|
"targetPath": "bin",
|
||||||
|
"targetName": "ircclient",
|
||||||
|
"targetType": "executable",
|
||||||
|
|
||||||
|
"sourceFiles-windows": ["$PACKAGE_DIR/src/win_app.def"],
|
||||||
|
|
||||||
|
"versions": ["EmbedStandardResources"],
|
||||||
|
|
||||||
|
"dependencies": {
|
||||||
|
"dlangui": {"path": "../../"}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,215 @@
|
||||||
|
<DProject>
|
||||||
|
<ProjectGuid>{20722E6B-CA27-467F-8BB8-07F80106B478}</ProjectGuid>
|
||||||
|
<Config name="Debug" platform="Win32">
|
||||||
|
<obj>0</obj>
|
||||||
|
<link>0</link>
|
||||||
|
<lib>0</lib>
|
||||||
|
<subsystem>2</subsystem>
|
||||||
|
<multiobj>0</multiobj>
|
||||||
|
<singleFileCompilation>0</singleFileCompilation>
|
||||||
|
<oneobj>0</oneobj>
|
||||||
|
<mscoff>0</mscoff>
|
||||||
|
<trace>0</trace>
|
||||||
|
<quiet>0</quiet>
|
||||||
|
<verbose>0</verbose>
|
||||||
|
<vtls>0</vtls>
|
||||||
|
<vgc>0</vgc>
|
||||||
|
<symdebug>3</symdebug>
|
||||||
|
<optimize>0</optimize>
|
||||||
|
<cpu>0</cpu>
|
||||||
|
<isX86_64>0</isX86_64>
|
||||||
|
<isLinux>0</isLinux>
|
||||||
|
<isOSX>0</isOSX>
|
||||||
|
<isWindows>0</isWindows>
|
||||||
|
<isFreeBSD>0</isFreeBSD>
|
||||||
|
<isSolaris>0</isSolaris>
|
||||||
|
<scheduler>0</scheduler>
|
||||||
|
<useDeprecated>0</useDeprecated>
|
||||||
|
<errDeprecated>0</errDeprecated>
|
||||||
|
<useAssert>0</useAssert>
|
||||||
|
<useInvariants>0</useInvariants>
|
||||||
|
<useIn>0</useIn>
|
||||||
|
<useOut>0</useOut>
|
||||||
|
<useArrayBounds>0</useArrayBounds>
|
||||||
|
<noboundscheck>0</noboundscheck>
|
||||||
|
<useSwitchError>0</useSwitchError>
|
||||||
|
<useUnitTests>0</useUnitTests>
|
||||||
|
<useInline>0</useInline>
|
||||||
|
<release>0</release>
|
||||||
|
<preservePaths>0</preservePaths>
|
||||||
|
<warnings>0</warnings>
|
||||||
|
<infowarnings>0</infowarnings>
|
||||||
|
<checkProperty>0</checkProperty>
|
||||||
|
<genStackFrame>0</genStackFrame>
|
||||||
|
<pic>0</pic>
|
||||||
|
<cov>0</cov>
|
||||||
|
<nofloat>0</nofloat>
|
||||||
|
<Dversion>2</Dversion>
|
||||||
|
<ignoreUnsupportedPragmas>0</ignoreUnsupportedPragmas>
|
||||||
|
<allinst>0</allinst>
|
||||||
|
<stackStomp>0</stackStomp>
|
||||||
|
<compiler>0</compiler>
|
||||||
|
<otherDMD>0</otherDMD>
|
||||||
|
<cccmd>$(CC) -c</cccmd>
|
||||||
|
<ccTransOpt>1</ccTransOpt>
|
||||||
|
<program>$(DMDInstallDir)windows\bin\dmd.exe</program>
|
||||||
|
<imppath>$(SolutionDir)/src $(SolutionDir)/3rdparty $(SolutionDir)/deps/DerelictGL3/source $(SolutionDir)/deps/DerelictUtil/source $(SolutionDir)/deps/DerelictFT/source $(SolutionDir)/deps/DerelictSDL2/source</imppath>
|
||||||
|
<fileImppath>views views/res views/res/i18n views/res/mdpi views/res/hdpi</fileImppath>
|
||||||
|
<outdir>$(ConfigurationName)</outdir>
|
||||||
|
<objdir>$(OutDir)</objdir>
|
||||||
|
<objname />
|
||||||
|
<libname />
|
||||||
|
<doDocComments>0</doDocComments>
|
||||||
|
<docdir />
|
||||||
|
<docname />
|
||||||
|
<modules_ddoc />
|
||||||
|
<ddocfiles />
|
||||||
|
<doHdrGeneration>0</doHdrGeneration>
|
||||||
|
<hdrdir />
|
||||||
|
<hdrname />
|
||||||
|
<doXGeneration>1</doXGeneration>
|
||||||
|
<xfilename>$(IntDir)\$(TargetName).json</xfilename>
|
||||||
|
<debuglevel>0</debuglevel>
|
||||||
|
<debugids />
|
||||||
|
<versionlevel>0</versionlevel>
|
||||||
|
<versionids>USE_OPENGL EmbedStandardResources ForceLogs</versionids>
|
||||||
|
<dump_source>0</dump_source>
|
||||||
|
<mapverbosity>0</mapverbosity>
|
||||||
|
<createImplib>0</createImplib>
|
||||||
|
<defaultlibname />
|
||||||
|
<debuglibname />
|
||||||
|
<moduleDepsFile />
|
||||||
|
<run>0</run>
|
||||||
|
<runargs />
|
||||||
|
<runCv2pdb>1</runCv2pdb>
|
||||||
|
<pathCv2pdb>$(VisualDInstallDir)cv2pdb\cv2pdb.exe</pathCv2pdb>
|
||||||
|
<cv2pdbPre2043>0</cv2pdbPre2043>
|
||||||
|
<cv2pdbNoDemangle>0</cv2pdbNoDemangle>
|
||||||
|
<cv2pdbEnumType>0</cv2pdbEnumType>
|
||||||
|
<cv2pdbOptions />
|
||||||
|
<objfiles />
|
||||||
|
<linkswitches />
|
||||||
|
<libfiles />
|
||||||
|
<libpaths />
|
||||||
|
<deffile />
|
||||||
|
<resfile />
|
||||||
|
<exefile>$(OutDir)\$(ProjectName).exe</exefile>
|
||||||
|
<useStdLibPath>1</useStdLibPath>
|
||||||
|
<cRuntime>2</cRuntime>
|
||||||
|
<privatePhobos>0</privatePhobos>
|
||||||
|
<additionalOptions />
|
||||||
|
<preBuildCommand />
|
||||||
|
<postBuildCommand />
|
||||||
|
<filesToClean>*.obj;*.cmd;*.build;*.json;*.dep</filesToClean>
|
||||||
|
</Config>
|
||||||
|
<Config name="Release" platform="Win32">
|
||||||
|
<obj>0</obj>
|
||||||
|
<link>0</link>
|
||||||
|
<lib>0</lib>
|
||||||
|
<subsystem>0</subsystem>
|
||||||
|
<multiobj>0</multiobj>
|
||||||
|
<singleFileCompilation>0</singleFileCompilation>
|
||||||
|
<oneobj>0</oneobj>
|
||||||
|
<mscoff>0</mscoff>
|
||||||
|
<trace>0</trace>
|
||||||
|
<quiet>0</quiet>
|
||||||
|
<verbose>0</verbose>
|
||||||
|
<vtls>0</vtls>
|
||||||
|
<vgc>0</vgc>
|
||||||
|
<symdebug>0</symdebug>
|
||||||
|
<optimize>1</optimize>
|
||||||
|
<cpu>0</cpu>
|
||||||
|
<isX86_64>0</isX86_64>
|
||||||
|
<isLinux>0</isLinux>
|
||||||
|
<isOSX>0</isOSX>
|
||||||
|
<isWindows>0</isWindows>
|
||||||
|
<isFreeBSD>0</isFreeBSD>
|
||||||
|
<isSolaris>0</isSolaris>
|
||||||
|
<scheduler>0</scheduler>
|
||||||
|
<useDeprecated>0</useDeprecated>
|
||||||
|
<errDeprecated>0</errDeprecated>
|
||||||
|
<useAssert>0</useAssert>
|
||||||
|
<useInvariants>0</useInvariants>
|
||||||
|
<useIn>0</useIn>
|
||||||
|
<useOut>0</useOut>
|
||||||
|
<useArrayBounds>0</useArrayBounds>
|
||||||
|
<noboundscheck>0</noboundscheck>
|
||||||
|
<useSwitchError>0</useSwitchError>
|
||||||
|
<useUnitTests>0</useUnitTests>
|
||||||
|
<useInline>1</useInline>
|
||||||
|
<release>1</release>
|
||||||
|
<preservePaths>0</preservePaths>
|
||||||
|
<warnings>0</warnings>
|
||||||
|
<infowarnings>0</infowarnings>
|
||||||
|
<checkProperty>0</checkProperty>
|
||||||
|
<genStackFrame>0</genStackFrame>
|
||||||
|
<pic>0</pic>
|
||||||
|
<cov>0</cov>
|
||||||
|
<nofloat>0</nofloat>
|
||||||
|
<Dversion>2.043</Dversion>
|
||||||
|
<ignoreUnsupportedPragmas>0</ignoreUnsupportedPragmas>
|
||||||
|
<allinst>0</allinst>
|
||||||
|
<stackStomp>0</stackStomp>
|
||||||
|
<compiler>0</compiler>
|
||||||
|
<otherDMD>0</otherDMD>
|
||||||
|
<cccmd>$(CC) -c</cccmd>
|
||||||
|
<ccTransOpt>1</ccTransOpt>
|
||||||
|
<program>$(DMDInstallDir)windows\bin\dmd.exe</program>
|
||||||
|
<imppath />
|
||||||
|
<fileImppath />
|
||||||
|
<outdir>$(ConfigurationName)</outdir>
|
||||||
|
<objdir>$(OutDir)</objdir>
|
||||||
|
<objname />
|
||||||
|
<libname />
|
||||||
|
<doDocComments>0</doDocComments>
|
||||||
|
<docdir />
|
||||||
|
<docname />
|
||||||
|
<modules_ddoc />
|
||||||
|
<ddocfiles />
|
||||||
|
<doHdrGeneration>0</doHdrGeneration>
|
||||||
|
<hdrdir />
|
||||||
|
<hdrname />
|
||||||
|
<doXGeneration>1</doXGeneration>
|
||||||
|
<xfilename>$(IntDir)\$(TargetName).json</xfilename>
|
||||||
|
<debuglevel>0</debuglevel>
|
||||||
|
<debugids />
|
||||||
|
<versionlevel>0</versionlevel>
|
||||||
|
<versionids />
|
||||||
|
<dump_source>0</dump_source>
|
||||||
|
<mapverbosity>0</mapverbosity>
|
||||||
|
<createImplib>0</createImplib>
|
||||||
|
<defaultlibname />
|
||||||
|
<debuglibname />
|
||||||
|
<moduleDepsFile />
|
||||||
|
<run>0</run>
|
||||||
|
<runargs />
|
||||||
|
<runCv2pdb>0</runCv2pdb>
|
||||||
|
<pathCv2pdb>$(VisualDInstallDir)cv2pdb\cv2pdb.exe</pathCv2pdb>
|
||||||
|
<cv2pdbPre2043>0</cv2pdbPre2043>
|
||||||
|
<cv2pdbNoDemangle>0</cv2pdbNoDemangle>
|
||||||
|
<cv2pdbEnumType>0</cv2pdbEnumType>
|
||||||
|
<cv2pdbOptions />
|
||||||
|
<objfiles />
|
||||||
|
<linkswitches />
|
||||||
|
<libfiles />
|
||||||
|
<libpaths />
|
||||||
|
<deffile />
|
||||||
|
<resfile />
|
||||||
|
<exefile>$(OutDir)\$(ProjectName).exe</exefile>
|
||||||
|
<useStdLibPath>1</useStdLibPath>
|
||||||
|
<cRuntime>1</cRuntime>
|
||||||
|
<privatePhobos>0</privatePhobos>
|
||||||
|
<additionalOptions />
|
||||||
|
<preBuildCommand />
|
||||||
|
<postBuildCommand />
|
||||||
|
<filesToClean>*.obj;*.cmd;*.build;*.json;*.dep</filesToClean>
|
||||||
|
</Config>
|
||||||
|
<Folder name="ircclient">
|
||||||
|
<Folder name="ircclient">
|
||||||
|
<Folder name="net">
|
||||||
|
<File path="src\ircclient\net\client.d" />
|
||||||
|
</Folder>
|
||||||
|
</Folder>
|
||||||
|
<File path="src\main.d" />
|
||||||
|
</Folder>
|
||||||
|
</DProject>
|
|
@ -0,0 +1,274 @@
|
||||||
|
module ircclient.net.client;
|
||||||
|
|
||||||
|
public import dlangui.core.asyncsocket;
|
||||||
|
import dlangui.core.logger;
|
||||||
|
import std.string : empty, format;
|
||||||
|
import std.conv : to;
|
||||||
|
|
||||||
|
interface IRCClientCallback {
|
||||||
|
void onIRCConnect(IRCClient client);
|
||||||
|
void onIRCDisconnect(IRCClient client);
|
||||||
|
void onIRCMessage(IRCClient client, IRCMessage message);
|
||||||
|
void onIRCPing(IRCClient client, string message);
|
||||||
|
void onIRCPrivmsg(IRCClient client, IRCAddress source, string target, string message);
|
||||||
|
void onIRCNotice(IRCClient client, IRCAddress source, string target, string message);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum IRCCommand : int {
|
||||||
|
UNKNOWN,
|
||||||
|
USER = 1000,
|
||||||
|
PRIVMSG, // :source PRIVMSG <target> :Message
|
||||||
|
NOTICE, // :source NOTICE <target> :Message
|
||||||
|
NICK,
|
||||||
|
PING, // PING :message
|
||||||
|
PONG, // PONG :message
|
||||||
|
QUIT, // :source QUIT :reason
|
||||||
|
JOIN, // :source JOIN :#channel
|
||||||
|
PART, // :source PART #channel :reason
|
||||||
|
MODE, //
|
||||||
|
}
|
||||||
|
|
||||||
|
IRCCommand findCommandId(string s) {
|
||||||
|
if (s.empty)
|
||||||
|
return IRCCommand.UNKNOWN;
|
||||||
|
if (s[0] >= '0' && s[0] <= '9') {
|
||||||
|
// parse numeric command ID
|
||||||
|
int n = 0;
|
||||||
|
foreach(ch; s)
|
||||||
|
if (ch >= '0' && ch <= '9')
|
||||||
|
n = n * 10 + (ch - '0');
|
||||||
|
return cast(IRCCommand)n;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (s) with(IRCCommand) {
|
||||||
|
case "USER": return USER;
|
||||||
|
case "PRIVMSG": return PRIVMSG;
|
||||||
|
case "NICK": return NICK;
|
||||||
|
case "QUIT": return QUIT;
|
||||||
|
case "PING": return PING;
|
||||||
|
case "PONG": return PONG;
|
||||||
|
case "JOIN": return JOIN;
|
||||||
|
case "PART": return PART;
|
||||||
|
case "NOTICE": return NOTICE;
|
||||||
|
default:
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// IRC message
|
||||||
|
class IRCMessage {
|
||||||
|
/// full message text
|
||||||
|
string msg;
|
||||||
|
/// optional first parameter of message, starting with : -- e.g. ":holmes.freenode.net"
|
||||||
|
string source; // source of message
|
||||||
|
IRCAddress sourceAddress; // parsed source
|
||||||
|
string command; // command text
|
||||||
|
IRCCommand commandId = IRCCommand.UNKNOWN; // command id
|
||||||
|
string[] params; // all parameters after command
|
||||||
|
string message; // optional message parameter, w/o starting :
|
||||||
|
string target; // for some command types - message target
|
||||||
|
/// parse message text
|
||||||
|
bool parse(string s) {
|
||||||
|
msg = s;
|
||||||
|
if (s.empty)
|
||||||
|
return false;
|
||||||
|
if (s[0] == ':') {
|
||||||
|
// parse source
|
||||||
|
source = parseDelimitedParameter(s);
|
||||||
|
if (source.length < 2)
|
||||||
|
return false;
|
||||||
|
source = source[1 .. $];
|
||||||
|
sourceAddress = new IRCAddress(source);
|
||||||
|
}
|
||||||
|
command = parseDelimitedParameter(s);
|
||||||
|
if (command.empty)
|
||||||
|
return false;
|
||||||
|
commandId = findCommandId(command);
|
||||||
|
while (!s.empty) {
|
||||||
|
if (s[0] == ':') {
|
||||||
|
params ~= s;
|
||||||
|
message = s[1 .. $];
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
params ~= parseDelimitedParameter(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch(commandId) with (IRCCommand) {
|
||||||
|
case PRIVMSG:
|
||||||
|
case NOTICE:
|
||||||
|
case JOIN:
|
||||||
|
case PART:
|
||||||
|
if (params.length > 0)
|
||||||
|
target = params[0];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IRCAddress {
|
||||||
|
string full;
|
||||||
|
string host;
|
||||||
|
string channel;
|
||||||
|
string nick;
|
||||||
|
string username;
|
||||||
|
this(string s) {
|
||||||
|
full = s;
|
||||||
|
string s1 = parseDelimitedParameter(s, '!');
|
||||||
|
if (!s.empty) {
|
||||||
|
// VadimLopatin!~Buggins@149.62.27.44
|
||||||
|
nick = s1;
|
||||||
|
username = s;
|
||||||
|
} else {
|
||||||
|
host = s1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@property string longName() {
|
||||||
|
if (!nick.empty) {
|
||||||
|
return nick ~ " (" ~ username ~ ")";
|
||||||
|
} else {
|
||||||
|
return full;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// IRC Client connection implementation
|
||||||
|
class IRCClient : AsyncSocketCallback {
|
||||||
|
protected:
|
||||||
|
AsyncSocket _socket;
|
||||||
|
IRCClientCallback _callback;
|
||||||
|
char[] _readbuf;
|
||||||
|
string _host;
|
||||||
|
ushort _port;
|
||||||
|
string _nick;
|
||||||
|
void onDataReceived(AsyncSocket socket, ubyte[] data) {
|
||||||
|
_readbuf ~= cast(char[])data;
|
||||||
|
// split by lines
|
||||||
|
int start = 0;
|
||||||
|
for (int i = 0; i + 1 < _readbuf.length; i++) {
|
||||||
|
if (_readbuf[i] == '\r' && _readbuf[i + 1] == '\n') {
|
||||||
|
if (i > start)
|
||||||
|
onMessageText(_readbuf[start .. i].dup);
|
||||||
|
start = i + 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (start < _readbuf.length) {
|
||||||
|
// has unfinished text
|
||||||
|
_readbuf = _readbuf[start .. $].dup;
|
||||||
|
} else {
|
||||||
|
// end of buffer
|
||||||
|
_readbuf.length = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void onMessageText(string msgText) {
|
||||||
|
IRCMessage msg = new IRCMessage();
|
||||||
|
if (msg.parse(msgText)) {
|
||||||
|
onMessage(msg);
|
||||||
|
} else {
|
||||||
|
Log.e("cannot parse IRC message " ~ msgText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void onMessage(IRCMessage msg) {
|
||||||
|
Log.d("MSG: " ~ msg.msg);
|
||||||
|
switch (msg.commandId) with (IRCCommand) {
|
||||||
|
case PING:
|
||||||
|
_callback.onIRCPing(this, msg.message);
|
||||||
|
break;
|
||||||
|
case PRIVMSG:
|
||||||
|
_callback.onIRCPrivmsg(this, msg.sourceAddress, msg.target, msg.message);
|
||||||
|
break;
|
||||||
|
case NOTICE:
|
||||||
|
_callback.onIRCNotice(this, msg.sourceAddress, msg.target, msg.message);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_callback.onIRCMessage(this, msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void onConnect(AsyncSocket socket) {
|
||||||
|
Log.e("onConnect");
|
||||||
|
_readbuf.length = 0;
|
||||||
|
_callback.onIRCConnect(this);
|
||||||
|
}
|
||||||
|
void onDisconnect(AsyncSocket socket) {
|
||||||
|
Log.e("onDisconnect");
|
||||||
|
_readbuf.length = 0;
|
||||||
|
_callback.onIRCDisconnect(this);
|
||||||
|
}
|
||||||
|
void onError(AsyncSocket socket, SocketError error, string msg) {
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
this() {
|
||||||
|
}
|
||||||
|
~this() {
|
||||||
|
if (_socket)
|
||||||
|
destroy(_socket);
|
||||||
|
}
|
||||||
|
@property string host() { return _host; }
|
||||||
|
@property ushort port() { return _port; }
|
||||||
|
@property string hostPort() { return "%s:%d".format(_host, _port); }
|
||||||
|
/// set socket to use
|
||||||
|
@property void socket(AsyncSocket sock) {
|
||||||
|
_socket = sock;
|
||||||
|
}
|
||||||
|
@property void callback(IRCClientCallback callback) {
|
||||||
|
_callback = callback;
|
||||||
|
}
|
||||||
|
void connect(string host, ushort port) {
|
||||||
|
_host = host;
|
||||||
|
_port = port;
|
||||||
|
_socket.connect(host, port);
|
||||||
|
}
|
||||||
|
void sendMessage(string msg) {
|
||||||
|
Log.d("CMD: " ~ msg);
|
||||||
|
_socket.send(cast(ubyte[])(msg ~ "\r\n"));
|
||||||
|
}
|
||||||
|
void pong(string msg) {
|
||||||
|
sendMessage("PONG :" ~ msg);
|
||||||
|
}
|
||||||
|
void privMsg(string destination, string message) {
|
||||||
|
sendMessage("PRIVMSG " ~ destination ~ " :" ~ message);
|
||||||
|
_callback.onIRCPrivmsg(this, new IRCAddress(_nick ~ "!~username@host"), destination, message);
|
||||||
|
}
|
||||||
|
void disconnect() {
|
||||||
|
_socket.disconnect();
|
||||||
|
}
|
||||||
|
@property string nick() {
|
||||||
|
return _nick;
|
||||||
|
}
|
||||||
|
@property void nick(string nickName) {
|
||||||
|
if (_nick.empty)
|
||||||
|
_nick = nickName;
|
||||||
|
sendMessage("NICK " ~ nickName);
|
||||||
|
}
|
||||||
|
void join(string channel) {
|
||||||
|
sendMessage("JOIN " ~ channel);
|
||||||
|
}
|
||||||
|
void part(string channel, string message) {
|
||||||
|
sendMessage("PART " ~ channel ~ (message.empty ? "" : ":" ~ message));
|
||||||
|
}
|
||||||
|
|
||||||
|
void quit(string message) {
|
||||||
|
sendMessage("QUIT " ~ (message.empty ? "" : ":" ~ message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// utility function to get first space delimited parameter from string
|
||||||
|
string parseDelimitedParameter(ref string s, char delimiter = ' ') {
|
||||||
|
string res;
|
||||||
|
int i = 0;
|
||||||
|
// parse source
|
||||||
|
for (; i < s.length; i++) {
|
||||||
|
if (s[i] == delimiter)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (i > 0) {
|
||||||
|
res = s[0 .. i];
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
s = i < s.length ? s[i .. $] : null;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,343 @@
|
||||||
|
module dmledit;
|
||||||
|
|
||||||
|
import dlangui;
|
||||||
|
import dlangui.dialogs.filedlg;
|
||||||
|
import dlangui.dialogs.dialog;
|
||||||
|
import dlangui.dml.dmlhighlight;
|
||||||
|
import std.array : replaceFirst;
|
||||||
|
import ircclient.net.client;
|
||||||
|
import std.string : startsWith, indexOf;
|
||||||
|
|
||||||
|
mixin APP_ENTRY_POINT;
|
||||||
|
|
||||||
|
// action codes
|
||||||
|
enum IDEActions : int {
|
||||||
|
//ProjectOpen = 1010000,
|
||||||
|
FileNew = 1010000,
|
||||||
|
FileOpen,
|
||||||
|
FileSave,
|
||||||
|
FileSaveAs,
|
||||||
|
FileSaveAll,
|
||||||
|
FileClose,
|
||||||
|
FileExit,
|
||||||
|
EditPreferences,
|
||||||
|
DebugStart,
|
||||||
|
HelpAbout,
|
||||||
|
}
|
||||||
|
|
||||||
|
// actions
|
||||||
|
const Action ACTION_FILE_NEW = new Action(IDEActions.FileNew, "MENU_FILE_NEW"c, "document-new", KeyCode.KEY_N, KeyFlag.Control);
|
||||||
|
const Action ACTION_FILE_SAVE = (new Action(IDEActions.FileSave, "MENU_FILE_SAVE"c, "document-save", KeyCode.KEY_S, KeyFlag.Control)).disableByDefault();
|
||||||
|
const Action ACTION_FILE_SAVE_AS = (new Action(IDEActions.FileSaveAs, "MENU_FILE_SAVE_AS"c)).disableByDefault();
|
||||||
|
const Action ACTION_FILE_OPEN = new Action(IDEActions.FileOpen, "MENU_FILE_OPEN"c, "document-open", KeyCode.KEY_O, KeyFlag.Control);
|
||||||
|
const Action ACTION_FILE_EXIT = new Action(IDEActions.FileExit, "MENU_FILE_EXIT"c, "document-close"c, KeyCode.KEY_X, KeyFlag.Alt);
|
||||||
|
const Action ACTION_EDIT_COPY = (new Action(EditorActions.Copy, "MENU_EDIT_COPY"c, "edit-copy"c, KeyCode.KEY_C, KeyFlag.Control)).addAccelerator(KeyCode.INS, KeyFlag.Control).disableByDefault();
|
||||||
|
const Action ACTION_EDIT_PASTE = (new Action(EditorActions.Paste, "MENU_EDIT_PASTE"c, "edit-paste"c, KeyCode.KEY_V, KeyFlag.Control)).addAccelerator(KeyCode.INS, KeyFlag.Shift).disableByDefault();
|
||||||
|
const Action ACTION_EDIT_CUT = (new Action(EditorActions.Cut, "MENU_EDIT_CUT"c, "edit-cut"c, KeyCode.KEY_X, KeyFlag.Control)).addAccelerator(KeyCode.DEL, KeyFlag.Shift).disableByDefault();
|
||||||
|
const Action ACTION_EDIT_UNDO = (new Action(EditorActions.Undo, "MENU_EDIT_UNDO"c, "edit-undo"c, KeyCode.KEY_Z, KeyFlag.Control)).disableByDefault();
|
||||||
|
const Action ACTION_EDIT_REDO = (new Action(EditorActions.Redo, "MENU_EDIT_REDO"c, "edit-redo"c, KeyCode.KEY_Y, KeyFlag.Control)).addAccelerator(KeyCode.KEY_Z, KeyFlag.Control|KeyFlag.Shift).disableByDefault();
|
||||||
|
const Action ACTION_EDIT_INDENT = (new Action(EditorActions.Indent, "MENU_EDIT_INDENT"c, "edit-indent"c, KeyCode.TAB, 0)).addAccelerator(KeyCode.KEY_BRACKETCLOSE, KeyFlag.Control).disableByDefault();
|
||||||
|
const Action ACTION_EDIT_UNINDENT = (new Action(EditorActions.Unindent, "MENU_EDIT_UNINDENT"c, "edit-unindent", KeyCode.TAB, KeyFlag.Shift)).addAccelerator(KeyCode.KEY_BRACKETOPEN, KeyFlag.Control).disableByDefault();
|
||||||
|
const Action ACTION_EDIT_TOGGLE_LINE_COMMENT = (new Action(EditorActions.ToggleLineComment, "MENU_EDIT_TOGGLE_LINE_COMMENT"c, null, KeyCode.KEY_DIVIDE, KeyFlag.Control)).disableByDefault();
|
||||||
|
const Action ACTION_EDIT_TOGGLE_BLOCK_COMMENT = (new Action(EditorActions.ToggleBlockComment, "MENU_EDIT_TOGGLE_BLOCK_COMMENT"c, null, KeyCode.KEY_DIVIDE, KeyFlag.Control|KeyFlag.Shift)).disableByDefault();
|
||||||
|
const Action ACTION_EDIT_PREFERENCES = (new Action(IDEActions.EditPreferences, "MENU_EDIT_PREFERENCES"c, null)).disableByDefault();
|
||||||
|
const Action ACTION_DEBUG_START = new Action(IDEActions.DebugStart, "MENU_DEBUG_UPDATE_PREVIEW"c, "debug-run"c, KeyCode.F5, 0);
|
||||||
|
const Action ACTION_HELP_ABOUT = new Action(IDEActions.HelpAbout, "MENU_HELP_ABOUT"c);
|
||||||
|
|
||||||
|
class IRCFrame : AppFrame, IRCClientCallback {
|
||||||
|
|
||||||
|
MenuItem mainMenuItems;
|
||||||
|
IRCClient _client;
|
||||||
|
|
||||||
|
~this() {
|
||||||
|
if (_client)
|
||||||
|
destroy(_client);
|
||||||
|
}
|
||||||
|
|
||||||
|
override protected void initialize() {
|
||||||
|
_appName = "DlangUI_IRCClient";
|
||||||
|
super.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// create main menu
|
||||||
|
override protected MainMenu createMainMenu() {
|
||||||
|
mainMenuItems = new MenuItem();
|
||||||
|
MenuItem fileItem = new MenuItem(new Action(1, "MENU_FILE"));
|
||||||
|
fileItem.add(//ACTION_FILE_NEW, ACTION_FILE_OPEN,
|
||||||
|
ACTION_FILE_EXIT);
|
||||||
|
mainMenuItems.add(fileItem);
|
||||||
|
//MenuItem editItem = new MenuItem(new Action(2, "MENU_EDIT"));
|
||||||
|
//editItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE,
|
||||||
|
// ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO,
|
||||||
|
// ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_EDIT_TOGGLE_BLOCK_COMMENT, ACTION_DEBUG_START);
|
||||||
|
//
|
||||||
|
//editItem.add(ACTION_EDIT_PREFERENCES);
|
||||||
|
//mainMenuItems.add(editItem);
|
||||||
|
MainMenu mainMenu = new MainMenu(mainMenuItems);
|
||||||
|
return mainMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// create app toolbars
|
||||||
|
override protected ToolBarHost createToolbars() {
|
||||||
|
ToolBarHost res = new ToolBarHost();
|
||||||
|
ToolBar tb;
|
||||||
|
tb = res.getOrAddToolbar("Standard");
|
||||||
|
tb.addButtons(//ACTION_FILE_NEW, ACTION_FILE_OPEN, ACTION_FILE_SAVE, ACTION_SEPARATOR,
|
||||||
|
ACTION_DEBUG_START);
|
||||||
|
|
||||||
|
//tb = res.getOrAddToolbar("Edit");
|
||||||
|
//tb.addButtons(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_SEPARATOR,
|
||||||
|
// ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool onCanClose() {
|
||||||
|
// todo
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// override to handle specific actions
|
||||||
|
override bool handleAction(const Action a) {
|
||||||
|
if (a) {
|
||||||
|
switch (a.id) {
|
||||||
|
case IDEActions.FileExit:
|
||||||
|
if (onCanClose())
|
||||||
|
window.close();
|
||||||
|
return true;
|
||||||
|
case IDEActions.HelpAbout:
|
||||||
|
window.showMessageBox(UIString("About DlangUI IRC Client"d),
|
||||||
|
UIString("DLangUI IRC Client\n(C) Vadim Lopatin, 2015\nhttp://github.com/buggins/dlangui\nSimple IRC client"d));
|
||||||
|
return true;
|
||||||
|
case IDEActions.EditPreferences:
|
||||||
|
//showPreferences();
|
||||||
|
return true;
|
||||||
|
case IDEActions.DebugStart:
|
||||||
|
connect();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.handleAction(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// override to handle specific actions state (e.g. change enabled state for supported actions)
|
||||||
|
override bool handleActionStateRequest(const Action a) {
|
||||||
|
switch (a.id) {
|
||||||
|
case IDEActions.HelpAbout:
|
||||||
|
case IDEActions.FileNew:
|
||||||
|
case IDEActions.FileSave:
|
||||||
|
case IDEActions.FileOpen:
|
||||||
|
case IDEActions.DebugStart:
|
||||||
|
case IDEActions.EditPreferences:
|
||||||
|
a.state = ACTION_STATE_ENABLED;
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.handleActionStateRequest(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TabWidget _tabs;
|
||||||
|
/// create app body widget
|
||||||
|
override protected Widget createBody() {
|
||||||
|
_tabs = new TabWidget("TABS");
|
||||||
|
_tabs.layoutWidth = FILL_PARENT;
|
||||||
|
_tabs.layoutHeight = FILL_PARENT;
|
||||||
|
//tabs.addTab(new IRCWindow("sample"), "Sample"d);
|
||||||
|
statusLine.setStatusText(toUTF32("Not Connected"));
|
||||||
|
return _tabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void connect() {
|
||||||
|
if (!_client) {
|
||||||
|
_client = new IRCClient();
|
||||||
|
AsyncSocket connection = window.createAsyncSocket(_client);
|
||||||
|
_client.socket = connection;
|
||||||
|
_client.callback = this;
|
||||||
|
}
|
||||||
|
_client.connect("irc.freenode.net", 6667);
|
||||||
|
}
|
||||||
|
|
||||||
|
IRCWindow getOrCreateWindowFor(string party) {
|
||||||
|
string winId = party;
|
||||||
|
IRCWindow w = cast(IRCWindow)_tabs.tabBody(winId);
|
||||||
|
if (!w) {
|
||||||
|
w = new IRCWindow(winId, _client);
|
||||||
|
_tabs.addTab(w, toUTF32(winId));
|
||||||
|
}
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onIRCConnect(IRCClient client) {
|
||||||
|
IRCWindow w = getOrCreateWindowFor(client.hostPort);
|
||||||
|
w.addLine("connected to " ~ client.hostPort);
|
||||||
|
client.sendMessage("USER username 0 * :Real name");
|
||||||
|
client.nick("dlangui_irc");
|
||||||
|
client.join("#clienttest");
|
||||||
|
statusLine.setStatusText(toUTF32("Connected to " ~ client.hostPort));
|
||||||
|
}
|
||||||
|
|
||||||
|
void onIRCDisconnect(IRCClient client) {
|
||||||
|
IRCWindow w = getOrCreateWindowFor(client.hostPort);
|
||||||
|
w.addLine("disconnected from " ~ client.hostPort);
|
||||||
|
statusLine.setStatusText(toUTF32("Disconnected"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void onIRCPing(IRCClient client, string message) {
|
||||||
|
IRCWindow w = getOrCreateWindowFor(client.hostPort);
|
||||||
|
w.addLine("PING " ~ message);
|
||||||
|
client.pong(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onIRCPrivmsg(IRCClient client, IRCAddress source, string target, string message) {
|
||||||
|
string wid = target.startsWith("#") ? target : client.hostPort;
|
||||||
|
if (target == client.nick)
|
||||||
|
wid = source.nick;
|
||||||
|
else if (source.nick == client.nick)
|
||||||
|
wid = target;
|
||||||
|
IRCWindow w = getOrCreateWindowFor(wid);
|
||||||
|
w.addLine("<" ~ (!source.nick.empty ? source.nick : source.full) ~ "> " ~ message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onIRCNotice(IRCClient client, IRCAddress source, string target, string message) {
|
||||||
|
IRCWindow w = getOrCreateWindowFor(target.startsWith("#") ? target : client.hostPort);
|
||||||
|
w.addLine("-" ~ source.full ~ "- " ~ message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onIRCMessage(IRCClient client, IRCMessage message) {
|
||||||
|
IRCWindow w = getOrCreateWindowFor(client.hostPort);
|
||||||
|
if (message.commandId < 1000) {
|
||||||
|
// custom server messages
|
||||||
|
w.addLine(message.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (message.commandId == IRCCommand.JOIN || message.commandId == IRCCommand.PART) {
|
||||||
|
if (message.sourceAddress && !message.sourceAddress.nick.empty && message.target.startsWith("#")) {
|
||||||
|
w = getOrCreateWindowFor(message.target);
|
||||||
|
if (message.commandId == IRCCommand.JOIN) {
|
||||||
|
w.addLine("* " ~ message.sourceAddress.longName ~ " has joined " ~ message.target);
|
||||||
|
} else {
|
||||||
|
w.addLine("* " ~ message.sourceAddress.longName ~ " has left " ~ message.target ~ (message.message.empty ? "" : ("(Reason: " ~ message.message ~ ")")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
w.addLine(message.msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum IRCWindowKind {
|
||||||
|
Server,
|
||||||
|
Channel,
|
||||||
|
Private
|
||||||
|
}
|
||||||
|
|
||||||
|
class IRCWindow : VerticalLayout, EditorActionHandler {
|
||||||
|
LogWidget _editBox;
|
||||||
|
StringListWidget _listBox;
|
||||||
|
EditLine _editLine;
|
||||||
|
IRCClient _client;
|
||||||
|
IRCWindowKind _kind;
|
||||||
|
this(string ID, IRCClient client) {
|
||||||
|
super(ID);
|
||||||
|
_client = client;
|
||||||
|
layoutWidth = FILL_PARENT;
|
||||||
|
layoutHeight = FILL_PARENT;
|
||||||
|
HorizontalLayout hlayout = new HorizontalLayout();
|
||||||
|
hlayout.layoutWidth = FILL_PARENT;
|
||||||
|
hlayout.layoutHeight = FILL_PARENT;
|
||||||
|
_editBox = new LogWidget();
|
||||||
|
_editBox.layoutWidth = FILL_PARENT;
|
||||||
|
_editBox.layoutHeight = FILL_PARENT;
|
||||||
|
hlayout.addChild(_editBox);
|
||||||
|
if (ID.startsWith("#")) {
|
||||||
|
_listBox = new StringListWidget();
|
||||||
|
_listBox.layoutHeight = FILL_PARENT;
|
||||||
|
_listBox.layoutWidth = WRAP_CONTENT;
|
||||||
|
_listBox.minWidth = 100;
|
||||||
|
_listBox.maxWidth = 200;
|
||||||
|
_listBox.orientation = Orientation.Vertical;
|
||||||
|
_listBox.items = ["Nick1"d, "Nick2"d];
|
||||||
|
hlayout.addChild(new ResizerWidget(null, Orientation.Horizontal));
|
||||||
|
hlayout.addChild(_listBox);
|
||||||
|
_kind = IRCWindowKind.Channel;
|
||||||
|
} else {
|
||||||
|
if (id.indexOf(':') >= 0)
|
||||||
|
_kind = IRCWindowKind.Server;
|
||||||
|
else
|
||||||
|
_kind = IRCWindowKind.Private;
|
||||||
|
}
|
||||||
|
addChild(hlayout);
|
||||||
|
_editLine = new EditLine();
|
||||||
|
addChild(_editLine);
|
||||||
|
_editLine.editorAction = this;
|
||||||
|
}
|
||||||
|
void addLine(string s) {
|
||||||
|
_editBox.appendText(toUTF32(s ~ "\n"));
|
||||||
|
if (visible)
|
||||||
|
window.update();
|
||||||
|
}
|
||||||
|
bool onEditorAction(const Action action) {
|
||||||
|
if (!_editLine.text.empty) {
|
||||||
|
string s = toUTF8(_editLine.text);
|
||||||
|
_editLine.text = ""d;
|
||||||
|
if (s.startsWith("/")) {
|
||||||
|
Log.d("Custom command: " ~ s);
|
||||||
|
// command
|
||||||
|
string cmd = parseDelimitedParameter(s);
|
||||||
|
|
||||||
|
if (cmd == "/quit") {
|
||||||
|
_client.quit(param);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string param = parseDelimitedParameter(s);
|
||||||
|
if (cmd == "/nick" && !param.empty) {
|
||||||
|
_client.nick(param);
|
||||||
|
} else if (cmd == "/join" && param.startsWith("#")) {
|
||||||
|
_client.join(param);
|
||||||
|
} else if (cmd == "/part" && param.startsWith("#")) {
|
||||||
|
_client.part(param, s);
|
||||||
|
} else if (cmd == "/msg" && !param.empty && !s.empty) {
|
||||||
|
_client.privMsg(param, s);
|
||||||
|
} else {
|
||||||
|
Log.d("Unknown command: " ~ cmd);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// message
|
||||||
|
if (_kind != IRCWindowKind.Server) {
|
||||||
|
_client.privMsg(id, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// entry point for dlangui based application
|
||||||
|
extern (C) int UIAppMain(string[] args) {
|
||||||
|
|
||||||
|
// embed non-standard resources listed in views/resources.list into executable
|
||||||
|
embeddedResourceList.addResources(embedResourcesFromList!("resources.list")());
|
||||||
|
|
||||||
|
/// set font gamma (1.0 is neutral, < 1.0 makes glyphs lighter, >1.0 makes glyphs bolder)
|
||||||
|
FontManager.fontGamma = 0.8;
|
||||||
|
FontManager.hintingMode = HintingMode.Normal;
|
||||||
|
|
||||||
|
// create window
|
||||||
|
Window window = Platform.instance.createWindow("DlangUI IRC Client"d, null, WindowFlag.Resizable, 700, 470);
|
||||||
|
|
||||||
|
// create some widget to show in window
|
||||||
|
window.windowIcon = drawableCache.getImage("dlangui-logo1");
|
||||||
|
|
||||||
|
|
||||||
|
// create some widget to show in window
|
||||||
|
window.mainWidget = new IRCFrame();
|
||||||
|
|
||||||
|
// show window
|
||||||
|
window.show();
|
||||||
|
|
||||||
|
// run message loop
|
||||||
|
return Platform.instance.enterMessageLoop();
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
EXETYPE NT
|
||||||
|
SUBSYSTEM WINDOWS
|
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 828 B |
After Width: | Height: | Size: 892 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.0 KiB |
|
@ -0,0 +1,40 @@
|
||||||
|
EXIT=Exit
|
||||||
|
MENU_FILE=&File
|
||||||
|
MENU_FILE_NEW=&New
|
||||||
|
MENU_FILE_OPEN=&Open
|
||||||
|
MENU_FILE_OPEN_RECENT=Open recent
|
||||||
|
MENU_FILE_SAVE=&Save
|
||||||
|
MENU_FILE_EXIT=E&xit
|
||||||
|
MENU_EDIT=&Edit
|
||||||
|
MENU_EDIT_COPY=&Copy
|
||||||
|
MENU_EDIT_PASTE=&Paste
|
||||||
|
MENU_EDIT_CUT=Cu&t
|
||||||
|
MENU_EDIT_UNDO=&Undo
|
||||||
|
MENU_EDIT_REDO=&Redo
|
||||||
|
MENU_EDIT_INDENT=Indent block
|
||||||
|
MENU_EDIT_UNINDENT=Unindent block
|
||||||
|
MENU_EDIT_TOGGLE_LINE_COMMENT=Toggle line comment
|
||||||
|
MENU_EDIT_TOGGLE_BLOCK_COMMENT=Toggle block comment
|
||||||
|
MENU_EDIT_PREFERENCES=&Preferences
|
||||||
|
MENU_VIEW=&View
|
||||||
|
MENU_VIEW_LANGUAGE=Interface &Language
|
||||||
|
MENU_VIEW_LANGUAGE_EN=English
|
||||||
|
MENU_VIEW_LANGUAGE_RU=Русский
|
||||||
|
MENU_VIEW_THEME=&Theme
|
||||||
|
MENU_VIEW_THEME_DEFAULT=Default
|
||||||
|
MENU_VIEW_THEME_DARK=Dark
|
||||||
|
MENU_VIEW_THEME_CUSTOM1=Custom 1
|
||||||
|
MENU_WINDOW=&Window
|
||||||
|
MENU_WINDOW_PREFERENCES=&Preferences
|
||||||
|
MENU_HELP=&Help
|
||||||
|
MENU_HELP_VIEW_HELP=&View help
|
||||||
|
MENU_HELP_ABOUT=&About
|
||||||
|
MENU_DEBUG_UPDATE_PREVIEW=Update Preview
|
||||||
|
|
||||||
|
TAB_LONG_LIST=Long list
|
||||||
|
TAB_BUTTONS=Buttons
|
||||||
|
TAB_ANIMATION=Animation
|
||||||
|
TAB_TABLE_LAYOUT=Table layout
|
||||||
|
TAB_EDITORS=Editors
|
||||||
|
TAB_CANVAS=Canvas
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
EXIT=Выход
|
||||||
|
MENU_FILE=&Файл
|
||||||
|
MENU_FILE_OPEN=&Открыть
|
||||||
|
MENU_FILE_OPEN_RECENT=Открыть из последних
|
||||||
|
MENU_FILE_SAVE=&Сохранить
|
||||||
|
MENU_FILE_EXIT=Вы&ход
|
||||||
|
MENU_EDIT=&Правка
|
||||||
|
MENU_EDIT_COPY=&Копировать
|
||||||
|
MENU_EDIT_PASTE=&Вставить
|
||||||
|
MENU_EDIT_CUT=Вырезать
|
||||||
|
MENU_EDIT_UNDO=&Отмена
|
||||||
|
MENU_EDIT_REDO=&Повторить
|
||||||
|
MENU_EDIT_PREFERENCES=&Настройки
|
||||||
|
MENU_VIEW=&Вид
|
||||||
|
MENU_VIEW_LANGUAGE=&Язык интерфейса
|
||||||
|
MENU_VIEW_LANGUAGE_EN=English
|
||||||
|
MENU_VIEW_LANGUAGE_RU=Русский
|
||||||
|
MENU_VIEW_THEME=&Тема
|
||||||
|
MENU_VIEW_THEME_DEFAULT=Стандартная
|
||||||
|
MENU_VIEW_THEME_DARK=Тёмная
|
||||||
|
MENU_VIEW_THEME_CUSTOM1=Пример 1
|
||||||
|
MENU_WINDOW=&Окно
|
||||||
|
MENU_WINDOW_PREFERENCES=&Настройки
|
||||||
|
MENU_HELP=&Справка
|
||||||
|
MENU_HELP_VIEW_HELP=&Просмотр справки
|
||||||
|
MENU_HELP_ABOUT=&О программе
|
||||||
|
|
||||||
|
TAB_LONG_LIST=Длинный список
|
||||||
|
TAB_BUTTONS=Кнопки
|
||||||
|
TAB_ANIMATION=Анимация
|
||||||
|
TAB_TABLE_LAYOUT=Табличный layout
|
||||||
|
TAB_EDITORS=Редакторы
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 383 B |
After Width: | Height: | Size: 605 B |
After Width: | Height: | Size: 516 B |
After Width: | Height: | Size: 593 B |
After Width: | Height: | Size: 701 B |
After Width: | Height: | Size: 635 B |
After Width: | Height: | Size: 771 B |
After Width: | Height: | Size: 559 B |
After Width: | Height: | Size: 436 B |
After Width: | Height: | Size: 368 B |
After Width: | Height: | Size: 413 B |
After Width: | Height: | Size: 598 B |
After Width: | Height: | Size: 753 B |
After Width: | Height: | Size: 810 B |
After Width: | Height: | Size: 367 B |
After Width: | Height: | Size: 684 B |
After Width: | Height: | Size: 8.8 KiB |
|
@ -0,0 +1,20 @@
|
||||||
|
res/i18n/en.ini
|
||||||
|
res/i18n/ru.ini
|
||||||
|
res/mdpi/cr3_logo.png
|
||||||
|
res/mdpi/debug-run.png
|
||||||
|
res/mdpi/document-close.png
|
||||||
|
res/mdpi/document-new.png
|
||||||
|
res/mdpi/document-open-recent.png
|
||||||
|
res/mdpi/document-open.png
|
||||||
|
res/mdpi/document-properties.png
|
||||||
|
res/mdpi/document-save-as.png
|
||||||
|
res/mdpi/document-save.png
|
||||||
|
res/mdpi/edit-copy.png
|
||||||
|
res/mdpi/edit-cut.png
|
||||||
|
res/mdpi/edit-indent.png
|
||||||
|
res/mdpi/edit-paste.png
|
||||||
|
res/mdpi/edit-redo.png
|
||||||
|
res/mdpi/edit-undo.png
|
||||||
|
res/mdpi/edit-unindent.png
|
||||||
|
res/mdpi/text-dml.png
|
||||||
|
res/mdpi/tx_fabric.jpg
|
|
@ -0,0 +1,195 @@
|
||||||
|
module dlangui.core.asyncsocket;
|
||||||
|
|
||||||
|
import std.socket;
|
||||||
|
import core.thread;
|
||||||
|
import dlangui.core.queue;
|
||||||
|
import dlangui.core.logger;
|
||||||
|
|
||||||
|
/// Socket state
|
||||||
|
enum SocketState {
|
||||||
|
Disconnected,
|
||||||
|
Connecting,
|
||||||
|
Connected
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asynchronous socket interface
|
||||||
|
interface AsyncSocket {
|
||||||
|
@property SocketState state();
|
||||||
|
void connect(string host, ushort port);
|
||||||
|
void disconnect();
|
||||||
|
void send(ubyte[] data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Socket error code
|
||||||
|
enum SocketError {
|
||||||
|
ConnectError,
|
||||||
|
WriteError,
|
||||||
|
NotConnected,
|
||||||
|
AlreadyConnected,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Callback interface for using by AsyncSocket implementations
|
||||||
|
interface AsyncSocketCallback {
|
||||||
|
void onDataReceived(AsyncSocket socket, ubyte[] data);
|
||||||
|
void onConnect(AsyncSocket socket);
|
||||||
|
void onDisconnect(AsyncSocket socket);
|
||||||
|
void onError(AsyncSocket socket, SocketError error, string msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// proxy for AsyncConnectionHandler - to call in GUI thread
|
||||||
|
class AsyncSocketCallbackProxy : AsyncSocketCallback {
|
||||||
|
private:
|
||||||
|
AsyncSocketCallback _handler;
|
||||||
|
void delegate(void delegate() runnable) _executor;
|
||||||
|
public:
|
||||||
|
this(AsyncSocketCallback handler, void delegate(void delegate() runnable) executor) {
|
||||||
|
_executor = executor;
|
||||||
|
_handler = handler;
|
||||||
|
}
|
||||||
|
void onDataReceived(AsyncSocket socket, ubyte[] data) {
|
||||||
|
_executor(delegate() {
|
||||||
|
_handler.onDataReceived(socket, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
void onConnect(AsyncSocket socket) {
|
||||||
|
_executor(delegate() {
|
||||||
|
_handler.onConnect(socket);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
void onDisconnect(AsyncSocket socket) {
|
||||||
|
_executor(delegate() {
|
||||||
|
_handler.onDisconnect(socket);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
void onError(AsyncSocket socket, SocketError error, string msg) {
|
||||||
|
_executor(delegate() {
|
||||||
|
_handler.onError(socket, error, msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asynchrous socket which uses separate thread for operation
|
||||||
|
class AsyncClientConnection : Thread, AsyncSocket {
|
||||||
|
protected:
|
||||||
|
Socket _sock;
|
||||||
|
SocketSet _readSet;
|
||||||
|
SocketSet _writeSet;
|
||||||
|
SocketSet _errorSet;
|
||||||
|
RunnableQueue _queue;
|
||||||
|
AsyncSocketCallback _callback;
|
||||||
|
SocketState _state = SocketState.Disconnected;
|
||||||
|
void threadProc() {
|
||||||
|
ubyte[] readBuf = new ubyte[65536];
|
||||||
|
Log.d("entering ClientConnection thread proc");
|
||||||
|
for(;;) {
|
||||||
|
if (_queue.closed)
|
||||||
|
break;
|
||||||
|
Runnable task;
|
||||||
|
if (_queue.get(task, _sock ? 10 : 1000)) {
|
||||||
|
if (_queue.closed)
|
||||||
|
break;
|
||||||
|
task();
|
||||||
|
}
|
||||||
|
if (_sock) {
|
||||||
|
_readSet.reset();
|
||||||
|
_writeSet.reset();
|
||||||
|
_errorSet.reset();
|
||||||
|
_readSet.add(_sock);
|
||||||
|
_writeSet.add(_sock);
|
||||||
|
_errorSet.add(_sock);
|
||||||
|
if (Socket.select(_readSet, _writeSet, _errorSet, dur!"msecs"(10)) > 0) {
|
||||||
|
if (_writeSet.isSet(_sock)) {
|
||||||
|
if (_state == SocketState.Connecting) {
|
||||||
|
_state = SocketState.Connected;
|
||||||
|
_callback.onConnect(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_readSet.isSet(_sock)) {
|
||||||
|
long bytesRead = _sock.receive(readBuf);
|
||||||
|
if (bytesRead > 0) {
|
||||||
|
_callback.onDataReceived(this, readBuf[0 .. cast(int)bytesRead].dup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_errorSet.isSet(_sock)) {
|
||||||
|
doDisconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doDisconnect();
|
||||||
|
Log.d("exiting ClientConnection thread proc");
|
||||||
|
}
|
||||||
|
void doDisconnect() {
|
||||||
|
if (_sock) {
|
||||||
|
_sock.shutdown(SocketShutdown.BOTH);
|
||||||
|
_sock.close();
|
||||||
|
destroy(_sock);
|
||||||
|
_sock = null;
|
||||||
|
if (_state != SocketState.Disconnected) {
|
||||||
|
_state = SocketState.Disconnected;
|
||||||
|
_callback.onDisconnect(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
this(AsyncSocketCallback cb) {
|
||||||
|
super(&threadProc);
|
||||||
|
_callback = cb;
|
||||||
|
_queue = new RunnableQueue();
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
~this() {
|
||||||
|
_queue.close();
|
||||||
|
join();
|
||||||
|
}
|
||||||
|
@property SocketState state() {
|
||||||
|
return _state;
|
||||||
|
}
|
||||||
|
void connect(string host, ushort port) {
|
||||||
|
_queue.put(delegate() {
|
||||||
|
if (_state == SocketState.Connecting) {
|
||||||
|
_callback.onError(this, SocketError.NotConnected, "socket is already connecting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_state == SocketState.Connected) {
|
||||||
|
_callback.onError(this, SocketError.NotConnected, "socket is already connected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
doDisconnect();
|
||||||
|
_sock = new TcpSocket();
|
||||||
|
_sock.blocking = false;
|
||||||
|
_readSet = new SocketSet();
|
||||||
|
_writeSet = new SocketSet();
|
||||||
|
_errorSet = new SocketSet();
|
||||||
|
_state = SocketState.Connecting;
|
||||||
|
_sock.connect(new InternetAddress(host, port));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
void disconnect() {
|
||||||
|
_queue.put(delegate() {
|
||||||
|
if (!_sock)
|
||||||
|
return;
|
||||||
|
doDisconnect();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
void send(ubyte[] data) {
|
||||||
|
_queue.put(delegate() {
|
||||||
|
if (!_sock) {
|
||||||
|
_callback.onError(this, SocketError.NotConnected, "socket is not connected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (;;) {
|
||||||
|
long bytesSent = _sock.send(data);
|
||||||
|
if (bytesSent == Socket.ERROR) {
|
||||||
|
_callback.onError(this, SocketError.WriteError, "error while writing to connection");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
//Log.d("Bytes sent:" ~ to!string(bytesSent));
|
||||||
|
if (bytesSent >= data.length)
|
||||||
|
return;
|
||||||
|
data = data[cast(int)bytesSent .. $];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
module dlangui.core.queue;
|
||||||
|
|
||||||
|
import core.sync.condition;
|
||||||
|
import core.sync.mutex;
|
||||||
|
|
||||||
|
class BlockingQueue(T) {
|
||||||
|
|
||||||
|
private Mutex _mutex;
|
||||||
|
private Condition _condition;
|
||||||
|
private T[] _buffer;
|
||||||
|
private int _readPos;
|
||||||
|
private int _writePos;
|
||||||
|
private shared bool _closed;
|
||||||
|
|
||||||
|
this() {
|
||||||
|
_mutex = new Mutex();
|
||||||
|
_condition = new Condition(_mutex);
|
||||||
|
_readPos = 0;
|
||||||
|
_writePos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
~this() {
|
||||||
|
close();
|
||||||
|
if (_condition) {
|
||||||
|
destroy(_condition);
|
||||||
|
_condition = null;
|
||||||
|
}
|
||||||
|
if (_mutex) {
|
||||||
|
destroy(_mutex);
|
||||||
|
_mutex = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
if (_mutex && !_closed) {
|
||||||
|
synchronized(_mutex) {
|
||||||
|
_closed = true;
|
||||||
|
if (_condition !is null)
|
||||||
|
_condition.notifyAll();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_closed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns true if queue is closed
|
||||||
|
@property bool closed() {
|
||||||
|
return _closed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void move() {
|
||||||
|
if (_readPos > 1024 && _readPos > _buffer.length * 3 / 4) {
|
||||||
|
// move buffer data
|
||||||
|
for (int i = 0; _readPos + i < _writePos; i++)
|
||||||
|
_buffer[i] = _buffer[_readPos + i];
|
||||||
|
_writePos -= _readPos;
|
||||||
|
_readPos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void append(ref T item) {
|
||||||
|
if (_writePos >= _buffer.length) {
|
||||||
|
move();
|
||||||
|
_buffer.length = _buffer.length == 0 ? 64 : _buffer.length * 2;
|
||||||
|
}
|
||||||
|
_buffer[_writePos++] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(T item) {
|
||||||
|
if (_closed)
|
||||||
|
return;
|
||||||
|
synchronized(_mutex) {
|
||||||
|
if (_closed)
|
||||||
|
return;
|
||||||
|
append(item);
|
||||||
|
_condition.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void put(T[] items) {
|
||||||
|
if (_closed)
|
||||||
|
return;
|
||||||
|
synchronized(_mutex) {
|
||||||
|
if (_closed)
|
||||||
|
return;
|
||||||
|
foreach(ref item; items) {
|
||||||
|
append(item);
|
||||||
|
}
|
||||||
|
_condition.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get(ref T value, int timeoutMillis = 0) {
|
||||||
|
if (_closed)
|
||||||
|
return false;
|
||||||
|
synchronized(_mutex) {
|
||||||
|
if (_closed)
|
||||||
|
return false;
|
||||||
|
if (_readPos < _writePos) {
|
||||||
|
value = _buffer[_readPos++];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (timeoutMillis <= 0)
|
||||||
|
_condition.wait(); // no timeout
|
||||||
|
else if (!_condition.wait(dur!"msecs"(timeoutMillis)))
|
||||||
|
return false; // timeout
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
if (_readPos < _writePos) {
|
||||||
|
value = _buffer[_readPos++];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getAll(ref T[] values, int timeoutMillis) {
|
||||||
|
if (_closed)
|
||||||
|
return false;
|
||||||
|
synchronized(_mutex) {
|
||||||
|
if (_closed)
|
||||||
|
return false;
|
||||||
|
values.length = 0;
|
||||||
|
while (_readPos < _writePos)
|
||||||
|
values ~= _buffer[_readPos++];
|
||||||
|
if (values.length > 0)
|
||||||
|
return true;
|
||||||
|
if (timeoutMillis <= 0)
|
||||||
|
_condition.wait(); // no timeout
|
||||||
|
else if (!_condition.wait(dur!"msecs"(timeoutMillis)))
|
||||||
|
return false; // timeout
|
||||||
|
while (_readPos < _writePos)
|
||||||
|
values ~= _buffer[_readPos++];
|
||||||
|
if (values.length > 0)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alias Runnable = void delegate();
|
||||||
|
alias RunnableQueue = BlockingQueue!Runnable;
|
|
@ -26,6 +26,7 @@ import dlangui.widgets.widget;
|
||||||
import dlangui.widgets.popup;
|
import dlangui.widgets.popup;
|
||||||
import dlangui.graphics.drawbuf;
|
import dlangui.graphics.drawbuf;
|
||||||
import dlangui.core.stdaction;
|
import dlangui.core.stdaction;
|
||||||
|
import dlangui.core.asyncsocket;
|
||||||
|
|
||||||
private import dlangui.graphics.gldrawbuf;
|
private import dlangui.graphics.gldrawbuf;
|
||||||
private import std.algorithm;
|
private import std.algorithm;
|
||||||
|
@ -814,6 +815,12 @@ class Window : CustomEventTarget {
|
||||||
postEvent(event);
|
postEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates async socket
|
||||||
|
AsyncSocket createAsyncSocket(AsyncSocketCallback callback) {
|
||||||
|
AsyncClientConnection conn = new AsyncClientConnection(new AsyncSocketCallbackProxy(callback, &executeInUiThread));
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
/// remove event from queue by unique id if not yet dispatched (this method can be used from background thread)
|
/// remove event from queue by unique id if not yet dispatched (this method can be used from background thread)
|
||||||
void cancelEvent(uint uniqueId) {
|
void cancelEvent(uint uniqueId) {
|
||||||
CustomEvent ev = _eventList.get(uniqueId);
|
CustomEvent ev = _eventList.get(uniqueId);
|
||||||
|
|