Skip to content

Commit

Permalink
Incubates #7732
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelDCurran committed Dec 11, 2017
2 parents 4f6fe6f + 450dcb4 commit a2a83c5
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 20 deletions.
51 changes: 50 additions & 1 deletion source/braille.py
Expand Up @@ -1614,7 +1614,7 @@ def _writeCells(self, cells):
# we just replace the data.
# This means that if multiple writes occur while an earlier write is still in progress,
# we skip all but the last.
if not alreadyQueued:
if not alreadyQueued and not self.display._awaitingAck:
# Queue a call to the background thread.
_BgThread.queueApc(_BgThread.executor)

Expand Down Expand Up @@ -1864,6 +1864,7 @@ def start(cls):
thread.daemon = True
thread.start()
cls.handle = ctypes.windll.kernel32.OpenThread(winKernel.THREAD_SET_CONTEXT, False, thread.ident)
cls.ackTimerHandle = winKernel.createWaitableTimer()

@classmethod
def queueApc(cls, func):
Expand All @@ -1874,6 +1875,10 @@ def stop(cls):
if not cls.thread:
return
cls.exit = True
if not ctypes.windll.kernel32.CancelWaitableTimer(cls.ackTimerHandle):
raise ctypes.WinError()
winKernel.closeHandle(cls.ackTimerHandle)
cls.ackTimerHandle = None
# Wake up the thread. It will exit when it sees exit is True.
cls.queueApc(cls.executor)
cls.thread.join()
Expand All @@ -1887,6 +1892,9 @@ def executor(param):
if _BgThread.exit:
# func will see this and exit.
return
if handler.display._awaitingAck:
# Do not write cells when we are awaiting an ACK
return
with _BgThread.queuedWriteLock:
data = _BgThread.queuedWrite
_BgThread.queuedWrite = None
Expand All @@ -1897,6 +1905,22 @@ def executor(param):
except:
log.error("Error displaying cells. Disabling display", exc_info=True)
handler.setDisplayByName("noBraille", isFallback=True)
else:
if handler.display.receivesAckPackets:
handler.display._awaitingAck = True
winKernel.setWaitableTimer(
_BgThread.ackTimerHandle,
int(handler.display.timeout*2000),
0,
_BgThread.ackTimeoutResetter
)

@winKernel.PAPCFUNC
def ackTimeoutResetter(param):
if handler.display.receivesAckPackets and handler.display._awaitingAck:
log.debugWarning("Waiting for %s ACK packet timed out"%handler.display.name)
handler.display._awaitingAck = False
_BgThread.queueApc(_BgThread.executor)

@classmethod
def func(cls):
Expand Down Expand Up @@ -1974,6 +1998,22 @@ class BrailleDisplayDriver(baseObject.AutoPropertyObject):
#: This is also required to use the L{hwIo} module.
#: @type: bool
isThreadSafe = False
#: Whether displays for this driver return acknowledgements for sent packets.
#: L{_handleAck} should be called when an ACK is received.
#: Note that thread safety is required for the generic implementation to function properly.
#: If a display is not thread safe, a driver should manually implement ACK processing.
#: @type: bool
receivesAckPackets = False
#: Whether this driver is awaiting an Ack for a connected display.
#: This is set to C{True} after displaying cells when L{receivesAckPackets} is True,
#: and set to C{False} by L{_handleAck} or when C{timeout} has elapsed.
#: This is for internal use by NVDA core code only and shouldn't be touched by a driver itself.
_awaitingAck = False
#: Maximum timeout to use for communication with a device (in seconds).
#: This can be used for serial connections.
#: Furthermore, it is used by L{_BgThread} to stop waiting for missed acknowledgement packets.
#: @type: float
timeout = 0.2

@classmethod
def check(cls):
Expand Down Expand Up @@ -2058,6 +2098,15 @@ def _getModifierGestures(cls, model=None):
if emuGesture.isModifier:
yield set(gesture.split(":")[1].split("+")), set(emuGesture._keyNamesInDisplayOrder)

def _handleAck(self):
"""Base implementation to handle acknowledgement packets."""
if not self.receivesAckPackets:
raise NotImplementedError("This display driver does not support ACK packet handling")
if not ctypes.windll.kernel32.CancelWaitableTimer(_BgThread.ackTimerHandle):
raise ctypes.WinError()
self._awaitingAck = False
_BgThread.queueApc(_BgThread.executor)

