Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,14 @@

### Bug Fixes

- IDA Pro: FLARE Capa Explorer has been fixed to work for IDA Pro 9.2 using PyQt6 @nicolaipre #2713

### capa Explorer Web

### capa Explorer IDA Pro plugin

- fixed bug where plugin did not open due to PyQt6 upgrade @nicolaipre #2713

### Development

- ci: remove redundant "test_run" action from build workflow @mike-hunhoff #2692
Expand Down
19 changes: 15 additions & 4 deletions capa/ida/plugin/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@
from capa.features.address import Address, FileOffsetAddress, AbsoluteVirtualAddress


# PyQT6 support with PyQt5 fallback
if hasattr(QtCore.Qt, 'ItemFlag'):
# PyQt6 / PySide6
ItemFlag = QtCore.Qt.ItemFlag
TRISTATE = ItemFlag.ItemIsAutoTristate
else:
# PyQt5
ItemFlag = QtCore.Qt
TRISTATE = ItemFlag.ItemIsTristate
Comment on lines +28 to +35

Choose a reason for hiding this comment

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

critical

This compatibility layer is a great idea to support both PyQt5 and PyQt6. However, the current implementation for the PyQt5 fallback will not work as intended. In PyQt5, ItemFlag is set to QtCore.Qt, and attributes like QtCore.Qt.ItemIsEnabled are integers. Accessing .value on an integer (e.g., ItemFlag.ItemIsEnabled.value) will raise an AttributeError.

To ensure PyQt5 compatibility while keeping the usage sites clean, you can create a shim for PyQt5 that mimics the structure of PyQt6 enums. This will make the code work for both versions without further changes.

Suggested change
if hasattr(QtCore.Qt, 'ItemFlag'):
# PyQt6 / PySide6
ItemFlag = QtCore.Qt.ItemFlag
TRISTATE = ItemFlag.ItemIsAutoTristate
else:
# PyQt5
ItemFlag = QtCore.Qt
TRISTATE = ItemFlag.ItemIsTristate
if hasattr(QtCore.Qt, 'ItemFlag'):
# PyQt6 / PySide6
ItemFlag = QtCore.Qt.ItemFlag
TRISTATE = ItemFlag.ItemIsAutoTristate
else:
# PyQt5
# Create a shim to behave like PyQt6's enums which have a .value attribute.
class EnumValue:
def __init__(self, value):
self.value = value
class QtEnumShim:
ItemIsEnabled = EnumValue(QtCore.Qt.ItemIsEnabled)
ItemIsSelectable = EnumValue(QtCore.Qt.ItemIsSelectable)
ItemIsUserCheckable = EnumValue(QtCore.Qt.ItemIsUserCheckable)
ItemIsEditable = EnumValue(QtCore.Qt.ItemIsEditable)
ItemFlag = QtEnumShim()
TRISTATE = EnumValue(QtCore.Qt.ItemIsTristate)



def info_to_name(display):
"""extract root value from display name

Expand Down Expand Up @@ -52,10 +63,10 @@ def __init__(self, parent: Optional["CapaExplorerDataItem"], data: list[str], ca
self._can_check = can_check

# default state for item
self.flags = QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
self.flags = ItemFlag.ItemIsEnabled.value | ItemFlag.ItemIsSelectable.value

if self._can_check:
self.flags = self.flags | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsTristate
self.flags = self.flags | ItemFlag.ItemIsUserCheckable.value | TRISTATE.value

if self.pred:
self.pred.appendChild(self)
Expand All @@ -66,9 +77,9 @@ def setIsEditable(self, isEditable=False):
@param isEditable: True, can edit, False cannot edit
"""
if isEditable:
self.flags |= QtCore.Qt.ItemIsEditable
self.flags |= ItemFlag.ItemIsEditable.value
else:
self.flags &= ~QtCore.Qt.ItemIsEditable
self.flags &= ~ItemFlag.ItemIsEditable.value

def setChecked(self, checked):
"""set item as checked
Expand Down
Loading