diff options
author | Szymon Tokarz <wsx22@o2.pl> | 2014-03-21 23:45:44 +0000 |
---|---|---|
committer | Szymon Tokarz <wsx22@o2.pl> | 2014-03-21 23:45:44 +0000 |
commit | 1a5937099e9333a426edc5fb6eef2ac626557857 (patch) | |
tree | aabe4beebb6a459338b4a0f2cbf0cd7eb0a795e7 /protocols/Sametime/src/conference.cpp | |
parent | 34daf458e330dd8258293db8067ef5bc3f6cb72c (diff) |
Sametime protocol adopted
- Meanwhile Library included into plugin sources
- needs glib dll files inside Miranda root directory
\dll_x32\libglib-2.0-0.dll and \dll_x32\intl.dll for x32
\dll_x64\libglib-2.0-0.dll and \dll_x64\libintl-8.dll for x64
- more info at docs\readme.txt
git-svn-id: http://svn.miranda-ng.org/main/trunk@8676 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c
Diffstat (limited to 'protocols/Sametime/src/conference.cpp')
-rw-r--r-- | protocols/Sametime/src/conference.cpp | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/protocols/Sametime/src/conference.cpp b/protocols/Sametime/src/conference.cpp new file mode 100644 index 0000000000..e72c089e73 --- /dev/null +++ b/protocols/Sametime/src/conference.cpp @@ -0,0 +1,566 @@ +#include "StdAfx.h"
+#include "sametime.h"
+
+
+
+void CloseMyConference(CSametimeProto* proto) {
+ mwConference_destroy(proto->my_conference, 0, Translate("I'm outa here."));
+ proto->my_conference = 0;
+}
+
+
+CSametimeProto* getProtoFromMwConference(mwConference* conf)
+{
+ mwServiceConference* servConference = mwConference_getServiceConference(conf);
+ mwService* service = mwServiceConference_getService(servConference);
+ mwSession* session = mwService_getSession(service);
+ return (CSametimeProto*)mwSession_getProperty(session, "PROTO_STRUCT_PTR");
+}
+
+
+/** triggered when we receive a conference invitation. Call
+ mwConference_accept to accept the invitation and join the
+ conference, or mwConference_close to reject the invitation.
+
+ @param conf the newly created conference
+ @param inviter the indentity of the user who sent the invitation
+ @param invite the invitation text
+*/
+void mwServiceConf_on_invited(mwConference* conf, mwLoginInfo* inviter, const char* invite) {
+
+ GList *members, *mem;
+ CSametimeProto* proto = getProtoFromMwConference(conf);
+ proto->debugLog(_T("mwServiceConf_on_invited() start"));
+
+ members = mem = mwConference_getMembers(conf);
+ for (;mem;mem=mem->next) {
+ if (proto->my_login_info && strcmp(proto->my_login_info->login_id, ((mwLoginInfo*)mem->data)->login_id) == 0) {
+ proto->debugLog(_T("mwServiceConf_on_invited() already present"));
+ char* utfs = mir_utf8encodeT(TranslateT("Invitation rejected - already present."));
+ mwConference_reject(conf, 0, utfs);
+ mir_free(utfs);
+ return;
+ }
+ }
+ g_list_free(members);
+
+ wchar_t ws_username[128];
+ MultiByteToWideChar(CP_UTF8, 0, (const char*)inviter->user_name, -1, ws_username, 128);
+
+ wchar_t ws_invite[512];
+ MultiByteToWideChar(CP_UTF8, 0, (const char*)invite, -1, ws_invite, 128);
+
+ if (MessageBoxW(0, ws_invite, ws_username, MB_OKCANCEL) == IDOK) {
+ proto->debugLog(_T("mwServiceConf_on_invited() mwConference_accept"));
+ mwConference_accept(conf);
+ } else {
+ proto->debugLog(_T("mwServiceConf_on_invited() mwConference_reject"));
+ char* temp = mir_utf8encodeT(TranslateT("Your invitation has been rejected."));
+ mwConference_reject(conf, 0, temp);
+ mir_free(temp);
+ }
+
+}
+
+void CSametimeProto::ClearInviteQueue() {
+
+ debugLog(_T("CSametimeProto::ClearInviteQueue() start"));
+ if (!my_conference) return;
+
+ mwIdBlock idb;
+ idb.community = 0;
+
+ while(invite_queue.size()) {
+ idb.user = (char *)invite_queue.front().c_str();
+
+ MCONTACT hContact = FindContactByUserId(idb.user);
+ if (!hContact) {
+ mwSametimeList* user_list = mwSametimeList_new();
+ char* utfs = mir_utf8encodeT(TranslateT("None"));
+ mwSametimeGroup* stgroup = mwSametimeGroup_new(user_list, mwSametimeGroup_NORMAL, utfs);
+ mwSametimeUser* stuser = mwSametimeUser_new(stgroup, mwSametimeUser_NORMAL, &idb);
+
+ hContact = AddContact(stuser, (options.add_contacts ? false : true));
+ mwSametimeList_free(user_list);
+ mir_free(utfs);
+
+ }
+
+ bool found = false;
+ GList *members, *mem;
+ members = mem = mwConference_getMembers(my_conference);
+ for (;mem;mem=mem->next) {
+ if (my_login_info && strcmp(idb.user, ((mwLoginInfo *)mem->data)->user_id) == 0) {
+ found = true;
+ break;
+ }
+ }
+ g_list_free(members);
+
+ if (!found) {
+ char* temp = mir_utf8encodeT(TranslateT("Please join this meeting."));
+ mwConference_invite(my_conference, &idb, temp);
+ mir_free(temp);
+ }
+
+ invite_queue.pop();
+ }
+}
+
+/** triggered when we enter the conference. Provides the initial
+ conference membership list as a GList of mwLoginInfo structures
+
+ @param conf the conference just joined
+ @param members mwLoginInfo list of existing conference members
+*/
+void mwServiceConf_conf_opened(mwConference* conf, GList* members)
+{
+ CSametimeProto* proto = getProtoFromMwConference(conf);
+ proto->debugLog(_T("mwServiceConf_conf_opened() start"));
+
+ TCHAR* tszConfId = mir_utf8decodeT(mwConference_getName(conf));
+ TCHAR* tszConfTitle = mir_utf8decodeT(mwConference_getTitle(conf));
+
+ // create new chat session
+ GCSESSION gcs = { sizeof(gcs) };
+ gcs.dwFlags = 0;
+ gcs.iType = GCW_CHATROOM;
+ gcs.pszModule = proto->m_szModuleName;
+ gcs.ptszID = tszConfId;
+ gcs.ptszName = tszConfTitle;
+ gcs.dwItemData = 0;
+
+ CallServiceSync(MS_GC_NEWSESSION, 0, (LPARAM)&gcs);
+ mir_free(tszConfTitle);
+
+ //add a group
+ GCDEST gcd = { proto->m_szModuleName, 0 };
+ gcd.iType = GC_EVENT_ADDGROUP;
+ gcd.ptszID = tszConfId;
+
+ GCEVENT gce = { sizeof(gce), &gcd };
+ gce.dwFlags = GCEF_ADDTOLOG;
+ gce.ptszStatus = TranslateT("Normal");
+
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce);
+
+ // add users
+ gcd.iType = GC_EVENT_JOIN;
+
+ GList *user = members;
+ for (;user; user=user->next) {
+ proto->debugLog(_T("mwServiceConf_conf_opened() add user"));
+
+ TCHAR* tszUserName = mir_utf8decodeT(((mwLoginInfo*)user->data)->user_name);
+ TCHAR* tszUserId = mir_utf8decodeT(((mwLoginInfo*)user->data)->login_id);
+ gce.ptszNick = tszUserName;
+ gce.ptszUID = tszUserId;
+ gce.bIsMe = (strcmp(((mwLoginInfo*)user->data)->login_id, proto->my_login_info->login_id) == 0);
+
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM) &gce);
+
+ mir_free(tszUserName);
+ mir_free(tszUserId);
+ }
+
+ // finalize setup (show window)
+ gcd.iType = GC_EVENT_CONTROL;
+ CallServiceSync(MS_GC_EVENT, SESSION_INITDONE, (LPARAM)&gce);
+
+ gcd.iType = GC_EVENT_CONTROL;
+ CallServiceSync(MS_GC_EVENT, SESSION_ONLINE, (LPARAM)&gce);
+
+ if (conf == proto->my_conference) {
+ proto->ClearInviteQueue();
+ }
+
+ mir_free(tszConfId);
+}
+
+/** triggered when a conference is closed. This is typically when
+ we've left it */
+void mwServiceConf_conf_closed(mwConference* conf, guint32 reason)
+{
+ CSametimeProto* proto = getProtoFromMwConference(conf);
+ proto->debugLog(_T("mwServiceConf_conf_closed() start"));
+
+ TCHAR* tszConfId = mir_utf8decodeT(mwConference_getName(conf));
+
+ GCDEST gcd = { proto->m_szModuleName };
+ gcd.ptszID = tszConfId;
+ gcd.iType = GC_EVENT_CONTROL;
+
+ GCEVENT gce = { sizeof(gce), &gcd };
+ gce.dwFlags = GCEF_ADDTOLOG;
+
+ CallService(MS_GC_EVENT, SESSION_OFFLINE, (LPARAM)&gce);
+ CallService(MS_GC_EVENT, SESSION_TERMINATE, (LPARAM)&gce);
+ mir_free(tszConfId);
+}
+
+/** triggered when someone joins the conference */
+void mwServiceConf_on_peer_joined(mwConference* conf, mwLoginInfo* user)
+{
+ CSametimeProto* proto = getProtoFromMwConference(conf);
+ proto->debugLog(_T("mwServiceConf_on_peer_joined() start"));
+
+ TCHAR* tszConfId = mir_utf8decodeT(mwConference_getName(conf));
+
+ MCONTACT hContact = proto->FindContactByUserId(((mwLoginInfo*)user)->user_id);
+ if (!hContact) {
+ mwIdBlock idb;
+ idb.user = ((mwLoginInfo *)user)->user_id;
+ idb.community = 0;
+
+ mwSametimeList* user_list = mwSametimeList_new();
+ char* utfs = mir_utf8encodeT(TranslateT("None"));
+ mwSametimeGroup* stgroup = mwSametimeGroup_new(user_list, mwSametimeGroup_NORMAL, utfs);
+ mwSametimeUser* stuser = mwSametimeUser_new(stgroup, mwSametimeUser_NORMAL, &idb);
+
+ hContact = proto->AddContact(stuser, (proto->options.add_contacts ? false : true));
+
+ mwSametimeList_free(user_list);
+ mir_free(utfs);
+ }
+
+ // add user
+ GCDEST gcd = { proto->m_szModuleName };
+ gcd.ptszID = tszConfId;
+ gcd.iType = GC_EVENT_JOIN;
+
+ GCEVENT gce = { sizeof(gce), &gcd };
+ gce.dwFlags = GCEF_ADDTOLOG;
+ TCHAR* tszUserName = mir_utf8decodeT(((mwLoginInfo*)user)->user_name);
+ TCHAR* tszUserId = mir_utf8decodeT(((mwLoginInfo*)user)->login_id);
+ gce.ptszNick = tszUserName;
+ gce.ptszUID = tszUserId;
+ gce.ptszStatus = _T("Normal");
+ gce.time = (DWORD)time(0);
+
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM) &gce);
+
+ mir_free(tszUserName);
+ mir_free(tszUserId);
+ mir_free(tszConfId);
+}
+
+/** triggered when someone leaves the conference */
+void mwServiceConf_on_peer_parted(mwConference* conf, mwLoginInfo* user)
+{
+ CSametimeProto* proto = getProtoFromMwConference(conf);
+ proto->debugLog(_T("mwServiceConf_on_peer_parted() start"));
+
+ TCHAR* tszConfId = mir_utf8decodeT(mwConference_getName(conf));
+
+ // remove user
+ GCDEST gcd = { proto->m_szModuleName };
+ gcd.ptszID = tszConfId;
+ gcd.iType = GC_EVENT_PART;
+
+ GCEVENT gce = { sizeof(gce), &gcd };
+ gce.dwFlags = GCEF_ADDTOLOG;
+ TCHAR* tszUserName = mir_utf8decodeT(((mwLoginInfo*)user)->user_name);
+ TCHAR* tszUserId = mir_utf8decodeT(((mwLoginInfo*)user)->login_id);
+ gce.ptszNick = tszUserName;
+ gce.ptszUID = tszUserId;
+ gce.ptszStatus = _T("Normal");
+ gce.time = (DWORD)time(0);
+
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)(GCEVENT *) &gce);
+
+ mir_free(tszUserName);
+ mir_free(tszUserId);
+ mir_free(tszConfId);
+}
+
+/** triggered when someone says something */
+void mwServiceConf_on_text(mwConference* conf, mwLoginInfo* user, const char* what)
+{
+ CSametimeProto* proto = getProtoFromMwConference(conf);
+ proto->debugLog(_T("mwServiceConf_on_text() start"));
+
+ TCHAR* tszConfId = mir_utf8decodeT(mwConference_getName(conf));
+
+ GCDEST gcd = { proto->m_szModuleName };
+ gcd.ptszID = tszConfId;
+ gcd.iType = GC_EVENT_MESSAGE;
+
+ GCEVENT gce = { sizeof(gce), &gcd };
+ gce.dwFlags = GCEF_ADDTOLOG;
+
+ TCHAR* textT = mir_utf8decodeT(what);
+ TCHAR* tszUserName = mir_utf8decodeT(((mwLoginInfo*)user)->user_name);
+ TCHAR* tszUserId = mir_utf8decodeT(((mwLoginInfo*)user)->login_id);
+ gce.ptszText = textT;
+ gce.ptszNick = tszUserName;
+ gce.ptszUID = tszUserId;
+ gce.time = (DWORD)time(0);
+
+ CallService(MS_GC_EVENT, 0, (LPARAM)(GCEVENT *) &gce);
+
+ mir_free(textT);
+ mir_free(tszUserName);
+ mir_free(tszUserId);
+ mir_free(tszConfId);
+}
+
+/** typing notification */
+void mwServiceConf_on_typing(mwConference* conf, mwLoginInfo* who, gboolean typing)
+{
+ CSametimeProto* proto = getProtoFromMwConference(conf);
+ proto->debugLog(_T("mwServiceConf_on_typing() start"));
+ ///TODO unimplemented
+}
+
+/** optional. called from mwService_free */
+void mwServiceConf_clear(mwServiceConference* srvc)
+{
+}
+
+mwConferenceHandler mwConference_handler = {
+ mwServiceConf_on_invited,
+ mwServiceConf_conf_opened,
+ mwServiceConf_conf_closed,
+ mwServiceConf_on_peer_joined,
+ mwServiceConf_on_peer_parted,
+ mwServiceConf_on_text,
+ mwServiceConf_on_typing,
+ mwServiceConf_clear
+};
+
+void CSametimeProto::TerminateConference(char* name)
+{
+ debugLog(_T("CSametimeProto::TerminateConference() start"));
+
+ GList *conferences, *conf;
+ conferences = conf = mwServiceConference_getConferences(service_conference);
+ for (;conf;conf = conf->next) {
+ if (strcmp(name, mwConference_getName((mwConference*)conf->data)) == 0) {
+
+ TCHAR* idt = mir_utf8decodeT(name);
+ GCDEST gcd = {m_szModuleName, idt, GC_EVENT_CONTROL};
+
+ GCEVENT gce = { sizeof(gce), &gcd };
+ gce.dwFlags = GCEF_ADDTOLOG;
+ CallService(MS_GC_EVENT, SESSION_TERMINATE, (LPARAM)&gce);
+
+ mir_free(idt);
+ }
+ }
+ g_list_free(conferences);
+}
+
+
+int CSametimeProto::GcEventHook(WPARAM wParam, LPARAM lParam) {
+
+ GCHOOK* gch = (GCHOOK*)lParam;
+
+ if (strcmp(gch->pDest->pszModule, m_szModuleName) != 0) return 0;
+
+ GList *conferences, *conf;
+ conferences = conf = mwServiceConference_getConferences(service_conference);
+ for (;conf;conf = conf->next) {
+ TCHAR* tszConfId = mir_utf8decodeT(mwConference_getName((mwConference*)conf->data));
+ if (_tcscmp(gch->pDest->ptszID, tszConfId) == 0) {
+
+ switch(gch->pDest->iType) {
+ case GC_USER_MESSAGE:
+ {
+ debugLog(_T("CSametimeProto::GcEventHook() GC_USER_MESSAGE"));
+ char* utf_msg;
+ utf_msg = mir_utf8encodeT(gch->ptszText);
+ mwConference_sendText((mwConference*)conf->data, utf_msg);
+ mir_free(utf_msg);
+ }
+ break;
+ case GC_SESSION_TERMINATE:
+ {
+ if (my_conference == conf->data){
+ debugLog(_T("CSametimeProto::GcEventHook() GC_SESSION_TERMINATE CloseMyConference"));
+ CloseMyConference(this);
+ } else {
+ debugLog(_T("CSametimeProto::GcEventHook() GC_SESSION_TERMINATE mwConference_destroy"));
+ char* utfs = mir_utf8encodeT(TranslateT("I'm outa here."));
+ mwConference_destroy((mwConference*)conf->data, 0, utfs);
+ mir_free(utfs);
+ }
+ }
+ break;
+ }
+
+ break;
+ }
+ mir_free(tszConfId);
+ }
+
+ g_list_free(conferences);
+
+ return 0;
+}
+
+int CSametimeProto::ChatDeleted(MCONTACT hContact) {
+
+ if (db_get_b(hContact, m_szModuleName, "ChatRoom", 0) == 0)
+ return 0;
+
+ debugLog(_T("CSametimeProto::ChatDeleted() hContact=[%x]"), hContact);
+ DBVARIANT dbv;
+ if (!db_get_s(hContact, m_szModuleName, "ChatRoomID", &dbv)) {
+ TerminateConference(dbv.pszVal);
+ db_free(&dbv);
+ }
+
+ return 0;
+}
+
+
+INT_PTR CSametimeProto::onMenuLeaveChat(WPARAM wParam, LPARAM lParam)
+{
+ MCONTACT hContact = (MCONTACT)wParam;
+ debugLog(_T("CSametimeProto::onMenuLeaveChat() hContact=[%x]"), hContact);
+ ChatDeleted(hContact);
+ return 0;
+}
+
+
+INT_PTR CSametimeProto::onMenuCreateChat(WPARAM wParam, LPARAM lParam)
+{
+ MCONTACT hContact = (MCONTACT)wParam;
+ debugLog(_T("CSametimeProto::onMenuCreateChat() hContact=[%x]"), hContact);
+ mwAwareIdBlock id_block;
+ mwIdBlock idb;
+ if (my_login_info && GetAwareIdFromContact(hContact, &id_block)) {
+ TCHAR title[512];
+ TCHAR* ts = mir_utf8decodeT(my_login_info->user_name);
+ mir_sntprintf(title, SIZEOF(title), TranslateT("%s's Conference"), ts);
+ mir_free(ts);
+
+ idb.user = id_block.user;
+ idb.community = id_block.community;
+
+ invite_queue.push(idb.user);
+
+ if (!my_conference) {
+ debugLog(_T("CSametimeProto::onMenuCreateChat() mwConference_open"));
+ char* utfs;
+ my_conference = mwConference_new(service_conference, utfs = mir_utf8encodeT(title));
+ mwConference_open(my_conference);
+ mir_free(utfs);
+ } else {
+ debugLog(_T("CSametimeProto::onMenuCreateChat() ClearInviteQueue"));
+ ClearInviteQueue();
+ }
+
+ free(id_block.user);
+ }
+
+ return 0;
+}
+
+int CSametimeProto::PrebuildContactMenu(WPARAM wParam, LPARAM lParam)
+{
+ MCONTACT hContact = (MCONTACT)wParam;
+ debugLog(_T("CSametimeProto::PrebuildContactMenu() hContact=[%x]"), hContact);
+ CLISTMENUITEM mi = {0};
+ mi.cbSize = sizeof(mi);
+ mi.flags = CMIM_FLAGS | (db_get_b(hContact, m_szModuleName, "ChatRoom", 0) == 1 ? 0 : CMIF_HIDDEN);
+ CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hLeaveChatMenuItem, (LPARAM)&mi);
+
+ // if user is already in our meeting,
+ bool not_present = true;
+ DBVARIANT dbv;
+ if (my_conference && !db_get_utf(hContact, m_szModuleName, "stid", &dbv)) {
+ char* user_id = dbv.pszVal;
+
+ GList *members, *mem;
+ members = mem = mwConference_getMembers(my_conference);
+ for (;mem;mem=mem->next) {
+ if (my_login_info && strcmp(user_id, ((mwLoginInfo *)mem->data)->user_id) == 0) {
+ not_present = false;
+ break;
+ }
+ }
+ g_list_free(members);
+
+ db_free(&dbv);
+ }
+ mi.flags = CMIM_FLAGS | CMIF_NOTOFFLINE | (db_get_b(hContact, m_szModuleName, "ChatRoom", 0) == 0 && not_present ? 0 : CMIF_HIDDEN);
+ CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hCreateChatMenuItem, (LPARAM)&mi);
+
+ return 0;
+}
+
+void CSametimeProto::InitConference()
+{
+ debugLog(_T("CSametimeProto::InitConference()"));
+
+ my_login_info = mwSession_getLoginInfo(session);
+
+ service_conference = mwServiceConference_new(session, &mwConference_handler);
+ mwSession_addService(session, (struct mwService*)service_conference);
+
+ HookProtoEvent(ME_GC_EVENT, &CSametimeProto::GcEventHook);
+}
+
+void CSametimeProto::DeinitConference()
+{
+ GList *conferences, *conf;
+ debugLog(_T("CSametimeProto::DeinitConference()"));
+
+ if (service_conference){
+ conferences = conf = mwServiceConference_getConferences(service_conference);
+ for (;conf;conf = conf->next) {
+ if (my_conference == conf->data) CloseMyConference(this);
+ else {
+ char* utfs = mir_utf8encodeT(TranslateT("I'm outa here."));
+ mwConference_destroy((mwConference*)conf->data, 0, utfs);
+ mir_free(utfs);
+ }
+ }
+ g_list_free(conferences);
+ }
+
+ my_login_info = 0;
+
+ mwSession_removeService(session, mwService_CONFERENCE);
+ if (service_conference){
+ mwService_free((mwService*)service_conference);
+ service_conference = 0;
+ }
+}
+
+void CSametimeProto::InitConferenceMenu()
+{
+ debugLog(_T("CSametimeProto::InitConferenceMenu()"));
+
+ CreateProtoService(MS_SAMETIME_MENULEAVECHAT, &CSametimeProto::onMenuLeaveChat);
+ CreateProtoService(MS_SAMETIME_MENUCREATECHAT, &CSametimeProto::onMenuCreateChat);
+
+ char service[128];
+
+ CLISTMENUITEM mi = { sizeof(mi) };
+ mi.flags = CMIF_TCHAR | CMIF_NOTOFFLINE;
+ mi.pszContactOwner = m_szModuleName;
+
+ mi.ptszName = LPGENT("Leave Conference");
+ mir_snprintf(service, sizeof(service), "%s%s", m_szModuleName, MS_SAMETIME_MENULEAVECHAT);
+ mi.pszService = service;
+ mi.icolibItem = GetIconHandle(IDI_ICON_LEAVE);
+ hLeaveChatMenuItem = Menu_AddContactMenuItem(&mi);
+
+ mi.ptszName = LPGENT("Start Conference");
+ mir_snprintf(service, sizeof(service), "%s%s", m_szModuleName, MS_SAMETIME_MENUCREATECHAT);
+ mi.pszService = service;
+ mi.icolibItem = GetIconHandle(IDI_ICON_INVITE);
+ hCreateChatMenuItem = Menu_AddContactMenuItem(&mi);
+
+ HookProtoEvent(ME_CLIST_PREBUILDCONTACTMENU, &CSametimeProto::PrebuildContactMenu);
+}
+
+void CSametimeProto::DeinitConferenceMenu()
+{
+ debugLog(_T("CSametimeProto::DeinitConferenceMenu()"));
+ CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)hLeaveChatMenuItem, (LPARAM)0);
+ CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)hCreateChatMenuItem, (LPARAM)0);
+}
+
|