class BrailleDisplayGesture(inputCore.InputGesture):
"""A button, wheel or other control pressed on a braille display.
Subclasses must provide L{source} and L{id}.
Expand Down
25 changes: 6 additions & 19 deletions source/brailleDisplayDrivers/handyTech.py
Expand Up @@ -20,7 +20,6 @@
from logHandler import log


TIMEOUT = 0.2
BAUD_RATE = 19200
PARITY = serial.PARITY_ODD

Expand Down Expand Up @@ -485,6 +484,8 @@ class BrailleDisplayDriver(braille.BrailleDisplayDriver, ScriptableObject):
# Translators: The name of a series of braille displays.
description = _("Handy Tech braille displays")
isThreadSafe = True
receivesAckPackets = True
timeout = 0.2

@classmethod
def check(cls):
Expand Down Expand Up @@ -543,8 +544,6 @@ def __init__(self, port="auto"):
self._ignoreKeyReleases = False
self._keysDown = set()
self._brailleInput = False
self._pendingCells = []
self._awaitingACK = False
self._hidSerialBuffer = ""

if port == "auto":
Expand All @@ -566,15 +565,15 @@ def __init__(self, port="auto"):
self._dev.write(HT_HID_RPT_InCommand + HT_HID_CMD_FlushBuffers)
else:
self._dev = hwIo.Serial(port, baudrate=BAUD_RATE, parity=PARITY,
timeout=TIMEOUT, writeTimeout=TIMEOUT, onReceive=self._onReceive)
timeout=self.timeout, writeTimeout=self.timeout, onReceive=self._onReceive)
except EnvironmentError:
log.debugWarning("", exc_info=True)
continue

self.sendPacket(HT_PKT_RESET)
for _i in xrange(3):
# An expected response hasn't arrived yet, so wait for it.
self._dev.waitForRead(TIMEOUT)
self._dev.waitForRead(self.timeout)
if self.numCells and self._model:
break

Expand Down Expand Up @@ -643,13 +642,6 @@ def _handleKeyRelease(self):
# being released, so they should be ignored.
self._ignoreKeyReleases = True

def _handleAck(self):
if not self._awaitingACK:
return
self._awaitingACK = False
if self._pendingCells:
self.display(self._pendingCells)

# pylint: disable=R0912
# Pylint complains about many branches, might be worth refactoring
def _onReceive(self, data):
Expand Down Expand Up @@ -761,13 +753,8 @@ def _onReceive(self, data):


def display(self, cells):
if not self._awaitingACK:
# cells will already be padded up to numCells.
self._model.display(cells)
self._awaitingACK = True
self._pendingCells = []
else:
self._pendingCells = cells
# cells will already be padded up to numCells.
self._model.display(cells)

scriptCategory = SCRCAT_BRAILLE

Expand Down
52 changes: 52 additions & 0 deletions source/winKernel.py
Expand Up @@ -55,6 +55,58 @@ def CreateFile(fileName,desiredAccess,shareMode,securityAttributes,creationDispo
return res


def createWaitableTimer(securityAttributes=None, manualReset=False, name=None):
"""Wrapper to the kernel32 CreateWaitableTimer function.
Consult https://msdn.microsoft.com/en-us/library/windows/desktop/ms682492.aspx for Microsoft's documentation.
In contrast with the original function, this wrapper assumes the following defaults.
@param securityAttributes: Defaults to C{None};
The timer object gets a default security descriptor and the handle cannot be inherited.
The ACLs in the default security descriptor for a timer come from the primary or impersonation token of the creator.
@type securityAttributes: pointer to L{SECURITY_ATTRIBUTES}
@param manualReset: Defaults to C{False} which means the timer is a synchronization timer.
If C{True}, the timer is a manual-reset notification timer.
@type manualReset: bool
@param name: Defaults to C{None}, the timer object is created without a name.
@type name: unicode
"""
res = kernel32.CreateWaitableTimerW(securityAttributes, manualReset, name)
if res==0:
raise ctypes.WinError()
return res

def setWaitableTimer(handle, dueTime, period=0, completionRoutine=None, arg=None, resume=False):
"""Wrapper to the kernel32 SETWaitableTimer function.
Consult https://msdn.microsoft.com/en-us/library/windows/desktop/ms686289.aspx for Microsoft's documentation.
@param handle: A handle to the timer object.
@type handle: int
@param dueTime: Relative time (in miliseconds).
Note that the original function requires relative time to be supplied as a negative nanoseconds value.
@type dueTime: int
@param period: Defaults to 0, timer is only executed once.
Value should be supplied in miliseconds.
@type period: int
@param completionRoutine: The function to be executed when the timer elapses.
@type completionRoutine: L{PAPCFUNC}
@param arg: Defaults to C{None}; a pointer to a structure that is passed to the completion routine.
@type arg: L{ctypes.c_void_p}
@param resume: Defaults to C{False}; the system is not restored.
If this parameter is TRUE, restores a system in suspended power conservation mode
when the timer state is set to signaled.
@type resume: bool
"""
res = kernel32.SetWaitableTimer(
handle,
# due time is in 100 nanosecond intervals, relative time should be negated.
byref(LARGE_INTEGER(dueTime*-10000)),
period,
completionRoutine,
arg,
resume
)
if res==0:
raise ctypes.WinError()
return True


def openProcess(*args):
return kernel32.OpenProcess(*args)
Expand Down

0 comments on commit a2a83c5

Please sign in to comment.