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..b058aa3
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,94 @@
+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)
+
+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)
+ 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)
+
+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/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 0000000..b429eed
Binary files /dev/null and b/icons/InspectorIcon.png differ
diff --git a/icons/LayerTreeIcon.png b/icons/LayerTreeIcon.png
new file mode 100644
index 0000000..fe67523
Binary files /dev/null and b/icons/LayerTreeIcon.png differ
diff --git a/icons/arrow_icon.png b/icons/arrow_icon.png
new file mode 100644
index 0000000..41db4b3
Binary files /dev/null and b/icons/arrow_icon.png differ
diff --git a/icons/capture.png b/icons/capture.png
new file mode 100644
index 0000000..4ec18f6
Binary files /dev/null and b/icons/capture.png differ
diff --git a/icons/chooseOpenFile.png b/icons/chooseOpenFile.png
new file mode 100644
index 0000000..2a3f26a
Binary files /dev/null and b/icons/chooseOpenFile.png differ
diff --git a/icons/close.png b/icons/close.png
new file mode 100644
index 0000000..c46bf52
Binary files /dev/null and b/icons/close.png differ
diff --git a/icons/disconnected.png b/icons/disconnected.png
new file mode 100644
index 0000000..91ccac5
Binary files /dev/null and b/icons/disconnected.png differ
diff --git a/icons/openfile.png b/icons/openfile.png
new file mode 100644
index 0000000..2c8e38b
Binary files /dev/null and b/icons/openfile.png differ
diff --git a/icons/savefile.png b/icons/savefile.png
new file mode 100644
index 0000000..afd4178
Binary files /dev/null and b/icons/savefile.png differ
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..34feac0
--- /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) : port(port) {
+ thread = std::make_unique([this] { worker(); });
+}
+
+ResolvService::~ResolvService() {
+ exit.store(true, std::memory_order_relaxed);
+ conditionVariable.notify_one();
+ if (thread) {
+ thread->join();
+ thread.reset();
+ }
+}
+
+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..7456e59
--- /dev/null
+++ b/src/ResolvService.h
@@ -0,0 +1,54 @@
+/////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// 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 = 0;
+ std::function callback = nullptr;
+ };
+
+ public:
+ explicit ResolvService(uint16_t port);
+
+ ~ResolvService();
+
+ void query(uint32_t ip, const std::function& callback);
+
+ private:
+ void worker();
+
+ std::atomic exit = false;
+ std::mutex mutex = {};
+ std::condition_variable conditionVariable = {};
+ std::vector queue = {};
+ uint16_t port = 0;
+ std::unique_ptr thread = nullptr;
+};
+} // namespace inspector
diff --git a/src/StartView.cpp b/src/StartView.cpp
new file mode 100644
index 0000000..b474c55
--- /dev/null
+++ b/src/StartView.cpp
@@ -0,0 +1,295 @@
+/////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// 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"
+#include "tgfx/core/Clock.h"
+
+namespace inspector {
+ClientData::ClientData(Data data) : data(std::move(data)) {
+}
+
+StartView::StartView(QObject* parent) : QObject(parent) {
+ resolv = std::make_unique(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 = {};
+ items.reserve(fileItems.size());
+ 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 = {};
+ 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);
+ }
+ }
+ return clientDatas;
+}
+
+QVector StartView::getLayerTreeClientItems() const {
+ 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);
+ }
+ }
+ return clientDatas;
+}
+
+void StartView::connectToClient(QObject* object) {
+ auto client = static_cast(object);
+ if (client) {
+ // TODO Connect to Insepector
+ }
+}
+
+void StartView::connectToClientByLayerInspector(QObject* object) {
+ auto client = static_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 = static_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 = static_cast(qmlEngine->rootObjects().first());
+ startWindow->show();
+ }
+}
+
+void StartView::loadRecentFiles() {
+ QSettings settings("TGFX", "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("TGFXInspector", "Inspector");
+ settings.setValue(QStringLiteral("recentFiles"), recentFiles);
+ settings.sync();
+}
+
+void StartView::updateBroadcastClients() {
+ const auto time = tgfx::Clock::Now();
+ 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 = 0;
+ for (;;) {
+ auto broadcastMessage = broadcastListen->readData(len, addr, 0);
+ if (!broadcastMessage) {
+ break;
+ }
+ if (len > sizeof(tgfx::debug::BroadcastMessage)) {
+ continue;
+ }
+ tgfx::debug::BroadcastMessage bm = {};
+ memcpy(&bm, broadcastMessage, 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](const char* name) {
+ std::lock_guard lock(resolvLock);
+ auto iter = resolvMap.find(ip);
+ assert(iter != resolvMap.end());
+ 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->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->data.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->data.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..1a3e40e
--- /dev/null
+++ b/src/StartView.h
@@ -0,0 +1,185 @@
+/////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// 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:
+ 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(data.procName);
+ }
+
+ QString getAddress() const {
+ return QString::fromStdString(data.address);
+ }
+
+ uint16_t getPort() const {
+ return data.port;
+ }
+
+ bool getConnected() const {
+ return connected;
+ }
+
+ uint8_t getType() const {
+ return data.type;
+ }
+
+ void setConnected(bool isConnect) {
+ connected = isConnect;
+ Q_EMIT connectStateChange();
+ }
+
+ Q_SIGNAL void connectStateChange();
+
+ bool connected = false;
+ Data data = {};
+};
+
+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;
+ std::unique_ptr resolv = nullptr;
+ std::unique_ptr broadcastListen = nullptr;
+ 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..d9eef47
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,66 @@
+/////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// 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..caa64de
--- /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/[username]/Qt/[QtVersion]/macos/lib/cmake\""
+ ]
+ }
+ },
+ {
+ "name": "flatbuffers",
+ "cmake": {
+ "targets": [
+ "flatbuffers"
+ ],
+ "arguments": [
+ "-DFLATBUFFERS_BUILD_FLATLIB=ON",
+ "-DFLATBUFFERS_BUILD_CPP17=ON"
+ ]
+ }
+ }
+ ]
+}
\ No newline at end of file