We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Say all on Desktop raises an error
### Steps to reproduce:
1. Focus the desktop.
2. Invoke caret say all

### Actual behavior:
The following error is raised:

```
ERROR - scriptHandler.executeScript (15:54:57.769):
error executing script: <bound method GlobalCommands.script_sayAll of <globalCommands.GlobalCommands object at 0x05875770>> with gesture 'NVDA+a'
Traceback (most recent call last):
  File "scriptHandler.pyc", line 190, in executeScript
  File "globalCommands.pyc", line 1334, in script_sayAll
  File "sayAllHandler.pyc", line 79, in readText
  File "sayAllHandler.pyc", line 119, in nextLine
AttributeError: '_TextReader' object has no attribute 'reader'
ERROR - stderr (15:54:57.779):
Exception ignored in:
ERROR - stderr (15:54:57.790):
<function _TextReader.__del__ at 0x0462F390>
ERROR - stderr (15:54:57.803):
Traceback (most recent call last):
ERROR - stderr (15:54:57.815):
  File "sayAllHandler.pyc", line 213, in __del__
ERROR - stderr (15:54:57.827):
  File "sayAllHandler.pyc", line 206, in stop
ERROR - stderr (15:54:57.839):
AttributeError
ERROR - stderr (15:54:57.851):
:
ERROR - stderr (15:54:57.863):
'_TextReader' object has no attribute 'reader'
```

### Expected behavior:
NO error

### System configuration
#### NVDA installed/portable/running from source:
Installed

#### NVDA version:
threshold-18069

