├── palette ├── res │ ├── theme │ │ ├── styles.json │ │ ├── ayumirage │ │ │ ├── styles.json │ │ │ ├── input-search.png │ │ │ └── window.css │ │ ├── ayuwhite │ │ │ ├── styles.json │ │ │ ├── input-search.png │ │ │ └── window.css │ │ ├── solarized-dark │ │ │ ├── styles.json │ │ │ └── window.css │ │ ├── solarized-light │ │ │ ├── styles.json │ │ │ └── window.css │ │ ├── ayudark │ │ │ ├── bg.png │ │ │ ├── styles.json │ │ │ ├── searchbox.css │ │ │ ├── item.css │ │ │ └── window.css │ │ ├── gitkraken │ │ │ └── window.css │ │ └── window.css │ ├── theme_bundle.qrc │ └── config.json ├── include │ └── palette │ │ ├── widgets.h │ │ ├── action.h │ │ ├── utils.h │ │ ├── api.h │ │ ├── search_services │ │ └── basic_service.h │ │ ├── widgets │ │ ├── palette_items.h │ │ ├── item.h │ │ └── palette.h │ │ ├── observers.h │ │ └── filter.h ├── src │ ├── widgets │ │ ├── items.cpp │ │ ├── item.cpp │ │ └── palette.cpp │ ├── bindings │ │ ├── pypalette.h │ │ └── pypalette.cpp │ ├── ida │ │ ├── python.cpp │ │ ├── plugin.h │ │ ├── CMakeLists.txt │ │ └── plugin.cpp │ ├── api.cpp │ ├── utils.cpp │ ├── filter.cpp │ └── search_services │ │ ├── basic_service.cpp │ │ └── fts_fuzzy_match.h └── CMakeLists.txt ├── .clang-format ├── screenshots ├── 1.png ├── 2.png └── 3.png ├── .gitignore ├── .gitmodules ├── standalone ├── CMakeLists.txt └── standalone.cpp ├── LICENSE ├── CMakeLists.txt ├── CHANGELOG.md ├── README.md └── azure-pipelines.yml /palette/res/theme/styles.json: -------------------------------------------------------------------------------- 1 | { 2 | "shadow": 30 3 | } -------------------------------------------------------------------------------- /palette/res/theme/ayumirage/styles.json: -------------------------------------------------------------------------------- 1 | { 2 | "shadow": 15 3 | } -------------------------------------------------------------------------------- /palette/res/theme/ayuwhite/styles.json: -------------------------------------------------------------------------------- 1 | { 2 | "shadow": 15 3 | } -------------------------------------------------------------------------------- /palette/res/theme/solarized-dark/styles.json: -------------------------------------------------------------------------------- 1 | { 2 | "shadow": 30 3 | } -------------------------------------------------------------------------------- /palette/res/theme/solarized-light/styles.json: -------------------------------------------------------------------------------- 1 | { 2 | "shadow": 30 3 | } -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | Standard: c++17 3 | BasedOnStyle: Google 4 | -------------------------------------------------------------------------------- /screenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinmo/ifred/HEAD/screenshots/1.png -------------------------------------------------------------------------------- /screenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinmo/ifred/HEAD/screenshots/2.png -------------------------------------------------------------------------------- /screenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinmo/ifred/HEAD/screenshots/3.png -------------------------------------------------------------------------------- /palette/res/theme/ayudark/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinmo/ifred/HEAD/palette/res/theme/ayudark/bg.png -------------------------------------------------------------------------------- /palette/res/theme/ayuwhite/input-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinmo/ifred/HEAD/palette/res/theme/ayuwhite/input-search.png -------------------------------------------------------------------------------- /palette/res/theme/ayumirage/input-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jinmo/ifred/HEAD/palette/res/theme/ayumirage/input-search.png -------------------------------------------------------------------------------- /palette/include/palette/widgets.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | -------------------------------------------------------------------------------- /palette/res/theme/ayudark/styles.json: -------------------------------------------------------------------------------- 1 | { 2 | "itemHeight": 47, 3 | "itemMarginLeft": 25, 4 | "itemMarginTop": 8, 5 | "itemHoverBackground": "#0c1014", 6 | "shadow": 5 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .vs 3 | # for test 4 | build 5 | # CLion files 6 | .idea 7 | cmake-build-* 8 | 9 | CMakeSettings\.json 10 | 11 | Qt/* 12 | build/* 13 | ida-sdk* 14 | scripts/* 15 | -------------------------------------------------------------------------------- /palette/res/theme_bundle.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | theme/styles.json 4 | theme/window.css 5 | config.json 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "palette/ida-cmake"] 2 | path = palette/ida-cmake 3 | url = https://github.com/Jinmo/ida-cmake.git 4 | [submodule "palette/pybind11"] 5 | path = palette/pybind11 6 | url = https://github.com/pybind/pybind11.git 7 | -------------------------------------------------------------------------------- /palette/res/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "blacklist": [ 3 | "^Window.", 4 | "^navbox.", 5 | "^Jump", 6 | "ifred:enter", 7 | "FormsTest", 8 | "^sle:", 9 | "TestDesktop", 10 | "FocusCLI2", 11 | "^msglist:" 12 | ] 13 | } -------------------------------------------------------------------------------- /palette/include/palette/action.h: -------------------------------------------------------------------------------- 1 | #ifndef ACTION_H 2 | #define ACTION_H 3 | #include 4 | #include 5 | 6 | struct Action { 7 | QString id, name, shortcut; 8 | QString description; 9 | }; 10 | 11 | Q_DECLARE_METATYPE(Action); 12 | 13 | #endif -------------------------------------------------------------------------------- /palette/res/theme/ayudark/searchbox.css: -------------------------------------------------------------------------------- 1 | QLineEdit { 2 | font-family: Segoe UI; 3 | height: 62px !important; 4 | font-size: 25px; 5 | color: rgb(191, 186, 176); 6 | border: none; 7 | width: 500px; 8 | padding-left: 60px; 9 | background: url(C:/Users/berry/take/theme/bg.png) no-repeat; 10 | background-color: rgb(19, 24, 29); 11 | } -------------------------------------------------------------------------------- /standalone/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | 3 | project(standalone) 4 | 5 | set(CMAKE_AUTOMOC ON) 6 | set(CMAKE_AUTORCC ON) 7 | 8 | # Locate Qt. 9 | find_package(Qt6Widgets CONFIG REQUIRED) 10 | 11 | add_executable(standalone standalone.cpp) 12 | target_link_libraries(standalone Qt6::Widgets palette) 13 | -------------------------------------------------------------------------------- /palette/res/theme/ayudark/item.css: -------------------------------------------------------------------------------- 1 | div { 2 | font-size: 18px; 3 | font-family: Segoe UI; 4 | color: rgb(191, 186, 176); 5 | color: rgb(71, 83, 89); 6 | } 7 | 8 | em { 9 | font-weight: bold; 10 | font-style: normal; 11 | color: #ff8f40; 12 | } 13 | 14 | span { 15 | font-size: 13px; 16 | font-family: Segoe UI; 17 | color: transparent; 18 | display: none; 19 | } -------------------------------------------------------------------------------- /palette/include/palette/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static bool static_updated; 9 | 10 | QJsonObject json(const char* filename, bool force_update = false); 11 | 12 | QString loadFile(const char* filename, bool force_update = false, 13 | bool& updated = static_updated); 14 | 15 | // File handler 16 | typedef QString (*pathhandler_t)(char const* path); 17 | extern pathhandler_t pluginPath; -------------------------------------------------------------------------------- /palette/include/palette/api.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void PALETTE_EXPORT show_palette(const QString& name, 9 | const QString& placeholder, 10 | const QVector& actions, 11 | const QString& closeKey, ActionHandler func); 12 | void PALETTE_EXPORT cleanup_palettes(); 13 | void PALETTE_EXPORT set_path_handler(pathhandler_t handler); 14 | -------------------------------------------------------------------------------- /palette/include/palette/search_services/basic_service.h: -------------------------------------------------------------------------------- 1 | #ifndef BASIC_SERVICE_H 2 | #define BASIC_SERVICE_H 3 | 4 | #include 5 | 6 | class BasicService : public SearchService { 7 | const QVector actions_; // immutable 8 | 9 | std::vector indexes_, recent_indexes_; 10 | QHash recent_actions_; 11 | QSettings storage_; 12 | bool canceled_; 13 | 14 | void search(QString keyword); 15 | 16 | public: 17 | BasicService(QObject* parent, const QString& palette_name, 18 | const QVector& actions); 19 | void cancel() override { canceled_ = true; } 20 | bool runInSeparateThread() override; 21 | }; 22 | 23 | #endif -------------------------------------------------------------------------------- /palette/include/palette/widgets/palette_items.h: -------------------------------------------------------------------------------- 1 | #ifndef ITEMS_H 2 | #define ITEMS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | class PaletteItems : public QListView { 12 | PaletteFilter* model_; 13 | ItemDelegate* item_delegate_; 14 | 15 | public: 16 | PaletteFilter* model() { return model_; } 17 | 18 | explicit PaletteItems(QWidget* parent, const QString& palette_name, 19 | SearchService* search_service); 20 | using QAbstractItemView::keyPressEvent; 21 | 22 | ~PaletteItems() { delete model_; } 23 | }; 24 | 25 | #endif // ITEMS_H 26 | -------------------------------------------------------------------------------- /palette/src/widgets/items.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | PaletteItems::PaletteItems(QWidget* parent, const QString& palette_name, 4 | SearchService* search_service) 5 | : QListView(parent), 6 | model_(new PaletteFilter(this, palette_name, search_service)), 7 | item_delegate_(new ItemDelegate(this)) { 8 | // Optimization 9 | setUniformItemSizes(true); 10 | 11 | setLineWidth(0); 12 | setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); 13 | 14 | QAbstractItemView::setModel(model_); 15 | QAbstractItemView::setItemDelegate(item_delegate_); 16 | 17 | connect(model_, &PaletteFilter::filteringDone, this, [=](int index) { 18 | item_delegate_->setRecents(index); 19 | setCurrentIndex(model_->index(0, 0)); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /palette/src/bindings/pypalette.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #undef NDEBUG 9 | #pragma push_macro("slots") 10 | #undef slots 11 | #include 12 | #include 13 | #include 14 | #pragma pop_macro("slots") 15 | 16 | namespace py = pybind11; 17 | 18 | class PyPalette { 19 | QVector actions_; 20 | QString name_; 21 | QString placeholder_; 22 | 23 | public: 24 | PyPalette(const std::string& name, const std::string& placeholder, 25 | const py::list& entries); 26 | 27 | const QVector& actions() { return actions_; } 28 | const QString& name() { return name_; } 29 | const QString& placeholder() { return placeholder_; } 30 | }; 31 | -------------------------------------------------------------------------------- /palette/res/theme/ayudark/window.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: Segoe UI; 3 | font-size: 15px; 4 | background: rgb(19, 24, 29); 5 | border-radius: 6px; 6 | } 7 | 8 | QScrollBar:vertical * { 9 | background-color: #000; 10 | } 11 | 12 | QScrollBar:vertical { 13 | border: none; 14 | background: none; 15 | background-color: #000; 16 | width: 12px; 17 | border-radius: 6px; 18 | } 19 | 20 | QScrollBar::handle:vertical { 21 | background: rgba(255,255,255,0.3); 22 | min-height: 20px; 23 | border-radius: 6px; 24 | } 25 | 26 | QScrollBar::add-line:vertical { 27 | width: 0; 28 | height: 0; 29 | subcontrol-position: bottom; 30 | subcontrol-origin: margin; 31 | } 32 | 33 | QScrollBar::sub-line:vertical { 34 | width: 0; 35 | height: 0; 36 | subcontrol-position: top; 37 | subcontrol-origin: margin; 38 | } 39 | 40 | QListView { 41 | border: none; 42 | min-height: 300px; 43 | max-height: 300px; 44 | } -------------------------------------------------------------------------------- /palette/src/ida/python.cpp: -------------------------------------------------------------------------------- 1 | #if PYTHON_SUPPORT 2 | #include 3 | 4 | #include "plugin.h" 5 | 6 | void init_python_module(); 7 | void initpy(); 8 | 9 | class gil_scoped_acquire { 10 | PyGILState_STATE state; 11 | bool reset; 12 | 13 | public: 14 | gil_scoped_acquire() { 15 | reset = false; 16 | state = PyGILState_Ensure(); 17 | } 18 | 19 | ~gil_scoped_acquire() { 20 | if (!reset) PyGILState_Release(state); 21 | } 22 | }; 23 | 24 | ssize_t idaapi on_python_loaded(void*, int notification_code, va_list va) { 25 | auto info = va_arg(va, plugin_info_t*); 26 | 27 | if (notification_code == ui_plugin_loaded && 28 | !strcmp(info->org_name, "IDAPython")) { 29 | initpy(); 30 | unhook_from_notification_point(HT_UI, on_python_loaded, nullptr); 31 | } 32 | 33 | return 0; 34 | } 35 | 36 | void initpy() { 37 | if (!Py_IsInitialized()) 38 | hook_to_notification_point(HT_UI, on_python_loaded, nullptr); 39 | else { 40 | gil_scoped_acquire gil; 41 | init_python_module(); 42 | } 43 | } 44 | 45 | #endif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 jinmo123 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /palette/include/palette/widgets/item.h: -------------------------------------------------------------------------------- 1 | #ifndef QITEM_H 2 | #define QITEM_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | class ItemDelegate : public QStyledItemDelegate { 11 | QTextDocument* document_; 12 | int recents_; 13 | 14 | bool style_updated_; 15 | QSize cached_size_; 16 | 17 | public: 18 | explicit ItemDelegate(QWidget* parent) 19 | : QStyledItemDelegate(parent), 20 | document_(new QTextDocument(this)), 21 | recents_(0), 22 | style_updated_(false) { 23 | updateCSS(loadFile("theme/window.css")); 24 | } 25 | 26 | void updateCSS(const QString& style_sheet); 27 | 28 | void paint(QPainter* painter, const QStyleOptionViewItem& option, 29 | const QModelIndex& index) const override; 30 | 31 | QSize sizeHint(const QStyleOptionViewItem& option, 32 | const QModelIndex& index) const override; 33 | 34 | QTextDocument* renderAction(bool just_return, const QString& className, 35 | const QString& keyword, Action& action); 36 | 37 | void setRecents(int index) { recents_ = index; } 38 | }; 39 | 40 | #endif // QITEM_H 41 | -------------------------------------------------------------------------------- /palette/src/ida/plugin.h: -------------------------------------------------------------------------------- 1 | #pragma warning(push) 2 | 3 | #ifdef _WIN32 4 | #include 5 | #endif 6 | 7 | #if _MSC_VER 8 | // Visual Studio 9 | #pragma warning(disable : 4244) 10 | #pragma warning(disable : 4267) 11 | #endif 12 | 13 | #if __clang__ 14 | // clang 15 | #pragma clang diagnostic ignored "-Wnullability-completeness" 16 | #pragma clang diagnostic ignored "-Wvarargs" 17 | #pragma clang diagnostic ignored "-Wlogical-op-parentheses" 18 | #endif 19 | 20 | // clang header uses the functions 21 | #define USE_STANDARD_FILE_FUNCTIONS 22 | #define USE_DANGEROUS_FUNCTIONS 23 | 24 | #define __DEFINE_PLUGIN_RETURN_CODES__ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #if IDA_SDK_VERSION >= 900 39 | #define SHOULD_USE_TYPEINF 1 40 | #else 41 | #define SHOULD_USE_TYPEINF 0 42 | #endif 43 | 44 | #if IDA_SDK_VERSION < 900 45 | #define get_ordinal_count get_ordinal_qty 46 | #include 47 | #include 48 | #endif 49 | 50 | #pragma warning(pop) 51 | -------------------------------------------------------------------------------- /palette/include/palette/observers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | class CSSObserver : public QFileSystemWatcher { 9 | const char* filename_; 10 | 11 | public: 12 | CSSObserver(QWidget* parent, const char* filename) 13 | : QFileSystemWatcher(parent), filename_(filename) { 14 | addPath(pluginPath(filename)); 15 | connect(this, &QFileSystemWatcher::fileChanged, this, 16 | &CSSObserver::updated); 17 | 18 | updated(); 19 | } 20 | 21 | void updated() { parentWidget()->setStyleSheet(loadFile(filename_)); } 22 | 23 | QWidget* parentWidget() { return static_cast(parent()); } 24 | }; 25 | 26 | class JSONObserver : public QFileSystemWatcher { 27 | const char* filename_; 28 | 29 | protected: 30 | JSONObserver(QObject* parent, const char* filename = "settings.json") 31 | : QFileSystemWatcher(parent), filename_(filename) { 32 | addPath(pluginPath(filename)); 33 | } 34 | 35 | void updated() { onUpdated(json(filename_, true)); } 36 | 37 | void activate() { 38 | connect(this, &QFileSystemWatcher::fileChanged, this, 39 | &JSONObserver::updated); 40 | updated(); 41 | } 42 | 43 | virtual void onUpdated(const QJsonObject& config) = 0; 44 | }; 45 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMakeList.txt : Top-level CMake project file, do global configuration 2 | # and include sub-projects here. 3 | # 4 | cmake_minimum_required (VERSION 3.8) 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_OSX_ARCHITECTURES x86_64) 7 | 8 | project ("palette") 9 | set(BUILD_EXAMPLES OFF CACHE BOOL "") 10 | 11 | # Prevent CMake from automatically adding rpaths 12 | set(CMAKE_SKIP_BUILD_RPATH TRUE) 13 | set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) 14 | set(CMAKE_INSTALL_RPATH "") 15 | set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE) 16 | 17 | # Prevent full paths from being embedded in binaries 18 | # Remap source paths to relative paths in debug info 19 | if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") 20 | get_filename_component(PROJECT_SOURCE_DIR_ABS "${CMAKE_SOURCE_DIR}" ABSOLUTE) 21 | # Remap absolute source directory to a relative path 22 | add_compile_options(-fdebug-prefix-map="${PROJECT_SOURCE_DIR_ABS}"=.) 23 | # Also remap build directory 24 | get_filename_component(PROJECT_BINARY_DIR_ABS "${CMAKE_BINARY_DIR}" ABSOLUTE) 25 | add_compile_options(-fdebug-prefix-map="${PROJECT_BINARY_DIR_ABS}"=.) 26 | endif() 27 | 28 | add_subdirectory(palette/pybind11) 29 | 30 | # Include sub-projects. 31 | add_subdirectory ("palette") 32 | 33 | if (${BUILD_EXAMPLES}) 34 | add_subdirectory ("standalone") 35 | endif () 36 | -------------------------------------------------------------------------------- /palette/src/api.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | CommandPalette* g_current_widget; 8 | pathhandler_t pluginPath; 9 | 10 | template 11 | static void postToThread2(F&& fun, QThread* thread = qApp->thread()) { 12 | QObject* obj = QAbstractEventDispatcher::instance(thread); 13 | Q_ASSERT(obj); 14 | QObject src; 15 | QObject::connect(&src, &QObject::destroyed, obj, std::forward(fun), 16 | Qt::QueuedConnection); 17 | } 18 | 19 | static QWidget* getMainWindow() { 20 | // This is not too expensive. 21 | for (auto& widget : qApp->topLevelWidgets()) { 22 | if (qobject_cast(widget)) { 23 | return widget; 24 | } 25 | } 26 | return nullptr; 27 | } 28 | 29 | void show_palette(const QString& name, const QString& placeholder, 30 | const QVector& actions, const QString& closeKey, 31 | ActionHandler func) { 32 | postToThread2([=]() { 33 | g_current_widget = new CommandPalette(getMainWindow()); 34 | g_current_widget->setAttribute(Qt::WA_DeleteOnClose); 35 | g_current_widget->show(name, placeholder, actions, closeKey, func); 36 | }); 37 | } 38 | 39 | void cleanup_palettes() { Q_CLEANUP_RESOURCE(theme_bundle); } 40 | 41 | void set_path_handler(pathhandler_t handler) { pluginPath = handler; } 42 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2025-12-10] 4 | 5 | ### Added 6 | 7 | * Prevented cmake to add rpaths into binaries 8 | * Prevented cmake to add full paths into binaries 9 | * Prevented build system to add debug directories in windows build 10 | * Made binaries stripped except macOs arm64 11 | * On macOS with IDA, use Qt6 for headers but don't link - plugin will link 12 | IDA's Qt 13 | 14 | ### Fixed 15 | 16 | * Made necessary changes to build on macOS 17 | * config.json turns a folder instead of a file. now it stays as a json file 18 | * if macOS arm64 is stripped then IDA cannot load plugin. Error message is 19 | `MH_DYLIB is missing LC_ID_DYLIB`. On macOS ARM64 (Apple Silicon), the 20 | dynamic linker (dyld) is much stricter about Mach-O header consistency 21 | than it is on `x86_64`. 22 | * if IDA Pro is not loaded with a binary and the user tries to open ida 23 | palette by using shortcut, then IDA pro crashes. Now it opens and 24 | empty palette if no binary have loaded to prevent crash. 25 | 26 | ## [2025-09-14] 27 | 28 | ### Added 29 | 30 | * Support for IDA Pro v9.2 and Qt6 31 | 32 | ### Removed 33 | 34 | * IDA Pro v9.1 support removed 35 | * Qt5 support removed 36 | * Created a new branch for IDA pro v9.1 and Qt5 37 | 38 | ## [2024-10-29][2024-10-29] 39 | 40 | ### Added 41 | 42 | * IDA Pro v9.0 support 43 | * Prebuilt binaries provided 44 | 45 | [2025-12-10]: https://github.com/blue-devil/ifred/compare/v2025.09.14...HEAD 46 | [2025-09-14]: https://github.com/blue-devil/ifred/compare/v2024.10.29...v2025.09.14 47 | [2024-10-29]: https://github.com/blue-devil/ifred/releases/tag/v2024.10.29 48 | -------------------------------------------------------------------------------- /palette/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | 3 | include("ida-cmake/cmake/QtIDA.cmake") 4 | include("ida-cmake/cmake/IDA.cmake") 5 | 6 | # Some required settings 7 | set(PYTHON_SUPPORT OFF CACHE BOOL "") 8 | set(PYTHON_EXECUTABLE "" CACHE PATH "Python executable (x64 if >= 7.0)") 9 | 10 | include(GenerateExportHeader) 11 | 12 | if (${PYTHON_SUPPORT}) 13 | set(python_binding_src src/bindings/pypalette.cpp) 14 | set(python_libraries pybind11::module) 15 | set(python_support_definitions "PYTHON_SUPPORT=1") 16 | else () 17 | set(python_binding_src) 18 | set(python_libraries) 19 | set(python_support_definitions "PYTHON_SUPPORT=0") 20 | endif () 21 | 22 | set(sources src/search_services/basic_service.cpp 23 | "src/widgets/palette.cpp" 24 | "src/filter.cpp" 25 | "src/widgets/items.cpp" 26 | "src/widgets/item.cpp" 27 | ${python_binding_src} 28 | src/utils.cpp 29 | "src/api.cpp" 30 | res/theme_bundle.qrc 31 | "include/palette/search_services/basic_service.h" 32 | "include/palette/filter.h" 33 | "include/palette/widgets/palette.h" 34 | "include/palette/action.h" 35 | "include/palette/widgets.h" 36 | ) 37 | 38 | add_library(palette STATIC ${sources}) 39 | target_link_libraries(palette PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets ${python_libraries}) 40 | target_compile_definitions(palette PUBLIC "QT_NAMESPACE=QT" ${python_support_definitions}) 41 | 42 | target_include_directories(palette 43 | PUBLIC 44 | $ 45 | ${CMAKE_BINARY_DIR}/palette 46 | PRIVATE 47 | $ 48 | ${CMAKE_CURRENT_SOURCE_DIR}/src 49 | ) 50 | 51 | GENERATE_EXPORT_HEADER(palette) 52 | 53 | # IDA plugins 54 | add_subdirectory(src/ida) 55 | 56 | # Compile with fPIC 57 | set_target_properties(palette PROPERTIES POSITION_INDEPENDENT_CODE ON) 58 | -------------------------------------------------------------------------------- /palette/src/utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | QString loadFileFromBundle(const char* filename, QFile& file, bool& updated) { 6 | static bool resource_initialized; 7 | 8 | if (!resource_initialized) { 9 | Q_INIT_RESOURCE(theme_bundle); 10 | resource_initialized = true; 11 | 12 | QDir::addSearchPath("theme", pluginPath("theme/")); 13 | } 14 | 15 | QFile resFile(":/bundle/" + QString(filename)); 16 | 17 | updated = false; 18 | 19 | if (resFile.exists()) { 20 | if (!resFile.open(QIODevice::ReadOnly)) return QString(); 21 | auto bytes = resFile.readAll(); 22 | auto content = QString::fromUtf8(bytes); 23 | 24 | QFileInfo fileInfo(file.fileName()); 25 | QDir().mkpath(fileInfo.absolutePath()); 26 | 27 | if (file.open(QIODevice::WriteOnly)) { 28 | file.write(bytes); 29 | file.close(); 30 | } 31 | 32 | updated = true; 33 | 34 | return content; 35 | } else 36 | return QString(); 37 | } 38 | 39 | QString loadFile(const char* filename, bool force_update, bool& updated) { 40 | auto absolutePath = pluginPath(filename); 41 | QFile file(absolutePath); 42 | 43 | updated = false; 44 | 45 | if (!file.exists()) { 46 | // Check if it exists in bundle resource 47 | return loadFileFromBundle(filename, file, updated); 48 | } 49 | 50 | if (!file.open(QIODevice::ReadOnly)) return QString(); 51 | 52 | auto content = QString::fromUtf8(file.readAll()); 53 | updated = true; 54 | 55 | return content; 56 | } 57 | 58 | QHash cached_json; 59 | 60 | QJsonObject json(const char* filename, bool force_update) { 61 | bool updated; 62 | const QString& content_str = loadFile(filename, force_update, updated); 63 | 64 | if (!updated) return cached_json[filename].object(); 65 | 66 | const QByteArray& content = content_str.toUtf8(); 67 | QJsonDocument json(QJsonDocument::fromJson(content)); 68 | cached_json[filename] = json; 69 | 70 | return json.object(); 71 | } 72 | -------------------------------------------------------------------------------- /palette/include/palette/filter.h: -------------------------------------------------------------------------------- 1 | #ifndef PALETTE_FILTER_H 2 | #define PALETTE_FILTER_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | class SearchService; 10 | 11 | class PaletteFilter : public QAbstractItemModel { 12 | Q_OBJECT; 13 | 14 | QVector shown_items_; 15 | QString keyword_; 16 | SearchService* search_service_; 17 | QThread *search_worker_; 18 | 19 | public: 20 | PaletteFilter(QWidget* parent, const QString& palette_name, 21 | SearchService* search_service); 22 | 23 | // Public interface 24 | void setFilter(const QString& keyword); 25 | const QString& filter() { return keyword_; } 26 | 27 | SearchService* searchService() { return search_service_; } 28 | 29 | void setSearchService(SearchService* new_service); 30 | 31 | // Implementations 32 | QModelIndex index(int row, int column, 33 | const QModelIndex& parent = QModelIndex()) const override; 34 | QVariant data(const QModelIndex& index, 35 | int role = Qt::DisplayRole) const override; 36 | QModelIndex parent(const QModelIndex& index) const override { return {}; } 37 | 38 | int columnCount(const QModelIndex& parent) const override { return 1; } 39 | int rowCount(const QModelIndex& parent = QModelIndex()) const override; 40 | 41 | public slots: 42 | void onDoneSearching(QString keyword, QVector items, 43 | int recent_count); 44 | void onDestroy(); 45 | 46 | signals: 47 | void filteringDone(int recent_count); 48 | }; 49 | 50 | class SearchService : public QObject { 51 | Q_OBJECT; 52 | 53 | public: 54 | SearchService(QObject* parent) : QObject(parent){}; 55 | 56 | void search(QString keyword) { 57 | cancel(); 58 | emit startSearching(keyword); 59 | } 60 | 61 | virtual void cancel() = 0; 62 | virtual bool runInSeparateThread() = 0; 63 | 64 | signals: 65 | // Request 66 | void startSearching(QString keyword); 67 | void itemClicked(QString action_id); 68 | 69 | // Response 70 | void doneSearching(QString keyword, QVector actions, 71 | int recent_count); 72 | }; 73 | 74 | #endif // PALETTE_FILTER_H 75 | -------------------------------------------------------------------------------- /palette/src/filter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void PaletteFilter::setFilter(const QString &keyword) { 4 | search_service_->search(keyword); 5 | } 6 | 7 | void PaletteFilter::onDoneSearching(QString keyword, QVector items, 8 | int recent_count) { 9 | emit layoutAboutToBeChanged(); 10 | shown_items_ = items; 11 | keyword_ = keyword; 12 | emit layoutChanged(); 13 | emit filteringDone(recent_count); 14 | } 15 | 16 | void PaletteFilter::onDestroy() { 17 | search_service_->cancel(); 18 | search_worker_->quit(); 19 | search_worker_->wait(); 20 | } 21 | 22 | QModelIndex PaletteFilter::index(int row, int column, 23 | const QModelIndex &parent) const { 24 | return createIndex(row, column); 25 | } 26 | 27 | QVariant PaletteFilter::data(const QModelIndex &index, int role) const { 28 | if (role == Qt::DisplayRole) 29 | return QVariant::fromValue(shown_items_[index.row()]); 30 | else if (role == Qt::UserRole) 31 | return keyword_; 32 | return QVariant(); 33 | } 34 | 35 | int PaletteFilter::rowCount(const QModelIndex &parent) const { 36 | return shown_items_.count(); 37 | } 38 | 39 | void PaletteFilter::setSearchService(SearchService *new_service) { 40 | search_service_ = new_service; 41 | 42 | if (search_service_->runInSeparateThread()) { 43 | connect(search_worker_, &QObject::destroyed, search_service_, 44 | &QObject::deleteLater); 45 | search_service_->setParent(nullptr); 46 | search_service_->moveToThread(search_worker_); 47 | search_worker_->start(); 48 | } else { 49 | search_service_->setParent(this); 50 | } 51 | } 52 | PaletteFilter::PaletteFilter(QWidget *parent, const QString &palette_name, 53 | SearchService *search_service) 54 | : QAbstractItemModel(parent), 55 | shown_items_(), 56 | search_service_(nullptr), 57 | search_worker_(new QThread(this)) { 58 | setSearchService(search_service); 59 | connect(search_service_, &SearchService::doneSearching, this, 60 | &PaletteFilter::onDoneSearching, Qt::QueuedConnection); 61 | connect(this, &QObject::destroyed, this, &PaletteFilter::onDestroy); 62 | } 63 | -------------------------------------------------------------------------------- /palette/include/palette/widgets/palette.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "palette_export.h" 14 | 15 | typedef std::function ActionHandler; 16 | Q_DECLARE_METATYPE(ActionHandler); 17 | 18 | class CommandPalette : public QMainWindow { 19 | Q_OBJECT; 20 | 21 | public: 22 | CommandPalette(QWidget* parent = nullptr); 23 | 24 | void show(const QString& name, const QString& placeholder, 25 | const QVector& actions, const QString& closeKey, 26 | ActionHandler func); 27 | 28 | void show(const QString& name, const QString& placeholder, 29 | SearchService* searchService, const QString& closeKey, 30 | ActionHandler func); 31 | 32 | void focusOutEvent(QFocusEvent* event) override { close(); } 33 | }; 34 | 35 | class PaletteFrame : public QFrame { 36 | Q_OBJECT; 37 | 38 | QString name_; 39 | 40 | QLineEdit* searchbox_; 41 | PaletteItems* items_; 42 | QShortcut* shortcut_; 43 | 44 | // Registered shortcuts not overriden by Qt::ShortcutOverride event 45 | QHash registered_keys_; 46 | 47 | bool eventFilter(QObject* obj, QEvent* event) override; 48 | void showEvent(QShowEvent* event) override; 49 | 50 | void arrowPressed(int delta); 51 | 52 | template 53 | QShortcut* registerShortcut(QKeySequence sequence, T callback) { 54 | // QKeyEvent treats Meta key as Ctrl, but QAction doesn't. 55 | QKeySequence keySequence(sequence.toString().replace("Meta+", "Ctrl+")); 56 | 57 | auto* shortcut = new QShortcut(keySequence, this); 58 | connect(shortcut, &QShortcut::activated, callback); 59 | 60 | registered_keys_.insert(sequence, shortcut); 61 | 62 | return shortcut; 63 | } 64 | 65 | public: 66 | PaletteFrame(QWidget* parent, const QString& name, const QString& closeKey, 67 | SearchService* search_service); 68 | 69 | void setPlaceholderText(const QString& placeholder); 70 | 71 | signals: 72 | bool itemClicked(Action& action); 73 | }; 74 | -------------------------------------------------------------------------------- /palette/res/theme/gitkraken/window.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: Segoe UI; 3 | font-size: 20px; 4 | color: rgb(204, 204, 204); 5 | background: transparent; 6 | } 7 | 8 | 9 | /* Searchbox */ 10 | 11 | QLineEdit, 12 | QLineEdit:hover, 13 | QLineEdit:active { 14 | width: 720px; 15 | height: 29px; 16 | border: 4px solid rgb(77, 136, 255); 17 | border-radius: 5px; 18 | border-bottom-left-radius: 0px; 19 | border-bottom-right-radius: 0px; 20 | font-size: 18px; 21 | background: rgb(51, 54, 63); 22 | padding: 6px 0px 6px 12px; 23 | } 24 | 25 | 26 | /* List view and items */ 27 | 28 | QListView { 29 | background-color: transparent; 30 | border: none; 31 | } 32 | 33 | QListView::item { 34 | background: rgb(39, 42, 49); 35 | padding-left: 14px; 36 | padding-top: 6px; 37 | padding-bottom: 13px; 38 | border-bottom: 1px solid #000; 39 | } 40 | 41 | QListView::item:hover { 42 | background: rgb(43, 52, 70); 43 | border: 0; 44 | border-bottom: 1px solid #000; 45 | } 46 | 47 | QListView::item:selected { 48 | background: rgb(46, 61, 90) !important; 49 | } 50 | 51 | div, 52 | span, 53 | em { 54 | background: transparent; 55 | } 56 | 57 | em { 58 | /* Highlighted Text */ 59 | font-weight: bold; 60 | color: rgb(77, 136, 255); 61 | font-style: normal; 62 | } 63 | 64 | span { 65 | /* Action ID text */ 66 | color: transparent; 67 | } 68 | 69 | 70 | /* List scrollbar */ 71 | 72 | QScrollBar:vertical * { 73 | background-color: rgb(56, 59, 65); 74 | } 75 | 76 | QScrollBar:vertical { 77 | border: none; 78 | width: 12px; 79 | background: rgb(56, 59, 65); 80 | } 81 | 82 | QScrollBar::add-page:vertical, 83 | QScrollBar::sub-page:vertical { 84 | background: rgba(0, 0, 0, 0.1); 85 | } 86 | 87 | QScrollBar::handle:vertical { 88 | background: rgba(62, 66, 78); 89 | border: 1px solid #4d515c; 90 | min-height: 20px; 91 | border-radius: 6px; 92 | } 93 | 94 | QScrollBar::add-line:vertical { 95 | width: 0; 96 | height: 0; 97 | subcontrol-position: bottom; 98 | subcontrol-origin: margin; 99 | } 100 | 101 | QScrollBar::sub-line:vertical { 102 | width: 0; 103 | height: 0; 104 | subcontrol-position: top; 105 | subcontrol-origin: margin; 106 | } 107 | -------------------------------------------------------------------------------- /palette/res/theme/window.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: Segoe UI; 3 | font-size: 16px; 4 | background: rgb(37, 37, 38); 5 | color: rgb(204, 204, 204); 6 | } 7 | 8 | /* Searchbox */ 9 | 10 | QLineEdit, QLineEdit:hover, QLineEdit:active { 11 | font-size: 16px; 12 | width: 720px; 13 | height: 29px; 14 | margin: 7px 7px 8px 8px; 15 | padding: 0px 7px; 16 | border: 1px solid rgb(23, 91, 137); 17 | background: rgb(60, 60, 60); 18 | } 19 | 20 | /* List view and items */ 21 | 22 | QListView { 23 | border: none; 24 | padding: 0; 25 | margin: 0; 26 | min-height: 350px; 27 | } 28 | 29 | QListView::item { 30 | padding-left: 14px; 31 | padding-top: 3px; 32 | } 33 | 34 | QListView::item:hover { 35 | background: rgb(42, 45, 46); 36 | border: 0; 37 | } 38 | 39 | QListView::item:checked { 40 | padding-bottom: 1px; 41 | border-bottom: 1px solid #3f3f46; 42 | } 43 | 44 | QListView::item:selected { 45 | background: #062f4a !important; 46 | } 47 | 48 | table, td, div, p, span, em { 49 | background: transparent; 50 | } 51 | 52 | table { 53 | color: #fff; 54 | margin-bottom: 5px; 55 | } 56 | 57 | td.placeholder { 58 | font-size: 1px; 59 | width: 700px; 60 | } 61 | 62 | td { 63 | padding: 0; 64 | margin: 0; 65 | vertical-align: middle; 66 | } 67 | 68 | td.shortcut { 69 | float: right; 70 | padding-right: 18px; 71 | } 72 | 73 | td.description { 74 | color: #555; 75 | } 76 | 77 | em { 78 | /* Highlighted Text */ 79 | font-weight: bold; 80 | color: rgb(0, 151, 251); 81 | font-style: normal; 82 | } 83 | 84 | span { 85 | /* Action ID text */ 86 | font-family: Segoe UI; 87 | padding-left: 20px; 88 | color: transparent; 89 | } 90 | 91 | /* List scrollbar */ 92 | 93 | QScrollBar:vertical * { 94 | background-color: #000; 95 | } 96 | 97 | QScrollBar:vertical { 98 | border: none; 99 | width: 12px; 100 | background: rgb(37, 37, 38); 101 | } 102 | 103 | QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { 104 | background: rgba(0, 0, 0, 0.1); 105 | } 106 | 107 | QScrollBar::handle:vertical { 108 | background: rgba(255, 255, 255, 0.3); 109 | min-height: 20px; 110 | border-radius: 6px; 111 | } 112 | 113 | QScrollBar::add-line:vertical { 114 | width: 0; 115 | height: 0; 116 | subcontrol-position: bottom; 117 | subcontrol-origin: margin; 118 | } 119 | 120 | QScrollBar::sub-line:vertical { 121 | width: 0; 122 | height: 0; 123 | subcontrol-position: top; 124 | subcontrol-origin: margin; 125 | } -------------------------------------------------------------------------------- /palette/res/theme/solarized-dark/window.css: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Blue DeviL // SCT 3 | Masto : @bluedevil@infosec.exchange 4 | Repo : github.com/blue-devil 5 | Repo : gitlab.com/bluedevil 6 | Date : 13.09.2023 7 | */ 8 | * { 9 | font-family: Segoe UI; 10 | font-size: 16px; 11 | background: rgb(0, 43, 54); 12 | color: rgb(238, 232, 213); 13 | } 14 | 15 | /* Searchbox */ 16 | 17 | QLineEdit, QLineEdit:hover, QLineEdit:active { 18 | font-size: 16px; 19 | width: 720px; 20 | height: 29px; 21 | margin: 7px 7px 8px 8px; 22 | padding: 0px 7px; 23 | border: 1px solid rgb(42, 161, 152); 24 | background: rgb(7, 54, 66); 25 | } 26 | 27 | /* List view and items */ 28 | 29 | QListView { 30 | border: none; 31 | padding: 0; 32 | margin: 0; 33 | min-height: 350px; 34 | } 35 | 36 | QListView::item { 37 | padding-left: 14px; 38 | padding-top: 3px; 39 | } 40 | 41 | QListView::item:hover { 42 | background: rgb(7, 54, 66); 43 | border: 0; 44 | } 45 | 46 | QListView::item:checked { 47 | padding-bottom: 1px; 48 | border-bottom: 1px solid #3f3f46; 49 | } 50 | 51 | QListView::item:selected { 52 | background: #07514a !important; 53 | } 54 | 55 | table, td, div, p, span, em { 56 | background: transparent; 57 | } 58 | 59 | table { 60 | color: #fff; 61 | margin-bottom: 5px; 62 | } 63 | 64 | td.placeholder { 65 | font-size: 1px; 66 | width: 700px; 67 | } 68 | 69 | td { 70 | padding: 0; 71 | margin: 0; 72 | vertical-align: middle; 73 | } 74 | 75 | td.shortcut { 76 | float: right; 77 | padding-right: 18px; 78 | } 79 | 80 | td.description { 81 | color: #555; 82 | } 83 | 84 | em { 85 | /* Highlighted Text */ 86 | font-weight: bold; 87 | color: rgb(42, 161, 152); 88 | font-style: normal; 89 | } 90 | 91 | span { 92 | /* Action ID text */ 93 | font-family: Segoe UI; 94 | padding-left: 20px; 95 | color: transparent; 96 | } 97 | 98 | /* List scrollbar */ 99 | 100 | QScrollBar:vertical * { 101 | background-color: #000; 102 | } 103 | 104 | QScrollBar:vertical { 105 | border: none; 106 | width: 12px; 107 | background: rgb(5, 33, 40); 108 | } 109 | 110 | QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { 111 | background: rgba(0, 0, 0, 0.1); 112 | } 113 | 114 | QScrollBar::handle:vertical { 115 | background: rgba(2, 255, 255, 0.3); 116 | min-height: 20px; 117 | border-radius: 6px; 118 | } 119 | 120 | QScrollBar::add-line:vertical { 121 | width: 0; 122 | height: 0; 123 | subcontrol-position: bottom; 124 | subcontrol-origin: margin; 125 | } 126 | 127 | QScrollBar::sub-line:vertical { 128 | width: 0; 129 | height: 0; 130 | subcontrol-position: top; 131 | subcontrol-origin: margin; 132 | } 133 | -------------------------------------------------------------------------------- /palette/res/theme/solarized-light/window.css: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Blue DeviL // SCT 3 | Masto : @bluedevil@infosec.exchange 4 | Repo : github.com/blue-devil 5 | Repo : gitlab.com/bluedevil 6 | Date : 13.09.2023 7 | */ 8 | * { 9 | font-family: Segoe UI; 10 | font-size: 16px; 11 | background: rgb(147, 161, 161); 12 | color: rgb(0, 43, 54); 13 | } 14 | 15 | /* Searchbox */ 16 | 17 | QLineEdit, QLineEdit:hover, QLineEdit:active { 18 | font-size: 16px; 19 | width: 720px; 20 | height: 29px; 21 | margin: 7px 7px 8px 8px; 22 | padding: 0px 7px; 23 | border: 1px solid rgb(7, 54, 66); 24 | background: rgb(238, 232, 213); 25 | } 26 | 27 | /* List view and items */ 28 | 29 | QListView { 30 | border: none; 31 | padding: 0; 32 | margin: 0; 33 | min-height: 350px; 34 | } 35 | 36 | QListView::item { 37 | padding-left: 14px; 38 | padding-top: 3px; 39 | } 40 | 41 | QListView::item:hover { 42 | background: rgb(238, 232, 213); 43 | border: 0; 44 | } 45 | 46 | QListView::item:checked { 47 | padding-bottom: 1px; 48 | border-bottom: 1px solid #3f3f46; 49 | } 50 | 51 | QListView::item:selected { 52 | background: #a5dfd9 !important; 53 | } 54 | 55 | table, td, div, p, span, em { 56 | background: transparent; 57 | } 58 | 59 | table { 60 | color: #fff; 61 | margin-bottom: 5px; 62 | } 63 | 64 | td.placeholder { 65 | font-size: 1px; 66 | width: 700px; 67 | } 68 | 69 | td { 70 | padding: 0; 71 | margin: 0; 72 | vertical-align: middle; 73 | } 74 | 75 | td.shortcut { 76 | float: right; 77 | padding-right: 18px; 78 | } 79 | 80 | td.description { 81 | color: #555; 82 | } 83 | 84 | em { 85 | /* Highlighted Text */ 86 | font-weight: bold; 87 | color: rgb(13, 64, 60); 88 | font-style: normal; 89 | } 90 | 91 | span { 92 | /* Action ID text */ 93 | font-family: Segoe UI; 94 | padding-left: 20px; 95 | color: transparent; 96 | } 97 | 98 | /* List scrollbar */ 99 | 100 | QScrollBar:vertical * { 101 | background-color: #000; 102 | } 103 | 104 | QScrollBar:vertical { 105 | border: none; 106 | width: 12px; 107 | background: rgb(88, 110, 117); 108 | } 109 | 110 | QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { 111 | background: rgba(0, 0, 0, 0.1); 112 | } 113 | 114 | QScrollBar::handle:vertical { 115 | background: rgba(165, 223, 217, 0.7); 116 | min-height: 20px; 117 | border-radius: 6px; 118 | } 119 | 120 | QScrollBar::add-line:vertical { 121 | width: 0; 122 | height: 0; 123 | subcontrol-position: bottom; 124 | subcontrol-origin: margin; 125 | } 126 | 127 | QScrollBar::sub-line:vertical { 128 | width: 0; 129 | height: 0; 130 | subcontrol-position: top; 131 | subcontrol-origin: margin; 132 | } 133 | -------------------------------------------------------------------------------- /palette/res/theme/ayumirage/window.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: Segoe UI; 3 | font-size: 18px; 4 | background-color: #232834; 5 | border-radius: 6px; 6 | } 7 | 8 | QMainWindow, QLineEdit, QListView { 9 | width: 700px; 10 | } 11 | 12 | QLineEdit { 13 | font-family: Segoe UI; 14 | height: 62px !important; 15 | font-size: 25px; 16 | color: #cbccc6; 17 | border: none; 18 | padding-left: 80px; 19 | background: url(theme:input-search.png) no-repeat; 20 | background-color: #232834; 21 | } 22 | 23 | QLineEdit[text=""] { 24 | color: #232834; 25 | } 26 | 27 | QScrollBar:vertical * { 28 | background-color: #fff; 29 | } 30 | 31 | QScrollBar:vertical { 32 | border: none; 33 | width: 12px; 34 | border-radius: 6px; 35 | background: transparent; 36 | } 37 | 38 | QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { 39 | background: rgb(245,246,246); 40 | } 41 | 42 | QScrollBar::handle:vertical { 43 | background: rgb(219,220,221); 44 | min-height: 20px; 45 | border-radius: 6px; 46 | } 47 | 48 | QScrollBar::add-line:vertical { 49 | width: 0; 50 | height: 0; 51 | subcontrol-position: bottom; 52 | subcontrol-origin: margin; 53 | } 54 | 55 | QScrollBar::sub-line:vertical { 56 | width: 0; 57 | height: 0; 58 | subcontrol-position: top; 59 | subcontrol-origin: margin; 60 | } 61 | 62 | QListView { 63 | border: none; 64 | min-height: 300px; 65 | max-height: 300px; 66 | } 67 | 68 | QListView::item { 69 | padding-top: 8px; 70 | padding-left: 25px; 71 | } 72 | 73 | QListView::item:hover { 74 | background: transparent; 75 | } 76 | 77 | QListView::item:selected { 78 | background: #191e2a; 79 | } 80 | 81 | table, td, div, p, span, em { 82 | background: transparent; 83 | } 84 | 85 | table { 86 | color: rgb(80, 87, 97); 87 | font-size: 18px; 88 | font-family: Segoe UI; 89 | margin-bottom: 18px; 90 | } 91 | 92 | table.selected { 93 | color: #cbccc6; 94 | } 95 | 96 | td.placeholder { 97 | font-size: 1px; 98 | width: 700px; 99 | } 100 | 101 | td { 102 | padding: 0; 103 | margin: 0; 104 | vertical-align: middle; 105 | } 106 | 107 | td.shortcut { 108 | float: right; 109 | padding-right: 32px; 110 | } 111 | 112 | td.description { 113 | color: rgb(185, 190, 195); 114 | font-size: 14px; 115 | } 116 | 117 | em { 118 | /* Highlighted Text */ 119 | font-weight: bold; 120 | color: rgb(255, 204, 102); 121 | font-style: normal; 122 | } 123 | 124 | span { 125 | color: transparent; 126 | /* Action ID text */ 127 | font-family: Segoe UI; 128 | padding-left: 20px; 129 | color: transparent; 130 | } 131 | 132 | -------------------------------------------------------------------------------- /palette/res/theme/ayuwhite/window.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: Segoe UI; 3 | font-size: 18px; 4 | background: #fff; 5 | border-radius: 6px; 6 | } 7 | 8 | QMainWindow, QLineEdit, QListView { 9 | width: 700px; 10 | } 11 | 12 | QFrame { 13 | background: #fff; 14 | } 15 | 16 | QLineEdit { 17 | font-family: Segoe UI; 18 | height: 62px !important; 19 | font-size: 25px; 20 | color: rgb(97, 103, 107); 21 | border: none; 22 | padding-left: 80px; 23 | background: url(theme:input-search.png) no-repeat; 24 | } 25 | 26 | QLineEdit[text=""] { 27 | color: #fff; 28 | } 29 | 30 | QScrollBar:vertical * { 31 | background-color: #fff; 32 | } 33 | 34 | QScrollBar:vertical { 35 | border: none; 36 | width: 12px; 37 | border-radius: 6px; 38 | background: transparent; 39 | } 40 | 41 | QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { 42 | background: rgb(245,246,246); 43 | } 44 | 45 | QScrollBar::handle:vertical { 46 | background: rgb(219,220,221); 47 | min-height: 20px; 48 | border-radius: 6px; 49 | } 50 | 51 | QScrollBar::add-line:vertical { 52 | width: 0; 53 | height: 0; 54 | subcontrol-position: bottom; 55 | subcontrol-origin: margin; 56 | } 57 | 58 | QScrollBar::sub-line:vertical { 59 | width: 0; 60 | height: 0; 61 | subcontrol-position: top; 62 | subcontrol-origin: margin; 63 | } 64 | 65 | QListView { 66 | border: none; 67 | min-height: 300px; 68 | max-height: 300px; 69 | } 70 | 71 | QListView::item { 72 | padding-top: 8px; 73 | padding-left: 25px; 74 | } 75 | 76 | QListView::item:hover { 77 | background: #fff; 78 | } 79 | 80 | QListView::item:selected { 81 | background: #f5f5f5; 82 | } 83 | 84 | table, td, div, p, span, em { 85 | background: transparent; 86 | } 87 | 88 | table { 89 | color: rgb(149, 157, 166); 90 | font-size: 18px; 91 | font-family: Segoe UI; 92 | margin-bottom: 18px; 93 | } 94 | 95 | table.selected { 96 | color: rgb(107, 118, 128); 97 | } 98 | 99 | td.placeholder { 100 | font-size: 1px; 101 | width: 700px; 102 | } 103 | 104 | td { 105 | padding: 0; 106 | margin: 0; 107 | vertical-align: middle; 108 | } 109 | 110 | td.shortcut { 111 | float: right; 112 | padding-right: 32px; 113 | } 114 | 115 | td.description { 116 | color: rgb(185, 190, 195); 117 | font-size: 14px; 118 | } 119 | 120 | em { 121 | /* Highlighted Text */ 122 | font-weight: bold; 123 | color: #ff8f40; 124 | font-style: normal; 125 | } 126 | 127 | span { 128 | color: transparent; 129 | /* Action ID text */ 130 | font-family: Segoe UI; 131 | padding-left: 20px; 132 | color: transparent; 133 | } 134 | 135 | -------------------------------------------------------------------------------- /standalone/standalone.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define COUNT 500000 9 | 10 | QString random_key() { 11 | const char* keys[] = {"Shift"}; 12 | return keys[rand() % (sizeof(keys) / sizeof(keys[0]))]; 13 | } 14 | 15 | QVector testItems(int count=COUNT) { 16 | QVector action_list; 17 | 18 | action_list.reserve(count + 1); 19 | action_list.push_back(Action{"std::runtime_error", "raise exception!!", "", 20 | "Just raises an exception"}); 21 | 22 | for (int i = 0; i < count; i++) { 23 | auto id = QString::number(1LL * rand() * rand() * rand() * rand(), 36) + 24 | ":" + QString::number(i); 25 | action_list.push_back(Action{id, id + id, 26 | random_key() + "+" + QString::number(i % 10), 27 | QString::number(i) + "th element"}); 28 | } 29 | 30 | return action_list; 31 | } 32 | 33 | class CustomService final : public SearchService { 34 | QVector items_; 35 | public: 36 | CustomService() : SearchService(nullptr), items_(testItems(500)) { 37 | connect(this, &SearchService::startSearching, this, 38 | &CustomService::onSearch); 39 | } 40 | 41 | void onSearch(QString keyword) { 42 | std::random_device rd; 43 | std::mt19937 g(rd()); 44 | 45 | std::shuffle(items_.begin(), items_.end(), g); 46 | emit doneSearching(keyword, items_, 0); 47 | emit doneSearching( 48 | keyword, {Action{"install", "Press ENTER to install your extension."}}, 49 | 0); 50 | } 51 | 52 | void cancel() override {} 53 | bool runInSeparateThread() override { return true; } 54 | }; 55 | 56 | QString TestPluginPath(const char* name) { 57 | // Don't worry! also packaged with bundle theme! 58 | // Just point a writable path 59 | return QString("./path_to_plugin_theme/") + name; 60 | } 61 | 62 | int main(int argc, char** argv) { 63 | QApplication app(argc, argv); 64 | set_path_handler(TestPluginPath); 65 | QApplication::setQuitOnLastWindowClosed(true); 66 | 67 | CommandPalette palette; 68 | palette.setWindowFlags(Qt::Tool); 69 | palette.setAttribute( 70 | Qt::WA_TranslucentBackground, false); // enable MainWindow to be transparent 71 | // palette.show("test dynamic palette", "Enter item name (dynamic service) ...", 72 | // new CustomService(), "Ctrl+P", 73 | // [](Action& action) { qDebug() << action.id << action.name; return false; }); 74 | palette.show("", "Enter item name...", testItems(), 75 | "Ctrl+P", 76 | [](Action& action) { 77 | if (action.id == "std::runtime_error") { 78 | throw std::runtime_error("raised!"); 79 | } 80 | qDebug() << action.id << action.name << action.shortcut; 81 | return false; 82 | }); 83 | 84 | QApplication::exec(); 85 | } 86 | -------------------------------------------------------------------------------- /palette/src/ida/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include("../../ida-cmake/cmake/IDA.cmake") 2 | 3 | set(sources plugin.cpp python.cpp) 4 | 5 | add_ida_plugin(ida_palette ${sources}) 6 | 7 | if(APPLE AND IDA_INSTALL_DIR) 8 | target_compile_definitions(ida_palette PUBLIC "QT_NAMESPACE=QT") 9 | endif() 10 | 11 | if (${PYTHON_SUPPORT}) 12 | target_compile_definitions(ida_palette PRIVATE "PYTHON_SUPPORT=1") 13 | else () 14 | target_compile_definitions(ida_palette PRIVATE "PYTHON_SUPPORT=0") 15 | endif () 16 | 17 | # In OSX, make ifred use @executable_path/python[64].dylib to use IDAPython bindings 18 | # I'm not sure if it's a best way. 19 | if ((${PYTHON_SUPPORT}) AND (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")) 20 | if (IDA_EA_64) 21 | set(IDAPYTHON "${IDA_INSTALL_DIR}/plugins/idapython${PYTHON_VERSION_MAJOR}_64.dylib") 22 | else () 23 | set(IDAPYTHON "${IDA_INSTALL_DIR}/plugins/idapython${PYTHON_VERSION_MAJOR}.dylib") 24 | endif () 25 | target_link_libraries(ida_palette PRIVATE ${IDAPYTHON}) 26 | endif () 27 | 28 | # Link palette and python libraries 29 | target_link_libraries(ida_palette PRIVATE palette ${python_libraries}) 30 | # On macOS with IDA, use Qt6 for headers (but don't link - we link IDA's Qt) 31 | if(APPLE AND IDA_INSTALL_DIR) 32 | target_link_libraries(ida_palette PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets) 33 | set_target_properties(ida_palette PROPERTIES INTERFACE_LINK_LIBRARIES "") 34 | else () 35 | target_link_libraries(ida_palette 36 | PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets palette ${python_libraries}) 37 | endif() 38 | 39 | # On macOS with IDA, link against IDA's Qt frameworks 40 | if(APPLE AND IDA_INSTALL_DIR) 41 | get_filename_component(IDA_APP_DIR "${IDA_INSTALL_DIR}" DIRECTORY) 42 | set(QT_FRAMEWORK_PATH "${IDA_APP_DIR}/Frameworks") 43 | if(EXISTS "${QT_FRAMEWORK_PATH}/QtCore.framework") 44 | get_filename_component(QT_FRAMEWORK_PATH "${QT_FRAMEWORK_PATH}" ABSOLUTE) 45 | # Set framework search path for IDA's Qt 46 | target_link_options(ida_palette PRIVATE "-F${QT_FRAMEWORK_PATH}") 47 | # Link IDA's Qt frameworks 48 | target_link_libraries(ida_palette PRIVATE 49 | "-framework QtCore" 50 | "-framework QtGui" 51 | "-framework QtWidgets" 52 | ) 53 | endif() 54 | endif() 55 | 56 | if(WIN32) 57 | target_link_options(ida_palette PRIVATE "$<$:/DEBUG:NONE>" "/NOCOFFGRPINFO") 58 | endif() 59 | 60 | if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") 61 | if(APPLE) 62 | if(CMAKE_OSX_ARCHITECTURES) 63 | set(MAC_ARCH "${CMAKE_OSX_ARCHITECTURES}") 64 | add_custom_command(TARGET ida_palette POST_BUILD 65 | COMMENT "1 ${MAC_ARCH}" 66 | ) 67 | else() 68 | set(MAC_ARCH "${CMAKE_SYSTEM_PROCESSOR}") 69 | add_custom_command(TARGET ida_palette POST_BUILD 70 | COMMENT "2 ${MAC_ARCH}" 71 | ) 72 | endif() 73 | if(MAC_ARCH STREQUAL "x86_64") 74 | add_custom_command(TARGET ida_palette POST_BUILD 75 | COMMENT "Stripping ${MAC_ARCH} binary" 76 | COMMAND strip -x $ 77 | ) 78 | elseif(MAC_ARCH STREQUAL "arm64") 79 | add_custom_command(TARGET ida_palette POST_BUILD 80 | COMMENT "TODO: Find proper way to strip ${MAC_ARCH} binary" 81 | #COMMAND strip -x $ 82 | ) 83 | endif() 84 | elseif(UNIX) 85 | add_custom_command(TARGET ida_palette POST_BUILD 86 | COMMENT "Stripping binary" 87 | COMMAND strip -s $ 88 | ) 89 | endif() 90 | endif() 91 | -------------------------------------------------------------------------------- /palette/src/bindings/pypalette.cpp: -------------------------------------------------------------------------------- 1 | #include "pypalette.h" 2 | 3 | #include 4 | #include 5 | 6 | PyPalette::PyPalette(const std::string& name, const std::string& placeholer, 7 | const py::list& entries) { 8 | QVector result; 9 | 10 | name_ = QString::fromStdString(name); 11 | placeholder_ = QString::fromStdString(placeholer); 12 | result.reserve(static_cast(entries.size())); 13 | 14 | for (py::handle item : entries) { 15 | result.push_back(Action{ 16 | QString::fromStdString(item.attr("id").cast()), 17 | QString::fromStdString(item.attr("name").cast()), 18 | QString::fromStdString(item.attr("shortcut").cast()), 19 | QString::fromStdString(item.attr("description").cast())}); 20 | } 21 | 22 | py::object scope = py::module::import("__palette__").attr("__dict__"); 23 | 24 | scope["__entries__"] = entries; 25 | py::exec("__cur_palette__ = {l.id: l for l in __entries__}", scope); 26 | 27 | actions_.swap(result); 28 | } 29 | 30 | PYBIND11_MODULE(__palette__, m) { 31 | m.doc() = R"()"; 32 | 33 | py::class_(m, "Palette") 34 | .def(py::init()); 35 | 36 | m.def( 37 | "show_palette", 38 | [](PyPalette& palette, std::string closeKey) -> bool { 39 | show_palette( 40 | palette.name(), palette.placeholder(), palette.actions(), 41 | QString::fromStdString(closeKey), [](const Action& action) { 42 | py::gil_scoped_acquire gil; 43 | 44 | try { 45 | py::object trigger_action_py = 46 | py::module::import("__palette__").attr("execute_action"); 47 | auto res = trigger_action_py(action.id.toStdString()); 48 | 49 | return true; 50 | } catch (const std::runtime_error& error) { 51 | // This should not throw error 52 | auto write = 53 | py::module::import("sys").attr("stdout").attr("write"); 54 | write(error.what()); 55 | write("\n"); 56 | PyErr_Clear(); 57 | } 58 | 59 | return true; 60 | }); 61 | return true; 62 | }, 63 | py::arg("palette"), py::arg("close_key") = ""); 64 | 65 | m.attr("threading") = py::module::import("threading"); 66 | 67 | auto globals = m.attr("__dict__"); 68 | PyDict_SetItemString(globals.ptr(), "__builtins__", PyEval_GetBuiltins()); 69 | 70 | py::exec(R"( 71 | 72 | class Action: 73 | def __init__(self, id, name, handler, shortcut="", description=""): 74 | self.id = id 75 | self.name = name 76 | self.shortcut = shortcut 77 | self.description = description 78 | self.handler = handler 79 | 80 | def execute_action(id): 81 | # on main thread 82 | threading.Thread(target=__cur_palette__[id].handler, args=(__cur_palette__[id], )).run() 83 | 84 | )", 85 | globals); 86 | 87 | m.attr("__version__") = "dev"; 88 | } 89 | 90 | void init_python_module() { 91 | #if PY_MAJOR_VERSION >= 3 /// Compatibility macros for various Python versions 92 | auto ptr = PyInit___palette__(); 93 | py::str str("__palette__"); 94 | _PyImport_FixupExtensionObject(ptr, str.ptr(), 95 | str.ptr() 96 | #if PY_MINOR_VERSION >= 7 97 | // Python 3.6 uses this by default 98 | , 99 | PyImport_GetModuleDict() 100 | #endif 101 | ); 102 | #else 103 | init__palette__(); 104 | #endif 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IDA command palette & more 2 | 3 | [![Build Status](https://jinmo123.visualstudio.com/idapkg/_apis/build/status/Jinmo.ifred?branchName=master)](https://jinmo123.visualstudio.com/idapkg/_build/latest?definitionId=1&branchName=master) ![GitHub Issues or Pull Requests](https://img.shields.io/github/issues/jinmo/ifred) 4 | 5 | ![screenshot1][01] 6 | 7 | ## How to build 8 | 9 | Tested on all operating systems that run IDA Pro. 10 | 11 | | OS | Arch | Status | 12 | | ------- | ------ | ------ | 13 | | Linux | x86_64 | ✅ | 14 | | macOS | x86_64 | ✅ | 15 | | macOS | arm64 | ✅ | 16 | | Windows | x86_64 | ✅ | 17 | 18 | Currently this repo supports IDA Pro with Qt6. For IDA Pro v9.1 and earlier 19 | with Qt5 support head to [qt5 branch][04]. To build you need IDA Pro SDK. 20 | 21 | For easier compiling, use Qt6 artifacts provided by [Binary Ninja Crew][05] 22 | 23 | You can download prebuilt binaries with Qt6 from this [repo][06]. 24 | 25 | You can download older [prebuilt plugins][07] with Qt5 from azure pipelines. 26 | 27 | ## Python API 28 | 29 | You can make a custom palette in IDAPython. 30 | 31 | ```py 32 | from __palette__ import show_palette, Palette, Action 33 | import random, string 34 | 35 | myhandler = lambda item: sys.stdout.write('You selected: %s\n' % item.name) 36 | random_str = lambda: "".join(random.choice(string.lowercase) for i in range(20)) 37 | 38 | entries = [Action(name=random_str(), # displayed text 39 | handler=myhandler, # callback 40 | id='action%d' % i # must be unique 41 | ) for i in range(20)] 42 | 43 | show_palette(Palette('palette name here', 'placeholder here...', entries)) 44 | ``` 45 | 46 | ## C++ API 47 | 48 | Currently cleaning up C++ API. See `standalone/` folder. 49 | 50 | ```cpp 51 | #include 52 | #define COUNT 100 53 | 54 | QVector testItems() { 55 | QVector action_list; 56 | 57 | action_list.reserve(COUNT + 1); 58 | action_list.push_back(Action("std::runtime_error", "raise exception", "")); 59 | 60 | for (int i = 0; i < COUNT; i++) { 61 | auto id = QString::number(rand()); 62 | action_list.push_back(Action(id, id, "")); 63 | } 64 | 65 | return action_list; 66 | } 67 | 68 | const QString TestPluginPath(const char* name) { 69 | // Don't worry! also packaged with bundle theme! 70 | // Just point a writable path 71 | return QString("./path_to_plugin_theme/") + name; 72 | } 73 | 74 | int main() { 75 | QApplication app(argc, argv); 76 | 77 | set_path_handler(TestPluginPath); 78 | 79 | show_palette("", "Enter item name...", testItems(), [](const Action & action) { 80 | if (action.id() == "std::runtime_error") { 81 | throw std::runtime_error("raised!"); 82 | } 83 | qDebug() << action.id() << action.description() << action.shortcut(); 84 | return false; 85 | }); 86 | 87 | app.exec(); 88 | } 89 | ``` 90 | 91 | ## Changing theme 92 | 93 | You can copy css, json files from `palette/res/theme//*` to 94 | `%APPDATA%/Hex-rays/IDA Pro/plugins/palette/theme/`, like the existing 95 | css, json files. 96 | 97 | ayu white: 98 | 99 | ![screenshot2][02] 100 | 101 | solarized dark: 102 | 103 | ![screenshot3][03] 104 | 105 | [01]: screenshots/1.png 106 | [02]: screenshots/2.png 107 | [03]: screenshots/3.png 108 | [04]: https://github.com/Jinmo/ifred/tree/qt5 109 | [05]: https://github.com/Vector35/qt-artifacts/releases 110 | [06]: https://github.com/blue-devil/ifred/releases 111 | [07]: https://jinmo123.visualstudio.com/idapkg/_build/latest?definitionId=1&branchName=master 112 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | trigger: 7 | - master 8 | 9 | jobs: 10 | - job: Windows 11 | 12 | pool: 13 | vmImage: windows-2019 14 | 15 | steps: 16 | - task: DownloadSecureFile@1 17 | inputs: 18 | secureFile: 'idasdk70-win.7z' 19 | 20 | - task: DownloadSecureFile@1 21 | inputs: 22 | secureFile: 'qt-win.7z' 23 | 24 | - checkout: self 25 | submodules: true 26 | 27 | - powershell: | 28 | cd $(Agent.TempDirectory) 29 | 30 | Import-Module BitsTransfer 31 | Invoke-WebRequest -Uri https://github.com/ninja-build/ninja/releases/download/v1.9.0/ninja-win.zip -OutFile ninja-win.zip 32 | 33 | 7z x "$(Agent.TempDirectory)\\qt-win.7z" '-oqt563' 34 | 7z x "$(Agent.TempDirectory)\\idasdk70-win.7z" '-oidasdk70' 35 | 7z x ninja-win.zip '-o$(Build.SourcesDirectory)' 36 | 37 | displayName: Install dependencies 38 | 39 | - task: BatchScript@1 40 | inputs: 41 | filename: C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Enterprise\\VC\\Auxiliary\\Build\\vcvars64.bat 42 | modifyEnvironment: true 43 | 44 | - powershell: | 45 | mkdir "$(Build.ArtifactStagingDirectory)\\no-py" 46 | 47 | $env:PATH="$(Build.SourcesDirectory);$env:PATH" 48 | $cl=Get-Command cl|Select-Object -ExpandProperty Definition 49 | 50 | mkdir build64-no-py 51 | cd build64-no-py 52 | 53 | cmake .. "-DCMAKE_PREFIX_PATH=$(Agent.TempDirectory)\\qt563" -DPYTHON_SUPPORT=OFF -DIDA_BINARY_64=True -DIDA_EA_64=True "-DIDA_SDK=$(Agent.TempDirectory)\\idasdk70" -G Ninja -DCMAKE_CXX_COMPILER:FILEPATH=$cl -DCMAKE_C_COMPILER:FILEPATH=$cl -DCMAKE_BUILD_TYPE=RelWithDebInfo 54 | ninja 55 | 56 | copy palette\\src\\ida\\ida_palette64.dll $(Build.ArtifactStagingDirectory)\\no-py 57 | 58 | cd .. 59 | mkdir build32-no-py 60 | cd build32-no-py 61 | 62 | cmake .. "-DCMAKE_PREFIX_PATH=$(Agent.TempDirectory)\\qt563" -DPYTHON_SUPPORT=OFF -DIDA_BINARY_64=True -DIDA_EA_64=False "-DIDA_SDK=$(Agent.TempDirectory)\\idasdk70" -G Ninja -DCMAKE_CXX_COMPILER:FILEPATH=$cl -DCMAKE_C_COMPILER:FILEPATH=$cl -DCMAKE_BUILD_TYPE=RelWithDebInfo 63 | ninja 64 | 65 | copy palette\\src\\ida\\ida_palette.dll $(Build.ArtifactStagingDirectory)\\no-py 66 | displayName: Build 67 | 68 | - task: PublishBuildArtifacts@1 69 | inputs: 70 | PathtoPublish: '$(Build.ArtifactStagingDirectory)' 71 | ArtifactName: 'win-7.x-both' 72 | 73 | - job: MacOSX 74 | pool: 75 | vmImage: macOS-12 76 | 77 | steps: 78 | - task: DownloadSecureFile@1 79 | inputs: 80 | secureFile: 'idasdk70-mac.zip' 81 | 82 | - task: DownloadSecureFile@1 83 | inputs: 84 | secureFile: 'idalib-7.0-mac.zip' 85 | 86 | - task: DownloadSecureFile@1 87 | inputs: 88 | secureFile: 'qt5.6.0.txz' 89 | 90 | - checkout: self 91 | submodules: true 92 | 93 | - script: | 94 | brew install cmake ninja 95 | cd $(Build.SourcesDirectory) 96 | unzip $(Agent.TempDirectory)/idasdk70-mac.zip -d idasdk70 97 | unzip $(Agent.TempDirectory)/idalib-7.0-mac.zip -d idalib-7.0 98 | tar xvf $(Agent.TempDirectory)/qt5.6.0.txz || exit 100 99 | 100 | mkdir $(Build.ArtifactStagingDirectory) 101 | 102 | mkdir ../build64 103 | cd ../build64 104 | cmake $(Build.SourcesDirectory) -DPYTHON_SUPPORT=OFF -DCMAKE_PREFIX_PATH=$PWD/../qt560/clang_64 -DIDA_SDK=$PWD/../idasdk70 -DIDA_BINARY_64=True -DIDA_EA_64=True -GNinja -DIDA_INSTALL_DIR=$PWD/../idalib-7.0/MacOS -DCMAKE_BUILD_TYPE=RelWithDebInfo 105 | ninja 106 | cp palette/src/ida/ida_palette64.dylib $(Build.ArtifactStagingDirectory) 107 | 108 | mkdir ../build32 109 | cd ../build32 110 | cmake $(Build.SourcesDirectory) -DPYTHON_SUPPORT=OFF -DCMAKE_PREFIX_PATH=$PWD/../qt560/clang_64 -DIDA_SDK=$PWD/../idasdk70 -DIDA_BINARY_64=True -DIDA_EA_64=False -GNinja -DIDA_INSTALL_DIR=$PWD/../idalib-7.0/MacOS -DCMAKE_BUILD_TYPE=RelWithDebInfo 111 | ninja 112 | cp palette/src/ida/ida_palette.dylib $(Build.ArtifactStagingDirectory) 113 | 114 | displayName: 'Build' 115 | 116 | - task: PublishBuildArtifacts@1 117 | inputs: 118 | PathtoPublish: '$(Build.ArtifactStagingDirectory)' 119 | ArtifactName: 'mac-7.x-both' 120 | -------------------------------------------------------------------------------- /palette/src/widgets/item.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | QString escape(QString str) { 5 | str = str.replace("<", "<"); 6 | return str; 7 | } 8 | 9 | QString highlight(const QString& needle, const QString& haystack) { 10 | static QString em(""), emEnd(""); 11 | QStringList highlights; 12 | 13 | if (needle.size()) { 14 | int pos = -1, last_pos = 0; 15 | for (auto c : needle) { 16 | pos = haystack.indexOf(c, pos + 1, Qt::CaseInsensitive); 17 | if (pos == -1) break; 18 | 19 | highlights << escape(haystack.mid(last_pos, pos - last_pos)); 20 | highlights << em << escape(haystack[pos]) << emEnd; 21 | last_pos = pos + 1; 22 | } 23 | 24 | // push remaining 25 | highlights << haystack.mid(last_pos); 26 | } else { 27 | highlights << escape(haystack); 28 | } 29 | 30 | return QString(highlights.join("")); 31 | } 32 | 33 | void ItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, 34 | const QModelIndex& index) const { 35 | QStyledItemDelegate::paint(painter, option, index); 36 | 37 | static QHash classNameMap = { 38 | {QStyle::State_Selected, "selected"}, 39 | {QStyle::State_MouseOver, "hover"}, 40 | {QStyle::State_Selected | QStyle::State_MouseOver, "selected hover"}}; 41 | 42 | QStyleOptionViewItem opt = option; 43 | initStyleOption(&opt, index); 44 | 45 | auto action = index.data().value(); 46 | auto keyword = index.data(Qt::UserRole).value(); 47 | 48 | painter->save(); 49 | 50 | auto* widget = option.widget; 51 | auto* style = widget->style(); 52 | 53 | if (index.row() == recents_ - 1) { 54 | opt.state |= QStyle::State_On; 55 | } 56 | 57 | opt.text = ""; 58 | opt.state &= ~QStyle::State_HasFocus; 59 | opt.state |= QStyle::State_Active; 60 | style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget); 61 | 62 | painter->restore(); 63 | painter->save(); 64 | 65 | auto textRect = 66 | style->subElementRect(QStyle::SE_ItemViewItemText, &option, widget); 67 | painter->translate(textRect.left(), textRect.top()); 68 | 69 | // TODO: write a correct algorithm to limit rendering in QFrame, not 70 | // QMainWindow currently, I added this condition to prevent determining height 71 | // of topmost item 72 | if (textRect.top() >= 0) 73 | textRect = textRect.intersected(widget->contentsRect()); 74 | 75 | auto document = const_cast(this)->renderAction( 76 | false, 77 | classNameMap[(int)opt.state & 78 | (QStyle::State_Selected | QStyle::State_MouseOver)], 79 | keyword, action); 80 | 81 | document->drawContents(painter, 82 | QRectF(0, 0, textRect.width(), textRect.height())); 83 | painter->restore(); 84 | } 85 | 86 | QSize ItemDelegate::sizeHint(const QStyleOptionViewItem& option, 87 | const QModelIndex& index) const { 88 | auto action = index.data().value(); 89 | 90 | auto document = const_cast(this)->renderAction( 91 | true, QString(), QString(), action); 92 | document->setTextWidth(option.rect.width()); 93 | 94 | return QSize(option.rect.width(), (int)document->size().height()); 95 | } 96 | 97 | void ItemDelegate::updateCSS(const QString& style_sheet) { 98 | document_->setDefaultStyleSheet(style_sheet); 99 | QTextOption textOption; 100 | textOption.setWrapMode(QTextOption::WrapAnywhere); 101 | document_->setDefaultTextOption(textOption); 102 | document_->setDocumentMargin(0); 103 | } 104 | 105 | QTextDocument* ItemDelegate::renderAction(bool size_hint, 106 | const QString& className, 107 | const QString& keyword, 108 | Action& action) { 109 | QString html = ""; 113 | 114 | if (action.shortcut.size()) 115 | html += ""; 117 | 118 | html += ""; 119 | 120 | if (action.description.size()) 121 | html += ""; 123 | 124 | html += "
" + 111 | (!size_hint ? highlight(keyword, action.name) : "keyword") + 112 | "" + 116 | action.shortcut + "
" + action.description + 122 | "
"; 125 | document_->setHtml(html); 126 | return document_; 127 | } 128 | -------------------------------------------------------------------------------- /palette/src/search_services/basic_service.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define FTS_FUZZY_MATCH_IMPLEMENTATION 4 | #include "fts_fuzzy_match.h" 5 | 6 | constexpr int MAX_RECENT_ITEMS = 100; 7 | constexpr int SAME_THREAD_THRESHOLD = 20000; 8 | 9 | using DistanceHashMap = QHash, int>; 10 | 11 | class canceled_error : public std::exception {}; 12 | 13 | int distance(const QString& s1, const QString& s2) { 14 | static QThreadStorage distances; 15 | 16 | QPair pair(s1, s2); 17 | 18 | if (!distances.hasLocalData()) { 19 | distances.setLocalData(new DistanceHashMap()); 20 | } 21 | 22 | auto distances_ = distances.localData(); 23 | auto it = distances_->find(pair); 24 | if (it != distances_->end()) return it.value(); 25 | 26 | int score; 27 | fts::fuzzy_match(reinterpret_cast(s1.data()), 28 | reinterpret_cast(s2.data()), score); 29 | 30 | distances_->insert(pair, -score); 31 | return -score; 32 | } 33 | 34 | static int convert(QVariant a) { return a.toInt(); } 35 | 36 | static int convert(int a) { return a; } 37 | 38 | template 39 | QHash convert(const QHash& source) { 40 | QHash result; 41 | for (auto&& it : source.keys()) { 42 | result[it] = convert(source[it]); 43 | } 44 | return result; 45 | } 46 | 47 | BasicService::BasicService(QObject* parent, const QString& palette_name, 48 | const QVector& actions) 49 | : SearchService(parent), 50 | actions_(actions), 51 | storage_("ifred", palette_name), 52 | indexes_(actions.size()), 53 | recent_indexes_(), 54 | canceled_(false) { 55 | connect(this, &SearchService::startSearching, this, &BasicService::search); 56 | connect(this, &SearchService::itemClicked, [=](const QString& id) { 57 | QVector to_remove; 58 | for (auto&& it : recent_actions_.keys()) { 59 | if (++recent_actions_[it] >= MAX_RECENT_ITEMS) { 60 | to_remove.push_back(it); 61 | } 62 | } 63 | for (auto&& key : to_remove) { 64 | recent_actions_.remove(key); 65 | } 66 | recent_actions_[id] = 0; 67 | storage_.setValue("recent_actions", 68 | convert(recent_actions_)); 69 | storage_.sync(); // save to platform-specific registry immediately 70 | }); 71 | 72 | storage_.sync(); 73 | recent_actions_ = 74 | convert(storage_.value("recent_actions").toHash()); 75 | recent_indexes_.resize(recent_actions_.size()); 76 | } 77 | 78 | bool BasicService::runInSeparateThread() { 79 | // In my opinion, total character 80 | // count can be checked either. 81 | return actions_.count() >= SAME_THREAD_THRESHOLD; 82 | } 83 | 84 | void BasicService::search(QString keyword) { 85 | long nonrecent_count = 0, recent_count = 0; 86 | QHash recent_actions(recent_actions_); 87 | 88 | canceled_ = false; 89 | 90 | /* Filter the items with fuzzy matching: see 91 | fts::fuzzy_match_simple, which is substr with non-neighbor 92 | characters support 93 | */ 94 | for (long i = 0; i < indexes_.size(); i++) { 95 | if (canceled_) return; 96 | if (keyword.isEmpty() || 97 | fts::fuzzy_match_simple(keyword, actions_[i].name)) { 98 | if (recent_actions.count(actions_[i].id)) { 99 | recent_indexes_[recent_count++] = i; 100 | } else { 101 | indexes_[nonrecent_count++] = i; 102 | } 103 | } 104 | } 105 | 106 | try { 107 | /* Sort by how recent the item is. 108 | If the job is canceled during sort, the compare function raises exception 109 | to abort sorting. 110 | */ 111 | std::sort(recent_indexes_.begin(), recent_indexes_.begin() + recent_count, 112 | [=](int lhs, int rhs) -> bool { 113 | if (canceled_) throw canceled_error(); 114 | auto lhs_r = recent_actions.find(actions_[lhs].id); 115 | auto rhs_r = recent_actions.find(actions_[rhs].id); 116 | 117 | return *lhs_r < *rhs_r; 118 | }); 119 | 120 | /* Sort by fuzzy matching if keyword is longer than 1 character */ 121 | if (keyword.size() > 1) 122 | std::sort(indexes_.begin(), indexes_.begin() + nonrecent_count, 123 | [=](int lhs, int rhs) -> bool { 124 | if (canceled_) throw canceled_error(); 125 | return distance(keyword, actions_[lhs].name) < 126 | distance(keyword, actions_[rhs].name); 127 | }); 128 | } catch (canceled_error&) { 129 | return; 130 | } 131 | 132 | QVector result; 133 | result.reserve(recent_count + nonrecent_count); 134 | 135 | for (int i = 0; i < recent_count; i++) { 136 | result.push_back(actions_[recent_indexes_[i]]); 137 | } 138 | 139 | for (int i = 0; i < nonrecent_count; i++) { 140 | result.push_back(actions_[indexes_[i]]); 141 | } 142 | 143 | emit doneSearching(keyword, result, recent_count); 144 | } 145 | -------------------------------------------------------------------------------- /palette/src/widgets/palette.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | PaletteFrame::PaletteFrame(QWidget* parent, const QString& name, 7 | const QString& closeKey, 8 | SearchService* search_service) 9 | : QFrame(parent), name_(name), shortcut_(nullptr) { 10 | QVBoxLayout* layout; 11 | 12 | // Create widgets 13 | searchbox_ = new QLineEdit(this); 14 | searchbox_->setAttribute(Qt::WA_MacShowFocusRect, 0); 15 | 16 | items_ = new PaletteItems(this, name_, search_service); 17 | items_->setAttribute(Qt::WA_MacShowFocusRect, 0); 18 | 19 | // Add widgets 20 | layout = new QVBoxLayout(this); 21 | layout->addWidget(searchbox_); 22 | layout->addWidget(items_); 23 | 24 | // Set margin and spacing between widgets 25 | // This is just like normalize.css because it can be overridden by css 26 | layout->setContentsMargins(0, 0, 0, 0); 27 | layout->setSpacing(0); 28 | 29 | setLayout(layout); 30 | 31 | connect(searchbox_, &QLineEdit::returnPressed, [=]() { 32 | if (items_->model()->rowCount()) { 33 | auto action = items_->currentIndex().data().value(); 34 | window()->hide(); 35 | emit itemClicked(action); 36 | window()->close(); 37 | } 38 | return true; 39 | }); 40 | 41 | connect(searchbox_, &QLineEdit::textChanged, [=](const QString& text) { 42 | style()->polish(searchbox_); 43 | items_->model()->setFilter(text); 44 | }); 45 | 46 | connect(items_, &QListView::clicked, this, [=](const QModelIndex& index) { 47 | auto action = index.data().value(); 48 | window()->hide(); 49 | emit itemClicked(action); 50 | window()->close(); 51 | }); 52 | 53 | searchbox_->installEventFilter(this); 54 | items_->installEventFilter(this); 55 | 56 | connect(this, &PaletteFrame::itemClicked, [=](Action& action) { 57 | emit items_->model()->searchService()->itemClicked(action.id); 58 | }); 59 | 60 | items_->model()->setFilter(QString()); 61 | 62 | if (!closeKey.isEmpty()) { 63 | shortcut_ = registerShortcut({closeKey}, [=]() { window()->close(); }); 64 | } 65 | 66 | registerShortcut({"Ctrl+J"}, [=]() { arrowPressed(-1); }); 67 | registerShortcut({"Ctrl+K"}, [=]() { arrowPressed(+1); }); 68 | 69 | /* This key closes the window */ 70 | registerShortcut({"Esc"}, [=]() { window()->close(); }); 71 | } 72 | 73 | void PaletteFrame::arrowPressed(int delta) { 74 | auto new_row = items_->currentIndex().row() + delta; 75 | auto row_count = items_->model()->rowCount(); 76 | 77 | if (new_row < 0) 78 | new_row = 0; 79 | else if (new_row >= row_count) 80 | new_row = row_count - 1; 81 | 82 | items_->setCurrentIndex(items_->model()->index(new_row, 0)); 83 | } 84 | 85 | bool PaletteFrame::eventFilter(QObject* obj, QEvent* event) { 86 | switch (event->type()) { 87 | case QEvent::KeyPress: { 88 | auto* keyEvent = dynamic_cast(event); 89 | 90 | QKeySequence sequence(keyEvent->key() | keyEvent->modifiers()); 91 | if (registered_keys_.contains(sequence)) { 92 | emit registered_keys_[sequence]->activated(); 93 | return true; 94 | } 95 | 96 | switch (keyEvent->key()) { 97 | case Qt::Key_Down: 98 | case Qt::Key_Up: { 99 | /* We manually process Up/Down key to handle corner cases and for ease 100 | * of edits */ 101 | arrowPressed(keyEvent->key() == Qt::Key_Down ? 1 : -1); 102 | return true; 103 | } 104 | case Qt::Key_PageDown: 105 | case Qt::Key_PageUp: 106 | /* Forward QKeyEvent for PageDown/PageUp key since QAbstractListView 107 | * already implemented the procedure */ 108 | event->ignore(); 109 | items_->keyPressEvent(keyEvent); 110 | return true; 111 | default: 112 | return obj->eventFilter(obj, event); 113 | } 114 | } 115 | case QEvent::FocusOut: { 116 | auto focusEvent = static_cast(event); 117 | if (obj == searchbox_ && focusEvent->reason() == Qt::MouseFocusReason) { 118 | searchbox_->setFocus(); 119 | return true; 120 | } else { 121 | return false; 122 | } 123 | } 124 | case QEvent::ShortcutOverride: { 125 | /* 126 | Handling ShortcutOverride prevents UI from hooking the shortcuts used in 127 | the command palette. If the shortcut is registered by registerShortcut(), 128 | it's not overriden. 129 | */ 130 | event->accept(); 131 | return true; 132 | } 133 | default: 134 | return QFrame::eventFilter(obj, event); 135 | } 136 | } 137 | 138 | void PaletteFrame::showEvent(QShowEvent* event) { searchbox_->setFocus(); } 139 | 140 | void PaletteFrame::setPlaceholderText(const QString& placeholder) { 141 | searchbox_->setPlaceholderText(placeholder); 142 | } 143 | 144 | static void centerWidgets(QWidget* window, QWidget* widget, 145 | QWidget* host = nullptr) { 146 | if (host) { 147 | auto hostRect = host->geometry(); 148 | window->move(hostRect.center() - widget->rect().center()); 149 | } else { 150 | QRect screenGeometry = QGuiApplication::screens().at(0)->geometry(); 151 | int x = (screenGeometry.width() - widget->width()) / 2; 152 | int y = (screenGeometry.height() - widget->height()) / 2; 153 | window->move(x, y); 154 | } 155 | } 156 | 157 | CommandPalette::CommandPalette(QWidget* parent) : QMainWindow(parent) { 158 | setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); 159 | setAttribute( 160 | Qt::WA_TranslucentBackground); // enable MainWindow to be transparent 161 | 162 | // Set style sheet from window.css 163 | setStyleSheet(loadFile("theme/window.css")); 164 | 165 | // Set shadow effect from styles.json 166 | //auto* shadow = new QGraphicsDropShadowEffect(this); 167 | 168 | int shadowWidth = json("theme/styles.json")["shadow-width"].toInt(); 169 | //QColor shadowColor(0, 0, 0, 100); 170 | 171 | //shadow->setBlurRadius(shadowWidth); 172 | //shadow->setColor(shadowColor); 173 | //shadow->setOffset(0); 174 | 175 | //setGraphicsEffect(shadow); 176 | setContentsMargins(shadowWidth, shadowWidth, shadowWidth, shadowWidth); 177 | } 178 | 179 | void CommandPalette::show(const QString& name, const QString& placeholder, 180 | const QVector& actions, 181 | const QString& closeKey, ActionHandler func) { 182 | show(name, placeholder, new BasicService(nullptr, name, actions), closeKey, 183 | std::move(func)); 184 | } 185 | 186 | void CommandPalette::show(const QString& name, const QString& placeholder, 187 | SearchService* searchService, const QString& closeKey, 188 | ActionHandler func) { 189 | auto inner = new PaletteFrame(this, name, closeKey, searchService); 190 | 191 | setCentralWidget(inner); 192 | connect(inner, &PaletteFrame::itemClicked, std::move(func)); 193 | 194 | inner->setPlaceholderText(placeholder); 195 | 196 | QMainWindow::show(); 197 | centerWidgets(this, this, parentWidget()); 198 | 199 | activateWindow(); 200 | } 201 | -------------------------------------------------------------------------------- /palette/src/search_services/fts_fuzzy_match.h: -------------------------------------------------------------------------------- 1 | // LICENSE 2 | // 3 | // This software is dual-licensed to the public domain and under the following 4 | // license: you are granted a perpetual, irrevocable license to copy, modify, 5 | // publish, and distribute this file as you see fit. 6 | // 7 | // VERSION 8 | // 0.2.0 (2017-02-18) Scored matches perform exhaustive search for best 9 | // score 0.1.0 (2016-03-28) Initial release 10 | // 11 | // AUTHOR 12 | // Forrest Smith 13 | // 14 | // NOTES 15 | // Compiling 16 | // You MUST add '#define FTS_FUZZY_MATCH_IMPLEMENTATION' before including 17 | // this header in ONE source file to create implementation. 18 | // 19 | // fuzzy_match_simple(...) 20 | // Returns true if each character in pattern is found sequentially within 21 | // str 22 | // 23 | // fuzzy_match(...) 24 | // Returns true if pattern is found AND calculates a score. 25 | // Performs exhaustive search via recursion to find all possible matches and 26 | // match with highest score. Scores values have no intrinsic meaning. 27 | // Possible score range is not normalized and varies with pattern. Recursion 28 | // is limited internally (default=10) to prevent degenerate cases 29 | // (pattern="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") Uses uint8_t for 30 | // match indices. Therefore patterns are limited to 256 characters. Score 31 | // system should be tuned for YOUR use case. Words, sentences, file names, 32 | // or method names all prefer different tuning. 33 | 34 | #ifndef FTS_FUZZY_MATCH_H 35 | #define FTS_FUZZY_MATCH_H 36 | 37 | #include // ::tolower, ::toupper 38 | 39 | #include // uint8_t 40 | #include 41 | #include // memcpy 42 | 43 | // Public interface 44 | namespace fts { 45 | static bool fuzzy_match_simple(const QString& pattern, const QString& str); 46 | 47 | static bool fuzzy_match(uint16_t const* pattern, uint16_t const* str, 48 | int& outScore); 49 | 50 | static bool fuzzy_match(uint16_t const* pattern, uint16_t const* str, 51 | int& outScore, uint8_t* matches, int maxMatches); 52 | } // namespace fts 53 | 54 | #ifdef FTS_FUZZY_MATCH_IMPLEMENTATION 55 | namespace fts { 56 | 57 | bool isseparator(char c) { 58 | static unsigned char table[] = {0, 124, 0, 0, 128, 2, 0, 0, 0, 0, 0, 59 | 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 61 | return (table[(unsigned char)c >> 3] & ((unsigned char)c & 7)) != 0; 62 | } 63 | 64 | // Forward declarations for "private" implementation 65 | namespace fuzzy_internal { 66 | static bool fuzzy_match_recursive(const uint16_t* pattern, const uint16_t* str, 67 | int& outScore, const uint16_t* strBegin, 68 | uint8_t const* srcMatches, 69 | uint8_t* newMatches, int maxMatches, 70 | int nextMatch, int& recursionCount, 71 | int recursionLimit); 72 | } 73 | 74 | static bool fuzzy_match(uint16_t const* pattern, uint16_t const* str, 75 | int& outScore) { 76 | uint8_t matches[256]; 77 | return fuzzy_match(pattern, str, outScore, matches, sizeof(matches)); 78 | } 79 | 80 | static bool fuzzy_match(uint16_t const* pattern, uint16_t const* str, 81 | int& outScore, uint8_t* matches, int maxMatches) { 82 | int recursionCount = 0; 83 | int recursionLimit = 10; 84 | 85 | return fuzzy_internal::fuzzy_match_recursive(pattern, str, outScore, str, 86 | nullptr, matches, maxMatches, 0, 87 | recursionCount, recursionLimit); 88 | } 89 | 90 | // Private implementation 91 | static bool fuzzy_internal::fuzzy_match_recursive( 92 | const uint16_t* pattern, const uint16_t* str, int& outScore, 93 | const uint16_t* strBegin, uint8_t const* srcMatches, uint8_t* matches, 94 | int maxMatches, int nextMatch, int& recursionCount, int recursionLimit) { 95 | // Count recursions 96 | ++recursionCount; 97 | if (recursionCount >= recursionLimit) return false; 98 | 99 | // Detect end of strings 100 | if (*pattern == '\0' || *str == '\0') return false; 101 | 102 | // Recursion params 103 | bool recursiveMatch = false; 104 | uint8_t bestRecursiveMatches[256]; 105 | int bestRecursiveScore = 0; 106 | 107 | // Loop through pattern and str looking for a match 108 | bool first_match = true; 109 | while (*pattern != '\0' && *str != '\0') { 110 | // Found match 111 | if (tolower(*pattern) == tolower(*str)) { 112 | // Supplied matches buffer was too short 113 | if (nextMatch >= maxMatches) return false; 114 | 115 | // "Copy-on-Write" srcMatches into matches 116 | if (first_match && srcMatches) { 117 | memcpy(matches, srcMatches, nextMatch); 118 | first_match = false; 119 | } 120 | 121 | // Recursive call that "skips" this match 122 | uint8_t recursiveMatches[256]; 123 | int recursiveScore; 124 | if (fuzzy_match_recursive(pattern, str + 1, recursiveScore, strBegin, 125 | matches, recursiveMatches, 126 | sizeof(recursiveMatches), nextMatch, 127 | recursionCount, recursionLimit)) { 128 | // Pick best recursive score 129 | if (!recursiveMatch || recursiveScore > bestRecursiveScore) { 130 | memcpy(bestRecursiveMatches, recursiveMatches, 256); 131 | bestRecursiveScore = recursiveScore; 132 | } 133 | recursiveMatch = true; 134 | } 135 | 136 | // Advance 137 | matches[nextMatch++] = (uint8_t)(str - strBegin); 138 | ++pattern; 139 | } 140 | ++str; 141 | } 142 | 143 | // Determine if full pattern was matched 144 | bool matched = *pattern == '\0' ? true : false; 145 | 146 | // Calculate score 147 | if (matched) { 148 | const int sequential_bonus = 30; // bonus for adjacent matches 149 | const int separator_bonus = 30; // bonus if match occurs after a separator 150 | const int camel_bonus = 151 | 30; // bonus if match is uppercase and prev is lower 152 | const int first_letter_bonus = 30; // bonus if the first letter is matched 153 | 154 | const int leading_letter_penalty = 155 | -5; // penalty applied for every letter in str before the first match 156 | const int max_leading_letter_penalty = 157 | -15; // maximum penalty for leading letters 158 | const int unmatched_letter_penalty = 159 | -1; // penalty for every letter that doesn't matter 160 | 161 | // Iterate str to end 162 | while (*str != '\0') ++str; 163 | 164 | // Initialize score 165 | outScore = 100; 166 | 167 | // Apply leading letter penalty 168 | int penalty = leading_letter_penalty * matches[0]; 169 | if (penalty < max_leading_letter_penalty) 170 | penalty = max_leading_letter_penalty; 171 | outScore += penalty; 172 | 173 | // Apply unmatched penalty 174 | int unmatched = (int)(str - strBegin) - nextMatch; 175 | outScore += unmatched_letter_penalty * unmatched; 176 | 177 | // Apply ordering bonuses 178 | for (int i = 0; i < nextMatch; ++i) { 179 | uint8_t currIdx = matches[i]; 180 | 181 | if (i > 0) { 182 | uint8_t prevIdx = matches[i - 1]; 183 | 184 | // Sequential 185 | if (currIdx == (prevIdx + 1)) outScore += sequential_bonus; 186 | } 187 | 188 | // Check for bonuses based on neighbor character value 189 | if (currIdx > 0) { 190 | // Camel case 191 | char neighbor = strBegin[currIdx - 1]; 192 | char curr = strBegin[currIdx]; 193 | if (::islower(neighbor) && ::isupper(curr)) outScore += camel_bonus; 194 | 195 | // Separator 196 | bool neighborSeparator = isseparator(neighbor); 197 | if (neighborSeparator) outScore += separator_bonus; 198 | } else { 199 | // First letter 200 | outScore += first_letter_bonus; 201 | } 202 | } 203 | } 204 | 205 | // Return best result 206 | if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) { 207 | // Recursive score is better than "this" 208 | memcpy(matches, bestRecursiveMatches, maxMatches); 209 | outScore = bestRecursiveScore; 210 | return true; 211 | } else if (matched) { 212 | // "this" score is better than recursive 213 | return true; 214 | } else { 215 | // no match 216 | return false; 217 | } 218 | } 219 | 220 | static bool fuzzy_match_simple(const QString& pattern, const QString& str) { 221 | auto it = pattern.begin(); 222 | auto itEnd = pattern.end(); 223 | 224 | if (it == itEnd) return true; 225 | 226 | for (auto&& c : str) { 227 | if (!(((*reinterpret_cast(it)) ^ 228 | (*reinterpret_cast(&c))) & 229 | ~0x20)) { 230 | ++it; 231 | if (it == itEnd) { 232 | break; 233 | } 234 | } 235 | } 236 | 237 | return it == itEnd; 238 | } 239 | } // namespace fts 240 | 241 | #endif // FTS_FUZZY_MATCH_IMPLEMENTATION 242 | 243 | #endif // FTS_FUZZY_MATCH_H 244 | -------------------------------------------------------------------------------- /palette/src/ida/plugin.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | // NO_OBSOLETE_FUNCS might be overkill, so let's just define this 10 | #if IDA_SDK_VERSION >= 750 11 | #define ACTION_DESC_LITERAL(name, label, handler, shortcut, tooltip, icon) \ 12 | { \ 13 | sizeof(action_desc_t), name, label, handler, &PLUGIN, shortcut, tooltip, \ 14 | icon, ADF_OT_PLUGIN \ 15 | } 16 | #endif 17 | 18 | #ifndef __APPLE__ 19 | #define CMD_PALETTE_SHORTCUT "Ctrl+Shift+P" 20 | #define NAME_PALETTE_SHORTCUT "Ctrl+P" 21 | #else 22 | #define CMD_PALETTE_SHORTCUT "Meta+Shift+P" 23 | #define NAME_PALETTE_SHORTCUT "Meta+P" 24 | #endif 25 | 26 | #ifdef __MAC__ 27 | #include 28 | bool mac_dlopen_workaround() { 29 | Dl_info res; 30 | if (dladdr(&PLUGIN, &res) == 0) { 31 | return false; 32 | } 33 | 34 | auto res_open = dlopen(res.dli_fname, RTLD_NODELETE); 35 | if (!res_open) { 36 | return false; 37 | } 38 | 39 | return true; 40 | } 41 | #endif 42 | 43 | QVector getBlacklist() { 44 | auto blacklist = json("config.json")["blacklist"].toArray(); 45 | QVector blacklist_converted; 46 | 47 | for (auto&& i : blacklist) { 48 | if (i.toString().size()) 49 | blacklist_converted.push_back(QRegularExpression(i.toString())); 50 | } 51 | return blacklist_converted; 52 | } 53 | 54 | void addActions(QVector& result, const qstrvec_t& actions) { 55 | qstring tooltip, shortcut; 56 | action_state_t state; 57 | 58 | auto blacklist = getBlacklist(); 59 | 60 | QRegularExpression remove_tilde("~(.*?)~"); 61 | 62 | for (auto& item : actions) { 63 | // Check blacklist 64 | bool skip = false; 65 | for (auto& pattern : blacklist) 66 | if (pattern.match(item.c_str()).hasMatch()) { 67 | skip = true; 68 | break; 69 | } 70 | 71 | if (skip) continue; 72 | 73 | // Enabled actions only 74 | if (!get_action_state(item.c_str(), &state)) continue; 75 | 76 | if (state > AST_ENABLE) continue; 77 | 78 | // Get metadata for the action 79 | QString tooltip_qstr; 80 | 81 | get_action_label(&tooltip, item.c_str()); 82 | get_action_shortcut(&shortcut, item.c_str()); 83 | 84 | tooltip_qstr = QString::fromUtf8(tooltip.c_str()); 85 | tooltip_qstr = tooltip_qstr.replace(remove_tilde, "\\1"); 86 | 87 | result.push_back( 88 | Action{QString(item.c_str()), tooltip_qstr, QString(shortcut.c_str())}); 89 | } 90 | } 91 | 92 | // Helper function to check if database is loaded 93 | static bool isDatabaseLoaded() { 94 | qstring idb_path = get_path(PATH_TYPE_IDB); 95 | return !idb_path.empty(); 96 | } 97 | 98 | void addNames(QVector& result) { 99 | if (!isDatabaseLoaded()) { 100 | return; 101 | } 102 | 103 | size_t names = get_nlist_size(); 104 | 105 | for (size_t i = 0; i < names; i++) { 106 | ea_t ea = get_nlist_ea(i); 107 | qstring demangled_name; 108 | get_demangled_name(&demangled_name, ea, 0, 0, GN_SHORT); 109 | 110 | result.push_back(Action{QString::number(get_nlist_ea(i), 16), 111 | demangled_name.c_str(), QString()}); 112 | } 113 | } 114 | 115 | // Get command palette items from IDA: 1. actions, 2. names 116 | const QVector getActions() { 117 | QVector result; 118 | 119 | qstrvec_t actions; 120 | get_registered_actions(&actions); 121 | 122 | // 0. Reserve vector to avoid multiple allocations 123 | result.reserve(static_cast(actions.size())); 124 | 125 | // 1. Add actions from IDA except blacklisted identifiers 126 | addActions(result, actions); 127 | 128 | std::sort( 129 | result.begin(), result.begin() + result.size(), 130 | [](Action& lhs, Action& rhs) { return lhs.name.compare(rhs.name) < 0; }); 131 | 132 | return result; 133 | } 134 | 135 | #if !SHOULD_USE_TYPEINF 136 | qstring get_nice_struc_name(tid_t struct_) { 137 | auto name = get_struc_name(struct_); 138 | tinfo_t tif; 139 | auto til = get_idati(); 140 | if (til) { 141 | tif.get_named_type(get_idati(), name.c_str()); 142 | tif.print(&name); 143 | } 144 | return name; 145 | } 146 | 147 | void addStructs(QVector& result) { 148 | if (!isDatabaseLoaded()) { 149 | return; 150 | } 151 | 152 | int idx = get_first_struc_idx(); 153 | while (idx != BADADDR) { 154 | tid_t sid = get_struc_by_idx(idx); 155 | result.push_back(Action{ 156 | "struct:" + QString::number(sid), 157 | QString::fromStdString(get_nice_struc_name(sid).c_str()), QString()}); 158 | 159 | idx = get_next_struc_idx(idx); 160 | } 161 | } 162 | 163 | void addEnums(QVector& result) { 164 | if (!isDatabaseLoaded()) { 165 | return; 166 | } 167 | 168 | size_t cnt = get_enum_qty(); 169 | for (size_t i = 0; i < cnt; i++) { 170 | enum_t n = getn_enum(i); 171 | result.push_back(Action{ 172 | "struct:" + QString::number(n), 173 | QString::fromStdString(get_nice_struc_name(n).c_str()), QString()}); 174 | } 175 | } 176 | #else 177 | qstring get_nice_struc_name(tid_t struct_) { 178 | tinfo_t tif; 179 | qstring name; 180 | auto til = get_idati(); 181 | if (til) { 182 | tif.get_numbered_type(get_idati(), struct_); 183 | tif.print(&name); 184 | } 185 | return name; 186 | } 187 | #endif 188 | 189 | void addTypes(QVector &result) { 190 | #if SHOULD_USE_TYPEINF 191 | if (!isDatabaseLoaded()) { 192 | return; 193 | } 194 | auto til = get_idati(); 195 | if (!til) { 196 | // TIL not available, skip adding types 197 | return; 198 | } 199 | auto count = get_ordinal_count(til); 200 | for (auto ordinal = 0; ordinal < count; ordinal++) { 201 | // NOTE: this shouldn'be freed I think? It's managed by IDA and doesn't always return the start of allocation 202 | const char *name = get_numbered_type_name(til, ordinal); 203 | if (!name) { 204 | // Type doesn't exist for this ordinal 205 | continue; 206 | } 207 | 208 | if (name[0] == '\0') { 209 | // Type is anonymous (not named) 210 | continue; 211 | } 212 | 213 | result.push_back(Action{ 214 | "struct:" + QString::number(ordinal), 215 | QString::fromStdString(get_nice_struc_name(ordinal).c_str()), QString()}); 216 | } 217 | #else 218 | // 1. Add structs from IDA 219 | addStructs(result); 220 | 221 | // 2. Add enums from IDA 222 | addEnums(result); 223 | #endif 224 | } 225 | 226 | class NamesManager { 227 | QHash address_to_name; 228 | QHash address_to_struct; 229 | QVector result; 230 | 231 | public: 232 | NamesManager() { 233 | hook_to_notification_point(HT_IDB, idb_hooks, this); 234 | hook_to_notification_point(HT_IDP, idp_hooks, this); 235 | } 236 | 237 | void init(QVector* names) { 238 | address_to_name.clear(); 239 | address_to_struct.clear(); 240 | 241 | int index = 0; 242 | for (auto& action : *names) { 243 | auto sep = action.id.indexOf(':'); 244 | if (action.id.startsWith("struct:")) { 245 | address_to_struct.insert(QStringView(action.id).mid(sep + 1).toULongLong(nullptr), 246 | index); 247 | } else { 248 | address_to_name.insert( 249 | QStringView(action.id).mid(0, sep).toULongLong(nullptr, 16), index); 250 | } 251 | index++; 252 | } 253 | } 254 | 255 | void rename(ea_t address, const char* name) { 256 | auto it = address_to_name.find(address); 257 | qstring demangled_name; 258 | get_demangled_name(&demangled_name, address, 0, 0, GN_SHORT); 259 | 260 | if (it == address_to_name.end()) { 261 | if (result.empty()) return; // Not initialized yet 262 | result.push_back(Action{QString::number(address, 16), 263 | demangled_name.c_str(), QString()}); 264 | address_to_name.insert(address, result.size() - 1); 265 | return; 266 | } 267 | 268 | Action& action = result[it.value()]; 269 | action.name = QString::fromStdString(demangled_name.c_str()); 270 | action.id = QString::number(address, 16); 271 | } 272 | 273 | void rebase(segm_move_infos_t& infos) { 274 | std::vector> moves; 275 | for (auto&& seg : infos) { 276 | for (auto&& key : address_to_name.keys()) { 277 | if (key >= seg.from && key < seg.from + seg.size) { 278 | moves.push_back({key, key + seg.to - seg.from}); 279 | } 280 | } 281 | } 282 | for (auto&& move : moves) { 283 | auto index = address_to_name[move.first]; 284 | auto& action = result[index]; 285 | address_to_name.remove(move.first); 286 | action.id = QString::number(move.second, 16); 287 | address_to_name[move.second] = index; 288 | } 289 | } 290 | 291 | void update_struct(tid_t id, const char* name) { 292 | if (result.empty()) return; // Not initialized yet 293 | 294 | auto it = address_to_struct.find(id); 295 | if (it == address_to_struct.end()) { 296 | result.push_back( 297 | Action{"struct:" + QString::number(id), name, QString()}); 298 | address_to_struct.insert(id, result.size() - 1); 299 | } else { 300 | Action& action = result[it.value()]; 301 | action.name = QString::fromStdString(name); 302 | } 303 | } 304 | 305 | void clear() { 306 | result.clear(); 307 | address_to_name.clear(); 308 | address_to_struct.clear(); 309 | } 310 | 311 | void clear_struct() { 312 | address_to_struct.clear(); 313 | } 314 | 315 | QVector get(bool clear = false) { 316 | if (!result.empty() && !clear) return result; 317 | 318 | // 1. Add names from IDA 319 | addNames(result); 320 | 321 | // 2. Add types from IDA 322 | addTypes(result); 323 | 324 | init(&result); 325 | 326 | return result; 327 | } 328 | 329 | static ssize_t idaapi idb_hooks(void* user_data, int notification_code, 330 | va_list va) { 331 | auto manager = reinterpret_cast(user_data); 332 | 333 | switch (notification_code) { 334 | case idb_event::allsegs_moved: { 335 | auto info = va_arg(va, segm_move_infos_t*); 336 | manager->rebase(*info); 337 | break; 338 | } 339 | case idb_event::renamed: { 340 | auto ea = va_arg(va, ea_t); 341 | auto new_name = va_arg(va, const char*); 342 | manager->rename(ea, new_name); 343 | break; 344 | } 345 | #if SHOULD_USE_TYPEINF && IDA_SDK_VERSION >= 900 346 | case idb_event::local_types_changed: { 347 | auto ltc = va_arg(va, local_type_change_t); 348 | auto ordinal = va_arg(va, uint32_t); 349 | auto name = va_arg(va, const char *); 350 | switch (ltc) { 351 | case LTC_ADDED: 352 | case LTC_ALIASED: 353 | case LTC_EDITED: 354 | manager->update_struct(ordinal, get_nice_struc_name(ordinal).c_str()); 355 | break; 356 | case LTC_COMPILER: 357 | case LTC_DELETED: 358 | case LTC_NONE: 359 | case LTC_TIL_COMPACTED: 360 | case LTC_TIL_LOADED: 361 | case LTC_TIL_UNLOADED: 362 | default: 363 | break; 364 | } 365 | break; 366 | } 367 | #else 368 | case idb_event::struc_renamed: { 369 | auto struc = va_arg(va, struc_t*); 370 | if (struc) { 371 | auto tid = struc->id; 372 | manager->update_struct(tid, get_nice_struc_name(tid).c_str()); 373 | } 374 | break; 375 | } 376 | case idb_event::struc_created: 377 | case idb_event::enum_created: 378 | case idb_event::enum_renamed: { 379 | auto tid = va_arg(va, tid_t); 380 | manager->update_struct(tid, get_nice_struc_name(tid).c_str()); 381 | } 382 | #endif 383 | default: 384 | break; 385 | } 386 | return 0; 387 | } 388 | 389 | static ssize_t idaapi idp_hooks(void* user_data, int notification_code, 390 | va_list va) { 391 | auto manager = reinterpret_cast(user_data); 392 | 393 | switch (notification_code) { 394 | case processor_t::ev_term: 395 | manager->clear(); 396 | } 397 | return 0; 398 | } 399 | }; 400 | 401 | QVector getNames(bool clear = false) { 402 | static NamesManager* manager; 403 | 404 | if (!manager) { 405 | manager = new NamesManager(); 406 | } 407 | 408 | return manager->get(); 409 | } 410 | 411 | class command_palette_handler : public action_handler_t { 412 | int idaapi activate(action_activation_ctx_t* context) override { 413 | qstring shortcut; 414 | get_action_shortcut(&shortcut, context->action); 415 | shortcut.replace("-", "+"); 416 | show_palette("command palette", "Enter action or option name...", 417 | getActions(), shortcut.c_str(), [](Action& action) { 418 | process_ui_action(action.id.toStdString().c_str()); 419 | return true; 420 | }); 421 | return 1; 422 | } 423 | 424 | action_state_t idaapi update(action_update_ctx_t*) override { 425 | return AST_ENABLE_ALWAYS; 426 | } 427 | }; 428 | 429 | void jump_to_type(unsigned long long tid) { 430 | #if SHOULD_USE_TYPEINF 431 | open_loctypes_window(tid); 432 | #else 433 | bool use_local_types = reg_read_bool("ifred_local_type", false); 434 | if (get_enum_idx(tid) == -1) { 435 | if (!use_local_types) { 436 | open_structs_window(tid); 437 | } else { 438 | struc_t* s = get_struc(tid); 439 | if (s) open_loctypes_window(s->ordinal); 440 | } 441 | } else { 442 | if (!use_local_types) 443 | open_enums_window(tid); 444 | else 445 | open_loctypes_window(get_enum_type_ordinal(tid)); 446 | } 447 | #endif 448 | } 449 | 450 | class name_palette_handler : public action_handler_t { 451 | int idaapi activate(action_activation_ctx_t* context) override { 452 | qstring shortcut; 453 | get_action_shortcut(&shortcut, context->action); 454 | shortcut.replace("-", "+"); 455 | show_palette( 456 | "name palette" + QString(get_path(PATH_TYPE_IDB)), 457 | "Enter symbol name...", getNames(), shortcut.c_str(), 458 | [](Action& action) { 459 | auto&& id = action.id; 460 | if (id.startsWith("struct:")) { 461 | auto tid = QStringView(id).mid(7).toULongLong(); 462 | jump_to_type(tid); 463 | } else { 464 | ea_t address = static_cast(id.toULongLong(nullptr, 16)); 465 | jumpto(address); 466 | qstring name; 467 | get_ea_name(&name, address, 0, 0); 468 | reg_update_strlist("History\\$", name.c_str(), 32); 469 | } 470 | 471 | return true; 472 | }); 473 | return 1; 474 | } 475 | 476 | action_state_t idaapi update(action_update_ctx_t*) override { 477 | return AST_ENABLE_ALWAYS; 478 | } 479 | }; 480 | 481 | class toggle_local_types_handler : public action_handler_t { 482 | int idaapi activate(action_activation_ctx_t* context) override { 483 | bool new_value = !reg_read_bool("ifred_local_type", false); 484 | reg_write_bool("ifred_local_type", new_value); 485 | return 1; 486 | } 487 | 488 | action_state_t idaapi update(action_update_ctx_t*) override { 489 | return AST_ENABLE_ALWAYS; 490 | } 491 | }; 492 | 493 | static command_palette_handler command_palette_handler_; 494 | static name_palette_handler name_palette_handler_; 495 | static toggle_local_types_handler toggle_local_types_handler_; 496 | 497 | static action_desc_t command_palette_action = ACTION_DESC_LITERAL( 498 | "ifred:command_palette", "ifred: Command Palette", 499 | &command_palette_handler_, CMD_PALETTE_SHORTCUT, "command palette", -1); 500 | 501 | static action_desc_t name_palette_action = ACTION_DESC_LITERAL( 502 | "ifred:name_palette", "ifred: Name Palette", &name_palette_handler_, 503 | NAME_PALETTE_SHORTCUT, "name palette", -1); 504 | 505 | static action_desc_t toggle_local_types_action = 506 | ACTION_DESC_LITERAL("ifred:toggle_local_types", "ifred: Toggle Local Types", 507 | &toggle_local_types_handler_, "", 508 | "toggle local types or struct/enum view", -1); 509 | 510 | //-------------------------------------------------------------------------- 511 | bool idaapi run(size_t) { return true; } 512 | 513 | extern char comment[], help[], wanted_name[]; 514 | 515 | //-------------------------------------------------------------------------- 516 | char comment[] = "ifred"; 517 | 518 | char help[] = "IDA palette"; 519 | 520 | //-------------------------------------------------------------------------- 521 | // This is the preferred name of the plugin module in the menu system 522 | // The preferred name may be overridden in plugins.cfg file 523 | 524 | char wanted_name[] = "ifred"; 525 | 526 | QString IdaPluginPath(const char* filename) { 527 | static QString g_plugin_path; 528 | if (g_plugin_path.size()) { 529 | QString r = g_plugin_path + filename; 530 | return r; 531 | } 532 | 533 | g_plugin_path = QString(get_user_idadir()) + "/plugins/palette/"; 534 | QDir plugin_dir(g_plugin_path); 535 | plugin_dir.mkpath("."); 536 | 537 | return g_plugin_path + filename; 538 | } 539 | 540 | #if IDA_SDK_VERSION >= 750 541 | #define INIT_RETURN_TYPE plugmod_t* 542 | #else 543 | #define INIT_RETURN_TYPE int 544 | #endif 545 | 546 | //-------------------------------------------------------------------------- 547 | INIT_RETURN_TYPE idaapi init() { 548 | if (!is_idaq()) 549 | // the plugin works only with idaq 550 | return PLUGIN_SKIP; 551 | 552 | msg("loading palettes...\n"); 553 | 554 | // 1. init IDAPython 555 | #if PYTHON_SUPPORT 556 | void initpy(); 557 | initpy(); 558 | #endif 559 | 560 | // 2. init theme path handler 561 | set_path_handler(IdaPluginPath); 562 | 563 | if (!register_action(command_palette_action)) { 564 | msg("command palette action loading error\n"); 565 | return PLUGIN_SKIP; 566 | }; 567 | 568 | if (!register_action(name_palette_action)) { 569 | msg("name palette action loading error\n"); 570 | return PLUGIN_SKIP; 571 | }; 572 | 573 | if (!register_action(toggle_local_types_action)) { 574 | msg("toggle local types action loading error\n"); 575 | return PLUGIN_SKIP; 576 | }; 577 | 578 | #ifdef __MAC__ 579 | if (!mac_dlopen_workaround()) { 580 | msg("ifred mac dlopen workaround error\n"); 581 | return PLUGIN_SKIP; 582 | } 583 | #endif 584 | 585 | #ifdef _WIN32 586 | HMODULE hModule; 587 | if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)&init, 588 | &hModule)) { 589 | msg("ifred windows loadlibrary workaround error"); 590 | return PLUGIN_SKIP; 591 | } 592 | #endif 593 | 594 | qstring shortcut, shortcut2; 595 | get_action_shortcut(&shortcut, "CommandPalette"); 596 | get_action_shortcut(&shortcut2, command_palette_action.name); 597 | 598 | if (shortcut == "Ctrl-Shift-P" && shortcut == shortcut2) 599 | update_action_shortcut("CommandPalette", ""); 600 | 601 | return PLUGIN_KEEP; 602 | } 603 | 604 | //-------------------------------------------------------------------------- 605 | void idaapi term() { cleanup_palettes(); } 606 | 607 | //-------------------------------------------------------------------------- 608 | // 609 | // PLUGIN DESCRIPTION BLOCK 610 | // 611 | //-------------------------------------------------------------------------- 612 | plugin_t PLUGIN = { 613 | IDP_INTERFACE_VERSION, 614 | PLUGIN_FIX | PLUGIN_HIDE, // plugin flags 615 | init, // initialize 616 | 617 | term, // terminate. this pointer may be NULL. 618 | 619 | run, // invoke plugin 620 | 621 | comment, // long comment about the plugin 622 | // it could appear in the status line 623 | // or as a hint 624 | 625 | help, // multiline help about the plugin 626 | 627 | wanted_name, // the preferred short name of the plugin 628 | "" // the preferred hotkey to run the plugin 629 | }; 630 | --------------------------------------------------------------------------------