diff options
Diffstat (limited to 'protocols/Sametime/src/sametime_session.cpp')
-rw-r--r-- | protocols/Sametime/src/sametime_session.cpp | 634 |
1 files changed, 634 insertions, 0 deletions
diff --git a/protocols/Sametime/src/sametime_session.cpp b/protocols/Sametime/src/sametime_session.cpp new file mode 100644 index 0000000000..bc03f86a03 --- /dev/null +++ b/protocols/Sametime/src/sametime_session.cpp @@ -0,0 +1,634 @@ +#include "StdAfx.h"
+#include "sametime.h"
+
+/// not in CSametimeProto (used at NETLIBOPENCONNECTION_tag.waitcallback)
+bool continue_connect;
+
+#define MS_SAMETIME_MENUANNOUNCESESSION "/SessionAnnounce"
+
+// utf8 encoded
+struct {
+ char* szOnline;
+ char* szAway;
+ char* szDND;
+} AwayMessages;
+
+
+void __cdecl SessionClear(mwSession* session)
+{
+ CSametimeProto* proto = (CSametimeProto*)mwSession_getProperty(session, "PROTO_STRUCT_PTR");
+ proto->debugLog(_T("SessionClear()"));
+}
+
+int __cdecl SessionWrite(mwSession* session, const unsigned char* buf, gsize len)
+{
+ CSametimeProto* proto = (CSametimeProto*)mwSession_getProperty(session, "PROTO_STRUCT_PTR");
+ proto->debugLog(_T("SessionWrite() server_connection=[%d], len=[%d]"), proto->server_connection, len);
+ if (!proto->server_connection) return 1;
+ if (Netlib_Send(proto->server_connection, (const char*)buf, len, 0) == SOCKET_ERROR)
+ return 1;
+ return 0;
+}
+
+void __cdecl SessionClose(mwSession* session)
+{
+ CSametimeProto* proto = (CSametimeProto*)mwSession_getProperty(session, "PROTO_STRUCT_PTR");
+ proto->debugLog(_T("SessionClose() server_connection=[%d]"), proto->server_connection);
+ Netlib_CloseHandle(proto->server_connection);
+ proto->server_connection = 0;
+}
+
+void CSametimeProto::SessionStarted()
+{
+ UserListCreate();
+ if (options.get_server_contacts) UserListAddStored();
+}
+
+void CSametimeProto::SessionStopping()
+{
+ UserListDestroy();
+}
+
+void CSametimeProto::InitMeanwhileServices()
+{
+ debugLog(_T("InitMeanwhileServices() start"));
+
+ if (options.encrypt_session) {
+ mwSession_addCipher(session, mwCipher_new_RC2_128(session));
+ mwSession_addCipher(session, mwCipher_new_RC2_40(session));
+ }
+
+ InitUserList();
+ InitMessaging();
+ InitFiles();
+ InitConference();
+
+ mwSession_setProperty(session, "PROTO_STRUCT_PTR", this, NULL);
+
+ ///TODO InitMeanwhileServices DeinitMeanwhileServices on LogIn LogOut, do not need restart
+ mwSession_setProperty(session, mwSession_AUTH_USER_ID, options.id, NULL);
+ mwSession_setProperty(session, mwSession_AUTH_PASSWORD, options.pword, NULL);
+ mwSession_setProperty(session, mwSession_CLIENT_TYPE_ID, (void*)options.client_id, NULL);
+
+ if (options.use_old_default_client_ver) {
+ mwSession_setProperty(session, mwSession_CLIENT_VER_MAJOR, GUINT_TO_POINTER(db_get_w(0, m_szModuleName, "ClientVersionMajor", MW_PROTOCOL_VERSION_MAJOR)), 0);
+ mwSession_setProperty(session, mwSession_CLIENT_VER_MINOR, GUINT_TO_POINTER(db_get_w(0, m_szModuleName, "ClientVersionMinor", MW_PROTOCOL_VERSION_MINOR)), 0);
+ } else {
+ mwSession_setProperty(session, mwSession_CLIENT_VER_MAJOR, GUINT_TO_POINTER(db_get_w(0, m_szModuleName, "ClientVersionMajor", 0x001e)), 0);
+ mwSession_setProperty(session, mwSession_CLIENT_VER_MINOR, GUINT_TO_POINTER(db_get_w(0, m_szModuleName, "ClientVersionMinor", 0x196f)), 0);
+ }
+
+}
+
+void CSametimeProto::DeinitMeanwhileServices()
+{
+ debugLog(_T("DeinitMeanwhileServices() start"));
+ DeinitConference();
+ DeinitFiles();
+ DeinitMessaging();
+ DeinitUserList();
+ mwCipher_free(mwSession_getCipher(session, mwCipher_RC2_40));
+ mwCipher_free(mwSession_getCipher(session, mwCipher_RC2_128));
+}
+
+void __cdecl SessionStateChange(mwSession* session, mwSessionState state, gpointer info)
+{
+ CSametimeProto* proto = (CSametimeProto*)mwSession_getProperty(session, "PROTO_STRUCT_PTR");
+ proto->debugLog(_T("SessionStateChange() state=[%d]"), state);
+
+ switch(state) {
+
+ case mwSession_STARTING:
+ break;
+
+ case mwSession_HANDSHAKE:
+ break;
+
+ case mwSession_HANDSHAKE_ACK:
+ break;
+
+ case mwSession_STARTED:
+ proto->SessionStarted();
+ break;
+
+ case mwSession_STOPPING:
+ if ((int)info) {// & ERR_FAILURE) {
+ char *msg = mwError((int)info);
+ TCHAR *msgT = mir_utf8decodeT(msg);
+ proto->showPopup(TranslateTS(msgT), SAMETIME_POPUP_ERROR);
+ mir_free(msgT);
+ g_free(msg);
+ }
+ proto->SessionStopping();
+ break;
+
+ case mwSession_STOPPED:
+ break;
+
+ case mwSession_LOGIN_REDIR:
+ proto->debugLog(_T("SessionStateChange() mwSession_LOGIN_REDIR info=[%s]"), _A2T((char*)info));
+ //options.server_name = str((char*)info);
+ strcpy(proto->options.server_name, (char*)info);
+ proto->LogOut();
+ proto->LogIn(proto->login_status, proto->m_hNetlibUser);
+ break;
+
+ case mwSession_LOGIN_CONT:
+ break;
+
+ case mwSession_LOGIN:
+ break;
+
+ case mwSession_LOGIN_ACK:
+ break;
+
+ case mwSession_UNKNOWN:
+ break;
+
+ }
+}
+
+void __cdecl SessionAdmin(struct mwSession* session, const char* text)
+{
+ CSametimeProto* proto = (CSametimeProto*)mwSession_getProperty(session, "PROTO_STRUCT_PTR");
+ proto->debugLog(_T("SessionAdmin()"));
+ TCHAR* tt = mir_utf8decodeT(text);
+ MessageBox(0, tt, TranslateT("Sametime Administrator Message"), MB_OK);
+ mir_free(tt);
+}
+
+void __cdecl SessionAnnounce(struct mwSession* session, struct mwLoginInfo* from, gboolean may_reply, const char* text)
+{
+ CSametimeProto* proto = (CSametimeProto*)mwSession_getProperty(session, "PROTO_STRUCT_PTR");
+ proto->debugLog(_T("SessionAnnounce()"));
+ TCHAR* stzFrom;
+ TCHAR* stzText;
+ TCHAR stzFromBuff[256];
+ stzFrom = mir_utf8decodeT(from->user_name);
+ stzText = mir_utf8decodeT(text);
+ mir_sntprintf(stzFromBuff, 256, TranslateT("Session Announcement - from '%s'"), stzFrom);
+ MessageBox(0, TranslateTS(stzText), stzFromBuff, MB_OK);
+ mir_free(stzText);
+ mir_free(stzFrom);
+}
+
+void __cdecl SessionSetPrivacyInfo(struct mwSession* session)
+{
+ CSametimeProto* proto = (CSametimeProto*)mwSession_getProperty(session, "PROTO_STRUCT_PTR");
+ proto->debugLog(_T("SessionSetPrivacyInfo()"));
+}
+
+void __cdecl SessionSetUserStatus(struct mwSession* session)
+{
+ CSametimeProto* proto = (CSametimeProto*)mwSession_getProperty(session, "PROTO_STRUCT_PTR");
+
+ int new_status;
+ struct mwUserStatus us;
+ mwUserStatus_clone(&us, mwSession_getUserStatus(session));
+
+ proto->debugLog(_T("SessionSetUserStatus() us.status=[%d]"), us.status);
+
+ switch(us.status) {
+
+ case mwStatus_ACTIVE:
+ new_status = ID_STATUS_ONLINE;
+ break;
+
+ case mwStatus_AWAY:
+ new_status = ID_STATUS_AWAY;
+ if (proto->idle_status) {
+ // ignore setting to away by idle module, after we've set ourselves idle
+ // most standard clients represent idle and away the same way anyway,
+ // but this allows miranda users to make use of the idle timestamp
+ // but show our status in clist as away
+ proto->BroadcastNewStatus(new_status);
+ mwUserStatus_clear(&us);
+ return;
+ }
+ break;
+
+ case mwStatus_BUSY:
+ new_status = ID_STATUS_DND;
+ break;
+
+ case mwStatus_IDLE:
+ new_status = ID_STATUS_AWAY;
+ if (!proto->first_online && !proto->options.idle_as_away) { // show our status in clist as away if idle when going online or treating idle as away
+ mwUserStatus_clear(&us);
+ return;
+ }
+ break;
+
+ case 8: // new 'in a meeting' status, not handled by meanwhile lib
+ new_status = ID_STATUS_OCCUPIED;
+ break;
+
+ default:
+ {
+ TCHAR buff[512];
+ mir_sntprintf(buff, 512, TranslateT("Unknown user status: %d"), us.status);
+ proto->showPopup(buff, SAMETIME_POPUP_ERROR);
+ proto->debugLog(buff);
+ }
+ mwUserStatus_clear(&us);
+ // just go online...to prevent us getting stuck 'connecting'
+ new_status = ID_STATUS_ONLINE;
+ break;
+
+ }
+
+ proto->m_iDesiredStatus = new_status;
+
+ if (proto->first_online) {
+ proto->first_online = false;
+ //proto->showPopup(TranslateT("Setting login status"), SAMETIME_POPUP_INFO);
+ proto->debugLog(_T("Setting login status"));
+ proto->SetSessionStatus(proto->login_status);
+ } else {
+ proto->BroadcastNewStatus(new_status);
+ }
+
+ mwUserStatus_clear(&us);
+}
+
+void CSametimeProto::UpdateSelfStatus()
+{
+ EnterCriticalSection(&session_cs);
+ if (session) SessionSetUserStatus(session);
+ LeaveCriticalSection(&session_cs);
+}
+
+int CSametimeProto::SetSessionStatus(int status)
+{
+ struct mwUserStatus us;
+ debugLog(_T("SetSessionStatus() start status=[%d]"), status);
+
+ if (idle_timerid) KillTimer(0, idle_timerid);
+
+ us.time = (DWORD)time(0);
+ //us.time = 0;
+
+ switch(status) {
+ case ID_STATUS_FREECHAT:
+ case ID_STATUS_ONLINE:
+ us.desc = AwayMessages.szOnline; us.status = mwStatus_ACTIVE;
+ break;
+ case ID_STATUS_NA:
+ case ID_STATUS_INVISIBLE:
+ case ID_STATUS_ONTHEPHONE:
+ case ID_STATUS_OUTTOLUNCH:
+ case ID_STATUS_AWAY:
+ us.desc = AwayMessages.szAway; us.status = mwStatus_AWAY;
+ break;
+ case ID_STATUS_OCCUPIED:
+ case ID_STATUS_DND:
+ us.desc = AwayMessages.szDND; us.status = mwStatus_BUSY;
+ break;
+ default:
+ // act as online for unsupported status
+ us.desc = AwayMessages.szOnline; us.status = mwStatus_ACTIVE; break;
+ }
+
+ debugLog(_T("SetSessionStatus() mwSession_setUserStatus us.status=[%d], us.desc:len=[%d]"), us.status, us.desc == NULL ? -1 : strlen(us.desc));
+ mwSession_setUserStatus(session, &us);
+
+ return 0;
+}
+
+VOID CALLBACK IdleTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
+{
+ CSametimeProto* proto = (CSametimeProto*)idEvent;
+
+ KillTimer(0, proto->idle_timerid);
+ proto->idle_timerid = 0;
+
+ if (proto->idle_status) {
+ struct mwUserStatus us;
+ us.time = (DWORD)time(0);
+ us.status = mwStatus_IDLE;
+ us.desc = 0;
+ mwSession_setUserStatus(proto->session, &us);
+ } else {
+ proto->SetSessionStatus(proto->m_iStatus);
+ }
+}
+
+int CSametimeProto::SetIdle(bool idle)
+{
+ // set a timer, to wait for any autoaway module which might set our status
+ debugLog(_T("CSametimeProto::SetIdle() idle=[%d], idle_status=[%d], idle_timerid=[%d]"), idle, idle_status, idle_timerid);
+ if (idle && !idle_status) {
+ idle_status = true;
+ if (!idle_timerid)
+ idle_timerid = SetTimer(0, (UINT_PTR)this, 200, IdleTimerProc);
+ } else if (idle_status) {
+ idle_status = false;
+ if (!idle_timerid)
+ idle_timerid = SetTimer(0, (UINT_PTR)this, 200, IdleTimerProc);
+ }
+ return 0;
+}
+
+void CSametimeProto::SetSessionAwayMessage(int status, const PROTOCHAR* msgT)
+{
+ debugLog(_T("SetSessionAwayMessage() status=[%d], msgT:len=[%d]"), status, msgT == NULL ? -1 : _tcslen(msgT));
+
+ ptrA msg(mir_utf8encodeT(msgT));
+ if (status == ID_STATUS_ONLINE) {
+ mir_free(AwayMessages.szOnline);
+ if (msg) {
+ AwayMessages.szOnline = mir_strdup(msg);
+ } else AwayMessages.szOnline = 0;
+ } else if (status == ID_STATUS_AWAY) {
+ mir_free(AwayMessages.szAway);
+ if (msg) {
+ AwayMessages.szAway = mir_strdup(msg);
+ } else AwayMessages.szAway = 0;
+ } else if (status == ID_STATUS_DND) {
+ mir_free(AwayMessages.szDND);
+ if (msg) {
+ AwayMessages.szDND = mir_strdup(msg);
+ } else AwayMessages.szDND = 0;
+ } else
+ return; // unsupported status
+
+ if (session){
+ SetSessionStatus(status); // update current away message
+ }
+}
+
+static VOID CALLBACK NullAPC (DWORD_PTR)
+{
+ // This function intentionally left blank
+}
+
+void WakeThread(HANDLE hThread)
+{
+ QueueUserAPC(NullAPC, hThread, 0);
+}
+
+void __cdecl KeepAliveThread(LPVOID param)
+{
+ CSametimeProto* proto = (CSametimeProto*)param;
+ int i = 120;
+ proto->debugLog(_T("KeepAliveThread() start"));
+
+ while(1) {
+
+ if (i <= 0){
+ i = 120;
+ // send keepalive every 120 * 250 = 30000[ms]
+ if (mwSession_isStarted(proto->session) && proto->session){
+ mwSession_sendKeepalive(proto->session);
+ }
+ }
+
+ i--;
+
+ SleepEx(250, TRUE);
+
+ EnterCriticalSection(&(proto->session_cs));
+ if (Miranda_Terminated() || !proto->session) {
+ LeaveCriticalSection(&(proto->session_cs));
+ proto->debugLog(_T("KeepAliveThread() end"));
+ break;
+ }
+ LeaveCriticalSection(&(proto->session_cs));
+ }
+
+ return;
+}
+
+int waitcallback(unsigned int* timeout)
+{
+ return continue_connect ? 1 : 0;
+}
+
+void __cdecl SessionThread(LPVOID param)
+{
+
+ CSametimeProto* proto = (CSametimeProto*)param;
+ HANDLE hNetlibUser = proto->m_hNetlibUser;
+ proto->debugLog(_T("SessionThread() start"));
+
+ continue_connect = true;
+
+ //setup
+ NETLIBOPENCONNECTION conn_data = {0};
+ conn_data.cbSize = sizeof(NETLIBOPENCONNECTION);
+ conn_data.flags = NLOCF_V2;
+ conn_data.szHost = proto->options.server_name;
+ conn_data.wPort = proto->options.port;
+ conn_data.timeout = 20;
+ conn_data.waitcallback = waitcallback;
+
+ proto->BroadcastNewStatus(ID_STATUS_CONNECTING);
+
+ proto->server_connection = (HANDLE)CallService(MS_NETLIB_OPENCONNECTION, (WPARAM)hNetlibUser, (LPARAM)&conn_data);
+
+ if (!proto->server_connection) {
+
+ proto->BroadcastNewStatus(ID_STATUS_OFFLINE);
+
+ if (continue_connect) {
+ // real timeout - not user cancelled
+ proto->showPopup(TranslateT("No server connection!"), SAMETIME_POPUP_ERROR);
+ }
+
+ proto->debugLog(_T("SessionThread() end, no server_connection, continue_connect=[%d]"), continue_connect);
+ return;
+ }
+
+ mwSessionHandler handler = {0};
+ handler.clear = SessionClear;
+ handler.io_write = SessionWrite;
+ handler.io_close = SessionClose;
+ handler.on_stateChange = SessionStateChange;
+ handler.on_admin = SessionAdmin;
+ handler.on_announce = SessionAnnounce;
+ handler.on_setPrivacyInfo = SessionSetPrivacyInfo;
+ handler.on_setUserStatus = SessionSetUserStatus;
+
+ EnterCriticalSection(&proto->session_cs);
+ proto->session = mwSession_new(&handler);
+
+ proto->InitMeanwhileServices();
+
+ mwSession_start(proto->session);
+ LeaveCriticalSection(&proto->session_cs);
+
+ mir_forkthread(KeepAliveThread, (void*)proto);
+
+ unsigned char* recv_buffer = (unsigned char*)mir_alloc(1024 * 32);
+ int bytes;
+ //while(session && server_connection && mwSession_getState(session) != mwSession_STOPPED) {
+ while(proto->server_connection) {// && session) {// && !mwSession_isStopped(session)) { // break on error
+ bytes = Netlib_Recv(proto->server_connection, (char *)recv_buffer, 1024 * 32, 0);
+ proto->debugLog(_T("SessionThread() Netlib_Recv'ed bytes=[%d]"), bytes);
+
+ if (bytes == 0) {
+ break;
+ } else if (bytes == SOCKET_ERROR) {
+ // this is normal - e.g. socket closed due to log off, during blocking read above
+ break;
+ } else {
+ EnterCriticalSection(&proto->session_cs);
+ mwSession_recv(proto->session, recv_buffer, bytes);
+ LeaveCriticalSection(&proto->session_cs);
+ }
+ }
+ mir_free(recv_buffer);
+
+ EnterCriticalSection(&proto->session_cs);
+ proto->DeinitMeanwhileServices();
+ mwSession* old_session = proto->session;
+ proto->session = 0; // kills keepalive thread, if awake
+ mwSession_free(old_session);
+ LeaveCriticalSection(&proto->session_cs);
+
+ proto->BroadcastNewStatus(ID_STATUS_OFFLINE);
+ proto->SetAllOffline();
+ proto->first_online = true;
+
+ proto->debugLog(_T("SessionThread() end"));
+ return;
+}
+
+WORD CSametimeProto::GetClientVersion()
+{
+ if (!session) return 0;
+
+ WORD retval = 0;
+ retval = (int)mwSession_getProperty(session, mwSession_CLIENT_VER_MAJOR) << 8;
+ retval |= (int)mwSession_getProperty(session, mwSession_CLIENT_VER_MINOR);
+ return retval;
+}
+
+WORD CSametimeProto::GetServerVersion()
+{
+ if (!session) return 0;
+
+ WORD retval = 0;
+ retval = (int)mwSession_getProperty(session, mwSession_SERVER_VER_MAJOR) << 8;
+ retval |= (int)mwSession_getProperty(session, mwSession_SERVER_VER_MINOR);
+ return retval;
+}
+
+int CSametimeProto::LogIn(int ls, HANDLE hNetlibUser)
+{
+ debugLog(_T("LogIn() start"));
+
+ EnterCriticalSection(&session_cs);
+ if (session) {
+ LeaveCriticalSection(&session_cs);
+ debugLog(_T("LogIn() end, currently in session"));
+ return 0;
+ }
+ LeaveCriticalSection(&session_cs);
+
+ login_status = ls;
+
+ mir_forkthread(SessionThread, (void*)this);
+
+ return 0;
+}
+
+int CSametimeProto::LogOut()
+{
+ debugLog(_T("LogOut() start"));
+ continue_connect = false;
+
+ EnterCriticalSection(&session_cs);
+ if (session && server_connection && m_iStatus != ID_STATUS_OFFLINE && !mwSession_isStopped(session) && !mwSession_isStopping(session)) {
+ debugLog(_T("LogOut() mwSession_stop"));
+ mwSession_stop(session, 0);
+ }
+ LeaveCriticalSection(&session_cs);
+
+ return 0;
+}
+
+void CSametimeProto::InitAwayMsg()
+{
+ AwayMessages.szOnline = 0;
+ AwayMessages.szAway = 0;
+ AwayMessages.szDND = 0;
+}
+
+void CSametimeProto::DeinitAwayMsg()
+{
+ mir_free(AwayMessages.szOnline);
+ mir_free(AwayMessages.szAway);
+ mir_free(AwayMessages.szDND);
+}
+
+void SendAnnouncement(SendAnnouncementFunc_arg* arg)
+{
+ CSametimeProto* proto = arg->proto;
+ char* utfs = mir_utf8encodeT(arg->msg);
+ if (proto->session && arg->recipients) mwSession_sendAnnounce(proto->session, false , utfs, arg->recipients);
+ mir_free(utfs);
+}
+
+INT_PTR CSametimeProto::SessionAnnounce(WPARAM wParam, LPARAM lParam)
+{
+ debugLog(_T("CSametimeProto::SessionAnnounce() start"));
+ SessionAnnounceDialogProc_arg* sadpArg = (SessionAnnounceDialogProc_arg*)mir_calloc(sizeof(SessionAnnounceDialogProc_arg));
+ sadpArg->proto = this;
+ sadpArg->sendAnnouncementFunc = SendAnnouncement;
+ CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_SESSIONANNOUNCE), GetDesktopWindow(), SessionAnnounceDialogProc, (LPARAM)sadpArg);
+ return 0;
+}
+
+void CSametimeProto::InitSessionMenu()
+{
+ debugLog(_T("CSametimeProto::InitSessionMenu()"));
+
+ CreateProtoService(MS_SAMETIME_MENUANNOUNCESESSION, &CSametimeProto::SessionAnnounce);
+
+ char service[128];
+
+ CLISTMENUITEM mi = { sizeof(mi) };
+ mi.flags = CMIF_TCHAR;
+ mi.popupPosition = 500085001;
+ mi.position = 2000060000;
+ mi.ptszName = LPGENT("Send Announcement...");
+ mir_snprintf(service, sizeof(service), "%s%s", m_szModuleName, MS_SAMETIME_MENUANNOUNCESESSION);
+ mi.pszService = service;
+ mi.icolibItem = GetIconHandle(IDI_ICON_ANNOUNCE);
+ mi.pszContactOwner = m_szModuleName;
+ hSessionAnnounceMenuItem = Menu_AddContactMenuItem(&mi);
+}
+
+void CSametimeProto::DeinitSessionMenu()
+{
+ debugLog(_T("CSametimeProto::DeinitSessionMenu()"));
+ CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)hSessionAnnounceMenuItem, (LPARAM)0);
+}
+
+void CSametimeProto::InitCritSection()
+{
+ debugLog(_T("CSametimeProto::InitCritSection()"));
+ InitializeCriticalSection(&session_cs);
+}
+
+void CSametimeProto::DeinitCritSection()
+{
+ debugLog(_T("CSametimeProto::DeinitCritSection()"));
+ DeleteCriticalSection(&session_cs);
+}
+
+void CSametimeProto::InitGroupChats()
+{
+ debugLog(_T("CSametimeProto::InitGroupChats()"));
+
+ // register with chat module
+ GCREGISTER gcr = { sizeof(gcr) };
+ gcr.pszModule = m_szModuleName;
+ gcr.ptszDispName = m_tszUserName;
+ gcr.dwFlags = 0;
+ gcr.iMaxText = MAX_MESSAGE_SIZE;
+ gcr.nColors = 0;
+ gcr.pColors = 0;
+ CallService(MS_GC_REGISTER, 0, (LPARAM)(GCREGISTER*) &gcr);
+}
|