-
-
Notifications
You must be signed in to change notification settings - Fork 615
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
Changes from 10 commits
a290640
13cf35c
3fd8fd2
ada9b18
663158e
da9fb4c
c8c2d8c
aaf0c1c
4d74915
7015b63
150970d
a71adc1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
---|---|---|
@@ -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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than having |
||
// 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)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.