Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Outlook: inProcess implementation to report replied / replied all / forwarded status on mail items in the message list #8756

Merged
merged 12 commits into from
Sep 19, 2018
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
57 changes: 57 additions & 0 deletions nvdaHelper/common/libraryLoader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
This file is a part of the NVDA project.
Copyright 2018 NV Access Limited.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2.0, as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
This license can be found at:
http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/

#pragma once

#include <windows.h>

// A Smart Library handle.
// Construct it with a handle returned by LoadLibrary or similar.
// Once the object goes out of scope, FreeLibrary will automatically be called on the handle.
class CLoadedLibrary {
private:
HMODULE _hModule {nullptr};
CLoadedLibrary(const CLoadedLibrary&)=delete;
const CLoadedLibrary& operator=(const CLoadedLibrary&)=delete;

public:

CLoadedLibrary(HMODULE h): _hModule(h) {};

void free() {
if(_hModule) {
FreeLibrary(_hModule);
_hModule=nullptr;
}
}

CLoadedLibrary& operator=(HMODULE h) {
free();
_hModule=h;
return *this;
}

operator HMODULE() {
return _hModule;
}

operator bool() {
return static_cast<bool>(_hModule);
}

~CLoadedLibrary() {
free();
}

};

Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ interface NvdaInProcUtils {
[fault_status,comm_status] getActiveObject();
[fault_status,comm_status] dumpOnCrash();
[fault_status,comm_status] IA2Text_findContentDescendant();
[fault_status,comm_status] outlook_getMAPIProp();
}
13 changes: 12 additions & 1 deletion nvdaHelper/interfaces/nvdaInProcUtils/nvdaInProcUtils.idl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
This file is a part of the NVDA project.
URL: http://www.nvda-project.org/
Copyright 2006-2010 NVDA contributers.
Copyright 2006-2018 NVDA contributers.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2.0, as published by
the Free Software Foundation.
Expand Down Expand Up @@ -57,4 +57,15 @@ interface NvdaInProcUtils {

error_status_t IA2Text_findContentDescendant([in] handle_t bindingHandle, [in] const unsigned long hwnd, [in] long parentID, [in] long what, [out] long* descendantID, [out] long* descendantOffset);

/*
* Fetches the requested property from the given MAPIProp object.
* @param threadID the threadID of the Outlook GUI thread where the MAPI object came from.
* @param mapiObject the IUnknown interface of the MAPI object of a mail item in Outlook.
* Fetch it from the Outlook object model with something like Something like application.activeExplorer().selection.item(1).mapiObject
* @param mapiPropTag the property tag for the requested property.
* In the form 0xAAAABBBB (where AAAA is the tag and BBBB is the type).
* @param value a pointer to a VARIANT that will hold the result.
*/
error_status_t outlook_getMAPIProp(const long threadID, [in] IUnknown* mapiObject, const unsigned long mapiPropTag, [out] VARIANT* val);

}
1 change: 1 addition & 0 deletions nvdaHelper/local/nvdaHelperLocal.def
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ EXPORTS
nvdaInProcUtils_winword_expandToLine
nvdaInProcUtils_winword_getTextInRange
nvdaInProcUtils_winword_moveByLine
nvdaInProcUtils_outlook_getMAPIProp
VBuf_createBuffer
VBuf_destroyBuffer
VBuf_findNodeByAttributes
Expand Down
2 changes: 1 addition & 1 deletion nvdaHelper/remote/inProcess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ bool unregisterWindowsHook(int hookType, HOOKPROC hookProc) {

//GetMessage hook callback
LRESULT CALLBACK inProcess_getMessageHook(int code, WPARAM wParam, LPARAM lParam) {
if(code<0||wParam==PM_NOREMOVE) {
if(code<0||!(wParam&PM_REMOVE)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this equal to
if(code<0||wParam~PM_REMOVE) {
I consider that to be somewhat more readble, but that's just a personal preference.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

~ in c++ is a unary operator I think. So it would have to be wParam&~PM_REMOVE.

return CallNextHookEx(0,code,wParam,lParam);
}
MSG* pmsg=(MSG*)lParam;
Expand Down
122 changes: 122 additions & 0 deletions nvdaHelper/remote/outlook.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
This file is a part of the NVDA project.
Copyright 2018 NV Access Limited.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2.0, as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
This license can be found at:
http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/

#define WIN32_LEAN_AND_MEAN

#include <memory>
#include <comdef.h>
#include <windows.h>
#include <common/log.h>
#include <common/libraryLoader.h>
#include "inProcess.h"
#include "nvdaInProcUtils.h"

// The following declarations come from MAPIDEFS.h which is no longer included in the Windows SDK

constexpr ULONG PT_LONG=3;
constexpr ULONG MAPI_E_NOTFOUND=0x8004010f;

typedef struct {
ULONG ulPropTag;
ULONG dwAlignPad;
union {
long l;
// other types removed
} Value;
} SPropValue;

using funcType_HrGetOneProp=HRESULT(STDAPICALLTYPE *)(IUnknown*,ULONG,SPropValue**);
using funcType_MAPIFreeBuffer=ULONG(STDAPICALLTYPE *)(SPropValue*);

// Our RPC function
error_status_t nvdaInProcUtils_outlook_getMAPIProp(handle_t bindingHandle, const long threadID, IUnknown* mapiObject, const unsigned long mapiPropTag, VARIANT* retVal) {
if(!mapiObject) {
LOG_ERROR(L"NULL MAPI object");
return E_INVALIDARG;
}
if((mapiPropTag&0xffff)!=PT_LONG) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than having 0xffff hard coded here, it might be better to give it a name. This ties in with the comment in nvdaInProcUtils right? Perhaps this could be called tagMask

// Right now this function only supports MAPI properties with a type of long.
// To support more types, we would need to know how to correctly pack them into a VARIANT.
LOG_ERROR(L"Unsupported MAPI prop type");
return E_INVALIDARG;
}
// Load mapi32 and manually lookup the functions we need.
CLoadedLibrary mapi32lib=LoadLibrary(L"mapi32.dll");
if(!mapi32lib) {
LOG_ERROR(L"Could not load mapi32.dll");
return E_UNEXPECTED;
}
auto HrGetOneProp=(funcType_HrGetOneProp)GetProcAddress(mapi32lib,"HrGetOneProp");
if(!HrGetOneProp) {
// Some versions of mapi32.dll name the HrGetOneProp symbol with an arguments size suffix
HrGetOneProp=(funcType_HrGetOneProp)GetProcAddress(mapi32lib,"HrGetOneProp@12");
}
if(!HrGetOneProp) {
LOG_ERROR(L"Could not locate function HrGetOneProp in mapi32.dll");
return E_UNEXPECTED;
}
auto MAPIFreeBuffer=(funcType_MAPIFreeBuffer)GetProcAddress(mapi32lib,"MAPIFreeBuffer");
if(!MAPIFreeBuffer) {
LOG_ERROR(L"Could not locate function MAPIFreeBuffer in mapi32.dll");
return E_UNEXPECTED;
}
// NVDA gave us an IUnknown pointer representing the MAPI object from Outlook.
// As the MAPIProp interface is not marshallable, we need to access it from its original STA thread as a real (non-proxied) raw pointer.
// Therefore register the IUnknown in the COM global interface table so we can unmarshal it in the main GUI thread.
IGlobalInterfaceTablePtr pGIT;
HRESULT res=pGIT.CreateInstance(CLSID_StdGlobalInterfaceTable);
if(res!=S_OK) {
LOG_ERROR(L"Could not create global interface table");
return res;
}
DWORD cookie=0;
res=pGIT->RegisterInterfaceInGlobal(mapiObject,IID_IUnknown,&cookie);
if(res!=S_OK) {
LOG_ERROR(L"Could not register object in global interface table");
return res;
}
// Execute the following code in Outlook's GUI thread.
execInThread(threadID,[=,&res](){
// Unmarshal the IUnknown pointer from the COM global interface table.
IUnknownPtr mapiObject=nullptr;
res=pGIT->GetInterfaceFromGlobal(cookie,IID_IUnknown,(void**)static_cast<IUnknown**>(&mapiObject));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(void**)static_cast<IUnknown**>(&mapiObject) looks wrong to me. Why cast to IUnknown** and then c-style cast to void**

if(res!=S_OK) {
LOG_ERROR(L"Could not unmarshal object, code "<<res);
return;
}
// Fetch the wanted property from the MAPI object
std::unique_ptr<SPropValue,funcType_MAPIFreeBuffer> propValue {nullptr,MAPIFreeBuffer};
{
SPropValue* _propValue=nullptr;
res=HrGetOneProp(mapiObject,mapiPropTag,&_propValue);
propValue.reset(_propValue);
}
if(res!=S_OK) {
// We should be quiet about the error where the property does not exist as this happens most of the time.
if(res!=MAPI_E_NOTFOUND) LOG_ERROR(L"Could not fetch MAPI property, code "<<res);
return;
}
if(!propValue) {
LOG_ERROR(L"NULL property value");
res=E_UNEXPECTED;
return;
}
// Pack the property value into the VARIANT for returning.
// We can assume here that the type is long and nothing else.
retVal->vt=VT_I4;
retVal->lVal=propValue->Value.l;
});
// Unregister the IUnknown from the COM global interface table as we don't need it anymore.
pGIT->RevokeInterfaceFromGlobal(cookie);
return res;
}
1 change: 1 addition & 0 deletions nvdaHelper/remote/sconscript
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ remoteLib=env.SharedLibrary(
"sysListView32.cpp",
"winword.cpp",
"WinWord/Fields.cpp",
"outlook.cpp",
"gdiHooks.cpp",
"displayModel.cpp",
"displayModelRemote.cpp",
Expand Down
29 changes: 29 additions & 0 deletions source/appModules/outlook.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
#See the file COPYING for more details.

from comtypes import COMError
from comtypes.hresult import S_OK
import comtypes.client
import comtypes.automation
import ctypes
from hwPortUtils import SYSTEMTIME
import scriptHandler
import winKernel
import comHelper
import NVDAHelper
import winUser
from logHandler import log
import textInfos
Expand All @@ -30,6 +34,20 @@
from NVDAObjects.behaviors import RowWithFakeNavigation, Dialog
from NVDAObjects.UIA import UIA

PR_LAST_VERB_EXECUTED=0x10810003
VERB_REPLYTOSENDER=102
VERB_REPLYTOALL=103
VERB_FORWARD=104
executedVerbLabels={
# Translators: the last action taken on an Outlook mail message
VERB_REPLYTOSENDER:_("replied"),
# Translators: the last action taken on an Outlook mail message
VERB_REPLYTOALL:_("REPLIED ALL"),
# Translators: the last action taken on an Outlook mail message
VERB_FORWARD:_("forwarded"),
}


#: The number of seconds in a day, used to make all day appointments and selections less verbose.
#: Type: float
SECONDS_PER_DAY = 86400.0
Expand Down Expand Up @@ -419,6 +437,17 @@ def _get_name(self):
unread=False
# Translators: when an email is unread
if unread: textList.append(_("unread"))
try:
mapiObject=selection.mapiObject
except COMError:
mapiObject=None
if mapiObject:
v=comtypes.automation.VARIANT()
res=NVDAHelper.localLib.nvdaInProcUtils_outlook_getMAPIProp(self.appModule.helperLocalBindingHandle,self.windowThreadID,mapiObject,PR_LAST_VERB_EXECUTED,ctypes.byref(v))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, since this line is quite long, please split the arguments onto multiple lines.

if res==S_OK:
verbLabel=executedVerbLabels.get(v.value,None)
if verbLabel:
textList.append(verbLabel)
try:
attachmentCount=selection.attachments.count
except COMError:
Expand Down