Skip to content

Commit

Permalink
Merged flashEnhancements branch, which adds a virtualBuffer for Adobe…
Browse files Browse the repository at this point in the history
… Flash content, and also changes the key command you should use to jump out of embedded objects in virtualBuffers (such as Flash applets) from nvda+space to nvda+control+space. This merge also slightly improves NVDA's handling of virtualBuffers with in virtualBuffers.

The change to the key command was necessary as Flash now has a virtualBuffer so therefore nvda+space would simply toggle focus/browse modes. 
The adobe Flash virtualBuffer is rather experimental and feedback is requested. Note that object navigation can still be used to navigate flash, and toggling to focus mode will still allow you to easily control your flash applet with direct keyboard input (e.g. using left/right arrow in Youtube flash to seek forward/back in the video).
  • Loading branch information
michaelDCurran committed May 5, 2010
2 parents edb6a2f + 1780128 commit 63997f7
Show file tree
Hide file tree
Showing 13 changed files with 379 additions and 19 deletions.
4 changes: 4 additions & 0 deletions source/NVDAHelper/sconstruct
Expand Up @@ -72,6 +72,10 @@ adobeAcrobatVBufBackend=env.SConscript('vbufBackends/adobeAcrobat/sconscript')
env.Install(libInstallDir,adobeAcrobatVBufBackend)
buildList.append(adobeAcrobatVBufBackend)

adobeFlashVBufBackend=env.SConscript('vbufBackends/adobeFlash/sconscript')
env.Install(libInstallDir,adobeFlashVBufBackend)
buildList.append(adobeFlashVBufBackend)

geckoVBufBackend=env.SConscript('vbufBackends/gecko_ia2/sconscript')
env.Install(libInstallDir,geckoVBufBackend)
buildList.append(geckoVBufBackend)
Expand Down
194 changes: 194 additions & 0 deletions source/NVDAHelper/vbufBackends/adobeFlash/adobeFlash.cpp
@@ -0,0 +1,194 @@
#include <sstream>
#include <cassert>
#include <map>
#include <windows.h>
#include <oleacc.h>
#include <remote/nvdaHelperRemote.h>
#include <vbufBase/backend.h>
#include "adobeFlash.h"

using namespace std;

void AdobeFlashVBufBackend_t::renderThread_initialize() {
registerWinEventHook(renderThread_winEventProcHook);
VBufBackend_t::renderThread_initialize();
}

void AdobeFlashVBufBackend_t::renderThread_terminate() {
unregisterWinEventHook(renderThread_winEventProcHook);
VBufBackend_t::renderThread_terminate();
}

void CALLBACK AdobeFlashVBufBackend_t::renderThread_winEventProcHook(HWINEVENTHOOK hookID, DWORD eventID, HWND hwnd, long objectID, long childID, DWORD threadID, DWORD time) {
switch(eventID) {
case EVENT_OBJECT_REORDER:
case EVENT_OBJECT_NAMECHANGE:
case EVENT_OBJECT_VALUECHANGE:
case EVENT_OBJECT_STATECHANGE:
break;
default:
return;
}

int docHandle=(int)hwnd;
int ID=childID;
VBufBackend_t* backend=NULL;
for(VBufBackendSet_t::iterator i=runningBackends.begin();i!=runningBackends.end();i++) {
HWND rootWindow=(HWND)((*i)->rootDocHandle);
if(rootWindow==hwnd) {
backend=(*i);
}
}
if(!backend) {
return;
}
VBufStorage_controlFieldNode_t* node=backend->getControlFieldNodeWithIdentifier(docHandle,ID);
if(!node) {
return;
}
backend->invalidateSubtree(node);
}

VBufStorage_fieldNode_t* AdobeFlashVBufBackend_t::renderControlContent(VBufStorage_buffer_t* buffer, VBufStorage_controlFieldNode_t* parentNode, VBufStorage_fieldNode_t* previousNode, int docHandle, IAccessible* pacc, long accChildID) {
assert(buffer);

int res;
//all IAccessible methods take a variant for childID, get one ready
VARIANT varChild;
varChild.vt=VT_I4;
varChild.lVal=accChildID;

VBufStorage_fieldNode_t* tempNode=NULL;

int id=accChildID;

//Make sure that we don't already know about this object -- protect from loops
if(buffer->getControlFieldNodeWithIdentifier(docHandle,id)!=NULL) {
return NULL;
}

//Add this node to the buffer
parentNode=buffer->addControlFieldNode(parentNode,previousNode,docHandle,id,TRUE);
assert(parentNode); //new node must have been created
previousNode=NULL;

wostringstream s;

// Get role with accRole
long role = 0;
VARIANT varRole;
VariantInit(&varRole);
if((res=pacc->get_accRole(varChild,&varRole))!=S_OK) {
s<<0;
} else if(varRole.vt==VT_BSTR) {
s << varRole.bstrVal;
} else if(varRole.vt==VT_I4) {
s << varRole.lVal;
role = varRole.lVal;
}
parentNode->addAttribute(L"IAccessible::role",s.str());
VariantClear(&varRole);

// Get states with accState
VARIANT varState;
VariantInit(&varState);
if((res=pacc->get_accState(varChild,&varState))!=S_OK) {
varState.vt=VT_I4;
varState.lVal=0;
}
int states=varState.lVal;
VariantClear(&varState);
//Add each state that is on, as an attrib
for(int i=0;i<32;i++) {
int state=1<<i;
if(state&states) {
s.str(L"");
s<<L"IAccessible::state_"<<state;
parentNode->addAttribute(s.str(),L"1");
}
}

BSTR tempBstr=NULL;
wstring name;
wstring value;
wstring content;

if ((res = pacc->get_accName(varChild, &tempBstr)) == S_OK) {
name = tempBstr;
SysFreeString(tempBstr);
}
if ((res = pacc->get_accValue(varChild, &tempBstr)) == S_OK) {
value = tempBstr;
SysFreeString(tempBstr);
}
if(!value.empty()) {
content=value;
} else if(role!=ROLE_SYSTEM_TEXT&&!name.empty()) {
content=name;
} else if (states & STATE_SYSTEM_FOCUSABLE) {
// This node is focusable, but contains no text.
// Therefore, add it with a space so that the user can get to it.
content = L" ";
}

if (!content.empty()) {
if (tempNode = buffer->addTextFieldNode(parentNode, previousNode, content)) {
previousNode=tempNode;
}
}

return parentNode;
}

void AdobeFlashVBufBackend_t::render(VBufStorage_buffer_t* buffer, int docHandle, int ID, VBufStorage_controlFieldNode_t* oldNode) {
DWORD res=0;
//Get an IAccessible by sending WM_GETOBJECT directly to bypass any proxying, to speed things up.
if(SendMessageTimeout((HWND)docHandle,WM_GETOBJECT,0,OBJID_CLIENT,SMTO_ABORTIFHUNG,2000,&res)==0||res==0) {
//Failed to send message or window does not support IAccessible
return;
}
IAccessible* pacc=NULL;
if(ObjectFromLresult(res,IID_IAccessible,0,(void**)&pacc)!=S_OK) {
//Could not get the IAccessible pointer from the WM_GETOBJECT result
return;
}
assert(pacc); //must get a valid IAccessible object
if(ID==0) {
VBufStorage_controlFieldNode_t* parentNode=buffer->addControlFieldNode(NULL,NULL,docHandle,ID,TRUE);
parentNode->addAttribute(L"IAccessible::role",L"10");
VBufStorage_fieldNode_t* previousNode=NULL;
long childCount=0;
pacc->get_accChildCount(&childCount);
map<pair<pair<long,long>,long>,long> childIDsByLocation;
VARIANT varChild;
varChild.vt=VT_I4;
HRESULT hRes;
for(int i=1;i<1000&&childIDsByLocation.size()<childCount;++i) {
IDispatch* childDisp=NULL;
varChild.lVal=i;
hRes=pacc->get_accChild(varChild,&childDisp);
if(hRes==S_OK) {
childDisp->Release();
long left=0, top=0, width=0, height=0;
if(pacc->accLocation(&left,&top,&width,&height,varChild)!=S_OK) {
left=top=width=height=0;
}
childIDsByLocation[make_pair(make_pair(top+(height/2),left+(width/2)),i)]=i;
}
}
for(map<pair<pair<long,long>,long>,long>::iterator i=childIDsByLocation.begin();i!=childIDsByLocation.end();++i) {
previousNode=this->renderControlContent(buffer,parentNode,previousNode,docHandle,pacc,i->second);
}
} else {
this->renderControlContent(buffer,NULL,NULL,docHandle,pacc,ID);
}
pacc->Release();
}

AdobeFlashVBufBackend_t::AdobeFlashVBufBackend_t(int docHandle, int ID): VBufBackend_t(docHandle,ID) {
}

