1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // Tox protocol thread
18 // Each Tox connection spawns a thread that does all the communication.
19 // Incoming messages are posted to [glconCtlWindow].
20 // Asking for outgoing actions are done with public interface.
21 // No tox internals are exposed to the outer world.
22 module toxproto
is aliced
;
27 import std
.concurrency
;
42 //version = ToxCoreDebug;
45 // ////////////////////////////////////////////////////////////////////////// //
49 // ////////////////////////////////////////////////////////////////////////// //
50 class ProtocolException
: Exception
{
51 this (string msg
, string file
=__FILE__
, usize line
=__LINE__
, Throwable next
=null) pure nothrow @safe @nogc {
52 super(msg
, file
, line
, next
);
57 // ////////////////////////////////////////////////////////////////////////// //
58 // messages thread sends to [glconCtlWindow].
59 // [who] can be the same as [self] to indicate account state changes
62 PubKey self
; // account
63 PubKey who
; // can be same as [self]
64 nothrow @trusted @nogc:
65 this (in ref PubKey aself
, in ref PubKey awho
) { self
[] = aself
[]; who
= awho
[]; }
68 // connection state changed
69 class ToxEventConnection
: ToxEventBase
{
71 nothrow @trusted @nogc:
72 this (in ref PubKey aself
, in ref PubKey awho
, bool aconnected
) { super(aself
, awho
); connected
= aconnected
; }
75 // online status changed
76 class ToxEventStatus
: ToxEventBase
{
78 nothrow @trusted @nogc:
79 this (in ref PubKey aself
, in ref PubKey awho
, ContactStatus astatus
) { super(aself
, awho
); status
= astatus
; }
83 class ToxEventNick
: ToxEventBase
{
84 string nick
; // new nick
85 nothrow @trusted @nogc:
86 this (in ref PubKey aself
, in ref PubKey awho
, string anick
) { super(aself
, awho
); nick
= anick
; }
89 // status message changed
90 class ToxEventStatusMsg
: ToxEventBase
{
91 string message
; // new message
92 nothrow @trusted @nogc:
93 this (in ref PubKey aself
, in ref PubKey awho
, string amessage
) { super(aself
, awho
); message
= amessage
; }
96 // typing status changed
97 class ToxEventTyping
: ToxEventBase
{
99 nothrow @trusted @nogc:
100 this (in ref PubKey aself
, in ref PubKey awho
, bool atyping
) { super(aself
, awho
); typing
= atyping
; }
104 class ToxEventMessage
: ToxEventBase
{
105 bool action
; // is this an "action" message? (/me)
109 this (in ref PubKey aself
, in ref PubKey awho
, bool aaction
, string amessage
) {
113 time
= Clock
.currTime
;
116 this (in ref PubKey aself
, in ref PubKey awho
, bool aaction
, string amessage
, SysTime atime
) {
124 // send message ack comes
125 class ToxEventMessageAck
: ToxEventBase
{
127 nothrow @trusted @nogc:
128 this (in ref PubKey aself
, in ref PubKey awho
, long amsgid
) { super(aself
, awho
); msgid
= amsgid
; }
131 // friend request comes
132 class ToxEventFriendReq
: ToxEventBase
{
133 string message
; // request message
134 nothrow @trusted @nogc:
135 this (in ref PubKey aself
, in ref PubKey awho
, string amessage
) { super(aself
, awho
); message
= amessage
; }
139 // ////////////////////////////////////////////////////////////////////////// //
141 /// shutdown protocol module
142 void toxCoreShutdownAll () {
144 synchronized(TPInfo
.classinfo
) {
145 foreach (TPInfo ti
; allProtos
) {
146 ti
.tid
.send(TrdCmdQuit
.init
);
150 while (acksLeft
> 0) {
152 (TrdCmdQuitAck ack
) { --acksLeft
; },
157 foreach (TPInfo tpi
; allProtos
) {
158 // save toxcode data, if there is any
159 if (tpi
.tox
!is null && tpi
.toxDataDiskName
.length
) saveToxCoreData(tpi
.tox
, tpi
.toxDataDiskName
);
160 if (tpi
.tox
!is null) { tox_kill(tpi
.tox
); tpi
.tox
= null; }
165 // ////////////////////////////////////////////////////////////////////////// //
168 static immutable PubKey toxCoreEmptyKey
= 0;
170 /// delegate that will be used to send messages.
171 /// can be called from any thread, so it should be thread-safe, and should avoid deadlocks.
172 /// the delegate should be set on program startup, and should not be changed anymore.
173 /// FIXME: add API to change this!
174 __gshared
void delegate (Object msg
) nothrow toxCoreSendEvent
;
177 bool isValidKey (in ref PubKey key
) nothrow @safe @nogc => (key
[] != toxCoreEmptyKey
[]);
180 /// returns public key for account with the given data file, or [toxCoreEmptyKey].
181 PubKey
toxCoreGetAccountPubKey (const(char)[] toxdatafname
) nothrow {
183 synchronized(TPInfo
.classinfo
) {
184 foreach (TPInfo ti
; allProtos
) if (ti
.toxDataDiskName
== toxdatafname
) { tacc
= ti
; break; }
187 // nope, not found, try to open account
188 ProtoOptions protoOpts
;
189 auto tox
= toxCreateInstance(toxdatafname
, protoOpts
, allowNew
:false);
190 if (tox
is null) return toxCoreEmptyKey
;
191 PubKey self
; tox_self_get_public_key(tox
, self
.ptr
);
192 // and immediately kill it, or it will go online. fuck.
199 PubKey self
; tox_self_get_public_key(tacc
.tox
, self
.ptr
);
203 return toxCoreEmptyKey
;
207 /// checks if we have a working thread for `self`.
208 bool toxCoreIsAccountOpen (in ref PubKey self
) nothrow {
209 synchronized(TPInfo
.classinfo
) {
210 foreach (TPInfo ti
; allProtos
) {
211 if (ti
.self
[] == self
[]) return true;
218 /// returns nick for the given account, or `null`.
219 string
toxCoreGetNick (in ref PubKey self
) nothrow {
220 synchronized(TPInfo
.classinfo
) {
221 foreach (TPInfo ti
; allProtos
) {
222 if (ti
.self
[] == self
[]) return ti
.nick
;
229 /// creates new Tox account (or opens old), stores it in data file, returns public key for new account.
230 /// returns [toxCoreEmptyKey] on error.
231 /// TODO: pass protocol options here
232 /// WARNING! don't call this from more than one thread with the same `toxdatafname`
233 PubKey
toxCoreCreateAccount (const(char)[] toxdatafname
, const(char)[] nick
) nothrow {
234 if (toxdatafname
.length
== 0) return toxCoreEmptyKey
;
236 if (nick
.length
== 0 || nick
.length
> tox_max_name_length()) return toxCoreEmptyKey
;
239 import std
.path
: absolutePath
;
241 string toxDataDiskName
= toxdatafname
.idup
.absolutePath
;
243 synchronized(TPInfo
.classinfo
) {
244 foreach (TPInfo ti
; allProtos
) {
245 if (ti
.toxDataDiskName
== toxDataDiskName
) return ti
.self
[];
249 ProtoOptions protoOpts
;
250 protoOpts
.udp
= true;
252 auto tox
= toxCreateInstance(toxdatafname
, protoOpts
, allowNew
:true);
253 if (tox
is null) return toxCoreEmptyKey
;
255 auto ti
= new TPInfo();
256 ti
.toxDataDiskName
= toxDataDiskName
;
257 tox_self_get_public_key(tox
, ti
.self
.ptr
);
261 tox_self_set_name(tox
, nick
.ptr
, nick
.length
);
262 saveToxCoreData(tox
, toxDataDiskName
);
264 // and immediately kill it, or it will go online. fuck.
267 synchronized(TPInfo
.classinfo
) {
272 } catch (Exception e
) {
275 return toxCoreEmptyKey
;
279 /// starts working thread for account with the given data file.
280 /// returns account public key, or [toxCoreEmptyKey] on error.
281 /// TODO: pass protocol options here
282 /// WARNING! don't call this from more than one thread with the same `toxdatafname`
283 PubKey
toxCoreOpenAccount (const(char)[] toxdatafname
) nothrow {
284 if (toxdatafname
.length
== 0) return toxCoreEmptyKey
;
287 import std
.path
: absolutePath
, dirName
;
289 string toxDataDiskName
= toxdatafname
.idup
.absolutePath
;
291 synchronized(TPInfo
.classinfo
) {
292 foreach (TPInfo ti
; allProtos
) {
293 if (ti
.toxDataDiskName
== toxDataDiskName
) return ti
.self
[];
297 ProtoOptions protoOpts
;
299 protoOpts
.txtunser(VFile(toxDataDiskName
.dirName
~"/proto.rc"));
300 } catch (Exception e
) {
301 protoOpts
= protoOpts
.default;
302 protoOpts
.udp
= true;
306 auto tox
= toxCreateInstance(toxdatafname
, protoOpts
, allowNew
:false);
307 if (tox
is null) return toxCoreEmptyKey
;
308 PubKey self
; tox_self_get_public_key(tox
, self
.ptr
);
310 auto ti
= new TPInfo();
311 ti
.toxDataDiskName
= toxDataDiskName
;
312 tox_self_get_public_key(tox
, ti
.self
.ptr
);
315 auto nsz
= tox_self_get_name_size(tox
);
317 if (nsz
> tox_max_name_length()) nsz
= tox_max_name_length(); // just in case
318 // k8: toxcore developers are idiots, so we have to do dynalloc here
319 auto xbuf
= new char[](tox_max_name_length());
321 tox_self_get_name(tox
, xbuf
.ptr
);
322 ti
.nick
= cast(string
)(xbuf
[0..nsz
]); // ah, cast it directly here
325 // and immediately kill it, or it will go online. fuck.
328 synchronized(TPInfo
.classinfo
) {
333 } catch (Exception e
) {
336 return toxCoreEmptyKey
;
340 /// stops working thread for account with the given data file.
341 /// returns success flag.
342 bool toxCoreCloseAccount (in ref PubKey self
) nothrow {
345 synchronized(TPInfo
.classinfo
) {
346 foreach (TPInfo ti
; allProtos
) if (ti
.self
[] == self
[]) { tpi
= ti
; break; }
348 if (tpi
is null) return false;
350 // send "quit" signal
351 tpi
.tid
.send(TrdCmdQuit
.init
);
355 (TrdCmdQuitAck ack
) { done
= true; },
361 // save toxcode data, if there is any
362 if (tpi
.tox
!is null && tpi
.toxDataDiskName
.length
) saveToxCoreData(tpi
.tox
, tpi
.toxDataDiskName
);
363 if (tpi
.tox
!is null) { tox_kill(tpi
.tox
); tpi
.tox
= null; }
365 // remove from the list
366 synchronized(TPInfo
.classinfo
) {
367 foreach (immutable idx
, TPInfo ti
; allProtos
) {
368 if (ti
.self
[] == self
[]) {
369 foreach (immutable c
; idx
+1..allProtos
.length
) allProtos
[c
-1] = allProtos
[c
];
370 delete allProtos
[$-1];
371 allProtos
.length
-= 1;
372 allProtos
.assumeSafeAppend
;
379 } catch (Exception e
) {
385 enum MsgIdError
= -1;
386 enum MsgIdOffline
= 0;
388 /// sends message. returns message id that can be used to track acks.
389 /// returns [MsgIdError] for unknown account, or unknown `dest`.
390 /// returns [MsgIdOffline] if `self` or `dest` is offline (message is not queued to send in this case).
391 /// note that `0` is a not a valid message id.
392 /// cannot send empty messages, and messages longer than `TOX_MAX_MESSAGE_LENGTH` chars.
393 long toxCoreSendMessage (in ref PubKey self
, in ref PubKey dest
, const(char)[] msg
, bool action
=false) nothrow {
394 long res
= MsgIdError
;
395 if (msg
.length
== 0) return res
; // cannot send empty messages
396 TOX_MESSAGE_TYPE tt
= (action ? TOX_MESSAGE_TYPE_ACTION
: TOX_MESSAGE_TYPE_NORMAL
);
398 if (msg.startsWith("/me ")) {
399 msg = msg[4..$].xstripleft;
400 if (msg.length == 0) return res; // cannot send empty messages
401 tt = TOX_MESSAGE_TYPE_ACTION;
404 if (msg
.length
> tox_max_message_length()) return res
; // message too long
405 doWithLockedTPByKey(self
, delegate (ti
) {
406 if (ti
.tox
is null) { res
= MsgIdOffline
; return; }
407 //if (tox_self_get_connection_status(ti.tox) == TOX_CONNECTION_NONE) { res = MsgIdOffline; return; }
409 uint frnum
= tox_friend_by_public_key(ti
.tox
, dest
.ptr
);
410 if (frnum
== uint.max
) return; // error
412 //if (tox_friend_get_connection_status(ti.tox, frnum) == TOX_CONNECTION_NONE) { res = MsgIdOffline; return; }
413 // nope, online; queue the message
414 TOX_ERR_FRIEND_SEND_MESSAGE err
= 0;
415 uint msgid
= tox_friend_send_message(ti
.tox
, frnum
, tt
, msg
.ptr
, msg
.length
, &err
);
417 case TOX_ERR_FRIEND_SEND_MESSAGE_OK
: res
= (cast(long)msgid
)+1; break;
418 case TOX_ERR_FRIEND_SEND_MESSAGE_NULL
: res
= MsgIdError
; break;
419 case TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_FOUND
: res
= MsgIdError
; break;
420 case TOX_ERR_FRIEND_SEND_MESSAGE_FRIEND_NOT_CONNECTED
: res
= MsgIdOffline
; break;
421 case TOX_ERR_FRIEND_SEND_MESSAGE_SENDQ
: res
= MsgIdError
; break;
422 case TOX_ERR_FRIEND_SEND_MESSAGE_TOO_LONG
: res
= MsgIdError
; break;
423 case TOX_ERR_FRIEND_SEND_MESSAGE_EMPTY
: res
= MsgIdError
; break;
424 default: res
= MsgIdError
; break;
426 if (res
> 0) ti
.ping();
433 /// returns `false` if `self` is invalid, or some error occured.
434 bool toxCoreSetStatus (in ref PubKey self
, ContactStatus status
) nothrow {
435 if (status
== ContactStatus
.Connecting
) return false; // oops
437 doWithLockedTPByKey(self
, delegate (ti
) {
438 // if we're going offline, kill toxcore instance
439 if (status
== ContactStatus
.Offline
) {
440 ti
.tid
.send(TrdCmdKillToxCore
.init
);
441 // send messages, just in case
442 if (toxCoreSendEvent
!is null) {
443 toxCoreSendEvent(new ToxEventStatus(ti
.self
, ti
.self
, ContactStatus
.Offline
));
444 toxCoreSendEvent(new ToxEventConnection(ti
.self
, ti
.self
, false));
453 if (ti
.tox
is null) {
454 // need to create toxcore object
456 import std
.path
: absolutePath
, dirName
;
458 ProtoOptions protoOpts
;
460 protoOpts
.txtunser(VFile(ti
.toxDataDiskName
.dirName
~"/proto.rc"));
461 } catch (Exception e
) {
462 protoOpts
= protoOpts
.default;
463 protoOpts
.udp
= true;
466 conwriteln("creating ToxCore...");
467 tox
= toxCreateInstance(ti
.toxDataDiskName
, protoOpts
, allowNew
:false);
469 conwriteln("can't create ToxCore...");
472 TrdCmdSetToxCore cmd
;
474 ti
.tid
.send(cast(shared)cmd
);
475 } catch (Exception e
) {
481 assert(tox
!is null);
484 final switch (status
) {
485 case ContactStatus
.Offline
: assert(0, "wtf?!");
486 case ContactStatus
.Online
: st
= TOX_USER_STATUS_NONE
; break;
487 case ContactStatus
.Away
: st
= TOX_USER_STATUS_AWAY
; break;
488 case ContactStatus
.Busy
: st
= TOX_USER_STATUS_BUSY
; break;
489 case ContactStatus
.Connecting
: assert(0, "wtf?!");
491 tox_self_set_status(tox
, st
);
500 /// sets new status message.
501 /// returns `false` if `self` is invalid, message is too long, or on any other error.
502 bool toxCoreSetStatusMessage (in ref PubKey self
, const(char)[] message
) nothrow {
503 if (message
.length
> tox_max_status_message_length()) return false;
505 doWithLockedTPByKey(self
, delegate (ti
) {
506 if (ti
.tox
is null) return;
507 res
= tox_self_set_status_message(ti
.tox
, message
.ptr
, message
.length
);
508 if (res
) ti
.needSave
= true;
515 /// sends friend request.
516 /// returns `false` if `self` is invalid, message is too long, or on any other error.
517 bool toxCoreSendFriendRequest (in ref PubKey self
, in ref ubyte[TOX_ADDRESS_SIZE
] dest
, const(char)[] message
) nothrow {
518 if (message
.length
> tox_max_friend_request_length()) return false; // cannot send long requests
520 doWithLockedTPByKey(self
, delegate (ti
) {
521 if (ti
.tox
is null) return; // this account is offline
522 //if (tox_self_get_connection_status(ti.tox) == TOX_CONNECTION_NONE) return; // this account is offline
524 uint frnum
= tox_friend_by_public_key(ti
.tox
, dest
.ptr
);
525 if (frnum
!= uint.max
) {
526 // we already has such friend, do nothing
530 frnum
= tox_friend_add(ti
.tox
, dest
.ptr
, message
.ptr
, message
.length
);
531 res
= (frnum
!= uint.max
);
532 if (res
) ti
.needSave
= true;
539 /// unconditionally adds a friend.
540 /// returns `false` if `self` is invalid, message is too long, friend not found, or on any other error.
541 bool toxCoreAddFriend (in ref PubKey self
, in ref PubKey dest
) nothrow {
543 doWithLockedTPByKey(self
, delegate (ti
) {
544 if (ti
.tox
is null) return; // this account is offline
545 //if (tox_self_get_connection_status(ti.tox) == TOX_CONNECTION_NONE) return; // this account is offline
547 uint frnum
= tox_friend_by_public_key(ti
.tox
, dest
.ptr
);
548 if (frnum
!= uint.max
) {
549 // we already has such friend, do nothing
553 frnum
= tox_friend_add_norequest(ti
.tox
, dest
.ptr
);
554 res
= (frnum
!= uint.max
);
555 if (res
) ti
.needSave
= true;
562 /// unconditionally removes a friend.
563 /// returns `false` if `self` is invalid, message is too long, friend not found, or on any other error.
564 bool toxCoreRemoveFriend (in ref PubKey self
, in ref PubKey dest
) nothrow {
566 doWithLockedTPByKey(self
, delegate (ti
) {
567 if (ti
.tox
is null) return; // this account is offline
568 //if (tox_self_get_connection_status(ti.tox) == TOX_CONNECTION_NONE) return; // this account is offline
570 uint frnum
= tox_friend_by_public_key(ti
.tox
, dest
.ptr
);
571 if (frnum
== uint.max
) { res
= false; return; } // no such friend
572 res
= tox_friend_delete(ti
.tox
, frnum
);
573 if (res
) ti
.needSave
= true;
580 /// checks if the given accound has a friend with the given pubkey.
581 /// returns `false` if `self` is invalid or offline, or on any other error, or if there is no such friend.
582 bool toxCoreHasFriend (in ref PubKey self
, in ref PubKey dest
) nothrow {
584 doWithLockedTPByKey(self
, delegate (ti
) {
585 if (ti
.tox
is null) return; // this account is offline
587 uint frnum
= tox_friend_by_public_key(ti
.tox
, dest
.ptr
);
588 res
= (frnum
!= uint.max
);
594 /// calls delegate for each known friend.
595 /// return `true` from delegate to stop.
596 /// WARNING! all background operations are locked, so don't spend too much time in delegate!
597 void toxCoreForEachFriend (in ref PubKey self
, scope bool delegate (in ref PubKey self
, in ref PubKey frpub
, scope const(char)[] nick
) dg
) nothrow {
598 if (dg
is null) return;
599 doWithLockedTPByKey(self
, delegate (ti
) {
600 if (ti
.tox
is null) return; // this account is offline
601 auto frcount
= tox_self_get_friend_list_size(ti
.tox
);
602 if (frcount
== 0) return;
603 auto list
= new uint[](frcount
);
604 scope(exit
) delete list
;
606 scope(exit
) delete nick
;
607 tox_self_get_friend_list(ti
.tox
, list
.ptr
);
609 foreach (immutable fidx
, immutable fid
; list
[]) {
610 if (!tox_friend_get_public_key(ti
.tox
, fid
, fpk
.ptr
)) continue;
611 auto nsz
= tox_friend_get_name_size(ti
.tox
, fid
);
612 if (nsz
> nick
.length
) nick
.length
= nsz
;
613 if (nsz
!= 0) tox_friend_get_name(ti
.tox
, fid
, nick
.ptr
);
615 dg(self
, fpk
, nick
[0..nsz
]);
616 } catch (Exception e
) {
624 /// calls delegate with ToxP. ugly name is intentional.
625 void toxCoreCallWithToxP (in ref PubKey self
, scope void delegate (ToxP tox
) dg
) nothrow {
626 if (dg
is null) return;
627 doWithLockedTPByKey(self
, delegate (ti
) {
628 if (ti
.tox
is null) return; // this account is offline
631 } catch (Exception e
) {}
636 // ////////////////////////////////////////////////////////////////////////// //
637 // private ToxCore protocol implementation details
641 static struct FileRejectEvent
{
647 string toxDataDiskName
;
652 FileRejectEvent
[] rej
;
653 bool doBootstrap
; // do bootstrapping
656 void ping () nothrow {
657 if (tox
is null) return;
658 try { tid
.send(TrdCmdPing
.init
); } catch (Exception e
) {}
662 __gshared TPInfo
[] allProtos
;
665 // ////////////////////////////////////////////////////////////////////////// //
666 struct TrdCmdQuit
{} // quit thread loop
667 struct TrdCmdQuitAck
{} // quit thread loop
668 struct TrdCmdPing
{} // do something
669 struct TrdCmdKillToxCore
{}
670 struct TrdCmdSetToxCore
{ ToxP tox
; }
673 // ////////////////////////////////////////////////////////////////////////// //
674 // this should be called with registered `ti`
675 void startThread (TPInfo ti
) {
677 ti
.tid
= spawn(&toxCoreThread
, thisTid
, *cast(immutable(void)**)&ti
);
681 // ////////////////////////////////////////////////////////////////////////// //
682 static void toxCoreThread (Tid ownerTid
, immutable(void)* tiptr
) {
684 MonoTime lastSaveTime
= MonoTime
.zero
;
685 TPInfo ti
= *cast(TPInfo
*)&tiptr
; // HACK!
690 bool doKillTox
= false;
692 if (ti
.tox
!is null && ti
.doBootstrap
) {
693 conwriteln("TOX(", ti
.nick
, "): bootstrapping");
694 ti
.doBootstrap
= false;
696 mswait
= tox_iteration_interval(ti
.tox
);
697 if (mswait
< 1) mswait
= 1;
698 conwriteln("TOX(", ti
.nick
, "): bootstrapping complete (mswait=", mswait
, ")");
701 receiveTimeout((mswait ? mswait
.msecs
: 10.hours
),
702 (TrdCmdQuit cmd
) { doQuit
= true; },
704 (TrdCmdKillToxCore cmd
) { doKillTox
= true; },
705 (shared TrdCmdSetToxCore cmd
) { newTox
= cast(ToxP
)cmd
.tox
; },
706 (Variant v
) { conwriteln("WUTAFUCK?! "); },
710 if (newTox
!is null) {
711 conwriteln("TOX(", ti
.nick
, "): got new toxcore pointer");
712 if (ti
.tox
!is newTox
) {
713 if (ti
.tox
!is null) tox_kill(ti
.tox
);
717 ti
.doBootstrap
= true;
722 if (ti
.tox
is null) { mswait
= 0; continue; }
725 conwriteln("TOX(", ti
.nick
, "): killing toxcore pointer");
727 if (ti
.tox
!is null) {
731 conwriteln("TOX(", ti
.nick
, "): wtf?!");
737 tox_iterate(ti
.tox
, null);
738 mswait
= tox_iteration_interval(ti
.tox
);
739 //conwriteln("TOX(", ti.nick, "): interval is ", mswait);
740 if (mswait
< 1) mswait
= 1;
743 auto ctt
= MonoTime
.currTime
;
744 if ((ctt
-lastSaveTime
).total
!"minutes" > 1) {
747 saveToxCoreData(ti
.tox
, ti
.toxDataDiskName
);
752 ownerTid
.send(TrdCmdQuitAck
.init
);
753 } catch (Throwable e
) {
754 // here, we are dead and fucked (the exact order doesn't matter)
755 import core
.stdc
.stdlib
: abort
;
756 import core
.stdc
.stdio
: fprintf
, stderr
;
757 import core
.memory
: GC
;
758 import core
.thread
: thread_suspendAll
;
759 GC
.disable(); // yeah
760 thread_suspendAll(); // stop right here, you criminal scum!
761 auto s
= e
.toString();
762 fprintf(stderr
, "\n=== FATAL ===\n%.*s\n", cast(uint)s
.length
, s
.ptr
);
763 abort(); // die, you bitch!
768 // ////////////////////////////////////////////////////////////////////////// //
769 // find TPInfo object for the given tox handle; used in callbacks
770 TPInfo
findTP (ToxP tox
) nothrow {
771 if (tox
is null) return null;
772 synchronized(TPInfo
.classinfo
) {
773 foreach (TPInfo ti
; allProtos
) {
774 if (ti
.tox
is tox
) return ti
;
781 // returns `true` if found and executed without errors
782 bool doWithLockedTPByKey (in ref PubKey self
, scope void delegate (TPInfo ti
) dg
) nothrow {
784 if (dg
is null) return false;
785 synchronized(TPInfo
.classinfo
) {
786 foreach (TPInfo ti
; allProtos
) if (ti
.self
[] == self
[]) { tpi
= ti
; break; }
793 } catch (Exception e
) {
795 conprintfln("\nTOX CB EXCEPTION: %s\n\n", e
.toString
);
796 } catch (Exception e
) {}
805 // returns `true` if found and executed without errors
806 bool doWithLockedTP (ToxP tox
, scope void delegate (TPInfo ti
) dg
) nothrow {
808 if (tox
is null || dg
is null) return false;
809 synchronized(TPInfo
.classinfo
) {
810 foreach (TPInfo ti
; allProtos
) if (ti
.tox
is tox
) { tpi
= ti
; break; }
817 } catch (Exception e
) {
819 conprintfln("\nTOX CB EXCEPTION: %s\n\n", e
.toString
);
820 } catch (Exception e
) {}
829 // ////////////////////////////////////////////////////////////////////////// //
832 // self connection state was changed
833 static extern(C
) void connectionCB (Tox
* tox
, TOX_CONNECTION status
, void* udata
) nothrow {
834 if (toxCoreSendEvent
is null) return;
836 if (!tox
.doWithLockedTP(delegate (TPInfo ti
) {
837 PubKey self
; tox_self_get_public_key(tox
, self
.ptr
);
838 conwriteln("TOX(", ti
.nick
, "): ", (status
!= TOX_CONNECTION_NONE ?
"" : "dis"), "connected.");
840 tcmsg
= new ToxEventConnection(self
, self
, status
!= TOX_CONNECTION_NONE
);
842 toxCoreSendEvent(tcmsg
);
846 // friend connection state was changed
847 static extern(C
) void friendConnectionCB (Tox
* tox
, uint frnum
, TOX_CONNECTION status
, void* udata
) nothrow {
848 if (toxCoreSendEvent
is null) return;
850 if (!tox
.doWithLockedTP(delegate (TPInfo ti
) {
851 PubKey self
; tox_self_get_public_key(tox
, self
.ptr
);
852 PubKey who
; if (!tox_friend_get_public_key(tox
, frnum
, who
.ptr
)) return; // wtf?!
853 conwriteln("TOX(", ti
.nick
, "): friend #", frnum
, " ", (status
!= TOX_CONNECTION_NONE ?
"" : "dis"), "connected.");
855 tcmsg
= new ToxEventConnection(self
, who
, status
!= TOX_CONNECTION_NONE
);
856 if (status
!= TOX_CONNECTION_NONE
) ti
.needSave
= true;
858 toxCoreSendEvent(tcmsg
);
862 static extern(C
) void friendStatusCB (Tox
* tox
, uint frnum
, TOX_USER_STATUS status
, void* udata
) nothrow {
863 if (toxCoreSendEvent
is null) return;
865 if (!tox
.doWithLockedTP(delegate (TPInfo ti
) {
866 PubKey self
; tox_self_get_public_key(tox
, self
.ptr
);
867 PubKey who
; if (!tox_friend_get_public_key(tox
, frnum
, who
.ptr
)) return; // wtf?!
868 conwriteln("TOX(", ti
.nick
, "): friend #", frnum
, " changed status to ", status
);
870 ContactStatus cst
= ContactStatus
.Offline
;
872 case TOX_USER_STATUS_NONE
: cst
= ContactStatus
.Online
; break;
873 case TOX_USER_STATUS_AWAY
: cst
= ContactStatus
.Away
; break;
874 case TOX_USER_STATUS_BUSY
: cst
= ContactStatus
.Busy
; break;
877 tcmsg
= new ToxEventStatus(self
, who
, cst
);
879 toxCoreSendEvent(tcmsg
);
883 static extern(C
) void friendNameCB (Tox
* tox
, uint frnum
, const(char)* name
, usize length
, void* udata
) nothrow {
884 if (toxCoreSendEvent
is null) return;
886 if (!tox
.doWithLockedTP(delegate (TPInfo ti
) {
887 PubKey self
; tox_self_get_public_key(tox
, self
.ptr
);
888 PubKey who
; if (!tox_friend_get_public_key(tox
, frnum
, who
.ptr
)) return; // wtf?!
889 conwriteln("TOX(", ti
.nick
, "): friend #", frnum
, " changed name to <", name
[0..length
], ">");
891 tcmsg
= new ToxEventNick(self
, who
, name
[0..length
].idup
);
894 toxCoreSendEvent(tcmsg
);
898 static extern(C
) void friendStatusMessageCB (Tox
* tox
, uint frnum
, const(char)* msg
, usize msglen
, void* user_data
) nothrow {
899 if (toxCoreSendEvent
is null) return;
901 if (!tox
.doWithLockedTP(delegate (TPInfo ti
) {
902 PubKey self
; tox_self_get_public_key(tox
, self
.ptr
);
903 PubKey who
; if (!tox_friend_get_public_key(tox
, frnum
, who
.ptr
)) return; // wtf?!
904 conwriteln("TOX(", ti
.nick
, "): friend #", frnum
, " changed status to <", msg
[0..msglen
], ">");
906 tcmsg
= new ToxEventStatusMsg(self
, who
, msg
[0..msglen
].idup
);
909 toxCoreSendEvent(tcmsg
);
913 static extern(C
) void friendReqCB (Tox
* tox
, const(ubyte)* pk
, const(char)* msg
, usize msglen
, void* udata
) nothrow {
914 if (toxCoreSendEvent
is null) return;
916 if (!tox
.doWithLockedTP(delegate (TPInfo ti
) {
917 PubKey self
; tox_self_get_public_key(tox
, self
.ptr
);
918 PubKey who
; who
[] = pk
[0..PubKey
.length
];
919 conwriteln("TOX(", ti
.nick
, "): friend request comes: <", msg
[0..msglen
], ">");
921 tcmsg
= new ToxEventFriendReq(self
, who
, msg
[0..msglen
].idup
);
924 toxCoreSendEvent(tcmsg
);
928 static extern(C
) void friendMsgCB (Tox
* tox
, uint frnum
, TOX_MESSAGE_TYPE type
, const(char)* msg
, usize msglen
, void* udata
) nothrow {
929 if (toxCoreSendEvent
is null) return;
931 if (!tox
.doWithLockedTP(delegate (TPInfo ti
) {
932 PubKey self
; tox_self_get_public_key(tox
, self
.ptr
);
933 PubKey who
; if (!tox_friend_get_public_key(tox
, frnum
, who
.ptr
)) return; // wtf?!
934 conwriteln("TOX(", ti
.nick
, "): friend #", frnum
, " sent a message.");
936 tcmsg
= new ToxEventMessage(self
, who
, (type
== TOX_MESSAGE_TYPE_ACTION
), msg
[0..msglen
].idup
);
938 toxCoreSendEvent(tcmsg
);
942 static extern(C
) void friendReceiptCB (Tox
* tox
, uint frnum
, uint msgid
, void* udata
) nothrow {
943 if (toxCoreSendEvent
is null) return;
945 if (!tox
.doWithLockedTP(delegate (TPInfo ti
) {
946 PubKey self
; tox_self_get_public_key(tox
, self
.ptr
);
947 PubKey who
; if (!tox_friend_get_public_key(tox
, frnum
, who
.ptr
)) return; // wtf?!
948 conwriteln("TOX(", ti
.nick
, "): friend #", frnum
, " acked message #", msgid
);
950 tcmsg
= new ToxEventMessageAck(self
, who
, (cast(long)msgid
)+1);
952 toxCoreSendEvent(tcmsg
);
956 static extern(C
) void fileRecvCB (Tox
* tox
, uint frnum
, uint flnum
, TOX_FILE_KIND kind
, ulong flsize
, const(char)* name
, usize namelen
, void* udata
) nothrow {
957 if (toxCoreSendEvent
is null) return;
958 if (!tox
.doWithLockedTP(delegate (TPInfo ti
) {
959 //PubKey self; tox_self_get_public_key(tox, self.ptr);
960 //PubKey who; if (!tox_friend_get_public_key(tox, frnum, who.ptr)) return false; // wtf?!
963 foreach (ref TPInfo
.FileRejectEvent fre
; ti
.rej
) if (fre
.frnum
== frnum
&& fre
.flnum
== flnum
) { found
= true; break; }
964 if (!found
) ti
.rej
~= TPInfo
.FileRejectEvent(frnum
, flnum
);
968 if (kind == TOX_FILE_KIND_AVATAR) {
969 if (flsize < 16 || flsize > 1024*1024) {
970 timp.addRejectEvent(frnum, flnum);
974 timp.addRejectEvent(frnum, flnum);
976 tox_file_control(tox, rej.frnum, rej.flnum, TOX_FILE_CONTROL_CANCEL, null);
981 static extern(C
) void fileRecvCtlCB (Tox
* tox
, uint frnum
, uint flnum
, TOX_FILE_CONTROL ctl
, void* udata
) nothrow {
983 auto timp = findTP(tox);
984 if (timp is null) return;
986 if (!tox_friend_get_public_key(tox, frnum, who.ptr)) return false; // wtf?!
988 conprintfln("got recv ctl for friend #%s, file #%s, ctl:%s", frnum, flnum, ctl);
991 timp.friendMessageAck(who, msgid);
994 } catch (Exception e) {
995 conprintfln("\nTOX[%s] CB EXCEPTION: %s\n\n", timp.srvalias, e.toString);
998 auto timp = *cast(Account*)&udata;
999 if (timp is null || timp.tox is null) return;
1001 final switch (ctl) {
1002 case TOX_FILE_CONTROL_RESUME: /*timp.resumeSendByNums(frnum, flnum);*/ break;
1003 case TOX_FILE_CONTROL_PAUSE: /*timp.pauseSendByNums(frnum, flnum);*/ break;
1004 case TOX_FILE_CONTROL_CANCEL: /*timp.abortSendByNums(frnum, flnum);*/ break;
1006 } catch (Exception e) {
1008 conprintfln("\nTOX[%s] CB EXCEPTION: %s\n\n", timp.srvalias, e.toString);
1010 } catch (Exception) {}
1016 static extern(C
) void fileChunkReqCB (Tox
* tox
, uint frnum
, uint flnum
, ulong pos
, usize len
, void* udata
) nothrow {
1017 if (toxCoreSendEvent
is null) return;
1018 if (!tox
.doWithLockedTP(delegate (TPInfo ti
) {
1019 PubKey self
; tox_self_get_public_key(tox
, self
.ptr
);
1020 PubKey who
; if (!tox_friend_get_public_key(tox
, frnum
, who
.ptr
)) return; // wtf?!
1023 foreach (ref TPInfo
.FileRejectEvent fre
; ti
.rej
) if (fre
.frnum
== frnum
&& fre
.flnum
== flnum
) { found
= true; break; }
1024 if (!found
) ti
.rej
~= TPInfo
.FileRejectEvent(frnum
, flnum
);
1027 //logwritefln("got chunk req recv ctl for friend #%s, file #%s, ctl:%s", frnum, flnum, ctl);
1030 timp.fschunks ~= ChunkToSend(frnum, flnum, pos, len);
1031 } catch (Exception e) {
1033 conprintfln("\nTOX[%s] CB EXCEPTION: %s\n\n", timp.srvalias, e.toString);
1034 timp.dropNetIOConnection();
1035 } catch (Exception) {}
1041 // ////////////////////////////////////////////////////////////////////////// //
1042 void bootstrap(string mode
="any") (TPInfo ti
) nothrow if (mode
== "udp" || mode
== "tcp" || mode
== "any") {
1043 if (ti
is null || ti
.tox
is null) return;
1045 ToxBootstrapServer
[] loadBootNodes () nothrow {
1047 auto bfname
= buildPath(ti
.toxDataDiskName
.dirName
, "tox_bootstrap.rc");
1048 ToxBootstrapServer
[] bootnodes
= null;
1050 bootnodes
.txtunser(VFile(bfname
));
1051 if (bootnodes
.length
> 0) return bootnodes
;
1052 } catch (Exception e
) {}
1055 bootnodes
= tox_download_bootstrap_list();
1056 if (bootnodes
.length
> 0) {
1058 bootnodes
.txtser(VFile(bfname
, "w"));
1059 } catch (Exception e
) {}
1062 } catch (Exception e
) {}
1066 conprintfln("Tox: loading bootstrap nodes...");
1067 auto nodes
= loadBootNodes();
1068 conprintfln("Tox: %s nodes loaded", nodes
.length
);
1069 if (nodes
.length
== 0) return;
1070 foreach (const ref ToxBootstrapServer srv
; nodes
) {
1071 if (srv
.ipv4
.length
< 2) continue;
1072 assert(srv
.ipv4
[$-1] == 0);
1073 //conprintfln(" node ip: %s:%u (maintainer: %s)", srv.ipv4[0..$-1], srv.port, srv.maintainer);
1074 static if (mode
== "udp") {
1076 tox_bootstrap(ti
.tox
, srv
.ipv4
.ptr
, srv
.port
, srv
.pubkey
.ptr
, null);
1078 } else static if (mode
== "tcp") {
1080 foreach (immutable ushort port
; srv
.tcpports
) {
1081 tox_add_tcp_relay(ti
.tox
, srv
.ipv4
.ptr
, port
, srv
.pubkey
.ptr
, null);
1086 tox_bootstrap(ti
.tox
, srv
.ipv4
.ptr
, srv
.port
, srv
.pubkey
.ptr
, null);
1087 } else if (srv
.udp
) {
1088 foreach (immutable ushort port
; srv
.tcpports
) {
1089 tox_add_tcp_relay(ti
.tox
, srv
.ipv4
.ptr
, port
, srv
.pubkey
.ptr
, null);
1093 tox_iterate(ti
.tox
, null);
1095 //conprintfln("Tox[%s]: %s nodes added", srvalias, nodes.length);
1099 // ////////////////////////////////////////////////////////////////////////// //
1100 // returns `null` if there is no such file or file cannot be loaded
1101 ubyte[] loadToxCoreData (const(char)[] toxdatafname
) nothrow {
1102 import core
.stdc
.stdio
: FILE
, fopen
, fclose
, rename
, fread
, ferror
, fseek
, ftell
, SEEK_SET
, SEEK_END
;
1103 import core
.stdc
.stdlib
: malloc
, free
;
1104 import core
.sys
.posix
.unistd
: unlink
;
1106 if (toxdatafname
.length
== 0) return null;
1108 static char* namebuf
= null;
1109 static uint nbsize
= 0;
1111 if (nbsize
< toxdatafname
.length
+1024) {
1112 import core
.stdc
.stdlib
: realloc
;
1113 nbsize
= cast(uint)toxdatafname
.length
+1024;
1114 namebuf
= cast(char*)realloc(namebuf
, nbsize
);
1115 if (namebuf
is null) assert(0, "out of memory");
1118 auto origName
= expandTilde(namebuf
[0..nbsize
-6], toxdatafname
);
1119 if (origName
== null) return null; // oops
1120 origName
.ptr
[origName
.length
] = 0; // zero-terminate
1122 FILE
* fi
= fopen(namebuf
, "r");
1123 if (fi
is null) return null;
1124 scope(exit
) fclose(fi
);
1127 if (fseek(fi
, 0, SEEK_END
) == -1) {
1128 import core
.stdc
.errno
;
1129 if (errno
== EINTR
) continue;
1139 import core
.stdc
.errno
;
1140 if (errno
== EINTR
) continue;
1146 if (fsize
> 1024*1024*256) { conwriteln("toxcore data file too big"); return null; }
1147 if (fsize
== 0) return null; // it cannot be zero-sized
1150 if (fseek(fi
, 0, SEEK_SET
) == -1) {
1151 import core
.stdc
.errno
;
1152 if (errno
== EINTR
) continue;
1158 auto res
= new ubyte[](cast(int)fsize
);
1159 conwriteln("loading toxcore data; size=", fsize
);
1162 while (left
.length
> 0) {
1163 auto rd
= fread(left
.ptr
, 1, left
.length
, fi
);
1164 if (rd
== 0) { delete res
; return null; } // error
1165 if (rd
< cast(int)left
.length
) {
1166 if (!ferror(fi
)) { delete res
; return null; } // error
1167 import core
.stdc
.errno
;
1168 if (errno
!= EINTR
) { delete res
; return null; } // error
1169 if (rd
> 0) left
= left
[rd
..$];
1172 if (rd
> left
.length
) { delete res
; return null; } // error
1176 if (left
.length
) { delete res
; return null; } // error
1182 bool saveToxCoreData (ToxP tox
, const(char)[] toxdatafname
) nothrow {
1183 import core
.stdc
.stdio
: FILE
, fopen
, fclose
, rename
, fwrite
;
1184 import core
.stdc
.stdlib
: malloc
, free
;
1185 import core
.sys
.posix
.unistd
: unlink
;
1186 //import std.internal.cstring;
1188 if (tox
is null || toxdatafname
.length
== 0) return false;
1190 auto size
= tox_get_savedata_size(tox
);
1191 if (size
> int.max
/8) return false; //throw new Exception("save data too big");
1193 char* savedata
= cast(char*)malloc(size
);
1194 if (savedata
is null) return false;
1195 scope(exit
) if (savedata
!is null) free(savedata
);
1197 tox_get_savedata(tox
, savedata
);
1198 conwriteln("save toxcore data; size=", size
);
1200 static char* namebuf
= null;
1201 static char* namebuf1
= null;
1202 static uint nbsize
= 0;
1204 if (nbsize
< toxdatafname
.length
+1024) {
1205 import core
.stdc
.stdlib
: realloc
;
1206 nbsize
= cast(uint)toxdatafname
.length
+1024;
1207 namebuf
= cast(char*)realloc(namebuf
, nbsize
);
1208 if (namebuf
is null) assert(0, "out of memory");
1209 namebuf1
= cast(char*)realloc(namebuf1
, nbsize
);
1210 if (namebuf1
is null) assert(0, "out of memory");
1213 auto origName
= expandTilde(namebuf
[0..nbsize
-6], toxdatafname
);
1214 if (origName
== null) return false; // oops
1215 origName
.ptr
[origName
.length
] = 0; // zero-terminate
1217 // create temporary name
1218 namebuf1
[0..origName
.length
] = origName
[];
1219 namebuf1
[origName
.length
..origName
.length
+5] = ".$$$\x00";
1221 FILE
* fo
= fopen(namebuf1
, "w");
1222 if (fo
is null) return false;
1224 auto left
= savedata
[0..size
];
1225 while (left
.length
> 0) {
1226 auto wr
= fwrite(left
.ptr
, 1, left
.length
, fo
);
1227 if (wr
< cast(int)left
.length
) {
1228 import core
.stdc
.errno
;
1229 if (errno
== EINTR
) {
1230 if (wr
> 0) left
= left
[wr
..$];
1235 if (wr
> left
.length
) break; // wtf?!
1248 if (rename(namebuf1
, namebuf
) != 0) {
1257 version(ToxCoreDebug
) {
1258 static extern(C
) void toxCoreLogCB (Tox
* tox
, TOX_LOG_LEVEL level
, const(char)* file
, uint line
, const(char)* func
, const(char)* message
, void* user_data
) nothrow {
1259 static inout(char)[] sz (inout(char)* s
) nothrow @trusted @nogc {
1260 if (s
is null) return null;
1262 while (s
[idx
]) ++idx
;
1263 return cast(inout(char)[])(s
[0..idx
]);
1266 conwriteln("level=", level
, "; file=", sz(file
), ":", line
, "; func=", sz(func
), "; msg=", sz(message
));
1271 // ////////////////////////////////////////////////////////////////////////// //
1272 // returns `false` if can't open/create
1273 ToxP
toxCreateInstance (const(char)[] toxdatafname
, in ref ProtoOptions protoOpts
, bool allowNew
, bool* wasCreated
=null) nothrow {
1274 if (wasCreated
!is null) *wasCreated
= false;
1276 if (toxdatafname
.length
== 0) return null;
1279 char* toxProxyHost
= null;
1281 import core
.stdc
.stdlib
: free
;
1282 if (toxProxyHost
!is null) free(toxProxyHost
);
1285 scope(exit
) delete savedata
;
1287 auto toxOpts
= tox_options_new();
1288 assert(toxOpts
!is null);
1289 scope(exit
) if (toxOpts
!is null) tox_options_free(toxOpts
);
1290 tox_options_default(toxOpts
);
1292 bool createNewToxCoreAccount
= false;
1293 toxOpts
.tox_options_set_ipv6_enabled(protoOpts
.ipv6
);
1294 toxOpts
.tox_options_set_udp_enabled(protoOpts
.udp
);
1295 toxOpts
.tox_options_set_local_discovery_enabled(protoOpts
.localDiscovery
);
1296 toxOpts
.tox_options_set_hole_punching_enabled(protoOpts
.holePunching
);
1297 toxOpts
.tox_options_set_start_port(protoOpts
.startPort
);
1298 toxOpts
.tox_options_set_end_port(protoOpts
.endPort
);
1299 toxOpts
.tox_options_set_tcp_port(protoOpts
.tcpPort
);
1301 toxOpts
.tox_options_set_proxy_type(protoOpts
.proxyType
);
1302 if (protoOpts
.proxyType
!= TOX_PROXY_TYPE_NONE
) {
1303 import core
.stdc
.stdlib
: malloc
;
1304 toxOpts
.tox_options_set_proxy_port(protoOpts
.proxyPort
);
1305 // create proxy address string
1306 toxProxyHost
= cast(char*)malloc(protoOpts
.proxyAddr
.length
+1);
1307 if (toxProxyHost
is null) assert(0, "out of memory");
1308 toxProxyHost
[0..protoOpts
.proxyAddr
.length
] = protoOpts
.proxyAddr
[];
1309 toxProxyHost
[protoOpts
.proxyAddr
.length
] = 0;
1310 toxOpts
.tox_options_set_proxy_host(toxProxyHost
);
1313 savedata
= loadToxCoreData(toxdatafname
);
1314 if (savedata
is null) {
1315 // create new tox instance
1316 if (wasCreated
!is null) *wasCreated
= true;
1317 if (!allowNew
) return null;
1318 toxOpts
.tox_options_set_savedata_type(TOX_SAVEDATA_TYPE_NONE
);
1319 conwriteln("creating new ToxCore account...");
1322 conwriteln("setting ToxCore account data (", savedata
.length
, " bytes)");
1323 toxOpts
.tox_options_set_savedata_type(TOX_SAVEDATA_TYPE_TOX_SAVE
);
1324 toxOpts
.tox_options_set_savedata_length(savedata
.length
);
1325 toxOpts
.tox_options_set_savedata_data(savedata
.ptr
, savedata
.length
);
1327 scope(exit
) delete savedata
;
1329 version(ToxCoreDebug
) {
1330 toxOpts
.tox_options_set_log_callback(&toxCoreLogCB
);
1333 // create tox instance
1335 ToxP tox
= tox_new(toxOpts
, &error
);
1337 conwriteln("cannot create ToxCore instance: error is ", error
);
1342 if (createNewToxCoreAccount) {
1344 if (info.nick.length > 0) {
1345 auto nc = info.nick;
1346 if (nc.length > TOX_MAX_NAME_LENGTH) nc = nc[0..TOX_MAX_NAME_LENGTH];
1347 tox_self_set_name(tox, nc.ptr, nc.length);
1352 auto nsz = tox_self_get_name_size(tox);
1354 if (nsz > TOX_MAX_NAME_LENGTH) nsz = TOX_MAX_NAME_LENGTH;
1355 char[TOX_MAX_NAME_LENGTH] xbuf = 0;
1356 tox_self_get_name(tox, xbuf.ptr);
1358 foreach (immutable idx, char ch; xbuf[]) {
1359 if (ch == 0) { info.nick = xbuf[0..idx].idup; break; }
1362 info.nick = xbuf[0..nsz].idup;
1367 tox_callback_self_connection_status(tox
, &connectionCB
);
1369 tox_callback_friend_name(tox
, &friendNameCB
);
1370 tox_callback_friend_status_message(tox
, &friendStatusMessageCB
);
1371 tox_callback_friend_status(tox
, &friendStatusCB
);
1372 tox_callback_friend_connection_status(tox
, &friendConnectionCB
);
1373 //tox_callback_friend_typing(tox, &friendTypingCB);
1374 tox_callback_friend_read_receipt(tox
, &friendReceiptCB
);
1375 tox_callback_friend_request(tox
, &friendReqCB
);
1376 tox_callback_friend_message(tox
, &friendMsgCB
);
1378 tox_callback_file_recv_control(tox
, &fileRecvCtlCB
);
1379 tox_callback_file_chunk_request(tox
, &fileChunkReqCB
);
1380 tox_callback_file_recv(tox
, &fileRecvCB
);
1381 //tox_callback_file_recv_chunk
1383 //tox_callback_conference_invite
1384 //tox_callback_conference_message
1385 //tox_callback_conference_title
1386 //tox_callback_conference_namelist_change
1388 //tox_callback_friend_lossy_packet
1389 //tox_callback_friend_lossless_packet