Merge pull request #3686 from x64dbg/release-notes
Release notes dialog
This commit is contained in:
commit
d6294a8370
|
@ -15,7 +15,7 @@
|
|||
<value>style=allman, convert-tabs, align-pointer=type, align-reference=middle, indent=spaces, indent-namespaces, indent-col1-comments, pad-oper, unpad-paren, keep-one-line-blocks, close-templates</value>
|
||||
</setting>
|
||||
<setting name="Ignore" serializeAs="String">
|
||||
<value>src/cross/vendor</value>
|
||||
<value>src/cross/vendor;src/gui/Src/ThirdPartyLibs/md4c</value>
|
||||
</setting>
|
||||
<setting name="License" serializeAs="String">
|
||||
<value />
|
||||
|
|
|
@ -30,7 +30,7 @@ jobs:
|
|||
|
||||
- name: Build
|
||||
run: |
|
||||
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_UNITY_BUILD=ON -DCMAKE_UNITY_BUILD_BATCH_SIZE=6
|
||||
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_UNITY_BUILD=ON -DCMAKE_UNITY_BUILD_BATCH_SIZE=6 -DX64DBG_RELEASE=${{ startsWith(github.ref, 'refs/tags/') && 'ON' || 'OFF' }}
|
||||
cmake --build build
|
||||
|
||||
- name: Upload Artifacts
|
||||
|
@ -42,8 +42,31 @@ jobs:
|
|||
include-hidden-files: true
|
||||
retention-days: 1
|
||||
|
||||
docs:
|
||||
# Skip building pull requests from the same repository
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository }}
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Build Documentation
|
||||
run: |
|
||||
docs\makechm.bat
|
||||
|
||||
- name: Upload Documentation
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docs
|
||||
path: docs/x64dbg.chm
|
||||
if-no-files-found: error
|
||||
include-hidden-files: true
|
||||
retention-days: 1
|
||||
|
||||
package:
|
||||
needs: cmake
|
||||
needs: [cmake, docs]
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
@ -63,7 +86,13 @@ jobs:
|
|||
name: build-x86
|
||||
path: bin
|
||||
|
||||
- name: Prepare release
|
||||
- name: Download Documentation
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: docs
|
||||
path: docs
|
||||
|
||||
- name: Prepare Release
|
||||
run: |
|
||||
curl.exe -L https://github.com/x64dbg/translations/releases/download/translations/qm.zip -o bin\qm.zip
|
||||
7z x bin\qm.zip -obin
|
||||
|
@ -71,7 +100,7 @@ jobs:
|
|||
$timestamp = Get-Date (Get-Date).ToUniversalTime() -Format "yyyy-MM-dd_HH-mm"
|
||||
echo "timestamp=$timestamp" >> $env:GITHUB_ENV
|
||||
|
||||
- name: Upload Artifacts
|
||||
- name: Upload Snapshot
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: snapshot_${{ env.timestamp }}
|
||||
|
@ -83,7 +112,7 @@ jobs:
|
|||
include-hidden-files: true
|
||||
compression-level: 9
|
||||
|
||||
- name: Upload Artifacts
|
||||
- name: Upload Symbols
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: symbols-snapshot_${{ env.timestamp }}
|
||||
|
@ -93,3 +122,13 @@ jobs:
|
|||
if-no-files-found: error
|
||||
include-hidden-files: true
|
||||
compression-level: 9
|
||||
|
||||
- name: Upload Plugin SDK
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: x64dbg-pluginsdk
|
||||
path: |
|
||||
release/pluginsdk
|
||||
if-no-files-found: error
|
||||
include-hidden-files: true
|
||||
compression-level: 9
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
/bin/*.ini
|
||||
/bin/*.chm
|
||||
/bin/*.zip
|
||||
/bin/release-notes.md
|
||||
!/bin/themes/
|
||||
/src/**/x64/
|
||||
/src/**/Win32/
|
||||
/src/gui_build/
|
||||
|
|
|
@ -33,6 +33,7 @@ endif()
|
|||
|
||||
# Options
|
||||
option(X64DBG_BUILD_IN_TREE "" ON)
|
||||
option(X64DBG_RELEASE "" OFF)
|
||||
|
||||
# Variables
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||
|
@ -53,32 +54,25 @@ find_package(Qt5 REQUIRED
|
|||
WinExtras
|
||||
)
|
||||
|
||||
# Target: zydis_wrapper
|
||||
set(zydis_wrapper_SOURCES
|
||||
cmake.toml
|
||||
"src/zydis_wrapper/Zydis/Zydis.c"
|
||||
"src/zydis_wrapper/Zydis/Zydis.h"
|
||||
"src/zydis_wrapper/zydis_wrapper.cpp"
|
||||
"src/zydis_wrapper/zydis_wrapper.h"
|
||||
)
|
||||
# Subdirectory: src/zydis_wrapper
|
||||
set(CMKR_CMAKE_FOLDER ${CMAKE_FOLDER})
|
||||
if(CMAKE_FOLDER)
|
||||
set(CMAKE_FOLDER "${CMAKE_FOLDER}/src/zydis_wrapper")
|
||||
else()
|
||||
set(CMAKE_FOLDER "src/zydis_wrapper")
|
||||
endif()
|
||||
add_subdirectory("src/zydis_wrapper")
|
||||
set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER})
|
||||
|
||||
add_library(zydis_wrapper STATIC)
|
||||
|
||||
target_sources(zydis_wrapper PRIVATE ${zydis_wrapper_SOURCES})
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${zydis_wrapper_SOURCES})
|
||||
|
||||
target_compile_definitions(zydis_wrapper PUBLIC
|
||||
ZYCORE_STATIC_BUILD
|
||||
ZYDIS_STATIC_BUILD
|
||||
)
|
||||
|
||||
target_include_directories(zydis_wrapper PUBLIC
|
||||
"src/zydis_wrapper"
|
||||
)
|
||||
|
||||
target_include_directories(zydis_wrapper PRIVATE
|
||||
"src/zydis_wrapper/Zydis"
|
||||
)
|
||||
# Subdirectory: src/gui/Src/ThirdPartyLibs/md4c
|
||||
set(CMKR_CMAKE_FOLDER ${CMAKE_FOLDER})
|
||||
if(CMAKE_FOLDER)
|
||||
set(CMAKE_FOLDER "${CMAKE_FOLDER}/src/gui/Src/ThirdPartyLibs/md4c")
|
||||
else()
|
||||
set(CMAKE_FOLDER "src/gui/Src/ThirdPartyLibs/md4c")
|
||||
endif()
|
||||
add_subdirectory("src/gui/Src/ThirdPartyLibs/md4c")
|
||||
set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER})
|
||||
|
||||
# Target: bridge
|
||||
set(bridge_SOURCES
|
||||
|
@ -629,6 +623,8 @@ set(gui_SOURCES
|
|||
"src/gui/Src/Gui/HexLineEdit.cpp"
|
||||
"src/gui/Src/Gui/HexLineEdit.h"
|
||||
"src/gui/Src/Gui/HexLineEdit.ui"
|
||||
"src/gui/Src/Gui/ImageTextBrowser.cpp"
|
||||
"src/gui/Src/Gui/ImageTextBrowser.h"
|
||||
"src/gui/Src/Gui/LineEditDialog.cpp"
|
||||
"src/gui/Src/Gui/LineEditDialog.h"
|
||||
"src/gui/Src/Gui/LineEditDialog.ui"
|
||||
|
@ -665,6 +661,9 @@ set(gui_SOURCES
|
|||
"src/gui/Src/Gui/ReferenceManager.h"
|
||||
"src/gui/Src/Gui/RegistersView.cpp"
|
||||
"src/gui/Src/Gui/RegistersView.h"
|
||||
"src/gui/Src/Gui/ReleaseNotesDialog.cpp"
|
||||
"src/gui/Src/Gui/ReleaseNotesDialog.h"
|
||||
"src/gui/Src/Gui/ReleaseNotesDialog.ui"
|
||||
"src/gui/Src/Gui/RichTextItemDelegate.cpp"
|
||||
"src/gui/Src/Gui/RichTextItemDelegate.h"
|
||||
"src/gui/Src/Gui/SEHChainView.cpp"
|
||||
|
@ -731,6 +730,9 @@ set(gui_SOURCES
|
|||
"src/gui/Src/QHexEdit/XByteArray.cpp"
|
||||
"src/gui/Src/QHexEdit/XByteArray.h"
|
||||
"src/gui/Src/ThirdPartyLibs/ldconvert/ldconvert.h"
|
||||
"src/gui/Src/ThirdPartyLibs/md4c/md4c-entity.h"
|
||||
"src/gui/Src/ThirdPartyLibs/md4c/md4c-html.h"
|
||||
"src/gui/Src/ThirdPartyLibs/md4c/md4c.h"
|
||||
"src/gui/Src/Tracer/TraceBrowser.cpp"
|
||||
"src/gui/Src/Tracer/TraceBrowser.h"
|
||||
"src/gui/Src/Tracer/TraceDump.cpp"
|
||||
|
@ -806,6 +808,12 @@ add_library(gui SHARED)
|
|||
target_sources(gui PRIVATE ${gui_SOURCES})
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${gui_SOURCES})
|
||||
|
||||
if(X64DBG_RELEASE) # X64DBG_RELEASE
|
||||
target_compile_definitions(gui PUBLIC
|
||||
X64DBG_RELEASE
|
||||
)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(gui PRIVATE
|
||||
BUILD_LIB
|
||||
NOMINMAX
|
||||
|
@ -833,11 +841,16 @@ if(NOT TARGET bridge)
|
|||
message(FATAL_ERROR "Target \"bridge\" referenced by \"gui\" does not exist!")
|
||||
endif()
|
||||
|
||||
if(NOT TARGET md4c-html)
|
||||
message(FATAL_ERROR "Target \"md4c-html\" referenced by \"gui\" does not exist!")
|
||||
endif()
|
||||
|
||||
target_link_libraries(gui PRIVATE
|
||||
Qt5::Widgets
|
||||
Qt5::WinExtras
|
||||
zydis_wrapper
|
||||
bridge
|
||||
md4c-html
|
||||
winmm
|
||||
wininet
|
||||
)
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
23
cmake.toml
23
cmake.toml
|
@ -4,6 +4,7 @@ cmkr-include = "cmake/cmkr.cmake"
|
|||
|
||||
[options]
|
||||
X64DBG_BUILD_IN_TREE = true
|
||||
X64DBG_RELEASE = false
|
||||
|
||||
[variables]
|
||||
CMAKE_MODULE_PATH = "${CMAKE_SOURCE_DIR}/cmake"
|
||||
|
@ -26,24 +27,8 @@ x64 = "CMAKE_SIZEOF_VOID_P EQUAL 8"
|
|||
[find-package]
|
||||
Qt5 = { components = ["Widgets", "WinExtras"] }
|
||||
|
||||
[target.zydis_wrapper]
|
||||
type = "static"
|
||||
sources = [
|
||||
"src/zydis_wrapper/*.cpp",
|
||||
"src/zydis_wrapper/*.h",
|
||||
"src/zydis_wrapper/Zydis/Zydis.h",
|
||||
"src/zydis_wrapper/Zydis/Zydis.c",
|
||||
]
|
||||
include-directories = [
|
||||
"src/zydis_wrapper",
|
||||
]
|
||||
private-include-directories = [
|
||||
"src/zydis_wrapper/Zydis",
|
||||
]
|
||||
compile-definitions = [
|
||||
"ZYCORE_STATIC_BUILD",
|
||||
"ZYDIS_STATIC_BUILD",
|
||||
]
|
||||
[subdir."src/zydis_wrapper"]
|
||||
[subdir."src/gui/Src/ThirdPartyLibs/md4c"]
|
||||
|
||||
[target.bridge]
|
||||
type = "shared"
|
||||
|
@ -160,6 +145,7 @@ private-link-libraries = [
|
|||
"Qt5::WinExtras",
|
||||
"::zydis_wrapper",
|
||||
"::bridge",
|
||||
"::md4c-html",
|
||||
"winmm",
|
||||
"wininet",
|
||||
]
|
||||
|
@ -186,6 +172,7 @@ private-compile-definitions = [
|
|||
"NOMINMAX",
|
||||
"X64DBG",
|
||||
]
|
||||
X64DBG_RELEASE.compile-definitions = ["X64DBG_RELEASE"]
|
||||
include-after = ["cmake/deps.cmake"]
|
||||
|
||||
[target.gui.properties]
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
_build*/
|
||||
*.chm
|
||||
python-2.7.18.amd64.portable/
|
||||
hha.dll
|
||||
hhc.exe
|
||||
itcc.dll
|
||||
*.7z
|
|
@ -89,7 +89,7 @@ language = None
|
|||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = ['_build*', 'Thumbs.db', '.DS_Store', 'README.md', '.git', 'python-2.7.18.amd64.portable']
|
||||
exclude_patterns = ['_build*', 'Thumbs.db', '.DS_Store', 'README.md', '.git', 'python-2.7.18.amd64.portable*']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
|
|
|
@ -2,8 +2,14 @@
|
|||
|
||||
REM Command file for Sphinx documentation
|
||||
set PYTHONNOUSERSITE=1
|
||||
set PYTHONHOME=%CD%\python-2.7.18.amd64.portable
|
||||
set PATH=%PYTHONHOME%;%PYTHONHOME%\Scripts;%PATH%
|
||||
set PORTABLE_PYTHON=%~dp0python-2.7.18.amd64.portable
|
||||
|
||||
if not exist "%PORTABLE_PYTHON%" (
|
||||
echo Portable Python not found!
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
set PATH=%PORTABLE_PYTHON%;%PORTABLE_PYTHON%\Scripts;%PATH%
|
||||
set SPHINXBUILD=sphinx-build
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
|
|
|
@ -1,13 +1,32 @@
|
|||
@echo off
|
||||
if "%VSVARSALLPATH%"=="" set VSVARSALLPATH=c:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat
|
||||
echo Setting VS Path
|
||||
call "%VSVARSALLPATH%"
|
||||
|
||||
cd /d "%~dp0"
|
||||
|
||||
set PORTABLE_PYTHON=%~dp0python-2.7.18.amd64.portable
|
||||
if not exist "%PORTABLE_PYTHON%\python.exe" (
|
||||
echo Downloading portable Python...
|
||||
curl.exe -L -O https://github.com/x64dbg/docs/releases/download/python27-portable/python-2.7.18.amd64.portable.7z
|
||||
7z x python-2.7.18.amd64.portable.7z -opython-2.7.18.amd64.portable
|
||||
)
|
||||
|
||||
if not exist "%~dp0hhc.exe" (
|
||||
echo Downloading HTML Help Workshop...
|
||||
pause
|
||||
curl.exe -L -O https://github.com/x64dbg/deps/releases/download/dependencies/hhc-4.74.8702.7z
|
||||
7z x hhc-4.74.8702.7z
|
||||
)
|
||||
|
||||
echo Building Help Project
|
||||
call make htmlhelp
|
||||
if %ERRORLEVEL% neq 0 exit /b %ERRORLEVEL%
|
||||
echo Applying CHM hacks
|
||||
copy theme.js .\_build\htmlhelp\_static\js\theme.js
|
||||
type hacks.css >> .\_build\htmlhelp\_static\css\theme.css
|
||||
echo Building CHM File
|
||||
hhc .\_build\htmlhelp\x64dbgdoc.hhp
|
||||
copy .\_build\htmlhelp\x64dbgdoc.chm x64dbg.chm
|
||||
hhc.exe .\_build\htmlhelp\x64dbgdoc.hhp
|
||||
copy /Y .\_build\htmlhelp\x64dbgdoc.chm x64dbg.chm
|
||||
if not exist "x64dbg.chm" (
|
||||
echo Failed to build CHM file!
|
||||
exit /b 1
|
||||
)
|
||||
echo Finished
|
|
@ -143,11 +143,6 @@ endif()
|
|||
# Target: release_notes
|
||||
set(release_notes_SOURCES
|
||||
cmake.toml
|
||||
"release_notes/ImageTextBrowser.cpp"
|
||||
"release_notes/ImageTextBrowser.h"
|
||||
"release_notes/ReleaseNotesDialog.cpp"
|
||||
"release_notes/ReleaseNotesDialog.h"
|
||||
"release_notes/ReleaseNotesDialog.ui"
|
||||
"release_notes/main.cpp"
|
||||
)
|
||||
|
||||
|
@ -162,12 +157,8 @@ target_include_directories(release_notes PRIVATE
|
|||
release_notes
|
||||
)
|
||||
|
||||
if(NOT TARGET md4c-html)
|
||||
message(FATAL_ERROR "Target \"md4c-html\" referenced by \"release_notes\" does not exist!")
|
||||
endif()
|
||||
|
||||
target_link_libraries(release_notes PRIVATE
|
||||
md4c-html
|
||||
x64dbg::widgets
|
||||
)
|
||||
|
||||
get_directory_property(CMKR_VS_STARTUP_PROJECT DIRECTORY ${PROJECT_SOURCE_DIR} DEFINITION VS_STARTUP_PROJECT)
|
||||
|
|
|
@ -59,7 +59,7 @@ sources = [
|
|||
]
|
||||
include-directories = ["release_notes"]
|
||||
link-libraries = [
|
||||
"::md4c-html",
|
||||
"x64dbg::widgets",
|
||||
]
|
||||
|
||||
[target.hex_viewer]
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
#include "ImageTextBrowser.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QResizeEvent>
|
||||
|
||||
ImageTextBrowser::ImageTextBrowser(QWidget* parent)
|
||||
: QTextBrowser(parent)
|
||||
, mResizeTimer(new QTimer(this))
|
||||
{
|
||||
mResizeTimer->setSingleShot(true);
|
||||
mResizeTimer->setInterval(300);
|
||||
connect(mResizeTimer, &QTimer::timeout, this, [this]()
|
||||
{
|
||||
qDebug() << "timeout";
|
||||
setText(toHtml());
|
||||
});
|
||||
}
|
||||
|
||||
QVariant ImageTextBrowser::loadResource(int type, const QUrl & name)
|
||||
{
|
||||
auto url = name.toString();
|
||||
if(url.startsWith("http"))
|
||||
{
|
||||
// TODO: download
|
||||
}
|
||||
else if(url.startsWith("data:"))
|
||||
{
|
||||
auto base64Index = url.indexOf(";base64,");
|
||||
if(base64Index != -1)
|
||||
{
|
||||
auto data = QByteArray::fromBase64(url.mid(base64Index + 8).toUtf8());
|
||||
auto image = QImage::fromData(data);
|
||||
auto maxWidth = document()->textWidth() - document()->documentMargin() * 2 - 1;
|
||||
if(image.width() > maxWidth)
|
||||
{
|
||||
image = image.scaledToWidth((int)maxWidth, Qt::SmoothTransformation);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
}
|
||||
return QTextBrowser::loadResource(type, name);
|
||||
}
|
||||
|
||||
void ImageTextBrowser::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
if(event->size() != event->oldSize())
|
||||
{
|
||||
//qDebug() << "resizeEvent";
|
||||
//mResizeTimer->start();
|
||||
}
|
||||
QTextBrowser::resizeEvent(event);
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <QTextBrowser>
|
||||
#include <QTimer>
|
||||
|
||||
class ImageTextBrowser : public QTextBrowser
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ImageTextBrowser(QWidget* parent = nullptr);
|
||||
|
||||
protected:
|
||||
QVariant loadResource(int type, const QUrl & name) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
private:
|
||||
QTimer* mResizeTimer = nullptr;
|
||||
};
|
|
@ -1,69 +0,0 @@
|
|||
#include "ReleaseNotesDialog.h"
|
||||
#include "ui_ReleaseNotesDialog.h"
|
||||
|
||||
#include <md4c-html.h>
|
||||
|
||||
static QString fixupHtmlEmojiBug(const QString & html)
|
||||
{
|
||||
// For some reason surrogates do not always display correctly in HTML elements.
|
||||
// As a workaround we add a zero-width space in front of the high surrogate.
|
||||
QString result;
|
||||
int size = html.size();
|
||||
result.reserve(size);
|
||||
for(int i = 0; i < size; i++)
|
||||
{
|
||||
auto ch = html[i];
|
||||
if(ch.isHighSurrogate() && i + 1 < size)
|
||||
{
|
||||
result += QChar(0x200B);
|
||||
}
|
||||
result += ch;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static QString markdownToHtml(const QByteArray & markdown)
|
||||
{
|
||||
auto appendString = [](const MD_CHAR * text, MD_SIZE size, void* userdata)
|
||||
{
|
||||
((std::string*)userdata)->append(text, size);
|
||||
};
|
||||
std::string html;
|
||||
unsigned int parserFlags =
|
||||
MD_FLAG_COLLAPSEWHITESPACE |
|
||||
MD_FLAG_TABLES |
|
||||
MD_FLAG_STRIKETHROUGH |
|
||||
MD_FLAG_TASKLISTS |
|
||||
MD_FLAG_PERMISSIVEAUTOLINKS |
|
||||
MD_FLAG_LATEXMATHSPANS;
|
||||
unsigned int rendererFlags = 0;
|
||||
auto result = md_html(markdown.constData(), markdown.size(), appendString, &html, parserFlags, rendererFlags);
|
||||
if(result != 0)
|
||||
{
|
||||
// TODO: throw an exception instead?
|
||||
return {};
|
||||
}
|
||||
return QString::fromStdString(html);
|
||||
}
|
||||
|
||||
ReleaseNotesDialog::ReleaseNotesDialog(const QByteArray & markdown, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, ui(new Ui::ReleaseNotesDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
auto font = QApplication::font();
|
||||
font.setPointSize(16);
|
||||
ui->textBrowser->document()->setDefaultFont(font);
|
||||
|
||||
ui->textBrowser->setOpenExternalLinks(true);
|
||||
|
||||
auto html = markdownToHtml(markdown);
|
||||
html = fixupHtmlEmojiBug(html);
|
||||
ui->textBrowser->setText(html);
|
||||
}
|
||||
|
||||
ReleaseNotesDialog::~ReleaseNotesDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class ReleaseNotesDialog;
|
||||
}
|
||||
|
||||
class ReleaseNotesDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ReleaseNotesDialog(const QByteArray & markdown, QWidget* parent = nullptr);
|
||||
~ReleaseNotesDialog();
|
||||
|
||||
private:
|
||||
Ui::ReleaseNotesDialog* ui;
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
#include <QApplication>
|
||||
#include <QFile>
|
||||
|
||||
#include "ReleaseNotesDialog.h"
|
||||
#include <Gui/ReleaseNotesDialog.h>
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
|
@ -20,6 +20,10 @@ int main(int argc, char* argv[])
|
|||
auto markdown = f.readAll();
|
||||
|
||||
QApplication a(argc, argv);
|
||||
ReleaseNotesDialog d(markdown);
|
||||
ReleaseNotesDialog d({});
|
||||
if(!d.setMarkdown(QString::fromUtf8(markdown), "https://github.com/x64dbg/x64dbg/issues/"))
|
||||
{
|
||||
puts("Failed to convert markdown!");
|
||||
}
|
||||
return d.exec();
|
||||
}
|
||||
|
|
|
@ -66,23 +66,3 @@ FetchContent_Declare(udmp-parser
|
|||
src
|
||||
)
|
||||
FetchContent_MakeAvailable(udmp-parser)
|
||||
|
||||
# Target: md4c-html
|
||||
set(md4c-html_SOURCES
|
||||
cmake.toml
|
||||
"md4c/md4c-entity.c"
|
||||
"md4c/md4c-entity.h"
|
||||
"md4c/md4c-html.c"
|
||||
"md4c/md4c-html.h"
|
||||
"md4c/md4c.c"
|
||||
"md4c/md4c.h"
|
||||
)
|
||||
|
||||
add_library(md4c-html STATIC)
|
||||
|
||||
target_sources(md4c-html PRIVATE ${md4c-html_SOURCES})
|
||||
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${md4c-html_SOURCES})
|
||||
|
||||
target_include_directories(md4c-html PUBLIC
|
||||
md4c
|
||||
)
|
||||
|
|
|
@ -26,9 +26,3 @@ tag = "3e98b047c52c07bb1816bd0936f561ce7797469d"
|
|||
git = "https://github.com/mrexodia/udmp-parser"
|
||||
tag = "bc5952eda39ba0b1a07fc57f57d166292f1e9563"
|
||||
subdir = "src"
|
||||
|
||||
# https://github.com/mity/md4c/commit/481fbfbdf72daab2912380d62bb5f2187d438408
|
||||
[target.md4c-html]
|
||||
type = "static"
|
||||
sources = ["md4c/*.c", "md4c/*.h"]
|
||||
include-directories = ["md4c"]
|
||||
|
|
|
@ -5,6 +5,11 @@ add_subdirectory(
|
|||
${CMAKE_CURRENT_BINARY_DIR}/zydis_wrapper
|
||||
)
|
||||
|
||||
add_subdirectory(
|
||||
${widgets_SOURCE_DIR}/ThirdPartyLibs/md4c
|
||||
${CMAKE_CURRENT_BINARY_DIR}/md4c
|
||||
)
|
||||
|
||||
include(Qt.cmake)
|
||||
|
||||
set(hook_SOURCES
|
||||
|
@ -58,6 +63,11 @@ set(widgets_SOURCES
|
|||
${widgets_SOURCE_DIR}/Gui/TypeWidget.h
|
||||
${widgets_SOURCE_DIR}/Gui/RichTextItemDelegate.cpp
|
||||
${widgets_SOURCE_DIR}/Gui/RichTextItemDelegate.h
|
||||
${widgets_SOURCE_DIR}/Gui/ReleaseNotesDialog.cpp
|
||||
${widgets_SOURCE_DIR}/Gui/ReleaseNotesDialog.h
|
||||
${widgets_SOURCE_DIR}/Gui/ReleaseNotesDialog.ui
|
||||
${widgets_SOURCE_DIR}/Gui/ImageTextBrowser.cpp
|
||||
${widgets_SOURCE_DIR}/Gui/ImageTextBrowser.h
|
||||
${widgets_SOURCE_DIR}/Memory/MemoryPage.cpp
|
||||
${widgets_SOURCE_DIR}/Memory/MemoryPage.h
|
||||
${widgets_SOURCE_DIR}/Utils/ActionHelpers.h
|
||||
|
@ -98,6 +108,7 @@ target_compile_definitions(x64dbg_widgets PRIVATE
|
|||
target_link_libraries(x64dbg_widgets PUBLIC
|
||||
${QT_LIBRARIES}
|
||||
zydis_wrapper
|
||||
md4c-html
|
||||
)
|
||||
|
||||
# https://doc.qt.io/qt-6/wasm.html#asyncify
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
#include <string.h>
|
||||
|
||||
// TODO: do something cross platform
|
||||
using duint = uint64_t;
|
||||
|
@ -18,7 +19,12 @@ int sprintf_s(char (&Dest)[Count], const char* fmt, Args... args)
|
|||
|
||||
inline size_t strcpy_s(char* dst, size_t size, const char* src)
|
||||
{
|
||||
return strlcpy(dst, src, size);
|
||||
if(!dst || !src || !size)
|
||||
return 0;
|
||||
size_t i = 0;
|
||||
while(i < size - 1 && src[i]) dst[i] = src[i], i++;
|
||||
dst[i] = 0;
|
||||
return i;
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
#include "ImageTextBrowser.h"
|
||||
|
||||
#include <QResizeEvent>
|
||||
#include <QApplication>
|
||||
#include <QScrollBar>
|
||||
#include <QTextBlock>
|
||||
|
||||
ImageTextBrowser::ImageTextBrowser(QWidget* parent)
|
||||
: QTextBrowser(parent)
|
||||
, mResizeTimer(new QTimer(this))
|
||||
{
|
||||
mResizeTimer->setSingleShot(true);
|
||||
mResizeTimer->setInterval(300);
|
||||
connect(mResizeTimer, &QTimer::timeout, this, [this]()
|
||||
{
|
||||
setText(toHtml());
|
||||
auto vbar = verticalScrollBar();
|
||||
vbar->setValue(vbar->maximum() * mSavedScrollPercentage);
|
||||
});
|
||||
}
|
||||
|
||||
void ImageTextBrowser::resizeImages()
|
||||
{
|
||||
auto vbar = verticalScrollBar();
|
||||
auto max = vbar->maximum();
|
||||
mSavedScrollPercentage = (max > 0) ? (qreal)vbar->value() / max : 0.0;
|
||||
mResizeTimer->start();
|
||||
}
|
||||
|
||||
QVariant ImageTextBrowser::loadResource(int type, const QUrl & name)
|
||||
{
|
||||
QImage image;
|
||||
auto url = name.toString();
|
||||
if(url.startsWith("http"))
|
||||
{
|
||||
auto itr = mImageCache.find(url);
|
||||
if(itr != mImageCache.end())
|
||||
{
|
||||
image = itr.value();
|
||||
}
|
||||
else if(mDownloadFn)
|
||||
{
|
||||
image = mDownloadFn(url);
|
||||
mImageCache.insert(url, image);
|
||||
}
|
||||
}
|
||||
else if(url.startsWith("data:"))
|
||||
{
|
||||
auto base64Index = url.indexOf(";base64,");
|
||||
if(base64Index != -1)
|
||||
{
|
||||
auto data = QByteArray::fromBase64(url.mid(base64Index + 8).toUtf8());
|
||||
image = QImage::fromData(data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return QTextBrowser::loadResource(type, name);
|
||||
}
|
||||
|
||||
// Scale the image to the width of the document
|
||||
auto maxWidth = viewport()->width() - document()->documentMargin() * 2;
|
||||
if(image.width() > maxWidth)
|
||||
{
|
||||
image = image.scaledToWidth((int)maxWidth, Qt::SmoothTransformation);
|
||||
}
|
||||
return image;
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <QTextBrowser>
|
||||
#include <QTextCursor>
|
||||
#include <QTimer>
|
||||
#include <QMap>
|
||||
|
||||
#include <functional>
|
||||
|
||||
class ImageTextBrowser : public QTextBrowser
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ImageTextBrowser(QWidget* parent = nullptr);
|
||||
void resizeImages();
|
||||
|
||||
using DownloadFn = std::function<QImage(const QString &)>;
|
||||
|
||||
void setDownloadFn(DownloadFn fn)
|
||||
{
|
||||
mDownloadFn = std::move(fn);
|
||||
}
|
||||
|
||||
protected:
|
||||
QVariant loadResource(int type, const QUrl & name) override;
|
||||
|
||||
private:
|
||||
qreal mSavedScrollPercentage = 0.0;
|
||||
QTimer* mResizeTimer = nullptr;
|
||||
DownloadFn mDownloadFn;
|
||||
QMap<QString, QImage> mImageCache;
|
||||
};
|
|
@ -2,6 +2,7 @@
|
|||
#include "ui_MainWindow.h"
|
||||
#include <QMutex>
|
||||
#include <QMessageBox>
|
||||
#include <QToolButton>
|
||||
#include <QIcon>
|
||||
#include <QUrl>
|
||||
#include <QFileDialog>
|
||||
|
@ -53,6 +54,7 @@
|
|||
#include "MRUList.h"
|
||||
#include "AboutDialog.h"
|
||||
#include "UpdateChecker.h"
|
||||
#include "Gui/ReleaseNotesDialog.h"
|
||||
#include "Tracer/TraceManager.h"
|
||||
//#include "Tracer/TraceWidget.h"
|
||||
#include "Utils/MethodInvoker.h"
|
||||
|
@ -390,6 +392,11 @@ MainWindow::MainWindow(QWidget* parent)
|
|||
setupThemesMenu();
|
||||
setupMenuCustomization();
|
||||
ui->actionAbout_Qt->setIcon(QApplication::style()->standardIcon(QStyle::SP_TitleBarMenuButton));
|
||||
auto toolButton = qobject_cast<QToolButton*>(ui->mainToolBar->widgetForAction(ui->actionCheckUpdates));
|
||||
if(toolButton)
|
||||
{
|
||||
toolButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
}
|
||||
|
||||
// Set default setttings (when not set)
|
||||
SettingsDialog defaultSettings;
|
||||
|
@ -1056,6 +1063,21 @@ void MainWindow::loadWindowSettings()
|
|||
tr("You are running x64dbg on an unsupported operating system version. <b>Future updates will completely stop running on this system.</b><br><br>For more information, see the official <a href=\"%1\">announcement</a>.").arg("https://transition.x64dbg.com")
|
||||
);
|
||||
}
|
||||
|
||||
#ifdef X64DBG_RELEASE
|
||||
auto compileDate = QDateTime(GetCompileDate());
|
||||
compileDate.setTimeSpec(Qt::UTC);
|
||||
auto compileEpoch = compileDate.toSecsSinceEpoch();
|
||||
duint releaseNotesEpoch = 0;
|
||||
BridgeSettingGetUint("Gui", "ReleaseNotesEpoch", &releaseNotesEpoch);
|
||||
if(releaseNotesEpoch < compileEpoch)
|
||||
{
|
||||
showReleaseNotes(releaseNotesEpoch);
|
||||
BridgeSettingSetUint("Gui", "ReleaseNotesPrevEpoch", releaseNotesEpoch);
|
||||
BridgeSettingSetUint("Gui", "ReleaseNotesEpoch", compileEpoch);
|
||||
BridgeSettingFlush();
|
||||
}
|
||||
#endif // X64DBG_RELEASE
|
||||
}
|
||||
|
||||
void MainWindow::setGlobalShortcut(QAction* action, const QKeySequence & key)
|
||||
|
@ -1185,6 +1207,50 @@ QAction* MainWindow::makeCommandAction(QAction* action, const QString & command)
|
|||
return action;
|
||||
}
|
||||
|
||||
void MainWindow::showReleaseNotes(duint cutoffEpoch)
|
||||
{
|
||||
QFile file(QString("%1/../release-notes.md").arg(QCoreApplication::applicationDirPath()));
|
||||
if(!file.open(QFile::ReadOnly))
|
||||
{
|
||||
SimpleErrorBox(
|
||||
this,
|
||||
tr("Error"),
|
||||
tr("Release notes are not available, see <a href=\"%1\">%2</a> for the latest updates.")
|
||||
.arg("https://update.x64dbg.com")
|
||||
.arg("update.x64dbg.com")
|
||||
);
|
||||
return;
|
||||
}
|
||||
auto markdown = QString::fromUtf8(file.readAll());
|
||||
file.close();
|
||||
|
||||
if(cutoffEpoch)
|
||||
{
|
||||
static QRegularExpression re(R"(<!-- *(\d\d\d\d.\d\d.\d\d) *-->)");
|
||||
auto i = re.globalMatch(markdown);
|
||||
QStringList words;
|
||||
while(i.hasNext())
|
||||
{
|
||||
QRegularExpressionMatch match = i.next();
|
||||
auto matchText = match.captured(1);
|
||||
auto matchDate = QDateTime::fromString(matchText, "yyyy.MM.dd");
|
||||
matchDate.setTimeSpec(Qt::UTC);
|
||||
auto matchEpoch = matchDate.toSecsSinceEpoch();
|
||||
if(matchEpoch <= cutoffEpoch)
|
||||
{
|
||||
markdown = markdown.left(match.capturedStart(0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReleaseNotesDialog dialog({}, this);
|
||||
dialog.move(frameGeometry().center() - dialog.rect().center());
|
||||
dialog.setMarkdown(markdown, "https://github.com/x64dbg/x64dbg/issues/");
|
||||
dialog.setWindowIcon(DIcon("bug"));
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void MainWindow::execCommandSlot()
|
||||
{
|
||||
QAction* action = qobject_cast<QAction*>(sender());
|
||||
|
@ -2765,6 +2831,13 @@ void MainWindow::on_actionAbout_Qt_triggered()
|
|||
delete w;
|
||||
}
|
||||
|
||||
void MainWindow::on_actionReleaseNotes_triggered()
|
||||
{
|
||||
duint cutoffEpoch = 0;
|
||||
BridgeSettingGetUint("Gui", "ReleaseNotesPrevEpoch", &cutoffEpoch);
|
||||
showReleaseNotes(cutoffEpoch);
|
||||
}
|
||||
|
||||
void MainWindow::updateStyle()
|
||||
{
|
||||
// Set configured link color
|
||||
|
|
|
@ -196,6 +196,7 @@ private:
|
|||
void onMenuCustomized();
|
||||
void setupMenuCustomization();
|
||||
QAction* makeCommandAction(QAction* action, const QString & command);
|
||||
void showReleaseNotes(duint cutoffEpoch);
|
||||
|
||||
//lists for menu customization
|
||||
QList<QAction*> mFileMenuStrings;
|
||||
|
@ -289,4 +290,5 @@ private slots:
|
|||
void on_actionCheckUpdates_triggered();
|
||||
void on_actionDefaultTheme_triggered();
|
||||
void on_actionAbout_Qt_triggered();
|
||||
void on_actionReleaseNotes_triggered();
|
||||
};
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
<property name="windowTitle">
|
||||
<string>x64dbg</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonIconOnly</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget"/>
|
||||
<widget class="QMenuBar" name="menuBar">
|
||||
<property name="geometry">
|
||||
|
@ -160,6 +163,7 @@
|
|||
<addaction name="actionManual"/>
|
||||
<addaction name="actionFaq"/>
|
||||
<addaction name="actionAbout"/>
|
||||
<addaction name="actionReleaseNotes"/>
|
||||
<addaction name="actionAbout_Qt"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionCrashDump"/>
|
||||
|
@ -300,7 +304,7 @@
|
|||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="allowedAreas">
|
||||
<set>Qt::BottomToolBarArea</set>
|
||||
<set>Qt::NoToolBarArea</set>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>BottomToolBarArea</enum>
|
||||
|
@ -1549,6 +1553,15 @@
|
|||
<string>Output the detailed help information about an assembly mnemonic to the log. Equivalent command "mnemonichelp name".</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionReleaseNotes">
|
||||
<property name="icon">
|
||||
<iconset theme="bug" resource="../../resource.qrc">
|
||||
<normaloff>:/Default/icons/bug.png</normaloff>:/Default/icons/bug.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Release Notes</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<resources>
|
||||
|
|
|
@ -0,0 +1,267 @@
|
|||
#include "ReleaseNotesDialog.h"
|
||||
#include "ui_ReleaseNotesDialog.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
#include <QDesktopServices>
|
||||
#include <QResizeEvent>
|
||||
#include <QScrollBar>
|
||||
|
||||
#include <md4c-html.h>
|
||||
|
||||
struct BadEmoji
|
||||
{
|
||||
QString original;
|
||||
QString replacement;
|
||||
|
||||
BadEmoji(const char* emoji) // NOLINT(google-explicit-constructor)
|
||||
: original(QString::fromUtf8(emoji))
|
||||
{
|
||||
replacement = QString("<span class=\"emoji\">%1</span>").arg(original);
|
||||
}
|
||||
};
|
||||
|
||||
static QString fixupHtmlEmojiBug(QString html)
|
||||
{
|
||||
// Certain emojis do not display correctly on Windows with the Segoe UI font.
|
||||
// As a workaround we enclose them in a <span class="emoji">
|
||||
#ifdef Q_OS_WINDOWS
|
||||
static BadEmoji badEmojis[] =
|
||||
{
|
||||
"\xe2\x98\xba", // ☺
|
||||
"\xe2\x98\xb9", // ☹
|
||||
"\xe2\x98\xa0", // ☠
|
||||
"\xe2\x9d\xa3", // ❣
|
||||
"\xe2\x9d\xa4", // ❤
|
||||
"\xf0\x9f\x97\xa8", // 🗨
|
||||
"\xe2\x9c\x8c", // ✌
|
||||
"\xe2\x98\x9d", // ☝
|
||||
"\xe2\x9c\x8d", // ✍
|
||||
"\xe2\x99\xa8", // ♨
|
||||
"\xe2\x9c\x88", // ✈
|
||||
"\xe2\x98\x80", // ☀
|
||||
"\xe2\x98\x81", // ☁
|
||||
"\xe2\x9d\x84", // ❄
|
||||
"\xe2\x98\x84", // ☄
|
||||
"\xe2\x99\xa0", // ♠
|
||||
"\xe2\x99\xa5", // ♥
|
||||
"\xe2\x99\xa6", // ♦
|
||||
"\xe2\x99\xa3", // ♣
|
||||
"\xe2\x99\x9f", // ♟
|
||||
"\xe2\x9c\x8f", // ✏
|
||||
"\xe2\x9c\x92", // ✒
|
||||
"\xe2\x9c\x82", // ✂
|
||||
"\xe2\x98\xa2", // ☢
|
||||
"\xe2\x98\xa3", // ☣
|
||||
"\xe2\x86\x97", // ↗
|
||||
"\xe2\x9e\xa1", // ➡
|
||||
"\xe2\x86\x98", // ↘
|
||||
"\xe2\x86\x99", // ↙
|
||||
"\xe2\x86\x96", // ↖
|
||||
"\xe2\x86\x95", // ↕
|
||||
"\xe2\x86\x94", // ↔
|
||||
"\xe2\x86\xa9", // ↩
|
||||
"\xe2\x86\xaa", // ↪
|
||||
"\xe2\xa4\xb4", // ⤴
|
||||
"\xe2\xa4\xb5", // ⤵
|
||||
"\xe2\x9c\xa1", // ✡
|
||||
"\xe2\x98\xb8", // ☸
|
||||
"\xe2\x98\xaf", // ☯
|
||||
"\xe2\x9c\x9d", // ✝
|
||||
"\xe2\x98\xa6", // ☦
|
||||
"\xe2\x98\xaa", // ☪
|
||||
"\xe2\x98\xae", // ☮
|
||||
"\xe2\x99\x88", // ♈
|
||||
"\xe2\x99\x89", // ♉
|
||||
"\xe2\x99\x8a", // ♊
|
||||
"\xe2\x99\x8b", // ♋
|
||||
"\xe2\x99\x8c", // ♌
|
||||
"\xe2\x99\x8d", // ♍
|
||||
"\xe2\x99\x8e", // ♎
|
||||
"\xe2\x99\x8f", // ♏
|
||||
"\xe2\x99\x90", // ♐
|
||||
"\xe2\x99\x91", // ♑
|
||||
"\xe2\x99\x92", // ♒
|
||||
"\xe2\x99\x93", // ♓
|
||||
"\xe2\x96\xb6", // ▶
|
||||
"\xe2\x97\x80", // ◀
|
||||
"\xe2\x99\x80", // ♀
|
||||
"\xe2\x99\x82", // ♂
|
||||
"\xe2\x9a\xa7", // ⚧
|
||||
"\xe2\x9c\x96", // ✖
|
||||
"\xe2\x80\xbc", // ‼
|
||||
"\xe2\x81\x89", // ⁉
|
||||
"\xe3\x80\xb0", // 〰
|
||||
"\xe2\x98\x91", // ☑
|
||||
"\xe2\x9c\x94", // ✔
|
||||
"\xe3\x80\xbd", // 〽
|
||||
"\xe2\x9c\xb3", // ✳
|
||||
"\xe2\x9c\xb4", // ✴
|
||||
"\xe2\x9d\x87", // ❇
|
||||
"\xe2\x84\xa2", // ™
|
||||
"\xe2\x93\x82", // Ⓜ
|
||||
"\xf0\x9f\x88\x81", // 🈁
|
||||
"\xf0\x9f\x88\x82", // 🈂
|
||||
"\xf0\x9f\x88\xb7", // 🈷
|
||||
"\xf0\x9f\x88\xb6", // 🈶
|
||||
"\xf0\x9f\x88\xaf", // 🈯
|
||||
"\xf0\x9f\x89\x90", // 🉐
|
||||
"\xf0\x9f\x88\xb9", // 🈹
|
||||
"\xf0\x9f\x88\x9a", // 🈚
|
||||
"\xf0\x9f\x88\xb2", // 🈲
|
||||
"\xf0\x9f\x89\x91", // 🉑
|
||||
"\xf0\x9f\x88\xb8", // 🈸
|
||||
"\xf0\x9f\x88\xb4", // 🈴
|
||||
"\xf0\x9f\x88\xb3", // 🈳
|
||||
"\xe3\x8a\x97", // ㊗
|
||||
"\xe3\x8a\x99", // ㊙
|
||||
"\xf0\x9f\x88\xba", // 🈺
|
||||
"\xf0\x9f\x88\xb5", // 🈵
|
||||
"\xe2\x97\xbc", // ◼
|
||||
"\xe2\x98\x82", // ☂
|
||||
"\xe2\x9c\x89", // ✉
|
||||
"\xe2\x96\xab", // ▫
|
||||
"\xe2\x96\xaa", // ▪
|
||||
"\xe2\x97\xbd", // ◽
|
||||
"\xe2\x97\xbe", // ◾
|
||||
};
|
||||
for(const auto & badEmoji : badEmojis)
|
||||
{
|
||||
html = html.replace(badEmoji.original, badEmoji.replacement);
|
||||
}
|
||||
#endif // Q_OS_WINDOWS
|
||||
|
||||
// For some reason surrogates do not always display correctly in HTML elements.
|
||||
// As a workaround we add a zero-width space in front of the high surrogate.
|
||||
QString result;
|
||||
int size = html.size();
|
||||
result.reserve(size);
|
||||
for(int i = 0; i < size; i++)
|
||||
{
|
||||
auto ch = html.at(i);
|
||||
if(ch.isHighSurrogate())
|
||||
{
|
||||
result += QChar(0x200B);
|
||||
for(; i < size; i++)
|
||||
{
|
||||
ch = html.at(i);
|
||||
result += ch;
|
||||
if(ch.unicode() < 0x0080)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result += ch;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static QString markdownToHtml(const QString & markdown)
|
||||
{
|
||||
auto appendString = [](const MD_CHAR * text, MD_SIZE size, void* userdata)
|
||||
{
|
||||
((std::string*)userdata)->append(text, size);
|
||||
};
|
||||
std::string html = "<body>";
|
||||
unsigned int parserFlags =
|
||||
MD_FLAG_COLLAPSEWHITESPACE |
|
||||
MD_FLAG_TABLES |
|
||||
MD_FLAG_STRIKETHROUGH |
|
||||
MD_FLAG_TASKLISTS |
|
||||
MD_FLAG_PERMISSIVEAUTOLINKS |
|
||||
MD_FLAG_LATEXMATHSPANS;
|
||||
unsigned int rendererFlags = 0;
|
||||
auto markdownUtf8 = markdown.toUtf8();
|
||||
if(md_html(markdownUtf8.constData(), markdownUtf8.size(), appendString, &html, parserFlags, rendererFlags) != 0)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
html += "</body>";
|
||||
return QString::fromStdString(html);
|
||||
}
|
||||
|
||||
static void markdownGithubLinks(QString & markdown, const QString & issueUrl)
|
||||
{
|
||||
static QRegularExpression usernameRegex(R"((?<=^|\s)@([a-zA-Z0-9-]{1,39})(?=\s|$))");
|
||||
markdown.replace(usernameRegex, R"([@\1](https://github.com/\1))");
|
||||
if(!issueUrl.isEmpty())
|
||||
{
|
||||
static QRegularExpression issueRegex(R"((?<=^|\s)#(\d+)(?=\s|$))");
|
||||
markdown.replace(issueRegex, QString(R"([#\1](%1\1))").arg(issueUrl));
|
||||
}
|
||||
}
|
||||
|
||||
ReleaseNotesDialog::ReleaseNotesDialog(ImageTextBrowser::DownloadFn downloadFn, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, ui(new Ui::ReleaseNotesDialog)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
ui->setupUi(this);
|
||||
ui->textBrowser->setDownloadFn(std::move(downloadFn));
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
QFont font("Segoe UI");
|
||||
#else
|
||||
QFont font = QApplication::font();
|
||||
#endif // Q_OS_WINDOWS
|
||||
font.setHintingPreference(QFont::PreferNoHinting);
|
||||
font.setPixelSize(16);
|
||||
font.setStyleHint(QFont::SansSerif);
|
||||
ui->textBrowser->document()->setDefaultFont(font);
|
||||
|
||||
QPalette palette = ui->textBrowser->palette();
|
||||
if(palette.color(QPalette::Text) == Qt::black)
|
||||
{
|
||||
palette.setColor(QPalette::Text, QColor("#1f2328"));
|
||||
ui->textBrowser->setPalette(palette);
|
||||
}
|
||||
|
||||
ui->textBrowser->document()->setDocumentMargin(14);
|
||||
|
||||
QString styleSheet = R"(
|
||||
h1, h2, h3, h4, h5, h6, strong, b {
|
||||
font-weight: 500;
|
||||
})";
|
||||
#ifdef Q_OS_WINDOWS
|
||||
styleSheet += R"(
|
||||
.emoji {
|
||||
font-family: "Segoe UI Emoji";
|
||||
})";
|
||||
#endif // Q_OS_WINDOWS
|
||||
|
||||
ui->textBrowser->document()->setDefaultStyleSheet(styleSheet);
|
||||
ui->textBrowser->setOpenLinks(false);
|
||||
connect(ui->textBrowser, &QTextBrowser::anchorClicked, this, [this](const QUrl & url)
|
||||
{
|
||||
ui->textBrowser->clearFocus();
|
||||
QDesktopServices::openUrl(url);
|
||||
});
|
||||
}
|
||||
|
||||
ReleaseNotesDialog::~ReleaseNotesDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool ReleaseNotesDialog::setMarkdown(QString markdown, const QString & issueUrl)
|
||||
{
|
||||
markdownGithubLinks(markdown, issueUrl);
|
||||
auto html = markdownToHtml(markdown);
|
||||
html = fixupHtmlEmojiBug(html);
|
||||
ui->textBrowser->setText(html);
|
||||
return !html.isEmpty();
|
||||
}
|
||||
|
||||
void ReleaseNotesDialog::setLabel(const QString & text)
|
||||
{
|
||||
ui->label->setText(text);
|
||||
}
|
||||
|
||||
void ReleaseNotesDialog::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QDialog::resizeEvent(event);
|
||||
ui->textBrowser->resizeImages();
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <functional>
|
||||
#include "ImageTextBrowser.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class ReleaseNotesDialog;
|
||||
}
|
||||
|
||||
class ReleaseNotesDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ReleaseNotesDialog(ImageTextBrowser::DownloadFn downloadFn, QWidget* parent = nullptr);
|
||||
~ReleaseNotesDialog() override;
|
||||
bool setMarkdown(QString markdown, const QString & issueUrl);
|
||||
void setLabel(const QString & text);
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
private:
|
||||
Ui::ReleaseNotesDialog* ui;
|
||||
};
|
|
@ -35,15 +35,15 @@
|
|||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>4</number>
|
||||
<number>8</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>4</number>
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
|
@ -53,6 +53,19 @@
|
|||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="text">
|
||||
|
@ -68,7 +81,7 @@
|
|||
<customwidget>
|
||||
<class>ImageTextBrowser</class>
|
||||
<extends>QTextBrowser</extends>
|
||||
<header>ImageTextBrowser.h</header>
|
||||
<header location="global">Gui/ImageTextBrowser.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
|
@ -0,0 +1,12 @@
|
|||
# https://github.com/mity/md4c/commit/481fbfbdf72daab2912380d62bb5f2187d438408
|
||||
add_library(md4c-html STATIC
|
||||
md4c.c
|
||||
md4c.h
|
||||
md4c-entity.c
|
||||
md4c-entity.h
|
||||
md4c-html.c
|
||||
md4c-html.h
|
||||
)
|
||||
target_include_directories(md4c-html PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
|
@ -2,6 +2,10 @@
|
|||
#include <QMessageBox>
|
||||
#include <QIcon>
|
||||
#include <QDateTime>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <Gui/ReleaseNotesDialog.h>
|
||||
|
||||
#include "StringUtil.h"
|
||||
#include "MiscUtil.h"
|
||||
|
@ -136,10 +140,25 @@ static std::string httpGet(const char* url,
|
|||
#endif // _WIN32
|
||||
|
||||
UpdateChecker::UpdateChecker(QWidget* parent)
|
||||
: QThread(parent),
|
||||
mParent(parent)
|
||||
: QThread(parent)
|
||||
, mParent(parent)
|
||||
, mUserAgent("x64dbg " + ToDateString(GetCompileDate()) + " " __TIME__)
|
||||
{
|
||||
connect(this, &UpdateChecker::updateCheckFinished, this, &UpdateChecker::finishedSlot);
|
||||
|
||||
auto downloadFn = [this](const QString & url) -> QImage
|
||||
{
|
||||
auto data = httpGet(url.toUtf8().constData(), mUserAgent.toUtf8().constData(), 3000);
|
||||
if(data.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
QByteArray byteArray(data.c_str(), (int)(data.size()));
|
||||
return QImage::fromData(byteArray);
|
||||
};
|
||||
mReleaseNotes = new ReleaseNotesDialog(downloadFn, mParent);
|
||||
mReleaseNotes->setWindowIcon(DIcon("bug"));
|
||||
}
|
||||
|
||||
void UpdateChecker::checkForUpdates()
|
||||
|
@ -153,9 +172,8 @@ void UpdateChecker::checkForUpdates()
|
|||
|
||||
void UpdateChecker::run()
|
||||
{
|
||||
QString userAgent = "x64dbg " + ToDateString(GetCompileDate()) + " " __TIME__;
|
||||
std::string result = httpGet("https://update.x64dbg.com/releases.json",
|
||||
userAgent.toUtf8().constData(), 3000);
|
||||
mUserAgent.toUtf8().constData(), 3000);
|
||||
emit updateCheckFinished(QString::fromStdString(result));
|
||||
}
|
||||
|
||||
|
@ -167,26 +185,72 @@ void UpdateChecker::finishedSlot(const QString & json)
|
|||
return;
|
||||
}
|
||||
|
||||
QRegExp regExp("\"published_at\": ?\"([^\"]+)\"");
|
||||
QDateTime serverTime;
|
||||
if(regExp.indexIn(json) >= 0)
|
||||
serverTime = QDateTime::fromString(regExp.cap(1), Qt::ISODate);
|
||||
if(!serverTime.isValid())
|
||||
QJsonParseError error;
|
||||
auto releases = QJsonDocument::fromJson(json.toUtf8(), &error).array();
|
||||
if(error.error != QJsonParseError::NoError || releases.isEmpty())
|
||||
{
|
||||
SimpleErrorBox(mParent, tr("Error!"), tr("File on server could not be parsed..."));
|
||||
return;
|
||||
}
|
||||
QRegExp regUrl("\"browser_download_url\": ?\"([^\"]+)\"");
|
||||
auto url = regUrl.indexIn(json) >= 0 ? regUrl.cap(1) : "https://releases.x64dbg.com";
|
||||
auto server = serverTime.date();
|
||||
auto build = GetCompileDate();
|
||||
|
||||
QString markdown;
|
||||
QString label;
|
||||
auto buildDate = GetCompileDate();
|
||||
for(int i = 0; i < releases.size(); i++)
|
||||
{
|
||||
QJsonObject release = releases[i].toObject();
|
||||
auto publishedAt = release.value("published_at").toString();
|
||||
auto publishedDate = QDateTime::fromString(publishedAt, Qt::ISODate).date();
|
||||
auto tagName = release.value("tag_name").toString();
|
||||
|
||||
if(i == 0)
|
||||
{
|
||||
if(publishedDate <= buildDate)
|
||||
{
|
||||
QString info;
|
||||
if(server > build)
|
||||
info = QString(tr("New build %1 available!<br>Download <a href=\"%2\">here</a><br><br>You are now on build %3")).arg(ToDateString(server)).arg(url).arg(ToDateString(build));
|
||||
else if(server < build)
|
||||
info = QString(tr("You have a development build (%1) of x64dbg!")).arg(ToDateString(build));
|
||||
if(publishedDate < buildDate)
|
||||
{
|
||||
info = QString(tr("You have a development build (%1) of x64dbg!")).arg(ToDateString(buildDate));
|
||||
}
|
||||
else
|
||||
info = QString(tr("You have the latest build (%1) of x64dbg!")).arg(ToDateString(build));
|
||||
{
|
||||
info = tr("You have the latest build (%1) of x64dbg!").arg(ToDateString(buildDate));
|
||||
}
|
||||
GuiAddStatusBarMessage((info + "\n").toUtf8().constData());
|
||||
SimpleInfoBox(mParent, tr("Information"), info);
|
||||
return;
|
||||
}
|
||||
|
||||
QString downloadUrl;
|
||||
auto assets = release.value("assets").toArray();
|
||||
for(int j = 0; j < assets.size(); j++)
|
||||
{
|
||||
auto asset = assets[i].toObject();
|
||||
downloadUrl = asset.value("browser_download_url").toString();
|
||||
break;
|
||||
}
|
||||
|
||||
label = tr("<p><b>New x64dbg version available</b>: <a href=\"%1\">%2</a></p>").arg(downloadUrl, tagName);
|
||||
GuiAddLogMessageHtml((label + "\n").toUtf8().constData());
|
||||
}
|
||||
|
||||
// Do not show release notes older than the current build
|
||||
if(publishedDate <= buildDate)
|
||||
break;
|
||||
|
||||
auto body = release.value("body").toString();
|
||||
if(!body.startsWith("#"))
|
||||
{
|
||||
markdown += QString("# %1\n").arg(tagName);
|
||||
}
|
||||
|
||||
markdown += body;
|
||||
markdown += QString("\n\n<sub>%1</sub>").arg(publishedAt);
|
||||
markdown += "\n\n";
|
||||
}
|
||||
|
||||
mReleaseNotes->move(mParent->frameGeometry().center() - mReleaseNotes->rect().center());
|
||||
mReleaseNotes->setMarkdown(markdown, "https://github.com/x64dbg/x64dbg/issues/");
|
||||
mReleaseNotes->setLabel(label);
|
||||
mReleaseNotes->exec();
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include <QThread>
|
||||
|
||||
class ReleaseNotesDialog;
|
||||
|
||||
class UpdateChecker : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -20,4 +22,6 @@ private slots:
|
|||
|
||||
private:
|
||||
QWidget* mParent = nullptr;
|
||||
ReleaseNotesDialog* mReleaseNotes = nullptr;
|
||||
QString mUserAgent;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue