From 1a5dc583b048727752b8f72df676ec408908041f Mon Sep 17 00:00:00 2001 From: George Hazan Date: Sun, 28 Nov 2021 17:34:41 +0300 Subject: user typing notification for Discord group chats --- include/m_chat.h | 7 +- include/m_srmm_int.h | 12 +++ libs/win32/mir_app.lib | Bin 222676 -> 223352 bytes libs/win64/mir_app.lib | Bin 218378 -> 219070 bytes plugins/Scriver/src/msgs.h | 3 +- plugins/Scriver/src/msgutils.cpp | 10 +- plugins/TabSRMM/src/controls.cpp | 6 +- plugins/TabSRMM/src/generic_msghandlers.cpp | 53 +++++----- plugins/TabSRMM/src/globals.cpp | 9 +- plugins/TabSRMM/src/msgdialog.cpp | 10 +- plugins/TabSRMM/src/msgdlgother.cpp | 4 +- plugins/TabSRMM/src/msglog.cpp | 2 +- plugins/TabSRMM/src/msgs.h | 148 ++++++++++++++-------------- protocols/Discord/src/dispatch.cpp | 28 ++++-- protocols/Discord/src/groupchat.cpp | 4 + src/core/stdmsg/src/msgdialog.cpp | 37 ++++--- src/core/stdmsg/src/msgs.h | 4 +- src/mir_app/src/chat.h | 1 + src/mir_app/src/chat_manager.cpp | 14 +++ src/mir_app/src/chat_svc.cpp | 3 + src/mir_app/src/chat_tools.cpp | 1 + src/mir_app/src/mir_app.def | 1 + src/mir_app/src/mir_app64.def | 1 + src/mir_app/src/srmm_base.cpp | 5 + 24 files changed, 218 insertions(+), 145 deletions(-) diff --git a/include/m_chat.h b/include/m_chat.h index 446124f1b2..06a16ebd84 100644 --- a/include/m_chat.h +++ b/include/m_chat.h @@ -330,6 +330,11 @@ EXTERN_C MIR_APP_DLL(struct SESSION_INFO*) Chat_NewSession( // registered with GC_EVENT_ADDGROUP. Ex "Voice" in IRC #define GC_EVENT_REMOVESTATUS 0x0800 +// GC_EVENT_TYPING - sets typing status for contact +// pszUID - Unique identifier of the one who's typing +// dwItemData - ON/OFF +#define GC_EVENT_TYPING 0x1001 + // GC_EVENT_SETCONTACTSTATUS - sets status icon for contact // pszUID - Unique identifier of the one who receives a new status // dwItemData - (DWORD)ID_STATUS_* or zero to remove status icon @@ -469,7 +474,7 @@ EXTERN_C MIR_APP_DLL(int) Chat_GetInfo(GC_INFO*); #define GC_USER_CHANMGR 2 // user clicked the settings button in a chat room #define GC_USER_LOGMENU 3 // user has selected a message log menu item, dwData is valid. See ME_GC_BUILDMENU #define GC_USER_NICKLISTMENU 4 // user has selected a userlist menu item, valid members: dwData. See ME_GC_BUILDMENU -#define GC_USER_TYPNOTIFY 5 // NOT IMPLEMENTED YET! user is typing +#define GC_USER_TYPNOTIFY 5 // user is typing #define GC_USER_PRIVMESS 6 // user requests to send a private message to a user. pszUID is valid #define GC_SESSION_TERMINATE 7 // the session is about to be terminated, the "user defined data" is passed in dwData, which can be good free'ing any allocated memory. diff --git a/include/m_srmm_int.h b/include/m_srmm_int.h index 9fbfed7d24..62d0b2d689 100644 --- a/include/m_srmm_int.h +++ b/include/m_srmm_int.h @@ -195,6 +195,7 @@ protected: INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) override; + bool AllowTyping() const; int NotifyEvent(int code); bool ProcessFileDrop(HDROP hDrop, MCONTACT hContact); bool PasteFilesAsURL(HDROP hDrop); @@ -209,6 +210,12 @@ protected: COLORREF m_clrInputBG, m_clrInputFG; time_t m_iLastEnterTime; + // user typing support; + DWORD m_nLastTyping = 0; + BYTE m_bShowTyping = 0; + int m_nTypeSecs = 0, m_nTypeMode = 0; + const USERINFO* m_pUserTyping = nullptr; + CCtrlListBox m_nickList; CCtrlButton m_btnColor, m_btnBkColor; CCtrlButton m_btnBold, m_btnItalic, m_btnUnderline; @@ -253,6 +260,11 @@ public: __forceinline bool isChat() const { return m_si != nullptr; } __forceinline SESSION_INFO *getChat() const { return m_si; } __forceinline CSrmmLogWindow *log() const { return m_pLog; } + + __forceinline void setTyping(int nSecs, const USERINFO* pUser = nullptr) { + m_pUserTyping = pUser; + m_nTypeSecs = nSecs; + } __inline void* operator new(size_t size) { return calloc(1, size); } __inline void operator delete(void *p) { free(p); } diff --git a/libs/win32/mir_app.lib b/libs/win32/mir_app.lib index d9b3730b5b..2c8e2c3762 100644 Binary files a/libs/win32/mir_app.lib and b/libs/win32/mir_app.lib differ diff --git a/libs/win64/mir_app.lib b/libs/win64/mir_app.lib index b8bb5dbb04..be614d3a51 100644 Binary files a/libs/win64/mir_app.lib and b/libs/win64/mir_app.lib differ diff --git a/plugins/Scriver/src/msgs.h b/plugins/Scriver/src/msgs.h index ee0d355c24..63130cbd56 100644 --- a/plugins/Scriver/src/msgs.h +++ b/plugins/Scriver/src/msgs.h @@ -109,11 +109,10 @@ class CMsgDialog : public CSrmmBaseDialog static INT_PTR CALLBACK FilterWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); - bool m_bIncoming, m_bShowTyping, m_bWindowCascaded; + bool m_bIncoming, m_bWindowCascaded; MEVENT m_hDbEventFirst, m_hDbEventLast, m_hDbUnreadEventFirst; int m_minLogBoxHeight, m_minEditBoxHeight; - int m_nTypeSecs, m_nTypeMode, m_nLastTyping; int m_iShowUnread; WORD m_wStatus; DWORD m_lastMessage; diff --git a/plugins/Scriver/src/msgutils.cpp b/plugins/Scriver/src/msgutils.cpp index 60c89a3801..3f2111f646 100644 --- a/plugins/Scriver/src/msgutils.cpp +++ b/plugins/Scriver/src/msgutils.cpp @@ -154,7 +154,10 @@ void CMsgDialog::NotifyTyping(int mode) // End user check m_nTypeMode = mode; - CallService(MS_PROTO_SELFISTYPING, m_hContact, m_nTypeMode); + if (isChat()) + Chat_DoEventHook(m_si, GC_USER_TYPNOTIFY, 0, 0, m_nTypeMode); + else + CallService(MS_PROTO_SELFISTYPING, m_hContact, m_nTypeMode); } void CMsgDialog::Reattach(HWND hwndContainer) @@ -525,7 +528,8 @@ void CMsgDialog::UpdateStatusBar() else if (m_nTypeSecs) { sbd.hIcon = g_plugin.getIcon(IDI_TYPING); sbd.pszText = szText; - mir_snwprintf(szText, TranslateT("%s is typing a message..."), Clist_GetContactDisplayName(m_hContact)); + mir_snwprintf(szText, TranslateT("%s is typing a message..."), + (m_pUserTyping) ? m_pUserTyping->pszNick : Clist_GetContactDisplayName(m_hContact)); m_nTypeSecs--; } else if (m_lastMessage) { @@ -577,7 +581,7 @@ void CMsgDialog::UpdateTabControl() void CMsgDialog::UserIsTyping(int iState) { - m_nTypeSecs = (iState > 0) ? iState : 0; + setTyping((iState > 0) ? iState : 0); } ///////////////////////////////////////////////////////////////////////////////////////// diff --git a/plugins/TabSRMM/src/controls.cpp b/plugins/TabSRMM/src/controls.cpp index 4c426a9e1d..c9b04eb7c3 100644 --- a/plugins/TabSRMM/src/controls.cpp +++ b/plugins/TabSRMM/src/controls.cpp @@ -956,11 +956,11 @@ LONG_PTR CALLBACK CMsgDialog::StatusBarSubclassProc(HWND hWnd, UINT msg, WPARAM break; if (!mir_strcmp(sid->szModule, MSG_ICON_MODULE)) { - if (sid->dwId == MSG_ICON_SOUND) + if (sid->dwId == MSG_ICON_SOUND) { mir_snwprintf(wBuf, TranslateT("Sounds are %s. Click to toggle status, hold Shift and click to set for all open containers"), pContainer->m_flags.m_bNoSound ? TranslateT("disabled") : TranslateT("enabled")); - - else if (sid->dwId == MSG_ICON_UTN && (!dat->isChat() || dat->m_si->iType == GCW_PRIVMESS)) { + } + else if (sid->dwId == MSG_ICON_UTN && dat->AllowTyping()) { int mtnStatus = g_plugin.getByte(dat->m_hContact, SRMSGSET_TYPING, g_plugin.getByte(SRMSGSET_TYPINGNEW, SRMSGDEFSET_TYPINGNEW)); mir_snwprintf(wBuf, TranslateT("Sending typing notifications is %s."), mtnStatus ? TranslateT("enabled") : TranslateT("disabled")); diff --git a/plugins/TabSRMM/src/generic_msghandlers.cpp b/plugins/TabSRMM/src/generic_msghandlers.cpp index a856dbfcad..b34ab7f6e6 100644 --- a/plugins/TabSRMM/src/generic_msghandlers.cpp +++ b/plugins/TabSRMM/src/generic_msghandlers.cpp @@ -474,7 +474,7 @@ LRESULT CMsgDialog::DM_MsgWindowCmdHandler(UINT cmd, WPARAM wParam, LPARAM lPara break; case IDC_SELFTYPING: - if (m_si == nullptr || m_si->iType == GCW_PRIVMESS) { + if (AllowTyping()) { int iCurrentTypingMode = g_plugin.getByte(m_hContact, SRMSGSET_TYPING, g_plugin.getByte(SRMSGSET_TYPINGNEW, SRMSGDEFSET_TYPINGNEW)); if (m_nTypeMode == PROTOTYPE_SELFTYPING_ON && iCurrentTypingMode) { DM_NotifyTyping(PROTOTYPE_SELFTYPING_OFF); @@ -822,27 +822,33 @@ void CMsgDialog::DM_NotifyTyping(int mode) if (!(typeCaps & PF4_SUPPORTTYPING)) return; - DWORD protoStatus = Proto_GetStatus(szProto); - if (protoStatus < ID_STATUS_ONLINE) - return; + if (isChat()) { + m_nTypeMode = mode; + Chat_DoEventHook(m_si, GC_USER_TYPNOTIFY, 0, 0, m_nTypeMode); + } + else { + DWORD protoStatus = Proto_GetStatus(szProto); + if (protoStatus < ID_STATUS_ONLINE) + return; - // check visibility/invisibility lists to not "accidentially" send MTN to contacts who - // should not see them (privacy issue) - DWORD protoCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0); - if (protoCaps & PF1_VISLIST && db_get_w(hContact, szProto, "ApparentMode", 0) == ID_STATUS_OFFLINE) - return; + // check visibility/invisibility lists to not "accidentially" send MTN to contacts who + // should not see them (privacy issue) + DWORD protoCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0); + if (protoCaps & PF1_VISLIST && db_get_w(hContact, szProto, "ApparentMode", 0) == ID_STATUS_OFFLINE) + return; - if (protoCaps & PF1_INVISLIST && protoStatus == ID_STATUS_INVISIBLE && db_get_w(hContact, szProto, "ApparentMode", 0) != ID_STATUS_ONLINE) - return; + if (protoCaps & PF1_INVISLIST && protoStatus == ID_STATUS_INVISIBLE && db_get_w(hContact, szProto, "ApparentMode", 0) != ID_STATUS_ONLINE) + return; - // don't send to contacts which are not permanently added to the contact list, - // unless the option to ignore added status is set. - if (!Contact_OnList(m_hContact) && !g_plugin.getByte(SRMSGSET_TYPINGUNKNOWN, SRMSGDEFSET_TYPINGUNKNOWN)) - return; + // don't send to contacts which are not permanently added to the contact list, + // unless the option to ignore added status is set. + if (!Contact_OnList(m_hContact) && !g_plugin.getByte(SRMSGSET_TYPINGUNKNOWN, SRMSGDEFSET_TYPINGUNKNOWN)) + return; - // End user check - m_nTypeMode = mode; - CallService(MS_PROTO_SELFISTYPING, hContact, m_nTypeMode); + // End user check + m_nTypeMode = mode; + CallService(MS_PROTO_SELFISTYPING, hContact, m_nTypeMode); + } } void CMsgDialog::DM_OptionsApplied(bool bRemakeLog) @@ -905,7 +911,9 @@ void CMsgDialog::DM_Typing(bool fForceOff) m_bShowTyping = 2; m_nTypeSecs = 86400; - mir_snwprintf(m_wszStatusBar, TranslateT("%s has entered text."), m_cache->getNick()); + if (!isChat()) + mir_snwprintf(m_wszStatusBar, TranslateT("%s has entered text."), m_cache->getNick()); + if (hwndStatus && m_pContainer->m_hwndActive == m_hwnd) SendMessage(hwndStatus, SB_SETTEXT, 0, (LPARAM)m_wszStatusBar); } @@ -930,7 +938,8 @@ void CMsgDialog::DM_Typing(bool fForceOff) tabUpdateStatusBar(); } else if (m_nTypeSecs > 0) { - mir_snwprintf(m_wszStatusBar, TranslateT("%s is typing a message"), m_cache->getNick()); + mir_snwprintf(m_wszStatusBar, TranslateT("%s is typing a message"), + (m_pUserTyping) ? m_pUserTyping->pszNick : m_cache->getNick()); m_nTypeSecs--; if (hwndStatus && m_pContainer->m_hwndActive == m_hwnd) { @@ -1221,7 +1230,7 @@ void CMsgDialog::DrawStatusIcons(HDC hDC, const RECT &rc, int gap) PluginConfig.m_smcxicon, PluginConfig.m_smcyicon, 0, nullptr, DI_NORMAL); } else if (sid->dwId == MSG_ICON_UTN) { - if (!isChat() || m_si->iType == GCW_PRIVMESS) { + if (AllowTyping()) { DrawIconEx(hDC, x, y, PluginConfig.g_buttonBarIcons[ICON_DEFAULT_TYPING], PluginConfig.m_smcxicon, PluginConfig.m_smcyicon, 0, nullptr, DI_NORMAL); DrawIconEx(hDC, x, y, g_plugin.getByte(m_hContact, SRMSGSET_TYPING, g_plugin.getByte(SRMSGSET_TYPINGNEW, SRMSGDEFSET_TYPINGNEW)) ? @@ -1275,7 +1284,7 @@ void CMsgDialog::CheckStatusIconClick(POINT pt, const RECT &rc, int gap, int cod InvalidateRect(m_pContainer->m_hwndStatus, nullptr, TRUE); } } - else if (sid->dwId == MSG_ICON_UTN && code != NM_RCLICK && (!isChat() || m_si->iType == GCW_PRIVMESS)) { + else if (sid->dwId == MSG_ICON_UTN && code != NM_RCLICK && AllowTyping()) { SendMessage(m_pContainer->m_hwndActive, WM_COMMAND, IDC_SELFTYPING, 0); InvalidateRect(m_pContainer->m_hwndStatus, nullptr, TRUE); } diff --git a/plugins/TabSRMM/src/globals.cpp b/plugins/TabSRMM/src/globals.cpp index 7a3ce34dc9..e9b8777437 100644 --- a/plugins/TabSRMM/src/globals.cpp +++ b/plugins/TabSRMM/src/globals.cpp @@ -365,12 +365,9 @@ int CGlobals::DBSettingChanged(WPARAM hContact, LPARAM lParam) PostMessage(hwnd, DM_UPDATESTATUSMSG, 0, 0); if (fChanged) { - if (dat && c->getStatus() == ID_STATUS_OFFLINE) { // clear typing notification in the status bar when contact goes offline - dat->m_nTypeSecs = 0; - dat->m_bShowTyping = 0; - dat->m_wszStatusBar[0] = 0; - PostMessage(dat->GetHwnd(), DM_UPDATELASTMESSAGE, 0, 0); - } + if (dat && c->getStatus() == ID_STATUS_OFFLINE) // clear typing notification in the status bar when contact goes offline + dat->ClearTyping(); + PostMessage(PluginConfig.g_hwndHotkeyHandler, DM_LOGSTATUSCHANGE, MAKELONG(c->getStatus(), c->getOldStatus()), (LPARAM)c); } } diff --git a/plugins/TabSRMM/src/msgdialog.cpp b/plugins/TabSRMM/src/msgdialog.cpp index c872c9c44a..a2100c9fcb 100644 --- a/plugins/TabSRMM/src/msgdialog.cpp +++ b/plugins/TabSRMM/src/msgdialog.cpp @@ -478,7 +478,7 @@ bool CMsgDialog::OnInitDialog() GetMyNick(); m_iMultiSplit = g_plugin.getDword("multisplit", 150); - if (m_si == nullptr || m_si->iType == GCW_PRIVMESS) { + if (AllowTyping()) { m_nTypeMode = PROTOTYPE_SELFTYPING_OFF; timerType.Start(1000); } @@ -718,7 +718,7 @@ void CMsgDialog::OnDestroy() else SendMessage(m_hwnd, WM_COMMAND, IDC_PIC, 0); } - if (m_si == nullptr || m_si->iType == GCW_PRIVMESS) + if (AllowTyping()) if (m_nTypeMode == PROTOTYPE_SELFTYPING_ON) DM_NotifyTyping(PROTOTYPE_SELFTYPING_OFF); @@ -817,7 +817,7 @@ void CMsgDialog::onClick_Ok(CCtrlButton *) Utils::enableDlgControl(m_hwnd, IDOK, false); // Typing support for GCW_PRIVMESS sessions - if (m_si->iType == GCW_PRIVMESS) + if (AllowTyping()) if (m_nTypeMode == PROTOTYPE_SELFTYPING_ON) DM_NotifyTyping(PROTOTYPE_SELFTYPING_OFF); @@ -1069,7 +1069,7 @@ void CMsgDialog::onChange_Message(CCtrlEdit*) m_btnOk.Enable(m_message.GetRichTextLength() != 0); // Typing support for GCW_PRIVMESS sessions - if (m_si == nullptr || m_si->iType == GCW_PRIVMESS) { + if (AllowTyping()) { if (!(GetKeyState(VK_CONTROL) & 0x8000)) { m_nLastTyping = GetTickCount(); if (GetWindowTextLength(m_message.GetHwnd())) { @@ -1086,7 +1086,7 @@ void CMsgDialog::onChange_Message(CCtrlEdit*) void CMsgDialog::onType(CTimer *) { - if (m_si == nullptr || m_si->iType == GCW_PRIVMESS) + if (AllowTyping()) DM_Typing(false); } diff --git a/plugins/TabSRMM/src/msgdlgother.cpp b/plugins/TabSRMM/src/msgdlgother.cpp index 3be7d4eb8b..36012b25f3 100644 --- a/plugins/TabSRMM/src/msgdlgother.cpp +++ b/plugins/TabSRMM/src/msgdlgother.cpp @@ -2009,12 +2009,12 @@ void CMsgDialog::tabUpdateStatusBar() const int CMsgDialog::Typing(int secs) { - if (m_si != nullptr && m_si->iType != GCW_PRIVMESS) + if (!AllowTyping()) return 0; int preTyping = m_nTypeSecs != 0; - m_nTypeSecs = (secs > 0) ? secs : 0; + setTyping(m_nTypeSecs = (secs > 0) ? secs : 0); if (m_nTypeSecs) m_bShowTyping = 0; diff --git a/plugins/TabSRMM/src/msglog.cpp b/plugins/TabSRMM/src/msglog.cpp index 18e33c596e..db83a7d661 100644 --- a/plugins/TabSRMM/src/msglog.cpp +++ b/plugins/TabSRMM/src/msglog.cpp @@ -1322,7 +1322,7 @@ void CLogWindow::LogEvents(LOGINFO *lin, bool bRedraw) if (m_rtf.GetHwnd() == nullptr || lin == nullptr || si == nullptr) return; - if (!bRedraw && (si->iType == GCW_CHATROOM || si->iType == GCW_PRIVMESS) && m_pDlg.m_bFilterEnabled && (m_pDlg.m_iLogFilterFlags & lin->iType) == 0) + if (!bRedraw && m_pDlg.AllowTyping() && m_pDlg.m_bFilterEnabled && (m_pDlg.m_iLogFilterFlags & lin->iType) == 0) return; bool bFlag = false, bDoReplace, bAtBottom = AtBottom(); diff --git a/plugins/TabSRMM/src/msgs.h b/plugins/TabSRMM/src/msgs.h index 9e262e132d..78fb6bfda9 100644 --- a/plugins/TabSRMM/src/msgs.h +++ b/plugins/TabSRMM/src/msgs.h @@ -58,6 +58,74 @@ #define MWF_LOG_INOUTICONS 0x10000000 #define MWF_LOG_GROUPMODE 0x80000000 + /* + * custom dialog window messages + */ + +#define TM_USER (WM_USER+300) + +#define EM_SEARCHSCROLLER (TM_USER+0x103) +#define EM_VALIDATEBOTTOM (TM_USER+0x104) +#define EM_THEMECHANGED (TM_USER+0x105) +#define EM_REFRESHWITHOUTCLIP (TM_USER+0x106) + +#define HM_EVENTSENT (TM_USER+10) +#define HM_DBEVENTADDED (TM_USER+12) +#define DM_SETINFOPANEL (TM_USER+13) +#define DM_OPTIONSAPPLIED (TM_USER+14) +#define DM_SPLITSENDACK (TM_USER+19) +#define DM_UPDATEWINICON (TM_USER+21) +#define DM_UPDATELASTMESSAGE (TM_USER+22) + +#define DM_STATUSICONCHANGE (TM_USER+25) +#define DM_CREATECONTAINER (TM_USER+26) +#define DM_QUERYLASTUNREAD (TM_USER+28) +#define DM_UPDATEPICLAYOUT (TM_USER+30) +#define DM_APPENDMCEVENT (TM_USER+34) +#define DM_CHECKINFOTIP (TM_USER+35) +#define DM_SAVESIZE (TM_USER+36) +#define DM_CHECKSIZE (TM_USER+37) +#define DM_FORCEREDRAW (TM_USER+38) +#define DM_QUERYHCONTACT (TM_USER+41) +#define DM_STATUSMASKSET (TM_USER+51) +#define DM_UPDATESTATUSMSG (TM_USER+53) +#define DM_OWNNICKCHANGED (TM_USER+55) +#define DM_CONFIGURETOOLBAR (TM_USER+56) +#define DM_FORCEDREMAKELOG (TM_USER+62) +#define DM_STATUSBARCHANGED (TM_USER+64) +#define DM_CHECKQUEUEFORCLOSE (TM_USER+70) +#define DM_CHECKAUTOHIDE (TM_USER+71) +#define DM_HANDLECLISTEVENT (TM_USER+73) +#define DM_REMOVECLISTEVENT (TM_USER+75) +#define DM_DOCREATETAB (TM_USER+77) +#define DM_SMILEYOPTIONSCHANGED (TM_USER+85) +#define DM_MYAVATARCHANGED (TM_USER+86) +#define DM_IEVIEWOPTIONSCHANGED (TM_USER+88) +#define DM_SPLITTERGLOBALEVENT (TM_USER+89) +#define DM_CLIENTCHANGED (TM_USER+91) +#define DM_SENDMESSAGECOMMANDW (TM_USER+93) +#define DM_LOGSTATUSCHANGE (TM_USER+98) +#define DM_SC_BUILDLIST (TM_USER+100) +#define DM_SC_INITDIALOG (TM_USER+101) +#define DM_SC_CONFIG (TM_USER+104) +#define DM_UPDATEUIN (TM_USER+103) + +#define MINSPLITTERX 60 +#define MINSPLITTERY 42 +#define MINLOGHEIGHT 30 +#define ERRORPANEL_HEIGHT 51 + +// wParam values for DM_SELECTTAB + +#define DM_SELECT_NEXT 1 +#define DM_SELECT_PREV 2 + +#define DM_SELECT_BY_HWND 3 // lParam specifies hwnd +#define DM_SELECT_BY_INDEX 4 // lParam specifies tab index + +#define DM_QUERY_NEXT 1 +#define DM_QUERY_MOSTRECENT 2 + #define SMODE_DEFAULT 0 #define SMODE_MULTIPLE 1 #define SMODE_CONTAINER 2 @@ -420,8 +488,6 @@ class CMsgDialog : public CSrmmBaseDialog RECT m_rcNick, m_rcUIN, m_rcStatus, m_rcPic; int m_originalSplitterY; SIZE m_minEditBoxSize; - int m_nTypeMode; - DWORD m_nLastTyping; DWORD m_lastMessage; DWORD m_dwTickLastEvent; HBITMAP m_hOwnPic; @@ -468,7 +534,7 @@ public: char *m_szProto; int m_iTabID; int m_iLogMode; - BYTE m_bShowTyping; + bool m_bIsHistory, m_bNotOnList, m_bIsIdle; bool m_bActualHistory; bool m_bIsAutosizingInput; @@ -495,7 +561,6 @@ public: MEVENT *m_hHistoryEvents; time_t m_lastEventTime; int m_iLastEventType; - int m_nTypeSecs; int m_iOpenJobs; int m_iInputAreaHeight = -1; int m_maxHistory, m_curHistory; @@ -589,6 +654,13 @@ public: m_pContainer->ActivateExistingTab(this); } + __forceinline void ClearTyping() { + m_nTypeSecs = 0; + m_bShowTyping = 0; + m_wszStatusBar[0] = 0; + PostMessage(m_hwnd, DM_UPDATELASTMESSAGE, 0, 0); + } + __forceinline CLogWindow* LOG() { return ((CLogWindow *)m_pLog); } @@ -704,74 +776,6 @@ struct TIconDescW #define MWF_LOG_DEFAULT (MWF_LOG_GROUPMODE | MWF_LOG_SHOWTIME | MWF_LOG_NORMALTEMPLATES | MWF_LOG_SHOWDATES | MWF_LOG_SYMBOLS | MWF_LOG_GRID | MWF_LOG_INOUTICONS) -/* - * custom dialog window messages - */ - -#define TM_USER (WM_USER+300) - -#define EM_SEARCHSCROLLER (TM_USER+0x103) -#define EM_VALIDATEBOTTOM (TM_USER+0x104) -#define EM_THEMECHANGED (TM_USER+0x105) -#define EM_REFRESHWITHOUTCLIP (TM_USER+0x106) - -#define HM_EVENTSENT (TM_USER+10) -#define HM_DBEVENTADDED (TM_USER+12) -#define DM_SETINFOPANEL (TM_USER+13) -#define DM_OPTIONSAPPLIED (TM_USER+14) -#define DM_SPLITSENDACK (TM_USER+19) -#define DM_UPDATEWINICON (TM_USER+21) -#define DM_UPDATELASTMESSAGE (TM_USER+22) - -#define DM_STATUSICONCHANGE (TM_USER+25) -#define DM_CREATECONTAINER (TM_USER+26) -#define DM_QUERYLASTUNREAD (TM_USER+28) -#define DM_UPDATEPICLAYOUT (TM_USER+30) -#define DM_APPENDMCEVENT (TM_USER+34) -#define DM_CHECKINFOTIP (TM_USER+35) -#define DM_SAVESIZE (TM_USER+36) -#define DM_CHECKSIZE (TM_USER+37) -#define DM_FORCEREDRAW (TM_USER+38) -#define DM_QUERYHCONTACT (TM_USER+41) -#define DM_STATUSMASKSET (TM_USER+51) -#define DM_UPDATESTATUSMSG (TM_USER+53) -#define DM_OWNNICKCHANGED (TM_USER+55) -#define DM_CONFIGURETOOLBAR (TM_USER+56) -#define DM_FORCEDREMAKELOG (TM_USER+62) -#define DM_STATUSBARCHANGED (TM_USER+64) -#define DM_CHECKQUEUEFORCLOSE (TM_USER+70) -#define DM_CHECKAUTOHIDE (TM_USER+71) -#define DM_HANDLECLISTEVENT (TM_USER+73) -#define DM_REMOVECLISTEVENT (TM_USER+75) -#define DM_DOCREATETAB (TM_USER+77) -#define DM_SMILEYOPTIONSCHANGED (TM_USER+85) -#define DM_MYAVATARCHANGED (TM_USER+86) -#define DM_IEVIEWOPTIONSCHANGED (TM_USER+88) -#define DM_SPLITTERGLOBALEVENT (TM_USER+89) -#define DM_CLIENTCHANGED (TM_USER+91) -#define DM_SENDMESSAGECOMMANDW (TM_USER+93) -#define DM_LOGSTATUSCHANGE (TM_USER+98) -#define DM_SC_BUILDLIST (TM_USER+100) -#define DM_SC_INITDIALOG (TM_USER+101) -#define DM_SC_CONFIG (TM_USER+104) -#define DM_UPDATEUIN (TM_USER+103) - -#define MINSPLITTERX 60 -#define MINSPLITTERY 42 -#define MINLOGHEIGHT 30 -#define ERRORPANEL_HEIGHT 51 - -// wParam values for DM_SELECTTAB - -#define DM_SELECT_NEXT 1 -#define DM_SELECT_PREV 2 - -#define DM_SELECT_BY_HWND 3 // lParam specifies hwnd -#define DM_SELECT_BY_INDEX 4 // lParam specifies tab index - -#define DM_QUERY_NEXT 1 -#define DM_QUERY_MOSTRECENT 2 - // implement a callback for the rich edit. Without it, no bitmaps // can be added to the richedit control. // this class has to implement the GetNewStorage() method diff --git a/protocols/Discord/src/dispatch.cpp b/protocols/Discord/src/dispatch.cpp index fee35b6c06..45fba1fb94 100644 --- a/protocols/Discord/src/dispatch.cpp +++ b/protocols/Discord/src/dispatch.cpp @@ -532,22 +532,32 @@ void CDiscordProto::OnCommandReady(const JSONNode &pRoot) void CDiscordProto::OnCommandTyping(const JSONNode &pRoot) { - SnowFlake userId = ::getId(pRoot["user_id"]); SnowFlake channelId = ::getId(pRoot["channel_id"]); - debugLogA("user typing notification: userid=%lld, channelid=%lld", userId, channelId); + debugLogA("user typing notification: channelid=%lld", channelId); - CDiscordUser *pUser = FindUser(userId); - if (pUser == nullptr) { - debugLogA("user with id=%lld is not found", userId); + CDiscordUser *pChannel = FindUserByChannel(channelId); + if (pChannel == nullptr) { + debugLogA("channel with id=%lld is not found", channelId); return; } - if (pUser->channelId == channelId) { - debugLogA("user is typing in his private channel"); - CallService(MS_PROTO_CONTACTISTYPING, pUser->hContact, 20); + // both private groupchats & guild channels are chat rooms for Miranda + if (pChannel->pGuild) { + debugLogA("user is typing in a group channel"); + + CMStringW wszUerId = pRoot["user_id"].as_mstring(); + ProcessGuildUser(pChannel->pGuild, pRoot); // never returns null + + GCEVENT gce = { m_szModuleName, 0, GC_EVENT_TYPING }; + gce.pszID.w = pChannel->wszUsername; + gce.pszUID.w = wszUerId; + gce.dwItemData = 1; + gce.time = time(0); + Chat_Event(&gce); } else { - debugLogA("user is typing in a group channel, skipped"); + debugLogA("user is typing in his private channel"); + CallService(MS_PROTO_CONTACTISTYPING, pChannel->hContact, 20); } } diff --git a/protocols/Discord/src/groupchat.cpp b/protocols/Discord/src/groupchat.cpp index 8c68d1276f..4e4c69e949 100644 --- a/protocols/Discord/src/groupchat.cpp +++ b/protocols/Discord/src/groupchat.cpp @@ -225,6 +225,10 @@ int CDiscordProto::GroupchatEventHook(WPARAM, LPARAM lParam) case GC_USER_NICKLISTMENU: Chat_ProcessNickMenu(gch); break; + + case GC_USER_TYPNOTIFY: + UserIsTyping(gch->si->hContact, (int)gch->dwData); + break; } return 1; diff --git a/src/core/stdmsg/src/msgdialog.cpp b/src/core/stdmsg/src/msgdialog.cpp index a115908a84..12746815e4 100644 --- a/src/core/stdmsg/src/msgdialog.cpp +++ b/src/core/stdmsg/src/msgdialog.cpp @@ -442,11 +442,11 @@ void CMsgDialog::OnType(CTimer*) } else { if (m_nTypeSecs) { - wchar_t szBuf[256]; - wchar_t *szContactName = Clist_GetContactDisplayName(m_hContact); HICON hTyping = Skin_LoadIcon(SKINICON_OTHER_TYPING); - mir_snwprintf(szBuf, TranslateT("%s is typing a message..."), szContactName); + wchar_t szBuf[256]; + mir_snwprintf(szBuf, TranslateT("%s is typing a message..."), + (m_pUserTyping) ? m_pUserTyping->pszNick : Clist_GetContactDisplayName(m_hContact)); m_nTypeSecs--; SendMessage(m_pOwner->m_hwndStatus, SB_SETTEXT, 0, (LPARAM)szBuf); @@ -1427,28 +1427,33 @@ void CMsgDialog::NotifyTyping(int mode) if (!m_szProto) return; - int protoStatus = Proto_GetStatus(m_szProto); - DWORD protoCaps = CallProtoService(m_szProto, PS_GETCAPS, PFLAGNUM_1, 0); DWORD typeCaps = CallProtoService(m_szProto, PS_GETCAPS, PFLAGNUM_4, 0); - if (!(typeCaps & PF4_SUPPORTTYPING)) return; + int protoStatus = Proto_GetStatus(m_szProto); if (protoStatus < ID_STATUS_ONLINE) return; - if (protoCaps & PF1_VISLIST && db_get_w(m_hContact, m_szProto, "ApparentMode", 0) == ID_STATUS_OFFLINE) - return; + if (isChat()) { + m_nTypeMode = mode; + Chat_DoEventHook(m_si, GC_USER_TYPNOTIFY, 0, 0, m_nTypeMode); + } + else { + DWORD protoCaps = CallProtoService(m_szProto, PS_GETCAPS, PFLAGNUM_1, 0); + if (protoCaps & PF1_VISLIST && db_get_w(m_hContact, m_szProto, "ApparentMode", 0) == ID_STATUS_OFFLINE) + return; - if (protoCaps & PF1_INVISLIST && protoStatus == ID_STATUS_INVISIBLE && db_get_w(m_hContact, m_szProto, "ApparentMode", 0) != ID_STATUS_ONLINE) - return; + if (protoCaps & PF1_INVISLIST && protoStatus == ID_STATUS_INVISIBLE && db_get_w(m_hContact, m_szProto, "ApparentMode", 0) != ID_STATUS_ONLINE) + return; - if (!g_dat.bTypingUnknown && !Contact_OnList(m_hContact)) - return; + if (!g_dat.bTypingUnknown && !Contact_OnList(m_hContact)) + return; - // End user check - m_nTypeMode = mode; - CallService(MS_PROTO_SELFISTYPING, m_hContact, m_nTypeMode); + // End user check + m_nTypeMode = mode; + CallService(MS_PROTO_SELFISTYPING, m_hContact, m_nTypeMode); + } } void CMsgDialog::RemakeLog() @@ -1679,5 +1684,5 @@ void CMsgDialog::UpdateTitle() void CMsgDialog::UserTyping(int nSecs) { - m_nTypeSecs = (nSecs > 0) ? nSecs : 0; + setTyping((nSecs > 0) ? nSecs : 0); } diff --git a/src/core/stdmsg/src/msgs.h b/src/core/stdmsg/src/msgs.h index a2c4f226cc..b5ddbf823d 100644 --- a/src/core/stdmsg/src/msgs.h +++ b/src/core/stdmsg/src/msgs.h @@ -94,14 +94,12 @@ class CMsgDialog : public CSrmmBaseDialog HFONT m_hFont = nullptr; - int m_nTypeSecs = 0, m_nTypeMode = 0; int m_limitAvatarH = 0; - DWORD m_nLastTyping = 0; DWORD m_lastMessage = 0; HANDLE m_hTimeZone = 0; WORD m_wStatus = ID_STATUS_OFFLINE, m_wOldStatus = ID_STATUS_OFFLINE; WORD m_wMinute = 0; - bool m_bIsMeta = false, m_bShowTyping = false, m_bWindowCascaded = false, m_bNoActivate = false; + bool m_bIsMeta = false, m_bWindowCascaded = false, m_bNoActivate = false; public: CMsgDialog(CTabbedWindow *pOwner, MCONTACT hContact); diff --git a/src/mir_app/src/chat.h b/src/mir_app/src/chat.h index 3d158e5beb..37aac65bb0 100644 --- a/src/mir_app/src/chat.h +++ b/src/mir_app/src/chat.h @@ -73,6 +73,7 @@ BOOL SM_SetContactStatus(const wchar_t *pszID, const char *pszModule, c BOOL SM_SetOffline(const char *pszModule, SESSION_INFO *si); BOOL SM_SetStatus(const char *pszModule, SESSION_INFO *si, int wStatus); BOOL SM_TakeStatus(const wchar_t *pszID, const char *pszModule, const wchar_t *pszUID, const wchar_t *pszStatus); +BOOL SM_UserTyping(GCEVENT* gce); SESSION_INFO* SM_FindSession(const wchar_t *pszID, const char *pszModule); SESSION_INFO* SM_FindSessionByIndex(const char *pszModule, int iItem); diff --git a/src/mir_app/src/chat_manager.cpp b/src/mir_app/src/chat_manager.cpp index f2bb050eb9..a815e2732b 100644 --- a/src/mir_app/src/chat_manager.cpp +++ b/src/mir_app/src/chat_manager.cpp @@ -424,6 +424,20 @@ BOOL SM_SetStatus(const char *pszModule, SESSION_INFO *si, int wStatus) return TRUE; } +BOOL SM_UserTyping(GCEVENT *gce) +{ + SESSION_INFO *si = SM_FindSession(gce->pszID.w, gce->pszModule); + if (si == nullptr || si->pDlg == nullptr) + return FALSE; + + USERINFO* ui = UM_FindUser(si, gce->pszUID.w); + if (ui == nullptr) + return FALSE; + + si->pDlg->setTyping(10, ui); + return TRUE; +} + BOOL SM_ChangeNick(const wchar_t *pszID, const char *pszModule, GCEVENT *gce) { if (!pszModule) diff --git a/src/mir_app/src/chat_svc.cpp b/src/mir_app/src/chat_svc.cpp index c831ea9c7e..b28b31d15a 100644 --- a/src/mir_app/src/chat_svc.cpp +++ b/src/mir_app/src/chat_svc.cpp @@ -493,6 +493,9 @@ static INT_PTR CALLBACK sttEventStub(void *_param) bIsHighlighted = g_chatApi.IsHighlighted(nullptr, &gce); break; + case GC_EVENT_TYPING: + return SM_UserTyping(&gce); + case GC_EVENT_JOIN: AddUser(&gce); bIsHighlighted = g_chatApi.IsHighlighted(nullptr, &gce); diff --git a/src/mir_app/src/chat_tools.cpp b/src/mir_app/src/chat_tools.cpp index f334a128b5..fbad9e5bc6 100644 --- a/src/mir_app/src/chat_tools.cpp +++ b/src/mir_app/src/chat_tools.cpp @@ -637,6 +637,7 @@ BOOL IsEventSupported(int eventType) case GC_EVENT_NOTICE: case GC_EVENT_MESSAGE: case GC_EVENT_TOPIC: + case GC_EVENT_TYPING: case GC_EVENT_INFORMATION: case GC_EVENT_ACTION: case GC_EVENT_ADDSTATUS: diff --git a/src/mir_app/src/mir_app.def b/src/mir_app/src/mir_app.def index cdb53fd277..46b27c22b8 100644 --- a/src/mir_app/src/mir_app.def +++ b/src/mir_app/src/mir_app.def @@ -787,3 +787,4 @@ _Netlib_SslPending@4 @874 NONAME _Netlib_SslRead@16 @875 NONAME _Netlib_SslShutdown@4 @876 NONAME _Netlib_SslWrite@12 @877 NONAME +?AllowTyping@CSrmmBaseDialog@@IBE_NXZ @878 NONAME diff --git a/src/mir_app/src/mir_app64.def b/src/mir_app/src/mir_app64.def index ec9a92f9bb..e78b512abf 100644 --- a/src/mir_app/src/mir_app64.def +++ b/src/mir_app/src/mir_app64.def @@ -787,3 +787,4 @@ Netlib_SslPending @874 NONAME Netlib_SslRead @875 NONAME Netlib_SslShutdown @876 NONAME Netlib_SslWrite @877 NONAME +?AllowTyping@CSrmmBaseDialog@@IEBA_NXZ @878 NONAME diff --git a/src/mir_app/src/srmm_base.cpp b/src/mir_app/src/srmm_base.cpp index 4557c7bbb9..02f0b0b29b 100644 --- a/src/mir_app/src/srmm_base.cpp +++ b/src/mir_app/src/srmm_base.cpp @@ -555,6 +555,11 @@ void CSrmmBaseDialog::AddLog() m_pLog->Clear(); } +bool CSrmmBaseDialog::AllowTyping() const +{ + return isChat() ? m_si->iType != GCW_SERVER : true; +} + void CSrmmBaseDialog::ClearLog() { m_pLog->Clear(); -- cgit v1.2.3