Repository: nvaccess/nvda

Issue:
StartisBack application fails to speak menu items
### Steps to reproduce:

* Install StartisBack for Windows 11:

https://www.startallback.com/

* Press WIN key

Navigate up or down if we have quick access items or right arrow to go to the different items such as Control Panel, Run, Settings, Favorites, Documents, Downloads, etc....

### Actual behavior:

When scrolling through the menu it only speaks the word [selected].

### Expected behavior:

You are expected to verbalize the name of the item that has the focus.

### System configuration
#### NVDA installed/portable/running from source:

For the tests I used both installed and portable versions.

#### NVDA version:

NVDA 2022.1beta4

#### Windows version:

Name SO	Microsoft Windows 11 Pro
Version	10.0.22000 Build 22000

#### Name and version of other software in use when reproducing the issue:

StartisBack version 3.3.5

#### Other information about your system:

N/A

### Other questions
#### Does the issue still occur after restarting your computer?
Yes

#### Have you tried any other versions of NVDA? If so, please report their behaviors.

* NVDA 2021.3.5

Behavior is as expected, correctly verbalizes menu items.

#### If NVDA add-ons are disabled, is your problem still occurring?

Yes

#### Does the issue still occur after you run the COM Registration Fixing Tool in NVDA's tools menu?

Yes

