Compare commits

...

9 Commits

Author SHA1 Message Date
gaasedelen 0350b0c538 Follow-up to #157: unsubscribe torn-down coverage overview refresh callback 2026-02-13 19:39:44 -05:00
gaasedelen 88051b0652 Follow-up to #157: keep IDA8/9.1 sip path and bump dev version to 0.9.4 2026-02-13 18:56:11 -05:00
gaasedelen 1d7e4b321c Merge PR #157: IDA 9.2/9.3 compatibility fixes 2026-02-13 18:55:54 -05:00
raptor 602bb611a4 Fix some issues in IDA 9.1 2025-09-09 14:04:28 +02:00
raptor c5cc03d7dc Improve IDA 9.2 support without breaking previous supported versions 2025-09-09 13:34:47 +02:00
raptor 720b036a9d Start working on IDA 9.2 port 2025-09-09 11:43:22 +02:00
raptor b8ff268c86 IDA 9.x SDK fix 2025-08-28 17:47:43 +02:00
gaasedelen 88ceac7b19 tweak to make lighthouse copy *all* in-box theme files to the user theme directory... 2024-02-06 13:59:33 -05:00
gaasedelen 9a56463103 make update dialog look a little less janky 2024-02-06 13:30:54 -05:00
9 changed files with 101 additions and 29 deletions
+2 -2
View File
@@ -20,5 +20,5 @@
"type": [
"helper"
],
"version": "0.9.3"
}
"version": "0.9.4"
}
+8 -1
View File
@@ -337,7 +337,7 @@ class CoverageDirector(object):
"""
Subscribe a callback for director refresh events.
"""
register_callback(self._refreshed_callbacks, callback)
return register_callback(self._refreshed_callbacks, callback)
def _notify_refreshed(self):
"""
@@ -345,6 +345,13 @@ class CoverageDirector(object):
"""
notify_callback(self._refreshed_callbacks)
def unregister_refreshed(self, callback_ref):
"""
Unsubscribe a callback reference from director refresh events.
"""
unregister_callback(self._refreshed_callbacks, callback_ref)
#----------------------------------------------------------------------
# Batch Loading
#----------------------------------------------------------------------
+3 -2
View File
@@ -25,7 +25,7 @@ class LighthouseCore(object):
# Plugin Metadata
#--------------------------------------------------------------------------
PLUGIN_VERSION = "0.9.3"
PLUGIN_VERSION = "0.9.4-DEV"
AUTHORS = "Markus Gaasedelen"
DATE = "2024"
@@ -190,7 +190,8 @@ class LighthouseCore(object):
"""
for lctx in self.lighthouse_contexts.values():
lctx.director.refresh_theme()
lctx.coverage_overview.refresh_theme()
if lctx.coverage_overview:
lctx.coverage_overview.refresh_theme()
lctx.painter.force_repaint()
def open_coverage_overview(self, dctx=None):
+24 -1
View File
@@ -29,6 +29,7 @@ class CoverageOverview(object):
self.lctx.coverage_overview = self
self.initialized = False
self._refreshed_callback = None
# see the EventProxy class below for more details
self._events = EventProxy(self)
@@ -42,7 +43,7 @@ class CoverageOverview(object):
self.refresh()
# register for cues from the director
self.director.refreshed(self.refresh)
self._refreshed_callback = self.director.refreshed(self.refresh)
#--------------------------------------------------------------------------
# Pseudo Widget Functions
@@ -64,11 +65,27 @@ class CoverageOverview(object):
"""
The CoverageOverview is being hidden / deleted.
"""
if self.widget is None:
return
if self._refreshed_callback:
self.director.unregister_refreshed(self._refreshed_callback)
self._refreshed_callback = None
if self.lctx.coverage_overview is self:
self.lctx.coverage_overview = None
self.initialized = False
self._combobox = None
self._shell = None
self._toolbar = None
self._table_view = None
self._table_controller = None
self._table_model = None
self._settings_button = None
self._settings_menu = None
self._shell_elements = None
self._events = None
self.widget = None
#--------------------------------------------------------------------------
@@ -229,6 +246,9 @@ class CoverageOverview(object):
"""
Refresh the Coverage Overview.
"""
if not (self._table_model and self._shell and self._combobox):
return
self._table_model.refresh()
self._shell.refresh()
self._combobox.refresh()
@@ -238,6 +258,9 @@ class CoverageOverview(object):
"""
Update visual elements based on theme change.
"""
if not (self._table_view and self._table_model and self._shell and self._combobox):
return
self._table_view.refresh_theme()
self._table_model.refresh_theme()
self._shell.refresh_theme()
+13 -6
View File
@@ -1,9 +1,8 @@
import os
import json
import struct
import glob
import shutil
import logging
import traceback
# NOTE: Py2/Py3 compat
try:
@@ -260,8 +259,13 @@ class LighthousePalette(object):
user_theme_dir = self.get_user_theme_dir()
makedirs(user_theme_dir)
# enumerate all in-box / default themes
plugin_theme_dir = self.get_plugin_theme_dir()
json_files = glob.glob(os.path.join(plugin_theme_dir, "*.json"))
# copy the default themes into the user directory if they don't exist
for theme_name in self._default_themes.values():
for default_theme_file in json_files:
theme_name = os.path.basename(default_theme_file)
#
# check if lighthouse has copied the default themes into the user
@@ -274,8 +278,7 @@ class LighthousePalette(object):
continue
# copy the in-box themes to the user theme directory
plugin_theme_file = os.path.join(self.get_plugin_theme_dir(), theme_name)
shutil.copy(plugin_theme_file, user_theme_file)
shutil.copy(default_theme_file, user_theme_file)
#
# if the user tries to switch themes, ensure the file dialog will start
@@ -546,7 +549,11 @@ class LighthousePalette(object):
# lmao, don't ask me why they forgot about this attribute from 5.0 - 5.6
#
if disassembler.NAME == "BINJA":
# IDA 9.2 SDK fix: migrate from PyQt5 to PySide6
# https://docs.hex-rays.com/user-guide/plugins/migrating-pyqt5-code-to-pyside6
if disassembler.NAME == "BINJA" or (disassembler.NAME == "IDA"
and disassembler._version_major == 9
and disassembler._version_minor >= 2):
test_widget.setAttribute(QtCore.Qt.WA_DontShowOnScreen)
else:
test_widget.setAttribute(103) # taken from http://doc.qt.io/qt-5/qt.html
@@ -150,14 +150,21 @@ class IDACoreAPI(DisassemblerCoreAPI):
self._dockable_factory[dockable_name] = create_widget_callback
def create_dockable_widget(self, parent, dockable_name):
import sip
# IDA 9.2 SDK fix: migrate from PyQt5 to PySide6
# https://docs.hex-rays.com/user-guide/plugins/migrating-pyqt5-code-to-pyside6
if USING_PYSIDE6:
from shiboken6 import wrapInstance
else:
import sip
def wrapInstance(ptr, base=None):
return sip.wrapinstance(int(ptr), base)
# create a dockable widget, and save a reference to it for later use
twidget = idaapi.create_empty_widget(dockable_name)
self._dockable_widgets[dockable_name] = twidget
# cast the IDA 'twidget' as a Qt widget for use
widget = sip.wrapinstance(int(twidget), QtWidgets.QWidget)
widget = wrapInstance(int(twidget), QtWidgets.QWidget)
widget.name = dockable_name
widget.visible = False
@@ -226,7 +233,12 @@ class IDACoreAPI(DisassemblerCoreAPI):
# attempt to generate an 'html' dump of the first 0x20 bytes (instructions)
ida_fd = idaapi.fopenWT(path)
idaapi.gen_file(idaapi.OFILE_LST, ida_fd, imagebase, imagebase+0x20, idaapi.GENFLG_GENHTML)
idaapi.eclose(ida_fd)
# IDA 9.x SDK fix: removed `idaapi.eclose`, added `ida_fpro.qfclose`
if int(idaapi.get_kernel_version()[0]) >= 9:
import ida_fpro
ida_fpro.qfclose(ida_fd)
else:
idaapi.eclose(ida_fd)
# read the dumped text
with open(path, "r") as fd:
@@ -290,9 +302,16 @@ class IDACoreAPI(DisassemblerCoreAPI):
# touch the target form so we know it is populated
self._touch_ida_window(twidget)
# locate the Qt Widget for a form and take 1px image slice of it
import sip
widget = sip.wrapinstance(int(twidget), QtWidgets.QWidget)
# IDA 9.2 SDK fix: migrate from PyQt5 to PySide6
# https://docs.hex-rays.com/user-guide/plugins/migrating-pyqt5-code-to-pyside6
if USING_PYSIDE6:
from shiboken6 import wrapInstance
else:
import sip
def wrapInstance(ptr, base=None):
return sip.wrapinstance(int(ptr), base)
widget = wrapInstance(int(twidget), QtWidgets.QWidget)
pixmap = widget.grab(QtCore.QRect(0, 10, widget.width(), 1))
# convert the raw pixmap into an image (easier to interface with)
@@ -592,4 +611,3 @@ def lex_citem_indexes(line):
# return all the citem indexes extracted from this line of text
return indexes
+11 -1
View File
@@ -146,6 +146,7 @@ def register_callback(callback_list, callback):
# 'register' the callback
callback_list.append(callback_ref)
return callback_ref
def notify_callback(callback_list, *args):
"""
@@ -207,4 +208,13 @@ def notify_callback(callback_list, *args):
# remove the deleted callbacks
for callback_ref in cleanup:
callback_list.remove(callback_ref)
def unregister_callback(callback_list, callback_ref):
"""
Remove a previously-registered callback reference.
"""
try:
callback_list.remove(callback_ref)
except (ValueError, AttributeError):
pass
+12 -7
View File
@@ -29,11 +29,16 @@ USING_PYSIDE6 = False
# specific dependencies in here...
#
# IDA 9.2 SDK fix: migrate from PyQt5 to PySide6
# https://docs.hex-rays.com/user-guide/plugins/migrating-pyqt5-code-to-pyside6
try:
import ida_idaapi
USING_IDA = True
import idaapi
ver_major, ver_minor = map(int, idaapi.get_kernel_version().split("."))
USING_NEW_IDA = ver_major == 9 and ver_minor >= 2
USING_OLD_IDA = not(USING_NEW_IDA)
except ImportError:
USING_IDA = False
USING_NEW_IDA = False
USING_OLD_IDA = False
try:
import binaryninjaui
@@ -47,8 +52,8 @@ except ImportError:
# PyQt5 Compatibility
#------------------------------------------------------------------------------
# attempt to load PyQt5 (IDA 7.0+)
if USING_IDA:
# attempt to load PyQt5 (IDA from 7.0 to 9.1)
if USING_OLD_IDA:
try:
import PyQt5.QtGui as QtGui
@@ -91,8 +96,8 @@ if not QT_AVAILABLE and USING_OLD_BINJA:
# PySide6 Compatibility
#------------------------------------------------------------------------------
# If all else fails, try to load PySide6 (New Binary Ninja)
if not QT_AVAILABLE and USING_NEW_BINJA:
# If all else fails, try to load PySide6 (New Binary Ninja and IDA)
if not QT_AVAILABLE and (USING_NEW_BINJA or USING_NEW_IDA):
try:
import PySide6.QtGui as QtGui
+3 -2
View File
@@ -32,6 +32,7 @@ def async_update_check(current_version, callback):
An async worker thread to check for an plugin update.
"""
logger.debug("Checking for update...")
current_version = "v" + current_version
try:
response = urlopen(UPDATE_URL, timeout=5.0)
@@ -54,8 +55,8 @@ def async_update_check(current_version, callback):
# notify the user if an update is available
update_message = "An update is available for Lighthouse!\n\n" \
" - Latest Version: %s\n" % (remote_version) + \
" - Current Version: %s\n\n" % (current_version) + \
" - Latest Version: %s\n" % (remote_version) + \
" - Current Version: %s\n\n" % (current_version) + \
"Please go download the update from GitHub."
callback(update_message)