Compare commits

..

6 commits

Author SHA1 Message Date
Florian Märkl
4cb6ff6f98
Update rizin for string set config vars (#3653)
Some checks failed
CI / (push) Failing after 4s
CI / linux-x86_64-system-deps (push) Failing after 8s
CI / linux-x86_64 (push) Failing after 6s
CI / tarball (push) Failing after 4s
CI / linux-x86_64-qt5-system-deps (push) Failing after 3s
CI / -1 (push) Failing after 3s
CI / linux-x86_64-qt5 (push) Failing after 2s
Docs / deploy (push) Failing after 2s
Linter / changes (push) Failing after 2s
Linter / clang-tidy (push) Has been skipped
Linter / clang-format (push) Failing after 0s
CI / -2 (push) Has been cancelled
CI / -3 (push) Has been cancelled
CI / macos-arm64 (push) Has been cancelled
CI / -4 (push) Has been cancelled
CI / windows-x86_64 (push) Has been cancelled
CI / macos-x86_64 (push) Has been cancelled
CI / plugin-test-macos-arm64 (push) Has been cancelled
CI / plugin-test-windows (push) Has been cancelled
CI / plugin-test-linux-x86_64 (push) Has been cancelled
Changed in rizin fa2db74f86c3432c7b68a4f1913a72722b4ac6ac:
Config vars storing multiple strings do not use a list anymore but a set
as the strings are meant to be unique.
2026-08-03 16:25:15 +02:00
XU Pengfei
26fa614d01
macos_sign.sh: shrink signed DMG before conversion (#3649)
Without the final shrinking pass, diskutil verifyVolume errors with "Invalid
number of allocation blocks", which causes issues for non-interactive mounting
in package managers.
2026-07-27 16:32:16 +02:00
Premade
867c54e838
Optimize disassembly scroll and selection logic (#3646)
1. Scroll by visual lines instead of disassembly lines (see #3604)

Previously whenever a scroll happened, cutter cleared all of the disassembled
lines it had and queried rizin again, even if the scroll was only for one
instruction. This causes two problems:

* All of the lines have to be queried again making scrolling slow.
* Since an instruction can have multiple metadata lines attached to it, it meant
  that whenever a user scrolled a single instruction all of the metadata lines
  will also be scrolled - making scrolling feel choppy.

To solve this we keep a buffer of "max visible lines * 5" which is filled in as
user scrolls, meaning at first only "max visible lines" are queried from rizin,
if user scrolls upwards the entire "max visible lines" above the current top
instruction is fetched and prepended to our "lines" buffer. Since the previous
lines are still saved - if the user scrolls back down we can just show those
specific lines from our buffer instead of querying again.

Lines are erased from start or end based on scroll direction if the buffer
exceeds the size cap of "max visible lines * 5" The "lines" buffer is fully
cleared if user seeks to some address using some external signal - like the
Visual nav bar at the top (basically seeking using any method other than
directly clicking on the instruction line itself)

2. Adds infinite selection via mouse and keyboard

There was no way in cutter to select more lines than whats currently shown on
screen and there was also no way to select text via keyboard. This is done via
manually handling the selection instead of letting Qt handle it. The selection
is preserved no matter how far user scrolls up or down.

3. Fixes empty space at the bottom of disassembly widget viewport

Whenever "max visible lines" were calculated it didn't account for the fact that
the last line might be partially visible meaning there is not enough space to
display it fully, which created empty space at the bottom

4. Adds a visible/blinking cursor to the disassembly panel

Avoids the issue of not knowing which line we are on, if the line contains
similar text that is highlighted. Also helps to know where selection will start
using physical keys
2026-07-17 12:52:28 +02:00
Anton Bolshakov
e01d86b5fa
cmake: add CUTTER_ENABLE_TRANSLATIONS option to skip translation build (#3640)
Allows building and installing Cutter without translation files by
passing -DCUTTER_ENABLE_TRANSLATIONS=OFF to CMake.

This is useful when building from a source snapshot (e.g. a GitHub
commit archive) that does not include the .ts files, or when the Qt
linguist tools are not available.
2026-07-02 16:43:10 +05:00
Andersen Dsa
2c6c838a13
Add viewport intersection check to cull invisible GraphView edges (#3619) 2026-07-02 13:23:57 +05:00
Florian Märkl
3978a4e8e4
Use x86 image for x86 macos builds (macOS 15, dev) (#3639) 2026-06-30 19:57:59 +02:00
27 changed files with 938 additions and 268 deletions

View file

@ -397,7 +397,7 @@ jobs:
python-version: 3.12.x
output-id: artifact_windows
- name: macos-x86_64
os: macos-14
os: macos-15-intel # Must be an x86_64 image: https://github.com/actions/runner-images#available-images
arch: x86_64
package: true
- name: macos-arm64

View file

@ -19,6 +19,7 @@ set(CUTTER_EXTRA_PLUGIN_DIRS "" CACHE STRING "List of addition plugin locations"
option(CUTTER_ENABLE_DEPENDENCY_DOWNLOADS "Enable downloading of dependencies. Setting to OFF doesn't affect any downloads done by rizin build." OFF)
option(CUTTER_ENABLE_PACKAGING "Enable building platform-specific packages for distributing" OFF)
option(CUTTER_ENABLE_SIGDB "Downloads and installs sigdb (only available when CUTTER_USE_BUNDLED_RIZIN=ON)." OFF)
option(CUTTER_ENABLE_TRANSLATIONS "Build and install translation files" ON)
option(CUTTER_PACKAGE_DEPENDENCIES "During install step include the third party dependencies." OFF)
option(CUTTER_PACKAGE_RZ_GHIDRA "Compile and install rz-ghidra during install step." OFF)
option(CUTTER_PACKAGE_RZ_LIBSWIFT "Compile and install rz-libswift demangler during the install step." OFF)

View file

@ -53,7 +53,7 @@ endif()
# TODO: This version number should be fetched automatically
# instead of being hardcoded.
set (Rizin_VERSION 0.9)
set (Rizin_VERSION 0.10)
set (RZ_LIBS rz_core rz_config rz_cons rz_io rz_util rz_flag rz_mark rz_arch rz_debug
rz_hash rz_bin rz_lang rz_il rz_egg rz_reg rz_search rz_syscall

4
dist/CMakeLists.txt vendored
View file

@ -201,9 +201,9 @@ if(CUTTER_PACKAGE_RZ_GHIDRA)
# installed Cutter.
ExternalProject_Add(rz-ghidra
GIT_REPOSITORY https://github.com/rizinorg/rz-ghidra
GIT_TAG v0.9.0
#GIT_TAG v0.3.0
#GIT_TAG c7a50a2e7c0a95cd52b167c9ee0fa1805223f08e
#GIT_TAG dev
GIT_TAG dev
#GIT_SHALLOW ON # disable this line when using commit hash
CONFIGURE_COMMAND ""
BUILD_COMMAND ""

View file

@ -4,7 +4,7 @@ $plugin_path = "$dist\plugins\native\"
$pathdll = "$plugin_path\jsdec_cutter.dll"
if (-not (Test-Path -Path 'jsdec' -PathType Container)) {
git clone https://github.com/rizinorg/jsdec.git --depth 1 --branch "v0.9.0"
git clone https://github.com/rizinorg/jsdec.git --depth 1 --branch "dev"
}
cd jsdec
$jsdecdir = (Get-Item .).FullName

View file

@ -2,7 +2,7 @@ $dist = $args[0]
$python = Split-Path((Get-Command python.exe).Path)
if (-not (Test-Path -Path 'libswift' -PathType Container)) {
git clone https://github.com/rizinorg/rz-libswift.git --depth 1 --branch "v0.9.0" libswift
git clone https://github.com/rizinorg/rz-libswift.git --depth 1 libswift
}
cd libswift
& meson.exe --buildtype=release --prefix=$dist build

View file

@ -3,7 +3,7 @@ $cmake_opts = $args[1]
$python = Split-Path((Get-Command python.exe).Path)
if (-not (Test-Path -Path 'rz_libyara' -PathType Container)) {
git clone https://github.com/rizinorg/rz-libyara.git --depth 1 --branch "v0.9.0" rz_libyara
git clone https://github.com/rizinorg/rz-libyara.git --depth 1 --branch main rz_libyara
git -C rz_libyara submodule init
git -C rz_libyara submodule update
}

View file

@ -3,7 +3,7 @@ $cmake_opts = $args[1]
$python = Split-Path((Get-Command python.exe).Path)
if (-not (Test-Path -Path 'rz-silhouette' -PathType Container)) {
git clone https://github.com/rizinorg/rz-silhouette.git --depth 1 --branch "v0.9.0-beta4" rz-silhouette
git clone https://github.com/rizinorg/rz-silhouette.git --depth 1 rz-silhouette
}
cd rz-silhouette
& meson.exe --buildtype=release --prefix=$dist build

2
rizin

@ -1 +1 @@
Subproject commit da228d11cfc865b06442bd66b192caeb6e5ff556
Subproject commit 136a337e1c40236723a58ccca6549aa577e8aaa5

View file

@ -5,7 +5,7 @@ set -e
cd $(dirname "${BASH_SOURCE[0]}")/..
mkdir -p cutter-deps && cd cutter-deps
DEPS_BASE_URL=https://github.com/rizinorg/cutter-deps/releases/download/v18
DEPS_BASE_URL=https://github.com/rizinorg/cutter-deps/releases/download/v19
if [ "$CUTTER_QT" == "5" ]; then
DEPS_FILE_linux_x86_64=cutter-deps-q5-linux-x86_64.tar.gz
@ -13,18 +13,18 @@ if [ "$CUTTER_QT" == "5" ]; then
DEPS_BASE_URL=https://github.com/rizinorg/cutter-deps/releases/download/qt5-v17
else
DEPS_FILE_linux_x86_64=cutter-deps-linux-x86_64.tar.gz
DEPS_SHA256_linux_x86_64=204683c44e4824df7cb9925f1939544f081e64b7a701fe95fd03f67125a165b4
DEPS_SHA256_linux_x86_64=b4bebf95c69f6216456d31b345d660ae3347517a6c14a4a4e9d568b03289c3de
fi
echo $DEPS_SHA256_linux_x86_64
DEPS_FILE_macos_x86_64=cutter-deps-macos-x86_64.tar.gz
DEPS_SHA256_macos_x86_64=a46c2dee12d4160410237da848075fc87c41c3850021f583d3b469a59c386650
DEPS_SHA256_macos_x86_64=6f202bd2b18cae780d4fe4ddce662909eebb4f0cd3f1c685e5b2bb6cd46a3e7d
DEPS_FILE_macos_arm64=cutter-deps-macos-arm64.tar.gz
DEPS_SHA256_macos_arm64=1ca381b562d632a370e05a85b0fe60a591a261bba0f48d97a0e40b4307c2577a
DEPS_SHA256_macos_arm64=fa4b3c58f02e7b57a18c2fe7b8c196dd008187deda72fa1ae685c8ed661e10c6
DEPS_FILE_win_x86_64=cutter-deps-win-x86_64.tar.gz
DEPS_SHA256_win_x86_64=95a3cf676178b8c58442d22ee8b21ce2530bfdae86f514901310490a8f28867a
DEPS_SHA256_win_x86_64=3d69280bfedfdd3363fac450f814fc2f09cdd6149f60541dcdaa81f782c3ac76
ARCH=x86_64

View file

@ -9,7 +9,7 @@ SCRIPTPATH=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
cd "$SCRIPTPATH/.."
if [ ! -d jsdec ]; then
git clone https://github.com/rizinorg/jsdec.git --depth 1 --branch "v0.9.0"
git clone https://github.com/rizinorg/jsdec.git --depth 1 --branch "dev"
fi
cd jsdec

View file

@ -108,9 +108,17 @@ resign_dmg() {
notarize_bundle Cutter-rw/Cutter.app
unmount
trap - EXIT
# Remove temporary signing space and restore HFS+ volume consistency.
ee hdiutil resize -size min Cutter-rw.dmg
OUTPUT="${1%.*}-signed.dmg"
echo_step "Creating final read-only ${OUTPUT}"
ee hdiutil convert -format UDZO -o "${OUTPUT}" Cutter-rw.dmg
echo_step "Verifying filesystem in ${OUTPUT}"
ee hdiutil attach "${OUTPUT}" -readonly -nobrowse -noautofsck -mountpoint Cutter-rw
trap unmount EXIT
ee diskutil verifyVolume Cutter-rw
unmount
trap - EXIT
}
case "$1" in

View file

@ -7,7 +7,7 @@ SCRIPTPATH=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
cd "$SCRIPTPATH/.."
if [[ ! -d libswift ]]; then
git clone https://github.com/rizinorg/rz-libswift.git --depth 1 --branch "v0.9.0" libswift
git clone https://github.com/rizinorg/rz-libswift.git --depth 1 libswift
fi
cd libswift

View file

@ -8,7 +8,7 @@ EXTRA_CMAKE_OPTS="$2"
cd "$SCRIPTPATH/.."
if [[ ! -d rz_libyara ]]; then
git clone https://github.com/rizinorg/rz-libyara.git --depth 1 --branch "v0.9.0" rz_libyara
git clone https://github.com/rizinorg/rz-libyara.git --depth 1 --branch main rz_libyara
git -C rz_libyara submodule init
git -C rz_libyara submodule update
fi

View file

@ -8,7 +8,7 @@ EXTRA_CMAKE_OPTS="$2"
cd "$SCRIPTPATH/.."
if [[ ! -d rz-silhouette ]]; then
git clone https://github.com/rizinorg/rz-silhouette.git --depth 1 --branch "v0.9.0-beta4" rz-silhouette
git clone https://github.com/rizinorg/rz-silhouette.git --depth 1 rz-silhouette
fi
cd rz-silhouette

View file

@ -613,7 +613,9 @@ if(CUTTER_ENABLE_PACKAGING)
target_compile_definitions(Cutter PRIVATE CUTTER_ENABLE_PACKAGING)
endif()
include(Translations)
if(CUTTER_ENABLE_TRANSLATIONS)
include(Translations)
endif()
# Install files
install(TARGETS Cutter

View file

@ -4,19 +4,55 @@
#include <QAbstractButton>
#include <QAbstractItemView>
#include <QAbstractTextDocumentLayout>
#include <QComboBox>
#include <QCryptographicHash>
#include <QDockWidget>
#include <QFileInfo>
#include <QFontMetricsF>
#include <QMenu>
#include <QPlainTextEdit>
#include <QString>
#include <QTextBlock>
#include <QTextEdit>
#include <QTreeWidget>
#include <QtCore>
#include <cmath>
namespace {
template<typename TextEditType>
int calculateMaxDisplayedLines(TextEditType *textEdit)
{
qreal lineHeight = 0;
const QTextBlock firstBlock = textEdit->document()->begin();
if (firstBlock.isValid()) {
lineHeight = textEdit->document()->documentLayout()->blockBoundingRect(firstBlock).height();
}
if (lineHeight <= 0) {
const QFontMetricsF fm(textEdit->font());
lineHeight = fm.lineSpacing();
}
qreal availableHeight = textEdit->viewport()->height();
const qreal margin = textEdit->document()->documentMargin();
availableHeight -= margin;
const qreal exactLines = availableHeight / lineHeight;
const qreal floorValue = std::floor(exactLines);
const qreal fractionalPart = exactLines - floorValue;
// If the last line is more than 15% visible then count it, otherwise leave it
// avoids the issue where there is empty space at the bottom of a textedit widget
if (fractionalPart >= 0.15) {
return std::ceil(availableHeight / lineHeight);
}
return floorValue;
}
}
namespace qhelpers {
QString formatByteCount(ut64 bytecount)
@ -174,20 +210,12 @@ void SizePolicyMinMax::restoreHeight(QWidget *widget) const
int getMaxFullyDisplayedLines(QTextEdit *textEdit)
{
const QFontMetrics fontMetrics(textEdit->document()->defaultFont());
return (textEdit->height()
- (textEdit->contentsMargins().top() + textEdit->contentsMargins().bottom()
+ (int)(textEdit->document()->documentMargin() * 2)))
/ fontMetrics.lineSpacing();
return calculateMaxDisplayedLines(textEdit);
}
int getMaxFullyDisplayedLines(QPlainTextEdit *plainTextEdit)
{
const QFontMetrics fontMetrics(plainTextEdit->document()->defaultFont());
return (plainTextEdit->height()
- (plainTextEdit->contentsMargins().top() + plainTextEdit->contentsMargins().bottom()
+ (int)(plainTextEdit->document()->documentMargin() * 2)))
/ fontMetrics.lineSpacing();
return calculateMaxDisplayedLines(plainTextEdit);
}
QByteArray applyColorToSvg(const QByteArray &data, QColor color)

View file

@ -1187,19 +1187,15 @@ void CutterCore::setConfig(const char *k, const RzInterval &itv)
void CutterCore::setConfig(const char *k, const QStringList &list)
{
CORE_LOCK();
RzList *rzList = rz_list_newf(free);
if (!rzList) {
RzSetS *set = rz_set_s_new(HT_STR_DUP);
if (!set) {
return;
}
for (const QString &str : list) {
char *dupStr = strdup(str.toUtf8().constData());
if (dupStr) {
rz_list_append(rzList, dupStr);
}
rz_set_s_add(set, str.toUtf8().constData());
}
rz_config_set_list(core->config, k, rzList);
rz_config_set_set(core->config, k, set);
rz_set_s_free(set);
}
int CutterCore::getConfigi(const char *k)
@ -1226,16 +1222,12 @@ RzInterval CutterCore::getConfigItv(const char *k)
return rz_config_get_interval(core->config, k);
}
QStringList CutterCore::getConfigList(const char *k)
QSet<QString> CutterCore::getConfigSet(const char *k)
{
CORE_LOCK();
QStringList res;
RzList *list = rz_config_get_list(core->config, k);
for (const auto *s : CutterRzList<const char>(list)) {
res << QString::fromUtf8(s);
}
rz_list_free(list);
RzSetS *set = rz_config_get_set(core->config, k);
QSet<QString> res = convertRzSetS(set);
rz_set_s_free(set);
return res;
}
@ -1295,18 +1287,18 @@ QString CutterCore::getConfig(const char *k)
return { rz_config_get(core->config, k) };
}
QStringList CutterCore::getConfigOptions(const char *k)
QSet<QString> CutterCore::getConfigOptions(const char *k)
{
CORE_LOCK();
const RzConfigNode *node = rz_config_node_get(core->config, k);
if (!(node && node->options)) {
return {};
}
QStringList list;
for (const auto &s : CutterRzList<char>(node->options)) {
list << s;
QSet<QString> res;
for (auto it = CutterRzIter<const char *>(rz_set_s_as_iter(node->options)); it; ++it) {
res << QString::fromUtf8(*it);
}
return list;
return res;
}
void CutterCore::setConfig(const char *k, const QVariant &v)
@ -4154,13 +4146,11 @@ QList<EvaluableVarDescription> CutterCore::getAllEvaluableVars()
var.type = EvaluableVarDescription::Interval;
const RzInterval itv = rz_config_var_get_interval(v);
value = QVariant::fromValue(itv);
} else if (RZ_CONFIG_VAR_IS_TYPE(flags, RZ_CONFIG_VAR_TYPE_LIST)) {
var.type = EvaluableVarDescription::List;
const RzList *list = rz_config_var_get_list(v);
QStringList stringList;
for (const auto *c : CutterRzList<const char>(list)) {
stringList << QString(c);
}
} else if (RZ_CONFIG_VAR_IS_TYPE(flags, RZ_CONFIG_VAR_TYPE_SET)) {
var.type = EvaluableVarDescription::Set;
RzSetS *set = rz_config_var_get_set(v);
value = QVariant::fromValue(convertRzSetS(set));
rz_set_s_free(set);
}
if (value.isNull()) {
@ -4168,13 +4158,9 @@ QList<EvaluableVarDescription> CutterCore::getAllEvaluableVars()
}
var.value = value;
const RzList *optionsList = rz_config_var_get_options(v);
if (optionsList) {
RzListIter *iter;
char *option;
CutterRzListForeach (optionsList, iter, char, option) {
var.options << QString::fromUtf8(option);
}
const RzSetS *optionsSet = rz_config_var_get_options(v);
if (optionsSet) {
var.options = convertRzSetS(optionsSet);
}
} else {
const RzConfigNode *node = &entry->node;
@ -4189,10 +4175,8 @@ QList<EvaluableVarDescription> CutterCore::getAllEvaluableVars()
var.value = QString::fromUtf8(rz_config_entry_get_as_string(entry));
RzListIter *iter;
char *option;
CutterRzListForeach (node->options, iter, char, option) {
var.options << QString::fromUtf8(option);
if (node->options) {
var.options = convertRzSetS(node->options);
}
}

View file

@ -495,12 +495,12 @@ public:
bool getConfigb(const QString &k) { return getConfigb(k.toUtf8().constData()); }
RzInterval getConfigItv(const char *k);
RzInterval getConfigItv(const QString &k) { return getConfigItv(k.toUtf8().constData()); }
QStringList getConfigList(const char *k);
QStringList getConfigList(const QString &k) { return getConfigList(k.toUtf8().constData()); }
QSet<QString> getConfigSet(const char *k);
QSet<QString> getConfigSet(const QString &k) { return getConfigSet(k.toUtf8().constData()); }
QString getConfig(const char *k);
QString getConfig(const QString &k) { return getConfig(k.toUtf8().constData()); }
QString getConfigDescription(const char *k);
QStringList getConfigOptions(const char *k);
QSet<QString> getConfigOptions(const char *k);
QStringList getColorThemes();
QHash<QString, QColor> getTheme();
QStringList getThemeKeys();

View file

@ -10,8 +10,8 @@
#include <QColor>
#include <QList>
#include <QMetaType>
#include <QSet>
#include <QString>
#include <QStringList>
#include <QVariant>
struct FunctionDescription
@ -448,11 +448,11 @@ struct EvaluableVarDescription
QString description;
bool readOnly;
enum Type : ut8 { Bool = 0, Int, String, Interval, List };
enum Type : ut8 { Bool = 0, Int, String, Interval, Set };
Type type;
QVariant value; ///< Can be either QString, QStringList or RzInterval depending on type
QList<QString> options;
QVariant value; ///< Can be either QString, QSet<QString> or RzInterval depending on type
QSet<QString> options;
};
Q_DECLARE_METATYPE(FunctionDescription)

View file

@ -1 +1,12 @@
#include "RizinCpp.h"
#include <QSet>
QSet<QString> convertRzSetS(const RzSetS *set)
{
QSet<QString> res;
for (auto it = CutterRzIter<const char *>(rz_set_s_as_iter(set)); it; ++it) {
res << QString::fromUtf8(*it);
}
return res;
}

View file

@ -187,9 +187,9 @@ public:
return *this;
}
operator bool() { return rzIter && rzIter->cur; }
T &operator*() { return *reinterpret_cast<RzCoreDecodedBytes *>(rzIter->cur); }
T *get() { return reinterpret_cast<RzCoreDecodedBytes *>(rzIter->cur); }
T *operator->() { return reinterpret_cast<RzCoreDecodedBytes *>(rzIter->cur); }
T &operator*() { return *reinterpret_cast<T *>(rzIter->cur); }
T *get() { return reinterpret_cast<T *>(rzIter->cur); }
T *operator->() { return reinterpret_cast<T *>(rzIter->cur); }
};
#define CutterHtDef(xx, XX, K, VB) \
@ -219,4 +219,6 @@ public:
CutterHtDef(sp, SP, const char *, void *);
QSet<QString> convertRzSetS(const RzSetS *set);
#endif // RIZINCPP_H

View file

@ -125,7 +125,7 @@ QVariant RizinConfigOptionsModel::data(const QModelIndex &index, int role) const
const auto itv = evalVar.value.value<RzInterval>();
return QString("%1+%2").arg(rzAddressString(itv.addr), rzSizeString(itv.size));
}
if (evalVar.type == EvaluableVarDescription::List) {
if (evalVar.type == EvaluableVarDescription::Set) {
const QStringList list = evalVar.value.toStringList();
if (list.isEmpty()) {
return QString();
@ -344,14 +344,16 @@ QWidget *RizinConfigOptionsDelegate::createEditor(QWidget *parent,
if (evalVar.type == EvaluableVarDescription::Bool
|| evalVar.type == EvaluableVarDescription::Interval
|| evalVar.type == EvaluableVarDescription::List) {
|| evalVar.type == EvaluableVarDescription::Set) {
// handled in lambda connected to ui->treeView::doubleClicked
return nullptr;
}
if (!evalVar.options.isEmpty()) {
auto *comboBox = new QComboBox(parent);
for (const auto &opt : evalVar.options) {
QStringList opts = evalVar.options.values();
opts.sort();
for (const auto &opt : opts) {
comboBox->addItem(opt);
}
return comboBox;
@ -486,7 +488,7 @@ RizinConfigOptionsWidget::RizinConfigOptionsWidget(PreferencesDialog *parent)
return;
}
if (evalVar.type == EvaluableVarDescription::List) {
if (evalVar.type == EvaluableVarDescription::Set) {
StringListDialog dialog(evalVar.value.toStringList(), this);
dialog.setWindowTitle(tr("Edit List for %1").arg(evalVar.name));
@ -596,8 +598,8 @@ void RizinConfigOptionsWidget::handleConfigOptionChanged(const QModelIndex &topL
if (!originalValues.contains(evalVar.name)) {
if (evalVar.type == EvaluableVarDescription::Interval) {
originalValues[evalVar.name] = QVariant::fromValue(Core()->getConfigItv(evalVar.name));
} else if (evalVar.type == EvaluableVarDescription::List) {
originalValues[evalVar.name] = QVariant::fromValue(Core()->getConfigList(evalVar.name));
} else if (evalVar.type == EvaluableVarDescription::Set) {
originalValues[evalVar.name] = QVariant::fromValue(Core()->getConfigSet(evalVar.name));
} else {
originalValues[evalVar.name] = Core()->getConfig(evalVar.name);
}

View file

@ -215,6 +215,10 @@ const QHash<QString, Shortcut> &getDefaultShortcuts()
{ { Qt::Key_Space },
QT_TRANSLATE_NOOP("DisassemblyWidget", "Switch to Graph"),
"DisassemblyWidget" } },
{ "Disassembly.moveLeft",
{ QList<QKeySequence> { Qt::Key_H }
+ QKeySequence::keyBindings(QKeySequence::MoveToPreviousChar),
QT_TRANSLATE_NOOP("DisassemblyWidget", "Move Cursor Down"), "DisassemblyWidget" } },
{ "Disassembly.moveDown",
{ QList<QKeySequence> { Qt::Key_J }
+ QKeySequence::keyBindings(QKeySequence::MoveToNextLine),
@ -223,6 +227,10 @@ const QHash<QString, Shortcut> &getDefaultShortcuts()
{ QList<QKeySequence> { Qt::Key_K }
+ QKeySequence::keyBindings(QKeySequence::MoveToPreviousLine),
QT_TRANSLATE_NOOP("DisassemblyWidget", "Move Cursor Up"), "DisassemblyWidget" } },
{ "Disassembly.moveRight",
{ QList<QKeySequence> { Qt::Key_L }
+ QKeySequence::keyBindings(QKeySequence::MoveToNextChar),
QT_TRANSLATE_NOOP("DisassemblyWidget", "Move Cursor Up"), "DisassemblyWidget" } },
{ "Disassembly.pageDown",
{ QKeySequence::keyBindings(QKeySequence::MoveToNextPage),
QT_TRANSLATE_NOOP("DisassemblyWidget", "Move Cursor Down By Page"),

File diff suppressed because it is too large Load diff

View file

@ -19,6 +19,8 @@ class DisassemblyContextMenu;
class DisassemblyLeftPanel;
class AddressRangeScrollBar;
enum class RefreshMode : ut8 { Append, Prepend, Reset, Keep, None };
/**
* @brief Main widget for showing disassembly of a binary
*
@ -33,48 +35,67 @@ public:
static QString getWidgetType();
QFontMetricsF getFontMetrics();
QList<DisassemblyLine> getLines();
int getStartIndex() const;
int getEndIndex() const;
/**
* @brief Updates the offset and character position where the cursor selection ends
*/
void updateSelectionPos(const QTextCursor &cursor);
/**
* @brief Updates the offset and character position where the cursor selection starts
*/
void updateSelectionAnchor(const QTextCursor &cursor);
public slots:
/**
* @brief Highlights the currently selected line and updates the
* highlighting of the same words under the cursor in the visible screen.
* This overrides all previous highlighting.
* @return List of selections to be highlighted
*/
void highlightCurrentLine();
QList<QTextEdit::ExtraSelection> highlightCurrentLine();
/**
* @brief Adds the PC line highlighting to the other current highlighting.
* This should be called after highlightCurrentLine since that function
* overrides all previous highlighting.
* This is generally called after highlightCurrentLine
* @return List of selections to be highlighted
*/
void highlightPCLine();
QList<QTextEdit::ExtraSelection> highlightPCLine();
void showDisasContextMenu(const QPoint &pt);
void fontsUpdatedSlot();
void colorsUpdatedSlot();
void scrollInstructions(int count, bool clampToScrollBarRange = false);
void seekPrev();
void setPreviewMode(bool previewMode);
QFontMetricsF getFontMetrics();
QList<DisassemblyLine> getLines();
/**
* @brief Forces the transient vertical scrollbar to appear on scroll
*/
void showTransientScrollBar();
void refreshDisasm(RVA offset = RVA_INVALID, RefreshMode mode = RefreshMode::Reset);
protected slots:
void onSeekChanged(RVA offset, CutterCore::SeekHistoryType type);
void refreshIfInRange(RVA offset);
void instructionChanged(RVA offset);
void refreshDisasm(RVA offset = RVA_INVALID);
bool updateMaxLines();
void cursorPositionChanged();
/**
* @brief Copies the currently highlighted disassembly text to the system clipboard
*/
void copySelection();
protected:
DisassemblyContextMenu *mCtxMenu;
DisassemblyScrollArea *mDisasScrollArea;
DisassemblyTextEdit *mDisasTextEdit;
DisassemblyLeftPanel *leftPanel;
QList<DisassemblyLine> lines;
private:
RVA topOffset;
@ -101,7 +122,31 @@ private:
int topOffsetHistoryPos = 0;
QList<RVA> topOffsetHistory;
int startIndex = 0;
int endIndex = 0;
QList<DisassemblyLine> lines;
// Cursor selection related
RVA selectionAnchorRVA = RVA_INVALID;
/**
* @brief metadata lines attached to instruction have the same offset as the instruction itself,
* this keeps track of which metdata line the cursor was at inside the offset block
*/
int selectionAnchorSubIndex = 0;
int selectionAnchorChar = 0;
RVA selectionPosRVA = RVA_INVALID;
/**
* @brief same use-case as @ref selectionAnchorSubIndex but for cursor position instead if
* anchor
*/
int selectionPosSubIndex = 0;
int selectionPosChar = 0;
QList<RVA> breakpoints;
/**
* @brief Set whenever breakpoints have been updated or the screen has been manually refreshed
*/
bool breakpointsDirty = true;
void setupFonts();
void setupColors();
@ -110,9 +155,53 @@ private:
void connectCursorPositionChanged(bool disconnect);
void moveCursorRelative(bool up, bool page);
void moveCursorRelative(QTextCursor::MoveOperation op, bool page);
void jumpToOffsetUnderCursor(const QTextCursor &);
/**
* @brief Visually highlights the text on screen between the selection start (anchor) and the
* current cursor position
*/
void updateSelection();
void updateContextMenuSelection(bool hasSelection);
/**
* @brief Finds the visual line number in the current view for a specific offset
* @param offset The memory address (RVA) to look for
* @param offsetSubIndex The specific sub-line to target if the instructions has metadata
* attached to it
* @return The index of the line, or -1 if not found
*
* @see DisassemblyHelper::getIndexInOffsetGroup()
*/
int getLineIndex(RVA offset, int offsetSubIndex) const;
/**
* @brief Refreshes the background colors in the disassembly view.
* It applies highlights to both the line currently under the user's cursor and the Program
* Counter (PC) line.
*/
void updateLineHighlights();
/**
* @brief Clears the current text selection so nothing is highlighted as selected
*/
void invalidateCursorSelection();
/**
* @brief Metadata lines attached to an instruction have the same offset saved with it as the
* instruction, This function returns the index of the current line within that offset
* group/block
* * e.g:
* * @code
* ; a comment
* ; another comment <------ assume cursor is here
* ; void func1()
* 0x1000 mov rax, rax
* @endcode
*
* Then the returned index is "1"
*/
int getIndexInOffsetGroup(const QTextCursor &cursor) const;
};
class DisassemblyScrollArea : public QAbstractScrollArea
@ -142,23 +231,30 @@ class DisassemblyTextEdit : public QPlainTextEdit
Q_OBJECT
public:
explicit DisassemblyTextEdit(QWidget *parent = nullptr)
: QPlainTextEdit(parent), lockScroll(false)
{
}
explicit DisassemblyTextEdit(DisassemblyWidget *disasmWidget = nullptr);
void setLockScroll(bool lock) { this->lockScroll = lock; }
qreal textOffset() const;
void setCursorVisible(bool visible);
protected:
bool viewportEvent(QEvent *event) override;
void scrollContentsBy(int dx, int dy) override;
void keyPressEvent(QKeyEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void paintEvent(QPaintEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
private:
bool lockScroll;
QTimer *blinkTimer;
bool cursorVisible = true;
QColor cursorColor;
DisassemblyWidget *disasmWidget = nullptr;
};
/**

View file

@ -349,13 +349,16 @@ void GraphView::paint(QPainter &p, QPoint offset, QRect viewport, qreal scale, b
p.setBrush(Qt::gray);
// Always draw edges
// TODO: Only draw edges if they are actually visible ...
// Draw edges
for (GraphEdge &edge : block.edges) {
if (edge.polyline.empty()) {
continue;
}
// Skips expensive polyline and arrow rendering if the edge's bounding box
// is completely outside the current viewport.
if (!edge.polyline.boundingRect().adjusted(-1, -1, 1, 1).intersects(windowF)) {
continue;
}
const QPolygonF polyline = edge.polyline;
const EdgeConfiguration ec =
edgeConfiguration(block, &blocks[edge.target], interactive);