From 1e9593a18ae73d57b4bb2342501b32800e104058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E7=AC=9B?= <1753894329@qq.com> Date: Wed, 20 Aug 2025 15:24:33 +0800 Subject: [PATCH 1/7] Revert "Revert "Initialize submission, add the start page."" This reverts commit b78bdadc8e7efcf511b86c36bc04273ef5a2b218. --- .clang-format | 84 ++++++ CMakeLists.txt | 115 +++++++ DEPS | 33 ++ codeformat.sh | 41 +++ icons/InspectorIcon.png | Bin 0 -> 5142 bytes icons/LayerTreeIcon.png | Bin 0 -> 3619 bytes icons/arrow_icon.png | Bin 0 -> 360 bytes icons/capture.png | Bin 0 -> 1311 bytes icons/chooseOpenFile.png | Bin 0 -> 3005 bytes icons/close.png | Bin 0 -> 387 bytes icons/disconnected.png | Bin 0 -> 1627 bytes icons/openfile.png | Bin 0 -> 750 bytes icons/savefile.png | Bin 0 -> 1067 bytes install_tools.sh | 18 ++ qml/StartView.qml | 638 +++++++++++++++++++++++++++++++++++++++ res.qrc | 13 + src/ResolvService.cpp | 74 +++++ src/ResolvService.h | 53 ++++ src/StartView.cpp | 296 ++++++++++++++++++ src/StartView.h | 177 +++++++++++ src/main.cpp | 67 ++++ sync_deps.sh | 13 + vendor.json | 32 ++ 23 files changed, 1654 insertions(+) create mode 100644 .clang-format create mode 100644 CMakeLists.txt create mode 100644 DEPS create mode 100755 codeformat.sh create mode 100644 icons/InspectorIcon.png create mode 100644 icons/LayerTreeIcon.png create mode 100644 icons/arrow_icon.png create mode 100644 icons/capture.png create mode 100644 icons/chooseOpenFile.png create mode 100644 icons/close.png create mode 100644 icons/disconnected.png create mode 100644 icons/openfile.png create mode 100644 icons/savefile.png create mode 100755 install_tools.sh create mode 100644 qml/StartView.qml create mode 100644 res.qrc create mode 100644 src/ResolvService.cpp create mode 100644 src/ResolvService.h create mode 100644 src/StartView.cpp create mode 100644 src/StartView.h create mode 100644 src/main.cpp create mode 100755 sync_deps.sh create mode 100644 vendor.json diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..80d3506 --- /dev/null +++ b/.clang-format @@ -0,0 +1,84 @@ +# Generated from CLion C/C++ Code Style settings +Language: Cpp +BasedOnStyle: Google +ColumnLimit: 100 +SortIncludes: true +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignOperands: Align +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Always +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterReturnType: None +AlwaysBreakTemplateDeclarations: Yes +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: true +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +CompactNamespaces: false +ContinuationIndentWidth: 4 +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 2 +KeepEmptyLinesAtTheStartOfBlocks: true +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +DerivePointerAlignment: false +PointerAlignment: Left +ReflowComments: false +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +IncludeBlocks: Merge +TabWidth: 2 +UseTab: Never + +--- +Language: ObjC +BasedOnStyle: Google +ColumnLimit: 100 + +# Only sort headers in each include block +SortIncludes: true +IncludeBlocks: Preserve +DerivePointerAlignment: false +PointerAlignment: Left +AllowShortFunctionsOnASingleLine: None +BraceWrapping: + SplitEmptyFunction: true \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6a9f27a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,115 @@ +cmake_minimum_required(VERSION 3.13) +project(inspectorTool) + +include(./third_party/vendor_tools/vendor.cmake) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +cmake_policy(SET CMP0063 NEW) +set(CMAKE_CXX_VISIBILITY_PRESET hidden) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(TGFX_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tgfx) +set(INSPECTOR_COMMON_DIR ${TGFX_DIR}/src/debug) + +if (NOT CMAKE_PREFIX_PATH) + if (NOT EXISTS ${PROJECT_SOURCE_DIR}/QTCMAKE.cfg) + file(WRITE ${PROJECT_SOURCE_DIR}/QTCMAKE.cfg + "set(CMAKE_PREFIX_PATH /Users/[username]/Qt/6.6.1/macos/lib/cmake) #put your own QT path here") + endif () + include("QTCMAKE.cfg") +endif () + +string(REGEX MATCH "([0-9]+)\\.[0-9]+\\.[0-9]+" QT_VERSION ${CMAKE_PREFIX_PATH}) +if (QT_VERSION) + string(REGEX MATCH "^[0-9]+" QT_VERSION_MAJOR ${QT_VERSION}) + if (QT_VERSION_MAJOR GREATER_EQUAL 6 AND CMAKE_SIZEOF_VOID_P EQUAL 4) + message(FATAL_ERROR "QT has dropped support for 32-bit builds since version 6.0.0") + endif () +endif () + +set(TGFX_USE_QT ON) +set(TGFX_BUILD_INSPECTOR OFF) +set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) +add_subdirectory(${TGFX_DIR} tgfx EXCLUDE_FROM_ALL) +list(APPEND PAG_STATIC_LIBS $) +list(APPEND PAG_DEPEND_TARGETS tgfx) + +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) +if (${QT_VERSION_MAJOR} LESS 6) + message("The QT version is less than 6.0, force to use x86_64 architecture.") + SET(CMAKE_SYSTEM_PROCESSOR x86_64) + SET(CMAKE_OSX_ARCHITECTURES x86_64) +endif () + +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Widgets OpenGL qml Quick WebSockets Network QuickControls2 Core5Compat) +list(APPEND TGFX_INSPECTOR_LIBS Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Widgets + Qt${QT_VERSION_MAJOR}::WebSockets Qt${QT_VERSION_MAJOR}::OpenGL Qt${QT_VERSION_MAJOR}::Qml Qt${QT_VERSION_MAJOR}::Quick + Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::QuickControls2 Qt${QT_VERSION_MAJOR}::Core5Compat) +if (${QT_VERSION} VERSION_LESS "5.15") + function(qt_add_resources outfiles) + qt5_add_resources("${outfiles}" ${ARGN}) + if (TARGET ${outfiles}) + cmake_parse_arguments(PARSE_ARGV 1 arg "" "OUTPUT_TARGETS" "") + if (arg_OUTPUT_TARGETS) + set(${arg_OUTPUT_TARGETS} ${${arg_OUTPUT_TARGETS}} PARENT_SCOPE) + endif () + else () + set("${outfiles}" "${${outfiles}}" PARENT_SCOPE) + endif () + endfunction() +endif () + +qt_add_resources(QT_RESOURCES res.qrc) + +if (APPLE) + find_library(APPLICATION_SERVICES_FRAMEWORK ApplicationServices REQUIRED) + list(APPEND TGFX_INSPECTOR_LIBS ${APPLICATION_SERVICES_FRAMEWORK}) + find_library(QUARTZ_CORE QuartzCore REQUIRED) + list(APPEND TGFX_INSPECTOR_LIBS ${QUARTZ_CORE}) + find_library(COCOA Cocoa REQUIRED) + list(APPEND TGFX_INSPECTOR_LIBS ${COCOA}) + find_library(FOUNDATION Foundation REQUIRED) + list(APPEND TGFX_INSPECTOR_LIBS ${FOUNDATION}) + find_library(ICONV_LIBRARIES NAMES iconv libiconv libiconv-2 c) + list(APPEND TGFX_INSPECTOR_LIBS ${ICONV_LIBRARIES}) + find_library(VIDEOTOOLBOX VideoToolbox) + list(APPEND TGFX_INSPECTOR_LIBS ${VIDEOTOOLBOX}) + find_library(CORE_MEDIA CoreMedia) + list(APPEND TGFX_INSPECTOR_LIBS ${CORE_MEDIA}) + find_library(COMPRESSION_LIBRARIES NAMES compression) + list(APPEND TGFX_INSPECTOR_LIBS ${COMPRESSION_LIBRARIES}) +elseif (WIN32) + set(BUILD_USE_64BITS ON) + add_definitions(-DNOMINMAX -D_USE_MATH_DEFINES) + find_library(Bcrypt_LIB Bcrypt) + list(APPEND TGFX_INSPECTOR_LIBS ${Bcrypt_LIB}) + find_library(ws2_32_LIB ws2_32) + list(APPEND TGFX_INSPECTOR_LIBS ${ws2_32_LIB}) +endif () + +list(APPEND TGFX_INSPECTOR_SRC src src/tags src/socket/*.* src/layerInspector/*.*) +file(GLOB TGFX_INSPECTOR_FILES src/*.* src/tags/*.* src/socket/*.* src/layerInspector/*.*) +set(LZ4_DIR ${TGFX_DIR}/third_party/lz4/lib) + +list(APPEND LZ4_FILE ${LZ4_DIR}/lz4.c) +list(APPEND TGFX_INSPECTOR_COMMON_INCLUDE ${LZ4_DIR} ${INSPECTOR_COMMON_DIR}) + +file(GLOB TGFX_INSPECTOR_COMMON_FILES + ${LZ4_FILE} + ${INSPECTOR_COMMON_DIR}/Socket.cpp) + +list(APPEND INSPECTOR_STATIC_VENDORS KDDockWidgets) +add_vendor_target(inspector-vendor STATIC_VENDORS ${INSPECTOR_STATIC_VENDORS}) +find_vendor_libraries(inspector-vendor STATIC INSPECTOR_VENDOR_STATIC_LIBRARIES) +list(APPEND TGFX_INSPECTOR_LIBS ${INSPECTOR_VENDOR_STATIC_LIBRARIES}) +list(APPEND TGFX_INSPECTOR_DEFINE KDDW_FRONTEND_QTQUICK KDDW_FRONTEND_QT KDDW_QTGUI_TYPES) + +list(APPEND TGFX_INSPECTOR_INCLUDE ./third_party/KDDockWidgets/src/fwd_headers) + +add_executable(inspectorTool ${RC_FILES} ${TGFX_INSPECTOR_FILES} ${TGFX_INSPECTOR_COMMON_FILES} ${QT_RESOURCES}) +target_include_directories(inspectorTool PUBLIC ${TGFX_INSPECTOR_SRC} ${TGFX_INSPECTOR_COMMON_INCLUDE}) +target_include_directories(inspectorTool SYSTEM PRIVATE ${TGFX_INSPECTOR_INCLUDE}) +target_compile_definitions(inspectorTool PUBLIC ${TGFX_INSPECTOR_DEFINE}) +target_link_libraries(inspectorTool tgfx ${TGFX_INSPECTOR_LIBS}) \ No newline at end of file diff --git a/DEPS b/DEPS new file mode 100644 index 0000000..445956a --- /dev/null +++ b/DEPS @@ -0,0 +1,33 @@ +{ + "version": "1.3.12", + "vars": { + "PAG_GROUP": "https://github.com/libpag" + }, + "repos": { + "common": [ + { + "url": "${PAG_GROUP}/vendor_tools.git", + "commit": "76b266c30e3314678a0d8daeb5b40a9389ea5544", + "dir": "third_party/vendor_tools" + }, + { + "url": "https://github.com/KDAB/KDDockWidgets.git", + "commit": "bdf4e7bd4ee49c961cac8a0caff44c25ca2a06b4", + "dir": "third_party/KDDockWidgets" + }, + { + "url": "https://github.com/Tencent/tgfx.git", + "commit": "ff01c45b1b00b5c732e388358cb3065188b23763", + "dir": "tgfx" + } + ] + }, + "actions": { + "common": [ + { + "command": "depsync --clean", + "dir": "third_party" + } + ] + } +} diff --git a/codeformat.sh b/codeformat.sh new file mode 100755 index 0000000..266dfc0 --- /dev/null +++ b/codeformat.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +cd $(dirname $0) + +if [[ $(uname) == 'Darwin' ]]; then + MAC_REQUIRED_TOOLS="python3" + for TOOL in ${MAC_REQUIRED_TOOLS[@]}; do + if [ ! $(which $TOOL) ]; then + if [ ! $(which brew) ]; then + echo "Homebrew not found. Trying to install..." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || + exit 1 + fi + echo "$TOOL not found. Trying to install..." + brew install $TOOL || exit 1 + fi + done + clangformat=`clang-format --version` + if [[ $clangformat =~ "14." ]] + then + echo "----$clangformat----" + else + echo "----install clang-format----" + pip3 install clang-format==14 + fi +fi + +echo "----begin to scan code format----" +find src -name "*.cpp" -print -o -name "*.c" -print -o -name "*.h" -print -o -name "*.mm" -print -o -name "*.m" -print | xargs clang-format -i + +git diff +result=`git diff` +if [[ $result =~ "diff" ]] +then + echo "----Failed to pass the code format check----" + exit 1 +else + echo "----Complete the code format check-----" +fi + + + diff --git a/icons/InspectorIcon.png b/icons/InspectorIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..b429eedcf59b2fe174088fe1f600647433557cdc GIT binary patch literal 5142 zcmcIo`9IX%-#(wu%xDa0vJ?`NCHs;rDa+Uyp=`GhV~IhVCAT6z<4Ym3?^!~Y5Jd}F z=8h=4vLt0`kfrQP)_LasK7YdVdVV_Fbm69q_DMU={$bUSmCNt3b!!+4cb=XYhKmzjZcT63b;iw*Qbh zxhz-6cg8@cBYCMjBQPXUhmKV@aG_Y+-CJ>&u; zpKrYT-g9BeeL?qvY8@JjSFsU->sNDi0rtH(pJvLUr~B)x8~ke6aQ>9wniGKN^R;_v z^q^Cs1ZrY-cGkXBHg3cM0qFv9s`Sv<>)ZA(4znb2;eyp*au}^FD@P$cUXPSU0B0kL zXGg+auk;qBS$V^G7d`o#E>@LIZ~2I9vbFj@x}^eLD4ncyYHB>raev4#U{}?UYQ=*` z`_=qvz(F?+)w(wtJO=^&X(@^F^72|Xcf(bs zOARV@@i9`ip_lPb_F8R(~_Dk*lbYg z6d6{Kx|8mf_(n=w;jgJDfOaYL`U>T;Y{Pl~v)PT5?^>Vkra{1C&&YGfL#CXi)wK={ z{!nhHUo3Z|;&~sOr+~slhHvdIZ_|?`{Nu@{N{)3wm!<*AGeciydv+)g(ARZo%Ah-1U)z4Y27nr>CiASUf<((u@3fc+qv50~}#a4)HPSQc|K3t&`hF`Thb`M`4Z^FQIwfbA`jbM`j+8kcN38G|%u20hJ7<&VJ z9>y=Te?b&6jK|$AF^=>KP?JclZhW{UE5l|Jju->V-qX10A=S_Y>L9v` zAAKGk-&`C!`q9emqkL(=UW3xfuz{S7bQH2y9n!tMey`t4GMh_Uo5l?aJ2W$$IzIj0 zymGz9+s5x6A5fqvPuRVU^?z7iB}8TciSr@dyCK@btW=&<_#_|*ScOP;S2CP}skXN&FRNmKSg7nl;L4|-vtcPU#E44Pc?@nq>_N4 zmh3=YARZ^swK(+crAAu&lSu1Aejw@|n%Oj{vp-_2J-)nrvFmiCL@B$0u&t7o@a3yc zmOoyKH!V01pe^HLuooZ2zW_q)qG>|2Jk z(5EDBpTq&BjDm_D%6tf>H_0@aWXTR@klx)r_ozE3?`RQpx}yNNoT&fU$4bO2mM!~l zPlr(HC zI#~-xFelVKLU&rLKj}_d&{p-(7~s26yj5Y_VXhT#j;XhSE&zf@XwSTyR%+CMr41xR zXGH-IhV68eBEJo^D8d|hZFtNIy^p>_Cr%Z$H2^F)vokNIy2yNO8M&hyumglthpN>M z=bJ(O7BqVvrdU615e9RKSrzTLyr;+%gm9|73mTfLabq*aT;NJ6zCv5W0SxHgPaL$R zJ36qatDiprqMrsYG~JUFGmN?;Py4J$mSSFGQY62qtP7EO#($XinqmOyeE0roi|nB< zmgIFQCIUaDHMFlJXGcvDf945@DU6%|tG=lAc~@>=15KwvpFx$P{wdFoWh5!2#8Kla zix~>&5K|e6M{IW-BU!KVfjpAL_5F%#q>p`-na!3`pk;gP>#tl+x2*W$&$E{lwQtMU z!;w;h+k%7KfY2_^1_Dp-4RuVdbnmM1$gUl}@!-}1^1*e!1Ca#8c+0oDH%h&*VZ8A~ z8Pq!zIAKQ;Hw886xdTM@)t?vj9(6A0Q{KQcr%WWTD0%cJEK@e#7S3)2I)1%6zOL3~ z^>cOW=W<}MTyx zpbZu1oU5ppqxK4b8j_e^?@JYy1lLOtjNL1rQ$Yj0(n1X4X45j=a|GtB2Ew;1WImIs zhdb%je%gM1$6m4Wnu?-(0|Z&Mop1@f|JfLAJzp?ZXH)a7QMvmuj{M5#>y^ZoUl}m< zDY~$;vi9`}=Y9V};i;Jezx@HBGyfnmeD3GpCY8<3USf#R7yjx?k(W%h>_-S|q5yZ1 z*cDUpkD>baFZ0di%cS+q)Q5*pAie%J_4ccLFAgC151c0s({+i&r@2oGAFx1r5hj59 z#RpDMn2Hj4+icS*9o)4nkpdth*tI6UyDFQ4=!JT93f^Yo z2w+r_;9SR#HCl6R*-c1A^$GedE@-c_1O*5WiiX~B^2k;V>Qq(1d_H=+@z-(PPEvKv z@Y{!*QvkXte$HJ>d>w!;4d}*n+yz!VK+~SVoGG+`jWw^3MLYpuku02%4msdugv^e} zHFi!W;BuuAksup;ea|`Zl7U&AlNf=1?Y}&t1(Oisb1u}uS2v0e!;2B3%-vGnw`Teh!B`067bbI@BgAo*RQ>1)k#X-8G0pK@%~B9 z+2rku=LD_J!3dyq%g*CVl)um6i7~lNEHYm{24Ujwd+@+7r+etcbh13gZfD^9)I%=3 zt2j6UA@4P)j!7qC#vwS;hl`otw%jGID+;g0d%gElq#->Xk5f%=wQzN~6NZ5Nm@|F- z7@J=GX3a|%PBf5_^D%|U{zd%C(sRLjiUVu6Qn$o5Ropjg4T>97BGV&a@I$RMWqk8w zSHmiv)p|}`ctW(**;zP!PcD4m+rl$u(20ARf@uoojnXfYQ+W@-j*d?|7$H7dj_D4P4cjtWt zh6IUM1HB&p%xSxy3L??HCmC40eYZZPIKeH_NA$R|iF-@Bl8a?hoG!49N>+Ax^(rHS zlF6##Cvd5mLVtetc?JqOxn+M)yL5oE6mi0Z6WGK1{Rij%4s1MDEU(Lqkj>wSsm>#v%`Ym(XiYPmgHxj8wF12ZMV zMMvSU148+`LxNntPLb;rei+lDcfjkglgCu@dNU93 z66w`ZOm3kszkrqTRTj$wYcJJ*Y|56${I+Q-26?Z)y|vF2;80~4IC|s1I66_SK0fOC z+0#Z}wwyDyYZr)X<90ora!@!7(})}?^oB; zDTWoe$%pUb_l{vbwPPQ;nnylnj;c?#!rkt)50FA&q}V|pfEVmbdIR0Tlb-Kz z1Qka8)GSkp(jdn{>vy+1M{Y^bh8b}bJjhFh90e=K;+W_UAASp%yKx9$%tTK;= z>MuvYCBtL(TZd4@I)$gWB?(`BvlmhEu+eJJqelc2L>17U0y#|hH_j!MN9m&ncvyfx z5AEPT$O8IMR5inp+6Txyz|70B(Gluz&HZ^rk{gu6b4f|E&v7?5#)gh^fWoDmj}J`$ zcD!2#<~jzp1XG)~?c333fW?9MCr4G$CMTwSCR=mcvKiM5n67~jQQ}?H&eQk7RlyCF z6KaBY}fXU28twr=u@CWdv;3f&OMdT8j>XnRJ}&|h3N~|gnQ}ji$o8nN`ok-!I4|nD@Dd zE}PP>sTd`D_YHcxT=xwS$!yXhd+{Ls_XsC?a|HyulWolmWcI8#!K)~-=hsEh>bA8F zaTe2YyQ!sKm?awwEf+W0AN}T@_iNLb(}kTv8Xo;n{HKdj>E&pbbSIatCepKXfJ}za zq^PmZ>v09~iVUf^1X+l9Rgz{HzP}Wo!@tMqSgcIz5ZgPt-?M6{Y1eWJ1!nhvPM(;l z{|HG#(b1>!1Jkx{q7>2ljEnu7pJ&&~v!?5Ja^O$X=1&-;EiwoAv;#?7jInu%s@|_u zj%wV=ZS&>?;mFYkF58j~qfN@uJOnZy$j=HJ5slpaaiI0Z>NBa!D`|HGkHhXz-gC(} zruSy3@?wpxK3$27(Lj-(VV(dF^Q$B`6{jSI>SNOnAgxJMYt`_{inu-LPt|Q)&pB^G zfDLbSH!>Ymn_2{Elud<{GWvXJ2Z4!yt)GljdkN#QYY(kPS-g zL9n{uKYtFZMtz59oM>#AA~v5-iy!R()A-Sf*iWJV3QEeg!%g~bbi**j9@vHh?WMXh z`Ry_dHi`9m@o|1&b0R+Jlb=fsuebhZr+K>a-~tp6JO5tqo1ZjlQaHy{B@x*}FJ{M{ z3MP!rye1kKR2X8z0r4I(c|hb2SLbPV%C>KRgEoO3_)-cr`K-+%>W)Y{TbMjpJR;hh z`@a&C)SnhOXI1^ZoeTXAqHQ-4C2#pf#3S)vNewsGRNfZbhpamcim(3rH$6q!Uxlgh zV2Oj;(9%Wz(56UOVJuQzT<88y>#VNxC|LYmJt@CsJIu8Ls8!$t>yPzpzpmkqk%r9} zcd>=R6L>*zxD2|%qQ9}3^ADE#V#NdyKGHk}rrehL^Ec?(;k3OHITVm%RUh67akB65 z(iwl1(*Y6WS^rQvgz#P-dkqPP;hkkiPEgJgoDtgcXC#3W7NR#Q0W7>jpvZ2ThzO%c zrS-towW>scC!&rMf66cAc0ku*m8}RoCDHqiYvFROz&K~#S!dQTuhJr=WJS#rG%=#( zyOa1rE}43#(S9HCSmhXV?5-se1X2t>HpPfwr7+S;pH(zZEEz&@o=9%3`V9WJInVmG zGxr0nyRSGu51VU{5`+N>_gyU~@t!<)3Z_PBWNiL-G}DE&Z`luoDZKgWpr}5=1%E@8 zDBikJVC@|5v0a$efB*a)S3T^_Lm=Ut&@;Z{ERO9vgXGE={}NO_&GPl9IBVtGN)}D~ zfwRe~)mpaSKX@NEFWtj2k0o6KnmJ>qtKMXktl!nCnlw#|<6x4LJnqJSLps9WkXv9S zCTAhRV_jXR$NSIl#b|bBO(x(K_VSIz|K!fP=5B2a2_5_%@L>A-Aqy)k9{jGQojzhs z5JqDW{iKZM1xp$Z&2hgujW0z2>PtRVfgQMt@-E`wYWYT?ssC+mR z&wGgl52B5m?b|GfY_Lcc6-_J`n4~20^dbPt6Yw9+!CmEvzXu@NZUNlN9Ul%hK@2AM z`(xM`l4MfuRFxuIL9Ey%4N&lp$LkVM$H|+qXF2wV8&M-+(A-387>y8@0@fhrW?(o6hj5ovu()?`qnReRUWRAJn!F?VXN~Vq@bNVv=Lya{RTuR* z9QpzRuy{QVDsD}R2-c+9R!fKggGGS$B))`L38RUXzW=YVB_Y2n$v=#R^o5y)K47eG KruR&T9QHpL_5vpW literal 0 HcmV?d00001 diff --git a/icons/LayerTreeIcon.png b/icons/LayerTreeIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..fe67523bd3d7223c781a53d0e1cbbc80c8e3be30 GIT binary patch literal 3619 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 S%xdBPIACpIZ(eKW8UH^j*<(Tg literal 0 HcmV?d00001 diff --git a/icons/close.png b/icons/close.png new file mode 100644 index 0000000000000000000000000000000000000000..c46bf520b801085dd045cce7ee32d7988b4869e8 GIT binary patch literal 387 zcmV-}0et?6P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10T4+< zK~xx(bwe3&ct#tpwtZgdW zan2mim7Fp8{lTDku3-S^_eUR0zDLST(@OvV002ovPDHLkV1icPqX+;1 literal 0 HcmV?d00001 diff --git a/icons/disconnected.png b/icons/disconnected.png new file mode 100644 index 0000000000000000000000000000000000000000..91ccac51a9a65a94c8f0ac7affc66ad2db0751dc GIT binary patch literal 1627 zcmV-h2Bi6kP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91D4+uX1ONa40RR91C;$Ke0D9(TtN;K7#Ysd#R9FeUnC(^DN)U!^hXP!J zAj$8T3J?|CqykI@I2FXIAW%V^3X)p^q5_=1`9ik?6_AAcJYrWz2-%hmCv(ndcV}m3 z-kq6U$sP`Nq??0y_s-xx(H?@sA%r zW+t zt%gMHGJ->p9UL6ExaiW|MW!uMdyJnfte+T=E?W-(npPAiK{+G*^R9(YA$$4qfW-=>Vd5tTiC zo#50beTD5N3K$CY06Hg`i|r;7WK&0j?v%(b?Gu5spFe+I8ri59qEclES%zL7 z=ek<3Tq&nb1(-O%L2kQ_aFpu2ts^kg&RzDJT^r>IIu*bMxCB|RH?NRY z+Ly4WmOFCH+$VIdv6sNTA{Tw*s|fw^UKFltjSTbhAVg(Kx2ipAvJfcalKvs{V>Xd< zYCVM$4^%o3tOX0N={jS3Sc+8WSr6($!hcTx1t7?!W1vz1c%jiv(6IHnyL%m`#l24 z1+tj_*Y>ID+;&GgavdA}&j?;QAT_D&28z+ky7aK|F39g~pi6gyY~nnTT#hhm1SPMI zr0R7%oMS;6rjoWwSX<4Ljm{0U*L018oy0lp69Rts8q(^vH@eoWS))hYlptbxuUi==xRS z8>3L(hr(Srj;FcLnzoLmLU1mTC-8!7ovKp4SroE=q> zZR$KlUvSu&2U|<(?yzmQ6QK1-s4wA(3#>`_bZzL{me-82pJ4j~oT?V=`*XoEyGNY> zqlO`8u_s%yc)dn;j;0&Oj{i8!Xzg2b7o2{_owOF9D*!nKq^U?8Lj{}zHUq5vjby}7 zI%xu9Tr$ph0i3Thuo0k(brAj`J!u&$7%$-~B2mV#a=$Ivlo9d^DTNcbCZmR~wO!W2 z6`fuHPx%s7XXYRCr$Pn_+gtAPC3PlWjZM){|{J*;evOo+hGz_yV!{vc1$;`S}OI*xNGs zbqiqs`?4%QqsHkG02uuFG#LI*F9h-XxcOxP#R~v5dT(&c#{08@8ioe|-w%mP4KINK z7-A4-0?^KSO%)Ub1akm%762Fs`Z?v}>HRl>N+3>rBoCv)_V?E&X8=fzhe4qqb0R&c zCJmC58n9o0jPB(I0L%znU^vIYE_0D-fVssUgS zfxi%qM_-WT!C)ovfujNy08YoFfN&EV?ij@YII-?8Z$-wEKQ-=06h49Yxd3Du2c*B zb@mRVVJXvI(j`#)J!A~%0l;0lx(03eu%!aR84n;W0i01Ra{V1!*=i3+N(?kO@?af9Z$ z1yUN1pR`0zh?>Sz8zQN^qHJ^RU4msi2LQCe(-e{)E=hdUs_o(Q2dCqj3viwf-{u7% g{1kQ?Fuel)0cc01L5IGuegFUf07*qoM6N<$g4JM8t^fc4 literal 0 HcmV?d00001 diff --git a/icons/savefile.png b/icons/savefile.png new file mode 100644 index 0000000000000000000000000000000000000000..afd4178fae61d979d2b2ed82521d5f7adc43b2bd GIT binary patch literal 1067 zcmV+`1l0S9P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91D4+uX1ONa40RR91C;$Ke0D9(TtN;K5l}SWFR9Fe^nC)@mFc5|hhXN?U z`T89?I8(vV0Zs*%RB)k!%vBJl15!b_3XTGj!B@diKtAqyjn*S{27>L7KhccPYPH&Z zSjjS|^JPPs&*#a@%gYFpgxYhJW?4F&&h+T$XrLapF}C-TqbM329v#HDlso~aL!4d{F!t0N#pmbeggJ%1WQ;TFrqXAy&z$t}@i7N< z3}_ZkSOFvpJ)o{JN<%}}&wV{k7k+<@{0Ds=jKyIe&>KL{$o`n@mwrn?2d5>{H?y(; z;+8t;cDplwq8V&!$x0g9DAv={Q;|KuzA_#=n+p<@Fg~zYB_IYrs3jZ|r_^%{b2quQ z#s#0^a0*P!mbfC;$OY)TRXkTe(g%AkMR_6Fm9aMed%#%$YZXITCjQuTw5gs0^$S4g zl|3ty+GvNJw3hBcCvUd=~?&^Invk$*rb;x@4G!XIM6CCT-=!DPa;Uac_7(7 zmeq^5A`)A0WfqY~TH?N0kv)IV)O&5&ixUfooU8ycFtfq1)zP3D>UZsL^P%I+^VusN z5~oc&S_Rw?K$(j*PJpZqq=U(o4PEpSwbOa77IH!sm2tKyTnpfU?(NS_iTUuIlyYF|CMi2CM_w z6izXB0^9|%4Y*o>>mWHp%con1zxJtYRkSsBS=QyO<>$^Bw8`O~1R}99+M&r!;N+t= zpeBcq()pz+oP0F`l!JAUZNRkxSOwV@Tx);mh~W>Oq8 zK$X`W$O(T7i+hoM5)xBzsl|k2EV*L}?r1VCvGr*ux~JjaSMKRr7xmPVxs^*(;C_<5 z3k53yWyrui)qjd)lesIAS{Imd5EjKkr+Yx%3#C-`t;~owATbLmN{1Ew2`1Sw*002ovPDHLkV1mrj+Uo!S literal 0 HcmV?d00001 diff --git a/install_tools.sh b/install_tools.sh new file mode 100755 index 0000000..71161ba --- /dev/null +++ b/install_tools.sh @@ -0,0 +1,18 @@ +#!/bin/bash -e +cd $(dirname $0) +# install all the tools required for the autotest. + +if [[ `uname` == 'Darwin' ]]; then + MAC_REQUIRED_TOOLS="cmake ninja git-lfs" + for TOOL in ${MAC_REQUIRED_TOOLS[@]}; do + if [ ! $(which $TOOL) ]; then + if [ ! $(which brew) ]; then + echo "Homebrew not found. Trying to install..." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + fi + echo "$TOOL not found. Trying to install..." + brew install $TOOL + fi + done +fi + diff --git a/qml/StartView.qml b/qml/StartView.qml new file mode 100644 index 0000000..848c1fd --- /dev/null +++ b/qml/StartView.qml @@ -0,0 +1,638 @@ +import QtQuick 2.6 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Window 2.12 +import QtQuick.Dialogs +import com.kdab.dockwidgets 2.0 as KDDW +import Qt5Compat.GraphicalEffects + + +ApplicationWindow { + id: startView + width: 800 + height: 600 + minimumWidth: 400 + minimumHeight: 300 + maximumWidth: 1600 + maximumHeight: 1200 + visible: true + title: "Inspector-welcome" + color: "#383838" + property int selectedIndex: 0 + property int selectedFilePathIndex: -1 + property int selectedClientIndex: -1 + + FileDialog { + id: openFileDialog + title: qsTr("open File") + fileMode: FileDialog.OpenFile + nameFilters: [ "Inspector files (*.isp)" ] + onAccepted: { + startViewModel.openFile(openFileDialog.selectedFile) + close() + } + } + + Column { + anchors.fill: parent + spacing: 2 + clip: true + ///* open file area header*/// + Column { + spacing: 2 + width: parent.width + + Rectangle { + width: parent.width + height: 30 + color: "#535353" + + Row { + anchors.left: parent.left + anchors.leftMargin: 10 + spacing: 10 + Text { + anchors.verticalCenter: parent.verticalCenter + text: "Open File" + color: "#DDDDDD" + font.pixelSize: 14 + } + + Item { + height: 30 + width: 30 + Image { + id: openFile + source: "qrc:icons/chooseOpenFile.png" + width: 20 + height: 20 + anchors.centerIn: parent + + ColorOverlay{ + anchors.fill: parent + color: "#DDDDDD" + source: parent + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: parent.scale = 1.1 + onExited: parent.scale = 1.0 + onClicked: { + openFileDialog.open(); + } + } + + } + } + } + } + + ///* open file and drag open file area */// + Row { + width: parent.width + height: (parent.height - 110) * 0.45 + spacing: 2 + + Rectangle { + width: parent.width / 2 + height: parent.height + color: "#434343" + + Text { + id: recentFilesTitle + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.topMargin: 5 + text: "Recent File" + color: "#DDDDDD" + font.pixelSize: 14 + } + + ListView { + id: recentFilesList + anchors.top: recentFilesTitle.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 10 + spacing: 6 + clip: true + model: startViewModel ? startViewModel.fileItems : null + + delegate: Rectangle { + width: recentFilesList.width + height: 40 + color: { + if(mouseArea.containsMouse) { + return "#6b6b6b" + } + else if(index === selectedFilePathIndex) { + return "#6b6b6b" + } + else { + return "transparent" + } + } + radius: 3 + + Column { + anchors.fill: parent + anchors.leftMargin: 8 + anchors.rightMargin: 8 + anchors.verticalCenter: parent.verticalCenter + spacing: 2 + + Text { + text: modelData.filesName + color: "#DDDDDD" + font.pixelSize: 12 + width: parent.width + elide: Text.ElideMiddle + } + + Text { + text: modelData.filesPath + color: "#dddddd" + font.pixelSize: 10 + width: parent.width + elide: Text.ElideMiddle + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: { + selectedFilePathIndex = index + } + onDoubleClicked: { + if (startViewModel && selectedIndex === 0) { + startViewModel.openFile(modelData.filesPath) + } + else if(selectedIndex === 1) { + console.error("Error: Cannot open file by double-clicking in LayerTree mode") + } + } + } + } + } + } + + Rectangle { + width: parent.width / 2 + height: parent.height + color: "#434343" + + Text { + id: dragAreaTitle + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.topMargin: 5 + text: "Drag Open File" + color: "#DDDDDD" + font.pixelSize: 14 + } + + Rectangle { + id: dropContainer + anchors.top: dragAreaTitle.bottom + anchors.topMargin: 10 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 20 + color: dropArea.containsMouse ? "#2d2d2d" : "#434343" + radius: 5 + + Image { + anchors.centerIn: parent + source : "qrc:/icons/openfile.png" + width: 48 + height: 48 + opacity: 0.5 + } + + Text { + anchors.centerIn: parent + anchors.verticalCenterOffset: 50 + text: "Drag file to here" + color: "#44ffffff" + font.pixelSize: 14 + } + + DropArea { + id: dropArea + anchors.fill: parent + + onDropped: function(drop) { + if (drop.hasUrls) { + var fileUrl = drop.urls[0]; + var filePath; + if (Qt.platform.os === "windows") { + filePath = fileUrl.toString().replace(/^(file:\/{3})|^file:\//, ""); + } else { + filePath = fileUrl.toString().replace(/^(file:\/{2})|^file:\//, ""); + } + filePath = decodeURIComponent(filePath); + if (startViewModel) { + startViewModel.openFile(filePath); + } + } + } + + onEntered: { + dropContainer.color = "#3d3d3d" + } + + onExited: { + dropContainer.color = "#434343" + } + } + } + } + } + + ///* open client and select launcher header*/// + Column { + spacing: 2 + width: parent.width + + Rectangle { + width: parent.width + height: 30 + color: "#535353" + + Text { + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.verticalCenter: parent.verticalCenter + text: "Start Connection" + color: "#DDDDDD" + font.pixelSize: 14 + } + } + } + + ///* open client and select launcher area */// + Row { + width: parent.width + height: (parent.height - 110) * 0.55 + spacing: 2 + + Rectangle { + width: parent.width / 2 + height: parent.height + color: "#434343" + + Text { + id: clientsTitle + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.topMargin: 5 + text: "Clients" + color: "#DDDDDD" + font.pixelSize: 14 + } + + ListView { + id: frameCaptureclientsList + visible: selectedIndex === 0 + anchors.top: clientsTitle.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 10 + spacing: 6 + clip: true + model: startViewModel ? startViewModel.frameCaptureClientItems : null + + delegate: Rectangle { + width: frameCaptureclientsList.width + height: 40 + color: { + if(mouseArea.containsMouse) { + return "#6b6b6b" + } + else if(index === selectedClientIndex) { + return "#6b6b6b" + } + else { + return "transparent" + } + } + radius: 3 + + Column { + anchors.fill: parent + anchors.leftMargin: 8 + anchors.rightMargin: 8 + anchors.verticalCenter: parent.verticalCenter + spacing: 2 + + Text { + text: modelData.procName + color: modelData.connected ? "#888888" : "#DDDDDD" + font.pixelSize: 12 + width: parent.width + elide: Text.ElideMiddle + } + + Text { + text: modelData.address + ":" + modelData.port + color: modelData.connected ? "#777777" : "#AAAAAA" + font.pixelSize: 10 + width: parent.width + elide: Text.ElideMiddle + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + enabled: !modelData.connected + onClicked: { + selectedClientIndex = index + } + + onDoubleClicked: { + if (startViewModel) { + if(selectedIndex === 0){ + startViewModel.connectToClient(modelData) + }else if(selectedIndex === 1){ + startViewModel.connectToClientByLayerInspector(modelData) + } + } + startView.hide() + } + } + } + } + + ListView { + id: layerTreeClientsList + visible: selectedIndex === 1 + anchors.top: clientsTitle.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 10 + spacing: 6 + clip: true + model: startViewModel ? startViewModel.layerTreeClientItems : null + + delegate: Rectangle { + width: layerTreeClientsList.width + height: 40 + color: { + if(mouseArea.containsMouse) { + return "#6b6b6b" + } + else if(index === selectedClientIndex) { + return "#6b6b6b" + } + else { + return "transparent" + } + } + radius: 3 + + Column { + anchors.fill: parent + anchors.leftMargin: 8 + anchors.rightMargin: 8 + anchors.verticalCenter: parent.verticalCenter + spacing: 2 + + Text { + text: modelData.procName + color: modelData.connected ? "#888888" : "#DDDDDD" + font.pixelSize: 12 + width: parent.width + elide: Text.ElideMiddle + } + + Text { + text: modelData.address + ":" + modelData.port + color: modelData.connected ? "#777777" : "#AAAAAA" + font.pixelSize: 10 + width: parent.width + elide: Text.ElideMiddle + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + enabled: !modelData.connected + onClicked: { + selectedClientIndex = index + } + + onDoubleClicked: { + if (startViewModel) { + if(selectedIndex === 0){ + startViewModel.connectToClient(modelData) + }else if(selectedIndex === 1){ + startViewModel.connectToClientByLayerInspector(modelData) + } + } + startView.hide() + } + } + } + } + + Text { + anchors.centerIn: parent + text: { + if(selectedIndex === 0 && frameCaptureclientsList.count === 0){ + return "Waitting for project start..."; + }else if(selectedIndex === 1 && layerTreeClientsList.count === 0){ + return "Waitting for project start..."; + }else{ + return ""; + } + } + color: "#44ffffff" + font.pixelSize: 14 + } + } + + Rectangle { + width: parent.width / 2 + height: parent.height + color: "#434343" + + + Row { + anchors.top: parent.top + anchors.left: parent.left + anchors.margins: 10 + spacing: 20 + + + Rectangle { + width: 100 + height: 120 + color: selectedIndex === 0 ? "#6b6b6b" : "transparent" + radius: 5 + + Column { + anchors.centerIn: parent + spacing: 5 + Image { + source: "qrc:/icons/InspectorIcon.png" + width: 80 + height: 80 + fillMode: Image.PreserveAspectFit + anchors.horizontalCenter: parent.horizontalCenter + } + Text { + text: "FrameCapture" + color: "#DDDDDD" + font.pixelSize: 14 + anchors.horizontalCenter: parent.horizontalCenter + } + } + + MouseArea { + anchors.fill: parent + onClicked: selectedIndex = 0 + } + } + + Rectangle { + width: 100 + height: 120 + color: selectedIndex === 1 ? "#6b6b6b" : "transparent" + radius: 5 + + Column { + anchors.centerIn: parent + spacing: 5 + Image { + source: "qrc:/icons/LayerTreeIcon.png" + width: 80 + height: 80 + fillMode: Image.PreserveAspectFit + } + Text { + text: "LayerTree" + color: "#DDDDDD" + font.pixelSize: 14 + anchors.horizontalCenter: parent.horizontalCenter + } + } + + MouseArea { + anchors.fill: parent + onClicked: selectedIndex = 1 + } + } + } + } + } + + ///* cancel and launch button */// + Rectangle { + width: parent.width + height: 40 + color: "#535353" + + Row { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: 20 + spacing: 10 + + + Rectangle { + id: cancelBtn + width: 100 + height: 30 + radius: 3 + color: cancelArea.containsMouse ? "#444444" : "#535353" + border.color: "#383838" + border.width: 1 + + Text { + anchors.centerIn: parent + text: "Cancel" + color: "#DDDDDD" + font.pixelSize: 14 + } + + MouseArea { + id: cancelArea + anchors.fill: parent + hoverEnabled: true + onClicked: Qt.quit() + } + } + + + Rectangle { + id: launchBtn + width: 100 + height: 30 + radius: 3 + property bool canLaunch: + (selectedFilePathIndex !== -1 && selectedIndex === 0) || + (selectedClientIndex !== -1) + color: { + if(!canLaunch){ + return "#666666" + } + else if(launchArea.containsMouse){ + return "#444444" + } + else{ + return "#535353" + } + } + border.color: canLaunch ? "#383838" : "#666666" + border.width: 1 + opacity: canLaunch ? 1.0 : 0.6 + + Text { + anchors.centerIn: parent + text: "Launch" + color: "#DDDDDD" + font.pixelSize: 14 + } + + MouseArea { + id: launchArea + anchors.fill: parent + hoverEnabled: true + enabled: launchBtn.canLaunch + onClicked: { + if(selectedIndex === 0 && selectedFilePathIndex !== -1){ + var selectedFilePath = startViewModel.fileItems[selectedFilePathIndex].filesPath + startViewModel.openFile(selectedFilePath) + } + else if(selectedClientIndex !== -1){ + var selectedClient; + if(selectedIndex === 0){ + selectedClient = startViewModel.frameCaptureClientItems[selectedClientIndex] + startViewModel.connectToClient(selectedClient) + } else if(selectedIndex === 1){ + selectedClient = startViewModel.layerTreeClientsList[selectedClientIndex] + startViewModel.connectToClientByLayerInspector(selectedClient) + } + } + startView.hide(); + } + } + } + } + } + } +} diff --git a/res.qrc b/res.qrc new file mode 100644 index 0000000..3f92c37 --- /dev/null +++ b/res.qrc @@ -0,0 +1,13 @@ + + + qml/StartView.qml + icons/InspectorIcon.png + icons/LayerTreeIcon.png + icons/openfile.png + icons/chooseOpenFile.png + icons/capture.png + icons/arrow_icon.png + icons/savefile.png + icons/disconnected.png + + diff --git a/src/ResolvService.cpp b/src/ResolvService.cpp new file mode 100644 index 0000000..f669ebb --- /dev/null +++ b/src/ResolvService.cpp @@ -0,0 +1,74 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// +#include "ResolvService.h" +#if defined(_WIN32) || defined(_WIN64) +#include +#include +#else +#include +#include +#include +#endif + +namespace inspector { +ResolvService::ResolvService(uint16_t port) + : exit(false), port(port), thread([this] { worker(); }) +{ +} + +ResolvService::~ResolvService() { + exit.store(true, std::memory_order_relaxed); + conditionVariable.notify_one(); + thread.join(); +} + +void ResolvService::query(uint32_t ip, const std::function& callback) { + std::lock_guard lock(mutex); + queue.emplace_back(QueueItem{ip, callback}); + conditionVariable.notify_one(); +} + +void ResolvService::worker() { + struct sockaddr_in addr = {}; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + char buf[128] = {}; + + for (;;) { + std::unique_lock lock(mutex); + conditionVariable.wait( + lock, [this] { + return !queue.empty() || exit.load(std::memory_order_relaxed); + }); + if (exit.load(std::memory_order_relaxed)) { + return; + } + auto query = queue.back(); + queue.pop_back(); + lock.unlock(); + + addr.sin_addr.s_addr = query.ip; + if (getnameinfo((const struct sockaddr*)&addr, sizeof(sockaddr_in), buf, 128, nullptr, 0, + NI_NOFQDN) != 0) { + inet_ntop(AF_INET, &query.ip, buf, 17); + } + query.callback(buf); + } +} +} // namespace inspector \ No newline at end of file diff --git a/src/ResolvService.h b/src/ResolvService.h new file mode 100644 index 0000000..8d5002d --- /dev/null +++ b/src/ResolvService.h @@ -0,0 +1,53 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace inspector { + +class ResolvService { + struct QueueItem { + uint32_t ip; + std::function callback; + }; + + public: + ResolvService(uint16_t port); + ~ResolvService(); + + void query(uint32_t ip, const std::function& callback); + + private: + void worker(); + + std::atomic exit; + std::mutex mutex; + std::condition_variable conditionVariable; + std::vector queue; + uint16_t port; + std::thread thread; +}; +} // namespace inspector diff --git a/src/StartView.cpp b/src/StartView.cpp new file mode 100644 index 0000000..d08f8b2 --- /dev/null +++ b/src/StartView.cpp @@ -0,0 +1,296 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include "StartView.h" + +#include +#include +#include +#include +#include +#include "Protocol.h" +#include "Socket.h" + +namespace inspector { +ClientData::ClientData(int64_t time, uint32_t protoVer, int32_t activeTime, uint16_t port, + uint64_t pid, std::string procName, std::string address, uint8_t type) + : time(time), protocolVersion(protoVer), activeTime(activeTime), port(port), pid(pid), + procName(std::move(procName)), address(std::move(address)), type(type) { +} + +StartView::StartView(QObject* parent) : QObject(parent), resolv(port) { + loadRecentFiles(); + + broadcastTimer = new QTimer(this); + connect(broadcastTimer, &QTimer::timeout, this, &StartView::updateBroadcastClients); + broadcastTimer->start(1000); +} + +StartView::~StartView() { + saveRecentFiles(); + qDeleteAll(fileItems); + if (broadcastTimer) { + broadcastTimer->stop(); + delete broadcastTimer; + } + for (auto& it : clients) { + delete it.second; + } + clients.clear(); + broadcastListen.reset(); + Q_EMIT quitStartView(); +} + +QList StartView::getFileItems() const { + QList items; + for (const auto& fileItem : fileItems) { + items.append(fileItem); + } + return items; +} + +void StartView::openFile(const QString& fPath) { + if (fPath.isEmpty() || !QFileInfo::exists(fPath)) { + return; + } + addRecentFile(fPath); + // TODO Open file +} + +void StartView::openFile(const QUrl& filePath) { + openFile(filePath.path()); +} + +void StartView::addRecentFile(const QString& fPath) { + if (fPath.isEmpty()) { + return; + } + recentFiles.removeAll(fPath); + recentFiles.prepend(fPath); + if (lastOpenFile != fPath) { + lastOpenFile = fPath; + Q_EMIT lastOpenFileChanged(); + } + while (recentFiles.size() >= 15) { + recentFiles.removeLast(); + } + qDeleteAll(fileItems); + fileItems.clear(); + for (const QString& file : recentFiles) { + fileItems.append(new FileItem(file, QFileInfo(file).fileName(), this)); + } + Q_EMIT fileItemsChanged(); + saveRecentFiles(); +} + +void StartView::clearRecentFiles() { + recentFiles.clear(); + qDeleteAll(fileItems); + fileItems.clear(); + + Q_EMIT fileItemsChanged(); + + saveRecentFiles(); +} + +QVector StartView::getFrameCaptureClientItems() const { + QVector clientDatas; + for (auto& client : clients) { + if (client.second->type == static_cast(tgfx::debug::ToolType::FrameCapture)) { + clientDatas.push_back(client.second); + } + } + return clientDatas; +} + +QVector StartView::getLayerTreeClientItems() const { + QVector clientDatas; + for (auto& client : clients) { + if (client.second->type == static_cast(tgfx::debug::ToolType::LayerTree)) { + clientDatas.push_back(client.second); + } + } + return clientDatas; +} + +void StartView::connectToClient(QObject* object) { + auto client = dynamic_cast(object); + if (client) { + // TODO Connect to Insepector + } +} + +void StartView::connectToClientByLayerInspector(QObject* object) { + auto client = dynamic_cast(object); + if (client) { + // TODO Connect to Layer Inspector + } +} + +void StartView::showStartView() { + if (!qmlEngine) { + qmlEngine = new QQmlApplicationEngine(this); + qmlEngine->rootContext()->setContextProperty("startViewModel", this); + qmlEngine->load(QUrl(QStringLiteral("qrc:/qml/StartView.qml"))); + + if (!qmlEngine->rootObjects().isEmpty()) { + auto startWindow = dynamic_cast(qmlEngine->rootObjects().first()); + startWindow->setFlags(Qt::Window); + startWindow->setTitle("Inspector - Start"); + startWindow->resize(1000, 600); + startWindow->show(); + + connect(startWindow, &QQuickWindow::closing, this, &StartView::onCloseAllView); + connect(this, &StartView::quitStartView, QApplication::instance(), &QApplication::quit); + } else { + qWarning() << "无法加载StartView.qml"; + } + } + + if (!qmlEngine->rootObjects().isEmpty()) { + auto startWindow = dynamic_cast(qmlEngine->rootObjects().first()); + startWindow->show(); + } +} + +void StartView::loadRecentFiles() { + QSettings settings("MyCompany", "Inspector"); + recentFiles = settings.value(QStringLiteral("recentFiles")).toStringList(); + + QStringList validFiles; + for (const QString& file : recentFiles) { + if (QFileInfo::exists(file)) { + validFiles.append(file); + } + } + + recentFiles = validFiles; + qDeleteAll(fileItems); + fileItems.clear(); + + for (const QString& file : recentFiles) { + fileItems.append(new FileItem(file, QFileInfo(file).fileName(), this)); + } + + Q_EMIT fileItemsChanged(); +} + +void StartView::saveRecentFiles() { + QSettings settings("MyCompany", "Inspector"); + settings.setValue(QStringLiteral("recentFiles"), recentFiles); + settings.sync(); +} + +void StartView::updateBroadcastClients() { + const auto time = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + if (!broadcastListen) { + bool isListen = false; + broadcastListen = std::make_unique(); + for (uint16_t i = 0; i < tgfx::debug::BroadcastNum; i++) { + isListen = broadcastListen->listenSock(port + i); + if (isListen) { + break; + } + } + if (!isListen) { + broadcastListen.reset(); + } + } else { + tgfx::debug::IpAddress addr; + size_t len; + for (;;) { + auto msg = broadcastListen->readData(len, addr, 0); + if (!msg) { + break; + } + if (len > sizeof(tgfx::debug::BroadcastMessage)) { + continue; + } + tgfx::debug::BroadcastMessage bm = {}; + memcpy(&bm, msg, len); + auto protoVer = bm.protocolVersion; + char procname[tgfx::debug::WelcomeMessageProgramNameSize]; + strcpy(procname, bm.programName); + auto activeTime = bm.activeTime; + auto listenPort = bm.listenPort; + auto pid = bm.pid; + auto type = bm.type; + + auto address = addr.getText(); + const auto ipNumerical = addr.getNumber(); + const auto clientId = uint64_t(ipNumerical) | (uint64_t(listenPort) << 32); + auto it = clients.find(clientId); + if (activeTime >= 0) { + if (it == clients.end()) { + std::string ip(address); + + resolvLock.lock(); + if (resolvMap.find(ip) == resolvMap.end()) { + resolvMap.emplace(ip, ip); + resolv.query(ipNumerical, [&, ip](std::string&& name) { + std::lock_guard lock(resolvLock); + auto iter = resolvMap.find(ip); + assert(iter != resolvMap.end()); + std::swap(iter->second, name); + }); + } + resolvLock.unlock(); + auto client = new ClientData{time, protoVer, activeTime, listenPort, + pid, procname, std::move(ip), type}; + clients.emplace(clientId, client); + Q_EMIT clientItemsChanged(); + } else { + auto client = it->second; + client->time = time; + client->activeTime = activeTime; + client->port = listenPort; + client->pid = pid; + client->protocolVersion = protoVer; + if (strcmp(client->procName.c_str(), procname) != 0) { + client->procName = procname; + } + client->type = type; + } + } else if (it != clients.end()) { + clients.erase(it); + Q_EMIT clientItemsChanged(); + } + } + auto it = clients.begin(); + while (it != clients.end()) { + const auto diff = time - it->second->time; + if (diff > 4000) { + it = clients.erase(it); + Q_EMIT clientItemsChanged(); + } else { + ++it; + } + } + } +} + +void StartView::onCloseView(QObject* view) { + view->deleteLater(); +} + +void StartView::onCloseAllView() { + this->deleteLater(); +} +} // namespace inspector diff --git a/src/StartView.h b/src/StartView.h new file mode 100644 index 0000000..0286c27 --- /dev/null +++ b/src/StartView.h @@ -0,0 +1,177 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ResolvService.h" +#include "Socket.h" + +namespace inspector { + +class ClientData : public QObject { + Q_OBJECT + Q_PROPERTY(bool connected READ getConnected NOTIFY connectStateChange) + Q_PROPERTY(uint16_t port READ getPort CONSTANT) + Q_PROPERTY(QString procName READ getProcName CONSTANT) + Q_PROPERTY(QString address READ getAddress CONSTANT) + Q_PROPERTY(uint8_t type READ getType CONSTANT) + public: + ClientData(int64_t time, uint32_t protoVer, int32_t activeTime, uint16_t port, uint64_t pid, + std::string procName, std::string address, uint8_t type); + + QString getProcName() const { + return QString::fromStdString(procName); + } + QString getAddress() const { + return QString::fromStdString(address); + } + uint16_t getPort() const { + return port; + } + bool getConnected() const { + return connected; + } + uint8_t getType() const { + return type; + } + void setConnected(bool isConnect) { + connected = isConnect; + Q_EMIT connectStateChange(); + } + + Q_SIGNAL void connectStateChange(); + + bool connected = false; + int64_t time = 0; + uint32_t protocolVersion = 0; + int32_t activeTime = 0; + uint16_t port = 0; + uint64_t pid = 0; + std::string procName; + std::string address; + uint8_t type; +}; + +class FileItem : public QObject { + Q_OBJECT + Q_PROPERTY(QString filesPath READ getFilePath CONSTANT) + Q_PROPERTY(QString filesName READ getFileName CONSTANT) + Q_PROPERTY(QDateTime lastOpened READ getLastOpened CONSTANT) + + public: + explicit FileItem(const QString& fsPath, const QString& fsName, QObject* parent) + : QObject(parent), filesPath(fsPath), filesName(fsName), + lastOpened(QDateTime::currentDateTime()) { + } + + QString getFilePath() const { + return filesPath; + } + QString getFileName() const { + return QFileInfo(filesPath).fileName(); + } + QDateTime getLastOpened() const { + return lastOpened; + } + + private: + QString filesPath; + QString filesName; + QDateTime lastOpened; +}; + +class StartView : public QObject { + Q_OBJECT + Q_PROPERTY(QList fileItems READ getFileItems NOTIFY fileItemsChanged) + Q_PROPERTY(QVector frameCaptureClientItems READ getFrameCaptureClientItems NOTIFY + clientItemsChanged) + Q_PROPERTY( + QVector layerTreeClientItems READ getLayerTreeClientItems NOTIFY clientItemsChanged) + Q_PROPERTY(QString lastOpenFile READ getLastOpenFile NOTIFY lastOpenFileChanged) + + public: + explicit StartView(QObject* parent = nullptr); + ~StartView() override; + + QStringList getRecentFiles() const { + return recentFiles; + } + QString getLastOpenFile() const { + return lastOpenFile; + } + + ///* file items */// + Q_INVOKABLE QList getFileItems() const; + Q_INVOKABLE void openFile(const QString& fPath); + Q_INVOKABLE void openFile(const QUrl& fPath); + Q_INVOKABLE void addRecentFile(const QString& fPath); + Q_INVOKABLE void clearRecentFiles(); + Q_INVOKABLE QString getFileNameFromPath(const QString& fPath) { + return QFileInfo(fPath).fileName(); + } + Q_INVOKABLE QString getDirectoryFromPath(const QString& fPath) { + return QFileInfo(fPath).path(); + } + ///* client items */// + Q_INVOKABLE QVector getFrameCaptureClientItems() const; + Q_INVOKABLE QVector getLayerTreeClientItems() const; + Q_INVOKABLE void connectToClient(QObject* object); + Q_INVOKABLE void connectToClientByLayerInspector(QObject* object); + Q_INVOKABLE void showStartView(); + + Q_SIGNAL void recentFilesChanged(); + Q_SIGNAL void fileItemsChanged(); + Q_SIGNAL void lastOpenFileChanged(); + Q_SIGNAL void openStatView(const QString& fPath); + + Q_SIGNAL void clientItemsChanged(); + Q_SIGNAL void openConnectView(const QString& address, uint16_t port); + Q_SIGNAL void quitStartView(); + + Q_SLOT void onCloseView(QObject* view); + Q_SLOT void onCloseAllView(); + + protected: + void loadRecentFiles(); + void saveRecentFiles(); + + void updateBroadcastClients(); + + private: + QString lastOpenFile; + QStringList recentFiles; + QList fileItems; + std::mutex resolvLock; + uint16_t port = 8086; + ResolvService resolv; + std::unique_ptr broadcastListen; + std::unordered_map clients; + std::unordered_map resolvMap; + + QTimer* broadcastTimer = nullptr; + QQmlApplicationEngine* qmlEngine = nullptr; +}; +} // namespace inspector diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1d6745f --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,67 @@ +///////////////////////////////////////////////////////////////////////////////////////////////// +// +// Tencent is pleased to support the open source community by making tgfx available. +// +// Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved. +// +// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// https://opensource.org/licenses/BSD-3-Clause +// +// unless required by applicable law or agreed to in writing, software distributed under the +// license is distributed on an "as is" basis, without warranties or conditions of any kind, +// either express or implied. see the license for the specific language governing permissions +// and limitations under the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +#include "StartView.h" + +int main(int argc, char* argv[]) { + QApplication::setApplicationName("Inspector"); + QApplication::setOrganizationName("org.tgfx"); + QSurfaceFormat defaultFormat = QSurfaceFormat(); + defaultFormat.setRenderableType(QSurfaceFormat::RenderableType::OpenGL); + defaultFormat.setVersion(3, 2); + defaultFormat.setProfile(QSurfaceFormat::CoreProfile); + QSurfaceFormat::setDefaultFormat(defaultFormat); + qputenv("QT_LOGGING_RULES", "qt.qpa.*=false"); + QQuickStyle::setStyle("Basic"); + +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); + +#else + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); +#endif + + QApplication app(argc, argv); + + static bool initialized = false; + if (!initialized) { + initFrontend(KDDockWidgets::FrontendType::QtQuick); + initialized = true; + } + + auto& config = KDDockWidgets::Config::self(); + config.setSeparatorThickness(2); + auto flags = config.flags() | KDDockWidgets::Config::Flag_TitleBarIsFocusable | + KDDockWidgets::Config::Flag_HideTitleBarWhenTabsVisible; + + config.setFlags(flags); + + // 创建并显示StartView + auto startView = new inspector::StartView(&app); + startView->showStartView(); + + return app.exec(); +} diff --git a/sync_deps.sh b/sync_deps.sh new file mode 100755 index 0000000..fea1df9 --- /dev/null +++ b/sync_deps.sh @@ -0,0 +1,13 @@ +#!/bin/bash +cd $(dirname $0) + +./install_tools.sh + +if [ ! $(which depsync) ]; then + echo "depsync not found. Trying to install..." + npm install -g depsync > /dev/null +else + npm update -g depsync > /dev/null +fi + +depsync || exit 1 \ No newline at end of file diff --git a/vendor.json b/vendor.json new file mode 100644 index 0000000..5658eac --- /dev/null +++ b/vendor.json @@ -0,0 +1,32 @@ +{ + "source": "third_party", + "out": "third_party/out", + "vendors": [ + { + "name": "KDDockWidgets", + "cmake": { + "targets": [ + "kddockwidgets" + ], + "arguments": [ + "-DKDDockWidgets_QT6=ON", + "-DKDDockWidgets_STATIC=ON", + "-DKDDW_FRONTEND_QTQUICK=ON", + "-DCMAKE_PREFIX_PATH=\"/Users/lifengdy/Qt/6.8.1/macos/lib/cmake\"" + ] + } + }, + { + "name": "flatbuffers", + "cmake": { + "targets": [ + "flatbuffers" + ], + "arguments": [ + "-DFLATBUFFERS_BUILD_FLATLIB=ON", + "-DFLATBUFFERS_BUILD_CPP17=ON" + ] + } + } + ] +} \ No newline at end of file From a9c05b52c684c26de81fd840f420566a8de0e5e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E7=AC=9B?= <1753894329@qq.com> Date: Wed, 20 Aug 2025 17:40:19 +0800 Subject: [PATCH 2/7] Initializing variables; Qt is fixed to use Qt6. --- CMakeLists.txt | 30 ++++-------------------------- src/ResolvService.cpp | 2 +- src/ResolvService.h | 12 ++++++------ src/StartView.cpp | 33 ++++++++++++++++----------------- src/StartView.h | 36 ++++++++++++++++++++++-------------- vendor.json | 2 +- 6 files changed, 50 insertions(+), 65 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a9f27a..a08b50b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,32 +36,10 @@ add_subdirectory(${TGFX_DIR} tgfx EXCLUDE_FROM_ALL) list(APPEND PAG_STATIC_LIBS $) list(APPEND PAG_DEPEND_TARGETS tgfx) -find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) -if (${QT_VERSION_MAJOR} LESS 6) - message("The QT version is less than 6.0, force to use x86_64 architecture.") - SET(CMAKE_SYSTEM_PROCESSOR x86_64) - SET(CMAKE_OSX_ARCHITECTURES x86_64) -endif () - -find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Widgets OpenGL qml Quick WebSockets Network QuickControls2 Core5Compat) -list(APPEND TGFX_INSPECTOR_LIBS Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Widgets - Qt${QT_VERSION_MAJOR}::WebSockets Qt${QT_VERSION_MAJOR}::OpenGL Qt${QT_VERSION_MAJOR}::Qml Qt${QT_VERSION_MAJOR}::Quick - Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::QuickControls2 Qt${QT_VERSION_MAJOR}::Core5Compat) -if (${QT_VERSION} VERSION_LESS "5.15") - function(qt_add_resources outfiles) - qt5_add_resources("${outfiles}" ${ARGN}) - if (TARGET ${outfiles}) - cmake_parse_arguments(PARSE_ARGV 1 arg "" "OUTPUT_TARGETS" "") - if (arg_OUTPUT_TARGETS) - set(${arg_OUTPUT_TARGETS} ${${arg_OUTPUT_TARGETS}} PARENT_SCOPE) - endif () - else () - set("${outfiles}" "${${outfiles}}" PARENT_SCOPE) - endif () - endfunction() -endif () - -qt_add_resources(QT_RESOURCES res.qrc) +find_package(Qt6 REQUIRED COMPONENTS Core Widgets OpenGL qml Quick WebSockets Network QuickControls2 Core5Compat) +list(APPEND TGFX_INSPECTOR_LIBS Qt6::Core Qt6::Widgets Qt6::WebSockets Qt6::OpenGL Qt6::Qml Qt6::Quick Qt6::Network + Qt6::QuickControls2 Qt6::Core5Compat) +qt6_add_resources(QT_RESOURCES res.qrc) if (APPLE) find_library(APPLICATION_SERVICES_FRAMEWORK ApplicationServices REQUIRED) diff --git a/src/ResolvService.cpp b/src/ResolvService.cpp index f669ebb..0590239 100644 --- a/src/ResolvService.cpp +++ b/src/ResolvService.cpp @@ -27,7 +27,7 @@ namespace inspector { ResolvService::ResolvService(uint16_t port) - : exit(false), port(port), thread([this] { worker(); }) + : port(port), thread([this] { worker(); }) { } diff --git a/src/ResolvService.h b/src/ResolvService.h index 8d5002d..ed543bc 100644 --- a/src/ResolvService.h +++ b/src/ResolvService.h @@ -35,7 +35,7 @@ class ResolvService { }; public: - ResolvService(uint16_t port); + explicit ResolvService(uint16_t port); ~ResolvService(); void query(uint32_t ip, const std::function& callback); @@ -43,11 +43,11 @@ class ResolvService { private: void worker(); - std::atomic exit; - std::mutex mutex; - std::condition_variable conditionVariable; - std::vector queue; - uint16_t port; + std::atomic exit = false; + std::mutex mutex = {}; + std::condition_variable conditionVariable = {}; + std::vector queue = {}; + uint16_t port = 0; std::thread thread; }; } // namespace inspector diff --git a/src/StartView.cpp b/src/StartView.cpp index d08f8b2..2ca8769 100644 --- a/src/StartView.cpp +++ b/src/StartView.cpp @@ -27,10 +27,8 @@ #include "Socket.h" namespace inspector { -ClientData::ClientData(int64_t time, uint32_t protoVer, int32_t activeTime, uint16_t port, - uint64_t pid, std::string procName, std::string address, uint8_t type) - : time(time), protocolVersion(protoVer), activeTime(activeTime), port(port), pid(pid), - procName(std::move(procName)), address(std::move(address)), type(type) { +ClientData::ClientData(Data data) + : data(std::move(data)) { } StartView::StartView(QObject* parent) : QObject(parent), resolv(port) { @@ -58,6 +56,7 @@ StartView::~StartView() { QList StartView::getFileItems() const { QList items; + items.reserve(fileItems.size()); for (const auto& fileItem : fileItems) { items.append(fileItem); } @@ -111,7 +110,7 @@ void StartView::clearRecentFiles() { QVector StartView::getFrameCaptureClientItems() const { QVector clientDatas; for (auto& client : clients) { - if (client.second->type == static_cast(tgfx::debug::ToolType::FrameCapture)) { + if (client.second->data.type == static_cast(tgfx::debug::ToolType::FrameCapture)) { clientDatas.push_back(client.second); } } @@ -121,7 +120,7 @@ QVector StartView::getFrameCaptureClientItems() const { QVector StartView::getLayerTreeClientItems() const { QVector clientDatas; for (auto& client : clients) { - if (client.second->type == static_cast(tgfx::debug::ToolType::LayerTree)) { + if (client.second->data.type == static_cast(tgfx::debug::ToolType::LayerTree)) { clientDatas.push_back(client.second); } } @@ -252,21 +251,21 @@ void StartView::updateBroadcastClients() { }); } resolvLock.unlock(); - auto client = new ClientData{time, protoVer, activeTime, listenPort, - pid, procname, std::move(ip), type}; + auto client = new ClientData({time, protoVer, activeTime, listenPort, + pid, procname, std::move(ip), type}); clients.emplace(clientId, client); Q_EMIT clientItemsChanged(); } else { auto client = it->second; - client->time = time; - client->activeTime = activeTime; - client->port = listenPort; - client->pid = pid; - client->protocolVersion = protoVer; - if (strcmp(client->procName.c_str(), procname) != 0) { - client->procName = procname; + client->data.time = time; + client->data.activeTime = activeTime; + client->data.port = listenPort; + client->data.pid = pid; + client->data.protocolVersion = protoVer; + if (strcmp(client->data.procName.c_str(), procname) != 0) { + client->data.procName = procname; } - client->type = type; + client->data.type = type; } } else if (it != clients.end()) { clients.erase(it); @@ -275,7 +274,7 @@ void StartView::updateBroadcastClients() { } auto it = clients.begin(); while (it != clients.end()) { - const auto diff = time - it->second->time; + const auto diff = time - it->second->data.time; if (diff > 4000) { it = clients.erase(it); Q_EMIT clientItemsChanged(); diff --git a/src/StartView.h b/src/StartView.h index 0286c27..233a26d 100644 --- a/src/StartView.h +++ b/src/StartView.h @@ -39,24 +39,39 @@ class ClientData : public QObject { Q_PROPERTY(QString address READ getAddress CONSTANT) Q_PROPERTY(uint8_t type READ getType CONSTANT) public: - ClientData(int64_t time, uint32_t protoVer, int32_t activeTime, uint16_t port, uint64_t pid, - std::string procName, std::string address, uint8_t type); + struct Data { + int64_t time = 0; + uint32_t protocolVersion = 0; + int32_t activeTime = 0; + uint16_t port = 0; + uint64_t pid = 0; + std::string procName = {}; + std::string address = {}; + uint8_t type = 0; + }; + + explicit ClientData(Data data); QString getProcName() const { - return QString::fromStdString(procName); + return QString::fromStdString(data.procName); } + QString getAddress() const { - return QString::fromStdString(address); + return QString::fromStdString(data.address); } + uint16_t getPort() const { - return port; + return data.port; } + bool getConnected() const { return connected; } + uint8_t getType() const { - return type; + return data.type; } + void setConnected(bool isConnect) { connected = isConnect; Q_EMIT connectStateChange(); @@ -65,14 +80,7 @@ class ClientData : public QObject { Q_SIGNAL void connectStateChange(); bool connected = false; - int64_t time = 0; - uint32_t protocolVersion = 0; - int32_t activeTime = 0; - uint16_t port = 0; - uint64_t pid = 0; - std::string procName; - std::string address; - uint8_t type; + Data data = {}; }; class FileItem : public QObject { diff --git a/vendor.json b/vendor.json index 5658eac..caa64de 100644 --- a/vendor.json +++ b/vendor.json @@ -12,7 +12,7 @@ "-DKDDockWidgets_QT6=ON", "-DKDDockWidgets_STATIC=ON", "-DKDDW_FRONTEND_QTQUICK=ON", - "-DCMAKE_PREFIX_PATH=\"/Users/lifengdy/Qt/6.8.1/macos/lib/cmake\"" + "-DCMAKE_PREFIX_PATH=\"/Users/[username]/Qt/[QtVersion]/macos/lib/cmake\"" ] } }, From 8592cab72da6ef1eb44ef7906ee373ff93877b17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E7=AC=9B?= <1753894329@qq.com> Date: Wed, 20 Aug 2025 17:45:16 +0800 Subject: [PATCH 3/7] Initializing variables; --- src/ResolvService.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ResolvService.h b/src/ResolvService.h index ed543bc..905f322 100644 --- a/src/ResolvService.h +++ b/src/ResolvService.h @@ -30,8 +30,8 @@ namespace inspector { class ResolvService { struct QueueItem { - uint32_t ip; - std::function callback; + uint32_t ip = 0; + std::function callback = nullptr; }; public: From ae984057e9084f8a254a347445708bb54a4c086c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E7=AC=9B?= <1753894329@qq.com> Date: Tue, 26 Aug 2025 17:09:40 +0800 Subject: [PATCH 4/7] Initializing variables; --- src/ResolvService.cpp | 8 ++------ src/ResolvService.h | 1 + src/StartView.cpp | 13 ++++++------- src/StartView.h | 22 +++++++++++----------- src/main.cpp | 3 +-- 5 files changed, 21 insertions(+), 26 deletions(-) diff --git a/src/ResolvService.cpp b/src/ResolvService.cpp index 0590239..d7bd84d 100644 --- a/src/ResolvService.cpp +++ b/src/ResolvService.cpp @@ -26,9 +26,7 @@ #endif namespace inspector { -ResolvService::ResolvService(uint16_t port) - : port(port), thread([this] { worker(); }) -{ +ResolvService::ResolvService(uint16_t port) : port(port), thread([this] { worker(); }) { } ResolvService::~ResolvService() { @@ -53,9 +51,7 @@ void ResolvService::worker() { for (;;) { std::unique_lock lock(mutex); conditionVariable.wait( - lock, [this] { - return !queue.empty() || exit.load(std::memory_order_relaxed); - }); + lock, [this] { return !queue.empty() || exit.load(std::memory_order_relaxed); }); if (exit.load(std::memory_order_relaxed)) { return; } diff --git a/src/ResolvService.h b/src/ResolvService.h index 905f322..d509f6c 100644 --- a/src/ResolvService.h +++ b/src/ResolvService.h @@ -35,6 +35,7 @@ class ResolvService { }; public: + ResolvService() = default; explicit ResolvService(uint16_t port); ~ResolvService(); diff --git a/src/StartView.cpp b/src/StartView.cpp index 2ca8769..8393f0a 100644 --- a/src/StartView.cpp +++ b/src/StartView.cpp @@ -17,7 +17,6 @@ ///////////////////////////////////////////////////////////////////////////////////////////////// #include "StartView.h" - #include #include #include @@ -27,8 +26,7 @@ #include "Socket.h" namespace inspector { -ClientData::ClientData(Data data) - : data(std::move(data)) { +ClientData::ClientData(Data data) : data(std::move(data)) { } StartView::StartView(QObject* parent) : QObject(parent), resolv(port) { @@ -199,6 +197,7 @@ void StartView::updateBroadcastClients() { const auto time = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()) .count(); + if (!broadcastListen) { bool isListen = false; broadcastListen = std::make_unique(); @@ -213,7 +212,7 @@ void StartView::updateBroadcastClients() { } } else { tgfx::debug::IpAddress addr; - size_t len; + size_t len = 0; for (;;) { auto msg = broadcastListen->readData(len, addr, 0); if (!msg) { @@ -239,7 +238,6 @@ void StartView::updateBroadcastClients() { if (activeTime >= 0) { if (it == clients.end()) { std::string ip(address); - resolvLock.lock(); if (resolvMap.find(ip) == resolvMap.end()) { resolvMap.emplace(ip, ip); @@ -251,8 +249,8 @@ void StartView::updateBroadcastClients() { }); } resolvLock.unlock(); - auto client = new ClientData({time, protoVer, activeTime, listenPort, - pid, procname, std::move(ip), type}); + auto client = new ClientData( + {time, protoVer, activeTime, listenPort, pid, procname, std::move(ip), type}); clients.emplace(clientId, client); Q_EMIT clientItemsChanged(); } else { @@ -272,6 +270,7 @@ void StartView::updateBroadcastClients() { Q_EMIT clientItemsChanged(); } } + auto it = clients.begin(); while (it != clients.end()) { const auto diff = time - it->second->data.time; diff --git a/src/StartView.h b/src/StartView.h index 233a26d..82217d1 100644 --- a/src/StartView.h +++ b/src/StartView.h @@ -106,9 +106,9 @@ class FileItem : public QObject { } private: - QString filesPath; - QString filesName; - QDateTime lastOpened; + QString filesPath = {}; + QString filesName = {}; + QDateTime lastOpened = {}; }; class StartView : public QObject { @@ -169,15 +169,15 @@ class StartView : public QObject { void updateBroadcastClients(); private: - QString lastOpenFile; - QStringList recentFiles; - QList fileItems; - std::mutex resolvLock; + QString lastOpenFile = {}; + QStringList recentFiles = {}; + QList fileItems = {}; + std::mutex resolvLock = {}; uint16_t port = 8086; - ResolvService resolv; - std::unique_ptr broadcastListen; - std::unordered_map clients; - std::unordered_map resolvMap; + ResolvService resolv = {}; + std::unique_ptr broadcastListen = nullptr; + std::unordered_map clients = {}; + std::unordered_map resolvMap = {}; QTimer* broadcastTimer = nullptr; QQmlApplicationEngine* qmlEngine = nullptr; diff --git a/src/main.cpp b/src/main.cpp index 1d6745f..d9eef47 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,13 +16,12 @@ // ///////////////////////////////////////////////////////////////////////////////////////////////// -#include #include #include +#include #include #include #include - #include "StartView.h" int main(int argc, char* argv[]) { From bbcdf6720baa7b4b07a6cd55be8006c0cd16289c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E7=AC=9B?= <1753894329@qq.com> Date: Tue, 26 Aug 2025 17:17:04 +0800 Subject: [PATCH 5/7] Initializing variables; --- src/StartView.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/StartView.cpp b/src/StartView.cpp index 8393f0a..628707d 100644 --- a/src/StartView.cpp +++ b/src/StartView.cpp @@ -24,6 +24,7 @@ #include #include "Protocol.h" #include "Socket.h" +#include "tgfx/core/Clock.h" namespace inspector { ClientData::ClientData(Data data) : data(std::move(data)) { @@ -106,7 +107,8 @@ void StartView::clearRecentFiles() { } QVector StartView::getFrameCaptureClientItems() const { - QVector clientDatas; + QVector clientDatas = {}; + clientDatas.reserve(static_cast(clients.size())); for (auto& client : clients) { if (client.second->data.type == static_cast(tgfx::debug::ToolType::FrameCapture)) { clientDatas.push_back(client.second); @@ -116,7 +118,8 @@ QVector StartView::getFrameCaptureClientItems() const { } QVector StartView::getLayerTreeClientItems() const { - QVector clientDatas; + QVector clientDatas = {}; + clientDatas.reserve(static_cast(clients.size())); for (auto& client : clients) { if (client.second->data.type == static_cast(tgfx::debug::ToolType::LayerTree)) { clientDatas.push_back(client.second); @@ -194,10 +197,7 @@ void StartView::saveRecentFiles() { } void StartView::updateBroadcastClients() { - const auto time = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - + const auto time = tgfx::Clock::Now(); if (!broadcastListen) { bool isListen = false; broadcastListen = std::make_unique(); From 8bde8130cc992ac4fdfc015b5faaa87bc2b091cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E7=AC=9B?= <1753894329@qq.com> Date: Mon, 1 Sep 2025 17:02:30 +0800 Subject: [PATCH 6/7] Initializing variables; --- src/ResolvService.cpp | 10 +++++++--- src/ResolvService.h | 8 ++++---- src/StartView.cpp | 21 +++++++++++---------- src/StartView.h | 8 ++++---- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/ResolvService.cpp b/src/ResolvService.cpp index d7bd84d..416d94b 100644 --- a/src/ResolvService.cpp +++ b/src/ResolvService.cpp @@ -26,16 +26,20 @@ #endif namespace inspector { -ResolvService::ResolvService(uint16_t port) : port(port), thread([this] { worker(); }) { +ResolvService::ResolvService(uint16_t port) : port(port) { + thread = std::make_unique(worker); } ResolvService::~ResolvService() { exit.store(true, std::memory_order_relaxed); conditionVariable.notify_one(); - thread.join(); + if (thread) { + thread->join(); + thread.reset(); + } } -void ResolvService::query(uint32_t ip, const std::function& callback) { +void ResolvService::query(uint32_t ip, const std::function& callback) { std::lock_guard lock(mutex); queue.emplace_back(QueueItem{ip, callback}); conditionVariable.notify_one(); diff --git a/src/ResolvService.h b/src/ResolvService.h index d509f6c..7456e59 100644 --- a/src/ResolvService.h +++ b/src/ResolvService.h @@ -31,15 +31,15 @@ namespace inspector { class ResolvService { struct QueueItem { uint32_t ip = 0; - std::function callback = nullptr; + std::function callback = nullptr; }; public: - ResolvService() = default; explicit ResolvService(uint16_t port); + ~ResolvService(); - void query(uint32_t ip, const std::function& callback); + void query(uint32_t ip, const std::function& callback); private: void worker(); @@ -49,6 +49,6 @@ class ResolvService { std::condition_variable conditionVariable = {}; std::vector queue = {}; uint16_t port = 0; - std::thread thread; + std::unique_ptr thread = nullptr; }; } // namespace inspector diff --git a/src/StartView.cpp b/src/StartView.cpp index 628707d..fccf06a 100644 --- a/src/StartView.cpp +++ b/src/StartView.cpp @@ -30,7 +30,8 @@ namespace inspector { ClientData::ClientData(Data data) : data(std::move(data)) { } -StartView::StartView(QObject* parent) : QObject(parent), resolv(port) { +StartView::StartView(QObject* parent) : QObject(parent) { + resolv = std::make_unique(port); loadRecentFiles(); broadcastTimer = new QTimer(this); @@ -54,7 +55,7 @@ StartView::~StartView() { } QList StartView::getFileItems() const { - QList items; + QList items = {}; items.reserve(fileItems.size()); for (const auto& fileItem : fileItems) { items.append(fileItem); @@ -169,7 +170,7 @@ void StartView::showStartView() { } void StartView::loadRecentFiles() { - QSettings settings("MyCompany", "Inspector"); + QSettings settings("TGFX", "Inspector"); recentFiles = settings.value(QStringLiteral("recentFiles")).toStringList(); QStringList validFiles; @@ -191,7 +192,7 @@ void StartView::loadRecentFiles() { } void StartView::saveRecentFiles() { - QSettings settings("MyCompany", "Inspector"); + QSettings settings("TGFXInspector", "Inspector"); settings.setValue(QStringLiteral("recentFiles"), recentFiles); settings.sync(); } @@ -214,17 +215,17 @@ void StartView::updateBroadcastClients() { tgfx::debug::IpAddress addr; size_t len = 0; for (;;) { - auto msg = broadcastListen->readData(len, addr, 0); - if (!msg) { + auto broadcastMessage = broadcastListen->readData(len, addr, 0); + if (!broadcastMessage) { break; } if (len > sizeof(tgfx::debug::BroadcastMessage)) { continue; } tgfx::debug::BroadcastMessage bm = {}; - memcpy(&bm, msg, len); + memcpy(&bm, broadcastMessage, len); auto protoVer = bm.protocolVersion; - char procname[tgfx::debug::WelcomeMessageProgramNameSize]; + char procname[tgfx::debug::WelcomeMessageProgramNameSize] = ""; strcpy(procname, bm.programName); auto activeTime = bm.activeTime; auto listenPort = bm.listenPort; @@ -241,11 +242,11 @@ void StartView::updateBroadcastClients() { resolvLock.lock(); if (resolvMap.find(ip) == resolvMap.end()) { resolvMap.emplace(ip, ip); - resolv.query(ipNumerical, [&, ip](std::string&& name) { + resolv->query(ipNumerical, [&, ip](const char* name) { std::lock_guard lock(resolvLock); auto iter = resolvMap.find(ip); assert(iter != resolvMap.end()); - std::swap(iter->second, name); + iter->second = name; }); } resolvLock.unlock(); diff --git a/src/StartView.h b/src/StartView.h index 82217d1..1a3e40e 100644 --- a/src/StartView.h +++ b/src/StartView.h @@ -106,8 +106,8 @@ class FileItem : public QObject { } private: - QString filesPath = {}; - QString filesName = {}; + QString filesPath = ""; + QString filesName = ""; QDateTime lastOpened = {}; }; @@ -169,12 +169,12 @@ class StartView : public QObject { void updateBroadcastClients(); private: - QString lastOpenFile = {}; + QString lastOpenFile = ""; QStringList recentFiles = {}; QList fileItems = {}; std::mutex resolvLock = {}; uint16_t port = 8086; - ResolvService resolv = {}; + std::unique_ptr resolv = nullptr; std::unique_ptr broadcastListen = nullptr; std::unordered_map clients = {}; std::unordered_map resolvMap = {}; From e3cce8bb81ced77a931b1f5e767f4f0b2e981cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E7=AC=9B?= <1753894329@qq.com> Date: Tue, 2 Sep 2025 14:49:49 +0800 Subject: [PATCH 7/7] Disable rtti. --- CMakeLists.txt | 5 +++-- src/ResolvService.cpp | 2 +- src/StartView.cpp | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a08b50b..b058aa3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,8 +33,6 @@ set(TGFX_USE_QT ON) set(TGFX_BUILD_INSPECTOR OFF) set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) add_subdirectory(${TGFX_DIR} tgfx EXCLUDE_FROM_ALL) -list(APPEND PAG_STATIC_LIBS $) -list(APPEND PAG_DEPEND_TARGETS tgfx) find_package(Qt6 REQUIRED COMPONENTS Core Widgets OpenGL qml Quick WebSockets Network QuickControls2 Core5Compat) list(APPEND TGFX_INSPECTOR_LIBS Qt6::Core Qt6::Widgets Qt6::WebSockets Qt6::OpenGL Qt6::Qml Qt6::Quick Qt6::Network @@ -86,8 +84,11 @@ list(APPEND TGFX_INSPECTOR_DEFINE KDDW_FRONTEND_QTQUICK KDDW_FRONTEND_QT KDDW_QT list(APPEND TGFX_INSPECTOR_INCLUDE ./third_party/KDDockWidgets/src/fwd_headers) +list(APPEND TGFX_INSPECTOR_OPTIONS -fno-rtti) + add_executable(inspectorTool ${RC_FILES} ${TGFX_INSPECTOR_FILES} ${TGFX_INSPECTOR_COMMON_FILES} ${QT_RESOURCES}) target_include_directories(inspectorTool PUBLIC ${TGFX_INSPECTOR_SRC} ${TGFX_INSPECTOR_COMMON_INCLUDE}) target_include_directories(inspectorTool SYSTEM PRIVATE ${TGFX_INSPECTOR_INCLUDE}) target_compile_definitions(inspectorTool PUBLIC ${TGFX_INSPECTOR_DEFINE}) +target_compile_options(inspectorTool PUBLIC ${TGFX_INSPECTOR_OPTIONS}) target_link_libraries(inspectorTool tgfx ${TGFX_INSPECTOR_LIBS}) \ No newline at end of file diff --git a/src/ResolvService.cpp b/src/ResolvService.cpp index 416d94b..34feac0 100644 --- a/src/ResolvService.cpp +++ b/src/ResolvService.cpp @@ -27,7 +27,7 @@ namespace inspector { ResolvService::ResolvService(uint16_t port) : port(port) { - thread = std::make_unique(worker); + thread = std::make_unique([this] { worker(); }); } ResolvService::~ResolvService() { diff --git a/src/StartView.cpp b/src/StartView.cpp index fccf06a..b474c55 100644 --- a/src/StartView.cpp +++ b/src/StartView.cpp @@ -130,14 +130,14 @@ QVector StartView::getLayerTreeClientItems() const { } void StartView::connectToClient(QObject* object) { - auto client = dynamic_cast(object); + auto client = static_cast(object); if (client) { // TODO Connect to Insepector } } void StartView::connectToClientByLayerInspector(QObject* object) { - auto client = dynamic_cast(object); + auto client = static_cast(object); if (client) { // TODO Connect to Layer Inspector } @@ -150,7 +150,7 @@ void StartView::showStartView() { qmlEngine->load(QUrl(QStringLiteral("qrc:/qml/StartView.qml"))); if (!qmlEngine->rootObjects().isEmpty()) { - auto startWindow = dynamic_cast(qmlEngine->rootObjects().first()); + auto startWindow = static_cast(qmlEngine->rootObjects().first()); startWindow->setFlags(Qt::Window); startWindow->setTitle("Inspector - Start"); startWindow->resize(1000, 600); @@ -164,7 +164,7 @@ void StartView::showStartView() { } if (!qmlEngine->rootObjects().isEmpty()) { - auto startWindow = dynamic_cast(qmlEngine->rootObjects().first()); + auto startWindow = static_cast(qmlEngine->rootObjects().first()); startWindow->show(); } }