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

No longer require pyWin32 #9639

Merged
merged 5 commits into from Jun 3, 2019
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions nvdaHelper/local/nvdaHelperLocal.def
Expand Up @@ -57,3 +57,4 @@ EXPORTS
dllImportTableHooks_unhookSingle
audioDucking_shouldDelay
logMessage
getOleClipboardText
44 changes: 44 additions & 0 deletions nvdaHelper/local/oleUtils.cpp
@@ -0,0 +1,44 @@
/*
This file is a part of the NVDA project.
URL: http://www.nvda-project.org/
Copyright 2019 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
*/

#include <windows.h>
#include <wtypes.h>
#include <common/log.h>

/*
* Fetches a text representation of the given OLE data object
* @param dataObject an IDataObject interface of an OLE object
* @param text a pointer to a BSTR which will hold the resulting text
* @return S_OK on success or an OLE error code.
*/
HRESULT getOleClipboardText(IDataObject* dataObject, BSTR* text) {
FORMATETC format={CF_UNICODETEXT,nullptr,DVASPECT_CONTENT,-1,TYMED_HGLOBAL};
STGMEDIUM medium={0};
HRESULT res=dataObject->GetData(&format,&medium);
if(res!=S_OK) {
LeonarddeR marked this conversation as resolved.
Show resolved Hide resolved
LOG_DEBUGWARNING(L"IDataObject::getData failed with error "<<res);
return res;
}
if(medium.tymed!=TYMED_HGLOBAL||!medium.hGlobal) {
LOG_DEBUGWARNING(L"Got back invalid medium");
return E_FAIL;
}
LPVOID addr=GlobalLock(medium.hGlobal);
if(addr) {
*text=SysAllocString((wchar_t*)addr);
}
GlobalUnlock(medium.hGlobal);
ReleaseStgMedium(&medium);
return res;
}
1 change: 1 addition & 0 deletions nvdaHelper/local/sconscript
Expand Up @@ -87,6 +87,7 @@ localLib=env.SharedLibrary(
"textUtils.cpp",
"UIAUtils.cpp",
"mixer.cpp",
"oleUtils.cpp",
],
LIBS=[
"advapi32.lib",
Expand Down
17 changes: 5 additions & 12 deletions source/NVDAObjects/window/edit.py
Expand Up @@ -8,11 +8,10 @@
import struct
import ctypes
from comtypes import COMError
import pythoncom
import win32clipboard
import oleTypes
import colors
import globalVars
import NVDAHelper
import eventHandler
import comInterfaces.tom
from logHandler import log
Expand Down Expand Up @@ -579,21 +578,15 @@ def _getEmbeddedObjectLabel(self,embedRangeObj):
except comtypes.COMError:
dataObj=None
if dataObj:
try:
dataObj=pythoncom._univgw.interface(hash(dataObj),pythoncom.IID_IDataObject)
format=(win32clipboard.CF_UNICODETEXT, None, pythoncom.DVASPECT_CONTENT, -1, pythoncom.TYMED_HGLOBAL)
medium=dataObj.GetData(format)
buf=ctypes.create_string_buffer(medium.data)
buf=ctypes.cast(buf,ctypes.c_wchar_p)
label=buf.value
except:
pass
text=comtypes.BSTR()
res=NVDAHelper.localLib.getOleClipboardText(dataObj,ctypes.byref(text));
label=text.value
if label:
return label
# As a final fallback (e.g. could not get display model text for Outlook Express), use the embedded object's user type (e.g. "recipient").
try:
oleObj=o.QueryInterface(oleTypes.IOleObject)
label=oleObj.GetUserType(1)
label="bad" #oleObj.GetUserType(1)
LeonarddeR marked this conversation as resolved.
Show resolved Hide resolved
except comtypes.COMError:
pass
return label
Expand Down
39 changes: 12 additions & 27 deletions source/api.py
Expand Up @@ -6,6 +6,7 @@

"""General functions for NVDA"""

import ctypes
import config
import textInfos
import review
Expand All @@ -18,8 +19,6 @@
import NVDAObjects.IAccessible
import winUser
import controlTypes
import win32clipboard
import win32con
import eventHandler
import braille
import watchdog
Expand Down Expand Up @@ -286,37 +285,23 @@ def copyToClip(text):
@param text: the text which will be copied to the clipboard
@type text: string
"""
if isinstance(text,basestring) and len(text)>0 and not text.isspace():
try:
win32clipboard.OpenClipboard()
except win32clipboard.error:
return False
try:
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardData(win32con.CF_UNICODETEXT, text)
finally:
win32clipboard.CloseClipboard()
win32clipboard.OpenClipboard() # there seems to be a bug so to retrieve unicode text we have to reopen the clipboard
try:
got = win32clipboard.GetClipboardData(win32con.CF_UNICODETEXT)
finally:
win32clipboard.CloseClipboard()
if got == text:
return True
return False
if not isinstance(text,basestring) or len(text)==0:
LeonarddeR marked this conversation as resolved.
Show resolved Hide resolved
return False
import gui
with winUser.openClipboard(gui.mainFrame.Handle):
winUser.emptyClipboard()
winUser.setClipboardData(winUser.CF_UNICODETEXT,text)
got=getClipData()
return got == text

def getClipData():
"""Receives text from the windows clipboard.
@returns: Clipboard text
@rtype: string
"""
text = ""
win32clipboard.OpenClipboard()
try:
text = win32clipboard.GetClipboardData(win32con.CF_UNICODETEXT)
finally:
win32clipboard.CloseClipboard()
return text
import gui
with winUser.openClipboard(gui.mainFrame.Handle):
return winUser.getClipboardData(winUser.CF_UNICODETEXT) or u""

def getStatusBar():
"""Obtain the status bar for the current foreground object.
Expand Down
6 changes: 4 additions & 2 deletions source/bdDetect.py
Expand Up @@ -20,17 +20,19 @@
import hwPortUtils
import braille
import winKernel
import winUser
import core
import ctypes
from logHandler import log
import config
import time
import thread
from win32con import WM_DEVICECHANGE, DBT_DEVNODES_CHANGED
import appModuleHandler
from baseObject import AutoPropertyObject
import re

DBT_DEVNODES_CHANGED=7

_driverDevices = OrderedDict()
USB_ID_REGEX = re.compile(r"^VID_[0-9A-F]{4}&PID_[0-9A-F]{4}$", re.U)

Expand Down Expand Up @@ -295,7 +297,7 @@ def rescan(self, usb=True, bluetooth=True, limitToDevices=None):
self._startBgScan(usb=usb, bluetooth=bluetooth, limitToDevices=limitToDevices)

def handleWindowMessage(self, msg=None, wParam=None):
if msg == WM_DEVICECHANGE and wParam == DBT_DEVNODES_CHANGED:
if msg == winUser.WM_DEVICECHANGE and wParam == DBT_DEVNODES_CHANGED:
self.rescan(bluetooth=self._detectBluetooth, limitToDevices=self._limitToDevices)

def pollBluetoothDevices(self):
Expand Down
2 changes: 1 addition & 1 deletion source/core.py
Expand Up @@ -303,7 +303,7 @@ def onEndSession(evt):
import windowUtils
class MessageWindow(windowUtils.CustomWindow):
className = u"wxWindowClassNR"
#Just define these constants here, so we don't have to import win32con
# Windows constants for power / display changes
WM_POWERBROADCAST = 0x218
WM_DISPLAYCHANGE = 0x7e
PBT_APMPOWERSTATUSCHANGE = 0xA
Expand Down
5 changes: 2 additions & 3 deletions source/nvda.pyw
Expand Up @@ -32,7 +32,6 @@ except:

import time
import argparse
import win32con
import globalVars
import config
import logHandler
Expand Down Expand Up @@ -122,7 +121,7 @@ parser.add_argument('--ease-of-access',action="store_true",dest='easeOfAccess',d

def terminateRunningNVDA(window):
processID,threadID=winUser.getWindowThreadProcessID(window)
winUser.PostMessage(window,win32con.WM_QUIT,0,0)
winUser.PostMessage(window,winUser.WM_QUIT,0,0)
h=winKernel.openProcess(winKernel.SYNCHRONIZE,False,processID)
if not h:
# The process is already dead.
Expand Down Expand Up @@ -219,7 +218,7 @@ log.debug("Debug level logging enabled")
if globalVars.appArgs.changeScreenReaderFlag:
winUser.setSystemScreenReaderFlag(True)
#Accept wm_quit from other processes, even if running with higher privilages
if not ctypes.windll.user32.ChangeWindowMessageFilter(win32con.WM_QUIT,1):
if not ctypes.windll.user32.ChangeWindowMessageFilter(winUser.WM_QUIT,1):
raise WinError()
# Make this the last application to be shut down and don't display a retry dialog box.
winKernel.SetProcessShutdownParameters(0x100, winKernel.SHUTDOWN_NORETRY)
Expand Down
13 changes: 11 additions & 2 deletions source/winInputHook.py
Expand Up @@ -9,8 +9,17 @@
import time
from ctypes import *
from ctypes.wintypes import *
from win32con import WM_QUIT, HC_ACTION, WH_KEYBOARD_LL, LLKHF_UP, LLKHF_EXTENDED, LLKHF_INJECTED, WH_MOUSE_LL, LLMHF_INJECTED
import watchdog
import winUser

# Some Windows constants
HC_ACTION = 0
WH_KEYBOARD_LL = 13
LLKHF_UP = 128
LLKHF_EXTENDED = 1
LLKHF_INJECTED = 16
WH_MOUSE_LL = 14
LLMHF_INJECTED = 1

class KBDLLHOOKSTRUCT(Structure):
_fields_=[
Expand Down Expand Up @@ -97,6 +106,6 @@ def terminate():
raise RuntimeError("winInputHook not running")
hookThreadRefCount-=1
if hookThreadRefCount==0:
windll.user32.PostThreadMessageW(hookThread.ident,WM_QUIT,0,0)
windll.user32.PostThreadMessageW(hookThread.ident,winUser.WM_QUIT,0,0)
hookThread.join()
hookThread=None
28 changes: 28 additions & 0 deletions source/winKernel.py
Expand Up @@ -4,6 +4,7 @@
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.

import contextlib
import ctypes
import ctypes.wintypes
from ctypes import *
Expand Down Expand Up @@ -329,3 +330,30 @@ def DuplicateHandle(sourceProcessHandle, sourceHandle, targetProcessHandle, desi

PAPCFUNC = ctypes.WINFUNCTYPE(None, ctypes.wintypes.ULONG)
THREAD_SET_CONTEXT = 16

GMEM_MOVEABLE=2

class HGLOBAL(HANDLE):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you add a short descriptive doc string here?


def __init__(self,h,autoFree=True):
super(HGLOBAL,self).__init__(h)
self._autoFree=autoFree

def __del__(self):
if self and self._autoFree:
windll.kernel32.GlobalFree(self)

@classmethod
def alloc(cls,flags,size):
h=windll.kernel32.GlobalAlloc(flags,size)
return cls(h)

@contextlib.contextmanager
def lock(self):
try:
yield windll.kernel32.GlobalLock(self)
finally:
windll.kernel32.GlobalUnlock(self)

def forget(self):
self.value=None
56 changes: 56 additions & 0 deletions source/winUser.py
Expand Up @@ -6,8 +6,10 @@

"""Functions that wrap Windows API functions from user32.dll"""

import contextlib
from ctypes import *
from ctypes.wintypes import *
import winKernel

#dll handles
user32=windll.user32
Expand Down Expand Up @@ -95,8 +97,10 @@ class GUITHREADINFO(Structure):
CBS_OWNERDRAWVARIABLE=0x0020
CBS_HASSTRINGS=0x00200
WM_NULL=0
WM_QUIT=18
WM_COPYDATA=74
WM_NOTIFY=78
WM_DEVICECHANGE=537
WM_USER=1024
#PeekMessage
PM_REMOVE=1
Expand Down Expand Up @@ -612,3 +616,55 @@ def SendInput(inputs):
n = len(inputs)
arr = (Input * n)(*inputs)
user32.SendInput(n, arr, sizeof(Input))

# Windows Clipboard format constants
CF_UNICODETEXT = 13

@contextlib.contextmanager
def openClipboard(hwndOwner=None):
"""
A context manager version of OpenClipboard from user32.
Use as the expression of a 'with' statement, and CloseClipboard will automatically be called at the end.
"""
if not windll.user32.OpenClipboard(hwndOwner):
raise RuntimeError("OpenClipboard failed")
LeonarddeR marked this conversation as resolved.
Show resolved Hide resolved
try:
yield
finally:
windll.user32.CloseClipboard()

def emptyClipboard():
windll.user32.EmptyClipboard()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
windll.user32.EmptyClipboard()
if not windll.user32.EmptyClipboard():
raise ctypes.WinError()


def getClipboardData(format):
# We only support unicode text for now
if format!=CF_UNICODETEXT:
raise ValueError("Unsupported format")
# Fetch the data from the clipboard as a global memory handle
h=windll.user32.GetClipboardData(format)
if h:
LeonarddeR marked this conversation as resolved.
Show resolved Hide resolved
# Lock the global memory while we fetch the unicode string
# But make sure not to free the memory accidentally -- it is not ours
h=winKernel.HGLOBAL(h,autoFree=False)
with h.lock() as addr:
if addr:
Copy link
Collaborator

Choose a reason for hiding this comment

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

What happens if not addr?

# Read the string from the local memory address
return wstring_at(addr)

def setClipboardData(format,data):
# For now only unicode is a supported format
if format!=CF_UNICODETEXT:
raise ValueError("Unsupported format")
text=unicode(data)
LeonarddeR marked this conversation as resolved.
Show resolved Hide resolved
# Allocate global memory
h=winKernel.HGLOBAL.alloc(winKernel.GMEM_MOVEABLE,(len(text)+1)*2)
# Acquire a lock to the global memory receiving a local memory address
with h.lock() as addr:
LeonarddeR marked this conversation as resolved.
Show resolved Hide resolved
# Write the text into the allocated memory
bufLen=len(text)+1
buf=(c_wchar*bufLen).from_address(addr)
buf.value=text
# Set the clipboard data with the global memory
windll.user32.SetClipboardData(format,h)
LeonarddeR marked this conversation as resolved.
Show resolved Hide resolved
# NULL the global memory handle so that it is not freed at the end of scope as the clipboard now has it.
h.forget()