#### Windows version:
Windows 10 1903 build 18362.239


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `source/sayAllHandler.py`
Content:
```
1 # A part of NonVisual Desktop Access (NVDA)
2 # Copyright (C) 2006-2017 NV Access Limited
3 # This file may be used under the terms of the GNU General Public License, version 2 or later.
4 # For more details see: https://www.gnu.org/licenses/gpl-2.0.html
5 
6 import weakref
7 import speech
8 import synthDriverHandler
9 from logHandler import log
10 import config
11 import controlTypes
12 import api
13 import textInfos
14 import queueHandler
15 import winKernel
16 
17 CURSOR_CARET = 0
18 CURSOR_REVIEW = 1
19 
20 lastSayAllMode = None
21 #: The active say all manager.
22 #: This is a weakref because the manager should be allowed to die once say all is complete.
23 _activeSayAll = lambda: None # Return None when called like a dead weakref.
24 
25 def stop():
26 	active = _activeSayAll()
27 	if active:
28 		active.stop()
29 
30 def isRunning():
31 	"""Determine whether say all is currently running.
32 	@return: C{True} if say all is currently running, C{False} if not.
33 	@rtype: bool
34 	"""
35 	return bool(_activeSayAll())
36 
37 def readObjects(obj):
38 	global _activeSayAll
39 	reader = _ObjectsReader(obj)
40 	_activeSayAll = weakref.ref(reader)
41 	reader.next()
42 
43 class _ObjectsReader(object):
44 
45 	def __init__(self, root):
46 		self.walker = self.walk(root)
47 		self.prevObj = None
48 
49 	def walk(self, obj):
50 		yield obj
51 		child=obj.simpleFirstChild
52 		while child:
53 			for descendant in self.walk(child):
54 				yield descendant
55 			child=child.simpleNext
56 
57 	def next(self):
58 		if not self.walker:
59 			# We were stopped.
60 			return
61 		if self.prevObj:
62 			# We just started speaking this object, so move the navigator to it.
63 			api.setNavigatorObject(self.prevObj, isFocus=lastSayAllMode==CURSOR_CARET)
64 			winKernel.SetThreadExecutionState(winKernel.ES_SYSTEM_REQUIRED)
65 		# Move onto the next object.
66 		self.prevObj = obj = next(self.walker, None)
67 		if not obj:
68 			return
69 		# Call this method again when we start speaking this object.
70 		callbackCommand = speech.CallbackCommand(self.next, name="say-all:next")
71 		speech.speakObject(obj, reason=controlTypes.REASON_SAYALL, _prefixSpeechCommand=callbackCommand)
72 
73 	def stop(self):
74 		self.walker = None
75 
76 def readText(cursor):
77 	global lastSayAllMode, _activeSayAll
78 	lastSayAllMode=cursor
79 	try:
80 		reader = _TextReader(cursor)
81 	except NotImplementedError:
82 		log.debugWarning("Unable to make reader", exc_info=True)
83 		return
84 	_activeSayAll = weakref.ref(reader)
85 	reader.nextLine()
86 
87 class _TextReader(object):
88 	"""Manages continuous reading of text.
89 	This is intended for internal use only.
90 
91 	The high level flow of control is as follows:
92 	1. The constructor sets things up.
93 	2. L{nextLine} is called to read the first line.
94 	3. When it speaks a line, L{nextLine} request that L{lineReached} be called
95 		when we start speaking this line, providing the position and state at this point.
96 	4. When we start speaking a line, L{lineReached} is called
97 		and moves the cursor to that line.
98 	5. L{lineReached} calls L{nextLine}.
99 	6. If there are more lines, L{nextLine} works as per steps 3 and 4.
100 	7. Otherwise, if the object doesn't support page turns, we're finished.
101 	8. If the object does support page turns,
102 		we request that L{turnPage} be called when speech is finished.
103 	9. L{turnPage} tries to turn the page.
104 	10. If there are no more pages, we're finished.
105 	11. If there is another page, L{turnPage} calls L{nextLine}.
106 	"""
107 	MAX_BUFFERED_LINES = 10
108 
109 	def __init__(self, cursor):
110 		self.cursor = cursor
111 		self.trigger = SayAllProfileTrigger()
112 		self.trigger.enter()
113 		# Start at the cursor.
114 		if cursor == CURSOR_CARET:
115 			try:
116 				self.reader = api.getCaretObject().makeTextInfo(textInfos.POSITION_CARET)
117 			except (NotImplementedError, RuntimeError) as e:
118 				raise NotImplementedError("Unable to make TextInfo: " + str(e))
119 		else:
120 			self.reader = api.getReviewPosition()
121 		self.speakTextInfoState = speech.SpeakTextInfoState(self.reader.obj)
122 		self.numBufferedLines = 0
123 
124 	def nextLine(self):
125 		if not self.reader:
126 			log.debug("no self.reader")
127 			# We were stopped.
128 			return
129 		if not self.reader.obj:
130 			log.debug("no self.reader.obj")
131 			# The object died, so we should too.
132 			self.finish()
133 			return
134 		bookmark = self.reader.bookmark
135 		# Expand to the current line.
136 		# We use move end rather than expand
137 		# because the user might start in the middle of a line
138 		# and we don't want to read from the start of the line in that case.
139 		# For lines after the first, it's also more efficient because
140 		# we're already at the start of the line, so there's no need to search backwards.
141 		delta = self.reader.move(textInfos.UNIT_READINGCHUNK, 1, endPoint="end")
142 		if delta <= 0:
143 			# No more text.
144 			if isinstance(self.reader.obj, textInfos.DocumentWithPageTurns):
145 				# Once the last line finishes reading, try turning the page.
146 				cb = speech.CallbackCommand(self.turnPage, name="say-all:turnPage")
147 				speech.speakWithoutPauses([cb, speech.EndUtteranceCommand()])
148 			else:
149 				self.finish()
150 			return
151 
152 		# Copy the speakTextInfoState so that speak callbackCommand
153 		# and its associated callback are using a copy isolated to this specific line.
154 		state = self.speakTextInfoState.copy()
155 		# Call lineReached when we start speaking this line.
156 		# lineReached will move the cursor and trigger reading of the next line.
157 
158 		def _onLineReached(obj=self.reader.obj, state=state):
159 			self.lineReached(obj, bookmark, state)
160 
161 		cb = speech.CallbackCommand(
162 			_onLineReached,
163 			name="say-all:lineReached"
164 		)
165 
166 		# Generate the speech sequence for the reader textInfo
167 		# and insert the lineReached callback at the very beginning of the sequence.
168 		# _linePrefix on speakTextInfo cannot be used here
169 		# As it would be inserted in the sequence after all initial control starts which is too late.
170 		speechGen = speech.getTextInfoSpeech(
171 			self.reader,
172 			unit=textInfos.UNIT_READINGCHUNK,
173 			reason=controlTypes.REASON_SAYALL,
174 			useCache=state
175 		)
176 		seq = list(speech._flattenNestedSequences(speechGen))
177 		seq.insert(0, cb)
178 		# Speak the speech sequence.
179 		spoke = speech.speakWithoutPauses(seq)
180 		# Update the textInfo state ready for when speaking the next line.
181 		self.speakTextInfoState = state.copy()
182 
183 		# Collapse to the end of this line, ready to read the next.
184 		try:
185 			self.reader.collapse(end=True)
186 		except RuntimeError:
187 			# This occurs in Microsoft Word when the range covers the end of the document.
188 			# without this exception to indicate that further collapsing is not possible, say all could enter an infinite loop.
189 			self.finish()
190 			return
191 		if not spoke:
192 			# This line didn't include a natural pause, so nothing was spoken.
193 			self.numBufferedLines += 1
194 			if self.numBufferedLines < self.MAX_BUFFERED_LINES:
195 				# Move on to the next line.
196 				# We queue this to allow the user a chance to stop say all.
197 				queueHandler.queueFunction(queueHandler.eventQueue, self.nextLine)
198 			else:
199 				# We don't want to buffer too much.
200 				# Force speech. lineReached will resume things when speech catches up.
201 				speech.speakWithoutPauses(None)
202 				# The first buffered line has now started speaking.
203 				self.numBufferedLines -= 1
204 
205 	def lineReached(self, obj, bookmark, state):
206 		# We've just started speaking this line, so move the cursor there.
207 		state.updateObj()
208 		updater = obj.makeTextInfo(bookmark)
209 		if self.cursor == CURSOR_CARET:
210 			updater.updateCaret()
211 		if self.cursor != CURSOR_CARET or config.conf["reviewCursor"]["followCaret"]:
212 			api.setReviewPosition(updater, isCaret=self.cursor==CURSOR_CARET)
213 		winKernel.SetThreadExecutionState(winKernel.ES_SYSTEM_REQUIRED)
214 		if self.numBufferedLines == 0:
215 			# This was the last line spoken, so move on.
216 			self.nextLine()
217 		else:
218 			self.numBufferedLines -= 1
219 
220 	def turnPage(self):
221 		try:
222 			self.reader.obj.turnPage()
223 		except RuntimeError:
224 			log.debug("No more pages")
225 			# No more pages.
226 			self.stop()
227 			return
228 		self.reader = self.reader.obj.makeTextInfo(textInfos.POSITION_FIRST)
229 		self.nextLine()
230 
231 	def finish(self):
232 		# There is no more text.
233 		# Call stop to clean up, but only after speech completely finishes.
234 		# Otherwise, if a different synth is being used for say all,
235 		# we might switch synths too early and truncate the final speech.
236 		# We do this by putting a CallbackCommand at the start of a new utterance.
237 		cb = speech.CallbackCommand(self.stop, name="say-all:stop")
238 		speech.speakWithoutPauses([
239 			speech.EndUtteranceCommand(),
240 			cb,
241 			speech.EndUtteranceCommand()
242 		])
243 
244 	def stop(self):
245 		if not self.reader:
246 			return
247 		self.reader = None
248 		self.trigger.exit()
249 		self.trigger = None
250 
251 	def __del__(self):
252 		self.stop()
253 
254 class SayAllProfileTrigger(config.ProfileTrigger):
255 	"""A configuration profile trigger for when say all is in progress.
256 	"""
257 	spec = "sayAll"
258
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/source/sayAllHandler.py b/source/sayAllHandler.py
--- a/source/sayAllHandler.py
+++ b/source/sayAllHandler.py
@@ -109,7 +109,7 @@
 	def __init__(self, cursor):
 		self.cursor = cursor
 		self.trigger = SayAllProfileTrigger()
-		self.trigger.enter()
+		self.reader = None
 		# Start at the cursor.
 		if cursor == CURSOR_CARET:
 			try:
@@ -118,6 +118,8 @@
 				raise NotImplementedError("Unable to make TextInfo: " + str(e))
 		else:
 			self.reader = api.getReviewPosition()
+		# #10899: SayAll profile can't be activated earlier because they may not be anything to read
+		self.trigger.enter()
 		self.speakTextInfoState = speech.SpeakTextInfoState(self.reader.obj)
 		self.numBufferedLines = 0

{"golden_diff": "diff --git a/source/sayAllHandler.py b/source/sayAllHandler.py\n--- a/source/sayAllHandler.py\n+++ b/source/sayAllHandler.py\n@@ -109,7 +109,7 @@\n \tdef __init__(self, cursor):\r\n \t\tself.cursor = cursor\r\n \t\tself.trigger = SayAllProfileTrigger()\r\n-\t\tself.trigger.enter()\r\n+\t\tself.reader = None\r\n \t\t# Start at the cursor.\r\n \t\tif cursor == CURSOR_CARET:\r\n \t\t\ttry:\r\n@@ -118,6 +118,8 @@\n \t\t\t\traise NotImplementedError(\"Unable to make TextInfo: \" + str(e))\r\n \t\telse:\r\n \t\t\tself.reader = api.getReviewPosition()\r\n+\t\t# #10899: SayAll profile can't be activated earlier because they may not be anything to read\r\n+\t\tself.trigger.enter()\r\n \t\tself.speakTextInfoState = speech.SpeakTextInfoState(self.reader.obj)\r\n \t\tself.numBufferedLines = 0\n", "issue": "Say all on Desktop raises an error\n### Steps to reproduce:\r\n1. Focus the desktop.\r\n2. Invoke caret say all\r\n\r\n### Actual behavior:\r\nThe following error is raised:\r\n\r\n```\r\nERROR - scriptHandler.executeScript (15:54:57.769):\r\nerror executing script: <bound method GlobalCommands.script_sayAll of <globalCommands.GlobalCommands object at 0x05875770>> with gesture 'NVDA+a'\r\nTraceback (most recent call last):\r\n  File \"scriptHandler.pyc\", line 190, in executeScript\r\n  File \"globalCommands.pyc\", line 1334, in script_sayAll\r\n  File \"sayAllHandler.pyc\", line 79, in readText\r\n  File \"sayAllHandler.pyc\", line 119, in nextLine\r\nAttributeError: '_TextReader' object has no attribute 'reader'\r\nERROR - stderr (15:54:57.779):\r\nException ignored in:\r\nERROR - stderr (15:54:57.790):\r\n<function _TextReader.__del__ at 0x0462F390>\r\nERROR - stderr (15:54:57.803):\r\nTraceback (most recent call last):\r\nERROR - stderr (15:54:57.815):\r\n  File \"sayAllHandler.pyc\", line 213, in __del__\r\nERROR - stderr (15:54:57.827):\r\n  File \"sayAllHandler.pyc\", line 206, in stop\r\nERROR - stderr (15:54:57.839):\r\nAttributeError\r\nERROR - stderr (15:54:57.851):\r\n:\r\nERROR - stderr (15:54:57.863):\r\n'_TextReader' object has no attribute 'reader'\r\n```\r\n\r\n### Expected behavior:\r\nNO error\r\n\r\n### System configuration\r\n#### NVDA installed/portable/running from source:\r\nInstalled\r\n\r\n#### NVDA version:\r\nthreshold-18069\r\n\r\n#### Windows version:\r\nWindows 10 1903 build 18362.239\r\n\n", "before_files": [{"content": "# A part of NonVisual Desktop Access (NVDA)\r\n# Copyright (C) 2006-2017 NV Access Limited\r\n# This file may be used under the terms of the GNU General Public License, version 2 or later.\r\n# For more details see: https://www.gnu.org/licenses/gpl-2.0.html\r\n\r\nimport weakref\r\nimport speech\r\nimport synthDriverHandler\r\nfrom logHandler import log\r\nimport config\r\nimport controlTypes\r\nimport api\r\nimport textInfos\r\nimport queueHandler\r\nimport winKernel\r\n\r\nCURSOR_CARET = 0\r\nCURSOR_REVIEW = 1\r\n\r\nlastSayAllMode = None\r\n#: The active say all manager.\r\n#: This is a weakref because the manager should be allowed to die once say all is complete.\r\n_activeSayAll = lambda: None # Return None when called like a dead weakref.\r\n\r\ndef stop():\r\n\tactive = _activeSayAll()\r\n\tif active:\r\n\t\tactive.stop()\r\n\r\ndef isRunning():\r\n\t\"\"\"Determine whether say all is currently running.\r\n\t@return: C{True} if say all is currently running, C{False} if not.\r\n\t@rtype: bool\r\n\t\"\"\"\r\n\treturn bool(_activeSayAll())\r\n\r\ndef readObjects(obj):\r\n\tglobal _activeSayAll\r\n\treader = _ObjectsReader(obj)\r\n\t_activeSayAll = weakref.ref(reader)\r\n\treader.next()\r\n\r\nclass _ObjectsReader(object):\r\n\r\n\tdef __init__(self, root):\r\n\t\tself.walker = self.walk(root)\r\n\t\tself.prevObj = None\r\n\r\n\tdef walk(self, obj):\r\n\t\tyield obj\r\n\t\tchild=obj.simpleFirstChild\r\n\t\twhile child:\r\n\t\t\tfor descendant in self.walk(child):\r\n\t\t\t\tyield descendant\r\n\t\t\tchild=child.simpleNext\r\n\r\n\tdef next(self):\r\n\t\tif not self.walker:\r\n\t\t\t# We were stopped.\r\n\t\t\treturn\r\n\t\tif self.prevObj:\r\n\t\t\t# We just started speaking this object, so move the navigator to it.\r\n\t\t\tapi.setNavigatorObject(self.prevObj, isFocus=lastSayAllMode==CURSOR_CARET)\r\n\t\t\twinKernel.SetThreadExecutionState(winKernel.ES_SYSTEM_REQUIRED)\r\n\t\t# Move onto the next object.\r\n\t\tself.prevObj = obj = next(self.walker, None)\r\n\t\tif not obj:\r\n\t\t\treturn\r\n\t\t# Call this method again when we start speaking this object.\r\n\t\tcallbackCommand = speech.CallbackCommand(self.next, name=\"say-all:next\")\r\n\t\tspeech.speakObject(obj, reason=controlTypes.REASON_SAYALL, _prefixSpeechCommand=callbackCommand)\r\n\r\n\tdef stop(self):\r\n\t\tself.walker = None\r\n\r\ndef readText(cursor):\r\n\tglobal lastSayAllMode, _activeSayAll\r\n\tlastSayAllMode=cursor\r\n\ttry:\r\n\t\treader = _TextReader(cursor)\r\n\texcept NotImplementedError:\r\n\t\tlog.debugWarning(\"Unable to make reader\", exc_info=True)\r\n\t\treturn\r\n\t_activeSayAll = weakref.ref(reader)\r\n\treader.nextLine()\r\n\r\nclass _TextReader(object):\r\n\t\"\"\"Manages continuous reading of text.\r\n\tThis is intended for internal use only.\r\n\r\n\tThe high level flow of control is as follows:\r\n\t1. The constructor sets things up.\r\n\t2. L{nextLine} is called to read the first line.\r\n\t3. When it speaks a line, L{nextLine} request that L{lineReached} be called\r\n\t\twhen we start speaking this line, providing the position and state at this point.\r\n\t4. When we start speaking a line, L{lineReached} is called\r\n\t\tand moves the cursor to that line.\r\n\t5. L{lineReached} calls L{nextLine}.\r\n\t6. If there are more lines, L{nextLine} works as per steps 3 and 4.\r\n\t7. Otherwise, if the object doesn't support page turns, we're finished.\r\n\t8. If the object does support page turns,\r\n\t\twe request that L{turnPage} be called when speech is finished.\r\n\t9. L{turnPage} tries to turn the page.\r\n\t10. If there are no more pages, we're finished.\r\n\t11. If there is another page, L{turnPage} calls L{nextLine}.\r\n\t\"\"\"\r\n\tMAX_BUFFERED_LINES = 10\r\n\r\n\tdef __init__(self, cursor):\r\n\t\tself.cursor = cursor\r\n\t\tself.trigger = SayAllProfileTrigger()\r\n\t\tself.trigger.enter()\r\n\t\t# Start at the cursor.\r\n\t\tif cursor == CURSOR_CARET:\r\n\t\t\ttry:\r\n\t\t\t\tself.reader = api.getCaretObject().makeTextInfo(textInfos.POSITION_CARET)\r\n\t\t\texcept (NotImplementedError, RuntimeError) as e:\r\n\t\t\t\traise NotImplementedError(\"Unable to make TextInfo: \" + str(e))\r\n\t\telse:\r\n\t\t\tself.reader = api.getReviewPosition()\r\n\t\tself.speakTextInfoState = speech.SpeakTextInfoState(self.reader.obj)\r\n\t\tself.numBufferedLines = 0\r\n\r\n\tdef nextLine(self):\r\n\t\tif not self.reader:\r\n\t\t\tlog.debug(\"no self.reader\")\r\n\t\t\t# We were stopped.\r\n\t\t\treturn\r\n\t\tif not self.reader.obj:\r\n\t\t\tlog.debug(\"no self.reader.obj\")\r\n\t\t\t# The object died, so we should too.\r\n\t\t\tself.finish()\r\n\t\t\treturn\r\n\t\tbookmark = self.reader.bookmark\r\n\t\t# Expand to the current line.\r\n\t\t# We use move end rather than expand\r\n\t\t# because the user might start in the middle of a line\r\n\t\t# and we don't want to read from the start of the line in that case.\r\n\t\t# For lines after the first, it's also more efficient because\r\n\t\t# we're already at the start of the line, so there's no need to search backwards.\r\n\t\tdelta = self.reader.move(textInfos.UNIT_READINGCHUNK, 1, endPoint=\"end\")\r\n\t\tif delta <= 0:\r\n\t\t\t# No more text.\r\n\t\t\tif isinstance(self.reader.obj, textInfos.DocumentWithPageTurns):\r\n\t\t\t\t# Once the last line finishes reading, try turning the page.\r\n\t\t\t\tcb = speech.CallbackCommand(self.turnPage, name=\"say-all:turnPage\")\r\n\t\t\t\tspeech.speakWithoutPauses([cb, speech.EndUtteranceCommand()])\r\n\t\t\telse:\r\n\t\t\t\tself.finish()\r\n\t\t\treturn\r\n\r\n\t\t# Copy the speakTextInfoState so that speak callbackCommand\r\n\t\t# and its associated callback are using a copy isolated to this specific line.\r\n\t\tstate = self.speakTextInfoState.copy()\r\n\t\t# Call lineReached when we start speaking this line.\r\n\t\t# lineReached will move the cursor and trigger reading of the next line.\r\n\r\n\t\tdef _onLineReached(obj=self.reader.obj, state=state):\r\n\t\t\tself.lineReached(obj, bookmark, state)\r\n\r\n\t\tcb = speech.CallbackCommand(\r\n\t\t\t_onLineReached,\r\n\t\t\tname=\"say-all:lineReached\"\r\n\t\t)\r\n\r\n\t\t# Generate the speech sequence for the reader textInfo\r\n\t\t# and insert the lineReached callback at the very beginning of the sequence.\r\n\t\t# _linePrefix on speakTextInfo cannot be used here\r\n\t\t# As it would be inserted in the sequence after all initial control starts which is too late.\r\n\t\tspeechGen = speech.getTextInfoSpeech(\r\n\t\t\tself.reader,\r\n\t\t\tunit=textInfos.UNIT_READINGCHUNK,\r\n\t\t\treason=controlTypes.REASON_SAYALL,\r\n\t\t\tuseCache=state\r\n\t\t)\r\n\t\tseq = list(speech._flattenNestedSequences(speechGen))\r\n\t\tseq.insert(0, cb)\r\n\t\t# Speak the speech sequence.\r\n\t\tspoke = speech.speakWithoutPauses(seq)\r\n\t\t# Update the textInfo state ready for when speaking the next line.\r\n\t\tself.speakTextInfoState = state.copy()\r\n\r\n\t\t# Collapse to the end of this line, ready to read the next.\r\n\t\ttry:\r\n\t\t\tself.reader.collapse(end=True)\r\n\t\texcept RuntimeError:\r\n\t\t\t# This occurs in Microsoft Word when the range covers the end of the document.\r\n\t\t\t# without this exception to indicate that further collapsing is not possible, say all could enter an infinite loop.\r\n\t\t\tself.finish()\r\n\t\t\treturn\r\n\t\tif not spoke:\r\n\t\t\t# This line didn't include a natural pause, so nothing was spoken.\r\n\t\t\tself.numBufferedLines += 1\r\n\t\t\tif self.numBufferedLines < self.MAX_BUFFERED_LINES:\r\n\t\t\t\t# Move on to the next line.\r\n\t\t\t\t# We queue this to allow the user a chance to stop say all.\r\n\t\t\t\tqueueHandler.queueFunction(queueHandler.eventQueue, self.nextLine)\r\n\t\t\telse:\r\n\t\t\t\t# We don't want to buffer too much.\r\n\t\t\t\t# Force speech. lineReached will resume things when speech catches up.\r\n\t\t\t\tspeech.speakWithoutPauses(None)\r\n\t\t\t\t# The first buffered line has now started speaking.\r\n\t\t\t\tself.numBufferedLines -= 1\r\n\r\n\tdef lineReached(self, obj, bookmark, state):\r\n\t\t# We've just started speaking this line, so move the cursor there.\r\n\t\tstate.updateObj()\r\n\t\tupdater = obj.makeTextInfo(bookmark)\r\n\t\tif self.cursor == CURSOR_CARET:\r\n\t\t\tupdater.updateCaret()\r\n\t\tif self.cursor != CURSOR_CARET or config.conf[\"reviewCursor\"][\"followCaret\"]:\r\n\t\t\tapi.setReviewPosition(updater, isCaret=self.cursor==CURSOR_CARET)\r\n\t\twinKernel.SetThreadExecutionState(winKernel.ES_SYSTEM_REQUIRED)\r\n\t\tif self.numBufferedLines == 0:\r\n\t\t\t# This was the last line spoken, so move on.\r\n\t\t\tself.nextLine()\r\n\t\telse:\r\n\t\t\tself.numBufferedLines -= 1\r\n\r\n\tdef turnPage(self):\r\n\t\ttry:\r\n\t\t\tself.reader.obj.turnPage()\r\n\t\texcept RuntimeError:\r\n\t\t\tlog.debug(\"No more pages\")\r\n\t\t\t# No more pages.\r\n\t\t\tself.stop()\r\n\t\t\treturn\r\n\t\tself.reader = self.reader.obj.makeTextInfo(textInfos.POSITION_FIRST)\r\n\t\tself.nextLine()\r\n\r\n\tdef finish(self):\r\n\t\t# There is no more text.\r\n\t\t# Call stop to clean up, but only after speech completely finishes.\r\n\t\t# Otherwise, if a different synth is being used for say all,\r\n\t\t# we might switch synths too early and truncate the final speech.\r\n\t\t# We do this by putting a CallbackCommand at the start of a new utterance.\r\n\t\tcb = speech.CallbackCommand(self.stop, name=\"say-all:stop\")\r\n\t\tspeech.speakWithoutPauses([\r\n\t\t\tspeech.EndUtteranceCommand(),\r\n\t\t\tcb,\r\n\t\t\tspeech.EndUtteranceCommand()\r\n\t\t])\r\n\r\n\tdef stop(self):\r\n\t\tif not self.reader:\r\n\t\t\treturn\r\n\t\tself.reader = None\r\n\t\tself.trigger.exit()\r\n\t\tself.trigger = None\r\n\r\n\tdef __del__(self):\r\n\t\tself.stop()\r\n\r\nclass SayAllProfileTrigger(config.ProfileTrigger):\r\n\t\"\"\"A configuration profile trigger for when say all is in progress.\r\n\t\"\"\"\r\n\tspec = \"sayAll\"\r\n", "path": "source/sayAllHandler.py"}], "after_files": [{"content": "# A part of NonVisual Desktop Access (NVDA)\r\n# Copyright (C) 2006-2017 NV Access Limited\r\n# This file may be used under the terms of the GNU General Public License, version 2 or later.\r\n# For more details see: https://www.gnu.org/licenses/gpl-2.0.html\r\n\r\nimport weakref\r\nimport speech\r\nimport synthDriverHandler\r\nfrom logHandler import log\r\nimport config\r\nimport controlTypes\r\nimport api\r\nimport textInfos\r\nimport queueHandler\r\nimport winKernel\r\n\r\nCURSOR_CARET = 0\r\nCURSOR_REVIEW = 1\r\n\r\nlastSayAllMode = None\r\n#: The active say all manager.\r\n#: This is a weakref because the manager should be allowed to die once say all is complete.\r\n_activeSayAll = lambda: None # Return None when called like a dead weakref.\r\n\r\ndef stop():\r\n\tactive = _activeSayAll()\r\n\tif active:\r\n\t\tactive.stop()\r\n\r\ndef isRunning():\r\n\t\"\"\"Determine whether say all is currently running.\r\n\t@return: C{True} if say all is currently running, C{False} if not.\r\n\t@rtype: bool\r\n\t\"\"\"\r\n\treturn bool(_activeSayAll())\r\n\r\ndef readObjects(obj):\r\n\tglobal _activeSayAll\r\n\treader = _ObjectsReader(obj)\r\n\t_activeSayAll = weakref.ref(reader)\r\n\treader.next()\r\n\r\nclass _ObjectsReader(object):\r\n\r\n\tdef __init__(self, root):\r\n\t\tself.walker = self.walk(root)\r\n\t\tself.prevObj = None\r\n\r\n\tdef walk(self, obj):\r\n\t\tyield obj\r\n\t\tchild=obj.simpleFirstChild\r\n\t\twhile child:\r\n\t\t\tfor descendant in self.walk(child):\r\n\t\t\t\tyield descendant\r\n\t\t\tchild=child.simpleNext\r\n\r\n\tdef next(self):\r\n\t\tif not self.walker:\r\n\t\t\t# We were stopped.\r\n\t\t\treturn\r\n\t\tif self.prevObj:\r\n\t\t\t# We just started speaking this object, so move the navigator to it.\r\n\t\t\tapi.setNavigatorObject(self.prevObj, isFocus=lastSayAllMode==CURSOR_CARET)\r\n\t\t\twinKernel.SetThreadExecutionState(winKernel.ES_SYSTEM_REQUIRED)\r\n\t\t# Move onto the next object.\r\n\t\tself.prevObj = obj = next(self.walker, None)\r\n\t\tif not obj:\r\n\t\t\treturn\r\n\t\t# Call this method again when we start speaking this object.\r\n\t\tcallbackCommand = speech.CallbackCommand(self.next, name=\"say-all:next\")\r\n\t\tspeech.speakObject(obj, reason=controlTypes.REASON_SAYALL, _prefixSpeechCommand=callbackCommand)\r\n\r\n\tdef stop(self):\r\n\t\tself.walker = None\r\n\r\ndef readText(cursor):\r\n\tglobal lastSayAllMode, _activeSayAll\r\n\tlastSayAllMode=cursor\r\n\ttry:\r\n\t\treader = _TextReader(cursor)\r\n\texcept NotImplementedError:\r\n\t\tlog.debugWarning(\"Unable to make reader\", exc_info=True)\r\n\t\treturn\r\n\t_activeSayAll = weakref.ref(reader)\r\n\treader.nextLine()\r\n\r\nclass _TextReader(object):\r\n\t\"\"\"Manages continuous reading of text.\r\n\tThis is intended for internal use only.\r\n\r\n\tThe high level flow of control is as follows:\r\n\t1. The constructor sets things up.\r\n\t2. L{nextLine} is called to read the first line.\r\n\t3. When it speaks a line, L{nextLine} request that L{lineReached} be called\r\n\t\twhen we start speaking this line, providing the position and state at this point.\r\n\t4. When we start speaking a line, L{lineReached} is called\r\n\t\tand moves the cursor to that line.\r\n\t5. L{lineReached} calls L{nextLine}.\r\n\t6. If there are more lines, L{nextLine} works as per steps 3 and 4.\r\n\t7. Otherwise, if the object doesn't support page turns, we're finished.\r\n\t8. If the object does support page turns,\r\n\t\twe request that L{turnPage} be called when speech is finished.\r\n\t9. L{turnPage} tries to turn the page.\r\n\t10. If there are no more pages, we're finished.\r\n\t11. If there is another page, L{turnPage} calls L{nextLine}.\r\n\t\"\"\"\r\n\tMAX_BUFFERED_LINES = 10\r\n\r\n\tdef __init__(self, cursor):\r\n\t\tself.cursor = cursor\r\n\t\tself.trigger = SayAllProfileTrigger()\r\n\t\tself.reader = None\r\n\t\t# Start at the cursor.\r\n\t\tif cursor == CURSOR_CARET:\r\n\t\t\ttry:\r\n\t\t\t\tself.reader = api.getCaretObject().makeTextInfo(textInfos.POSITION_CARET)\r\n\t\t\texcept (NotImplementedError, RuntimeError) as e:\r\n\t\t\t\traise NotImplementedError(\"Unable to make TextInfo: \" + str(e))\r\n\t\telse:\r\n\t\t\tself.reader = api.getReviewPosition()\r\n\t\t# #10899: SayAll profile can't be activated earlier because they may not be anything to read\r\n\t\tself.trigger.enter()\r\n\t\tself.speakTextInfoState = speech.SpeakTextInfoState(self.reader.obj)\r\n\t\tself.numBufferedLines = 0\r\n\r\n\tdef nextLine(self):\r\n\t\tif not self.reader:\r\n\t\t\tlog.debug(\"no self.reader\")\r\n\t\t\t# We were stopped.\r\n\t\t\treturn\r\n\t\tif not self.reader.obj:\r\n\t\t\tlog.debug(\"no self.reader.obj\")\r\n\t\t\t# The object died, so we should too.\r\n\t\t\tself.finish()\r\n\t\t\treturn\r\n\t\tbookmark = self.reader.bookmark\r\n\t\t# Expand to the current line.\r\n\t\t# We use move end rather than expand\r\n\t\t# because the user might start in the middle of a line\r\n\t\t# and we don't want to read from the start of the line in that case.\r\n\t\t# For lines after the first, it's also more efficient because\r\n\t\t# we're already at the start of the line, so there's no need to search backwards.\r\n\t\tdelta = self.reader.move(textInfos.UNIT_READINGCHUNK, 1, endPoint=\"end\")\r\n\t\tif delta <= 0:\r\n\t\t\t# No more text.\r\n\t\t\tif isinstance(self.reader.obj, textInfos.DocumentWithPageTurns):\r\n\t\t\t\t# Once the last line finishes reading, try turning the page.\r\n\t\t\t\tcb = speech.CallbackCommand(self.turnPage, name=\"say-all:turnPage\")\r\n\t\t\t\tspeech.speakWithoutPauses([cb, speech.EndUtteranceCommand()])\r\n\t\t\telse:\r\n\t\t\t\tself.finish()\r\n\t\t\treturn\r\n\r\n\t\t# Copy the speakTextInfoState so that speak callbackCommand\r\n\t\t# and its associated callback are using a copy isolated to this specific line.\r\n\t\tstate = self.speakTextInfoState.copy()\r\n\t\t# Call lineReached when we start speaking this line.\r\n\t\t# lineReached will move the cursor and trigger reading of the next line.\r\n\r\n\t\tdef _onLineReached(obj=self.reader.obj, state=state):\r\n\t\t\tself.lineReached(obj, bookmark, state)\r\n\r\n\t\tcb = speech.CallbackCommand(\r\n\t\t\t_onLineReached,\r\n\t\t\tname=\"say-all:lineReached\"\r\n\t\t)\r\n\r\n\t\t# Generate the speech sequence for the reader textInfo\r\n\t\t# and insert the lineReached callback at the very beginning of the sequence.\r\n\t\t# _linePrefix on speakTextInfo cannot be used here\r\n\t\t# As it would be inserted in the sequence after all initial control starts which is too late.\r\n\t\tspeechGen = speech.getTextInfoSpeech(\r\n\t\t\tself.reader,\r\n\t\t\tunit=textInfos.UNIT_READINGCHUNK,\r\n\t\t\treason=controlTypes.REASON_SAYALL,\r\n\t\t\tuseCache=state\r\n\t\t)\r\n\t\tseq = list(speech._flattenNestedSequences(speechGen))\r\n\t\tseq.insert(0, cb)\r\n\t\t# Speak the speech sequence.\r\n\t\tspoke = speech.speakWithoutPauses(seq)\r\n\t\t# Update the textInfo state ready for when speaking the next line.\r\n\t\tself.speakTextInfoState = state.copy()\r\n\r\n\t\t# Collapse to the end of this line, ready to read the next.\r\n\t\ttry:\r\n\t\t\tself.reader.collapse(end=True)\r\n\t\texcept RuntimeError:\r\n\t\t\t# This occurs in Microsoft Word when the range covers the end of the document.\r\n\t\t\t# without this exception to indicate that further collapsing is not possible, say all could enter an infinite loop.\r\n\t\t\tself.finish()\r\n\t\t\treturn\r\n\t\tif not spoke:\r\n\t\t\t# This line didn't include a natural pause, so nothing was spoken.\r\n\t\t\tself.numBufferedLines += 1\r\n\t\t\tif self.numBufferedLines < self.MAX_BUFFERED_LINES:\r\n\t\t\t\t# Move on to the next line.\r\n\t\t\t\t# We queue this to allow the user a chance to stop say all.\r\n\t\t\t\tqueueHandler.queueFunction(queueHandler.eventQueue, self.nextLine)\r\n\t\t\telse:\r\n\t\t\t\t# We don't want to buffer too much.\r\n\t\t\t\t# Force speech. lineReached will resume things when speech catches up.\r\n\t\t\t\tspeech.speakWithoutPauses(None)\r\n\t\t\t\t# The first buffered line has now started speaking.\r\n\t\t\t\tself.numBufferedLines -= 1\r\n\r\n\tdef lineReached(self, obj, bookmark, state):\r\n\t\t# We've just started speaking this line, so move the cursor there.\r\n\t\tstate.updateObj()\r\n\t\tupdater = obj.makeTextInfo(bookmark)\r\n\t\tif self.cursor == CURSOR_CARET:\r\n\t\t\tupdater.updateCaret()\r\n\t\tif self.cursor != CURSOR_CARET or config.conf[\"reviewCursor\"][\"followCaret\"]:\r\n\t\t\tapi.setReviewPosition(updater, isCaret=self.cursor==CURSOR_CARET)\r\n\t\twinKernel.SetThreadExecutionState(winKernel.ES_SYSTEM_REQUIRED)\r\n\t\tif self.numBufferedLines == 0:\r\n\t\t\t# This was the last line spoken, so move on.\r\n\t\t\tself.nextLine()\r\n\t\telse:\r\n\t\t\tself.numBufferedLines -= 1\r\n\r\n\tdef turnPage(self):\r\n\t\ttry:\r\n\t\t\tself.reader.obj.turnPage()\r\n\t\texcept RuntimeError:\r\n\t\t\tlog.debug(\"No more pages\")\r\n\t\t\t# No more pages.\r\n\t\t\tself.stop()\r\n\t\t\treturn\r\n\t\tself.reader = self.reader.obj.makeTextInfo(textInfos.POSITION_FIRST)\r\n\t\tself.nextLine()\r\n\r\n\tdef finish(self):\r\n\t\t# There is no more text.\r\n\t\t# Call stop to clean up, but only after speech completely finishes.\r\n\t\t# Otherwise, if a different synth is being used for say all,\r\n\t\t# we might switch synths too early and truncate the final speech.\r\n\t\t# We do this by putting a CallbackCommand at the start of a new utterance.\r\n\t\tcb = speech.CallbackCommand(self.stop, name=\"say-all:stop\")\r\n\t\tspeech.speakWithoutPauses([\r\n\t\t\tspeech.EndUtteranceCommand(),\r\n\t\t\tcb,\r\n\t\t\tspeech.EndUtteranceCommand()\r\n\t\t])\r\n\r\n\tdef stop(self):\r\n\t\tif not self.reader:\r\n\t\t\treturn\r\n\t\tself.reader = None\r\n\t\tself.trigger.exit()\r\n\t\tself.trigger = None\r\n\r\n\tdef __del__(self):\r\n\t\tself.stop()\r\n\r\nclass SayAllProfileTrigger(config.ProfileTrigger):\r\n\t\"\"\"A configuration profile trigger for when say all is in progress.\r\n\t\"\"\"\r\n\tspec = \"sayAll\"\r\n", "path": "source/sayAllHandler.py"}]}