extern "C" __declspec(dllexport) VBufBackend_t* VBufBackend_create(int docHandle, int ID) {
VBufBackend_t* backend=new AdobeFlashVBufBackend_t(docHandle,ID);
return backend;
}
38 changes: 38 additions & 0 deletions source/NVDAHelper/vbufBackends/adobeFlash/adobeFlash.h
@@ -0,0 +1,38 @@
/**
* backends/adobeFlash/adobeFlash.h
* Part of the NV Virtual Buffer Library
* This library is copyright 2007-2008 NV Virtual Buffer Library Contributors
* This library is licensed under the GNU Lesser General Public Licence. See license.txt which is included with this library, or see
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
*/

#ifndef VIRTUALBUFFER_BACKENDS_MSAATEST_H
#define VIRTUALBUFFER_BACKENDS_MSAATEST_H

#include <set>
#include <vbufBase/backend.h>

class AdobeFlashVBufBackend_t: public VBufBackend_t {
private:

VBufStorage_fieldNode_t* renderControlContent(VBufStorage_buffer_t* buffer, VBufStorage_controlFieldNode_t* parentNode, VBufStorage_fieldNode_t* previousNode, int docHandle, IAccessible* pacc, long accChildID);

protected:

static void CALLBACK renderThread_winEventProcHook(HWINEVENTHOOK hookID, DWORD eventID, HWND hwnd, long objectID, long childID, DWORD threadID, DWORD time);

virtual void renderThread_initialize();

virtual void renderThread_terminate();

virtual void render(VBufStorage_buffer_t* buffer, int docHandle, int ID, VBufStorage_controlFieldNode_t* oldNode);

//virtual ~AdobeFlashVBufBackend_t();

public:

AdobeFlashVBufBackend_t(int docHandle, int ID);

};

#endif
20 changes: 20 additions & 0 deletions source/NVDAHelper/vbufBackends/adobeFlash/sconscript
@@ -0,0 +1,20 @@
Import([
'env',
'vbufBaseStaticLib',
])

adobeFlashBackendLib=env.SharedLibrary(
target="VBufBackend_adobeFlash",
source=[
"adobeFlash.cpp",
],
LIBS=[
vbufBaseStaticLib,
"user32",
"kernel32",
"oleacc",
"oleaut32",
],
)

Return('adobeFlashBackendLib')
2 changes: 1 addition & 1 deletion source/NVDAObjects/IAccessible/__init__.py
Expand Up @@ -338,7 +338,7 @@ def findOverlayClasses(self,clsList):
clsList.append(newCls)

# Some special cases.
if (windowClassName in ("MozillaWindowClass", "GeckoPluginWindow") and not isinstance(self.IAccessibleObject, IAccessibleHandler.IAccessible2) and role == oleacc.ROLE_SYSTEM_TEXT) or windowClassName in ("MacromediaFlashPlayerActiveX", "ApolloRuntimeContentWindow", "ShockwaveFlash", "ShockwaveFlashLibrary"):
if (windowClassName in ("MozillaWindowClass", "GeckoPluginWindow") and not isinstance(self.IAccessibleObject, IAccessibleHandler.IAccessible2)) or windowClassName in ("MacromediaFlashPlayerActiveX", "ApolloRuntimeContentWindow", "ShockwaveFlash", "ShockwaveFlashLibrary"):
# This is possibly a Flash object.
from . import adobeFlash
adobeFlash.findExtraOverlayClasses(self, clsList)
Expand Down
30 changes: 28 additions & 2 deletions source/NVDAObjects/IAccessible/adobeFlash.py
Expand Up @@ -4,11 +4,13 @@
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.

import winUser
import oleacc
from . import IAccessible
from NVDAObjects import NVDAObjectTextInfo
from NVDAObjects.behaviors import EditableTextWithoutAutoSelectDetection
from comtypes import COMError, IServiceProvider, hresult
from comtypes.gen.FlashAccessibility import ISimpleTextSelection
from comtypes.gen.FlashAccessibility import ISimpleTextSelection, IFlashAccessibility
from logHandler import log

class InputTextFieldTextInfo(NVDAObjectTextInfo):
Expand Down Expand Up @@ -40,14 +42,38 @@ def _getSelectionOffsets(self):
class InputTextField(EditableTextWithoutAutoSelectDetection, IAccessible):
TextInfo = InputTextFieldTextInfo

class Root(IAccessible):
def _get_presentationType(self):
return self.presType_content


def _get_virtualBufferClass(self):
import virtualBuffers.adobeFlash
return virtualBuffers.adobeFlash.AdobeFlash

def findExtraOverlayClasses(obj, clsList):
"""Determine the most appropriate class if this is a Flash object.
This works similarly to L{NVDAObjects.NVDAObject.findOverlayClasses} except that it never calls any other findOverlayClasses method.
"""
try:
servProv = obj.IAccessibleObject.QueryInterface(IServiceProvider)
except COMError:
return

# Check whether this is the Flash root accessible.
if obj.IAccessibleRole == oleacc.ROLE_SYSTEM_CLIENT:
try:
servProv.QueryService(IFlashAccessibility._iid_, IFlashAccessibility)
clsList.append(Root)
except COMError:
pass
# If this is a client and IFlashAccessibility wasn't present, this is not a Flash object.
return

# Check whether this is a Flash input text field.
try:
# We have to fetch ISimpleTextSelectionObject in order to check whether this is an input text field, so store it on the instance.
obj.ISimpleTextSelectionObject = obj.IAccessibleObject.QueryInterface(IServiceProvider).QueryService(ISimpleTextSelection._iid_, ISimpleTextSelection)
obj.ISimpleTextSelectionObject = servProv.QueryService(ISimpleTextSelection._iid_, ISimpleTextSelection)
clsList.append(InputTextField)
except COMError:
pass
2 changes: 0 additions & 2 deletions source/api.py
Expand Up @@ -121,8 +121,6 @@ def setFocusObject(obj):
o=None
for o in ancestors[focusDifferenceLevel:]+[obj]:
virtualBufferObject=virtualBufferHandler.update(o)
if virtualBufferObject:
break
#Always make sure that the focus object's virtualBuffer is forced to either the found virtualBuffer (if its in it) or to None
#This is to make sure that the virtualBuffer does not have to be looked up, which can cause problems for winInputHook
if obj is o or virtualBufferObject.isNVDAObjectInVirtualBuffer(obj):
Expand Down
28 changes: 16 additions & 12 deletions source/appModules/_default.py
Expand Up @@ -549,21 +549,25 @@ def script_speechMode(self,keyPress):
speech.speechMode=newMode
script_speechMode.__doc__=_("Toggles between the speech modes of off, beep and talk. When set to off NVDA will not speak anything. If beeps then NVDA will simply beep each time it its supposed to speak something. If talk then NVDA wil just speak normally.")

def _getDocumentForFocusedEmbeddedObject(self):
for ancestor in reversed(api.getFocusAncestors()):
if ancestor.role == controlTypes.ROLE_DOCUMENT:
return ancestor
def script_moveToParentVirtualBuffer(self,keyPress):
obj=api.getFocusObject()
parent=obj.parent
#Move up parents untill the virtualBuffer of the parent is different to the virtualBuffer of the object.
#Note that this could include the situation where the parent has no virtualBuffer but the object did.
while parent and parent.virtualBuffer==obj.virtualBuffer:
parent=parent.parent
#If the parent has no virtualBuffer, keep moving up the parents until we find a parent that does have one.
while parent and not parent.virtualBuffer:
parent=parent.parent
if parent:
parent.virtualBuffer.rootNVDAObject.setFocus()
import eventHandler
eventHandler.executeEvent("gainFocus",parent.virtualBuffer.rootNVDAObject)
script_moveToParentVirtualBuffer.__doc__=_("Moves the focus to the next closest virtualBuffer that contains the focus")

def script_toggleVirtualBufferPassThrough(self,keyPress):
vbuf = api.getFocusObject().virtualBuffer
if not vbuf:
# We might be in an embedded object or application, so try searching the ancestry for an object which can return focus to the document.
docObj = self._getDocumentForFocusedEmbeddedObject()
if not docObj:
return
docObj.setFocus()
return

if not vbuf: return
# Toggle virtual buffer pass-through.
vbuf.passThrough = not vbuf.passThrough
# If we are enabling pass-through, the user has explicitly chosen to do so, so disable auto-pass-through.
Expand Down
1 change: 1 addition & 0 deletions source/appModules/_default_desktop.kbd
Expand Up @@ -83,3 +83,4 @@ NVDA+control+t=braille_toggleTether
NVDA+c=reportClipboardText
NVDA+f9=review_markStartForCopy
NVDA+f10=review_copy
nvda+control+space=moveToParentVirtualBuffer
1 change: 1 addition & 0 deletions source/appModules/_default_laptop.kbd
Expand Up @@ -116,3 +116,4 @@ Control+NVDA+z=activatePythonConsole
NVDA+c=reportClipboardText
NVDA+f9=review_markStartForCopy
NVDA+f10=review_copy
nvda+control+space=moveToParentVirtualBuffer

0 comments on commit 63997f7

Please sign in to comment.