diff --git a/.github/workflows/action-cpp.yml b/.github/workflows/action-cpp.yml index 993367c..75bc6e6 100644 --- a/.github/workflows/action-cpp.yml +++ b/.github/workflows/action-cpp.yml @@ -8,19 +8,15 @@ jobs: matrix: include: - arch: aarch64 - distro: ubuntu_latest + distro: ubuntu22.04 - arch: ppc64le - distro: ubuntu_latest + distro: ubuntu22.04 - arch: s390x - distro: ubuntu_latest + distro: ubuntu22.04 - arch: riscv64 - distro: ubuntu_latest + distro: ubuntu22.04 - arch: aarch64 distro: fedora_latest - - arch: ppc64le - distro: fedora_latest - - arch: s390x - distro: fedora_latest steps: - name: Get current date id: date @@ -31,7 +27,7 @@ jobs: - name: Set artifacts dir id: artifacts run: echo "artifacts=$(echo $PWD/artifacts)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: uraimo/run-on-arch-action@v2 name: Run commands id: configure-and-build @@ -50,16 +46,17 @@ jobs: install: | case "${{ matrix.distro }}" in ubuntu*|jessie|stretch|buster|bullseye) - apt-get update -y && apt-get install -yq binutils python3-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release + apt-get update -y && apt-get install -yq binutils python3-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release libc6-dev libpthread-stubs0-dev glibc-dev ;; fedora*) - yum update -y && yum install -yq binutils python3-nautilus qt5-qtbase-devel qt5-qttools rpm-build redhat-lsb + yum update -y && yum install -yq binutils nautilus-python qt5-qtbase-devel qt5-qttools rpm-build glibc-devel ;; esac run: | + ulimit -s unlimited && mkdir cmake-build-release && cmake -DCOMMITTER_DATE="${{ steps.date.outputs.date }}" -DCOMMITTER_FULLSHA="${{ steps.git_sha.outputs.git_sha }}" -DCOMMITTER_SHORTSHA="$(echo ${{ steps.git_sha.outputs.git_sha }} | cut -c1-7)" -DCMAKE_BUILD_TYPE=Release -Bcmake-build-release -H. && - cmake --build cmake-build-release/ --target all && + cmake --build cmake-build-release/ --target all -- -j1 && [[ "$(lsb_release -i -s)" == "Ubuntu" ]] && export CPACK_FORMAT=DEB || export CPACK_FORMAT=RPM && [[ "$(lsb_release -i -s)" == "Ubuntu" ]] && export PKG_FORMAT=deb || export PKG_FORMAT=rpm && cd cmake-build-release && cpack -G ${CPACK_FORMAT} && cd .. && @@ -70,7 +67,7 @@ jobs: id: get-version run: echo "prj_ver=$(cat ${PWD}/artifacts/VERSION.txt)" >> $GITHUB_OUTPUT - name: Upload Qldd binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ format('Qldd-{0}.{1}-{2}', steps.get-version.outputs.prj_ver, matrix.distro, matrix.arch) }} path: ${{ format('{0}/Qldd-{1}-{2}.???', steps.artifacts.outputs.artifacts ,steps.get-version.outputs.prj_ver, matrix.arch) }} @@ -85,6 +82,7 @@ jobs: matrix: container: - ubuntu:22.04 + - ubuntu:24.04 container: image: ${{ matrix.container }} @@ -96,7 +94,7 @@ jobs: - name: Get commit sha id: git_sha run: echo "git_sha=$(echo $GITHUB_SHA)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # install dependencies - name: devel-pkgs run: apt-get update -y && apt-get install -yq binutils python3-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release @@ -115,61 +113,7 @@ jobs: - name: cpack run: cd cmake-build-release && cpack -G DEB && cd .. # upload artifact - - uses: mad9000/actions-find-and-replace-string@3 - id: container - with: - source: ${{ matrix.container }} - find: ':' # we want to remove : from container name - replace: '-' # and replace it with - - - - name: Upload Qldd binary - uses: actions/upload-artifact@v3 - with: - name: ${{ format('Qldd-{0}.{1}', steps.get-version.outputs.prj_ver, steps.container.outputs.value) }} - path: cmake-build-release/${{ format('Qldd-{0}-x86_64.???', steps.get-version.outputs.prj_ver) }} - ubuntu-20-04: - runs-on: ubuntu-latest - name: Build on ${{ matrix.container }} x86_64 - strategy: - # - # matrix for containers - # - matrix: - container: - - ubuntu:20.04 - - container: - image: ${{ matrix.container }} - - steps: - - name: Get current date - id: date - run: echo "date=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_OUTPUT - - name: Get commit sha - id: git_sha - run: echo "git_sha=$(echo $GITHUB_SHA)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 - # install dependencies - - name: devel-pkgs - run: | - echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections && - apt-get update -y && apt-get install -yq python-nautilus binutils make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release - # build project - - name: mkdir - run: mkdir cmake-build-release - - name: cmake cmake-build-release - run: cmake -DCOMMITTER_DATE="${{ steps.date.outputs.date }}" -DCOMMITTER_FULLSHA="${{ steps.git_sha.outputs.git_sha }}" -DCOMMITTER_SHORTSHA="$(echo ${{ steps.git_sha.outputs.git_sha }} | cut -c1-7)" -DCMAKE_BUILD_TYPE=Release -Bcmake-build-release -H. - - name: cmake make - run: cmake --build cmake-build-release/ --target all - env: - MAKEFLAGS: "-j2" - - name: get-version - id: get-version - run: echo "prj_ver=$(cat ./VERSION.txt)" >> $GITHUB_OUTPUT - - name: cpack - run: cd cmake-build-release && cpack -G DEB && cd .. - # upload artifact - - uses: mad9000/actions-find-and-replace-string@3 + - uses: mad9000/actions-find-and-replace-string@5 id: container with: source: ${{ matrix.container }} @@ -177,7 +121,7 @@ jobs: replace: '-' # and replace it with - - name: Upload Qldd binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ format('Qldd-{0}.{1}', steps.get-version.outputs.prj_ver, steps.container.outputs.value) }} path: cmake-build-release/${{ format('Qldd-{0}-x86_64.???', steps.get-version.outputs.prj_ver) }} @@ -202,7 +146,7 @@ jobs: - name: Get commit sha id: git_sha run: echo "git_sha=$(echo $GITHUB_SHA)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # install dependencies - name: devel-pkgs run: apt-get update -y && apt-get install -yq binutils python3-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release @@ -221,7 +165,7 @@ jobs: - name: cpack run: cd cmake-build-release && cpack -G DEB && cd .. # upload artifact - - uses: mad9000/actions-find-and-replace-string@3 + - uses: mad9000/actions-find-and-replace-string@5 id: container with: source: ${{ matrix.container }} @@ -229,11 +173,11 @@ jobs: replace: '-' # and replace it with - - name: Upload Qldd binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ format('Qldd-{0}.{1}', steps.get-version.outputs.prj_ver, steps.container.outputs.value) }} path: cmake-build-release/${{ format('Qldd-{0}-x86_64.???', steps.get-version.outputs.prj_ver) }} - debian-buster: + debian-bookworm: runs-on: ubuntu-latest name: Build on ${{ matrix.container }} x86_64 strategy: @@ -242,7 +186,7 @@ jobs: # matrix: container: - - debian:buster + - debian:bookworm container: image: ${{ matrix.container }} @@ -254,10 +198,10 @@ jobs: - name: Get commit sha id: git_sha run: echo "git_sha=$(echo $GITHUB_SHA)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # install dependencies - name: devel-pkgs - run: apt-get update -y && apt-get install -yq binutils python-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release + run: apt-get update -y && apt-get install -yq binutils python3-nautilus make cmake qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools gcc g++ lsb-release # build project - name: mkdir run: mkdir cmake-build-release @@ -273,7 +217,7 @@ jobs: - name: cpack run: cd cmake-build-release && cpack -G DEB && cd .. # upload artifact - - uses: mad9000/actions-find-and-replace-string@3 + - uses: mad9000/actions-find-and-replace-string@5 id: container with: source: ${{ matrix.container }} @@ -281,7 +225,7 @@ jobs: replace: '-' # and replace it with - - name: Upload Qldd binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ format('Qldd-{0}.{1}', steps.get-version.outputs.prj_ver, steps.container.outputs.value) }} path: cmake-build-release/${{ format('Qldd-{0}-x86_64.???', steps.get-version.outputs.prj_ver) }} @@ -294,7 +238,7 @@ jobs: # matrix: container: - - centos:latest + - quay.io/centos/centos:stream9 container: image: ${{ matrix.container }} @@ -306,16 +250,15 @@ jobs: - name: Get commit sha id: git_sha run: echo "git_sha=$(echo $GITHUB_SHA)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # install dependencies - name: devel-pkgs run: | - cd /etc/yum.repos.d/ && - sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* && - sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* && - cd / && - yum update -y && yum install -yq epel-next-release && - yum update -y && yum install -yq binutils python3-nautilus qt5-qtbase-devel qt5-qttools rpm-build redhat-lsb-core + yum install -y dnf-plugins-core && + yum config-manager --set-enabled crb && + yum install -y epel-release epel-next-release && + yum update -y && + yum install -yq binutils nautilus-python qt5-qtbase-devel qt5-qttools rpm-build # build project - name: mkdir run: mkdir cmake-build-release @@ -331,15 +274,12 @@ jobs: - name: cpack run: cd cmake-build-release && cpack -G RPM && cd .. # upload artifact - - uses: mad9000/actions-find-and-replace-string@3 + - name: Sanitize container name for artifact id: container - with: - source: ${{ matrix.container }} - find: ':' # we want to remove : from container name - replace: '-' # and replace it with - + run: echo "value=$(echo '${{ matrix.container }}' | tr ':/' '--')" >> $GITHUB_OUTPUT - name: Upload Qldd binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ format('Qldd-{0}.{1}', steps.get-version.outputs.prj_ver, steps.container.outputs.value) }} path: cmake-build-release/${{ format('Qldd-{0}-x86_64.???', steps.get-version.outputs.prj_ver) }} @@ -364,10 +304,10 @@ jobs: - name: Get commit sha id: git_sha run: echo "git_sha=$(echo $GITHUB_SHA)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # install dependencies - name: devel-pkgs - run: yum update -y && yum install -yq binutils python3-nautilus qt5-qtbase-devel qt5-qttools rpm-build redhat-lsb + run: yum update -y && yum install -yq binutils nautilus-python qt5-qtbase-devel qt5-qttools rpm-build # build project - name: mkdir run: mkdir cmake-build-release @@ -383,7 +323,7 @@ jobs: - name: cpack run: cd cmake-build-release && cpack -G RPM && cd .. # upload artifact - - uses: mad9000/actions-find-and-replace-string@3 + - uses: mad9000/actions-find-and-replace-string@5 id: container with: source: ${{ matrix.container }} @@ -391,13 +331,13 @@ jobs: replace: '-' # and replace it with - - name: Upload Qldd binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ format('Qldd-{0}.{1}', steps.get-version.outputs.prj_ver, steps.container.outputs.value) }} path: cmake-build-release/${{ format('Qldd-{0}-x86_64.???', steps.get-version.outputs.prj_ver) }} macos-clang-cmake: runs-on: macos-latest - name: Build on macos-latest x86_64 + name: Build on macos-latest steps: - name: Get current date @@ -406,7 +346,7 @@ jobs: - name: Get commit sha id: git_sha run: echo "git_sha=$(echo $GITHUB_SHA)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # install dependencies - name: brew-pkgs run: brew install qt@5 @@ -414,7 +354,7 @@ jobs: - name: mkdir run: mkdir cmake-build-release - name: cmake cmake-build-release - run: cmake -DCMAKE_PREFIX_PATH=$(brew --prefix)/opt/qt5 -DCOMMITTER_DATE="${{ steps.date.outputs.date }}" -DCOMMITTER_FULLSHA="${{ steps.git_sha.outputs.git_sha }}" -DCOMMITTER_SHORTSHA="$(echo ${{ steps.git_sha.outputs.git_sha }} | cut -c1-7)" -DCMAKE_BUILD_TYPE=Release -Bcmake-build-release -H. + run: cmake -DCMAKE_PREFIX_PATH=$(brew --prefix)/opt/qt@5 -DCOMMITTER_DATE="${{ steps.date.outputs.date }}" -DCOMMITTER_FULLSHA="${{ steps.git_sha.outputs.git_sha }}" -DCOMMITTER_SHORTSHA="$(echo ${{ steps.git_sha.outputs.git_sha }} | cut -c1-7)" -DCMAKE_BUILD_TYPE=Release -Bcmake-build-release -H. - name: cmake make run: cmake --build cmake-build-release/ --target all env: @@ -424,7 +364,7 @@ jobs: run: echo "prj_ver=$(cat ./VERSION.txt)" >> $GITHUB_OUTPUT - name: cpack run: cd cmake-build-release && cpack -G DragNDrop && cd .. - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: ${{ format('Qldd-{0}-Darwin.dmg', steps.get-version.outputs.prj_ver) }} path: cmake-build-release/${{ format('Qldd-{0}-Darwin.dmg', steps.get-version.outputs.prj_ver) }} diff --git a/.gitignore b/.gitignore index c02a68a..3ee38d4 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ VERSION.txt /.DS_Store /CMakeLists.txt.user /cmake-vsc-build/ +/build/ +/.qtcreator/ \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..936776c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,76 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project overview + +**Qldd** (DependencyViewer) is a Qt-based GUI application that shows all dependent libraries of a given executable or shared library on GNU/Linux and macOS. It wraps `ldd` (Linux) / `otool` (macOS), `file`, and `nm` shell commands and presents results in a tree/list view. There are no automated tests — the app is verified manually. + +## Build + +```bash +# Configure (from repo root) +cmake -DCMAKE_BUILD_TYPE=Release -Bcmake-build-release -H. +# Or for a debug build: +cmake -DCMAKE_BUILD_TYPE=Debug -Bcmake-build-debug -H. + +# Build +cmake --build cmake-build-release/ --target all + +# macOS: Qt5 must be installed via Homebrew and its prefix passed explicitly +cmake -DCMAKE_PREFIX_PATH=$(brew --prefix)/opt/qt@5 -DCMAKE_BUILD_TYPE=Release -Bcmake-build-release -H. +``` + +CMake auto-detects Qt5 first; falls back to Qt6. To force Qt6: +```bash +cmake -DFORCE_QT6=ON ... +``` + +### Packaging + +```bash +cd cmake-build-release +cpack -G DEB # Ubuntu/Debian → .deb +cpack -G RPM # Fedora/CentOS → .rpm +cpack -G DragNDrop # macOS → .dmg +``` + +## Architecture + +The app is a single-window Qt application with no separate library targets. + +| File | Role | +|---|---| +| `main.cpp` | Entry point — parses optional filename arg, constructs `MainWindow` | +| `mainwindow.{cpp,h,ui}` | Top-level Qt window; owns a `QLdd` instance, manages menus, file open dialog, export-filter UI, demangling rules, and permission checkboxes | +| `qldd.{cpp,h}` | Core logic — runs `ldd`/`otool`, `file`, `nm` via `popen`; parses output into tree nodes for `QTreeWidget` and list entries for `QListWidget`; reads file metadata (size, timestamps, owner/group, permissions) | +| `demanglerules.{cpp,h,ui}` | Dialog + data model for configuring regex-based symbol demangling rules; rules persist to/from a JSON file | +| `finfdialog.{cpp,h,ui}` | "File info" dialog that displays metadata gathered by `QLdd` | +| `customtypes.h` | Shared typedefs (`RulesMap`) used across the codebase | +| `resources/rules.json` | Default demangling rules shipped with the app | +| `config.h.in` / `version.h.in` | CMake-configured headers providing `CONFIG_PATH` and build version info | +| `cmake/modules/` | `FindVersionHeader.cmake` — extracts git SHA/date into `version.h`; `FindConfigPath.cmake` — resolves user config directory | + +### Key design pattern + +`execAndDoOnEveryLine` (defined in `qldd.h`) is a generic template that runs an arbitrary shell command via `popen` and calls an action callback for each output line, optionally async. All `ldd`/`otool`/`nm`/`file` invocations go through this helper. + +### File-manager integrations + +- `nautilus-python/extensions/` — Python extension for Nautilus (GNOME) +- `nemo/actions/` — Nemo action file +- `dolphin/` — KDE service menu entry + +These are installed as separate CPack components (`nautilus-integration`, `dolphin-integration`) and are not part of the C++ build. + +## Workflow constraints + +**Never commit or push without explicit user approval.** Always show the intended changes and ask before running `git commit` or `git push`. Each commit/push requires a separate confirmation — a prior approval does not carry over. + +## Code style + +`.clang-format` and `.clang-tidy` are present. Run clang-format before committing: +```bash +clang-format -i *.cpp *.h +``` + diff --git a/CMakeLists.txt b/CMakeLists.txt index 068f61a..9a64a9c 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,8 +2,8 @@ set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${CMAKE_SOURCE_DIR}/cmake/modules") cmake_minimum_required(VERSION 3.5) set(MAJOR "1") -set(MINOR "2") -set(PATCH "1") +set(MINOR "3") +set(PATCH "0") cmake_policy(SET CMP0048 NEW) project(Qldd @@ -20,17 +20,45 @@ configure_file(${CMAKE_SOURCE_DIR}/resources/qldd.desktop.in ${CMAKE_BINARY_DIR} function(get_linux_lsb_release_information) find_program(LSB_RELEASE_EXEC lsb_release) - if(NOT LSB_RELEASE_EXEC) - message(FATAL_ERROR "Could not detect lsb_release executable, can not gather required information") + if(LSB_RELEASE_EXEC) + execute_process(COMMAND "${LSB_RELEASE_EXEC}" --short --id OUTPUT_VARIABLE LSB_RELEASE_ID_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND "${LSB_RELEASE_EXEC}" --short --release OUTPUT_VARIABLE LSB_RELEASE_VERSION_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND "${LSB_RELEASE_EXEC}" --short --codename OUTPUT_VARIABLE LSB_RELEASE_CODENAME_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE) + set(LSB_RELEASE_ID_SHORT "${LSB_RELEASE_ID_SHORT}" PARENT_SCOPE) + set(LSB_RELEASE_VERSION_SHORT "${LSB_RELEASE_VERSION_SHORT}" PARENT_SCOPE) + set(LSB_RELEASE_CODENAME_SHORT "${LSB_RELEASE_CODENAME_SHORT}" PARENT_SCOPE) + elseif(EXISTS "/etc/os-release") + # Fallback: parse /etc/os-release (present on all systemd-based distros) + set(_id "unknown") + set(_version "unknown") + set(_codename "unknown") + file(STRINGS "/etc/os-release" _os_release) + foreach(_line ${_os_release}) + if(_line MATCHES "^ID=(.+)$") + set(_id "${CMAKE_MATCH_1}") + string(REPLACE "\"" "" _id "${_id}") + # Capitalize first letter to match lsb_release output (ubuntu -> Ubuntu) + string(SUBSTRING "${_id}" 0 1 _first) + string(TOUPPER "${_first}" _first) + string(SUBSTRING "${_id}" 1 -1 _rest) + set(_id "${_first}${_rest}") + elseif(_line MATCHES "^VERSION_ID=(.+)$") + set(_version "${CMAKE_MATCH_1}") + string(REPLACE "\"" "" _version "${_version}") + elseif(_line MATCHES "^VERSION_CODENAME=(.+)$") + set(_codename "${CMAKE_MATCH_1}") + string(REPLACE "\"" "" _codename "${_codename}") + endif() + endforeach() + set(LSB_RELEASE_ID_SHORT "${_id}" PARENT_SCOPE) + set(LSB_RELEASE_VERSION_SHORT "${_version}" PARENT_SCOPE) + set(LSB_RELEASE_CODENAME_SHORT "${_codename}" PARENT_SCOPE) + else() + message(WARNING "Neither lsb_release nor /etc/os-release found; distro info unavailable") + set(LSB_RELEASE_ID_SHORT "unknown" PARENT_SCOPE) + set(LSB_RELEASE_VERSION_SHORT "unknown" PARENT_SCOPE) + set(LSB_RELEASE_CODENAME_SHORT "unknown" PARENT_SCOPE) endif() - - execute_process(COMMAND "${LSB_RELEASE_EXEC}" --short --id OUTPUT_VARIABLE LSB_RELEASE_ID_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND "${LSB_RELEASE_EXEC}" --short --release OUTPUT_VARIABLE LSB_RELEASE_VERSION_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND "${LSB_RELEASE_EXEC}" --short --codename OUTPUT_VARIABLE LSB_RELEASE_CODENAME_SHORT OUTPUT_STRIP_TRAILING_WHITESPACE) - - set(LSB_RELEASE_ID_SHORT "${LSB_RELEASE_ID_SHORT}" PARENT_SCOPE) - set(LSB_RELEASE_VERSION_SHORT "${LSB_RELEASE_VERSION_SHORT}" PARENT_SCOPE) - set(LSB_RELEASE_CODENAME_SHORT "${LSB_RELEASE_CODENAME_SHORT}" PARENT_SCOPE) endfunction() message(STATUS "COMMITTER_FULLSHA ${COMMITTER_FULLSHA}") @@ -51,22 +79,22 @@ set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Threads REQUIRED) -set(QtVerPath qt5) +set(QtVerPath qt@5) if (FORCE_QT6) set(QtVer Qt6) - set(QtVerPath qt6) + set(QtVerPath qt@6) find_package(${QtVer} COMPONENTS Widgets REQUIRED) else() set(QtVer Qt5) find_package(${QtVer} COMPONENTS Widgets QUIET) if (NOT Qt5_FOUND) set(QtVer Qt6) - set(QtVerPath qt6) + set(QtVerPath qt@6) find_package(${QtVer} COMPONENTS Widgets REQUIRED) endif () endif() @@ -163,9 +191,18 @@ if(APPLE) OUTPUT_VARIABLE brew_path) string(STRIP ${brew_path} brew_path) + link_directories("${brew_path}/lib") + add_custom_command(TARGET Qldd + POST_BUILD + COMMAND ln -sf "${brew_path}/lib" "lib") add_custom_command(TARGET Qldd POST_BUILD COMMAND "${brew_path}/opt/${QtVerPath}/bin/macdeployqt" Qldd.app) + # Re-sign the entire bundle after macdeployqt copies unsigned Homebrew dylibs into it. + # Without this macOS 15+ kills the process with CODESIGNING / Invalid Page on launch. + add_custom_command(TARGET Qldd + POST_BUILD + COMMAND codesign --force --deep --sign - Qldd.app) else() # Components: @@ -186,9 +223,6 @@ else() install(FILES ${CMAKE_SOURCE_DIR}/nautilus-python/extensions/dependency-viewer.py DESTINATION share/nautilus-python/extensions COMPONENT nautilus-integration) - install(FILES ${CMAKE_SOURCE_DIR}/nautilus-python/extensions/dependency-viewer-43.py - DESTINATION share/nautilus-python/extensions - COMPONENT nautilus-integration) install(FILES ${CMAKE_SOURCE_DIR}/dolphin/kde-qldd-menu.desktop DESTINATION share/kservices5/ServiceMenus COMPONENT dolphin-integration) diff --git a/demanglerules.cpp b/demanglerules.cpp index e542fd3..e11b229 100644 --- a/demanglerules.cpp +++ b/demanglerules.cpp @@ -11,9 +11,9 @@ static T enum_cast(demanglerules::Fields field) { return static_cast(field); }; -demanglerules::demanglerules(QWidget *parent) : QDialog(parent), ui(new Ui::demanglerules) { +demanglerules::demanglerules(QWidget *parent) : QDialog(parent), ui(new Ui::demanglerules), _mainWindow(dynamic_cast(parent)) { ui->setupUi(this); - auto *m = dynamic_cast(parent); + auto *m = _mainWindow; ui->tableWidget->setColumnCount(enum_cast(Fields::COUNT)); const auto &rulesRef = m->demangleRules(); QStringList tableHeader; @@ -39,7 +39,7 @@ demanglerules::~demanglerules() { delete ui; } void demanglerules::insertNewRow(int row, const QString &src, const QString &dst) { ui->tableWidget->insertRow(row); - auto *m = dynamic_cast(parent()); + auto *m = _mainWindow; auto *checkBoxWidget = new QWidget(); auto *checkBox = new QCheckBox(); @@ -96,7 +96,7 @@ void demanglerules::on_pBRemoveRule_clicked() { } void demanglerules::on_buttonBox_accepted() { - auto *m = dynamic_cast(parent()); + auto *m = _mainWindow; RulesMap rules; for (int i = 0; i < ui->tableWidget->rowCount(); ++i) { auto *itemSrc = ui->tableWidget->item(i, enum_cast(Fields::Source)); diff --git a/demanglerules.h b/demanglerules.h index bcef12e..611d1ac 100644 --- a/demanglerules.h +++ b/demanglerules.h @@ -3,6 +3,8 @@ #include +class MainWindow; + namespace Ui { class demanglerules; } @@ -17,6 +19,7 @@ class demanglerules : public QDialog { private: Ui::demanglerules *ui; + MainWindow *_mainWindow; void insertNewRow(int row, const QString &src, const QString &dst); private slots: void selectRow(bool flag); diff --git a/main.cpp b/main.cpp index e7bc3c8..7ad0a35 100755 --- a/main.cpp +++ b/main.cpp @@ -4,7 +4,9 @@ #include int main(int argc, char *argv[]) { +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#endif QApplication app(argc, argv); QApplication::setApplicationName("Qldd"); diff --git a/mainwindow.cpp b/mainwindow.cpp index 53b9610..38b604d 100755 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -239,7 +239,7 @@ void MainWindow::initDemangleRules() { QJsonDocument doc = QJsonDocument::fromJson(val.toUtf8()); QJsonObject obj = doc.object(); QJsonArray arrRules = obj["rules"].toArray(); - for (const auto &item : qAsConst(arrRules)) { + for (const auto &item : arrRules) { auto itemObj = item.toObject(); auto itemKeys = itemObj.keys(); if (itemKeys.size() == 1) { @@ -261,13 +261,12 @@ void MainWindow::showContextMenu(const QPoint &pos) { } void MainWindow::copyExportItem() { - QClipboard *clipboard = qApp->clipboard(); - // If multiple selection is on, we need to erase all selected items - for (int i = 0; i < ui->listWidgetExportTable->selectedItems().size(); ++i) { - // Get curent item on selected row - QListWidgetItem *item = ui->listWidgetExportTable->item(ui->listWidgetExportTable->currentRow()); - // And copy text from it - clipboard->setText(item->text()); + QStringList texts; + for (const auto *item : ui->listWidgetExportTable->selectedItems()) { + texts << item->text(); + } + if (!texts.isEmpty()) { + qApp->clipboard()->setText(texts.join('\n')); } } void MainWindow::on_checkBoxOwnerRead_clicked(bool checked) { ui->checkBoxOwnerRead->setChecked(!checked); } diff --git a/nautilus-python/extensions/dependency-viewer-43.py b/nautilus-python/extensions/dependency-viewer-43.py deleted file mode 100755 index 61b5736..0000000 --- a/nautilus-python/extensions/dependency-viewer-43.py +++ /dev/null @@ -1,27 +0,0 @@ -import os, os.path -import urllib.parse - -from gi.repository import Nautilus, GObject - -QLDD_KEY = '/usr/bin/Qldd' - -class OpenQlddExtension(GObject.GObject, Nautilus.MenuProvider): - def __init__(self): - pass - - def _open_qldd(self, file): - filename = urllib.parse.unquote(file.get_uri()[7:]) - qldd = QLDD_KEY - os.system('%s %s &' % (qldd, filename)) - - def menu_activate_cb(self, menu, files): - for file in files: - self._open_qldd(file) - - def get_file_items(self, files): - item = Nautilus.MenuItem( - name='Qldd', - label='View dependencies', - icon='/usr/share/icons/qldd/Qldd.png') - item.connect('activate', self.menu_activate_cb, files) - return item, diff --git a/nautilus-python/extensions/dependency-viewer.py b/nautilus-python/extensions/dependency-viewer.py index 0da3a53..fb440fa 100755 --- a/nautilus-python/extensions/dependency-viewer.py +++ b/nautilus-python/extensions/dependency-viewer.py @@ -1,24 +1,29 @@ -import os, os.path +import os import urllib.parse from gi.repository import Nautilus, GObject QLDD_KEY = '/usr/bin/Qldd' + class OpenQlddExtension(GObject.GObject, Nautilus.MenuProvider): def __init__(self): pass def _open_qldd(self, file): filename = urllib.parse.unquote(file.get_uri()[7:]) - qldd = QLDD_KEY - os.system('%s %s &' % (qldd, filename)) + os.system('%s %s &' % (QLDD_KEY, filename)) def menu_activate_cb(self, menu, files): for file in files: self._open_qldd(file) - def get_file_items(self, window, files): + # Nautilus 43+ removed the `window` parameter from get_file_items. + # Using *args makes this extension work on both old and new API: + # Nautilus <43 calls get_file_items(window, files) -> args = (window, files) + # Nautilus 43+ calls get_file_items(files) -> args = (files,) + def get_file_items(self, *args): + files = args[-1] item = Nautilus.MenuItem( name='Qldd', label='View dependencies', diff --git a/qldd.cpp b/qldd.cpp index 79524d6..6cd4478 100644 --- a/qldd.cpp +++ b/qldd.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include #include @@ -27,11 +29,11 @@ #endif QLdd::QLdd(QString fileName, QString lddDirPath, RulesMap demangleRules) - : _fileName(std::move(fileName)), + : _fileName(QFileInfo(fileName).absoluteFilePath()), _fileInfo(_fileName), _link(false), - _lddDirPath(std::move(lddDirPath)), _demangleRules(std::move(demangleRules)) { + Q_UNUSED(lddDirPath) _ownerMod.read = _fileInfo.permission(QFile::ReadOwner); _ownerMod.write = _fileInfo.permission(QFile::WriteOwner); _ownerMod.execute = _fileInfo.permission(QFile::ExeOwner); @@ -79,9 +81,9 @@ void QLdd::fillDependency(QTreeWidget &treeWidget) { std::stringstream ss; - QDir::setCurrent(getPathOfBinary()); ss << CMD_LDD << " \"" << _fileName.toStdString() << "\""; + treeWidget.setUpdatesEnabled(false); execAndDoOnEveryLine(ss.str(), [this, &treeWidget](const QString &line) { QTreeWidgetItem *item = nullptr; QStringList sl; @@ -92,7 +94,7 @@ void QLdd::fillDependency(QTreeWidget &treeWidget) { sl = line.split(DEPEND_SPLITTER); } int i = 0; - for (const QString &v : qAsConst(sl)) { + for (const QString &v : sl) { if (v.contains("(0x") || v.contains("(compatibility")) { QStringList slTmp = v.split("("); if (slTmp.size() > 1) { @@ -121,11 +123,10 @@ void QLdd::fillDependency(QTreeWidget &treeWidget) { treeWidget.addTopLevelItem(item); sl.removeFirst(); QTreeWidgetItem *tmp = item; - QColor redC("red"); - for (const QString &v : qAsConst(sl)) { + for (const QString &v : sl) { if (!v.trimmed().isEmpty()) { if (v.contains("not found")) { - tmp->setForeground(0, QBrush(redC)); + tmp->setForeground(0, QBrush(Qt::red)); tmp->setText(0, tmp->text(0) + " " + v); tmp->setToolTip(0, tmp->text(0)); } else { @@ -138,8 +139,7 @@ void QLdd::fillDependency(QTreeWidget &treeWidget) { } } }); - - QDir::setCurrent(_lddDirPath); + treeWidget.setUpdatesEnabled(true); } void QLdd::fillExportTable(QListWidget &listWidget, const QString &filter) { @@ -147,33 +147,44 @@ void QLdd::fillExportTable(QListWidget &listWidget, const QString &filter) { std::mutex mutex; std::stringstream ss; ss << NM << " \"" << _fileName.toStdString() << "\" | grep \\ T\\ "; + + // Collect demangled results from worker threads; never touch the widget from them. + std::vector> results; + execAndDoOnEveryLine( ss.str(), - [&mutex, &listWidget, &filter, this](const QString &line) { - int status = 0; + [&mutex, &results, &filter, this](const QString &line) { QStringList info = line.split(" "); + if (info.size() < 3) return; + + int status = 0; QString demangled(info.at(2)); - char *realname = abi::__cxa_demangle(info.at(2).toStdString().c_str(), nullptr, nullptr, &status); + char *realname = abi::__cxa_demangle(info.at(2).toUtf8().constData(), nullptr, nullptr, &status); if (realname) { demangled = QString::fromLocal8Bit(realname); ::free(realname); - for (auto &_demangleRule : _demangleRules) { - demangled.replace(_demangleRule.first, _demangleRule.second); - if (demangled.contains("string")) { - qDebug() << "from->" << _demangleRule.first << " to->" << _demangleRule.second; - } + for (const auto &rule : _demangleRules) { + demangled.replace(rule.first, rule.second); } } - std::unique_ptr item(new QListWidgetItem(info.at(0) + " " + demangled)); - item->setToolTip(demangled); - std::unique_lock lock(mutex); - if (!filter.isEmpty() && demangled.contains(filter, Qt::CaseInsensitive)) { - listWidget.addItem(item.release()); - } else if (filter.isEmpty()) { - listWidget.addItem(item.release()); + + if (!filter.isEmpty() && !demangled.contains(filter, Qt::CaseInsensitive)) { + return; } + + std::unique_lock lock(mutex); + results.emplace_back(info.at(0), std::move(demangled)); }, Exec::ASYNC); + + // All async work is done — populate the widget on the main thread. + listWidget.setUpdatesEnabled(false); + for (const auto &r : results) { + auto *item = new QListWidgetItem(r.first + " " + r.second); + item->setToolTip(r.second); + listWidget.addItem(item); + } + listWidget.setUpdatesEnabled(true); } QString QLdd::getPathOfBinary() { return _fileInfo.absolutePath(); } @@ -188,12 +199,11 @@ QString QLdd::getInfo() { std::stringstream ss; ss << "file \"" << _fileName.toStdString() << "\""; QString buf; - execAndDoOnEveryLine(ss.str(), [&buf](const QString &line) { buf.append(line + "\n"); }); - QStringList slTmp = buf.split(INFO_SPLITTER); - buf.clear(); - for (const QString &v : qAsConst(slTmp)) { - buf.append(v.trimmed()).append("\n"); - } + execAndDoOnEveryLine(ss.str(), [&buf](const QString &line) { + for (const QString &part : line.split(INFO_SPLITTER)) { + buf.append(part.trimmed()).append("\n"); + } + }); return buf; } const QMOD &QLdd::getOwnerMod() const { return _ownerMod; } diff --git a/qldd.h b/qldd.h index c5bcc1b..8437c9e 100755 --- a/qldd.h +++ b/qldd.h @@ -65,7 +65,6 @@ class QLdd { QString _tmCreate; QString _tmAccess; QString _tmModify; - QString _lddDirPath; QString _fileSize; QMOD _ownerMod{}; QMOD _groupMod{}; @@ -84,7 +83,7 @@ void execAndDoOnEveryLine(const std::string &execString, const Action &action, E QTextStream nmOutStream(cmdStream.get()); QString line; - using returnType = typename std::result_of::type; + using returnType = std::invoke_result_t; std::list> retList; do { line = nmOutStream.readLine();