[logs.zip](https://github.com/nvaccess/nvda/files/8609725/logs.zip)



Discussion:
Hi @hxebolax 

It seems that #13657 aims to fix this issue. Would you test the build generated in this PR and report the result in the PR or here?


Hi @CyrilleB79.

Indeed #13657 solves my problem.

It now verbose the start menu items correctly.

Thank you very much.
Hi @hxebolax - thanks for your testing. I've updated the title to be more accurate. 

Just to confirm - does any application actually crash? From my understanding, menu items are just failed to be read correctly.
The rest of the issue description seems to suggest this, and has no mention of crashes occurring.
There are no crashes, the translator chose the wrong word when translating the message from spanish to english.
Unfortunately we are unable to safely fix this issue. The StartIsBack and StartAllBack developers need to be contacted.
This information might be helpful for future developers investigating the issue.

When navigating through the start menu with the application StartAllBack/StartIsBack installed, a layer is created over file explorer. NVDA uses the file explorer app module, but fetching the column order array with SendMessage fails.
We fetch the column order array ([LVM_GETCOLUMNORDERARRAY](https://docs.microsoft.com/en-us/windows/win32/controls/lvm-getcolumnorderarray)) with [SendMessage](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendmessage). While other related ListView messages are successful, we cannot be determine the correct data without a known column order array.

When this fails, 3 different errors are reported:
- 1400: [ERROR_INVALID_WINDOW_HANDLE](https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--1300-1699-)
- 0: [ERROR_SUCCESS?](https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-)
- 5: [ERROR_ACCESS_DENIED](https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-)

We are uncertain why these errors are reported, or why the error codes are inconsistent. I expect it is to do with the StartIsBack/StartAllBack dll that is injected into file explorer.


Resolving patch:
diff --git a/source/NVDAObjects/IAccessible/sysListView32.py b/source/NVDAObjects/IAccessible/sysListView32.py
--- a/source/NVDAObjects/IAccessible/sysListView32.py
+++ b/source/NVDAObjects/IAccessible/sysListView32.py
@@ -217,7 +217,10 @@ def _get_isMultiColumn(self):
 	def _get_rowCount(self):
 		return watchdog.cancellableSendMessage(self.windowHandle, LVM_GETITEMCOUNT, 0, 0)
 
-	def _get_columnCount(self):
+	columnCount: int
+	"""Typing information for auto-property: _get_columnCount"""
+
+	def _get_columnCount(self) -> int:
 		if not self.isMultiColumn:
 			return 0
 		headerHwnd= watchdog.cancellableSendMessage(self.windowHandle,LVM_GETHEADER,0,0)
@@ -287,10 +290,27 @@ def _getColumnOrderArrayRaw(self, columnCount: int) -> Optional[ctypes.Array]:
 			return self._getColumnOrderArrayRawOutProc(columnCount)
 		return self._getColumnOrderArrayRawInProc(columnCount)
 
-	_columnOrderArray: Optional[ctypes.Array]
-
-	def _get__columnOrderArray(self) -> Optional[ctypes.Array]:
-		return self._getColumnOrderArrayRaw(self.columnCount)
+	def _getMappedColumn(self, presentationIndex: int) -> Optional[int]:
+		"""
+		Multi-column SysListViews can have their columns re-ordered.
+		To keep a consistent internal mapping, a column order array is used
+		to map a presentation index to a consistent internal index.
+		For single-column SysListViews, the mapping is not necessary.
+		If the column order array cannot be fetched from a multi-column SysListView,
+		this returns None.
+
+		@param presentationIndex: One based index for the column as presented to the user.
+		@return: The internal / logical column zero based index for the column.
+		None if the mapped column cannot be determined.
+		"""
+		if presentationIndex == 1 and self.columnCount == 1:
+			# Use an implied default column mapping for single column list views
+			return 0
+		columnOrderArray = self._getColumnOrderArrayRaw(self.columnCount)
+		if columnOrderArray is None:
+			log.error("Cannot fetch column as column order array is unknown")
+			return None
+		return columnOrderArray[presentationIndex - 1]
 
 
 class GroupingItem(Window):
@@ -454,10 +474,10 @@ def _getColumnLocationRaw(self, index: int) -> Optional[RectLTRB]:
 		return RectLTRB(left, top, right, bottom).toScreen(self.windowHandle).toLTWH()
 
 	def _getColumnLocation(self, column: int) -> Optional[RectLTRB]:
-		if self.parent._columnOrderArray is None:
-			log.debugWarning("Cannot fetch column location as column order array is unknown")
+		mappedColumn = self.parent._getMappedColumn(column)
+		if mappedColumn is None:
 			return None
-		return self._getColumnLocationRaw(self.parent._columnOrderArray[column - 1])
+		return self._getColumnLocationRaw(mappedColumn)
 
 	def _getColumnContentRawInProc(self, index: int) -> Optional[str]:
 		"""Retrieves text for a given column.
@@ -510,10 +530,10 @@ def _getColumnContentRaw(self, index: int) -> Optional[str]:
 		return self._getColumnContentRawInProc(index)
 
 	def _getColumnContent(self, column: int) -> Optional[str]:
-		if self.parent._columnOrderArray is None:
-			log.debugWarning("Cannot fetch column content as column order array is unknown")
+		mappedColumn = self.parent._getMappedColumn(column)
+		if mappedColumn is None:
 			return None
-		return self._getColumnContentRaw(self.parent._columnOrderArray[column - 1])
+		return self._getColumnContentRaw(mappedColumn)
 
 	def _getColumnImageIDRaw(self, index):
 		processHandle=self.processHandle
@@ -530,10 +550,10 @@ def _getColumnImageIDRaw(self, index):
 		return item.iImage
 
 	def _getColumnImageID(self, column):
-		if self.parent._columnOrderArray is None:
-			log.debugWarning("Cannot fetch column image ID as column order array is unknown")
+		mappedColumn = self.parent._getMappedColumn(column)
+		if mappedColumn is None:
 			return None
-		return self._getColumnImageIDRaw(self.parent._columnOrderArray[column - 1])
+		return self._getColumnImageIDRaw(mappedColumn)
 
 	def _getColumnHeaderRawOutProc(self, index: int) -> Optional[str]:
 		"""Retrieves text of the header for the given column.
@@ -584,10 +604,10 @@ def _getColumnHeaderRaw(self, index: int) -> Optional[str]:
 		return self._getColumnHeaderRawInProc(index)
 
 	def _getColumnHeader(self, column: int) -> Optional[str]:
-		if self.parent._columnOrderArray is None:
-			log.debugWarning("Cannot fetch column header as column order array is unknown")
+		mappedColumn = self.parent._getMappedColumn(column)
+		if mappedColumn is None:
 			return None
-		return self._getColumnHeaderRaw(self.parent._columnOrderArray[column - 1])
+		return self._getColumnHeaderRaw(mappedColumn)
 
 	def _get_name(self):
 		parent = self.parent


Test patch: